zmh
2 years ago
4 changed files with 728 additions and 4 deletions
@ -1,3 +1,38 @@ |
|||
'use strict'; |
|||
|
|||
import { ApiTable, basicAction } from '$utils' |
|||
import { ApiTable, basicAction } from '$utils' |
|||
|
|||
export function importDepartmentTrainRecord(data) { |
|||
return dispatch => basicAction({ |
|||
type: 'post', |
|||
dispatch: dispatch, |
|||
actionType: 'IMPORT_DEPARTMENT_TRAIN_RECORD', |
|||
url: ApiTable.importDepartmentTrainRecord, |
|||
data: data, |
|||
msg: { option: '导入部门培训记录' }, |
|||
}); |
|||
} |
|||
|
|||
export function getDepartmentTrainRecord(query) {//查询
|
|||
return (dispatch) => basicAction({ |
|||
type: "get", |
|||
dispatch: dispatch, |
|||
actionType: "GET_DEPARTMENT_TRAIN_RECORD_LIST", |
|||
query: query, |
|||
url: `${ApiTable.getDepartmentTrainRecord}`, |
|||
msg: { option: "查询部门培训记录列表" }, |
|||
reducer: { name: "departmentTrainRecord", params: { noClear: true } }, |
|||
}); |
|||
} |
|||
|
|||
export function modifyDepartmentTrainRecord(data) { |
|||
return (dispatch) => |
|||
basicAction({ |
|||
type: "put", |
|||
dispatch: dispatch, |
|||
data, |
|||
actionType: "PUT_DEPARTMENT_TRAIN_RECORD", |
|||
url: `${ApiTable.modifyDepartmentTrainRecord}`, |
|||
msg: { option: "修改部门培训记录" } |
|||
}); |
|||
} |
@ -0,0 +1,247 @@ |
|||
import React, { useEffect, useRef, useState } from 'react'; |
|||
import { connect } from "react-redux"; |
|||
import { Select, Modal, Form, Button, Toast } from "@douyinfe/semi-ui"; |
|||
import { IconUpload } from '@douyinfe/semi-icons'; |
|||
const EditModal = (props) => { |
|||
const { dispatch, actions, user, onCancel, dataToEdit, apiRoot, close } = props; |
|||
const { humanAffairs } = actions; |
|||
const form = useRef();//表单
|
|||
const [options, setOptions] = useState([]); |
|||
const [attachPath, setAttachPath] = useState(); |
|||
const [attachName, setAttachName] = useState(); |
|||
const [fileName, setFileName] = useState(); |
|||
const [fileSize, setFileSize] = useState(); |
|||
const [fileType, setFileType] = useState(); |
|||
const [validateStatus, setValidateStatus] = useState('default'); |
|||
//初始化
|
|||
useEffect(() => { |
|||
let optionData = []; |
|||
if (user.allDepartment && user.allDepartment.departments) { |
|||
optionData = user.allDepartment.departments.filter(d => !d.delete).map(d => <Select.Option value={d.name} key={d.id}> |
|||
{d.name} |
|||
</Select.Option>) |
|||
setOptions(optionData); |
|||
} |
|||
if (dataToEdit?.attachPath) { |
|||
setAttachPath(dataToEdit?.attachPath); |
|||
let str = dataToEdit.attachPath.split('/'); |
|||
let len = str.length; |
|||
setAttachName(str[len - 1]); |
|||
|
|||
setFileName(dataToEdit?.fileName); |
|||
setFileSize(dataToEdit?.fileSize); |
|||
setFileType(dataToEdit?.fileType); |
|||
} |
|||
}, []); |
|||
|
|||
const handleOk = () => { |
|||
form.current.validate().then((values) => { |
|||
if (dataToEdit) { |
|||
values.attachPath = attachPath; |
|||
if (!values.trainWho) { |
|||
values.trainWho = null; |
|||
} |
|||
if (!values.trainer) { |
|||
values.trainer = null; |
|||
} |
|||
dispatch(humanAffairs.modifyDepartmentTrainRecord({ id: dataToEdit.id, fileName, fileSize, fileType, ...values })).then((res) => { |
|||
if (res.success) { |
|||
close(); |
|||
} |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
const uploadOnChange = (fileList, currentFile, event) => { |
|||
if (fileList.currentFile.status == "success") { |
|||
setAttachPath(fileList.currentFile?.response?.key); |
|||
let index = fileList.currentFile.name.lastIndexOf('.'); |
|||
setFileName(fileList.currentFile.name.substring(0, index)); |
|||
setFileType(fileList.currentFile.name.substring(index)); |
|||
setFileSize(fileList.currentFile.size); |
|||
} |
|||
if (fileList.fileList.length == 0) { |
|||
setAttachPath(null); |
|||
setFileName(null); |
|||
setFileType(null); |
|||
setFileSize(null); |
|||
} |
|||
} |
|||
const validate = (val, values) => { |
|||
if (!val) { |
|||
setValidateStatus('error'); |
|||
return <span>不能为空</span>; |
|||
} else if (val && val.trim() === '') { |
|||
setValidateStatus('error'); |
|||
return <span>不能为空格</span>; |
|||
} else if (val && val.length > 20) { |
|||
setValidateStatus('error'); |
|||
return <span>最多20个字符</span>; |
|||
} else { |
|||
setValidateStatus('success'); |
|||
return ''; |
|||
} |
|||
}; |
|||
|
|||
const validateContent = (val, values) => { |
|||
if (!val) { |
|||
setValidateStatus('error'); |
|||
return <span>不能为空</span>; |
|||
} else if (val && val.trim() === '') { |
|||
setValidateStatus('error'); |
|||
return <span>不能为空格</span>; |
|||
} else if (val && val.length > 200) { |
|||
setValidateStatus('error'); |
|||
return <span>最多200个字符</span>; |
|||
} else { |
|||
setValidateStatus('success'); |
|||
return ''; |
|||
} |
|||
}; |
|||
return ( |
|||
<Modal title={dataToEdit?.id ? '修改部门培训' : '新增部门培训'} |
|||
visible={true} |
|||
destroyOnClose |
|||
okText='保存' width={600} |
|||
onOk={handleOk} |
|||
onCancel={onCancel}> |
|||
<Form getFormApi={(formApi) => (form.current = formApi)} |
|||
labelPosition={'left'} labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}> |
|||
<Form.Select |
|||
initValue={dataToEdit?.departmentName || null} |
|||
label="部门名称" |
|||
field='departmentName' |
|||
showClear |
|||
rules={[{ required: true, message: '请选择部门名称' }]} |
|||
placeholder='请选择部门名称' |
|||
filter |
|||
style={{ width: '100%' }} |
|||
disabled={dataToEdit?.id ? true : false} |
|||
> |
|||
{options} |
|||
</Form.Select> |
|||
<Form.Input |
|||
field="trainingType" |
|||
label="培训类型" |
|||
style={{ width: '100%' }} |
|||
initValue={dataToEdit?.trainingType || ""} |
|||
placeholder="请输入培训类型" |
|||
showClear |
|||
validate={validate} |
|||
validateStatus={validateStatus} |
|||
/> |
|||
<Form.DatePicker |
|||
field='trainDate' |
|||
type='dateTime' |
|||
label="培训时间" |
|||
initValue={dataToEdit?.trainDate || new Date()} |
|||
placeholder="请选择培训时间" |
|||
style={{ width: '100%' }} |
|||
rules={[{ required: true, message: "培训时间不可空" }]} |
|||
disabled={dataToEdit?.id ? true : false} |
|||
/> |
|||
<Form.Input |
|||
field="trainContent" |
|||
label="培训内容" |
|||
style={{ width: '100%' }} |
|||
initValue={dataToEdit?.trainContent || ""} |
|||
placeholder="请输入培训内容" |
|||
showClear |
|||
|
|||
validate={validateContent} |
|||
validateStatus={validateStatus} |
|||
// rules={[{
|
|||
// required: true, message: "培训内容不可空"
|
|||
// }, {
|
|||
// max: 200, message: "最多200个字符"
|
|||
// }]}
|
|||
/> |
|||
<Form.Input |
|||
field="trainWho" |
|||
label='针对人群' |
|||
style={{ width: '100%' }} |
|||
initValue={dataToEdit?.trainWho || ""} |
|||
placeholder="请输入培训针对人群" |
|||
showClear |
|||
/> |
|||
<Form.Input |
|||
field="trainer" |
|||
label='培训讲师' |
|||
style={{ width: '100%' }} |
|||
initValue={dataToEdit?.trainer || ""} |
|||
placeholder="请输入培训讲师" |
|||
showClear |
|||
/> |
|||
<Form.Input |
|||
field="trainMethod" |
|||
label='培训方式' |
|||
style={{ width: '100%' }} |
|||
initValue={dataToEdit?.trainMethod || ""} |
|||
placeholder="请输入培训方式" |
|||
showClear |
|||
validate={validate} |
|||
validateStatus={validateStatus} |
|||
/> |
|||
<Form.Input |
|||
field="appraisalMethod" |
|||
label='考核形式' |
|||
style={{ width: '100%' }} |
|||
initValue={dataToEdit?.appraisalMethod || ""} |
|||
placeholder="请输入考核形式" |
|||
showClear |
|||
validate={validate} |
|||
validateStatus={validateStatus} |
|||
// rules={[{
|
|||
// required: true, message: "考核形式不可空"
|
|||
// }, {
|
|||
// max: 20, message: "最多20个字符"
|
|||
// }]}
|
|||
/> |
|||
<Form.Input |
|||
field="trainTime" |
|||
label='培训时长' |
|||
style={{ width: '100%' }} |
|||
initValue={dataToEdit?.trainTime || ""} |
|||
placeholder="请输入培训时长" |
|||
showClear |
|||
validate={validate} |
|||
validateStatus={validateStatus} |
|||
/> |
|||
<Form.Upload |
|||
field='attachPath' |
|||
label='附件' |
|||
action={apiRoot + '/attachments/departmentTraining?token=' + user.token} |
|||
initValue={ |
|||
dataToEdit?.attachPath ? [ |
|||
{ |
|||
uid: '1', |
|||
name: attachName, |
|||
status: 'success', |
|||
preview: false, |
|||
url: '/_file-server/' + dataToEdit.attachPath, |
|||
} |
|||
] : [] |
|||
} |
|||
maxSize={204800} limit={1} accept='.zip,.rar,.7z' |
|||
onSizeError={(file, fileList) => Toast.error(`${file.name} 超过200M`)} |
|||
onChange={uploadOnChange} |
|||
> |
|||
<Button icon={<IconUpload />} theme="light"> |
|||
上传 zip, rar, 7z |
|||
</Button> |
|||
</Form.Upload> |
|||
</Form> |
|||
</Modal > |
|||
) |
|||
} |
|||
|
|||
function mapStateToProps(state) { |
|||
const { auth, global } = state; |
|||
return { |
|||
user: auth.user, |
|||
actions: global.actions, |
|||
apiRoot: global.apiRoot |
|||
}; |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(EditModal); |
@ -0,0 +1,230 @@ |
|||
'use strict'; |
|||
import React, { useState, useEffect } from 'react'; |
|||
import { connect } from 'react-redux'; |
|||
import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui'; |
|||
import { IconUpload } from '@douyinfe/semi-icons'; |
|||
import XLSX from 'xlsx'; |
|||
import moment from 'moment'; |
|||
|
|||
//下载模板和上传文件读取
|
|||
const ImportModal = props => { |
|||
const { dispatch, actions, user, onCancel } = props |
|||
const { humanAffairs } = actions; |
|||
const [msg, setMsg] = useState('') |
|||
const [loading, setLoading] = useState('') |
|||
const [postData, setPostData] = useState([]) |
|||
//初始化
|
|||
useEffect(() => { |
|||
|
|||
}, []); |
|||
|
|||
const confirm = () => { |
|||
if (postData.length) { |
|||
setLoading(true) |
|||
dispatch(humanAffairs.importDepartmentTrainRecord(postData)).then(res => { |
|||
if (res.success) { |
|||
onCancel() |
|||
} |
|||
setLoading(false) |
|||
}) |
|||
} else { |
|||
Notification.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 }) |
|||
} |
|||
} |
|||
|
|||
const download = () => { |
|||
const head = [["部门", "实际培训类型", "培训时间", "培训内容", "培训针对人群", "培训讲师", "培训方式", "考核形式", "培训时长"]]; |
|||
let sheetName = '部门培训台账'; |
|||
let workbook = { SheetNames: [sheetName], Sheets: {} }; |
|||
workbook.Sheets[sheetName] = XLSX.utils.aoa_to_sheet(head);//json转excel
|
|||
workbook.Sheets[sheetName]['!cols'] = [ |
|||
{ wch: 12 }, { wch: 12 }, { wch: 15 }, { wch: 20 }, { wch: 12 }, |
|||
{ wch: 10 }, { wch: 10 }, { wch: 10 }, { wch: 10 }]; |
|||
let wopts = { bookType: 'xlsx', type: 'buffer' };// 生成excel的配置项
|
|||
XLSX.writeFile(workbook, '部门培训记录导入模板.xlsx', wopts); |
|||
} |
|||
const error = (msg) => { |
|||
setMsg(msg) |
|||
onError({ message: msg }) |
|||
} |
|||
|
|||
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("失败"); |
|||
error(`文件解析失败,请检查编码格式`) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
request.send(); |
|||
}) |
|||
} |
|||
const judgeNull = (value) => { |
|||
return value ? String(value).trim().replace(/\s*/g, "") : null; |
|||
} |
|||
|
|||
const changeTime = (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 a; |
|||
} |
|||
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={'.xlsx'} |
|||
maxSize={102400} limit={1} |
|||
onRemove={(currentFile, fileList, fileItem) => { |
|||
setMsg(''); |
|||
setPostData([]); |
|||
}} |
|||
customRequest={(data) => { |
|||
const { file, onSuccess, onError } = data |
|||
getFileBlob(file.url).then(res => { |
|||
if (res.length > 1000) { |
|||
error('一次性上传数据行数应小于1000行,请分批上传') |
|||
return |
|||
} |
|||
if (!res.length) { |
|||
error('请填写至少一行数据') |
|||
return |
|||
} |
|||
let postData = []; |
|||
let ymdhms = /([0-9]{3}[1-9]|[0-9][1-9][0-9]2022-01-07 19:29:33|[0-9]2022-01-07 19:29:33[1-9][0-9]|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8])))([ ])([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])/; |
|||
for (let i = 0; i < res.length; i++) { |
|||
let d = res[i]; |
|||
let departmentName = judgeNull(d['部门']); |
|||
let trainingType = judgeNull(d['实际培训类型']); |
|||
let trainDate = d['培训时间']; |
|||
let trainContent = judgeNull(d['培训内容']); |
|||
let trainWho = judgeNull(d['培训针对人群']); |
|||
let trainer = judgeNull(d['培训讲师']); |
|||
let trainMethod = judgeNull(d['培训方式']); |
|||
let appraisalMethod = judgeNull(d['考核形式']); |
|||
let trainTime = judgeNull(d['培训时长']); |
|||
if (!departmentName) { |
|||
error(`第${i + 2}行【部门】为空,请填写`) |
|||
return |
|||
} |
|||
if (user.allDepartment && user.allDepartment.departments) { |
|||
let dept = user.allDepartment.departments.find(d => !d.delete && d.name === departmentName); |
|||
if (dept) { |
|||
departmentName = dept.name; |
|||
} else { |
|||
error(`第${i + 2}行【部门】数据有误,请确认后重新填写`) |
|||
return |
|||
} |
|||
} |
|||
if (!trainingType) { |
|||
error(`第${i + 2}行【实际培训类型】为空,请填写`) |
|||
return |
|||
} |
|||
if (!trainDate) { |
|||
error(`第${i + 2}行【培训时间】为空,请填写`) |
|||
return |
|||
} |
|||
if (trainDate instanceof Date) { |
|||
trainDate = moment(trainDate).format("YYYY-MM-DD HH:mm:ss"); |
|||
if (!ymdhms.test(trainDate)) { |
|||
error(`第${i + 2}行【培训时间】填写错误,请填写yyyy/mm/dd hh:mm格式`) |
|||
return |
|||
} |
|||
} else { |
|||
error(`第${i + 2}行【培训时间】填写错误,请确认后重新填写`) |
|||
return |
|||
} |
|||
trainDate = changeTime(trainDate); |
|||
|
|||
if (!trainContent) { |
|||
error(`第${i + 2}行【培训内容】为空,请填写`) |
|||
return |
|||
} |
|||
if (!trainMethod) { |
|||
error(`第${i + 2}行【培训方式】为空,请填写`) |
|||
return |
|||
} |
|||
if (!appraisalMethod) { |
|||
error(`第${i + 2}行【考核形式】为空,请填写`) |
|||
return |
|||
} |
|||
if (!trainTime) { |
|||
error(`第${i + 2}行【培训时长】为空,请填写`) |
|||
return |
|||
} |
|||
postData.push({ |
|||
departmentName, trainingType, trainDate, trainContent, |
|||
trainWho: trainWho || '', trainer: trainer || '', |
|||
trainMethod, appraisalMethod, trainTime, origin: 'import' |
|||
}) |
|||
} |
|||
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)(ImportModal); |
Loading…
Reference in new issue