|
@@ -2,15 +2,19 @@ import { CVO_match, FVO_match, QVO_match, UVAO_match } from '../../interface/pla
|
|
|
import { ApiResponse, ApiTags, ApiQuery } from '@midwayjs/swagger';
|
|
|
import { Validate } from '@midwayjs/validate';
|
|
|
import { Controller, Inject, Get, Param, Post, Body, Del, Query, Config } from '@midwayjs/core';
|
|
|
-import { cloneDeep, get, omit, pick } from 'lodash';
|
|
|
+import { cloneDeep, get, head, isArray, omit, pick } from 'lodash';
|
|
|
import { ServiceError, ErrorCode } from '../../error/service.error';
|
|
|
import { BaseController } from '../../frame/BaseController';
|
|
|
import { MatchRegistrationService } from '../../service/match/matchRegistration.service';
|
|
|
import { UserService } from '../../service/system/user.service';
|
|
|
import * as bcrypt from 'bcryptjs';
|
|
|
+import * as Path from 'path'
|
|
|
import dayjs = require('dayjs');
|
|
|
import { MatchService } from '../../service/platform/match.service';
|
|
|
import { ServiceUtilService } from '../../service/serviceUtil.service';
|
|
|
+import * as Excel from 'exceljs';
|
|
|
+import { UtilService } from '../../service/util.service';
|
|
|
+import { AliyunSmsService } from '../../service/thirdParty/aliyunSms.service';
|
|
|
const namePrefix = '创新大赛-赛事报名';
|
|
|
@ApiTags(['创新大赛-赛事报名'])
|
|
|
@Controller('/matchReg', { tagName: namePrefix })
|
|
@@ -23,6 +27,12 @@ export class MatchRegistrationController implements BaseController {
|
|
|
matchService: MatchService
|
|
|
@Inject()
|
|
|
serviceUtil: ServiceUtilService
|
|
|
+ @Inject()
|
|
|
+ utilService: UtilService;
|
|
|
+ @Inject()
|
|
|
+ smsService: AliyunSmsService
|
|
|
+
|
|
|
+
|
|
|
@Config('PathConfig.path')
|
|
|
path;
|
|
|
@Get('/')
|
|
@@ -64,6 +74,7 @@ export class MatchRegistrationController implements BaseController {
|
|
|
async create(@Body() data: object) {
|
|
|
const userColumns = ['user', 'user_id'];
|
|
|
let regData = omit(data, userColumns);
|
|
|
+ let returnUser = {};
|
|
|
if (get(data, 'user_id')) {
|
|
|
regData = { user_id: get(data, 'user_id'), ...regData };
|
|
|
} else {
|
|
@@ -74,12 +85,18 @@ export class MatchRegistrationController implements BaseController {
|
|
|
await this.userService.checkPhone(user);
|
|
|
await this.userService.checkEmail(user);
|
|
|
// 处理密码, TODO:可能会要自动生成
|
|
|
- const passowrd = get(user, 'password');
|
|
|
- if (passowrd) {
|
|
|
+ const password = get(user, 'password');
|
|
|
+ if (password) {
|
|
|
const salt = bcrypt.genSaltSync(10);
|
|
|
- const hash = bcrypt.hashSync(passowrd, salt);
|
|
|
+ const hash = bcrypt.hashSync(password, salt);
|
|
|
Object.assign(user, { password: hash });
|
|
|
}
|
|
|
+ returnUser = { password, account: get(user, 'account') }
|
|
|
+ // 发送短信
|
|
|
+ const phone = get(user, 'phone')
|
|
|
+ const msg = { "账号": get(user, 'account'), "密码": password }
|
|
|
+ await this.smsService.send(phone, msg)
|
|
|
+
|
|
|
const dbData = await this.userService.create(user);
|
|
|
regData = { user_id: dbData.id, ...regData };
|
|
|
}
|
|
@@ -92,7 +109,8 @@ export class MatchRegistrationController implements BaseController {
|
|
|
const no = `${get(data, 'match_id')}-${get(regData, 'user_id')}-${matchRegTotal + 1}`;
|
|
|
regData = { ...regData, no, time: dayjs().format('YYYY-MM-DD HH:mm:ss') };
|
|
|
const result = await this.service.create(regData);
|
|
|
- return result;
|
|
|
+ if (Object.keys(returnUser).length > 0) return returnUser
|
|
|
+ else return result;
|
|
|
}
|
|
|
|
|
|
@Post('/:id', { routerName: `修改${namePrefix}` })
|
|
@@ -121,7 +139,7 @@ export class MatchRegistrationController implements BaseController {
|
|
|
return data;
|
|
|
}
|
|
|
|
|
|
- @Get('/detail/:match_id')
|
|
|
+ @Get('/export/:match_id')
|
|
|
@ApiTags('导出初赛名单')
|
|
|
async exportList(@Param('match_id') match_id: string) {
|
|
|
// 查询所有未被退回的报名信息
|
|
@@ -133,32 +151,114 @@ export class MatchRegistrationController implements BaseController {
|
|
|
if (!match) throw new ServiceError(ErrorCode.MATCH_NOT_FOUND)
|
|
|
// 获取赛事名称, 赛事名称放首行
|
|
|
const match_name = get(match, 'name')
|
|
|
+ const fir = ['赛事名称', match_name]
|
|
|
// 第二行是表头: 项目编号,用户名称,报名时间, ...报名信息,分数
|
|
|
- const header = ['项目编号', '用户名称', '报名时间']
|
|
|
- // TODO: 将报名信息的问题塞进来
|
|
|
-
|
|
|
+ const sec = ['项目编号', '用户名称', '报名时间']
|
|
|
+ // 将报名信息的问题塞进来, 并整理出文件列,计算出文件列的字母
|
|
|
+ const firstData = head(data);
|
|
|
+ const info = get(firstData, 'info', [])
|
|
|
+ const fileColumns = []
|
|
|
+ for (const i of info) {
|
|
|
+ const problem = get(i, 'problem')
|
|
|
+ sec.push(problem)
|
|
|
+ const type = get(i, 'type')
|
|
|
+ if (type === '5' || type === '7') {
|
|
|
+ const length = sec.length
|
|
|
+ const col = this.utilService.ToExcelColumn(length - 1)
|
|
|
+ const sid = get(i, 'sid')
|
|
|
+ fileColumns.push({ sid, col })
|
|
|
+ }
|
|
|
+ }
|
|
|
// 填充分数列
|
|
|
- header.push('分数')
|
|
|
- const list = [];
|
|
|
+ sec.push('分数')
|
|
|
+ const workbook = new Excel.Workbook();
|
|
|
+ const sheet = workbook.addWorksheet('sheet1');
|
|
|
+ sheet.addRow(fir)
|
|
|
+ sheet.addRow(sec);
|
|
|
// 整理数据
|
|
|
- for (const i of data) {
|
|
|
+ for (let index = 0; index < data.length; index++) {
|
|
|
+ const i = data[index];
|
|
|
const item = [];
|
|
|
// 项目编号
|
|
|
const no = get(i, 'no', "")
|
|
|
item.push(no)
|
|
|
// 用户信息
|
|
|
const user = await this.userService.fetch({ id: get(i, 'user_id') });
|
|
|
- if (user) item.push(get(user, 'nick_nake'))
|
|
|
+ if (user) item.push(get(user, 'nick_name'))
|
|
|
else item.push("")
|
|
|
// 报名时间
|
|
|
const time = get(i, 'time', "")
|
|
|
item.push(time)
|
|
|
- // TODO:处理报名信息
|
|
|
+ // 处理报名信息
|
|
|
+ const info = get(i, 'info', [])
|
|
|
+ for (const is of info) {
|
|
|
+ const type = get(is, 'type')
|
|
|
+ const val = get(is, 'reply', '')
|
|
|
+ // 0:单选;2:下拉;3:单行文本;4:多行文本
|
|
|
+ if (type === '0' || type === '2' || type === '3' || type === '4') {
|
|
|
+ item.push(val)
|
|
|
+ } else if (type === '1') {
|
|
|
+ // 1:多选
|
|
|
+ item.push(val.join(';'))
|
|
|
+ } else if (type === '5' || type === '7') {
|
|
|
+ // 5:图片/文件;7:附件模板
|
|
|
+ // 1.需要确定单元格位置,生成超链接格式,1个单元格只能有一个超链接
|
|
|
+ const sid = get(is, 'sid')
|
|
|
+ const headFile = head(val)
|
|
|
+ const link = {
|
|
|
+ text: get(headFile, 'name'),
|
|
|
+ hyperlink: get(headFile, 'uri'),
|
|
|
+ toolTip: get(headFile, 'uri'),
|
|
|
+ }
|
|
|
+ const posObj = fileColumns.find(f => f.sid === sid)
|
|
|
+ if (!posObj) continue;
|
|
|
+ posObj.value = link;
|
|
|
+ posObj.line = index + 1 + 2; // +1 是索引加1为行数; +2 是表格从第三行开始的
|
|
|
+ // for (const valI of val) {
|
|
|
+ // const fileName = get(valI, 'name')
|
|
|
+ // // TODO:需要加上域名
|
|
|
+ // const uri = get(valI, 'uri')
|
|
|
+ // const link = {
|
|
|
+ // text: fileName,
|
|
|
+ // hyperlink: uri,
|
|
|
+ // tooltip: uri
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+
|
|
|
+ } else if (type === '6') {
|
|
|
+ // 6:数组,将sid外的 key,value拼成字符串作为内容
|
|
|
+ if (!isArray(val)) continue;
|
|
|
+ const midArr = []
|
|
|
+ for (const valI of val) {
|
|
|
+ const obj = omit(valI, ['sid'])
|
|
|
+ for (const key in obj) {
|
|
|
+ midArr.push(`${key}: ${obj[key]}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 将键值对拼成字符串
|
|
|
+ item.push(midArr.join(';'))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sheet.addRow(item)
|
|
|
+ }
|
|
|
+ // 处理超链接
|
|
|
+ for (const i of fileColumns) {
|
|
|
+ const col = get(i, 'col')
|
|
|
+ const line = get(i, 'line')
|
|
|
+ const pos = `${col}${line}`
|
|
|
+ const value = get(i, 'value')
|
|
|
+ if (!value) continue;
|
|
|
+ sheet.getCell(pos).value = value
|
|
|
}
|
|
|
- const filename = `${match_name}初赛名单.xlsx`
|
|
|
+ const nowDate = new Date().getTime();
|
|
|
+ const filename = `${match_name}初赛名单-${nowDate}.xlsx`
|
|
|
const path = this.path;
|
|
|
if (!path) {
|
|
|
throw new ServiceError('服务端没有设置存储路径');
|
|
|
}
|
|
|
+ const filepath = Path.resolve(path, filename);
|
|
|
+ await workbook.xlsx.writeFile(filepath);
|
|
|
+ return `/files/cxyy/export/${filename}`;
|
|
|
}
|
|
|
}
|