周沫沫历险记 2 years ago
parent
commit
f8304650a9
  1. 50
      api/app/lib/controllers/departmentTrain/index.js
  2. 8
      api/app/lib/routes/departmentTrain/index.js
  3. 2
      web/client/src/sections/humanAffairs/actions/index.js
  4. 72
      web/client/src/sections/humanAffairs/actions/personalTrainRecord.js
  5. 222
      web/client/src/sections/humanAffairs/containers/importPersonalTrainRecord.jsx
  6. 43
      web/client/src/sections/humanAffairs/containers/personalTrainRecord.jsx
  7. 6
      web/client/src/utils/webapi.js

50
api/app/lib/controllers/departmentTrain/index.js

@ -1,8 +1,16 @@
'use strict';
async function getDepartmentTrainRecordList(ctx) {
async function get(ctx) {
try {
const { limit, page } = ctx.query;
const models = ctx.fs.dc.models;
let res = await models.DeptTraining.findAndCountAll({
where: {},
offset: Number(page) * Number(limit),
limit: Number(limit),
order: [['id', 'ASC']]
})
ctx.status = 200;
ctx.body = {};
ctx.body = res;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
@ -10,9 +18,45 @@ async function getDepartmentTrainRecordList(ctx) {
message: '查询部门培训记录列表失败'
}
}
}
async function importData(ctx) {
let errorMsg = { message: '导入部门培训记录信息失败' };
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const data = ctx.request.body;
await models.DeptTraining.bulkCreate(data, { transaction });
await transaction.commit();
ctx.status = 204;
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = errorMsg;
}
}
async function modify(ctx) {
try {
const { models } = ctx.fs.dc;
const { id, ...rest } = ctx.request.body;
const existRes = await models.DeptTraining.findOne({ where: { id } });
if (!existRes) {
throw '当前部门培训记录信息不存在';
}
await models.DeptTraining.update({ ...rest }, { where: { id: id } });
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: '修改部门培训记录失败'
}
}
}
module.exports = {
getDepartmentTrainRecordList
get,
importData,
modify
}

8
api/app/lib/routes/departmentTrain/index.js

@ -5,5 +5,11 @@ const departmentTrain = require('../../controllers/departmentTrain');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/department/train/record/list'] = { content: '查询部门培训记录列表', visible: true };
router.get('/department/train/record/list', departmentTrain.getDepartmentTrainRecordList);
router.get('/department/train/record/list', departmentTrain.get);
app.fs.api.logAttr['POST/department/train/record/bulk'] = { content: '导入部门培训记录信息', visible: true };
router.post('/department/train/record/bulk', departmentTrain.importData);
app.fs.api.logAttr['PUT/department/train/record/modify'] = { content: '编辑部门培训记录信息', visible: true };
router.put('/department/train/record/modify', departmentTrain.modify);
};

2
web/client/src/sections/humanAffairs/actions/index.js

