| | |
| | | :disabled="scope.row.state != 0 || nickName !== scope.row.writeUserName">上传 |
| | | </el-button> |
| | | </el-upload> |
| | | <!-- <el-button type="text" size="small" :disabled="scope.row.state != 0 || nickName !== scope.row.writeUserName"--> |
| | | <!-- @click="handleRestore(scope.row)">还原</el-button>--> |
| | | <el-button v-if="scope.row.urlS && !isBrowserEditedReportUrl(scope.row.urlS)" link type="primary" size="small" |
| | | :disabled="scope.row.state != 0 || nickName !== scope.row.writeUserName || !scope.row.url" |
| | | @click="handleRestore(scope.row)">还原系统报告</el-button> |
| | | <el-button link type="primary" size="small" @click="sendBackTask(scope.row)">退回任务</el-button> |
| | | <el-button link type="primary" size="small" :disabled="scope.row.state != 0 || nickName !== scope.row.writeUserName" |
| | | @click="handle(scope.row)">提交 |
| | |
| | | </span></template> |
| | | </el-dialog> |
| | | <el-dialog :fullscreen="fullscreen" :modal-append-to-body="false" v-model="issuedVisible" title="报告审核" |
| | | width="80vw"> |
| | | width="90vw" :close-on-click-modal="false" :before-close="closeAuditEditor"> |
| | | <div class="full-screen"> |
| | | <lims-icon name="el-icon-full-screen" v-if="!fullscreen" style="cursor: pointer;font-size: 18px" |
| | | @click="fullscreen = true;"></lims-icon> |
| | | <img v-else alt="" src="@/assets/images/no-full.svg" style="cursor: pointer;" @click="fullscreen = false;"> |
| | | </div> |
| | | <div v-if="issuedVisible" style="height: 70vh;"> |
| | | <onlyoffice ref="onlyoffice" :options="option" style="width: 100%;height: 100%;"/> |
| | | <div class="audit-editor-toolbar"> |
| | | <el-radio-group v-if="currentInfo && currentInfo.inspectionUrl" |
| | | v-model="currentAuditReportType" :disabled="auditOpening || savingReport || loadingIssued || addApproverDia" |
| | | @change="handleAuditReportTypeChange"> |
| | | <el-radio-button :value="0">检测报告</el-radio-button> |
| | | <el-radio-button :value="1">检验报告</el-radio-button> |
| | | </el-radio-group> |
| | | <span class="margin-tip">修改字体请先选中文字或定位光标;保存后生效</span> |
| | | </div> |
| | | <div v-if="issuedVisible" class="audit-editor-content"> |
| | | <report-word-editor v-if="auditDocument" :key="auditDocument.sessionToken" ref="auditWordEditor" |
| | | :document="auditDocument" :readonly="savingReport || loadingIssued || addApproverDia" |
| | | @ready="onAuditEditorReady" @error="onAuditEditorError" |
| | | @change="auditDirty = true" @save="saveAuditDraft"/> |
| | | <div v-if="auditOpening" class="audit-opening-overlay"> |
| | | <div class="audit-opening-panel" role="status" aria-live="polite"> |
| | | <div class="audit-opening-title">正在打开报告</div> |
| | | <el-progress :percentage="auditOpenProgress" :stroke-width="10" |
| | | :indeterminate="auditOpenProgress < 30 || auditOpenProgress >= 80" :duration="3"/> |
| | | <div class="audit-opening-step">{{ auditOpenText }}</div> |
| | | </div> |
| | | </div> |
| | | <div v-else-if="auditOpenError && !auditDocument" class="audit-opening-overlay"> |
| | | <div class="audit-opening-panel audit-opening-error" role="alert"> |
| | | <div class="audit-opening-title">报告打开失败</div> |
| | | <p>{{ auditOpenError }}</p> |
| | | <el-button type="primary" @click="handleIssued(currentInfo)">重试</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #footer><span class="dialog-footer"> |
| | | <el-button :disabled="loadingIssued" @click="issuedReasonVisible = true">不通过</el-button> |
| | | <el-button type="primary" @click="subIssued">通 过</el-button> |
| | | <el-button :disabled="loadingIssued || savingReport || !auditEditorReady || addApproverDia" @click="saveAuditDraft">保存</el-button> |
| | | <el-button :disabled="auditOpening || !!auditOpenError || loadingIssued || savingReport || addApproverDia" @click="issuedReasonVisible = true">不通过</el-button> |
| | | <el-button type="primary" :loading="savingReport" :disabled="loadingIssued || !auditEditorReady" @click="subIssued">通 过</el-button> |
| | | </span></template> |
| | | </el-dialog> |
| | | <el-dialog :modal-append-to-body="false" v-model="issuedReasonVisible" title="不通过原因" width="400px"> |
| | |
| | | |
| | | <script> |
| | | import onlyoffice from "@/components/Onlyoffice/onlyoffice.vue"; |
| | | import ReportWordEditor from "@/components/ReportWordEditor/index.vue"; |
| | | // import ShowInfo from "../do/b1-material-ins-order/showInfo.vue"; |
| | | import { |
| | | downAll, |
| | | downloadReport, |
| | | examineReport, |
| | | getReportEditDocument, |
| | | saveReportEditDocument, |
| | | getReportEditConfig, |
| | | pageInsReport, |
| | | regenerateReport, |
| | | ratifyReport, |
| | |
| | | import limsTable from "@/components/Table/lims-table.vue"; |
| | | import {delFile, downFile, getFileList} from "@/api/business/rawMaterialOrder"; |
| | | import {selectEnabledSeals} from "@/api/structural/sealConfig"; |
| | | import {loadSuperDoc} from "@/utils/loadSuperDoc"; |
| | | |
| | | export default { |
| | | name: 'ReportPreparation', |
| | | components: {limsTable, onlyoffice}, |
| | | components: {limsTable, onlyoffice, ReportWordEditor}, |
| | | dicts: ['material_prop_type'], |
| | | data() { |
| | | return { |
| | |
| | | loadingApprove: false, |
| | | loadingIssuedReason: false, |
| | | loadingIssued: false, |
| | | savingReport: false, |
| | | auditEditorReady: false, |
| | | auditOpening: false, |
| | | auditOpenProgress: 0, |
| | | auditOpenText: '', |
| | | auditOpenError: '', |
| | | auditOpenGeneration: 0, |
| | | currentAuditReportType: 0, |
| | | loadedAuditReportType: null, |
| | | auditEditSessions: {}, |
| | | auditDocument: null, |
| | | auditDirty: false, |
| | | value: ``, |
| | | reason: '', |
| | | currentInfo: null, |
| | |
| | | reader.readAsText(res) |
| | | return |
| | | } |
| | | const reportUrl = reportType === 1 ? row.inspectionUrl : (row.urlS || row.url || '') |
| | | const filename = decodeURIComponent(reportUrl.substring(reportUrl.lastIndexOf('/') + 1)) || `${row.code}.docx` |
| | | const reportNumber = String(row.code || '报告').replace(/[\\/:*?"<>|\x00-\x1f]/g, '-').trim() |
| | | const reportSuffix = row.inspectionUrl ? (reportType === 1 ? '-检验报告' : '-检测报告') : '' |
| | | const filename = `${reportNumber}${reportSuffix}.docx` |
| | | this.$download.saveAs(res, filename) |
| | | this.downloadDialogVisible = false |
| | | }).catch(() => { |
| | |
| | | }) |
| | | }, |
| | | handleRegenerate(row) { |
| | | this.$confirm('将根据当前原始记录重新生成并覆盖系统报告,是否继续?', '提示', { |
| | | if (row.urlS && !this.isBrowserEditedReportUrl(row.urlS)) { |
| | | this.$message.warning('当前使用手动上传的报告,请先点击“还原系统报告”,再重新生成') |
| | | return |
| | | } |
| | | const replacingEditedReport = this.isBrowserEditedReportUrl(row.urlS) |
| | | const prompt = replacingEditedReport |
| | | ? '重新生成将覆盖审核页面保存的修改,按当前原始记录生成新报告。是否继续?' |
| | | : '将根据当前原始记录重新生成并覆盖系统报告,是否继续?' |
| | | this.$confirm(prompt, '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | |
| | | }).catch(() => { |
| | | }) |
| | | }, |
| | | // 还原操作 |
| | | isBrowserEditedReportUrl(url) { |
| | | return /^\/word\/(?:edited|reviewed)-[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\.docx$/i.test(url || '') |
| | | }, |
| | | // 停用手动上传的报告,恢复使用系统生成的版本。 |
| | | handleRestore(row) { |
| | | upReportUrl({ |
| | | id: row.id |
| | | }).then(res => { |
| | | if (res.code === 200) { |
| | | this.$message.success('操作成功') |
| | | this.refreshTable('page') |
| | | } |
| | | this.$confirm('还原后将使用原系统报告,手动上传的版本将不再作为当前报告。是否继续?', '还原系统报告', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | return upReportUrl({ id: row.id }).then(res => { |
| | | if (res.code === 200) { |
| | | this.$message.success('已还原系统报告,可以重新生成') |
| | | this.refreshTable('page') |
| | | } |
| | | }) |
| | | }).catch(() => { |
| | | }) |
| | | }, |
| | | // 审核按钮 |
| | | handleIssued(row) { |
| | | this.currentInfo = row; |
| | | let fileName = (row.urlS === null || row.urlS === '') ? row.url : row.urlS |
| | | let fileType = "docx" |
| | | if (row.tempUrlPdf != null || row.tempUrlPdf === '') { |
| | | fileName = row.tempUrlPdf |
| | | fileType = "pdf" |
| | | async handleIssued(row) { |
| | | const generation = ++this.auditOpenGeneration |
| | | this.currentInfo = row |
| | | this.auditEditSessions = {} |
| | | this.auditDocument = null |
| | | this.auditEditorReady = false |
| | | this.auditDirty = false |
| | | this.loadedAuditReportType = null |
| | | this.currentAuditReportType = 0 |
| | | this.auditOpening = true |
| | | this.auditOpenProgress = 5 |
| | | this.auditOpenText = '正在确认报告版本…' |
| | | this.auditOpenError = '' |
| | | this.issuedVisible = true |
| | | // 让编辑器脚本与报告版本请求同时加载,避免下载完 Word 后再等待脚本。 |
| | | loadSuperDoc().catch(() => {}) |
| | | try { |
| | | // 两份报告都确认版本;未打开的报告沿用服务端持久化版本。 |
| | | const types = row.inspectionUrl ? [0, 1] : [0] |
| | | const configs = await Promise.all(types.map(type => getReportEditConfig(row.id, type))) |
| | | if (generation !== this.auditOpenGeneration || !this.issuedVisible) return |
| | | types.forEach((type, index) => { this.auditEditSessions[type] = configs[index].data }) |
| | | this.auditOpenProgress = 30 |
| | | await this.loadAuditEditor(0, generation) |
| | | } catch (e) { |
| | | if (generation !== this.auditOpenGeneration || !this.issuedVisible) return |
| | | this.auditOpening = false |
| | | this.auditOpenError = e.message || '报告打开失败,请重试' |
| | | } |
| | | fileName = fileName.replace('/word/', '') |
| | | const userName = this.nickName |
| | | this.option = { |
| | | url: this.javaApi + "/word/" + fileName, |
| | | isEdit: false, |
| | | fileType: fileType, |
| | | title: fileName, |
| | | lang: 'zh-CN', |
| | | isPrint: false, |
| | | user_id: 1, |
| | | user_name: userName, |
| | | editUrl: this.javaApi + "/insReport/onlyOffice/save?fileName=" + fileName |
| | | }, |
| | | async loadAuditEditor(reportType, generation = this.auditOpenGeneration) { |
| | | this.auditEditorReady = false |
| | | this.auditOpening = true |
| | | this.auditOpenProgress = Math.max(this.auditOpenProgress, 30) |
| | | const reportName = reportType === 0 ? '检测报告' : '检验报告' |
| | | this.auditOpenText = `正在下载${reportName}…` |
| | | const config = this.auditEditSessions[reportType] |
| | | const blob = await getReportEditDocument(config.sessionToken, event => { |
| | | if (generation !== this.auditOpenGeneration || !this.issuedVisible) return |
| | | const ratio = event.total > 0 ? event.loaded / event.total : event.progress |
| | | if (typeof ratio === 'number' && Number.isFinite(ratio)) { |
| | | this.auditOpenProgress = Math.max(this.auditOpenProgress, 30 + Math.round(Math.min(1, ratio) * 45)) |
| | | this.auditOpenText = `正在下载${reportName}(${Math.round(Math.min(1, ratio) * 100)}%)…` |
| | | } |
| | | }) |
| | | if (generation !== this.auditOpenGeneration || !this.issuedVisible) return false |
| | | if (!(blob instanceof Blob)) throw new Error('读取报告失败') |
| | | if (blob.type.includes('json')) { |
| | | const error = JSON.parse(await blob.text()) |
| | | throw new Error(error.msg || '读取报告失败') |
| | | } |
| | | this.issuedVisible = true; |
| | | this.auditOpenProgress = 80 |
| | | this.auditOpenText = '正在排版 Word 文档…' |
| | | this.auditDocument = { ...config, blob, code: this.currentInfo?.code } |
| | | this.loadedAuditReportType = reportType |
| | | this.currentAuditReportType = reportType |
| | | this.auditDirty = false |
| | | return true |
| | | }, |
| | | onAuditEditorReady() { |
| | | if (!this.issuedVisible || !this.auditDocument) return |
| | | this.auditEditorReady = true |
| | | this.auditOpenProgress = 100 |
| | | this.auditOpenText = '报告已打开' |
| | | this.auditOpenError = '' |
| | | this.auditOpening = false |
| | | }, |
| | | onAuditEditorError(error) { |
| | | if (!this.issuedVisible || !this.auditDocument) return |
| | | this.auditEditorReady = false |
| | | this.auditOpening = false |
| | | this.auditOpenError = error || 'Word 编辑器加载失败' |
| | | }, |
| | | async handleAuditReportTypeChange(reportType) { |
| | | const previous = this.loadedAuditReportType |
| | | if (Number(reportType) === Number(previous)) return |
| | | this.savingReport = true |
| | | this.auditOpening = true |
| | | this.auditOpenProgress = 10 |
| | | this.auditOpenText = '正在保存当前报告…' |
| | | this.auditOpenError = '' |
| | | try { |
| | | await this.saveCurrentAuditReport() |
| | | await this.loadAuditEditor(Number(reportType)) |
| | | } catch (e) { |
| | | this.currentAuditReportType = previous |
| | | this.auditEditorReady = !!this.auditDocument |
| | | this.auditOpening = false |
| | | this.auditOpenError = '' |
| | | this.$message.error(e.message || '报告保存失败,无法切换') |
| | | } finally { |
| | | this.savingReport = false |
| | | } |
| | | }, |
| | | async saveCurrentAuditReport() { |
| | | if (!this.auditEditorReady || !this.$refs.auditWordEditor) throw new Error('报告尚未加载完成') |
| | | const config = this.auditEditSessions[this.loadedAuditReportType] |
| | | const blob = await this.$refs.auditWordEditor.exportDocx() |
| | | const res = await saveReportEditDocument(config.sessionToken, config.version, blob) |
| | | config.version = res.data.version |
| | | this.auditDirty = false |
| | | }, |
| | | async saveAuditDraft() { |
| | | if (this.savingReport) return |
| | | this.savingReport = true |
| | | try { |
| | | await this.saveCurrentAuditReport() |
| | | this.$message.success('报告已保存') |
| | | this.refreshTable('page') |
| | | } catch (e) { |
| | | this.$message.error(e.message || '报告保存失败') |
| | | } finally { |
| | | this.savingReport = false |
| | | } |
| | | }, |
| | | async closeAuditEditor(done) { |
| | | if (this.savingReport || this.loadingIssued || this.addApproverDia) return |
| | | if (this.auditDirty) { |
| | | try { |
| | | await this.$confirm('有尚未保存的编辑,关闭将丢弃这些修改。确认关闭?', '关闭审核') |
| | | } catch (_) { return } |
| | | } |
| | | this.auditOpenGeneration++ |
| | | this.auditOpening = false |
| | | this.auditOpenError = '' |
| | | this.auditDocument = null |
| | | done() |
| | | }, |
| | | // 查看报告 |
| | | viewIssued(row) { |
| | |
| | | }); |
| | | }, |
| | | // 审核通过 |
| | | submitAddApprover() { |
| | | async submitAddApprover() { |
| | | if (!this.approver) { |
| | | this.$message.error('请选择审批人') |
| | | return |
| | |
| | | this.$message.error('审核人和批准人不能为同一人,请重新选择批准人') |
| | | return |
| | | } |
| | | if (this.loadingIssued || this.savingReport) return |
| | | this.loadingIssued = true; |
| | | try { |
| | | // 选择批准人后再次导出,审核始终绑定当前编辑内容。 |
| | | await this.saveCurrentAuditReport() |
| | | } catch (e) { |
| | | this.loadingIssued = false |
| | | this.$message.error(e.message || '报告保存失败') |
| | | return |
| | | } |
| | | examineReport({ |
| | | id: this.currentInfo.id, |
| | | userId: this.approver, |
| | | isExamine: 1 |
| | | isExamine: 1, |
| | | editSessions: Object.fromEntries(Object.entries(this.auditEditSessions).map(([type, config]) => [type, config.sessionToken])) |
| | | }).then(res => { |
| | | this.loadingIssued = false; |
| | | this.$message.success('提交成功') |
| | |
| | | this.approver = '' |
| | | }, |
| | | // 点击通过,需要选择批准人员 |
| | | subIssued() { |
| | | this.getAuthorizedPerson(true) |
| | | this.addApproverDia = true |
| | | async subIssued() { |
| | | if (this.savingReport || this.loadingIssued) return |
| | | if (!this.auditEditorReady) { |
| | | this.$message.error('报告尚未加载完成,请稍后重试') |
| | | return |
| | | } |
| | | this.savingReport = true |
| | | try { |
| | | await this.saveCurrentAuditReport() |
| | | this.getAuthorizedPerson(true) |
| | | this.addApproverDia = true |
| | | } catch (e) { |
| | | this.$message.error(e.message || 'Word 报告保存失败,暂不能审核通过') |
| | | } finally { |
| | | this.savingReport = false |
| | | } |
| | | }, |
| | | // 获取人员列表 |
| | | getAuthorizedPerson(filterExamine) { |
| | |
| | | top: 22px; |
| | | } |
| | | |
| | | .audit-editor-toolbar { |
| | | min-height: 36px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .audit-editor-content { |
| | | position: relative; |
| | | height: 70vh; |
| | | } |
| | | |
| | | .audit-opening-overlay { |
| | | position: absolute; |
| | | inset: 0; |
| | | z-index: 20; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: #fff; |
| | | } |
| | | |
| | | .audit-opening-panel { |
| | | width: min(420px, calc(100% - 48px)); |
| | | text-align: center; |
| | | } |
| | | |
| | | .audit-opening-title { |
| | | margin-bottom: 20px; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .audit-opening-step { |
| | | margin-top: 12px; |
| | | color: #606266; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .audit-opening-error p { |
| | | margin: 0 0 20px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .margin-tip { |
| | | color: #606266; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .btns { |
| | | position: absolute; |
| | | right: 40px; |