From 352afdba4627237e1b815dd3382745590fa73a4a Mon Sep 17 00:00:00 2001 From: ww664853070 Date: Mon, 19 Dec 2022 09:18:56 +0800 Subject: [PATCH] =?UTF-8?q?(+)=E4=B8=AA=E4=BA=BA=E5=9F=B9=E8=AE=AD?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=EF=BC=8C=E5=88=97=E8=A1=A8=EF=BC=8C=E5=AF=BC?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sections/humanAffairs/actions/index.js | 4 +- .../actions/personalTrainRecord.js | 72 ++++++ .../containers/importPersonalTrainRecord.jsx | 222 ++++++++++++++++++ .../containers/personalTrainRecord.jsx | 43 ++-- web/client/src/utils/webapi.js | 5 +- 5 files changed, 320 insertions(+), 26 deletions(-) create mode 100644 web/client/src/sections/humanAffairs/actions/personalTrainRecord.js create mode 100644 web/client/src/sections/humanAffairs/containers/importPersonalTrainRecord.jsx diff --git a/web/client/src/sections/humanAffairs/actions/index.js b/web/client/src/sections/humanAffairs/actions/index.js index f75ecde..809f472 100644 --- a/web/client/src/sections/humanAffairs/actions/index.js +++ b/web/client/src/sections/humanAffairs/actions/index.js @@ -4,9 +4,11 @@ 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' export default { ...personnelFiles, ...employeeInformation, ...salesDistribution, - ...departmentTrain + ...departmentTrain, + ...personalTrainRecord } \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/actions/personalTrainRecord.js b/web/client/src/sections/humanAffairs/actions/personalTrainRecord.js new file mode 100644 index 0000000..277df99 --- /dev/null +++ b/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: {}, +// }); +// } \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/importPersonalTrainRecord.jsx b/web/client/src/sections/humanAffairs/containers/importPersonalTrainRecord.jsx new file mode 100644 index 0000000..1623cfb --- /dev/null +++ b/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) => { + // //注意的点:xlsx将excel中的时间内容解析后,会小一天 + // //如2020/8/1,xlsx会解析成 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 ( + { + setMsg('') + setLoading(false) + setPostData([]) + onCancel() + }} + > +
+
+ { + 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 }) + }) + }}> + + + {msg} +
最大不超过200M,导入文件需与 + download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板 + 一致
+
+
+ ) +} + +function mapStateToProps(state) { + const { auth, global } = state; + return { + user: auth.user, + actions: global.actions, + } +} + +export default connect(mapStateToProps)(ImportPerformanceSummaryModal); \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/personalTrainRecord.jsx b/web/client/src/sections/humanAffairs/containers/personalTrainRecord.jsx index 0c00bdb..c9e5a90 100644 --- a/web/client/src/sections/humanAffairs/containers/personalTrainRecord.jsx +++ b/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) => {
{ setImportModalV(true); }} + onClick={() => { setImportModalV(true); }} > 导入
-
download()}> - 导入模板下载 -
@@ -190,6 +179,12 @@ const PersonalTrainRecord = (props) => {
+ { + importModalV ? { + setImportModalV(false); + }} /> : '' + } ) diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index 0c29fa0..ce9a884 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -37,11 +37,14 @@ export const ApiTable = { editSalesMember: 'sales/member/modify', delSalesMember: 'sales/member/del', addSalesMemberBulk: 'add/sales/members/bulk', - + //部门培训记录 getDepartmentTrainRecord: 'department/train/record/list', importDepartmentTrainRecord: 'department/train/record/bulk', modifyDepartmentTrainRecord: 'department/train/record/modify', + + //个人培训记录 + getPersonalTrainRecord: 'personal/train/record/list', }; export const RouteTable = { apiRoot: "/api/root",