<!-- 培训签到表详情 --> <script name="TrainSignDetail" lang="ts" setup> import { ElLoading, ElMessage, ElMessageBox, dayjs } from 'element-plus' import type { IStaffBasicInfo } from '../register/person-regitster' import type { IParticipantInfo, IRegistrationForm } from './train-interface' import FilterRegistedStaff from '@/views/resource/common/filterRegistedStaff.vue' import { addTrainSignInfo, confirmSign, detailTrainSignInfo, getStream, updateTrainSignInfo, workbenchReminder } from '@/api/resource/train' import type { TableColumn } from '@/components/NormalTable/table_interface' import type { IDictType } from '@/commonInterface/resource-interface' import { getDictByCode } from '@/api/system/dict' import { getStaffList } from '@/api/resource/register' import useUserStore from '@/store/modules/user' import { printPdf } from '@/utils/printUtils' import type { dictType } from '@/global' import filterGrantNotice from '@/views/resource/common/filterGrantNotice.vue' import { exportFile } from '@/utils/exportUtils' import { getBase64 } from '@/utils/download' const type = ref<string>('') const id = ref<string>('') const route = useRoute() const router = useRouter() const loading = ref<boolean>(false) // 字典值 const labCodeDict = ref<Array<IDictType>>([]) // 实验室代码 const groupCodeDict = ref<Array<IDictType>>([]) // 部门代码 const trainInfo = ref<IRegistrationForm>({ id: '', fileCode: '', // 文件编号 fileName: '培训签到表', // 文件名称 labCode: '', labCodeName: '', groupCode: '', groupCodeName: '', trainingTime: '', position: '', host: '', fileNoteCode: '', content: '', createTime: '', noveltyId: '', participantsList: [], }) const basicFormRef = ref() const staffFormRef = ref() const refFilterStaff = ref() const trainBasicRules = ref({ labCode: [{ required: true, message: '实验室不能为空,请选择', trigger: ['change', 'blur'] }], groupCode: [{ required: true, message: '部门不能为空,请选择', trigger: ['change', 'blur'] }], trainingTime: [{ required: true, message: '培训时间不能为空,请选择', trigger: ['change', 'blur'] }], position: [{ required: true, message: '培训地点不能为空', trigger: 'blur' }], host: [{ required: true, message: '培训会主持人不能为空', trigger: 'blur' }], content: [{ required: true, message: '训练内容不能为空', trigger: 'blur' }], }) // 表单验证规则 const staffColumns = ref<Array<TableColumn>>([ { text: '姓名', value: 'staffName', align: 'center', width: '150' }, { text: '军官/文职证号', value: 'officerNo', align: 'center', width: '160' }, { text: '工作部门', value: 'deptName', align: 'center' }, { text: '岗位', value: 'station', align: 'center' }, { text: '人员类别', value: 'staffTypeName', align: 'center', width: '120' }, { text: '是否已发送通知', value: 'isNoticedName', align: 'center', width: '100' }, { text: '是否进行确认', value: 'isConformName', align: 'center', width: '100' }, { text: '签到结果', value: 'isSignName', align: 'center', width: '100' }, { text: '确认时间', value: 'confirmTime', align: 'center', width: '180' }, ]) // 表头 // 表格被选中的行 const staffSelected = ref<IParticipantInfo[]>([]) const userInfo = useUserStore() // ---------------------------------------------------------------------------------------------- const staffUserList = ref<dictType[]>([]) // 计量人员 // 获取计量人员 const fetchStaffList = async () => { // 计量人员 const res = await getStaffList({ staffNo: '', // 人员编号 name: '', // 姓名 deptId: '', // 所在部门 limit: 999999, offset: 1, }) staffUserList.value = res.data.rows } // ---------------------------------------------------------------------------------------------- // 关闭 const resetForm = () => { sessionStorage.removeItem('trainSignInfo') router.go(-1) } const staffMultiSelect = (e: any) => { staffSelected.value = e } const detail = () => { detailTrainSignInfo({ id: id.value }).then((res) => { if (res.code === 200) { trainInfo.value = res.data trainInfo.value.participantsList = trainInfo.value.participantsList.map((staff) => { return { ...staff, isConformName: staff.isConform === '1' ? '已确认' : '未确认', isSignName: staff.isSign === '1' ? '已签到' : '未签到', isNoticedName: staff.isNoticed === '1' ? '已通知' : '未通知', } }) loading.value = false } }).catch(() => { loading.value = false }) } // 新增培训签到表 const addTrainAndStaffList = () => { trainInfo.value.createTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss') addTrainSignInfo(trainInfo.value).then((res) => { if (res.code === 200) { // 提示保存成功 ElMessage.success('保存成功') id.value = res.data trainInfo.value.id = res.data // id type.value = 'detail' trainInfo.value.fileCode = res.data } else { // 提示失败信息 ElMessage.error(`保存失败:${res.message}`) } }) } // 编辑登记表 const updateTrainAndStaffList = () => { updateTrainSignInfo(trainInfo.value).then((res) => { if (res.code === 200) { // 提示保存成功 ElMessage.success('保存成功') type.value = 'detail' trainInfo.value.participantsList = trainInfo.value.participantsList.map((staff) => { return { ...staff, isConformName: staff.isConform === '1' ? '已确认' : '未确认', isSignName: staff.isSign === '1' ? '已签到' : '未签到', isNoitcedName: staff.isNoticed === '1' ? '已通知' : '未通知', } }) } else { // 提示失败信息 ElMessage.error(`保存失败:${res.message}`) } }) } // 保存基本信息 const saveTrainForm = async () => { if (!basicFormRef) { return } await basicFormRef.value.validate((valid: boolean, fields: any) => { if (valid === true) { if (trainInfo.value.participantsList !== undefined && trainInfo.value.participantsList.length > 0) { ElMessageBox.confirm( '确认保存吗?', '提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', }, ).then(() => { if (type.value === 'create') { addTrainAndStaffList() } else if (type.value === 'update') { updateTrainAndStaffList() } }) } else { ElMessage.warning('实训人员不能为空') } } }) } // 点击编辑按钮 const editButtHandler = () => { type.value = 'update' } // 点击增加行按钮 选择人员 const addEditableRow = () => { refFilterStaff.value.showOrHideFilterDialog(true) } // 选定人员 const recordSelectedHandler = (rows: Array<IStaffBasicInfo>) => { refFilterStaff.value.showOrHideFilterDialog(false) rows.forEach((staff) => { const exist = trainInfo.value.participantsList.filter(part => staff.id === part.participantId) // 表中没有重复的人员 则添加 if (exist.length === 0) { trainInfo.value.participantsList.push({ formId: id.value, participantId: staff.id!, isConform: '0', isSign: '0', isNoticed: '0', staffName: staff.staffName, officerNo: staff.officerNo, deptName: staff.deptName, station: staff.station, staffTypeName: staff.staffTypeName, }) } }) } // 删除实训人员 const delTrainStaffRec = async () => { if (staffSelected.value.length === 0) { ElMessage.warning('请至少选择一行') return } // 校验已发送通知的不能删除 const isNoticedIndex = staffSelected.value.findIndex(item => item.isNoticed === '1') if (isNoticedIndex !== -1) { ElMessage.warning(`${staffSelected.value[isNoticedIndex].staffName}已经发送通知,不允许删除`) return } // 校验已确认的不能删除 const index = staffSelected.value.findIndex(item => item.isConform === '1') if (index !== -1) { ElMessage.warning(`${staffSelected.value[index].staffName}已经确认过,不允许删除`) return } // 前端界面删除 trainInfo.value.participantsList = trainInfo.value.participantsList.filter(item => staffSelected.value.includes(item) === false) } // ----------------------------文件发放通知单------------------------------------------- const selectFileInformComRef = ref() // 选择文件发放通知单 // 点击选择文件发放通知单 const selectFileInform = () => { selectFileInformComRef.value.showOrHideFilterDialog(true) } // 选好文件 const confirmSelectFile = (value: any) => { const distributor = value.distributor.split(',') // 发放人员id const distributorNames = value.distributorNames // 发放人员名称 const tempStaffUserList = [] as any if (distributor && distributor.length) { distributor.forEach((item: any) => { const index = staffUserList.value.findIndex(i => i.id === item) if (index !== -1) { const staff = staffUserList.value[index] as any tempStaffUserList.push({ formId: id.value, participantId: staff.id!, isConform: '0', isSign: '0', isNoticed: '0', staffName: staff.staffName, officerNo: staff.officerNo, deptName: staff.deptName, station: staff.station, staffTypeName: staff.staffTypeName, }) } }) } console.log('发放人员', tempStaffUserList) trainInfo.value.fileNoteCode = value.formNo // 文件发放编号 // 发放人员列表 trainInfo.value.participantsList = tempStaffUserList } // --------------------------------导出word、pdf、打印---------------------------------------------------- const stream = ref() const streamNormal = ref(true) // 流是否正常 // 获取流 const fetchStream = async (isPdf = true) => { const loading = ElLoading.service({ lock: true, text: '加载中...', background: 'rgba(255, 255, 255, 0.6)', }) const res = await getStream({ id: id.value, pdf: isPdf }) stream.value = res.data loading.close() } // 打印Word const printToWord = () => { fetchStream(false).then(() => { if (streamNormal.value) { exportFile(stream.value, '培训签到表.doc') } }) } // 导出PDF const printToPDF = () => { fetchStream().then(() => { if (streamNormal.value) { exportFile(new Blob([stream.value]), '培训签到表.pdf') } }) } // 打印 const printClickedHandler = () => { fetchStream().then(() => { if (streamNormal.value) { const blobUrl = URL.createObjectURL(stream.value) printPdf(blobUrl) } }) } // ------------------------------------------------------------------------------------ // 向参训人员发送通知 const sendToParticipant = () => { const participantNameSend = trainInfo.value.participantsList.map((item) => { if (item.isNoticed !== '1') { return item.staffName } else { return '' } }).filter(item => item !== '') if (participantNameSend.length === 0) { ElMessage.warning('所有的用户都已经通知过了,请勿重复发送通知') } else { loading.value = true workbenchReminder({ id: id.value }).then((res) => { loading.value = false if (res.code === 200) { ElMessage.success(`向以下用户发送通知成功:\n${participantNameSend.join(',')}`) trainInfo.value.participantsList.forEach((item) => { if (item.isNoticed !== '1') { item.isNoticed = '1' item.isNoticedName = '已通知' } }) } }).catch(() => { loading.value = false }) } } const title = ref('') const initDialog = (params: any) => { type.value = params.type id.value = params.id switch (params.type) { case 'create' : trainInfo.value.createTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss') trainInfo.value.host = userInfo.name trainInfo.value.labCode = userInfo.bizLabCode // 实验室代码 trainInfo.value.groupCode = userInfo.groupNo // 部门代码 trainInfo.value.labCodeName = userInfo.labCodeName // 实验室名称 trainInfo.value.groupCodeName = userInfo.groupName // 部门名称 if (route.query.noveltyId) { trainInfo.value.noveltyId = route.query.noveltyId as string } title.value = '培训签到表(新增)' break case 'update': loading.value = true detail() title.value = '培训签到表(编辑)' break case 'detail': loading.value = true detail() title.value = '培训签到表(详情)' break default: title.value = '' break } } const getLabCodeDict = async () => { if (sessionStorage.getItem('bizLabCode') !== null && sessionStorage.getItem('bizLabCode') !== '') { labCodeDict.value = JSON.parse(sessionStorage.getItem('bizLabCode')!) } else { await getDictByCode('bizLabCode').then((res) => { if (res.code === 200) { labCodeDict.value = res.data sessionStorage.setItem('bizLabCode', JSON.stringify(res.data)) } }) } } const getGroupCodeDict = async () => { if (sessionStorage.getItem('bizGroupCode') !== null && sessionStorage.getItem('bizGroupCode') !== '') { groupCodeDict.value = JSON.parse(sessionStorage.getItem('bizGroupCode')!) } else { await getDictByCode('bizGroupCode').then((res) => { if (res.code === 200) { groupCodeDict.value = res.data sessionStorage.setItem('bizGroupCode', JSON.stringify(res.data)) } }) } } // 参训人员确认 const confirm = () => { const params = { formId: id.value, isSign: '1', userId: userInfo.id, } confirmSign(params).then(() => { // ElMessage.success('确认成功') }) } const initDict = () => { getLabCodeDict() getGroupCodeDict() } onMounted(async () => { await fetchStaffList() // 获取计量人员 initDict() initDialog(route.query) // 从工作台跳转过来需要调签到接口 const lastPage = `${router.options.history.state.back}` // 上一页的路径 if (lastPage.includes('workbench') && route.query.fromWorkBench === 'work') { // 从工作台来跳转过来显示编辑标准库按钮 confirm() } }) </script> <template> <app-container> <detail-page :title="title"> <template #btns> <el-button v-if="type === 'detail'" type="primary" @click="sendToParticipant()"> 向参训人员发送通知 </el-button> <template v-if="type === 'detail'"> <el-button type="primary" @click="printToWord"> 导出Word </el-button> <el-button type="primary" @click="printToPDF"> 导出PDF </el-button> <el-button type="primary" @click="printClickedHandler"> 打印 </el-button> <el-button type="primary" @click="editButtHandler()"> 编辑 </el-button> </template> <el-button v-if="type !== 'detail'" type="primary" @click="saveTrainForm()"> 保存 </el-button> <el-button type="info" @click="resetForm()"> 关闭 </el-button> </template> <el-form ref="basicFormRef" :model="trainInfo" :rules="trainBasicRules" label-position="right" :class="[type === 'detail' ? 'isDetail' : '']" label-width="110" border stripe> <el-row :gutter="24"> <!-- 第一行 第一列 --> <el-col :span="6"> <el-form-item label="实验室" prop="labCode"> <el-select v-model="trainInfo.labCode" style="width: 100%;" placeholder="请选择实验室" :disabled="type !== 'create'"> <el-option v-for="site in labCodeDict" :key="site.id" :value="site.value" :label="site.name" /> </el-select> </el-form-item> <el-form-item label="时间" prop="trainingTime"> <el-date-picker v-model="trainInfo.trainingTime" type="datetime" format="YYYY-MM-DD HH:mm" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择培训时间" :disabled="type === 'detail'" :clearable="true" style="width: 100%;" /> </el-form-item> </el-col> <!-- 第一行 第二列 --> <el-col :span="6"> <el-form-item label="部门" prop="groupCode"> <el-select v-model="trainInfo.groupCode" style="width: 100%;" placeholder="请选择部门" :disabled="type !== 'create'"> <el-option v-for="dict in groupCodeDict" :key="dict.id" :value="dict.value" :label="dict.name" /> </el-select> </el-form-item> <el-form-item label="地点" prop="position"> <el-input v-model="trainInfo.position" placeholder="地点" :show-word-limit="true" :maxlength="50" :disabled="type === 'detail'" /> </el-form-item> </el-col> <!-- 第一行 第三列 --> <el-col :span="6"> <el-form-item label="文件编号"> <el-input v-model="trainInfo.fileCode" placeholder="系统自动生成" disabled /> </el-form-item> <el-form-item label="主持" prop="host"> <el-input v-model="trainInfo.host" :clearable="true" placeholder="请输入培训会主持人" :disabled="type === 'detail'" /> </el-form-item> </el-col> <!-- 第一行 第四列 --> <el-col :span="6"> <el-form-item label="文件名称"> <el-input v-model="trainInfo.fileName" disabled /> </el-form-item> <el-form-item label="文件发放通知单"> <el-input v-model="trainInfo.fileNoteCode" placeholder="文件发放通知单" disabled> <template v-if="type !== 'detail'" #append> <el-button @click="selectFileInform"> 选择 </el-button> </template> </el-input> </el-form-item> </el-col> </el-row> <!-- 第二行 --> <el-row :gutter="24"> <el-col :span="24"> <el-form-item label="内容" prop="content"> <el-input v-model="trainInfo.content" placeholder="请输入培训内容" type="textarea" :show-word-limit="true" :maxlength="500" :rows="5" :clearable="true" :disabled="type === 'detail'" /> </el-form-item> </el-col> </el-row> </el-form> </detail-page> <el-form ref="staffFormRef" label-position="right" label-width="110px" border stripe> <table-container v-loading="loading" title="参加人员" style="margin-top: 20px;"> <template v-if="type !== 'detail' && !trainInfo.fileNoteCode" #btns-right> <el-button type="primary" @click="addEditableRow"> 批量增加 </el-button> <el-button type="info" @click="delTrainStaffRec"> 删除行 </el-button> </template> <!-- 表格区域 --> <el-table :data="trainInfo.participantsList" :columns="staffColumns" border @selection-change="staffMultiSelect"> <el-table-column v-if="type !== 'detail'" type="selection" align="center" width="40" /> <el-table-column align="center" label="序号" width="55" type="index" /> <el-table-column v-for="item in staffColumns" :key="item.value" :prop="item.value" :label="item.text" :width="item.width" align="center" > <template #header> <span>{{ item.text }}</span> </template> </el-table-column> <el-table-column align="center" label="电子签名" width="240"> <template #default="scope"> <el-image v-if="scope.row.signFileUrl" :src="scope.row.signFileUrl" />/> </template> </el-table-column> </el-table> </table-container> </el-form> <filter-grant-notice ref="selectFileInformComRef" @confirm-select="confirmSelectFile" /> <filter-registed-staff ref="refFilterStaff" @record-selected="recordSelectedHandler" /> </app-container> </template> <style lang="scss" scoped> .isDetail { ::v-deep { .el-form-item.is-required:not(.is-no-asterisk) .el-form-item__label-wrap > .el-form-item__label::before, .el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label::before { content: ""; display: none; } } } </style>