From 53bc8cc8fd330d5e2a4a3698967548a0b74a395a Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期五, 23 十月 2026 18:09:16 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'
---
src/views/business/reportPreparation/index.vue | 330 +++++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 286 insertions(+), 44 deletions(-)
diff --git a/src/views/business/reportPreparation/index.vue b/src/views/business/reportPreparation/index.vue
index 0ea5aa2..495f00f 100644
--- a/src/views/business/reportPreparation/index.vue
+++ b/src/views/business/reportPreparation/index.vue
@@ -56,8 +56,9 @@
: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)">鎻愪氦
@@ -97,18 +98,46 @@
</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">姝e湪鎵撳紑鎶ュ憡</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">
@@ -266,11 +295,15 @@
<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,
@@ -284,10 +317,11 @@
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 {
@@ -328,6 +362,18 @@
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,
@@ -871,8 +917,9 @@
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(() => {
@@ -885,7 +932,15 @@
})
},
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'
@@ -900,40 +955,153 @@
}).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 = '姝e湪纭鎶ュ憡鐗堟湰鈥�'
+ 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 = `姝e湪涓嬭浇${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 = `姝e湪涓嬭浇${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 = '姝e湪鎺掔増 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 = '姝e湪淇濆瓨褰撳墠鎶ュ憡鈥�'
+ 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) {
@@ -983,7 +1151,7 @@
});
},
// 瀹℃牳閫氳繃
- submitAddApprover() {
+ async submitAddApprover() {
if (!this.approver) {
this.$message.error('璇烽�夋嫨瀹℃壒浜�')
return
@@ -992,11 +1160,21 @@
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('鎻愪氦鎴愬姛')
@@ -1013,9 +1191,22 @@
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) {
@@ -1237,6 +1428,57 @@
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;
--
Gitblit v1.9.3