@ -4,6 +4,7 @@ import * as personnelFiles from './personnelFiles'
import * as employeeInformation from './employeeInformation'
import * as salesDistribution from './salesDistribution'
import * as departmentTrain from './departmentTrain'
import * as personalTrainRecord from './personalTrainRecord'
import * as resourceRepository from './resourceRepository'
export default {
@ -11,5 +12,6 @@ export default {
...employeeInformation,
...salesDistribution,
...departmentTrain,
...personalTrainRecord,
...resourceRepository
}

72
web/client/src/sections/humanAffairs/actions/personalTrainRecord.js

@ -0,0 +1,72 @@
'use strict';
import { ApiTable, basicAction } from '$utils'
export function personalTrainRecordAll(values) {
return dispatch => basicAction({
type: 'post',
dispatch: dispatch,
actionType: 'SALES_MEMBER_BULK_ADD',
url: ApiTable.addSalesMemberBulk,
data: values,
msg: { option: '导入销售人员信息' },
});
}
export function getPersonalTrainRecord(query) {//查询
return (dispatch) => basicAction({
type: "get",
dispatch: dispatch,
actionType: "GET_PERSONAL_TRAIN_RECORD_LIST",
query: query,
url: `${ApiTable.getPersonalTrainRecord}`,
msg: { option: "查询个人培训记录列表" },
reducer: { name: "personalTrainRecordList", params: { noClear: true } },
});
}
// export function delSalesMember(data) {//删除
// let msg = ''
// if (data) {
// msg = data.msg
// }
// return (dispatch) =>
// basicAction({
// type: "del",
// query: data,
// dispatch: dispatch,
// actionType: "DEL_SALES_MEMBER",
// url: `${ApiTable.delSalesMember}`,
// msg: { option: msg }, //删除
// reducer: {},
// });
// }
// export function getMemberExport(query) {//导出员工信息
// return (dispatch) => basicAction({
// type: "get",
// dispatch: dispatch,
// actionType: "GET_MemberEXPORT",
// query: query,
// url: `${ApiTable.getMemberExport}`,
// msg: { option: "导出员工信息" },
// reducer: { name: "MemberExport", params: { noClear: true } },
// });
// }
// export function editSalesMember(data) {//更新
// let msg = ''
// if (data) {
// msg = data.msg
// }
// return (dispatch) =>
// basicAction({
// type: "put",
// dispatch: dispatch,
// data,
// actionType: "PUT_SALES_MEMBER",
// url: `${ApiTable.editSalesMember}`,
// msg: { option: msg }, //更新
// reducer: {},
// });
// }

222
web/client/src/sections/humanAffairs/containers/importPersonalTrainRecord.jsx

@ -0,0 +1,222 @@
'use strict';
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import moment from 'moment';
import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui';
import { IconUpload } from '@douyinfe/semi-icons';
import XLSX from 'xlsx'
//
const ImportPerformanceSummaryModal = props => {
const { dispatch, actions, onCancel } = props;
const { businessManagement } = actions
const [msg, setMsg] = useState('');
const [loading, setLoading] = useState('');
const [postData, setPostData] = useState([]);
let personalTrainRecord = { index: '序号', personalName: '姓名', departmentName: '部门', trainingType: '培训类型', topic: '课程主题', trainer: '培训讲师', trainDate: '培训时间', trainTime: '培训时长', trainMethod: '培训方式', attendanceScore: '考勤分数', appraisalMethod: '考核形式', appraisalScore: '考核分数', totalScore: '总分' }
//
const confirm = () => {
if (postData.length) {
setLoading(true)
// dispatch(businessManagement.importSalePerformance(postData)).then(res => {
// if (res.success) {
// onCancel()
// }
// setLoading(false)
// })
} else {
Notification.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 })
}
}
const dldCsvMb = () => {
//
let head = [];
Object.keys(personalTrainRecord).map(key => {
head.push(personalTrainRecord[key]);
})
head = head.join(',') + "\n";
//
//let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5
let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head;
//a
let link = document.createElement("a");
//a
link.setAttribute("href", templateCsv);
link.setAttribute("download", "个人培训记录表模板.csv");
//a
link.click();
}
const download = () => {
dldCsvMb();
}
const fileLimit = '.csv';
const getFileBlob = (url) => {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest()
request.open("GET", url, true)
request.responseType = "blob"
request.onreadystatechange = e => {
if (request.readyState == 4) {
if (request.status == 200) {
if (window.FileReader) {
let reader = new FileReader();
reader.readAsBinaryString(request.response);
reader.onload = event => {
try {
const { result } = event.target;
// excel
const workbook = XLSX.read(result, {
type: "binary",
cellDates: true,//true
codepage: 936,//
});
let data = []; //
//
for (const sheet in workbook.Sheets) {
if (workbook.Sheets.hasOwnProperty(sheet)) {
data = data.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet]));
}
}
resolve(data);//
} catch (e) {
reject("失败");
}
}
}
}
}
}
request.send();
})
}
// const judgeNull = (value) => {
// return value ? String(value).trim().replace(/\s*/g, "") : null;
// }
// const judgeNullTime = (v) => {
// //xlsxexcel
// //2020/8/1xlsx Fri Jul 31 2020 23:59:17 GMT+0800 43
// let a = new Date(v);
// a.setTime(a.getTime() + 43 * 1000);
// return v ? a : null;
// }
const judgeTimeValid = (time) => {
let valid = true;
if (!time) {
return valid;//
}
const ymd = /^((19|20)[0-9]{2})[\/\-]((0[1-9])|(1[0-2]))[\/\-]((0[1-9])|((1|2)[0-9])|(3[0-1]))$/;//
if (time instanceof Date) {
let timeStr = moment(time).format('YYYY/MM/DD');
if (!ymd.test(timeStr)) {
valid = false;
}
} else {
valid = false;
}
return valid;
}
return (
<Modal
title="导入" visible={true}
onOk={confirm} width={620}
confirmLoading={loading}
onCancel={() => {
setMsg('')
setLoading(false)
setPostData([])
onCancel()
}}
>
<div style={{ borderBottom: '1px solid #DCDEE0', margin: '0px -24px' }}></div>
<Form>
<Form.Upload
label={'个人培训记录表'} labelPosition='left'
action={'/'} accept={fileLimit}
maxSize={200} limit={1}
onRemove={(currentFile, fileList, fileItem) => {
setMsg('');
setPostData([]);
}}
customRequest={(data) => {
const { file, onSuccess, onError } = data
getFileBlob(file.url).then(res => {
const error = (msg) => {
setMsg(msg)
onError({ message: msg })
}
if (res.length > 1000) {
error('一次性上传数据行数应小于1000行,请分批上传')
return
}
if (!res.length) {
error('请填写至少一行数据')
return
}
let postData = [];
let zzsPattern = /^[+]{0,1}(\d+)$/;//
for (let i = 0; i < res.length; i++) {
let d = res[i];
let obj = {};
Object.keys(personalTrainRecord).map(key => {
obj[key] = d[personalTrainRecord[key]] || null;
//}
})
let tValid = judgeTimeValid(obj.recConDate);
// if (!tValid) {
// error(`${i + 2}yyyy/mm/dd`)
// return
// }
// if (obj.isApproval && ['', ''].indexOf(obj.isApproval) == -1) {
// error(`${i + 2}`)
// return
// }
// //
// if (obj.repurchaseCount && !zzsPattern.test(obj.repurchaseCount)) {
// error(`${i + 2}`)
// return
// }
// if (obj.reproducible && ['', ''].indexOf(obj.reproducible) == -1) {
// error(`${i + 2}`)
// return
// }
postData.push(obj);
}
setPostData(postData)
let msg = '文件解析完成,点击确定按钮上传保存!'
setMsg(msg)
onSuccess({ message: msg })
})
}}>
<Button icon={<IconUpload />} theme="light">
请选择文件
</Button>
</Form.Upload>
<span>{msg}</span>
<div style={{ color: '#ccc' }}>最大不超过200M导入文件需与
<span onClick={() => download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板</span>
一致</div>
</Form>
</Modal >
)
}
function mapStateToProps(state) {
const { auth, global } = state;
return {
user: auth.user,
actions: global.actions,
}
}
export default connect(mapStateToProps)(ImportPerformanceSummaryModal);

