Authored by yyq

seo list

@@ -2,14 +2,69 @@ @@ -2,14 +2,69 @@
2 2
3 const Router = require('koa-router'); 3 const Router = require('koa-router');
4 const _ = require('lodash'); 4 const _ = require('lodash');
  5 +const pager = require('../utils/pager');
5 6
6 let r = new Router(); 7 let r = new Router();
7 8
  9 +const TYPE_LIST = [
  10 + {name: '商品', type: 'skn', lt: 'SKN'},
  11 + {name: '逛', type: 'article', lt: 'ID'},
  12 + {name: '店铺', type: 'shop', lt: 'ShopId'},
  13 + {name: '链接', type: 'url', lt: 'URL'}
  14 +];
  15 +
8 const tdk = { 16 const tdk = {
9 17
10 // tdk 列表 18 // tdk 列表
11 index: async(ctx, next) => { 19 index: async(ctx, next) => {
12 - await ctx.render('action/seo_tdk', {title: 'TDK管理'}); 20 + let resData = {};
  21 + let type = ctx.query.type || 'skn',
  22 + page = parseInt(`0${ctx.query.page}`, 10) || 1,
  23 + limit = parseInt(`0${ctx.query.limit}`, 10) || 20;
  24 + let listKey = `tdk:${type}:links`,
  25 + startId = (page - 1) * limit,
  26 + typeObj = _.find(TYPE_LIST, {'type': type}) || {};
  27 + let hmget = [];
  28 +
  29 + await ctx.redis.multi([
  30 + ['llen', listKey],
  31 + ['lrange', listKey, startId, page * limit]
  32 + ]).execAsync().then(function(res) {
  33 + let total = res[0] || 1;
  34 +
  35 + resData.pager = pager(Math.floor((total - 1) / limit) + 1, ctx.query);
  36 +
  37 + _.forEach(res[1], value => {
  38 + hmget.push(['hmget', `tdk:${type}:${value}`, 'key', 'title', 'keywords', 'description']);
  39 + });
  40 + });
  41 +
  42 + await ctx.redis.multi(hmget).execAsync().then(function(res) {
  43 + let tdkList = [];
  44 +
  45 + _.forEach(res, value => {
  46 + tdkList.push({
  47 + id: ++startId,
  48 + typeName: typeObj.name,
  49 + typeLt: typeObj.lt,
  50 + key: value[0],
  51 + title: value[1],
  52 + keywords: value[2],
  53 + description: value[3]
  54 + });
  55 + });
  56 +
  57 + if (tdkList.length) {
  58 + resData.tdkList = tdkList;
  59 + }
  60 + });
  61 +
  62 + await ctx.render('action/seo_tdk', Object.assign(resData, {
  63 + title: 'TDK管理',
  64 + typeList: TYPE_LIST,
  65 + type: typeObj.type,
  66 + typeName: typeObj.name
  67 + }));
13 }, 68 },
14 69
15 // 添加tdk 70 // 添加tdk
@@ -38,12 +93,13 @@ const tdk = { @@ -38,12 +93,13 @@ const tdk = {
38 }); 93 });
39 94
40 ctx.response.body = result; 95 ctx.response.body = result;
41 - } 96 + },
  97 +
