Authored by huangyi

领券

... ... @@ -4,11 +4,63 @@
* @date: 08/08/2018
*/
// const _ = require('lodash');
const _ = require('lodash');
const CouponModel = require('../models/coupon');
const moment = require('moment');
const excelExport = require('excel-export');
const INVALID_PARAMS = '参数错误';
const xlsx = require('xlsx');
const DO_SUCCESS = '操作成功';
const GET_SUCCESS = '获取成功';
let loadExcel = function(path) {
let workbook = xlsx.readFile(path);
const sheetNames = workbook.SheetNames;
const worksheet = workbook.Sheets[sheetNames[0]];
return Promise.resolve(xlsx.utils.sheet_to_json(worksheet));
};
const timeFormat = (time) => {
if (_.isNumber(time)) {
time = moment.unix(time);
}
return moment(time).format('YYYY-MM-DD HH:mm:ss');
};
const couponController = {
async loadCouponNoList(req, res, next) {
let file = req.files.up_excel;
let id = req.body.id;
let data = await loadExcel(file.path);
// let arr = [];
//
// for (let item of data) {
// let rowArr = [id];
//
// rowArr.push(item.couponNo);
// arr.push(rowArr);
// }
try {
for (let item of data) {
let param = {couponId: id, couponNo: item.couponNo};
await req.ctx(CouponModel).couponNoAdd(param);
}
return res.json({
code: 200,
message: DO_SUCCESS
});
} catch (e) {
next;
}
},
couponListPage(req, res) {
res.render('coupon/list', {
bodyClass: 'nav-md',
... ... @@ -16,6 +68,20 @@ const couponController = {
page: 'coupon'
});
},
async couponOptionPage(req, res) {
let result = await req.ctx(CouponModel).couponById(req.query.id);
if (result instanceof Array) {
result = result[0];
}
res.render('coupon/coupon-option', {
bodyClass: 'nav-md',
module: 'admin',
content: result,
page: 'coupon-option'
});
},
async couponList(req, res) {
const pageNo = req.query.pageNo || 1;
const pageSize = req.query.pageSize || 20;
... ... @@ -34,27 +100,72 @@ const couponController = {
});
},
async createCoupon(req, res) {
let param = req.body;
await req.ctx(CouponModel).couponCreate(param);
res.json({
code: 200,
message: DO_SUCCESS
});
async modifyCoupon(req, res, next) {
try {
await req.ctx(CouponModel).couponModify(req.body);
res.json({
code: 200,
message: DO_SUCCESS
});
} catch (e) {
next;
}
},
downloadNoList(req, res, next) {
const couponId = req.query.id;
async updateConpon(req, res) {
let param = req.body;
if (!couponId) {
return res.json({
code: 400,
message: INVALID_PARAMS
});
}
await req.ctx(CouponModel).couponUpdate(param);
res.json({
code: 200,
message: DO_SUCCESS
});
},
let conf = {
name: 'mysheet',
cols: [
{
caption: '券ID',
type: 'number'
},
{
caption: '券码',
type: 'string'
},
{
caption: '用户id',
type: 'number'
},
{
caption: '创建时间',
type: 'string'
}
],
rows: []
};
req.ctx(CouponModel).couponNoListJoinUser(couponId)
.then(result => {
let temp = [];
//
_.each(result, item => {
temp = [];
temp.push(couponId);
temp.push(item.couponNo);
temp.push(item.userId);
temp.push(timeFormat(item.createTime));
conf.rows.push(temp);
});
let exportFile = excelExport.execute(conf);
res.setHeader('Content-Type', 'application/vnd.openxmlformats');
res.setHeader('Content-Disposition', 'attachment; filename=couponList.xlsx');
res.end(exportFile, 'binary');
})
.catch(next);
},
};
... ...
... ... @@ -24,71 +24,87 @@ class CouponModel extends global.yoho.BaseModel {
});
}
couponCreate(obj) {
return mysqlCli.insert(
`insert into ${TABLE_COUPON} (coupon_name, coupon_desc, shop_name, shop_logo_url, status,type,sort)
values (:couponName, :couponDesc, :shopName, :shopLogoUrl, :status, :type, :sort);`,
{
couponName: obj.couponName,
couponDesc: obj.couponDesc,
shopName: obj.shopName,
shopLogoUrl: obj.shopLogoUrl,
status: obj.status,
type: obj.type,
sort: obj.sort
couponById(id) {
if (!id) {
return new Promise(resolve => {
return resolve({});
});
} else {
return mysqlCli.query(
`select id, coupon_name couponName, coupon_desc couponDesc, shop_name shopName, shop_logo_url shopLogoUrl, status, type, sort , create_time createTime
from ${TABLE_COUPON}
where id = :id`, {
id
});
}
}
couponUpdate(obj) {
let sql = `UPDATE ${TABLE_COUPON} SET `;
let sqlParam = {};
let keyArr = Object.keys(obj);
couponNoAdd(arr) {
// return mysqlCli.insert(`insert into ${TABLE_COUPON_NO} (coupon_id, coupon_no) values ?`, arr);
return mysqlCli.insert(`insert into ${TABLE_COUPON_NO} (coupon_id, coupon_no) values (:couponId,:couponNo)`, arr);
}
couponModify(obj) {
if (obj.id) {
let sql = `UPDATE ${TABLE_COUPON} SET `;
let sqlParam = {};
let keyArr = Object.keys(obj);
keyArr.map((value, index) => {
if (value !== 'id') {
let col = toLine(value);
keyArr.map((value, index) => {
if (value !== 'id') {
let col = toLine(value);
sql += col + ' = :' + value;
if (index !== (keyArr.length - 1)) {
sql += ',';
sql += col + ' = :' + value;
if (index !== (keyArr.length - 1)) {
sql += ',';
}
}
}
sqlParam[value] = obj[value];
});
sql += 'WHERE id = :id';
return mysqlCli.update(
sqlParam[value] = obj[value];
});
sql += ' WHERE id = :id';
return mysqlCli.update(
sql,
sqlParam
);
} else {
return mysqlCli.insert(
`insert into ${TABLE_COUPON} (coupon_name, coupon_desc, shop_name, shop_logo_url, status,type,sort)
values (:couponName, :couponDesc, :shopName, :shopLogoUrl, :status, :type, :sort);`,
{
couponName: obj.couponName,
couponDesc: obj.couponDesc,
shopName: obj.shopName,
shopLogoUrl: obj.shopLogoUrl,
status: obj.status,
type: obj.type,
sort: obj.sort
});
}
}
allCouponNum() {
return mysqlCli.query(
`select count(*) as total from ${TABLE_COUPON};`
);
}
couponNoListByCId(couponId) {
return mysqlCli.query(
`select id, coupon_id couponId,coupon_no couponNo,send_flag sendFlag,create_time createTime
from ${TABLE_COUPON_NO}
where coupon_id = :couponId `, {couponId: couponId});
}
couponNoListJoinUser(couponId) {
return mysqlCli.query(
`select cn.coupon_id couponId,cn.coupon_no couponNo,cn.send_flag sendFlag,cu.user_id userId,cu.create_time createTime
from ${TABLE_COUPON_NO} as cn left join ${TABLE_COUPON_USER} as cu
where coupon_id = :couponId `, {couponId: couponId});
on cu.coupon_id = cn.coupon_id where cn.coupon_id = :couponId`, {couponId});
}
couponUserListByCId(couponId) {
return mysqlCli.query(
`select id, coupon_id couponId,coupon_no_id couponNoId,user_id userId,create_time createTime
`select id, coupon_id couponId,coupon_no_id couponNoId,user_id userId,create_time createTime
from ${TABLE_COUPON_USER}
where coupon_id = :couponId `, {couponId: couponId});
}
}
module.export = CouponModel;
module.exports = CouponModel;
... ...
... ... @@ -9,6 +9,8 @@ const admin = require('./controllers/admin');
const activity = require('./controllers/activity');
const user = require('./controllers/user');
const coupon = require('./controllers/coupon');
const multipart = require('connect-multiparty');
const mutilpartMiddleware = multipart();
// 管理员[page]
router.get('/login', admin.loginPage);
... ... @@ -39,6 +41,8 @@ router.get('/user/exportUserLoginLog', user.exportUserLoginLog);
// 优惠券管理[page]
router.get('/coupon/list', coupon.couponListPage);
router.get('/coupon/option', coupon.couponOptionPage);
router.get('/coupon/update', coupon.couponOptionPage);
// 管理员[ajax]
router.post('/api/login', admin.login);
... ... @@ -59,8 +63,9 @@ router.post('/api/activity/createY100Article', activity.createY100Article);
// 优惠券管理[ajax]
router.get('/api/coupon/list', coupon.couponList);
router.post('/api/coupon/add', coupon.createCoupon);
router.post('/api/coupon/update', coupon.updateConpon);
router.post('/api/coupon/modify', coupon.modifyCoupon);
router.post('/api/coupon/batchAddNo', mutilpartMiddleware, coupon.loadCouponNoList);
router.get('/api/coupon/downloadNo', coupon.downloadNoList);
// 用户管理[ajax]
router.post('/api/user/delete', user.deleteUser);
... ...
<div class="right_col" role="main">
<div class="col-md-12 col-sm-12 col-xs-12">
<div class="x_panel">
{{# content}}
<div class="x_title">
<h2>创建券</h2>
<div class="clearfix"></div>
</div>
<form id="createForm" class="form-horizontal form-label-left">
<input class="form-imput" type="hidden" data-type="id" value="{{id}}">
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="actName">
券名称<span class="required">*</span>
</label>
<div class="col-md-6 col-sm-6 col-xs-12">
<input class="form-control col-md-7 col-xs-12 form-imput"
data-type="couponName"
data-toggle="tooltip"
data-placement="bottom"
title="券名称不能为空"
name="couponName"
type="text"
maxlength="50"
value="{{couponName}}">
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="actName">
商家名称<span class="required">*</span>
</label>
<div class="col-md-6 col-sm-6 col-xs-12">
<input class="form-control col-md-7 col-xs-12 form-imput"
data-type="shopName"
data-toggle="tooltip"
data-placement="bottom"
title="商品名称不能为空"
name="shopName"
type="text"
maxlength="50"
value="{{shopName}}">
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="actName">
商家logo图片<span class="required">*</span>
</label>
<div class="col-md-6 col-sm-6 col-xs-12">
<img src="{{shopLogoUrl}}" class="col-md-6 product-thumb {{#unless shopLogoUrl}}hide{{/unless}}">
<button type="button" class="btn btn-primary btn-upload-thumb" data-cover="{{shopLogoUrl}}">上传</button>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12">
券开启状态
</label>
<div class="col-md-6 col-sm-6 col-xs-12">
<label class="control-label col-md-1">
<input type="radio" name="status" required="required"
class="form-contro col-md-7 col-xs-12" checked value="1">
</label>
<label class="control-label col-md-1">
<input type="radio" name="status" required="required"
class="form-contro col-md-7 col-xs-12" value="0">
</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12">
券类型
</label>
<div class="col-md-9 col-sm-9 col-xs-12">
<label class="control-label radi">
<input type="radio" name="type" required="required"
class="form-contro col-md-4 col-xs-12" checked value="1">
通用码
</label>
<label class="control-label radi">
<input type="radio" name="type" required="required"
class="form-contro col-md-4 col-xs-12" value="2">
一人一码
</label>
<label class="control-label radi">
<input type="radio" name="type" required="required"
class="form-contro col-md-4 col-xs-12" value="3">
不用码
</label>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="actName">排序<span
class="required">*</span>
</label>
<div class="col-md-2 col-sm-2 col-xs-12">
<input id="actVoteLimit" class="form-control col-md-2 form-imput"
data-type="sort"
data-toggle="tooltip" data-placement="bottom" title="排序"
name="actVoteLimit"
type="text"
maxlength="10"
value="{{sort}}">
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="actName">券说明<span
class="required">*</span>
</label>
<div class="col-md-4 col-sm-4 col-xs-12">
<textarea class="form-control form-imput" data-type="couponDesc" rows="3" style="resize: none;height: 300px">{{couponDesc}}</textarea>
</div>
</div>
<div class="item form-group">
<div class="col-md-2 col-md-offset-10 action-wrap">
<button type="button" class="btn btn-primary btn-save">保存</button>
</div>
</div>
</form>
<a id="upload-btn"></a>
{{/ content}}
</div>
</div>
</div>
... ...
... ... @@ -5,7 +5,7 @@
<div class="col-md-12 col-sm-12 col-xs-12">
<div class="x_panel">
<div class="x_title">
<button class="btn btn-primary btn-export-user-list" data-id="{{id}}">导出用户列表</button>
<a href="/admin/coupon/option" class="btn btn-primary">创建券</a>
<div class="clearfix"></div>
</div>
<div class="x_content">
... ... @@ -16,7 +16,7 @@
<th class="column-title">ID</th>
<th class="column-title">券名称</th>
<th class="column-title">券描述</th>
<th class="column-title">名称</th>
<th class="column-title">名称</th>
<th class="column-title">商铺图标</th>
<th class="column-title">上下架状态</th>
<th class="column-title">券类型</th>
... ... @@ -25,11 +25,11 @@
<th class="column-title">操作</th>
</tr>
</thead>
<tbody class="user-list">
<tbody class="coupon-list">
</tbody>
</table>
</div>
<div class="table-pagination user-pagination pull-right"></div>
<div class="table-pagination coupon-pagination pull-right"></div>
</div>
</div>
</div>
... ...
... ... @@ -63,6 +63,11 @@
<li><a href="/admin/user/login-log">用户登录日志</a></li>
</ul>
</li>
<li><a><i class="fa fa-coupon"></i> 券管理 <span class="fa fa-chevron-down"></span></a>
<ul class="nav child_menu">
<li><a href="/admin/coupon/list">券列表</a></li>
</ul>
</li>
</ul>
</div>
</div>
... ...
require('admin/coupon-option.page.css');
require('bootstrap-daterangepicker');
require('bootpag/lib/jquery.bootpag.min');
let uploadedFn;
let $upload;
function initUpload() {
$upload = $('#upload-btn');
if (!$upload.length) {
return;
}
const imgArr = ['image/jpeg', 'image/jpg', 'image/png'];
// why? reference: https://github.com/qiniu/js-sdk/issues/266
Promise.all([
require('mOxie/bin/js/moxie'),
require('plupload/js/plupload.dev'),
require('qiniu-js')
]).then(([moxie, plupload]) => {
window.moxie = moxie;
window.plupload = plupload;
Qiniu.uploader({ // eslint-disable-line
runtimes: 'html5,flash,html4',
browse_button: 'upload-btn',
uptoken_url: '/api/getToken',
unique_names: true,
multi_selection: false,
filters: {
mime_types: [{
extensions: 'jpg,jpeg,png,mp4'
}]
},
dragdrop: false,
domain: 'http://img01.yohoboys.com/',
max_file_size: '10mb',
max_retries: 3,
chunk_size: '10mb',
auto_start: true,
init: {
BeforeUpload: function(up) {
window.checkup = up;
},
UploadProgress: function() {
console.log('上传中...');
},
FileUploaded: function(up, file, info) {
var domain = up.getOption('domain');
var res = JSON.parse(info.response);
var url = domain + res.key;
uploadedFn && uploadedFn(url, imgArr.indexOf(file.type) >= 0);
},
Error: function(up, err, errTip) {
console.log(err, errTip);
}
}
});
});
}
function bindEditPageEvent() {
initUpload();
function packInfo() {
let resData = {};
let error;
$('.form-imput').each(function() {
let $this = $(this);
let val = $this.val();
let data = $this.data();
if (!val && !error && data.type !== 'id') {
error = data.empty || `请填写${data.type}`;
}
resData[data.type] = val;
});
if (error) {
alert(error);//eslint-disable line
return;
}
resData.shopLogoUrl = $('.btn-upload-thumb').data('cover');
resData.status = $('input[name=status]:checked').val();
resData.type = $('input[name=type]:checked').val();
if (!resData.shopLogoUrl) {
alert('请上传商家logo图');
return;
}
return resData;
}
function GenUploadedFn($dom) {
return function(url, isImg) {
if (!isImg) {
alert('请上传图片');
}
$dom.prev().attr('src', url).removeClass('hide');
$dom.data('cover', url);
};
}
$('.btn-upload-thumb').on('click', function() {
uploadedFn = new GenUploadedFn($(this));
$upload[0].click();
});
$('.btn-save').on('click', function() {
let info = packInfo();
if (!info || window.saveing) {
return;
}
window.saveing = true;
$.ajax({
method: 'post',
url: '/admin/api/coupon/modify',
data: info
}).then(res => {
window.saveing = false;
if (res.code === 200) {
alert('保存成功');
} else {
alert(res.message || '保存失败');
}
});
});
}
(function() {
bindEditPageEvent();
}());
... ...
... ... @@ -5,8 +5,29 @@ require('bootpag/lib/jquery.bootpag.min');
const _ = require('lodash');
function bind_table_pagination() {
const $ul = $('.user-list');
const $up = $('.user-pagination');
const $ul = $('.coupon-list');
const $up = $('.coupon-pagination');
let uploadFn = function() {
let $form = $('#uploadForm')[0];
let formData = new FormData($form);
formData.append('id', $(this).data('id'));
if ($form) {
$.ajax({
url: '/admin/api/coupon/batchAddNo',
data: formData,
method: 'POST',
cache: false,
processData: false,
contentType: false,
success: function() {
alert('导入成功');//eslint-disable line
}
});
}
};
const fetchRender = (pageNo, pageSize) => {
$.ajax({
... ... @@ -41,18 +62,22 @@ function bind_table_pagination() {
<td class="">${item.couponName}</td>
<td class="">${item.couponDesc}</td>
<td class="">${item.shopName}</td>
<td class="">${item.shopLogoUrl}</td>
<td class="">${item.status}</td>
<td class=""><img src="${item.shopLogoUrl}" width="100%"/></td>
<td class="">${item.statusText}</td>
<td class="">${item.typeText}</td>
<td class="">${item.sort}</td>
<td class="">${item.createTime}</td>
<td class="">
<button class="btn btn-danger" data-id="${item.id}">导入券码
<form id="uploadForm" enctype="multipart/form-data">
<div class="x_title form-group">
<input id="up_excel" name="up_excel" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" style="display: inline-block; width: 80%;" type="file">
<button type="button" class="btn btn-danger btn-import" style="display: inline-block;" data-id="${item.id}">导入券码</button>
<div class="clearfix"></div>
</div>
</form>
<button class="btn btn-danger btn-export-no" data-id="${item.id}">导出券码
</button>
<button class="btn btn-danger" data-id="${item.id}">导出券码
</button>
<button class="btn btn-danger" data-id="${item.id}">修改</button>
<a class="btn btn-danger btn-update" href="/admin/coupon/update?id=${item.id}">修改</a>
</td>
</tr>`;
});
... ... @@ -67,10 +92,19 @@ function bind_table_pagination() {
}).on('page', function(event, num) {
fetchRender(num, 20);
});
}
$('.btn-export-no').on('click', function() {
let id = $(this).attr('data-id');
window.location = '/admin/api/coupon/downloadNo?id=' + id;
});
$('.btn-import').on('click', uploadFn);
});
};
fetchRender(1, 20);
}
... ...
... ... @@ -366,7 +366,7 @@ function bindEditPageEvent() {
uploadedFn = new GenUploadedFn($this);
$upload[0].click();
break;
default:
default: //eslint-disable-line
let info = floorContent[data.cid];
let content = info.newContent || info.content;
let $dom = $(`<textarea class="edit-text form-control">${content || ''}</textarea>`);
... ...
@import '~bootstrap-daterangepicker';
.radi{
white-space: nowrap;
overflow: hidden;
padding: 0 30px 0 10px;
display: inline-block;
}
... ...