43
web/client/src/sections/humanAffairs/containers/personalTrainRecord.jsx

@ -3,13 +3,24 @@ import { connect } from 'react-redux';
import { Table, Pagination, Skeleton } from '@douyinfe/semi-ui';
import { SkeletonScreen } from "$components";
import '../style.less'
import ImportPersonalTrainRecord from './importPersonalTrainRecord'
const PersonalTrainRecord = (props) => {
const { dispatch, actions } = props
const [limits, setLimits] = useState()//
const [query, setQuery] = useState({ limit: 10, page: 0 }); //
const [importModalV, setImportModalV] = useState(false); //
console.log(actions, '================')
useEffect(() => {
const { getPersonalTrainRecord } = actions.humanAffairs
dispatch(getPersonalTrainRecord(query))
}, []);
const [tableData, setTableData] = useState([{ id: 1, personalName: '张三', departmentName: '营销第一军团', trainingType: '内部培训', topic: '新员工入职培训', trainer: '李四', trainDate: '2022-12-30 18:30', trainTime: '1小时', trainMethod: '线下', attendanceScore: '95', appraisalMethod: '笔试', appraisalScore: '86', totalScore: '80' }, { id: 2, personalName: '张三', departmentName: '营销第一军团', trainingType: '内部培训', topic: '新员工入职培训', trainer: '李四', trainDate: '2022-12-30 18:30', trainTime: '1小时', trainMethod: '线下', attendanceScore: '95', appraisalMethod: '笔试', appraisalScore: '86', totalScore: '80' }, { id: 3, personalName: '张三', departmentName: '营销第一军团', trainingType: '内部培训', topic: '新员工入职培训', trainer: '李四', trainDate: '2022-12-30 18:30', trainTime: '1小时', trainMethod: '线下', attendanceScore: '95', appraisalMethod: '笔试', appraisalScore: '86', totalScore: '80' }, { id: 4, personalName: '张三', departmentName: '营销第一军团', trainingType: '内部培训', topic: '新员工入职培训', trainer: '李四', trainDate: '2022-12-30 18:30', trainTime: '1小时', trainMethod: '线下', attendanceScore: '95', appraisalMethod: '笔试', appraisalScore: '86', totalScore: '80' }]);
useEffect(() => {
}, [])
function handleRow(record, index) {//
if (index % 2 === 0) {
@ -23,25 +34,6 @@ const PersonalTrainRecord = (props) => {
}
}
const download = () => {
//
let head = [];
let headData = { index: '序号', personalName: '姓名', departmentName: '部门', trainingType: '培训类型', topic: '课程主题', trainer: '培训讲师', trainDate: '培训时间', trainTime: '培训时长', trainMethod: '培训方式', attendanceScore: '考勤分数', appraisalMethod: '考核形式', appraisalScore: '考核分数', totalScore: '总分' }
Object.keys(headData).map(key => {
head.push(headData[key]);
})
head = head.join(',') + "\n";
//
let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head;
//a
let link = document.createElement("a");
//a
link.setAttribute("href", templateCsv);
link.setAttribute("download", "个人培训表单.csv");
//a
link.click();
}
const columns = [{
title: '序号',
dataIndex: 'id',
@ -114,7 +106,7 @@ const PersonalTrainRecord = (props) => {
width: 120
}];
const scroll = useMemo(() => ({}), []);
return (
<>
@ -139,13 +131,10 @@ const PersonalTrainRecord = (props) => {
<div style={{ display: 'flex', margin: '16px 0px', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', marginRight: 20 }}>
<div style={{ padding: '6px 20px', background: '#0F7EFB', color: '#FFFFFF', fontSize: 14, cursor: "pointer" }}
// onClick={() => { setImportModalV(true); }}
onClick={() => { setImportModalV(true); }}
>
导入
</div>
<div style={{ padding: '6px 20px', background: '#00BA85', color: '#FFFFFF', fontSize: 14, marginLeft: 18, cursor: "pointer" }} onClick={() => download()}>
导入模板下载
</div>
</div>
</div>
<div style={{ marginTop: 20 }}>
@ -190,6 +179,12 @@ const PersonalTrainRecord = (props) => {
</div>
</div>
</div>
{
importModalV ? <ImportPersonalTrainRecord
onCancel={() => {
setImportModalV(false);
}} /> : ''
}
</div>
</>
)

6
web/client/src/utils/webapi.js

@ -42,11 +42,17 @@ export const ApiTable = {
getDepartmentTrainRecord: 'department/train/record/list',
importDepartmentTrainRecord: 'department/train/record/bulk',
modifyDepartmentTrainRecord: 'department/train/record/modify',
//个人培训记录
getPersonalTrainRecord: 'personal/train/record/list',
//-培训资料库
getResourceClassify: 'train/trainFiles/resourceRepository/classify',
postResourceClassify: 'train/trainFiles/resourceRepository/classify',
putResourceClassify: 'train/trainFiles/resourceRepository/classify/{id}',
delResourceClassify: 'train/trainFiles/resourceRepository/classify/{id}',
};
export const RouteTable = {
apiRoot: "/api/root",

Loading…
Cancel
Save