Authored by yyq

hot

/**
*
* @author: yyq<yanqing.yang@yoho.cn>
* @date: 18/5/10
*/
const _ = require('lodash');
const moment = require('moment');
const Router = require('koa-router');
const r = new Router();
const Mysql = require('../../../lib/mysql-promise');
const pager = require('../utils/pager');
const config = require('../../../config/config');
const singleBrandKeyPre = config.singleBrandKeyPre;
const singleSortKeyPre = config.singleSortKeyPre;
//通过小分类同步中分类和大分类
const getMsort = async(ctx) => {
let redis = ctx.redis;
return redis.getAsync(`global:yoho:sorts`).then((sorts) => {
let sData = {};
sorts = JSON.parse(sorts) || [];
_.forEach(_.get(sorts, 'data.sort'), (msort) => {
_.forEach(_.get(msort, 'sub'), (misort) => {
_.forEach(_.get(misort, 'sub'), (sort) => {
if (!sData[sort.sort_id]) {
sData[sort.sort_id] = {
msort: msort.sort_id,
msort_name: msort.sort_name,
misort: misort.sort_id,
misort_name: misort.sort_name,
sort_id: sort.sort_id,
sort_name: sort.sort_name,
};
}
})
})
})
return sData;
});
};
r.get('/list', async(ctx) => {
let resData = {};
let q = ctx.request.query || {};
let query = q.query || '';
let page = parseInt(`0${q.page}`, 10) || 1;
let limit = parseInt(`0${q.limit}`, 10) || 10;
let mysql = new Mysql();
let total = 0;
let typeList = [
{
type: 'keyword',
name: '关键词'
},
{
type: 'wordroot',
name: '词根ID'
},
{
type: 'goodnum',
name: '商品数'
}
];
let type = q.type || typeList[0].type;
let typeName = _.result(_.find(typeList, {'type': type}), 'name') || typeList[0].name;
let wheres = '';
let conditions = [];
if (q.hot) {
wheres += ' AND is_hot = 1';
}
switch (query && type) {
case 'keyword':
wheres += ` AND keyword like '%%${query}%'`;
break;
case 'wordroot':
wheres += ' AND root_id = ?'
conditions.push(query);
break;
case 'brand':
wheres += ' AND brand_id = ?'
conditions.push(query);
break;
case 'sort':
wheres += ' AND sort_id = ?'
conditions.push(query);
break;
case 'goodnum':
wheres += ' AND yoho_goods_num >= ?'
conditions.push(query);
break;
}
let d = await mysql.query(`SELECT COUNT(*) as total FROM seo_keywords WHERE status = 1 ${wheres}`, conditions);
resData.total = d[0] && d[0].total || 0;
conditions.push((page - 1) * limit, limit);
d = await mysql.query(`SELECT * FROM seo_keywords WHERE status = 1 ${wheres} ORDER BY id DESC limit ?, ?`, conditions);
let sortIds = [];
sortIds = await getMsort(ctx);
resData.keywords = _.map(d, (elem) => {
const _sortId = sortIds[elem.sort_id];
return Object.assign({}, elem, {
sortName: _sortId && _sortId.sort_name,
misortName: _sortId && _sortId.misort_name,
msortName: _sortId && _sortId.msort_name,
is_push: elem.is_push ? '是' : '否',
add_time: elem.add_time && moment(elem.add_time * 1000).format('YYYY-MM-DD HH:mm'),
});
});
resData.typeList = typeList;
resData.typeName = typeName;
resData.type = type;
resData.query = query;
return ctx.body = {
code: 200,
message: 'success',
data: resData
};
});
module.exports = r;
... ...
... ... @@ -58,7 +58,7 @@ const addDataValues = async(ctx, value)=>{
let key=`keywords_mana:${value}`;
multi.set(key, value);
multi.lrem('keywords_mana_list', 1, key).lpush('keywords_mana_list', key);
return multiAsync(multi);
}
//编辑
... ... @@ -70,7 +70,7 @@ const editDataValues = async(ctx, value, oldValue)=>{
}catch(e){
return false;
}
}
//搜索
const searchDataValues = async(ctx, value)=>{
... ... @@ -159,7 +159,7 @@ r.get('/delKeywords', async(ctx) => {
message: 'fail',
data: ''
};
}
}
});
r.get('/addKeywords', async(ctx) => {
... ... @@ -178,7 +178,7 @@ r.get('/addKeywords', async(ctx) => {
message: 'fail',
data: ''
};
}
}
});
r.get('/editKeywords', async(ctx) => {
... ... @@ -197,7 +197,7 @@ r.get('/editKeywords', async(ctx) => {
message: 'fail',
data: ''
};
}
}
});
r.get('/searchKeywords', async(ctx) => {
... ... @@ -211,7 +211,7 @@ r.get('/searchKeywords', async(ctx) => {
message: 'success',
data: result,
total:1
};
};
});
... ... @@ -239,7 +239,7 @@ const getRootKeyword = (ids, mysql) => {
for(let i = 0; i < len; i++) {
marks.push('?');
}
}
return mysql.query(`SELECT id, keyword FROM seo_keywords_root WHERE id IN (${marks.join(',')}) AND status = 1;`, ids).then(d => {
let ddata = {};
... ... @@ -264,7 +264,7 @@ r.get('/syncWord', async(ctx) => {
code: 200,
message: 'success',
data: d
};
};
});
});
... ... @@ -295,23 +295,27 @@ r.get('/expand', async(ctx) => {
let wheres = '';
let conditions = [];
if (query && query.hot) {
wheres += ' AND is_hot = 1';
}
switch (query && type) {
case 'keyword':
wheres = ` AND keyword like '%%${query}%'`;
case 'keyword':
wheres += ` AND keyword like '%%${query}%'`;
break;
case 'wordroot':
case 'wordroot':
wheres += ' AND root_id = ?'
conditions.push(query);
break;
case 'brand':
case 'brand':
wheres += ' AND brand_id = ?'
conditions.push(query);
break;
case 'sort':
case 'sort':
wheres += ' AND sort_id = ?'
conditions.push(query);
break;
case 'goodnum':
case 'goodnum':
wheres += ' AND yoho_goods_num >= ?'
conditions.push(query);
break;
... ... @@ -368,6 +372,8 @@ r.post('/expand/del', async(ctx) => {
let q = ctx.request.body;
let ids = q.ids && JSON.parse(q.ids) || [];
ids = _.concat([], ids);
let len = ids.length;
let marks = [];
... ... @@ -376,20 +382,20 @@ r.post('/expand/del', async(ctx) => {
code: 400,
message: 'ids is empty',
data: ''
};
};
}
let mysql = new Mysql();
for(let i = 0; i < len; i++) {
marks.push('?');
}
}
return mysql.query(`UPDATE seo_keywords SET status = 0 WHERE id IN (${marks.join(',')})`, ids).then(d => {
return ctx.body = {
code: 200,
message: 'success',
data: d
};
};
});
});
... ... @@ -558,6 +564,9 @@ r.post('/add', async (ctx) => {
let msort = ctx.request.body.msort || 0;
let misort = ctx.request.body.misort || 0;
let sort = ctx.request.body.sort || 0;
let describe = ctx.request.body.describe || '';
let goods_img = ctx.request.body.goodsImg || '';
let is_hot = ctx.request.body.isHot || 0;
let addTime = Date.parse(new Date())/1000;
let result = {code: 400, message: ''};
... ... @@ -574,7 +583,7 @@ r.post('/add', async (ctx) => {
return ctx.response.body = result;
}
await mysql.query(`insert into seo_keywords (keyword, brand_id, msort, misort, sort_id, add_time) values ('${keyword}', ${brand}, ${msort}, ${misort}, ${sort}, ${addTime})`);
await mysql.query(`insert into seo_keywords (keyword, brand_id, msort, misort, sort_id, add_time, describe, goods_img, is_hot) values ('${keyword}', ${brand}, ${msort}, ${misort}, ${sort}, ${addTime}, '${describe}', '${goods_img}', ${is_hot})`);
result.code = 200;
return ctx.response.body = result;
... ...
... ... @@ -18,6 +18,7 @@ const apiCache = require('./actions/api_cache');
const degrade = require('./actions/degrade');
const deploy = require('./actions/deploy');
const keywords = require('./actions/keywords');
const hotKeywords = require('./actions/hot-keywords');
const api = require('./actions/api');
const abuseProtection = require('./actions/abuse_protection');
const crawler = require('./actions/crawler');
... ... @@ -63,6 +64,7 @@ module.exports = function(app) {
base.use('/degrade', degrade.routes(), degrade.allowedMethods());
base.use('/deploys', deploy.routes(), deploy.allowedMethods());
base.use('/keywords', keywords.routes(), keywords.allowedMethods());
base.use('/hot-keywords', hotKeywords.routes(), hotKeywords.allowedMethods());
base.use('/profile', profile.routes(), profile.allowedMethods());
const white = crawler('/crawler/ip_whitelists', '/crawler/ua_whitelists', '白名单', false);
... ...
... ... @@ -6,7 +6,7 @@
"scripts": {
"test": "./node_modules/.bin/ava",
"dev": "nodemon -e js --ignore public/ --ignore packages/ --harmony app.js",
"static": "ada --port 9001 --cwd public --hotVue --noRem",
"static": "ada --port 9001 --cwd public --hotReact --noRem",
"build": "ada build --port 9001 --cwd public --noRem",
"start": "node --harmony app.js"
},
... ...
No preview for this file type
import React from 'react'
import { Table, Divider, Modal, Button, Input, Cascader, Upload, Icon, message } from 'antd'
function HotKeywords() {
return <div>
HotKeywords
</div>
import HotApi from '../../services/seo/hot-keywords'
const { TextArea } = Input;
const columns = [{
title: '序号',
dataIndex: 'id',
key: 'id',
}, {
title: '关键词',
dataIndex: 'keyword',
key: 'keyword',
}, {
title: '大品类',
dataIndex: 'msortName',
key: 'msortName',
}, {
title: '小品类',
dataIndex: 'sortName',
key: 'sortName',
}, {
title: '商品数',
dataIndex: 'yoho_goods_num',
key: 'yoho_goods_num',
}, {
title: '是否推送',
dataIndex: 'is_push',
key: 'is_push',
}, {
title: '添加时间',
dataIndex: 'add_time',
key: 'add_time',
}];
function ActionButton(props) {
function editClick() {
props.keyword.callbackFn('edit', props.keyword);
}
function deleteClick() {
props.keyword.callbackFn('delete', props.keyword);
}
return (
<span>
<a href="javascript:;" onClick={editClick}>编辑</a>
<Divider type="vertical" />
<a href="javascript:;" onClick={deleteClick}>删除</a>
</span>
);
}
class OptionModal extends React.Component {
constructor(props) {
super(props);
this.props = props;
this.state = {
showModal: false,
sorts: []
};
this.hotApi = new HotApi();
this.handleOk = this.handleOk.bind(this);
this.handleKeywordChange = this.handleKeywordChange.bind(this);
this.handleDescChange = this.handleDescChange.bind(this);
this.handleSortChange = this.handleSortChange.bind(this);
this.handleImageChange = this.handleImageChange.bind(this);
this.loadSortData = this.loadSortData.bind(this);
this.loadSortDataAsync().then(result => {
this.setState({sorts: result});
});
}
handleOk() {
this.hotApi.saveHotKeywords(this.renderData).then(result => {
if (result.code === 200) {
this.props.loadHotList && this.props.loadHotList();
}
})
this.props.hideOptionModal && this.props.hideOptionModal();
}
handleKeywordChange(e) {
this.renderData.keyword = e.target.value;
console.log(this.renderData);
}
handleDescChange(e) {
this.renderData.describe = e.target.value;
console.log(this.renderData);
}
handleSortChange(select) {
this.renderData.msort = select[0];
this.renderData.misort = select[1];
this.renderData.sort_id = select[2];
console.log(this.renderData);
}
handleImageChange() {
// this.renderData.goods_img = select[2];
}
beforeUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
message.error('You can only upload JPG file!');
} else if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJPG && isLt2M;
}
loadSortData(selectedOptions) {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
this.loadSortDataAsync(targetOption.value).then(result => {
targetOption.loading = false;
targetOption.children = result;
this.setState({sorts: this.state.sorts});
});
}
loadSortDataAsync(id) {
return this.hotApi.getSubSorts(id || 0).then(result => {
result.map(val => {
if (id) {
val.value = val.sort_id;
val.label = val.sort_name;
val.isLeaf = false;
val.children = (val.sub || []).map(sval => {
sval.value = sval.sort_id;
sval.label = sval.sort_name;
sval.isLeaf = true;
return sval;
})
} else {
val.value = val.id;
val.label = val.sortName;
val.isLeaf = false;
}
return val;
});
return result;
});
}
render() {
const {showModal, hideOptionModal, record} = this.props;
let title = '';
this.renderData = Object.assign({}, record || {});
if (record) {
switch (record.type) {
case 'edit':
title = '编辑关键词';
break;
case 'delete':
title = '删除关键词';
break;
default:
title = '新建关键词';
break;
}
}
if (record && record.type === 'delete') {
return (
<Modal title={title} visible={showModal} onOk={this.handleOk} onCancel={hideOptionModal}>
<p>确认删除该关键词?</p>
</Modal>
)
} else {
const uploadButton = (
<div>
<Icon type={this.state.loading ? 'loading' : 'plus'} />
<div className="ant-upload-text">Upload</div>
</div>
);
const imageUrl = '';
return (
<Modal title={title} visible={showModal} onOk={this.handleOk} onCancel={hideOptionModal}>
<div style={{ paddingBottom: 10 }}>
<span>关键词:</span>
<Input onChange={this.handleKeywordChange} />
</div>
<div style={{ paddingBottom: 10 }}>
<span>描述:</span>
<TextArea rows={4} onChange={this.handleDescChange} />
</div>
<div style={{ paddingBottom: 10 }}>
<span>封面图:</span>
<Upload
name="avatar"
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
action="//jsonplaceholder.typicode.com/posts/"
beforeUpload={this.beforeUpload}
onChange={this.handleImageChange}
style={{ width: 600 }}>
{imageUrl ? <img src={imageUrl} alt="" /> : uploadButton}
</Upload>
</div>
<div style={{ paddingBottom: 10 }}>
<span>品类:</span>
<Cascader
options={this.state.sorts}
loadData={this.loadSortData}
onChange={this.handleSortChange}
style={{ width: '100%' }}
placeholder="" />
</div>
</Modal>
)
}
}
}
columns.push({
title: '操作',
key: 'action',
render(keyword) {
return <ActionButton keyword={keyword}/>
}
});
class HotKeywords extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: false,
dataSource: [],
columns: columns,
selectedRowKeys: [],
pagination: {
current: 1,
total: 1,
pageSize: 10
}
};
this.hotApi = new HotApi();
this.loadHotList();
}
loadHotList(params = {}) {
const { current, pageSize } = this.state.pagination;
params.page = params.page || current || 1;
params.limit = params.limit || pageSize || 10;
this.hotApi.getHotList(params).then(result => {
if (result.code === 200 && result.data) {
const pagination = this.state.pagination;
const { keywords, total } = result.data;
pagination.total = total;
this.setState({
dataSource: keywords,
pagination: pagination
});
}
});
}
onSelectChange(selectedRowKeys) {
this.setState({selectedRowKeys});
}
handleTableChange(pagination) {
this.setState({pagination});
this.loadHotList({page: pagination.current});
}
deleteTableRow() {
this.deleteKeywordsAsync(this.state.selectedRowKeys);
}
addTableRow() {
this.showOptionModal('add');
}
sendToBaidu() {
}
deleteKeywordsAsync(ids) {
this.hotApi.delHotKeywords(ids).then(result => {
result.code === 200 && this.loadHotList();
});
}
showOptionModal(type, record) {
// const modelRecord = {...record};
const modelRecord = record || {};
modelRecord.type = type;
this.modelRecord = modelRecord;
this.setState({showModal: true});
}
hideOptionModal() {
this.modelRecord = null;
this.setState({showModal: false});
}
render() {
const { showModal, dataSource, columns, selectedRowKeys, pagination, loading } = this.state;
const hasSelected = selectedRowKeys.length > 0;
const loadHotList = this.loadHotList.bind(this);
const handleTableChange = this.handleTableChange.bind(this);
const deleteTableRow = this.deleteTableRow.bind(this);
const hideOptionModal = this.hideOptionModal.bind(this);
const addTableRow = this.addTableRow.bind(this);
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange.bind(this)
};
const rowKey = record => {
record.callbackFn = this.showOptionModal.bind(this);
return record.id
}
return (
<div>
<div style={{ paddingBottom: 10 }}>
<Button type="primary" onClick={deleteTableRow} disabled={!hasSelected} loading={loading}>删除</Button>
<Button type="primary" style={{ marginLeft: 10 }} onClick={addTableRow}>添加</Button>
<Button type="primary" style={{ marginLeft: 10 }} onClick={this.sendToBaidu}>推送百度</Button>
<OptionModal showModal={showModal} hideOptionModal={hideOptionModal} record={this.modelRecord} loadHotList={loadHotList}/>
</div>
<Table rowSelection={rowSelection} dataSource={dataSource} columns={columns}
rowKey={rowKey} pagination={pagination} onChange={handleTableChange}/>
</div>
);
}
}
export default HotKeywords
... ...
import Service from '../service';
export default class extends Service {
getHotList(params) {
params = params || {};
params.hot = 1;
return this.get('/hot-keywords/list', {params});
}
delHotKeywords(ids) {
return this.post('/keywords/expand/del', {ids});
}
saveHotKeywords(info) {
console.log(info);
let params = {
keywords: info.keyword,
msort: info.msort,
misort: info.misort,
sort: info.sort_id,
describe: info.describe,
goodsImg: info.goods_img,
isHot: 1
};
return this.post('/keywords/add', params);
}
getSubSorts(sortId) {
return this.post('/seo/rootwords/getsubsorts', {sortId})
}
}
... ...