| | |
| | | <div style="display: flex;justify-content: space-between"> |
| | | <div style="display: flex;"> |
| | | <div style="margin-bottom: 18px;margin-right: 10px;display: flex;align-items: center;line-height: 32px;"> |
| | | <el-radio-group v-model="yearDetailForm.causeType" @change="getYearDetailPlan" size="small"> |
| | | <el-radio-button :value="1">定期监督</el-radio-button> |
| | | <el-radio-button :value="2">动态监督</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | <div style="margin-bottom: 18px;margin-right: 10px;display: flex;align-items: center;line-height: 32px;"> |
| | | <span style="width: 88px;font-size: 14px;font-weight: 700;color: var(--lims-text-2);">监控目的</span> |
| | | <el-input v-model="yearDetailForm.supervisePurpose" placeholder="请输入" size="small" |
| | | @keyup.enter="getYearDetailPlan"></el-input> |
| | |
| | | <el-button :loading="ratifyLoading" type="primary" @click="handleRatify(1)">批 准</el-button> |
| | | </span></template> |
| | | </el-dialog> |
| | | <el-dialog v-model="downloadDialog" title="导出" width="600px"> |
| | | <span> |
| | | <el-button plain type="primary" :disabled="download.recordStatus !== 3" @click="controlDown">记录单导出</el-button> |
| | | <el-button plain type="primary" :disabled="download.accordingStatus !== 3" @click="processingDown">处理单导出</el-button> |
| | | <el-button plain type="primary" :disabled="download.correctStatus !== 3" @click="supervisoryDown">纠正单导出</el-button> |
| | | </span> |
| | | <el-dialog v-model="downloadDialog" title="导出" width="640px"> |
| | | <div class="doc-card"> |
| | | <div class="doc-card-head"> |
| | | <span class="doc-card-name">日常监督检查记录表(记录单)</span> |
| | | <el-tag :type="download.recordStatus === 3 ? 'success' : 'info'" size="small"> |
| | | {{ download.recordStatus === 3 ? '已批准' : '未批准' }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="doc-card-foot"> |
| | | <el-button type="primary" :disabled="download.recordStatus !== 3" @click="controlDown">导 出</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="doc-card"> |
| | | <div class="doc-card-head"> |
| | | <span class="doc-card-name">不符合工作控制单(处理单)</span> |
| | | <el-tag :type="download.accordingStatus === 3 ? 'success' : 'info'" size="small"> |
| | | {{ download.accordingStatus === 3 ? '已批准' : '未批准' }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="doc-card-foot"> |
| | | <el-button type="primary" :disabled="download.accordingStatus !== 3" @click="processingDown">导 出</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="doc-card"> |
| | | <div class="doc-card-head"> |
| | | <span class="doc-card-name">纠正措施处理单(纠正单)</span> |
| | | <el-tag :type="download.correctStatus === 3 ? 'success' : 'info'" size="small"> |
| | | {{ download.correctStatus === 3 ? '已批准' : '未批准' }} |
| | | </el-tag> |
| | | </div> |
| | | <div class="doc-card-foot"> |
| | | <el-button type="primary" :disabled="download.correctStatus !== 3" @click="supervisoryDown">导 出</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="down-tip"> |
| | | 仅"已批准"的表单可导出,灰色表示对应表单尚未批准或暂无数据 |
| | | </div> |
| | | <template #footer><span class="dialog-footer"> |
| | | <el-button @click="downloadDialog = false">取 消</el-button> |
| | | </span></template> |
| | |
| | | <!--导入计划--> |
| | | <el-dialog v-model="uploadDia" title="数据导入" width="500px" :close-on-click-modal="false" |
| | | :close-on-press-escape="false"> |
| | | <div style="display: flex; align-items: center;"> |
| | | <span style="width: 80px">年份:</span> |
| | | <el-date-picker v-model="superviseYear" type="year" value-format="YYYY" clearable size="small" format="YYYY" |
| | | placeholder="选择年"> |
| | | </el-date-picker> |
| | | <span style="width: 110px">监督员:</span> |
| | | <el-select v-model="recordUserIds" placeholder="请选择" size="small" @change="splitList" :multiple-limit="2" |
| | | filterable multiple style="width: 100%"> |
| | | <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"></el-option> |
| | | </el-select> |
| | | </div> |
| | | <div style="display: flex;align-items: center;margin: 10px 0"> |
| | | <div style="width: 60px">批准人:</div> |
| | | <el-select v-model="ratifyUserId" clearable filterable size="small" style="width: 50%;"> |
| | | <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"> |
| | | </el-option> |
| | | </el-select> |
| | | <el-form label-width="70px" size="small"> |
| | | <el-form-item label="年份"> |
| | | <el-date-picker v-model="superviseYear" type="year" value-format="YYYY" clearable format="YYYY" |
| | | placeholder="选择年" style="width: 100%"> |
| | | </el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="监督员"> |
| | | <el-select v-model="recordUserIds" placeholder="请选择" @change="splitList" :multiple-limit="2" |
| | | filterable multiple style="width: 100%"> |
| | | <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="批准人"> |
| | | <el-select v-model="ratifyUserId" clearable filterable style="width: 100%"> |
| | | <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"> |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;"> |
| | | <span>第一次导入请先下载模板,按模板格式填写后再上传。</span> |
| | | <el-button link type="primary" @click="downloadTemplate">下载导入模板</el-button> |
| | | </div> |
| | | <div style="margin: 0 auto;"> |
| | | <el-upload ref="upload1" :action="action" :auto-upload="false" :file-list="fileList" :headers="uploadHeader" |
| | | :limit="1" accept='.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar' |
| | | :limit="1" accept=".xls,.xlsx" |
| | | :on-change="beforeUpload" :on-error="onError" :on-success="handleSuccessUp" drag |
| | | :data="{ recordUserIds: recordUserIds1, superviseYear: superviseYear, ratifyUserId: ratifyUserId }" |
| | | name="file"> |
| | | <lims-icon name="el-icon-upload"></lims-icon> |
| | | <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> |
| | | </el-upload> |
| | | </div> |
| | | <div style="margin-top: 10px; color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px;"> |
| | | <div>支持格式:.xls、.xlsx;单个文件不超过10MB。</div> |
| | | <div>监督日期、监控项目、被监督人员为必填项。</div> |
| | | <div>监督日期建议填“年.月”(如 2026.3);填成 2026-03-15 也可以,系统会自动换算为 2026.3。</div> |
| | | <div>被监督人员必须填写系统中已存在的人员姓名,否则整批导入失败。</div> |
| | | <div>导入只读取模板的第一个工作表。</div> |
| | | <div style="color: var(--el-color-warning);">导入后计划为“待批准”:需使用所选批准人账号登录并点击“批准”,批准后才能下发和导出。</div> |
| | | </div> |
| | | <template #footer><span class="dialog-footer"> |
| | | <el-button @click="closeUploadDia">取 消</el-button> |
| | |
| | | import limsTable from "@/components/Table/lims-table.vue"; |
| | | import { |
| | | delQualitySupervise, delQualitySuperviseDetail, |
| | | exportQualitySupervise, exportSuperviseDetailRecord, |
| | | exportQualitySupervise, exportSuperviseDetailRecord, issueQualitySupervise, |
| | | pageQualitySupervise, pageQualitySuperviseDetail, |
| | | ratifyQualitySupervise, superviseDetailAccordingExport, |
| | | exportSuperviseDetaillCorrect |
| | |
| | | } else if (params === 1) { |
| | | return '批准'; |
| | | } else { |
| | | return null; |
| | | return '待批准'; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | |
| | | } else if (params === 1) { |
| | | return 'success'; |
| | | } else { |
| | | return null; |
| | | return 'warning'; |
| | | } |
| | | } |
| | | }, { |
| | |
| | | label: '批准日期', |
| | | prop: 'ratifyTime', |
| | | width: '160' |
| | | }, { |
| | | dataType: 'tag', |
| | | label: '下发状态', |
| | | prop: 'issueStatus', |
| | | width: '100', |
| | | formatData: (params) => { |
| | | if (params === 1) { |
| | | return '已下发'; |
| | | } else { |
| | | return '未下发'; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params === 1) { |
| | | return 'success'; |
| | | } else { |
| | | return 'info'; |
| | | } |
| | | } |
| | | }, { |
| | | dataType: 'action', |
| | | label: '操作', |
| | |
| | | }, |
| | | clickFun: (row) => { |
| | | this.approvalPlan(row) |
| | | } |
| | | }, |
| | | { |
| | | name: '下发', |
| | | type: 'text', |
| | | disabled: (row) => { |
| | | if (row.ratifyStatus !== 1 || row.issueStatus === 1) { |
| | | return true |
| | | } else { |
| | | return false |
| | | } |
| | | }, |
| | | clickFun: (row) => { |
| | | this.issuePlan(row) |
| | | } |
| | | }, |
| | | { |
| | |
| | | yearLoading: false, |
| | | yearDetailForm: { |
| | | supervisePurpose: '', |
| | | superviseProject: '', |
| | | causeType: 1 |
| | | superviseProject: '' |
| | | }, |
| | | yearDetailColumnData: [ |
| | | { |
| | |
| | | type: 'text', |
| | | clickFun: (row) => { |
| | | this.records(row) |
| | | }, |
| | | disabled: () => { |
| | | if (this.currentPlan.issueStatus !== 1) { |
| | | return true |
| | | } else { |
| | | return false |
| | | } |
| | | } |
| | | }, |
| | | { |
| | |
| | | }, |
| | | currentScrollPosition: 0, |
| | | superviseId: '', |
| | | currentPlan: {}, |
| | | formDia: false, |
| | | recordsDia: false, |
| | | processingDia: false, |
| | |
| | | this.yearPage.size = limit; |
| | | this.getYearPlanList(); |
| | | }, |
| | | // 下载导入模板 |
| | | downloadTemplate() { |
| | | const link = document.createElement('a') |
| | | // 带版本参数避免浏览器继续使用缓存里的旧模板 |
| | | link.href = '/import-templates/质量监督计划导入模板.xlsx?v=3' |
| | | link.download = '质量监督计划导入模板.xlsx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | document.body.removeChild(link) |
| | | }, |
| | | // 导入流程 |
| | | beforeUpload(file) { |
| | | if (!/\.(xls|xlsx)$/i.test(file.name || '')) { |
| | | this.$message.error('请选择 .xls 或 .xlsx 格式的文件'); |
| | | this.$refs.upload1.clearFiles() |
| | | return false; |
| | | } |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | this.$message.error('上传文件不超过10M'); |
| | | this.$refs.upload1.clearFiles() |
| | | return false; |
| | | } |
| | | return true; |
| | | }, |
| | | onError(err, file, fileList) { |
| | | this.$message.error('上传失败') |
| | |
| | | handleSuccessUp(response) { |
| | | this.uploading = false; |
| | | if (response.code == 200) { |
| | | this.$message.success('上传成功'); |
| | | const approver = this.userList.find(item => Number(item.id) === Number(this.ratifyUserId)) |
| | | const approverName = approver ? approver.name : '所选批准人' |
| | | if (Number(this.ratifyUserId) === Number(this.userId)) { |
| | | this.$message.success('导入成功,请在列表中点击“批准”完成批准,批准后才能下发和导出') |
| | | } else { |
| | | this.$message.success(`导入成功,计划已提交给${approverName}批准;请用该账号登录批准,批准后才能下发和导出`) |
| | | } |
| | | this.recordUserIds = [] |
| | | this.superviseYear = '' |
| | | this.fileList = [] |
| | |
| | | this.getUserList() |
| | | }, |
| | | getUserList() { |
| | | selectUserCondition({ type: 2 }).then((res) => { |
| | | this.userList = res.data; |
| | | // 质量监督面向全实验室, 人员列表不按部门筛选 |
| | | selectUserCondition({ type: 1 }).then((res) => { |
| | | const list = res.data || [] |
| | | // 当前登录用户可能因角色(如 admin)被接口过滤,仍允许选自己作为批准人,避免导入后无人可批准 |
| | | if (this.userId && !list.some(item => Number(item.id) === Number(this.userId))) { |
| | | list.unshift({ id: this.userId, name: this.nickName }) |
| | | } |
| | | this.userList = list |
| | | }) |
| | | }, |
| | | // 批准 |
| | |
| | | // 导出 |
| | | handleDown(row) { |
| | | exportQualitySupervise({ superviseId: row.superviseId }).then(res => { |
| | | try { |
| | | this.outLoading = false |
| | | const blob = new Blob([res], { type: 'application/msword' }); |
| | | this.$download.saveAs(blob, row.superviseName + '.docx') |
| | | } catch (error) { |
| | | console.error('创建Blob对象时出错:', error); |
| | | } |
| | | this.outLoading = false |
| | | this.saveDocx(res, row.superviseName + '.docx') |
| | | }).catch(err => { |
| | | console.log('err---', err); |
| | | }) |
| | | }, |
| | | // 保存导出文件: 后端导出失败时会返回错误JSON, 此时弹提示而不是存成损坏的docx |
| | | saveDocx(res, filename) { |
| | | const reader = new FileReader(); |
| | | reader.readAsText(res, 'utf-8'); |
| | | reader.onload = () => { |
| | | const text = (reader.result || '').trim(); |
| | | if (text.startsWith('{') || text.startsWith('[')) { |
| | | let msg = '导出失败'; |
| | | try { msg = JSON.parse(text).msg || msg; } catch (e) { } |
| | | this.$message.error(msg); |
| | | } else { |
| | | this.$download.saveAs(new Blob([res], { type: 'application/msword' }), filename); |
| | | } |
| | | }; |
| | | reader.onerror = () => { |
| | | this.$download.saveAs(new Blob([res], { type: 'application/msword' }), filename); |
| | | }; |
| | | }, |
| | | // 删除进度计划表 |
| | | delPlan(row) { |
| | |
| | | // 年度计划表格,点击行数据后刷新详情 |
| | | rowClick(row) { |
| | | this.superviseId = row.superviseId |
| | | this.currentPlan = row |
| | | this.getYearDetailPlan() |
| | | }, |
| | | // 下发计划 |
| | | issuePlan(row) { |
| | | this.$confirm('下发后监督员与相关检测人员将收到通知, 是否继续?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | this.yearLoading = true |
| | | issueQualitySupervise({ superviseId: row.superviseId }).then(res => { |
| | | this.yearLoading = false |
| | | this.$message.success('下发成功') |
| | | this.getYearPlanList() |
| | | }).catch(err => { |
| | | this.yearLoading = false |
| | | console.log('err---', err); |
| | | }) |
| | | }).catch(() => { |
| | | this.$message({ |
| | | type: 'info', |
| | | message: '已取消下发' |
| | | }); |
| | | }); |
| | | }, |
| | | getYearDetailPlan () { |
| | | this.yearDetailPage.current = 1 |
| | |
| | | this.getYearDetailPlanList() |
| | | }, |
| | | // 获取年度明细表 |
| | | // 监督日期统一成 "YYYY.M":兼容 2026-03-15 / 2026/3/15 / 2026年3月15日 等导入写法 |
| | | normalizeSuperviseMonth(value) { |
| | | if (!value) return value |
| | | const text = String(value).trim() |
| | | const matched = text.match(/^(\d{4})\s*[./年-]\s*(\d{1,2})(?:\s*[./月-]\s*\d{1,2}\s*日?)?$/) |
| | | if (!matched) return text |
| | | const month = Number(matched[2]) |
| | | return month >= 1 && month <= 12 ? `${matched[1]}.${month}` : text |
| | | }, |
| | | getYearDetailPlanList() { |
| | | const entity = { |
| | | superviseId: this.superviseId, |
| | | supervisePurpose: this.yearDetailForm.supervisePurpose, |
| | | superviseProject: this.yearDetailForm.superviseProject, |
| | | causeType: this.yearDetailForm.causeType, |
| | | } |
| | | const page = this.yearDetailPage |
| | | this.yearDetailLoading = true |
| | | pageQualitySuperviseDetail({ ...entity, ...page }).then(res => { |
| | | this.yearDetailLoading = false |
| | | this.yearDetailTableData = res.data.records |
| | | // 历史导入的监督日期可能是 2026/3/15、2026-03-15 等完整日期,统一显示成 2026.3 |
| | | this.yearDetailTableData = (res.data.records || []).map(item => ({ |
| | | ...item, |
| | | superviseTime: this.normalizeSuperviseMonth(item.superviseTime) |
| | | })) |
| | | this.yearDetailPage.total = res.data.total |
| | | }).catch(err => { |
| | | console.log('err---', err); |
| | |
| | | showDialog(type, row) { |
| | | this.formDia = true |
| | | this.$nextTick(() => { |
| | | this.$refs.formDia.openDia(type, row, this.yearDetailForm.causeType) |
| | | this.$refs.formDia.openDia(type, row) |
| | | }) |
| | | }, |
| | | closeDia() { |
| | |
| | | // 导出记录 |
| | | controlDown() { |
| | | exportSuperviseDetailRecord({ superviseDetailsId: this.download.superviseDetailsId }).then(res => { |
| | | const blob = new Blob([res], { type: 'application/msword' }); |
| | | this.$download.saveAs(blob, '记录导出.docx') |
| | | this.saveDocx(res, '记录导出.docx') |
| | | }).catch(err => { |
| | | console.log('err---', err); |
| | | }) |
| | |
| | | // 处理单导出 |
| | | processingDown() { |
| | | superviseDetailAccordingExport({ superviseDetailsId: this.download.superviseDetailsId }).then(res => { |
| | | const blob = new Blob([res], { type: 'application/msword' }); |
| | | this.$download.saveAs(blob, '不符合项导出.docx') |
| | | this.saveDocx(res, '不符合项导出.docx') |
| | | }).catch(err => { |
| | | console.log('err---', err); |
| | | }) |
| | |
| | | // 纠正单导出 |
| | | supervisoryDown() { |
| | | exportSuperviseDetaillCorrect({ superviseDetailsCorrectId: this.download.superviseDetailsCorrectId }).then(res => { |
| | | const blob = new Blob([res], { type: 'application/msword' }); |
| | | this.$download.saveAs(blob, '监督纠正措施.docx') |
| | | this.saveDocx(res, '监督纠正措施.docx') |
| | | }).catch(err => { |
| | | console.log('err---', err); |
| | | }) |
| | | }, |
| | | // 删除年度详情列表 |
| | |
| | | action() { |
| | | return this.javaApi + '/qualitySupervise/importQualitySupervise' |
| | | }, |
| | | ...mapGetters(["userId"]), |
| | | ...mapGetters(["userId", "nickName"]), |
| | | }, |
| | | }; |
| | | </script> |
| | |
| | | margin: 0 5px; |
| | | } |
| | | |
| | | .doc-card { |
| | | padding: 12px 16px; |
| | | margin-bottom: 12px; |
| | | background-color: #f7f8fa; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | .doc-card-head { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .doc-card-name { |
| | | font-size: 14px; |
| | | color: var(--lims-text-2, #303133); |
| | | } |
| | | |
| | | .doc-card-foot { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .down-tip { |
| | | margin-top: 10px; |
| | | color: #909399; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .search { |
| | | width: 150px; |
| | | padding: 0 16px; |