Authored by 周少峰

Merge branch 'feature/keywords'

... ... @@ -84,6 +84,10 @@ const defaultDegrades = [
path: '/pc/open/bughd',
name: '【开关】打开JS错误收集功能'
},
{
path: '/pc/ci/tdk',
name: '【开关】打开TDK'
},
//wap
{
path: '/wap/plustar/removeCollect',
... ... @@ -140,6 +144,10 @@ const defaultDegrades = [
{
path: '/wap/open/bughd',
name: '【开关】打开JS错误收集功能'
},
{
path: '/wap/ci/tdk',
name: '【开关】打开TDK'
}
];
... ...
/**
*
* @author: shijian<jian.shi@yoho.cn>
* @date: 17/5/31
*/
'use strict';
const Router = require('koa-router');
const moment = require('moment');
const pager = require('../utils/pager');
const r = new Router();
const multiAsync=(multi)=>{
return multi.execAsync().then(function(res) {
return res;
});
}
//查看
const getDataList = async(ctx, key, page, limit)=>{
let multi = ctx.redis.multi();
let start = (page-1)*limit;
let end = (page)*limit-1;
multi.lrange(key, start, end).LLEN(key);
return multiAsync(multi);
}
const getDataValues = async(ctx, arr)=>{
let multi = ctx.redis.multi();
arr.forEach(item=>{
multi.get(item);
});
return multiAsync(multi);
}
//删除
const delDataValues = async(ctx, arr)=>{
let multi = ctx.redis.multi();
arr.forEach(item=>{
let key = `keywords_mana:${item}`;
multi.del(key).lrem('keywords_mana_list', 1, key);
});
return multiAsync(multi);
}
//添加
const addDataValues = async(ctx, value)=>{
let multi = ctx.redis.multi();
let key=`keywords_mana:${value}`;
multi.set(key, value);
multi.lrem('keywords_mana_list', 1, key).lpush('keywords_mana_list', key);
return multiAsync(multi);
}
//编辑
const editDataValues = async(ctx, value, oldValue)=>{
try{
await delDataValues(ctx, [oldValue]);
await addDataValues(ctx, value);
return true;
}catch(e){
return false;
}
}
//搜索
const searchDataValues = async(ctx, value)=>{
let multi = ctx.redis.multi();
let key=`keywords_mana:*${value}*`;
multi.KEYS(key);
return multiAsync(multi);
}
r.get('/', async(ctx) => {
let resData = {};
let q = ctx.request.query,
page = parseInt(`0${ctx.query.page}`, 10) || 1,
limit = parseInt(`0${ctx.query.limit}`, 10) || 10;
let r = await getDataList(ctx, "keywords_mana_list", page, limit);
let result = [];
if(r[0].length)result = await getDataValues(ctx, r[0]);
let total = r[1];
resData.pager = pager(Math.floor((total - 1) / limit) + 1, ctx.query);
resData.tabs = result;
await ctx.render('action/keywords', resData);
});
r.get('/delKeywords', async(ctx) => {
let q = ctx.request.query;
let keys = JSON.parse(q['keywords']);
try{
await delDataValues(ctx, keys);
ctx.body = {
code: 200,
message: 'success',
data: ''
};
}catch(e){
ctx.body = {
code: 500,
message: 'fail',
data: ''
};
}
});
r.get('/addKeywords', async(ctx) => {
let q = ctx.request.query;
let key = q['keywords'];
try{
await addDataValues(ctx, key);
ctx.body = {
code: 200,
message: 'success',
data: ''
};
}catch(e){
ctx.body = {
code: 500,
message: 'fail',
data: ''
};
}
});
r.get('/editKeywords', async(ctx) => {
let q = ctx.request.query;
let key = JSON.parse(q['keywords']);
try{
await editDataValues(ctx, key[0], key[1]);
ctx.body = {
code: 200,
message: 'success',
data: ''
};
}catch(e){
ctx.body = {
code: 500,
message: 'fail',
data: ''
};
}
});
r.get('/searchKeywords', async(ctx) => {
let q = ctx.request.query;
let key = q['association'];
let r = await searchDataValues(ctx, key);
let result = [];
if(r[0].length)result = await getDataValues(ctx, r[0]);
ctx.body = {
code: 200,
message: 'success',
data: result,
total:1
};
});
module.exports = r;
... ...
'use strict';
const Router = require('koa-router');
const _ = require('lodash');
const md5 = require('md5');
const pager = require('../utils/pager');
let r = new Router();
const TYPE_LIST = [
{name: '商品', type: 'skn', lt: 'SKN'},
{name: '逛', type: 'article', lt: 'ID'},
{name: '店铺', type: 'shop', lt: 'ShopId'},
{name: '链接', type: 'url', lt: 'URL'}
];
const tdk = {
_getDeaultList: async(ctx, next) => {
},
// tdk 列表
index: async(ctx, next) => {
let resData = {};
let type = ctx.query.type || 'skn',
page = parseInt(`0${ctx.query.page}`, 10) || 1,
limit = parseInt(`0${ctx.query.limit}`, 10) || 20,
query = ctx.query.query;
let listKey = `tdk:${type}:links`,
startId = (page - 1) * limit,
typeObj = _.find(TYPE_LIST, {'type': type}) || {};
let hmget = [];
if (query) {
query = _.trim(query);
resData.query = query;
query = type === 'url' ? md5(query) : query;
await ctx.redis.multi([
['exists', `tdk:${type}:${query}`],
['hmget', `tdk:${type}:${query}`, 'key', 'title', 'keywords', 'description']
]).execAsync().then(function(res) {
if (!res[0]) {
return;
}
resData.tdkList = [{
id: 1,
typeName: typeObj.name,
typeLt: typeObj.lt,
key: res[1][0],
title: res[1][1],
keywords: res[1][2],
description: res[1][3]
}];
});
} else {
await ctx.redis.multi([
['llen', listKey],
['lrange', listKey, startId, page * limit]
]).execAsync().then(function(res) {
let total = res[0] || 1;
resData.pager = pager(Math.floor((total - 1) / limit) + 1, ctx.query);
_.forEach(res[1], value => {
hmget.push(['hmget', `tdk:${type}:${value}`, 'key', 'title', 'keywords', 'description']);
});
});
await ctx.redis.multi(hmget).execAsync().then(function(res) {
let tdkList = [];
_.forEach(res, value => {
tdkList.push({
id: ++startId,
typeName: typeObj.name,
typeLt: typeObj.lt,
key: value[0],
title: value[1],
keywords: value[2],
description: value[3]
});
});
if (tdkList.length) {
resData.tdkList = tdkList;
}
});
}
await ctx.render('action/seo_tdk', Object.assign(resData, {
title: 'TDK管理',
typeList: TYPE_LIST,
type: typeObj.type,
typeName: typeObj.name
}));
},
// 添加tdk
add: async(ctx, next) => {
let result = {code: 500, message: '非法参数'};
// skn, article, shop, url
let type = ctx.request.body.type,
key = ctx.request.body.key,
title = ctx.request.body.title || '',
keywords = ctx.request.body.keywords || '',
description = ctx.request.body.description || '';
// type 不合法, 返回错误
if (!_.find(TYPE_LIST, ['type', type])) {
return ctx.response.body = result;
}
let hashKey = type === 'url' ? md5(key) : key;
await ctx.redis.multi([
['lpushx', `tdk:${type}:links`, hashKey],
['hmset', `tdk:${type}:${hashKey}`, 'key', key, 'title', title, 'keywords', keywords,
'description', description, 'modify_time', Date.parse(new Date()) / 1000]
]).execAsync().then(function(res) {
if (res[1]) {
if (!res[0]) {
ctx.redis.lpush(`tdk:${type}:links`, hashKey);
}
Object.assign(result, {code:200, message: 'success'});
} else {
Object.assign(result, {code:400, message: 'failed'});
}
});
ctx.response.body = result;
},
edit: async(ctx, next) => {
let result = {code: 500, message: '非法参数'};
// skn, article, shop, url
let type = ctx.request.body.type,
key = ctx.request.body.key,
title = ctx.request.body.title || '',
keywords = ctx.request.body.keywords || '',
description = ctx.request.body.description || '';
// type 不合法, 返回错误
if (!_.find(TYPE_LIST, ['type', type])) {
return ctx.response.body = result;
}
let hashKey = type === 'url' ? md5(key) : key;
await ctx.redis.multi([
['hmset', `tdk:${type}:${hashKey}`, 'title', title, 'keywords', keywords,
'description', description, 'modify_time', Date.parse(new Date()) / 1000]
]).execAsync().then(function(res) {
if (res[0]) {
Object.assign(result, {code:200, message: 'success'});
} else {
Object.assign(result, {code:400, message: 'failed'});
}
});
ctx.response.body = result;
},
delete: async(ctx, next) => {
let result = {code: 500, message: '非法参数'};
let list = ctx.request.body.list;
let delArr = ['del']
let multiArr = [];
// list 不合法, 返回错误
if (!list || !list.length) {
return ctx.response.body = result;
}
_.forEach(list, value => {
let hashKey = value.type === 'url' ? md5(value.key) : value.key;
if (!_.find(TYPE_LIST, ['type', value.type])) {
return;
}
multiArr.push(['lrem', `tdk:${value.type}:links`, 1, hashKey]);
delArr.push(`tdk:${value.type}:${hashKey}`)
});
if (multiArr.length) {
multiArr.push(delArr);
await ctx.redis.multi(multiArr).execAsync().then(function(res) {
Object.assign(result, {code:200, message: 'success'});
});
}
ctx.response.body = result;
}
};
r.get('/', tdk.index);
r.get('/tdk', tdk.index);
r.post('/tdk/add', tdk.add);
r.post('/tdk/edit', tdk.edit);
r.post('/tdk/delete', tdk.delete);
module.exports = r;
... ...
... ... @@ -12,6 +12,7 @@ const helpers = require('../../lib/helpers');
const routers = require('./routers');
const collectData = require('./actions/collect_data');
const checkcode = require('./actions/checkcode');
const redis = require('../../lib/redis');
const app = new Koa();
... ... @@ -38,6 +39,8 @@ checkcode.check();
collectData.collect();
app.use(async(ctx, next) => {
ctx.redis = redis.client;
ctx.locals = {
title: 'Yoho Node.js 持续集成平台'
};
... ...
... ... @@ -16,9 +16,11 @@ const productCache = require('./actions/product_cache');
const apiCache = require('./actions/api_cache');
const degrade = require('./actions/degrade');
const deploy = require('./actions/deploy');
const keywords = require('./actions/keywords');
const api = require('./actions/api');
const abuseProtection = require('./actions/abuse_protection');
const crawler = require('./actions/crawler');
const seo = require('./actions/seo');
const checkcode = require('./actions/checkcode').router;
const noAuth = new Router();
const base = new Router();
... ... @@ -52,6 +54,7 @@ module.exports = function(app) {
base.use('/api_cache', apiCache.routes(), apiCache.allowedMethods());
base.use('/degrade', degrade.routes(), degrade.allowedMethods());
base.use('/deploys', deploy.routes(), deploy.allowedMethods());
base.use('/keywords', keywords.routes(), keywords.allowedMethods());
const white = crawler('/crawler/ip_whitelists', '/crawler/ua_whitelists', '白名单', false);
const black = crawler('/crawler/ip_blacklists', '/crawler/ua_blacklists', '黑名单', true);
... ... @@ -60,6 +63,7 @@ module.exports = function(app) {
base.use('/crawler_black', black.routes(), black.allowedMethods());
base.use('/abuse_protection', abuseProtection.routes(), degrade.allowedMethods());
base.use('/seo', seo.routes(), seo.allowedMethods());
base.use('', index.routes(), index.allowedMethods());
... ...
/**
* 分页处理
* @author: yyq<yanqing.yang@yoho.cn>
* @date: 2017/6/1
*/
'use strict';
const _ = require('lodash');
/**
* 拼接url
* @function joinUrl
* @param params 要拼接的 参数
* @returns {string}
*/
const joinUrl = (params) => {
let dest = '?';
_.forEach(params, function(value, key) {
dest += `${key}=${value}&`;
});
return _.trim(dest, '&');
};
/**
* 设置分页
* @setPager setPager
* @param total 总页数
* @param params 参数
* @returns {object}
*/
module.exports = (total, params)=>{
let resData = {};
let defParams = _.cloneDeep(params);
let cutStatus, // 切割状态 1:去头 2:去尾 3:双切
i;
let pages = [];
let currentPage = parseInt(defParams.page || 1, 10); // 当前页
// 小于两页直接退出
if (total < 2) {
return resData;
}
for (i = currentPage - 2; i <= currentPage + 2; i++) {
if (i < 1) {
cutStatus = 1;
continue;
}
if (i > total) {
cutStatus = cutStatus ? 3 : 2;
continue;
}
pages.push({
url: joinUrl(Object.assign(defParams, {page: i})),
num: i,
cur: currentPage === i
});
}
// 分页中部补全
let len = 5 - pages.length;
let list = [];
if (cutStatus === 1) {
for (i = 1; i <= len; i++) {
let p = currentPage + i + 2;
if (p > total) {
break;
}
list.push({
url: joinUrl(Object.assign(defParams, {page: p})),
num: p
});
}
pages = _.concat(pages, list);
} else if (cutStatus === 2) {
for (i = 1; i <= len; i++) {
let p = currentPage - i - 2;
if (p < 1) {
break;
}
list.push({
url: joinUrl(Object.assign(defParams, {page: p})),
num: p
});
}
pages = _.concat(list, pages);
}
// 分页头尾补全
let fnum = _.get(_.head(pages), 'num', 1),
lnum = _.get(_.last(pages), 'num', 1);
if (fnum > 1) {
if (fnum > 2) {
pages = _.concat({
url: joinUrl(Object.assign(defParams, {page: 1})),
num: 1
}, {num: '...'}, pages);
} else {
pages = _.concat({
url: joinUrl(Object.assign(defParams, {page: 1})),
num: 1
}, pages);
}
}
if (lnum < total) {
if (lnum < total - 1) {
pages = _.concat(pages, {num: '...'}, {
url: joinUrl(Object.assign(defParams, {page: total})),
num: total
});
} else {
pages = _.concat(pages, {
url: joinUrl(Object.assign(defParams, {page: total})),
num: total
});
}
}
resData.pages = pages;
// 上一页
if (currentPage > 1) {
resData.prePage = {url: joinUrl(Object.assign(defParams, {page: currentPage - 1}))};
}
// 下一页
if (currentPage < total) {
resData.nextPage = {url: joinUrl(Object.assign(defParams, {page: currentPage + 1}))};
}
return resData;
};
... ...
<div class="pageheader">
<div class="media">
<div class="pageicon pull-left">
<i class="fa fa-th-list"></i>
</div>
<div class="media-body">
<ul class="breadcrumb">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
<li><a href="/keywords">关键词管理</a></li>
</ul>
<h4>关键词管理</h4>
</div>
</div>
<!-- media -->
</div>
<div class="contentpanel project-index-page" style="padding-bottom:0;">
<div class="panel panel-default">
<div class="panel-body">
<label style="margin-right:20px;"><input id="allSelected" type="checkbox" style="margin-right:5px;">全选</label>
<a data-toggle="modal" href="#pop" class="btn btn-default" style="margin-right:10px;">增加</a>
<a class="btn btn-default deleteAll">删除</a>
<button class="btn btn-default search" style="float:right;">搜索</button>
<div class="col-sm-3" style="float:right;">
<input type="text" class="form-control" id="firstname" placeholder="请输入关键词">
</div>
</div>
</div>
</div>
<div id="pop" class="modal fade in" style="display: none; ">
<div class="modal-header">
<a class="close" data-dismiss="modal">×</a>
<h3>添加关键词</h3>
</div>
<div class="modal-body">
<input type="text" style="height:50px;width:400px;" id="addDatas">
</div>
<div class="modal-footer">
<a class="btn btn-success addKeywords">确定</a>
</div>
</div>
<div class="contentpanel project-index-page" style="padding-top:0;">
<div class="panel panel-default">
<div class="panel-body">
<table id="table-oper-log" class="table table-striped table-bordered building-table">
<thead><tr><th>ID</th><th>关键词</th><th>操作</th></tr></thead>
{{# tabs}}
<tr><td><label><input type="checkbox">{{@index}}</label></td><td><input class="values" value="{{.}}" disabled></td><td><button class="btn btn-default nostyle edit">编辑</button><button class="btn btn-default nostyle delete">删除</button></td></tr>
{{/ tabs}}
</table>
</div>
</div>
</div>
{{# pager}}
<div class="text-right">
<ul class="pagination">
{{# prePage}}
<li><a href="{{url}}">上一页</a></li>
{{/ prePage}}
{{# pages}}
<li class="{{#unless url}}disabled {{/unless}}{{#if cur}}active{{/if}}"><a {{#if url}}href="{{url}}"{{^}}href="javascript:;"{{/if}}>{{num}}</a></li>
{{/ pages}}
{{# nextPage}}
<li><a href="{{url}}">下一页</a></li>
{{/ nextPage}}
</ul>
</div>
{{/ pager}}
<style>
.nostyle{
background: transparent;
}
input[type=checkbox]{
vertical-align: sub;
margin-right: 5px;
}
input[disabled]{
background: transparent;
border: none;
}
#pop{
width:500px;
height:250px;
background: #fff;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin:auto;
}
.text-right{
float: right;
margin-right: 20px;
margin-top:0;
}
.pages{
cursor: pointer;
}
</style>
<script>
var currentPage=1,pageCount=10,pageTotal;
var tabHead='<thead><tr><th>ID</th><th>关键词</th><th>操作</th></tr></thead>';
//展示列表
/**$(document).on('ready pjax:success', function() {
$.get('/keywords/getKeywords', {page:currentPage, limit:pageCount}, function(data){
var html='';
let datas=data.data;
if(datas){
datas.forEach(function(item, index){
html+=tableHtml(item, index);
});
$('#table-oper-log').html(tabHead+html);
pageTotal=Math.ceil(data.total/pageCount);
}
});
});
$('.privious').on('click', function() {
if(currentPage==1)return;
$.get('/keywords/getKeywords?',{currentPage:--currentPage, pageCount:pageCount}, function(data){
var html='';
let datas=data.data;
if(datas){
datas.forEach(function(item, index){
html+=tableHtml(item, index);
});
$('#table-oper-log').html(tabHead+html);
}
});
});
$('.next').on('click', function() {
if(currentPage>=pageTotal)return;
$.get('/keywords/getKeywords?',{currentPage:++currentPage, pageCount:pageCount}, function(data){
var html='';
let datas=data.data;
if(datas){
datas.forEach(function(item, index){
html+=tableHtml(item, index);
});
$('#table-oper-log').html(tabHead+html);
}
});
});**/
//删除
$(document).on('click', '.delete', function(){
var val=$($(this).parents('tr').find('input')[1]).val();
var parent=$(this).parents('tr');
$.get('/keywords/delKeywords', {keywords:JSON.stringify([val])}, function(data){
if(data.code===200){
parent.replaceWith('');
}
});
});
//编辑
$(document).on('click', '.edit', function(){
let input=$($(this).parents('tr').find('input')[1]);
input.attr('disabled',false);
let oldValue=input.val();
input.attr('oldValue',oldValue);
input.on('blur', function(){
var val=input.val();
$.get('/keywords/editKeywords', {keywords:JSON.stringify([val, oldValue])}, function(data){
if(data.code===200){
var isSame=false;
$('table .values').each(function(){
if($(this)!=input){
if($(this).val()==input.val())isSame=true;
}
});
if(isSame) location.reload();
else input.attr('disabled',true);
}
});
});
});
//添加
$(document).on('click', '.addKeywords', function(){
var val=$('#addDatas').val();
$.get('/keywords/addKeywords', {keywords:val}, function(data){
if(data.code===200){
location.reload();
}
});
});
//全选
$('#allSelected').on('click', function(){
var check=false;
if($(this).attr('checked')){
check=true;
}
$("table :checkbox").attr("checked", check);
});
$('.deleteAll').on('click',function(){
let arr=[];
$("table :checkbox:checked").each(function(item){
arr.push($($(this).parents('tr').find('input')[1]).val());
});
if(!arr.length)return;
$.get('/keywords/delKeywords', {keywords:JSON.stringify(arr)}, function(data){
if(data.code===200){
location.reload();
}
});
});
function tableHtml(item, index){
return '<tr><td><label><input type="checkbox">'+index+'</label></td><td><input class="values" value="'+item+'" disabled></td><td><button class="btn btn-default nostyle edit">编辑</button><button class="btn btn-default nostyle delete">删除</button></td></tr>';
}
//搜索
$('.search').on('click',function(){
if(!$('#firstname').val())return;
$.get('/keywords/searchKeywords', {association:$('#firstname').val()}, function(data){
var html='';
let datas=data.data;
if(data.code===200){
datas.forEach(function(item, index){
html+=tableHtml(item, index);
});
$('#table-oper-log').html(tabHead+html);
$('.text-right').hide();
}
});
});
</script>
\ No newline at end of file
... ...
<style>
.seo-tdk-page ul {
padding: 0;
}
.seo-tdk-page li {
list-style: none;
}
.seo-tdk-page .query-form {
font-size: 0;
}
.seo-tdk-page .query-form .btn-group {
margin-bottom: 0;
}
.seo-tdk-page .query-form .dropdown-toggle {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.seo-tdk-page .query-form .query-submit-btn {
height: 39px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.seo-tdk-page .query-key {
width: 300px;
height: 39px;
font-size: 14px;
vertical-align: middle;
outline: none;
}
.seo-tdk-page .text-limit {
max-width: 78px;
display: inline-block;
vertical-align: top;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.seo-tdk-page .pagination {
margin: 0;
}
.seo-tdk-page #table-tdk th,
.seo-tdk-page #table-tdk td {
height: 36px;
}
.seo-tdk-page #pop{
width:500px;
height: 434px;
background: #fff;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin:auto;
}
.seo-tdk-page #pop .cover-title {
position: absolute;
background: #fff;
margin-top: -34px;
}
.seo-tdk-page #pop .control-label {
width: 60px;
text-align: right;
}
.seo-tdk-page #pop li {
padding: 6px 0;
}
.seo-tdk-page #pop select,
.seo-tdk-page #pop input {
height: 30px;
width: 200px;
}
.seo-tdk-page #pop textarea {
height: 70px;
vertical-align: text-top;
border-color: #ccc;
resize: none;
}
.seo-tdk-page #pop .full-w {
width: 390px;
}
.seo-tdk-page #pop .controls {
display: inline-block;
}
.seo-tdk-page #pop .err-tip {
color: #a94442;
}
</style>
<div class="pageheader">
<div class="media">
<div class="pageicon pull-left">
<i class="fa fa-th-list"></i>
</div>
<div class="media-body">
<ul class="breadcrumb">
<li><a href=""><i class="glyphicon glyphicon-home"></i></a></li>
<li><a href="/seo/tdk">{{title}}</a></li>
<li>{{typeName}}</li>
</ul>
<h4>{{title}}</h4>
</div>
</div>
<!-- media -->
</div>
<!-- pageheader -->
<div class="contentpanel seo-tdk-page" style="padding-bottom:0;">
<div class="panel panel-default">
<div class="panel-body">
<label style="margin-right:20px;"><input type="checkbox" id="check-all" style="margin-right:5px;">全选</label>
<a data-toggle="modal" href="#pop" class="btn btn-default" style="margin-right:10px;">增加</a>
<button class="btn btn-default delete-all" type="submit">删除</button>
<div class="input-append pull-right">
<form id="query-form" action="/seo/tdk" class="query-form" method="get">
<div class="btn-group">
<button class="btn dropdown-toggle" data-toggle="dropdown">
{{typeName}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
{{# typeList}}
<li><a href="?type={{type}}">{{name}}</a></li>
{{/ typeList}}
</ul>
</div>
<input type="hidden" name="type" value="{{type}}">
<input class="span2 query-key" type="text" name="query" value="{{query}}">
<button class="btn query-submit-btn" type="submit">搜索</button>
</form>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-body">
<table id="table-tdk" class="table table-striped table-bordered responsive">
<thead>
<tr>
<th class="text-center" width="60">ID</th>
<th class="text-center" width="160">类型</th>
<th class="text-center">标题</th>
<th class="text-center">关键词</th>
<th class="text-center">描述</th>
<th class="text-center" width="120">操作</th>
</tr>
</thead>
<tbody>
{{#each tdkList}}
<tr data-type="{{../type}}" data-key="{{key}}" data-title="{{title}}" data-keywords="{{keywords}}" data-description="{{description}}">
<td class="text-center"><input type="checkbox" style="margin-right:5px;">{{id}}</td>
<td class="text-center" title="{{typeName}}({{typeLt}}:{{key}})">{{typeName}}({{typeLt}}:<span class="text-limit">{{key}}</span>)</td>
<td>{{title}}</td>
<td>{{keywords}}</td>
<td>{{description}}</td>
<td class="text-center">
<a href="#pop" class="edit-tdk-btn" data-toggle="modal">编辑</a>
<a href="javascript:;" class="del-tdk-btn">删除</a>
</td>
</tr>
{{/each}}
{{#unless tdkList}}
<tr>
<td class="text-center" colspan="6">暂无数据</td>
</tr>
{{/unless}}
</tbody>
</table>
{{# pager}}
<div class="text-right">
{{#if pages}}
<ul class="pagination">
{{# prePage}}
<li><a href="{{url}}">上一页</a></li>
{{/ prePage}}
{{# pages}}
<li class="{{#unless url}}disabled {{/unless}}{{#if cur}}active{{/if}}"><a {{#if url}}href="{{url}}"{{^}}href="javascript:;"{{/if}}>{{num}}</a></li>
{{/ pages}}
{{# nextPage}}
<li><a href="{{url}}">下一页</a></li>
{{/ nextPage}}
</ul>
{{/if}}
</div>
{{/ pager}}
</div>
</div>
<div id="pop" class="modal fade in" style="display: none;">
<div class="modal-header">
<a class="close clear-input" data-dismiss="modal">×</a>
<h4>添加TDK</h4>
<h4 class="cover-title"></h4>
</div>
<div class="modal-body">
<ul>
<li>
<label class="control-label" for="select-type">类型:</label>
<div class="controls">
<select id="select-type">
{{# typeList}}
<option value="{{type}}">{{name}}</option>
{{/ typeList}}
</select>
</div>
</li>
<li>
<label class="control-label key-label" for="input-key">SKN:</label>
<div class="controls">
<input type="text" id="input-key">
</div>
</li>
<li>
<label class="control-label" for="input-title">标题:</label>
<div class="controls">
<input type="text" id="input-title" class="full-w">
</div>
</li>
<li>
<label class="control-label" for="input-keywords">关键词:</label>
<div class="controls">
<input type="text" id="input-keywords" class="full-w">
</div>
</li>
<li>
<label class="control-label" for="input-description">描述:</label>
<div class="controls">
<textarea id="input-description" class="full-w"></textarea>
</div>
</li>
</ul>
</div>
<div class="modal-footer">
<span class="err-tip"></span>
<a class="btn clear-input close-pop-btn" data-dismiss="modal">关闭</a>
<a class="btn btn-primary sure-btn">确定</a>
</div>
</div>
</div>
<script>
var pop = {
ltList: {
skn: 'SKN:',
article: 'ID:',
shop: 'ShopId:',
url: 'URL:'
},
init: function() {
var that = this;
this.$base = $('#pop');
this.$popTitle = $('.cover-title', this.$base);
this.$keyLabel = $('.key-label', this.$base);
this.$type = $('#select-type', this.$base);
this.$key = $('#input-key', this.$base);
this.$title = $('#input-title', this.$base);
this.$keywords = $('#input-keywords', this.$base);
this.$description = $('#input-description', this.$base);
this.$closeBtn = $('.close-pop-btn', this.$base);
this.$errTip = $('.err-tip', this.$base);
this.$base.on('change', '#select-type', function() {
that.$keyLabel.text(that.ltList[$(this).val()] || that.ltList.skn);
}).on('click', '.clear-input', function() {
that.clearInput();
}).on('click', '.sure-btn', function() {
var data;
if (that.saving) {
return;
}
data = that.packReqData();
data.key = data.key.replace(/http[s]?:\/\//,'');
if (!data) {
that.$errTip.text('请填写完整tdk信息');
return;
}
that.saving = true;
$.ajax({
url: that.editInfo ? '/seo/tdk/edit' : '/seo/tdk/add',
type: 'POST',
data: data,
}).done(function(res) {
if (res.code === 200) {
history.go(0);
}
}).always(function() {
that.saving = false;
});
});
},
clearInput: function() {
this.editInfo = false;
this.$popTitle.empty();
this.$errTip.empty();
this.$type.val('skn').change();
this.$type.removeAttr('disabled')
this.$key.removeAttr('readonly');
$('input, textarea', this.$base).val('');
},
fillInput(info) {
if (info) {
this.editInfo = info;
this.$popTitle.text('编辑TDK');
this.$type.val(info.type).change().attr('disabled', true);
this.$key.attr('readonly', 'readonly').val(info.key);
this.$title.val(info.title);
this.$keywords.val(info.keywords);
this.$description.val(info.description);
}
this.$base.show();
},
packReqData() {
var data = {
type: this.$type.val(),
key: $.trim(this.$key.val()),
title: $.trim(this.$title.val()),
keywords: $.trim(this.$keywords.val()),
description: $.trim(this.$description.val())
};
var i;
if (this.editInfo) {
data.type = this.editInfo.type;
data.key = this.editInfo.key;
}
for (i in data) {
if (data.hasOwnProperty(i) && !data[i]) {
return false;
}
}
return data;
},
close() {
this.$closeBtn.trigger('click');
}
};
$(function(){
var $checkboxs = $('#table-tdk :checkbox');
var deling;
pop.init();
function delTdk(data) {
deling = true;
$.ajax({
url: '/seo/tdk/delete',
type: 'POST',
data: {list: data},
}).done(function(res) {
if (res.code === 200) {
history.go(0);
}
}).always(function() {
deling = false;
});
};
$('#check-all').click(function() {
if ($(this).attr('checked') === 'checked') {
$checkboxs.attr('checked', 'checked');
} else {
$checkboxs.removeAttr('checked');
}
});
$('.delete-all').click(function() {
var arr = [];
$('#table-tdk :checkbox:checked').each(function(){
arr.push($(this).closest('tr').data());
});
arr.length ? delTdk(arr) : false;
});
$('#table-tdk').on('click', '.edit-tdk-btn', function(e) {
pop.fillInput($(e.target).closest('tr').data());
}).on('click', '.del-tdk-btn', function(e) {
var data = $(e.target).closest('tr').data();
delTdk([data]);
});
});
</script>
... ...
... ... @@ -71,6 +71,7 @@
<script src="/js/socket.io-1.4.5.js"></script>
<script src="/js/codemirror/codemirror.js"></script>
<script src="/js/custom.js"></script>
<script src="/js/bootstrap-modal.js"></script>
<script>
$(function(){
... ...
... ... @@ -66,7 +66,9 @@
<li><a href="/check/list">代码检查</a></li>
</ul>
</li>
<li><a href="/seo/tdk"><i class="fa fa-file-code-o"></i> <span>TDK管理</span></a></li>
<li><a href="/keywords"><i class="fa fa-list"></i> <span>关键词管理</span></a></li>
{{/if}}
</ul>
</div>
\ No newline at end of file
</div>
... ...
... ... @@ -11,6 +11,13 @@ const defaults = {
influxdb: {
host: 'influxdblog.web.yohoops.org',
port: 4444
},
redis: {
connect: {
host: '127.0.0.1',
port: '6379',
//password: ''
}
}
};
... ...
const redis = require('redis');
const bluebird = require('bluebird');
const config = require('../config/config');
const client = redis.createClient(config.redis.connect);
bluebird.promisifyAll(redis.RedisClient.prototype);
bluebird.promisifyAll(redis.Multi.prototype);
module.exports = {
client
}
\ No newline at end of file
... ...
This diff could not be displayed because it is too large.
... ... @@ -25,7 +25,7 @@
"author": "jiangfeng <jeff.jiang@yoho.cn>",
"license": "ISC",
"dependencies": {
"bluebird": "^3.4.1",
"bluebird": "^3.5.0",
"co": "^4.6.0",
"co-body": "^4.2.0",
"formidable": "^1.0.17",
... ... @@ -57,11 +57,15 @@
"qcloudapi-sdk": "^0.1.5",
"qn": "^1.3.0",
"qs": "^6.2.0",
"redis": "^2.7.1",
"request-promise": "^4.1.1",
"shelljs": "^0.7.0",
"socket.io": "^1.4.6",
"ssh2": "^0.5.4",
"tar": "^2.2.1",
"utility": "^1.8.0"
},
"devDependencies": {
"nodemon": "^1.11.0"
}
}
}
\ No newline at end of file
... ...
/* =========================================================
* bootstrap-modal.js v2.0.1
* http://twitter.github.com/bootstrap/javascript.html#modals
* =========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
!function( $ ){
"use strict"
/* MODAL CLASS DEFINITION
* ====================== */
var Modal = function ( content, options ) {
this.options = options
this.$element = $(content)
.delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
}
Modal.prototype = {
constructor: Modal
, toggle: function () {
return this[!this.isShown ? 'show' : 'hide']()
}
, show: function () {
var that = this
if (this.isShown) return
$('body').addClass('modal-open')
this.isShown = true
this.$element.trigger('show')
escape.call(this)
backdrop.call(this, function () {
var transition = $.support.transition && that.$element.hasClass('fade')
!that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position
that.$element
.show()
if (transition) {
that.$element[0].offsetWidth // force reflow
}
that.$element.addClass('in')
transition ?
that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
that.$element.trigger('shown')
})
}
, hide: function ( e ) {
e && e.preventDefault()
if (!this.isShown) return
var that = this
this.isShown = false
$('body').removeClass('modal-open')
escape.call(this)
this.$element
.trigger('hide')
.removeClass('in')
$.support.transition && this.$element.hasClass('fade') ?
hideWithTransition.call(this) :
hideModal.call(this)
}
}
/* MODAL PRIVATE METHODS
* ===================== */
function hideWithTransition() {
var that = this
, timeout = setTimeout(function () {
that.$element.off($.support.transition.end)
hideModal.call(that)
}, 500)
this.$element.one($.support.transition.end, function () {
clearTimeout(timeout)
hideModal.call(that)
})
}
function hideModal( that ) {
this.$element
.hide()
.trigger('hidden')
backdrop.call(this)
}
function backdrop( callback ) {
var that = this
, animate = this.$element.hasClass('fade') ? 'fade' : ''
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.appendTo(document.body)
if (this.options.backdrop != 'static') {
this.$backdrop.click($.proxy(this.hide, this))
}
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
this.$backdrop.addClass('in')
doAnimate ?
this.$backdrop.one($.support.transition.end, callback) :
callback()
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in')
$.support.transition && this.$element.hasClass('fade')?
this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) :
removeBackdrop.call(this)
} else if (callback) {
callback()
}
}
function removeBackdrop() {
this.$backdrop.remove()
this.$backdrop = null
}
function escape() {
var that = this
if (this.isShown && this.options.keyboard) {
$(document).on('keyup.dismiss.modal', function ( e ) {
e.which == 27 && that.hide()
})
} else if (!this.isShown) {
$(document).off('keyup.dismiss.modal')
}
}
/* MODAL PLUGIN DEFINITION
* ======================= */
$.fn.modal = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('modal')
, options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option]()
else if (options.show) data.show()
})
}
$.fn.modal.defaults = {
backdrop: true
, keyboard: true
, show: true
}
$.fn.modal.Constructor = Modal
/* MODAL DATA-API
* ============== */
$(function () {
$('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
var $this = $(this), href
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
, option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data())
e.preventDefault()
$target.modal(option)
})
})
}( window.jQuery );
\ No newline at end of file
... ...