42 }; 98 };
43 99
44 r.get('/', tdk.index); 100 r.get('/', tdk.index);
45 r.get('/tdk', tdk.index); 101 r.get('/tdk', tdk.index);
46 -r.get('/tdk/add', tdk.add); 102 +r.post('/tdk/add', tdk.add);
47 103
48 104
49 module.exports = r; 105 module.exports = r;
  1 +/**
  2 + * 分页处理
  3 + * @author: yyq<yanqing.yang@yoho.cn>
  4 + * @date: 2017/6/1
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const _ = require('lodash');
  10 +
  11 +/**
  12 + * 拼接url
  13 + * @function joinUrl
  14 + * @param params 要拼接的 参数
  15 + * @returns {string}
  16 + */
  17 +const joinUrl = (params) => {
  18 + let dest = '?';
  19 +
  20 + _.forEach(params, function(value, key) {
  21 + dest += `${key}=${value}&`;
  22 + });
  23 +
  24 + return _.trim(dest, '&');
  25 +};
  26 +
  27 +/**
  28 + * 设置分页
  29 + * @setPager setPager
  30 + * @param total 总页数
  31 + * @param params 参数
  32 + * @returns {object}
  33 + */
  34 +module.exports = (total, params)=>{
  35 + let resData = {};
  36 + let defParams = _.cloneDeep(params);
  37 + let cutStatus, // 切割状态 1:去头 2:去尾 3:双切
  38 + i;
  39 + let pages = [];
  40 + let currentPage = parseInt(defParams.page || 1, 10); // 当前页
  41 +
  42 + // 小于两页直接退出
  43 + if (total < 2) {
  44 + return resData;
  45 + }
  46 +
  47 + for (i = currentPage - 2; i <= currentPage + 2; i++) {
  48 + if (i < 1) {
  49 + cutStatus = 1;
  50 + continue;
  51 + }
  52 +
  53 + if (i > total) {
  54 + cutStatus = cutStatus ? 3 : 2;
  55 + continue;
  56 + }
  57 +
  58 + pages.push({
  59 + url: joinUrl(Object.assign(defParams, {page: i})),
  60 + num: i,
  61 + cur: currentPage === i
  62 + });
  63 + }
  64 +
  65 + // 分页中部补全
  66 + let len = 5 - pages.length;
  67 + let list = [];
  68 +
  69 + if (cutStatus === 1) {
  70 + for (i = 1; i <= len; i++) {
  71 + let p = currentPage + i + 2;
  72 +
  73 + if (p > total) {
  74 + break;
  75 + }
  76 +
  77 + list.push({
  78 + url: joinUrl(Object.assign(defParams, {page: p})),
  79 + num: p
  80 + });
  81 + }
  82 + pages = _.concat(pages, list);
  83 + } else if (cutStatus === 2) {
  84 + for (i = 1; i <= len; i++) {
  85 + let p = currentPage - i - 2;
  86 +
  87 + if (p < 1) {
  88 + break;
  89 + }
  90 +
  91 + list.push({
  92 + url: joinUrl(Object.assign(defParams, {page: p})),
  93 + num: p
  94 + });
  95 + }
  96 + pages = _.concat(list, pages);
  97 + }
  98 +
  99 + // 分页头尾补全
  100 + let fnum = _.get(_.head(pages), 'num', 1),
  101 + lnum = _.get(_.last(pages), 'num', 1);
  102 +
  103 + if (fnum > 1) {
  104 + if (fnum > 2) {
  105 + pages = _.concat({
  106 + url: joinUrl(Object.assign(defParams, {page: 1})),
  107 + num: 1
  108 + }, {num: '...'}, pages);
  109 + } else {
  110 + pages = _.concat({
  111 + url: joinUrl(Object.assign(defParams, {page: 1})),
  112 + num: 1
  113 + }, pages);
  114 + }
  115 + }
  116 +
  117 + if (lnum < total) {
  118 + if (lnum < total - 1) {
  119 + pages = _.concat(pages, {num: '...'}, {
  120 + url: joinUrl(Object.assign(defParams, {page: total})),
  121 + num: total
  122 + });
  123 + } else {
  124 + pages = _.concat(pages, {
  125 + url: joinUrl(Object.assign(defParams, {page: total})),
  126 + num: total
  127 + });
  128 + }
  129 + }
  130 +
  131 + resData.pages = pages;
  132 +
  133 + // 上一页
  134 + if (currentPage > 1) {
  135 + resData.prePage = {url: joinUrl(Object.assign(defParams, {page: currentPage - 1}))};
  136 + }
  137 +
  138 + // 下一页
  139 + if (currentPage < total) {
  140 + resData.nextPage = {url: joinUrl(Object.assign(defParams, {page: currentPage + 1}))};
  141 + }
  142 +
  143 + return resData;
  144 +};
  1 +<style>
  2 + .seo-tdk-page ul {
  3 + padding: 0;
  4 + }
  5 +
  6 + .seo-tdk-page li {
  7 + list-style: none;
  8 + }
  9 +
  10 + .seo-tdk-page .query-form {
  11 + font-size: 0;
  12 + }
  13 +
  14 + .seo-tdk-page .query-form .btn-group {
  15 + margin-bottom: 0;
  16 + }
  17 +
  18 + .seo-tdk-page .query-form .dropdown-toggle {
  19 + border-top-right-radius: 0;
  20 + border-bottom-right-radius: 0;
  21 + }
  22 +
  23 + .seo-tdk-page .query-form .submit-btn {
  24 + height: 39px;
  25 + border-top-left-radius: 0;
  26 + border-bottom-left-radius: 0;
  27 + }
  28 +
  29 + .seo-tdk-page .query-key {
  30 + width: 220px;
  31 + height: 39px;
  32 + font-size: 14px;
  33 + vertical-align: middle;
  34 + outline: none;
  35 + }
  36 +
  37 + .seo-tdk-page .text-limit {
  38 + max-width: 78px;
  39 + display: inline-block;
  40 + vertical-align: top;
  41 + overflow: hidden;
  42 + text-overflow: ellipsis;
  43 + white-space: nowrap;
  44 + }
  45 +
  46 + .seo-tdk-page .pagination {
  47 + margin: 0;
  48 + }
  49 +
  50 + .seo-tdk-page #pop{
  51 + width:500px;
  52 + height: 434px;
  53 + background: #fff;
  54 + left: 0;
  55 + right: 0;
  56 + top: 0;
  57 + bottom: 0;
  58 + margin:auto;
  59 + }
  60 +
  61 + .seo-tdk-page #pop .control-label {
  62 + width: 60px;
  63 + text-align: right;
  64 + }
  65 +
  66 + .seo-tdk-page #pop li {
  67 + padding: 6px 0;
  68 + }
  69 +
  70 + .seo-tdk-page #pop select,
  71 + .seo-tdk-page #pop input {
  72 + height: 30px;
  73 + width: 200px;
  74 + }
  75 +
  76 + .seo-tdk-page #pop textarea {
  77 + height: 70px;
  78 + vertical-align: text-top;
  79 + border-color: #ccc;
  80 + resize: none;
  81 + }
  82 +
  83 + .seo-tdk-page #pop .full-w {
  84 + width: 390px;
  85 + }
  86 +
  87 + .seo-tdk-page #pop .controls {
  88 + display: inline-block;
  89 + }
  90 +</style>
  91 +
