Commit e5268415 authored by 任国军's avatar 任国军

add export

parent 4b47e173
Pipeline #24841 passed with stage
in 1 minute 34 seconds
......@@ -76,6 +76,25 @@ class BackController extends Controller {
const ret = await service.course.back.getClassList(queryParams);
ctx.success(ret);
}
// 导出订单
async exportOrder() {
const { ctx, service } = this;
const queryParams = ctx.request.query;
await service.course.back.exportOrder(queryParams);
// ctx.success(ret);
}
// 导出兑换码
async exportRedeemCode() {
const { ctx, service } = this;
const queryParams = ctx.request.query;
await service.course.back.exportRedeemCode(queryParams);
}
}
module.exports = BackController;
......@@ -10,4 +10,6 @@ module.exports = app => {
router.get('third', '/redeem', auth, 'course.back.getRedeemCodeList');
router.post('third', '/redeem', auth, 'course.back.addRedeemCode');
router.get('third', '/class', auth, 'course.back.getClassList');
router.get('third', '/export/order', auth, 'course.back.exportOrder');
router.get('third', '/export/redeem', auth, 'course.back.exportRedeemCode');
};
......@@ -119,7 +119,7 @@ class BackService extends Service {
};
}
let classList = await ctx.classModel.V5.CourseV5Class.findAll({ where: { id: { $in: R.pluck('class_id', orderList.rows) } }, attributes: [ 'id', 'name' ], raw: true });
let classList = await ctx.classModel.V5.CourseV5Class.findAll({ where: { id: { $in: _.uniq(R.pluck('class_id', orderList.rows)) } }, attributes: [ 'id', 'name' ], raw: true });
classList = _.groupBy(classList, 'id');
const results = [];
......@@ -177,7 +177,7 @@ class BackService extends Service {
};
}
let classList = await ctx.classModel.V5.CourseV5Class.findAll({ where: { id: { $in: R.pluck('class_id', redeemCodeList.rows) } }, attributes: [ 'id', 'name' ], raw: true });
let classList = await ctx.classModel.V5.CourseV5Class.findAll({ where: { id: { $in: _.uniq(R.pluck('class_id', redeemCodeList.rows)) } }, attributes: [ 'id', 'name' ], raw: true });
classList = _.groupBy(classList, 'id');
const results = [];
......@@ -277,6 +277,104 @@ class BackService extends Service {
list: results,
};
}
// 导出订单
async exportOrder(input) {
const { ctx } = this;
input.page = 1;
input.limit = 9999;
let orderList = await this.getOrderList(input);
orderList = orderList.list;
if (ctx.isEmpty(orderList)) {
ctx.failed('无有效数据');
}
const results = [];
for (const v of orderList) {
const address = ctx.isEmpty(v.address) ? { province: '', city: '', area: '', address: '', name: '', phone: '' } : v.address;
results.push({
order_no: v.order_no,
class_id: v.class_id,
class_name: v.class_name,
pay: v.pay,
pay_time: v.pay_time,
type: v.type === 1 ? '微信支付' : '兑换码',
redeem: v.redeem,
province: address.province,
city: address.city,
area: address.area,
address: address.address,
name: address.name,
phone: address.phone,
});
}
await ctx.service.excel
.newExport()
.newSheet(results, '订单列表')
.newColumn('order_no', '订单编号')
.newColumn('class_name', '课程')
.newColumn('pay', '支付金额')
.newColumn('pay_time', '支付时间')
.newColumn('type', '支付类型')
.newColumn('redeem', '兑换码')
.newColumn('province', '省')
.newColumn('city', '市')
.newColumn('area', '区')
.newColumn('address', '详细地址')
.newColumn('name', '姓名')
.newColumn('phone', '手机')
.setFileName('订单列表')
.get();
return true;
}
// 导出兑换码
async exportRedeemCode(input) {
const { ctx } = this;
input.page = 1;
input.limit = 9999;
let orderList = await this.getRedeemCodeList(input);
orderList = orderList.list;
if (ctx.isEmpty(orderList)) {
ctx.failed('无有效数据');
}
const results = [];
for (const v of orderList) {
results.push({
class_name: v.class_name,
code: v.code,
channel: v.channel,
is_used: v.is_used === 1 ? '是' : '否',
used_time: v.used_time,
created_time: v.created_time,
});
}
await ctx.service.excel
.newExport()
.newSheet(results, '兑换码列表')
.newColumn('class_name', '课程')
.newColumn('code', '兑换码')
.newColumn('channel', '渠道')
.newColumn('is_used', '是否已用')
.newColumn('used_time', '使用时间')
.newColumn('created_time', '生成时间')
.setFileName('兑换码列表')
.get();
return true;
}
}
module.exports = BackService;
......@@ -84,9 +84,9 @@ class OptionService extends Service {
const options = [
{ title: '所在年级', value: ageList, alias: 'age' },
{ title: '科目类型', value: categoryList, alias: 'category' },
{ title: '授课形式', value: MODE, alias: 'mode' },
{ title: '课程类型', value: PRICE_TYPE, alias: 'price_type' },
{ title: '科目类型', value: categoryList, alias: 'category' },
];
const ret = {
......
'use strict';
const Service = require('egg').Service;
const Excel = require('exceljs');
const Stream = require('stream');
const _ = require('lodash');
const contentDisposition = require('content-disposition');
const DATA = Symbol('excel_data');
const CALL = Symbol('excel_call');
class ExcelHelper {
async get(arg) {
return this._get(arg);
}
getObjectValue(obj, key) {
const keys = key.split('.');
let ret = obj;
for (let i = 0; i < keys.length; i++) {
if (ret !== undefined) {
ret = ret[keys[i]];
} else {
return undefined;
}
}
return ret;
}
}
class ExcelExportHelper extends ExcelHelper {
constructor(ctx) {
super();
this.ctx = ctx;
this.sheets = [];
this.fileName = '未命名' + (+new Date());
}
setFileName(fileName) {
this.fileName = fileName;
return this;
}
newSheet(data, sheetName = false) {
if (!sheetName) {
sheetName = 'sheet' + (this.sheets.length + 1);
}
this.sheets.push({
data,
sheetName,
columns: [],
});
return this;
}
newColumn(key, option) {
const lastSheet = this.sheets[this.sheets.length - 1];
const param = {};
if (lastSheet) {
if (typeof option === 'string') {
param.header = option;
} else {
param.header = option.title || key;
param.format = option.format;
}
}
param.key = key;
if (/\./.test(key)) {
if (!param.format) {
param.format = (val, i, row) => {
return this.getObjectValue(row, key);
};
} else {
const srcFormat = param.format;
param.format = (val, i, row) => {
return srcFormat(this.getObjectValue(row, key), row, i);
};
}
}
lastSheet.columns.push(param);
return this;
}
getFixedCellWidth(v) {
return Math.max(10, ('' + v).length * 2);
}
async _get() {
const stream = new Stream.PassThrough();
// const workbook = new Excel.Workbook();
const workbook = new Excel.stream.xlsx.WorkbookWriter({
stream,
useStyles: true,
useSharedStrings: true,
});
this.sheets.forEach(item => {
const worksheet = workbook.addWorksheet(item.sheetName);
const formatList = [];
if (item.data.length) {
const row = item.data[0];
if (item.columns.length === 0) {
for (const k in row) {
const v = row[k];
const type = typeof v;
if (type === 'string' || type === 'number') {
item.columns.push({
header: k,
key: k,
width: this.getFixedCellWidth(v),
});
}
}
} else {
for (let i = 0; i < item.columns.length; i++) {
const column = item.columns[i];
if (!column.width) {
column.width = this.getFixedCellWidth(row[column.key]);
}
if (column.format) {
formatList.push({
key: column.key,
fn: column.format,
});
}
}
}
}
worksheet.columns = item.columns;
for (let i = 0; i < item.data.length; i++) {
let row = item.data[i];
if (row instanceof Object) {
row = JSON.parse(JSON.stringify(row));
} else {
row = {};
}
for (let i = 0; i < formatList.length; i++) {
const item = formatList[i];
row[item.key] = item.fn(row[item.key], i, row);
}
worksheet.addRow(row).commit();
}
});
let { fileName } = this;
fileName += '.xlsx';
// fileName = encodeURIComponent(fileName);
// this.ctx.set('content-disposition', 'attachment;filename=' + fileName);
this.ctx.set('content-disposition', contentDisposition(fileName));
workbook.commit();
this.ctx.body = stream;
this.ctx.status = 200;
return stream;
}
}
class ExcelImportHelper extends ExcelHelper {
constructor(ctx) {
super();
this.ctx = ctx;
this.columns = [];
this[DATA] = false;
this.lineNumberKey = false;
}
showLineNumber(key) {
this.lineNumberKey = key;
return this;
}
newColumn(header, option) {
const param = {};
if (typeof option === 'string') {
param.key = option;
} else {
param.key = option.key || header;
param.format = option.format;
param.type = option.type;
param.required = option.required;
if (option.expect) {
const { expect } = option;
if (expect instanceof RegExp) {
param.expect = (val, rowNumber, colNumber) => {
if (!expect.test(val)) {
this.ctx.failed(`第${rowNumber}行第${colNumber}${header}格式不正确`);
}
};
} else if (expect instanceof Array) {
param.expect = (val, rowNumber, colNumber) => {
if (expect.indexOf(val) === -1) {
this.ctx.failed(`第${rowNumber}行第${colNumber}${header}只能为如下值:`
+ expect.join('、'));
}
};
}
}
}
param.header = header;
this.columns.push(param);
return this;
}
[CALL](i) {
const data = this[DATA][i];
const columns = [];
const ret = [];
if (data.length) {
const { values } = data[0];
for (let i = 0; i < this.columns.length; i++) {
const header = this.columns[i].header;
let find = false;
for (let j = 1; j < values.length; j++) {
let val = values[j];
if (val && val.richText) {
val = val.richText.map(item => item.text).join('');
}
if (val === header) {
const {
key,
format,
type,
expect,
required,
} = this.columns[i];
columns.push({
index: j,
key,
format,
type,
expect,
required,
});
find = true;
break;
}
}
if (!find) {
this.ctx.failed(`缺失列[${header}]`);
}
}
}
for (let i = 1; i < data.length; i++) {
const dict = {};
const { values, rowNumber } = data[i];
if (this.lineNumberKey) {
dict[this.lineNumberKey] = rowNumber;
}
for (let j = 0; j < columns.length; j++) {
const c = columns[j];
const colNumber = c.index;
let v = values[colNumber];
if (v instanceof Object) {
if ('text' in v) {
v = v.text;
} else if ('richText' in v) {
v = v.richText.map(item => item.text).join('');
} else if ('result' in v) {
v = v.result;
if (_.isNumber(v)) {
const tmp = Number(v.toFixed(4));
if (Math.abs(v - tmp) < 0.00000000001) {
v = tmp;
}
}
}
}
if (c.trim && _.isString(v)) {
v = _.trim(v);
}
if (v === undefined || v === '') {
if (c.required) {
this.ctx.failed(`第${rowNumber}行第${colNumber}列不能为空`);
}
}
switch (c.type) {
case 'int':
case 'integer':
if (!_.isInteger(v)) {
if (_.isString(v) && /^\s*\d+\s*$/.test(v)) {
v = +v;
} else if (v === undefined || v === '') {
v = null;
} else {
this.ctx.failed(`第${rowNumber}行第${colNumber}列不是整数`);
}
}
break;
case 'number':
if (!_.isNumber(v)) {
if (_.isString(v) && /^\s*\d+(?:\.\d+)?\s*$/.test(v)) {
v = +v;
} else if (v === undefined || v === '') {
v = null;
} else {
this.ctx.failed(`第${rowNumber}行第${colNumber}列不是数字`);
}
}
break;
case 'string':
if (!_.isString(v)) {
if (_.isNumber(v)) {
v = '' + v;
} else {
this.ctx.failed(`第${rowNumber}行第${colNumber}列不是字符串`);
}
}
break;
default:
break;
}
c.expect && c.expect(v, rowNumber, colNumber);
dict[c.key] = c.format ? c.format(v, rowNumber, colNumber) : v;
}
ret.push(dict);
}
this.lineNumberKey = false;
return ret;
}
async _get(stream) {
const sheetIndex = 0;
if (this[DATA]) {
return this[CALL](sheetIndex);
}
stream = stream || await this.ctx.getFileStream();
const workbook = new Excel.Workbook();
const data = [];
await workbook.xlsx.read(stream).then(workbook => {
workbook.eachSheet(function(worksheet) {
const sheetData = [];
worksheet.eachRow(function(row, rowNumber) {
sheetData.push({ rowNumber, values: row.values });
});
data.push(sheetData);
});
});
this[DATA] = data;
return this[CALL](sheetIndex);
}
}
class ExcelService extends Service {
newExport() {
return new ExcelExportHelper(this.ctx);
}
newImport() {
return new ExcelImportHelper(this.ctx);
}
}
module.exports = ExcelService;
......@@ -27,7 +27,8 @@
"request": "^2.88.0",
"uuid": "^3.3.2",
"xml2js": "^0.4.22",
"body-parser": "^1.19.0"
"body-parser": "^1.19.0",
"exceljs": "^1.6.2"
},
"devDependencies": {
"autod": "^3.0.1",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment