Authored by yyq

list

@@ -14,7 +14,7 @@ const TABLE_ACT_PRIZE_PRODUCT_USER = 'act_prize_product_user'; @@ -14,7 +14,7 @@ const TABLE_ACT_PRIZE_PRODUCT_USER = 'act_prize_product_user';
14 const MINUTE_TIMES = 60; 14 const MINUTE_TIMES = 60;
15 const PRODUCT_CACHE_TIMES = MINUTE_TIMES * 5; // 商品(列表&详情)缓存时间 15 const PRODUCT_CACHE_TIMES = MINUTE_TIMES * 5; // 商品(列表&详情)缓存时间
16 const RECENT_CODE_CACHE_TIME = MINUTE_TIMES; // 最近获取记录缓存时间 16 const RECENT_CODE_CACHE_TIME = MINUTE_TIMES; // 最近获取记录缓存时间
17 -const MAX_JOIN_TIMES = 2; // 最大活动参与次数 17 +const MAX_JOIN_TIMES = 5; // 最大活动参与次数
18 18
19 const userTimesCache = new MemoryCache(); 19 const userTimesCache = new MemoryCache();
20 20
@@ -62,29 +62,20 @@ module.exports = class extends global.yoho.BaseModel { @@ -62,29 +62,20 @@ module.exports = class extends global.yoho.BaseModel {
62 * @param extra 62 * @param extra
63 * @returns {*} 63 * @returns {*}
64 */ 64 */
65 - getList(status, page, extra = {}) {  
66 - const pageSize = 20; 65 + getList(page, extra = {}) {
  66 + const pageSize = 10;
67 const actId = parseInt(extra.actId, 10) || 0; 67 const actId = parseInt(extra.actId, 10) || 0;
68 68
69 - status = parseInt(status, 10);  
70 page = parseInt(page, 10) || 1; 69 page = parseInt(page, 10) || 1;
71 70
72 - if (_.isNaN(status)) {  
73 - status = '> 0';  
74 - } else if (status < 0) {  
75 - status = '>= 0';  
76 - } else {  
77 - status = `= ${status}`;  
78 - }  
79 -  
80 let limit = `${(page - 1) * pageSize},${page * pageSize}`; 71 let limit = `${(page - 1) * pageSize},${page * pageSize}`;
81 72
82 return mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT} 73 return mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT}
83 - where act_id = :actId and status ${status} 74 + where act_id = :actId and status > 0
84 order by sort desc limit ${limit}`, { 75 order by sort desc limit ${limit}`, {
85 actId 76 actId
86 }, { 77 }, {
87 - cache: extra.noCache ? 0 : PRODUCT_CACHE_TIMES 78 + cache: PRODUCT_CACHE_TIMES
88 }).then(handelResult); 79 }).then(handelResult);
89 } 80 }
90 81
@@ -23,14 +23,17 @@ const timeFormat = (time) => { @@ -23,14 +23,17 @@ const timeFormat = (time) => {
23 23
24 const zeroBuy = { 24 const zeroBuy = {
25 zeroBuyList(req, res, next) { 25 zeroBuyList(req, res, next) {
26 - req.ctx(ActivityModel).getZerobuyList().then(result => {  
27 - res.render('activity/zero-buy-list', { 26 + req.ctx(ActivityModel).getZerobuyList(req.query).then(result => {
  27 + res.render('activity/zero-buy-list', Object.assign(result, {
28 bodyClass: 'nav-md', 28 bodyClass: 'nav-md',
29 - list: result,  
30 module: 'admin', 29 module: 'admin',
31 - page: 'activity'  
32 - }); 30 + page: 'zero-buy'
  31 + }));
33 }).catch(next); 32 }).catch(next);
  33 + },
  34 + zeroBuyStatusEdit(req, res, next) {
  35 + req.ctx(ActivityModel).editZerobuyStatus(req.body.id, req.body.status)
  36 + .then(res.json).catch(next);
34 } 37 }
35 }; 38 };
36 39
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 6
7 const mysqlCli = global.yoho.utils.mysqlCli; 7 const mysqlCli = global.yoho.utils.mysqlCli;
8 const _ = require('lodash'); 8 const _ = require('lodash');
9 -const zerobuyModel = require('../../activity/models/zero-buy'); 9 +const pager = require('../../../utils/pager');
10 10
11 const TB_USER = 'user'; 11 const TB_USER = 'user';
12 const TB_ACTIVITY = 'activity'; 12 const TB_ACTIVITY = 'activity';
@@ -15,6 +15,11 @@ const TB_ACT_ARTICLE_IMG = 'act_article_img'; @@ -15,6 +15,11 @@ const TB_ACT_ARTICLE_IMG = 'act_article_img';
15 const TB_ACT_ARTICLE_GOOD = 'act_article_good'; 15 const TB_ACT_ARTICLE_GOOD = 'act_article_good';
16 const TB_ACT_ARTICLE_Y100 = 'act_article_y100'; 16 const TB_ACT_ARTICLE_Y100 = 'act_article_y100';
17 17
  18 +const TABLE_ACT_PRIZE_PRODUCT = 'act_prize_product';
  19 +
  20 +// const TABLE_ACT_PRIZE_PRODUCT_CONTENT = 'act_prize_product_content';
  21 +// const TABLE_ACT_PRIZE_PRODUCT_USER = 'act_prize_product_user';
  22 +
18 class AdminModel extends global.yoho.BaseModel { 23 class AdminModel extends global.yoho.BaseModel {
19 constructor(ctx) { 24 constructor(ctx) {
20 super(ctx); 25 super(ctx);
@@ -363,12 +368,111 @@ class AdminModel extends global.yoho.BaseModel { @@ -363,12 +368,111 @@ class AdminModel extends global.yoho.BaseModel {
363 ); 368 );
364 } 369 }
365 370
366 - getZerobuyList(page) {  
367 - return this.ctx.req.ctx(zerobuyModel).getList(-1, page, {  
368 - noCache: true  
369 - }).then(result => {  
370 - console.log(result.data);  
371 - return result.data; 371 + /**
  372 + * 获取0元购活动列表
  373 + * @param params
  374 + * @returns {*}
  375 + */
  376 + getZerobuyList(params) {
  377 + const actId = 0;
  378 + const pageSize = 10;
  379 + const page = parseInt(params.page, 10) || 1;
  380 +
  381 + return Promise.all([
  382 + mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT}
  383 + where act_id = :actId order by id desc
  384 + limit :start,:end`, {
  385 + actId,
  386 + start: (page - 1) * pageSize,
  387 + end: page * pageSize
  388 + }),
  389 + mysqlCli.query(`select count(*) as total from ${TABLE_ACT_PRIZE_PRODUCT}
  390 + where act_id = :actId`, {
  391 + actId
  392 + }),
  393 + ]).then(result => {
  394 + let resData = {};
  395 + let now = new Date().getTime() / 1000;
  396 +
  397 + const btns = [
  398 + {type: 'edit', color: 'info', name: '编辑'},
  399 + {type: 'switch', color: 'info', name: '开启/关闭'},
  400 + {type: 'export', color: 'info', name: '导出'},
  401 + {type: 'publish', color: 'danger', name: '开奖'}
  402 + ];
  403 +
  404 + resData.list = _.forEach(result[0], value => {
  405 + let statusStr = '';
  406 +
  407 + value.btns = _.cloneDeep(btns);
  408 +
  409 + switch (value.status) {
  410 + case 1:
  411 + statusStr = '开启';
  412 +
  413 + _.set(value, 'btns[1]', {
  414 + type: 'switch-close',
  415 + color: 'info',
  416 + name: '关闭'
  417 + });
  418 +
  419 + if (now > value.end_time) {
  420 + statusStr += ' 已结束';
  421 + } else if (now < value.start_time) {
  422 + statusStr += ' 未开始';
  423 + } else {
  424 + statusStr += ' 进行中';
  425 + }
  426 + break;
  427 + case 2:
  428 + statusStr = '已开奖';
  429 + delete value.btns[3];
  430 + delete value.btns[1];
  431 + break;
  432 + default:
  433 + statusStr = '关闭';
  434 +
  435 + _.set(value, 'btns[1]', {
  436 + type: 'switch-open',
  437 + color: 'info',
  438 + name: '开启'
  439 + });
  440 + break;
  441 + }
  442 +
  443 + value.statusStr = statusStr;
  444 +
  445 + return value;
  446 + });
  447 +
  448 + Object.assign(resData, pager(_.get(result, '[1].total', 1), params));
  449 +
  450 + return resData;
  451 + });
  452 + }
  453 +
  454 +
  455 + /**
  456 + * 获取0元购活动列表
  457 + * @param id 活动id
  458 + * @returns {*}
  459 + */
  460 + editZerobuyStatus(id, status) {
  461 + if (!id || !status) {
  462 + return {
  463 + code: 400,
  464 + message: '缺少参数'
  465 + };
  466 + }
  467 +
  468 + return mysqlCli.update(`update ${TABLE_ACT_PRIZE_PRODUCT} set status = :status where id = :id`, {
  469 + id,
  470 + status
  471 + }).then(() => {
  472 + return {
  473 + code: 200,
  474 + message: '操作成功'
  475 + };
372 }); 476 });
373 } 477 }
374 } 478 }
@@ -21,6 +21,7 @@ router.get('/activity/createY100Article', activity.createY100ArticlePage); @@ -21,6 +21,7 @@ router.get('/activity/createY100Article', activity.createY100ArticlePage);
21 router.get('/activity/y100Article', activity.actY100ArticleListPage); 21 router.get('/activity/y100Article', activity.actY100ArticleListPage);
22 router.get('/activity/upload_excel', activity.uploadExcelPage); 22 router.get('/activity/upload_excel', activity.uploadExcelPage);
23 router.get('/activity/zerobuy', activity.zeroBuyList); 23 router.get('/activity/zerobuy', activity.zeroBuyList);
  24 +router.post('/activity/zerobuy/editStatus', activity.zeroBuyStatusEdit);
24 25
25 // 用户管理[page] 26 // 用户管理[page]
26 router.get('/user/list', user.userListPage); 27 router.get('/user/list', user.userListPage);
@@ -4,40 +4,96 @@ @@ -4,40 +4,96 @@
4 <div class="x_panel"> 4 <div class="x_panel">
5 <table class="table table-bordered"> 5 <table class="table table-bordered">
6 <thead> 6 <thead>
7 - <tr class="headings">  
8 - <th width="7%" class="column-title text-center">活动ID</th>  
9 - <th width="12%" class="clolumn-title text-center">商品名称</th>  
10 - <th width="12%" class="column-title text-center">活动状态</th>  
11 - <th width="10%" class="column-title text-center">活动有效时间</th>  
12 - <th width="10%" class="column-title text-center">开奖需要人数</th>  
13 - <th width="10%" class="column-title text-center">活动排序</th>  
14 - <th width="20%" class="column-title text-center">封面图</th>  
15 - <th width="29%" class="column-title text-center">操作</th>  
16 - </tr> 7 + <tr class="headings">
  8 + <th width="7%" class="column-title text-center">活动ID</th>
  9 + <th width="12%" class="clolumn-title text-center">商品名称</th>
  10 + <th width="12%" class="column-title text-center">活动状态</th>
  11 + <th width="10%" class="column-title text-center">活动有效时间</th>
  12 + <th width="10%" class="column-title text-center">开奖需要人数</th>
  13 + <th width="10%" class="column-title text-center">活动排序</th>
  14 + <th width="20%" class="column-title text-center">封面图</th>
  15 + <th width="29%" class="column-title text-center">操作</th>
  16 + </tr>
17 </thead> 17 </thead>
18 18
19 - <tbody class="article-list"> 19 + <tbody >
20 {{#each list}} 20 {{#each list}}
21 - <tr class="even pointer"> 21 + <tr>
22 <td class="text-center">{{id}}</td> 22 <td class="text-center">{{id}}</td>
23 <td class="text-center">{{name}}</td> 23 <td class="text-center">{{name}}</td>
24 - <td class="text-center">{{repeatLimit}}</td>  
25 - <td class="text-center">{{voteLimit}}</td> 24 + <td class="text-center">{{statusStr}}</td>
  25 + <td class="text-center">{{start_time}}-{{end_time}}</td>
26 <td class="text-center">{{limit}}</td> 26 <td class="text-center">{{limit}}</td>
27 <td class="text-center">{{sort}}</td> 27 <td class="text-center">{{sort}}</td>
28 <td class="text-center"> 28 <td class="text-center">
29 <image src="{{cover_img}}"> 29 <image src="{{cover_img}}">
30 </td> 30 </td>
31 - <td class="text-center" data-id="{{id}}"> 31 + <td class="text-center">
32 {{# btns}} 32 {{# btns}}
33 - <button class="btn btn-danger btn-{{type}}">{{name}}</button> 33 + <button class="btn btn-{{color}} btn-{{type}}" data-id="{{../id}}">{{name}}</button>
34 {{/ btns}} 34 {{/ btns}}
35 </td> 35 </td>
36 </tr> 36 </tr>
37 {{/each}} 37 {{/each}}
38 </tbody> 38 </tbody>
39 </table> 39 </table>
40 - <div class="table-pagination article-pagination pull-right"></div> 40 + <div class="table-pagination article-pagination pull-right">
  41 + <nav aria-label="...">
  42 + <ul class="pagination">
  43 + {{# prePage}}
  44 + <li class="page-item">
  45 + <a class="page-link" href="{{url}}">上一页</a>
  46 + </li>
  47 + {{/ prePage}}
  48 + {{# pages}}
  49 + <li class="page-item{{#if cur}} active{{/if}}">
  50 + {{#if url}}
  51 + <a class="page-link" href="{{url}}">{{num}}</a>
  52 + {{^}}
  53 + <a class="page-link" href="javascript:;">{{num}}</a>
  54 + {{/if}}
  55 + </li>
  56 + {{/ pages}}
  57 + {{# nextLimit}}
  58 + <li class="page-item">
  59 + <a class="page-link" href="{{url}}">下一页</a>
  60 + </li>
  61 + {{/ nextLimit}}
  62 + </ul>
  63 + </nav>
  64 + </div>
  65 + </div>
  66 + </div>
  67 +</div>
  68 +
  69 +
  70 +
  71 +<div id="alert-modal" class="modal fade in" style="display: none; ">
  72 + <div class="modal-dialog">
  73 + <div class="modal-content">
  74 + <div class="modal-body">
  75 + <p class="modal-text"></p>
  76 + </div>
  77 + <div class="modal-footer">
  78 + <a class="btn btn-success" data-dismiss="modal">好的</a>
  79 + </div>
  80 + </div>
  81 + </div>
  82 +</div>
  83 +<div id="confirm-modal" class="modal fade in" style="display: none; ">
  84 + <div class="modal-dialog">
  85 + <div class="modal-content">
  86 + <div class="modal-header">
  87 + <a class="close" data-dismiss="modal">×</a>
  88 + <h3>确认开奖</h3>
  89 + </div>
  90 + <div class="modal-body">
  91 + <p>确认将此活动设为开奖状态?开奖后无法进行状态变更</p>
  92 + </div>
  93 + <div class="modal-footer">
  94 + <a class="btn btn-success" data-dismiss="modal">取消</a>
  95 + <a class="btn btn-success sure-publish-btn">确定</a>
  96 + </div>
41 </div> 97 </div>
42 </div> 98 </div>
43 </div> 99 </div>
1 require('admin/activity.page.css'); 1 require('admin/activity.page.css');
  2 +
  3 +const $alert = $('#alert-modal');
  4 +const $confirm = $('#confirm-modal');
  5 +
  6 +// $('#alert-modal').modal('show')
  7 +
  8 +function bindEvent() {
  9 + const editStatusFn = function(id, status) {
  10 + return $.ajax({
  11 + method: 'post',
  12 + url: '/admin/activity/zerobuy/editStatus',
  13 + data: {
  14 + id,
  15 + status
  16 + }
  17 + });
  18 + };
  19 +
  20 + const editFn = function() {
  21 + window.open('/admin/activity/zerobuy/edit?id=' +
  22 + $(this).parent().data('id'), '_blank');
  23 + };
  24 +
  25 + const switchFn = function() {
  26 + let $this = $(this);
  27 + let status = $this.hasClass('btn-switch-open') ? 1 : 0;
  28 +
  29 + editStatusFn($this.data('id'), status).then(res => {
  30 + if (res.code === 200) {
  31 + location.reload();
  32 + } else {
  33 + $alert.find('.modal-text').text(res.message);
  34 + $alert.modal('show');
  35 + }
  36 + });
  37 + };
  38 +
  39 + const exportFn = function() {
  40 +
  41 + };
  42 +
  43 + let publishId;
  44 + const publishFn = function() {
  45 + editStatusFn(publishId, 2).then(res => {
  46 + if (res.code === 200) {
  47 + location.reload();
  48 + } else {
  49 + $alert.find('.modal-text').text(res.message);
  50 + $alert.modal('show');
  51 + }
  52 + });
  53 + };
  54 +
  55 +
  56 + $('.btn-edit').on('click', editFn);
  57 + $('.btn-switch-open').on('click', switchFn);
  58 + $('.btn-switch-close').on('click', switchFn);
  59 + $('.btn-export').on('click', exportFn);
  60 + $('.btn-publish').on('click', function() {
  61 + publishId = $(this).data('id');
  62 + $confirm.modal('show');
  63 + });
  64 + $('.sure-publish-btn').on('click', publishFn);
  65 +}
  66 +
  67 +
  68 +bindEvent();
  1 +const _ = require('lodash');
  2 +
  3 +/**
  4 + * 处理用于筛选的 URL , 拼接 URL 参数
  5 + * @param originParam 当前 URL 中的参数
  6 + * @param newParam 要拼接的 参数
  7 + * @returns {string}
  8 + */
  9 +const handleFilterUrl = (originParam, newParam, delParam) => {
  10 + let dest = '?';
  11 + let tempOriginParam = {};
  12 +
  13 + delParam = delParam || {};
  14 +
  15 + tempOriginParam = Object.assign(tempOriginParam, originParam, newParam);
  16 + delete tempOriginParam.uid;
  17 +
  18 + _.forEach(tempOriginParam, info => {
  19 + if (!delParam[info.key] && info.value) {
  20 + // NOTE: 这里会对 query 进行编码,因为 query 有可以能是中文
  21 + if (info.key === 'query') {
  22 + info.value = encodeURIComponent(info.value);
  23 + }
  24 + dest += `${info.key}=${info.value}&`;
  25 + }
  26 + });
  27 +
  28 + return _.trim(dest, '&');
  29 +};
  30 +
  31 +module.exports = (total, params = {}, baseUrl = '') => {
  32 + const curPage = _.parseInt(params.page || 1);
  33 + const pageSize = _.parseInt(params.limit) || 10;
  34 + const totalPage = _.ceil(total / pageSize);
  35 + const tip = {
  36 + start: (curPage - 1) * pageSize + 1,
  37 + end: curPage * pageSize > total ? total : curPage * pageSize,
  38 + total
  39 + };
  40 + let prePage, nextPage;
  41 + const pageUrl = handleFilterUrl(baseUrl, params, {page: '${page}'});
  42 +
  43 + if (curPage > 1) {
  44 + prePage = {
  45 + url: pageUrl.replace('${page}', curPage - 1)
  46 + };
  47 + }
  48 + if (curPage < totalPage) {
  49 + nextPage = {
  50 + url: pageUrl.replace('${page}', curPage + 1)
  51 + };
  52 + }
  53 + let pages = [];
  54 + let pagesNums = totalPage > 5 ? 5 : totalPage;
  55 + let offsetPage = curPage + _.round(0 - pagesNums / 2); // 遍历页码左侧数字
  56 +
  57 + if (offsetPage <= 0) {
  58 + offsetPage = 1;
  59 + } else if (offsetPage + pagesNums > totalPage) {
  60 + offsetPage = totalPage - pagesNums + 1;
  61 + }
  62 + const preLimit = offsetPage - 1; // 页码1和遍历页码左侧的距离
  63 + const nextLimit = totalPage - (offsetPage - 1 + pagesNums); // 总页码和遍历页码右侧的距离
  64 +
  65 + if (preLimit === 1) { // 如果左侧间距为1则显示这个页码
  66 + pagesNums++;
  67 + offsetPage--;
  68 + }
  69 + if (nextLimit === 1) { // 如果右侧间距为1则显示这个页码
  70 + pagesNums++;
  71 + }
  72 +
  73 + if (preLimit >= 2) {
  74 + pages = _.concat(pages, {
  75 + num: 1,
  76 + url: pageUrl.replace('${page}', 1),
  77 + }, {
  78 + num: '...'
  79 + });
  80 + }
  81 + if (offsetPage <= 0) {
  82 + offsetPage = 1;
  83 + } else if (offsetPage + pagesNums > totalPage) {
  84 + offsetPage = totalPage - pagesNums + 1;
  85 + }
  86 +
  87 + for (let i = 0; i < pagesNums; i++) {
  88 + pages.push({
  89 + num: offsetPage,
  90 + url: pageUrl.replace('${page}', offsetPage),
  91 + cur: curPage === offsetPage
  92 + });
  93 + offsetPage++;
  94 + }
  95 + if (nextLimit >= 2) {
  96 + pages = _.concat(pages, {
  97 + num: '...'
  98 + }, {
  99 + num: totalPage,
  100 + url: pageUrl.replace('${page}', totalPage),
  101 + });
  102 + }
  103 + return {tip, prePage, nextPage, pages};
  104 +};