1 <div class="pageheader"> 92 <div class="pageheader">
2 <div class="media"> 93 <div class="media">
3 <div class="pageicon pull-left"> 94 <div class="pageicon pull-left">
@@ -6,8 +97,8 @@ @@ -6,8 +97,8 @@
6 <div class="media-body"> 97 <div class="media-body">
7 <ul class="breadcrumb"> 98 <ul class="breadcrumb">
8 <li><a href=""><i class="glyphicon glyphicon-home"></i></a></li> 99 <li><a href=""><i class="glyphicon glyphicon-home"></i></a></li>
9 - <li><a href="">Hotfix</a></li>  
10 - <li>{{type}}</li> 100 + <li><a href="/seo/tdk">{{title}}</a></li>
  101 + <li>{{typeName}}</li>
11 </ul> 102 </ul>
12 <h4>{{title}}</h4> 103 <h4>{{title}}</h4>
13 </div> 104 </div>
@@ -16,42 +107,125 @@ @@ -16,42 +107,125 @@
16 </div> 107 </div>
17 <!-- pageheader --> 108 <!-- pageheader -->
18 109
19 -<div class="contentpanel">  
20 - <div class="panel panel-primary-head">  
21 - <div class="panel-heading">  
22 - <div class="pull-right">  
23 - <a id="new-page" href="/hotfix/new/{{type}}" class="btn btn-success btn-rounded"><i class="glyphicon glyphicon-plus"></i> 新增Hotfix</a> 110 +<div class="contentpanel seo-tdk-page" style="padding-bottom:0;">
  111 + <div class="panel panel-default">
  112 + <div class="panel-body">
  113 + <label style="margin-right:20px;"><input type="checkbox" style="margin-right:5px;">全选</label>
  114 + <a data-toggle="modal" href="#pop" class="btn btn-default" style="margin-right:10px;">增加</a>
  115 + <button class="btn btn-default" type="submit">删除</button>
  116 +
  117 + <div class="input-append query-form pull-right">
  118 + <div class="btn-group">
  119 + <button class="btn dropdown-toggle" data-toggle="dropdown" data-type="{{type}}">
  120 + {{typeName}}
  121 + <span class="caret"></span>
  122 + </button>
  123 + <ul class="dropdown-menu">
  124 + {{# typeList}}
  125 + <li><a href="?type={{type}}">{{name}}</a></li>
  126 + {{/ typeList}}
  127 + </ul>
  128 + </div>
  129 + <input class="span2 query-key" type="text">
  130 + <button class="btn submit-btn" type="button">搜索</button>
24 </div> 131 </div>
25 - <h4 class="panel-title">{{type}} hotfix</h4>  
26 - <p>&nbsp;</p>  
27 </div> 132 </div>
28 - <!-- panel-heading -->  
29 -  
30 - <table id="table-hotfixs" class="table table-striped table-bordered responsive">  
31 - <thead class="">  
32 - <tr>  
33 - <th>ID</th>  
34 - <th>类型</th>  
35 - <th>标题</th>  
36 - <th>关键词</th>  
37 - <th>描述</th>  
38 - <th>操作</th>  
39 - </tr>  
40 - </thead>  
41 -  
42 - <tbody>  
43 - {{#each hotfixs}}  
44 - <tr>  
45 - <td>{{id}}</td>  
46 - <td>{{type}}</td>  
47 - <td>{{title}}</td>  
48 - <td>{{keywords}}</td>  
49 - <td>{{description}}</td>  
50 - <td>{{operate}}</td>  
51 - </tr>  
52 - {{/each}}  
53 - </tbody>  
54 - </table>  
55 </div> 133 </div>
56 - <!-- panel -->  
57 -</div>  
  134 +
  135 + <div class="panel panel-default">
  136 + <div class="panel-body">
  137 + <table id="table-tdk" class="table table-striped table-bordered responsive">
  138 + <thead>
  139 + <tr>
  140 + <th class="text-center" width="60">ID</th>
  141 + <th class="text-center" width="160">类型</th>
  142 + <th class="text-center">标题</th>
  143 + <th class="text-center">关键词</th>
  144 + <th class="text-center">描述</th>
  145 + <th class="text-center" width="120">操作</th>
  146 + </tr>
  147 + </thead>
  148 +
  149 + <tbody>
  150 + {{#each tdkList}}
  151 + <tr data-type="{{../type}}" data-key="{{key}}" data-title="{{title}}" data-keywords="{{keywords}}" data-description="{{description}}">
  152 + <td class="text-center"><input type="checkbox" style="margin-right:5px;">{{id}}</td>
  153 + <td class="text-center" title="{{typeName}}({{typeLt}}:{{key}})">{{typeName}}({{typeLt}}:<span class="text-limit">{{key}}</span>)</td>
  154 + <td>{{title}}</td>
  155 + <td>{{keywords}}</td>
  156 + <td>{{description}}</td>
  157 + <td class="text-center">
  158 + <span>编辑</span>
  159 + <span>删除</span>
  160 + </td>
  161 + </tr>
  162 + {{/each}}
  163 + </tbody>
  164 + </table>
  165 + {{# pager}}
  166 + <div class="text-right">
  167 + <ul class="pagination">
  168 + {{# prePage}}
  169 + <li><a href="{{url}}">上一页</a></li>
  170 + {{/ prePage}}
  171 + {{# pages}}
  172 + <li class="{{#unless url}}disabled {{/unless}}{{#if cur}}active{{/if}}"><a {{#if url}}href="{{url}}"{{^}}href="javascript:;"{{/if}}>{{num}}</a></li>
  173 + {{/ pages}}
  174 + {{# nextPage}}
  175 + <li><a href="{{url}}">下一页</a></li>
  176 + {{/ nextPage}}
  177 + </ul>
  178 + </div>
  179 + {{/ pager}}
  180 + </div>
  181 + </div>
  182 +
  183 + <div id="pop" class="modal fade in" style="display: none;">
  184 + <div class="modal-header">
  185 + <a class="close" data-dismiss="modal">×</a>
  186 + <h4>添加TDK</h4>
  187 + </div>
  188 + <div class="modal-body">
  189 + <ul>
  190 + <li>
  191 + <label class="control-label" for="select-type">类型:</label>
  192 + <div class="controls">
  193 + <select id="select-type">
  194 + {{# typeList}}
  195 + <option value="{{type}}">{{name}}</option>
  196 + {{/ typeList}}
  197 + </select>
  198 + </div>
  199 + </li>
  200 + <li>
  201 + <label class="control-label" for="input-key">SKN:</label>
  202 + <div class="controls">
  203 + <input type="text" id="input-key">
  204 + </div>
  205 + </li>
  206 + <li>
  207 + <label class="control-label" for="input-title">标题:</label>
  208 + <div class="controls">
  209 + <input type="text" id="input-title" class="full-w">
  210 + </div>
  211 + </li>
  212 + <li>
  213 + <label class="control-label" for="input-keywords">关键词:</label>
  214 + <div class="controls">
  215 + <input type="text" id="input-keywords" class="full-w">
  216 + </div>
  217 + </li>
  218 + <li>
  219 + <label class="control-label" for="input-description">描述:</label>
  220 + <div class="controls">
  221 + <textarea id="input-description" class="full-w"></textarea>
  222 + </div>
  223 + </li>
  224 + </ul>
  225 + </div>
  226 + <div class="modal-footer">
  227 + <a class="btn">关闭</a>
  228 + <a class="btn btn-primary">确定</a>
  229 + </div>
  230 + </div>
  231 +</div>