From 6eeb82e56306d645a290a0f5ba202efe3ef2385d Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期二, 27 十月 2026 17:49:09 +0800
Subject: [PATCH] feat(report): 新增PDF报告预览功能并重构报告下载流程
---
src/permission.js | 6
src/views/business/materialOrderComponents/materialOrder/downFileDialog.vue | 99 ++----
src/views/business/reportPreparation/index.vue | 183 +++++++------
src/api/cnas/process/reportResults.js | 3
src/views/CNAS/process/reportResults/index.vue | 21 +
src/views/business/productOrder/index.vue | 22 +
src/api/business/insReport.js | 13
src/views/business/inspectionTask/index.vue | 14 +
src/views/business/materialOrder/index.vue | 4
src/utils/request.js | 2
tests/attachment-download.test.mjs | 2
package.json | 1
src/components/ReportPreview/PdfViewer.vue | 193 ++++++++++++++
src/components/ReportPreview/index.vue | 123 +++++++++
vite.config.js | 3
src/utils/reportPreview.js | 40 +++
16 files changed, 573 insertions(+), 156 deletions(-)
diff --git a/package.json b/package.json
index d678fa9..da5a5e5 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
"mammoth": "^1.13.0",
"nprogress": "0.2.0",
"pako": "^3.0.2",
+ "pdfjs-dist": "5.7.284",
"pinia": "3.0.4",
"print-js": "^1.6.0",
"qrcode.vue": "^3.11.0",
diff --git a/src/api/business/insReport.js b/src/api/business/insReport.js
index d704bd2..96b869b 100644
--- a/src/api/business/insReport.js
+++ b/src/api/business/insReport.js
@@ -27,12 +27,21 @@
}
// 鍗曚唤鎶ュ憡涓嬭浇
-export function downloadReport(id, reportType) {
+export function downloadReport(id, reportType, signal) {
return request({
url: '/insReport/download',
method: 'get',
params: { id, reportType },
+ signal,
responseType: 'blob'
+ })
+}
+
+// 鎸� Word 鐗堝紡鐢熸垚鍒嗛〉 PDF锛屾部鐢ㄦ姤鍛婁笅杞界殑鏁版嵁鑼冨洿銆�
+export function previewReportPdf(id, reportType, signal) {
+ return request({
+ url: '/insReport/preview', method: 'get',
+ params: { id, reportType }, signal, responseType: 'blob'
})
}
@@ -78,7 +87,7 @@
data: { id }
})
}
-// 鎶ュ憡閫�鍥炲埌妫�楠屼换鍔�
+// 鎶ュ憡閫�鍥炲埌妫�楠屼换鍔℃垨鎶ュ憡瀹℃牳
export function sendBackTask(query) {
return request({
url: '/insReport/sendBackTask',
diff --git a/src/api/cnas/process/reportResults.js b/src/api/cnas/process/reportResults.js
index 4902f6d..64ceb98 100644
--- a/src/api/cnas/process/reportResults.js
+++ b/src/api/cnas/process/reportResults.js
@@ -48,11 +48,12 @@
}
// 涓嬭浇鎵瑰噯鍚庣敓鎴愮殑鏈夌珷鎶ュ憡
-export function downloadStampedReport(id, format) {
+export function downloadStampedReport(id, format, signal) {
return request({
url: "/processReport/downloadStampedReport",
method: "get",
params: { id, format },
+ signal,
responseType: "blob",
});
}
diff --git a/src/components/ReportPreview/PdfViewer.vue b/src/components/ReportPreview/PdfViewer.vue
new file mode 100644
index 0000000..4c64a9d
--- /dev/null
+++ b/src/components/ReportPreview/PdfViewer.vue
@@ -0,0 +1,193 @@
+<template>
+ <div class="report-pdf-viewer">
+ <div class="pdf-toolbar">
+ <el-button size="small" :disabled="currentPage <= 1" @click="goToPage(currentPage - 1)">涓婁竴椤�</el-button>
+ <span>{{ currentPage }} / {{ pages.length || '鈥�' }}</span>
+ <el-button size="small" :disabled="currentPage >= pages.length" @click="goToPage(currentPage + 1)">涓嬩竴椤�</el-button>
+ <span class="toolbar-spacer" />
+ <el-button size="small" :disabled="zoom <= 0.5" @click="zoom = Math.max(0.5, zoom - 0.25)">缂╁皬</el-button>
+ <el-button size="small" @click="zoom = 1">{{ Math.round(zoom * 100) }}%</el-button>
+ <el-button size="small" :disabled="zoom >= 2" @click="zoom = Math.min(2, zoom + 0.25)">鏀惧ぇ</el-button>
+ </div>
+ <div ref="viewport" v-loading="loading" element-loading-text="姝e湪鍔犺浇鎶ュ憡鈥�" class="pdf-viewport" @scroll="updatePage">
+ <el-result v-if="error" icon="warning" title="鎶ュ憡棰勮澶辫触" :sub-title="error">
+ <template #extra><el-button @click="openDocument">閲嶈瘯</el-button></template>
+ </el-result>
+ <div v-for="page in pages" :key="page.number" class="pdf-paper" :data-page-number="page.number"
+ :style="{ width: pageWidth(page) + 'px', height: pageWidth(page) * page.height / page.width + 'px' }">
+ <canvas :ref="element => setCanvas(element, page.number)" :data-page-number="page.number"
+ role="img" :aria-label="'鎶ュ憡绗� ' + page.number + ' 椤�'" />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, nextTick, watch, onMounted, onBeforeUnmount } from 'vue'
+import workerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?worker&url'
+import { loadReportFile, validateReportPreview } from '@/utils/reportPreview'
+
+const props = defineProps({ source: { default: null }, load: Function })
+const emit = defineEmits(['ready', 'error'])
+const viewport = ref(null)
+const pages = ref([])
+const loading = ref(false)
+const error = ref('')
+const zoom = ref(1)
+const availableWidth = ref(800)
+const currentPage = ref(1)
+const canvases = new Map()
+const rendering = new Map()
+const completed = new Set()
+let generation = 0, renderGeneration = 0, pdf = null, task = null, controller = null
+let observer = null, resizeObserver = null, resizeTimer = null
+
+// PDF 鐨� 72pt 杞负灞忓箷 96px锛欰4 绾搁粯璁ょ害 794px锛岀獎绐楀彛鍙缉灏忥紝涓嶈嚜鍔ㄦ斁澶с��
+function pageWidth(page) { return Math.min(page.width * zoom.value, Math.max(1, availableWidth.value - 32)) }
+function setCanvas(element, number) { if (element) canvases.set(number, element); else canvases.delete(number) }
+function cancelRendering() {
+ renderGeneration++
+ observer?.disconnect()
+ for (const item of rendering.values()) item.cancel()
+ rendering.clear()
+ completed.clear()
+}
+function dispose() {
+ generation++
+ controller?.abort()
+ cancelRendering()
+ task?.destroy().catch(() => {})
+ task = null
+ pdf = null
+ for (const canvas of canvases.values()) { canvas.width = 0; canvas.height = 0 }
+ canvases.clear()
+}
+async function renderPage(number, documentGeneration, viewGeneration) {
+ if (!pdf || completed.has(number) || rendering.has(number)) return
+ completed.add(number)
+ try {
+ const page = await pdf.getPage(number)
+ if (documentGeneration !== generation || viewGeneration !== renderGeneration) return
+ const canvas = canvases.get(number)
+ if (!canvas) return
+ delete canvas.dataset.rendered
+ const spec = pages.value[number - 1]
+ const scale = pageWidth(spec) / spec.width * 96 / 72
+ const density = Math.min(window.devicePixelRatio || 1, 2)
+ const size = page.getViewport({ scale })
+ canvas.width = Math.ceil(size.width * density)
+ canvas.height = Math.ceil(size.height * density)
+ const render = page.render({ canvasContext: canvas.getContext('2d'), viewport: size,
+ transform: [density, 0, 0, density, 0, 0], background: '#ffffff' })
+ rendering.set(number, render)
+ await render.promise
+ if (documentGeneration === generation && viewGeneration === renderGeneration) {
+ canvas.dataset.rendered = 'true'
+ if (number === 1) { loading.value = false; emit('ready') }
+ }
+ } catch (cause) {
+ if (documentGeneration !== generation || viewGeneration !== renderGeneration) return
+ if (cause.name !== 'RenderingCancelledException') {
+ error.value = '鎶ュ憡椤甸潰鍔犺浇澶辫触锛岃閲嶈瘯'
+ loading.value = false
+ emit('error', error.value)
+ }
+ } finally {
+ if (documentGeneration === generation && viewGeneration === renderGeneration) rendering.delete(number)
+ }
+}
+async function renderVisiblePages() {
+ if (!pdf || !viewport.value) return
+ const cancelled = [...rendering.values()].map(item => item.promise.catch(() => {}))
+ cancelRendering()
+ const documentGeneration = generation, viewGeneration = renderGeneration
+ await Promise.all(cancelled)
+ await nextTick()
+ if (documentGeneration !== generation || viewGeneration !== renderGeneration) return
+ observer = new IntersectionObserver(entries => {
+ for (const entry of entries) if (entry.isIntersecting) {
+ renderPage(Number(entry.target.dataset.pageNumber), documentGeneration, viewGeneration)
+ }
+ }, { root: viewport.value, rootMargin: '300px' })
+ for (const canvas of canvases.values()) observer.observe(canvas)
+ if (loading.value) renderPage(1, documentGeneration, viewGeneration)
+}
+async function openDocument() {
+ dispose()
+ const version = generation
+ controller = new AbortController()
+ pages.value = []
+ currentPage.value = 1
+ zoom.value = 1
+ error.value = ''
+ loading.value = true
+ try {
+ const content = props.load ? await props.load(controller.signal)
+ : props.source instanceof Blob ? props.source : await loadReportFile(props.source, controller.signal)
+ const blob = await validateReportPreview(content, 'pdf')
+ const library = await import('pdfjs-dist/build/pdf.mjs')
+ const bytes = new Uint8Array(await blob.arrayBuffer())
+ if (version !== generation) return
+ library.GlobalWorkerOptions.workerSrc = workerUrl
+ task = library.getDocument({ data: bytes, useWasm: false, useSystemFonts: true, isEvalSupported: false })
+ const document = await task.promise
+ if (version !== generation) return
+ pdf = document
+ const sizes = []
+ for (let number = 1; number <= document.numPages; number++) {
+ const page = await document.getPage(number)
+ if (version !== generation) return
+ const size = page.getViewport({ scale: 96 / 72 })
+ sizes.push({ number, width: size.width, height: size.height })
+ }
+ pages.value = sizes
+ await nextTick()
+ viewport.value.scrollTop = 0
+ await renderVisiblePages()
+ } catch (cause) {
+ if (version !== generation) return
+ error.value = cause.message || '鎶ュ憡鍔犺浇澶辫触锛岃閲嶈瘯'
+ loading.value = false
+ emit('error', error.value)
+ }
+}
+function goToPage(number) {
+ const target = viewport.value?.querySelector(`[data-page-number="${number}"]`)
+ if (target) { viewport.value.scrollTop = target.offsetTop - 16; currentPage.value = number }
+}
+function updatePage() {
+ const top = viewport.value.getBoundingClientRect().top
+ for (const paper of viewport.value.querySelectorAll('.pdf-paper')) {
+ if (paper.getBoundingClientRect().bottom > top + 32) {
+ currentPage.value = Number(paper.dataset.pageNumber)
+ break
+ }
+ }
+}
+watch(() => [props.source, props.load], () => { if (viewport.value) openDocument() })
+watch(zoom, renderVisiblePages)
+onMounted(() => {
+ availableWidth.value = viewport.value.clientWidth
+ resizeObserver = new ResizeObserver(() => {
+ const width = viewport.value?.clientWidth
+ if (!width || width === availableWidth.value) return
+ const previousWidths = pages.value.map(pageWidth)
+ availableWidth.value = width
+ if (pages.value.every((page, index) => pageWidth(page) === previousWidths[index])) return
+ clearTimeout(resizeTimer)
+ resizeTimer = setTimeout(renderVisiblePages, 100)
+ })
+ resizeObserver.observe(viewport.value)
+ openDocument()
+})
+onBeforeUnmount(() => { clearTimeout(resizeTimer); resizeObserver?.disconnect(); dispose() })
+</script>
+
+<style scoped>
+.report-pdf-viewer { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; background: #fff; color: #303133; color-scheme: light; }
+.pdf-toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; flex: 0 0 auto; padding: 8px 12px; background: #fff; border-bottom: 1px solid #ebeef5; }
+.toolbar-spacer { flex: 1; }
+.pdf-viewport { flex: 1; min-height: 0; overflow: auto; position: relative; background: #fff; }
+.pdf-paper { position: relative; margin: 16px auto; background: #fff; box-shadow: 0 0 0 1px #e4e7ed; }
+.pdf-paper canvas { display: block; width: 100%; height: 100%; background: #fff; }
+</style>
diff --git a/src/components/ReportPreview/index.vue b/src/components/ReportPreview/index.vue
new file mode 100644
index 0000000..1559a9a
--- /dev/null
+++ b/src/components/ReportPreview/index.vue
@@ -0,0 +1,123 @@
+<template>
+ <el-dialog v-model="visible" title="鎶ュ憡棰勮" width="90vw" :fullscreen="fullscreen"
+ append-to-body destroy-on-close :close-on-click-modal="false" @close="reset">
+ <div class="report-preview-toolbar">
+ <el-radio-group v-if="variants.length > 1" :model-value="selectedKey" @change="selectReport">
+ <el-radio-button v-for="item in variants" :key="item.key" :value="item.key">{{ item.label }}</el-radio-button>
+ </el-radio-group>
+ <span class="report-preview-name">{{ current?.filename }}</span>
+ <el-button @click="fullscreen = !fullscreen">{{ fullscreen ? '閫�鍑哄叏灞�' : '鍏ㄥ睆鏌ョ湅' }}</el-button>
+ <el-button type="primary" :disabled="!blob || loading" :loading="downloading" @click="download">涓嬭浇褰撳墠鎶ュ憡</el-button>
+ </div>
+ <div v-loading="loading" element-loading-text="姝e湪鍔犺浇鎶ュ憡鈥�" class="report-preview-content"
+ :class="{ 'is-fullscreen': fullscreen }">
+ <el-result v-if="error" icon="warning" title="鎶ュ憡棰勮澶辫触" :sub-title="error">
+ <template #extra><el-button @click="selectReport(selectedKey, true)">閲嶈瘯</el-button></template>
+ </el-result>
+ <pdf-viewer v-else-if="blob" :source="blob" />
+ </div>
+ </el-dialog>
+</template>
+
+<script>
+import { validateReportPreview } from '@/utils/reportPreview'
+import PdfViewer from './PdfViewer.vue'
+
+export default {
+ name: 'ReportPreview',
+ components: { PdfViewer },
+ data() {
+ return {
+ visible: false, fullscreen: false, variants: [], selectedKey: null,
+ loading: false, downloading: false, error: '', blob: null, objectUrl: '',
+ generation: 0, controller: null, downloadController: null, cache: new Map()
+ }
+ },
+ computed: {
+ current() { return this.variants.find(item => item.key === this.selectedKey) }
+ },
+ beforeUnmount() { this.reset() },
+ methods: {
+ open(variants, selectedKey = variants[0].key) {
+ this.reset()
+ this.variants = variants
+ this.fullscreen = false
+ this.visible = true
+ return this.selectReport(selectedKey)
+ },
+ releaseUrl() {
+ if (this.objectUrl) URL.revokeObjectURL(this.objectUrl)
+ this.objectUrl = ''
+ this.blob = null
+ },
+ reset() {
+ this.generation++
+ this.controller?.abort()
+ this.downloadController?.abort()
+ this.controller = null
+ this.downloadController = null
+ this.releaseUrl()
+ this.cache.clear()
+ this.loading = false
+ this.downloading = false
+ this.error = ''
+ },
+ async selectReport(key, retry = false) {
+ this.controller?.abort()
+ this.downloadController?.abort()
+ this.downloading = false
+ const generation = ++this.generation
+ this.selectedKey = key
+ this.releaseUrl()
+ this.error = ''
+ this.loading = true
+ const controller = new AbortController()
+ this.controller = controller
+ const item = this.current
+ try {
+ let blob = retry ? null : this.cache.get(key)
+ if (!blob) blob = await validateReportPreview(await item.load(controller.signal), 'pdf')
+ // 鍏抽棴寮圭獥銆佸揩閫熷垏鎹㈡姤鍛婃垨鎵撳紑鍙︿竴琛屽悗锛屾棫璇锋眰涓嶅緱瑕嗙洊褰撳墠鍐呭銆�
+ if (generation !== this.generation) return
+ this.cache.set(key, blob)
+ this.blob = blob
+ this.objectUrl = URL.createObjectURL(blob)
+ } catch (error) {
+ if (generation !== this.generation) return
+ const status = error.response?.status
+ this.error = status === 404 ? '鎶ュ憡鏂囦欢涓嶅瓨鍦紝璇疯仈绯荤鐞嗗憳'
+ : status === 401 || status === 403 ? '鐧诲綍宸茶繃鏈熸垨娌℃湁鎶ュ憡鏌ョ湅鏉冮檺'
+ : error.message || '鎶ュ憡鍔犺浇澶辫触锛岃绋嶅悗閲嶈瘯'
+ } finally {
+ if (generation === this.generation) this.loading = false
+ }
+ },
+ async download() {
+ if (!this.blob || this.loading || this.downloading) return
+ const generation = this.generation
+ const item = this.current
+ this.downloading = true
+ const controller = new AbortController()
+ this.downloadController = controller
+ try {
+ // 棰勮缁熶竴鏄剧ず PDF锛涙姤鍛婄紪鍒朵粛涓嬭浇鍘� Word锛屽叾浠栧叆鍙e鐢ㄥ凡鍔犺浇鐨� PDF銆�
+ const blob = item.download
+ ? await validateReportPreview(await item.download(controller.signal), item.downloadFormat || 'docx')
+ : this.blob
+ if (generation === this.generation) this.$download.saveAs(blob, item.filename)
+ } catch (error) {
+ if (generation === this.generation) this.$message.error(error.message || '鎶ュ憡涓嬭浇澶辫触')
+ } finally {
+ if (generation === this.generation) this.downloading = false
+ }
+ }
+ }
+}
+</script>
+
+<style scoped>
+.report-preview-toolbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
+.report-preview-name { flex: 1; min-width: 120px; overflow-wrap: anywhere; color: var(--el-text-color-secondary); }
+.report-preview-content { height: 75vh; min-height: 300px; overflow: auto; background: #fff; }
+.report-preview-content.is-fullscreen { height: calc(100vh - 160px); }
+</style>
diff --git a/src/permission.js b/src/permission.js
index c66a9d3..983a66e 100644
--- a/src/permission.js
+++ b/src/permission.js
@@ -75,3 +75,9 @@
router.afterEach(() => {
NProgress.done()
})
+
+router.onError(error => {
+ NProgress.done()
+ console.error('[router] 椤甸潰鍔犺浇澶辫触锛�', error)
+ ElMessage.error('椤甸潰鍔犺浇澶辫触锛岃鍒锋柊椤甸潰鍚庨噸璇�')
+})
diff --git a/src/utils/reportPreview.js b/src/utils/reportPreview.js
new file mode 100644
index 0000000..90eef92
--- /dev/null
+++ b/src/utils/reportPreview.js
@@ -0,0 +1,40 @@
+import axios from 'axios'
+import { getToken } from '@/utils/auth'
+import { getJavaApi } from '@/utils/java-api'
+import { resolveAttachmentUrl, validateAttachmentBlob } from '@/utils/attachment'
+
+export function reportFilename(value, extension) {
+ const name = String(value || '鎶ュ憡').replace(/[\\/:*?"<>|\x00-\x1f]/g, '-').trim() || '鎶ュ憡'
+ return name.replace(/\.(docx|pdf)$/i, '') + '.' + extension
+}
+
+// 鍙浛鎹㈡湯灏炬墿灞曞悕锛屽吋瀹圭洰褰曞悕銆佹姤鍛婄紪鍙蜂腑鍚皬鏁扮偣鐨勫巻鍙叉枃浠躲��
+export function reportPdfPath(path) {
+ return path.replace(/\.docx?$/i, '.pdf')
+}
+
+// 涓嬪崟椤垫部鐢ㄥ凡鏈夋姤鍛婃枃浠跺叆鍙o紝閴存潈淇℃伅鍙彂閫佺粰鏈郴缁熴��
+export async function loadReportFile(path, signal) {
+ const url = resolveAttachmentUrl(path, 2)
+ const backend = new URL(getJavaApi(), window.location.origin)
+ const target = new URL(url, window.location.origin)
+ const isBackend = target.origin === backend.origin
+ && target.pathname.startsWith(backend.pathname.replace(/\/$/, '') + '/')
+ const { data } = await axios.get(url, {
+ responseType: 'blob', signal,
+ headers: isBackend && getToken() ? { Authorization: 'Bearer ' + getToken() } : {}
+ })
+ return data
+}
+
+export async function validateReportPreview(blob, format) {
+ if (!(blob instanceof Blob) || !blob.size) throw new Error('鎶ュ憡鍐呭涓虹┖锛岃閲嶈瘯鎴栬仈绯荤鐞嗗憳')
+ await validateAttachmentBlob(blob, '鎶ュ憡.' + format)
+ const header = new Uint8Array(await blob.slice(0, 5).arrayBuffer())
+ const isPdf = String.fromCharCode(...header) === '%PDF-'
+ const isDocx = header[0] === 0x50 && header[1] === 0x4b
+ if (format === 'pdf' ? !isPdf : !isDocx) throw new Error('鏈幏鍙栧埌鏈夋晥鐨勬姤鍛婃枃浠讹紝璇烽噸璇曟垨鑱旂郴绠$悊鍛�')
+ // 闈欐�佹枃浠舵湇鍔″櫒鍙兘杩斿洖 octet-stream锛孭DF 闃呰鍣ㄩ渶瑕佹槑纭殑 MIME 绫诲瀷銆�
+ return new Blob([blob], { type: format === 'pdf' ? 'application/pdf'
+ : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' })
+}
diff --git a/src/utils/request.js b/src/utils/request.js
index f93bf2e..6a89da6 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -115,6 +115,8 @@
}
},
error => {
+ // 棰勮绐楀彛鍏抽棴鎴栧垏鎹㈡姤鍛婁細涓诲姩鍙栨秷璇诲彇锛屼笉鎻愮ず涓烘帴鍙e紓甯搞��
+ if (axios.isCancel(error)) return Promise.reject(error)
console.log('err' + error)
let { message } = error
if (message == "Network Error") {
diff --git a/src/views/CNAS/process/reportResults/index.vue b/src/views/CNAS/process/reportResults/index.vue
index b594601..91094c0 100644
--- a/src/views/CNAS/process/reportResults/index.vue
+++ b/src/views/CNAS/process/reportResults/index.vue
@@ -114,14 +114,18 @@
</el-form>
<template #footer><span class="dialog-footer">
<el-button @click="downloadDialogVisible = false">鍙� 娑�</el-button>
+ <el-button :disabled="!downloadRow" @click="previewReport(downloadRow)">棰勮鏈夌珷鎶ュ憡</el-button>
<el-button type="primary" :loading="downloadingReportId !== null"
@click="downloadSelectedReport">涓� 杞�</el-button>
</span></template>
</el-dialog>
+ <report-preview ref="reportPreview" />
</div>
</template>
<script>
+import ReportPreview from '@/components/ReportPreview/index.vue'
+import { reportFilename } from '@/utils/reportPreview'
import limsTable from "@/components/Table/lims-table.vue";
import {
exportProcessReport,
@@ -137,7 +141,8 @@
export default {
name: 'ReportResults',
components: {
- limsTable
+ limsTable,
+ ReportPreview
},
data() {
return {
@@ -171,8 +176,13 @@
dataType: "action",
fixed: "right",
label: "鎿嶄綔",
- width: "180px",
+ width: "230px",
operation: [
+ {
+ name: "棰勮",
+ type: "text",
+ clickFun: row => this.previewReport(row),
+ },
{
name: "涓嬭浇",
type: "text",
@@ -305,6 +315,13 @@
this.addInfo = { ...row }
this.addDialogVisible = true;
},
+ previewReport(row) {
+ this.$refs.reportPreview.open([{
+ key: 'stamped', label: '鏈夌珷鎶ュ憡', format: 'pdf',
+ filename: reportFilename(row.insReportCode || '鏈夌珷鎶ュ憡', 'pdf'),
+ load: signal => downloadStampedReport(row.id, 'pdf', signal)
+ }])
+ },
handleDownload(row) {
this.downloadRow = row
this.selectedDownloadFormat = 'pdf'
diff --git a/src/views/business/inspectionTask/index.vue b/src/views/business/inspectionTask/index.vue
index 9131551..28e4def 100644
--- a/src/views/business/inspectionTask/index.vue
+++ b/src/views/business/inspectionTask/index.vue
@@ -417,7 +417,7 @@
},
},
{
- label: "鐘舵��",
+ label: "妫�楠岀姸鎬�",
prop: "insState",
dataType: "tag",
formatData: (params) => {
@@ -429,6 +429,18 @@
return item ? item.type : "info";
},
},
+ {
+ label: "鎶ュ憡閫�鍥炵姸鎬�",
+ prop: "reportReturnSource",
+ width: "140px",
+ dataType: "tag",
+ formatData: (source) => {
+ if (source === "APPROVED") return "鎶ュ憡鎵瑰噯閫�鍥�";
+ if (source === "REPORT_REVIEW") return "鎶ュ憡瀹℃牳閫�鍥�";
+ return null;
+ },
+ formatType: () => "warning",
+ },
{ label: "妫�楠屼汉", prop: "userName" },
{ label: "澶嶆牳浜�", prop: "checkName" },
{ label: "绾﹀畾鏃堕棿", prop: "appointed" },
diff --git a/src/views/business/materialOrder/index.vue b/src/views/business/materialOrder/index.vue
index 3695671..017a0ba 100644
--- a/src/views/business/materialOrder/index.vue
+++ b/src/views/business/materialOrder/index.vue
@@ -552,7 +552,7 @@
}
},
{
- name: '鎶ュ憡涓嬭浇',
+ name: '鎶ュ憡棰勮/涓嬭浇',
type: 'text',
clickFun: (row) => {
this.download(row);
@@ -887,7 +887,7 @@
},
},
{
- name: '鎶ュ憡涓嬭浇',
+ name: '鎶ュ憡棰勮/涓嬭浇',
type: 'text',
clickFun: (row) => {
this.download(row);
diff --git a/src/views/business/materialOrderComponents/materialOrder/downFileDialog.vue b/src/views/business/materialOrderComponents/materialOrder/downFileDialog.vue
index 37bc1d8..76f465c 100644
--- a/src/views/business/materialOrderComponents/materialOrder/downFileDialog.vue
+++ b/src/views/business/materialOrderComponents/materialOrder/downFileDialog.vue
@@ -1,85 +1,58 @@
<template>
<div>
- <el-dialog title="鎶ュ憡鏌ョ湅涓嬭浇" v-model="isShow" width="500px" @closed="$emit('closeDownFileDialog')">
- <div style="margin-bottom: 18px">
- <span style="font-size: 16px;">杩涘巶妫�楠屾姤鍛婁笅杞�</span>
- <lims-icon name="el-icon-paperclip" style="color:var(--el-color-primary);margin-left: 6px"></lims-icon>
- <el-link :underline="false" style="vertical-align: bottom;font-size: 16px;"
- :disabled="!downLoadInfo.enterUrlS && !downLoadInfo.enterUrl"
- type="primary" @click="downLoad0">鏌ョ湅</el-link>
- </div>
- <div>
- <span style="font-size: 16px;">瀛e害妫�楠屾姤鍛婁笅杞�</span>
- <lims-icon name="el-icon-paperclip" style="color:var(--el-color-primary);margin-left: 6px"></lims-icon>
- <el-link :underline="false" style="vertical-align: bottom;font-size: 16px;"
- :disabled="!downLoadInfo.quarterUrlS && !downLoadInfo.quarterUrl"
- type="primary" @click="downLoad1">鏌ョ湅</el-link>
+ <el-dialog title="鎶ュ憡棰勮/涓嬭浇" v-model="isShow" width="520px" @closed="$emit('closeDownFileDialog')">
+ <div v-for="item in reports" :key="item.key" class="report-choice">
+ <span>{{ item.label }}</span>
+ <el-button link type="primary" :disabled="!canView(item)" @click="preview(item)">棰勮</el-button>
+ <el-button link type="primary" :disabled="!canView(item)" @click="download(item)">涓嬭浇</el-button>
+ <span v-if="!canView(item)" class="report-unavailable">鏈敓鎴愭垨灏氭湭鎵瑰噯</span>
</div>
</el-dialog>
+ <report-preview ref="reportPreview" />
</div>
</template>
<script>
+import ReportPreview from '@/components/ReportPreview/index.vue'
+import { loadReportFile, reportFilename, reportPdfPath } from '@/utils/reportPreview'
+
export default {
- name: "downFileDialog",
- // import 寮曞叆鐨勭粍浠堕渶瑕佹敞鍏ュ埌瀵硅薄涓墠鑳戒娇鐢�
- components: {},
+ name: 'downFileDialog',
+ components: { ReportPreview },
props: {
- downFileDialogVisible: {
- type: Boolean,
- default: () => false
- },
- downLoadInfo: {
- type: Object,
- default: () => {}
- },
+ downFileDialogVisible: { type: Boolean, default: false },
+ downLoadInfo: { type: Object, default: () => ({}) }
},
data() {
- // 杩欓噷瀛樻斁鏁版嵁
- return {
- isShow: this.downFileDialogVisible,
+ return { isShow: this.downFileDialogVisible }
+ },
+ computed: {
+ reports() {
+ const info = this.downLoadInfo
+ return [
+ { key: 'enter', label: '杩涘巶妫�楠屾姤鍛�', url: info.enterUrlS || info.enterUrl, status: info.enterRatifyStatus },
+ { key: 'quarter', label: '瀛e害妫�楠屾姤鍛�', url: info.quarterUrlS || info.quarterUrl, status: info.quarterRatifyStatus }
+ ]
}
},
- // 鏂规硶闆嗗悎
methods: {
- // 杩涘巶妫�楠屾姤鍛婁笅杞�
- downLoad0 () {
- if(this.downLoadInfo.enterRatifyStatus !== 1){
- this.$message.error("鎶ュ憡鏈鎵规垨瀹℃壒鏈�氳繃锛屾棤娉曚笅杞�")
- return
- }
- let url = this.downLoadInfo.enterUrlS ? this.downLoadInfo.enterUrlS : this.downLoadInfo.enterUrl
- if(url){
- url = url.split('.')[0]+'.pdf'
- const link = document.createElement('a');
- link.href = this.javaApi + url;
- link.target = '_blank';
- document.body.appendChild(link);
- link.click();
- }
+ canView(item) { return !!item.url && Number(item.status) === 1 },
+ preview(item) {
+ if (!this.canView(item)) return
+ this.$refs.reportPreview.open([{
+ key: item.key, label: item.label, format: 'pdf',
+ filename: reportFilename(item.label, 'pdf'),
+ load: signal => loadReportFile(reportPdfPath(item.url), signal)
+ }])
},
- // 瀛e害妫�楠屾姤鍛婁笅杞�
- downLoad1 () {
- if(this.downLoadInfo.quarterRatifyStatus !== 1){
- this.$message.error("鎶ュ憡鏈鎵规垨瀹℃壒鏈�氳繃锛屾棤娉曚笅杞�")
- return
- }
- let url = this.downLoadInfo.quarterUrlS ? this.downLoadInfo.quarterUrlS : this.downLoadInfo.quarterUrl
- if(url){
- url = url.split('.')[0]+'.pdf'
- const link = document.createElement('a');
- link.href = this.javaApi + url;
- link.target = '_blank';
- document.body.appendChild(link);
- link.click();
- }
+ download(item) {
+ if (this.canView(item)) this.$download.url(reportPdfPath(item.url), item.label + '.pdf')
}
- },
+ }
}
</script>
<style scoped>
-:deep(.el-dialog) {
- height: 160px;
-}
+.report-choice { display: flex; align-items: center; gap: 12px; padding: 12px 0; flex-wrap: wrap; }
+.report-unavailable { color: var(--el-text-color-secondary); font-size: 12px; }
</style>
diff --git a/src/views/business/productOrder/index.vue b/src/views/business/productOrder/index.vue
index 49965e2..8bdd92c 100644
--- a/src/views/business/productOrder/index.vue
+++ b/src/views/business/productOrder/index.vue
@@ -265,10 +265,13 @@
<el-button :loading="submitListLoad" type="primary" @click="submitList">纭� 瀹�</el-button>
</span></template>
</el-dialog>
+ <report-preview ref="reportPreview" />
</div>
</template>
<script>
+import ReportPreview from '@/components/ReportPreview/index.vue'
+import { loadReportFile, reportFilename, reportPdfPath } from '@/utils/reportPreview'
// vue-qr 鍙敮鎸� Vue2锛屾崲鎴愬悓 props 鐨勬湰鍦扮粍浠讹紙鍐呴儴鐢� Vue3 鐨� qrcode.vue锛�
import vueQr from '@/components/QrCode'
import PrintJS from 'print-js'
@@ -290,6 +293,7 @@
components: {
limsTable,
PrintDialog,
+ ReportPreview,
vueQr
},
data() {
@@ -373,6 +377,13 @@
showHide: (row) => {
return this.tabIndex === 1 || this.tabIndex === 2 || this.tabIndex === 3 || this.tabIndex === 6 || this.tabIndex === 7
}
+ },
+ {
+ name: '鎶ュ憡棰勮',
+ type: 'text',
+ clickFun: (row) => this.previewReport(row),
+ disabled: (row) => !this.canDownloadReport(row),
+ showHide: () => [2, 3, 7].includes(this.tabIndex)
},
{
name: '鎶ュ憡涓嬭浇',
@@ -882,12 +893,21 @@
canDownloadReport(row) {
return !!(row && row.reportId && (row.urlS || row.url))
},
+ previewReport(row) {
+ if (!this.canDownloadReport(row)) return
+ const path = reportPdfPath(row.urlS || row.url)
+ this.$refs.reportPreview.open([{
+ key: 'approved', label: '鏈夌珷鎶ュ憡', format: 'pdf',
+ filename: reportFilename(row.reportCode || row.entrustCode || row.code, 'pdf'),
+ load: signal => loadReportFile(path, signal)
+ }])
+ },
download(row) {
if (!this.canDownloadReport(row)) {
return
}
let url = row.urlS ? row.urlS : row.url;
- url = url.split('.')[0] + '.pdf'
+ url = reportPdfPath(url)
const link = document.createElement('a');
link.href = this.javaApi + url;
link.target = '_blank';
diff --git a/src/views/business/reportPreparation/index.vue b/src/views/business/reportPreparation/index.vue
index 2cb25ad..09de219 100644
--- a/src/views/business/reportPreparation/index.vue
+++ b/src/views/business/reportPreparation/index.vue
@@ -48,7 +48,8 @@
:isSelection="true" :handleSelectionChange="handleChange" :height="'calc(100vh - 290px)'"
@pagination="pagination" key="valueTableData">
<template #action="scope"><div>
- <!-- <el-button size="small" type="text" @click="viewIssued(scope.row)">鏌ョ湅鎶ュ憡</el-button>-->
+ <el-button link type="primary" size="small" :disabled="!scope.row.url && !scope.row.urlS"
+ @click="previewReport(scope.row)">棰勮</el-button>
<el-upload ref='upload1' style="display: inline;margin: 0 6px" :action="fileAction1 + '?id=' + scope.row.id"
:auto-upload="true" :before-upload="fileBeforeUpload1" :headers="uploadHeader" :on-error="onError1"
:on-success="handleSuccessUp1" :show-file-list="false" accept='.doc,.docx'>
@@ -59,7 +60,8 @@
<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 v-if="scope.row.state == 1" link type="primary" size="small"
+ @click="openSendBack(scope.row)">閫�鍥炰换鍔�</el-button>
<el-button link type="primary" size="small" :disabled="scope.row.state != 0 || nickName !== scope.row.writeUserName"
@click="handle(scope.row)">鎻愪氦
</el-button>
@@ -68,7 +70,7 @@
@click="handleIssued(scope.row)">瀹℃牳
</el-button>
<el-button link type="primary" size="small"
- :disabled="scope.row.state == null || scope.row.state == 0 || scope.row.isExamine == 0 || scope.row.isExamine == null || scope.row.isRatify == 1 || nickName !== scope.row.ratifyUser"
+ :disabled="scope.row.state == null || scope.row.state == 0 || scope.row.isExamine != 1 || scope.row.isRatify == 1 || nickName !== scope.row.ratifyUser"
@click="handleApprove(scope.row)">鎵瑰噯
</el-button>
<el-button style="margin-left: 10px" link type="primary" size="small"
@@ -84,6 +86,25 @@
</div></template>
</lims-table>
</div>
+ <el-dialog v-model="sendBackVisible" title="閫�鍥炰换鍔�" width="460px" :close-on-click-modal="false">
+ <el-form label-width="90px">
+ <el-form-item label="閫�鍥炰綅缃�">
+ <el-radio-group v-if="sendBackRow && sendBackRow.isRatify == 1" v-model="sendBackForm.target">
+ <el-radio value="INSPECTION_TASK">閫�鍥炲埌妫�楠屼换鍔�</el-radio>
+ <el-radio value="REPORT_REVIEW">閫�鍥炲埌鎶ュ憡瀹℃牳</el-radio>
+ </el-radio-group>
+ <span v-else>閫�鍥炲埌妫�楠屼换鍔�</span>
+ </el-form-item>
+ <el-form-item label="閫�鍥炲師鍥�">
+ <el-input v-model="sendBackForm.reason" type="textarea" :rows="4" maxlength="1000"
+ show-word-limit placeholder="璇疯緭鍏ラ��鍥炲師鍥�" />
+ </el-form-item>
+ </el-form>
+ <template #footer><span class="dialog-footer">
+ <el-button :disabled="sendBackLoading" @click="sendBackVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" :loading="sendBackLoading" @click="submitSendBack">纭畾閫�鍥�</el-button>
+ </span></template>
+ </el-dialog>
<el-dialog :fullscreen="fullscreen" :modal-append-to-body="false" v-model="claimVisible" title="鍦ㄧ嚎缂栧埗"
width="22cm">
<div class="full-screen">
@@ -160,7 +181,7 @@
<img v-else alt="" src="@/assets/images/no-full.svg" style="cursor: pointer;" @click="fullscreen = false;">
</div>
<div v-if="approveVisible" class="approve-preview">
- <onlyoffice ref="onlyoffice" :options="option" style="width: 100%;height: 100%;"/>
+ <pdf-viewer :key="currentInfo.id" :load="loadApprovalPdf" />
</div>
<div class="seal-select-block" v-loading="sealLoading">
<div class="seal-select-title">
@@ -247,18 +268,7 @@
</el-dialog>
<!--浜т笟閾句俊鎭煡鐪�-->
<!-- <ShowInfo v-if="showInfoDialog" ref="showInfoDialog" :showInfoDialog="showInfoDialog"></ShowInfo>-->
- <!--鎶ュ憡鏌ョ湅-->
- <el-dialog :fullscreen="fullscreen" :modal-append-to-body="false" v-model="viewIssuedVisible" title="鎶ュ憡鏌ョ湅"
- width="80vw">
- <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="viewIssuedVisible" style="height: 80vh;">
- <onlyoffice ref="onlyoffice" :options="option" style="width: 100%;height: 100%;"/>
- </div>
- </el-dialog>
+ <report-preview ref="reportPreview" />
<!--闄勪欢鏌ョ湅-->
<el-dialog v-model="filesDialogVisible" title="闄勪欢鏌ョ湅" width="80%" @closed="closeFilesLook">
<div style="margin-bottom: 10px">
@@ -285,6 +295,7 @@
</el-form>
<template #footer><span class="dialog-footer">
<el-button @click="downloadDialogVisible = false">鍙� 娑�</el-button>
+ <el-button :disabled="!downloadRow" @click="previewReport(downloadRow, selectedReportType)">棰� 瑙�</el-button>
<el-button type="primary" :loading="downloadingReportType !== null"
:disabled="selectedReportType === null || downloadingReportType !== null"
@click="downloadSelectedReport(selectedReportType)">涓� 杞�</el-button>
@@ -294,12 +305,15 @@
</template>
<script>
-import onlyoffice from "@/components/Onlyoffice/onlyoffice.vue";
+import PdfViewer from '@/components/ReportPreview/PdfViewer.vue'
import ReportWordEditor from "@/components/ReportWordEditor/index.vue";
+import ReportPreview from '@/components/ReportPreview/index.vue'
+import { reportFilename } from '@/utils/reportPreview'
// import ShowInfo from "../do/b1-material-ins-order/showInfo.vue";
import {
downAll,
downloadReport,
+ previewReportPdf,
examineReport,
getReportEditDocument,
saveReportEditDocument,
@@ -321,10 +335,11 @@
export default {
name: 'ReportPreparation',
- components: {limsTable, onlyoffice, ReportWordEditor},
+ components: {limsTable, PdfViewer, ReportWordEditor, ReportPreview},
dicts: ['material_prop_type'],
data() {
return {
+ hasActivated: false,
entity: {
queryStatus: null,
code: null,
@@ -345,6 +360,13 @@
issuedReasonVisible: false,
approveVisible: false,
approveReasonVisible: false,
+ sendBackVisible: false,
+ sendBackLoading: false,
+ sendBackRow: null,
+ sendBackForm: {
+ target: null,
+ reason: ''
+ },
sealOptions: [],
sealPositions: [
{ key: 'maSealId', label: 'MA鏍囧織绔�', description: 'PDF 绗�1椤靛乏涓婅' },
@@ -418,11 +440,11 @@
customsInspection: {},
currentId: null,
examine: null,
- viewIssuedVisible: false,
queryStatusList: [
{label: '寰呮彁浜�', value: 0},
{label: '寰呭鏍�', value: 1},
{label: '寰呮壒鍑�', value: 2},
+ {label: '宸查��鍥�', value: 3},
],
state: 0,
orderId: 0,
@@ -580,6 +602,8 @@
return "涓嶉�氳繃";
} else if (params == 1) {
return "閫氳繃";
+ } else if (params == 2) {
+ return "宸查��鍥�";
} else {
return null
}
@@ -589,6 +613,8 @@
return "danger";
} else if (params == 1) {
return "success";
+ } else if (params == 2) {
+ return "warning";
} else {
return undefined
}
@@ -607,6 +633,8 @@
return "涓嶆壒鍑�";
} else if (params == 1) {
return "鎵瑰噯";
+ } else if (params == 2) {
+ return "宸查��鍥�";
} else {
return null
}
@@ -616,6 +644,8 @@
return "danger";
} else if (params == 1) {
return "success";
+ } else if (params == 2) {
+ return "warning";
} else {
return undefined
}
@@ -647,6 +677,13 @@
},
mounted() {
this.refreshTable()
+ },
+ activated() {
+ // 棣栨杩涘叆鐢� mounted 鍔犺浇锛岀紦瀛橀〉闈㈠啀娆℃縺娲绘椂鍒锋柊鍒楄〃銆�
+ if (this.hasActivated) {
+ this.refreshTable()
+ }
+ this.hasActivated = true
},
methods: {
refreshTable() {
@@ -1103,52 +1140,47 @@
this.auditDocument = null
done()
},
- // 鏌ョ湅鎶ュ憡
- viewIssued(row) {
- console.log('this.javaApi', this.javaApi)
- 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"
- }
- 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
- }
- this.viewIssuedVisible = true;
+ // 浣跨敤鏈嶅姟绔寜 Word 鐗堝紡鐢熸垚鐨� PDF锛屽鏉傝〃鏍间笉鍦ㄦ祻瑙堝櫒涓噸鏂版帓鐗堛��
+ previewReport(row, reportType = 0) {
+ const variants = [{ key: 0, label: '妫�娴嬫姤鍛�' }]
+ if (row.inspectionUrl) variants.push({ key: 1, label: '妫�楠屾姤鍛�' })
+ this.$refs.reportPreview.open(variants.map(item => ({
+ ...item, format: 'pdf',
+ filename: reportFilename((row.code || '鎶ュ憡') + (row.inspectionUrl ? '-' + item.label : ''), 'docx'),
+ load: signal => previewReportPdf(row.id, item.key, signal),
+ download: signal => downloadReport(row.id, item.key, signal), downloadFormat: 'docx'
+ })), reportType)
},
- // 閫�鍥炲埌浠诲姟
- sendBackTask(row) {
- this.$confirm('纭閫�鍥炲埌妫�楠屼换鍔�?閫�鍥炲悗闇�閲嶆柊鎻愪氦澶嶆牳', '鎻愮ず', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
+ openSendBack(row) {
+ this.sendBackRow = row
+ this.sendBackForm = {
+ target: row.isRatify == 1 ? null : 'INSPECTION_TASK',
+ reason: ''
+ }
+ this.sendBackVisible = true
+ },
+ submitSendBack() {
+ if (!this.sendBackForm.target) {
+ return this.$message.error('璇烽�夋嫨閫�鍥炰綅缃�')
+ }
+ if (!this.sendBackForm.reason || !this.sendBackForm.reason.trim()) {
+ return this.$message.error('璇疯緭鍏ラ��鍥炲師鍥�')
+ }
+ if (this.sendBackLoading || !this.sendBackRow) return
+ this.sendBackLoading = true
+ const targetName = this.sendBackForm.target === 'REPORT_REVIEW' ? '鎶ュ憡瀹℃牳' : '妫�楠屼换鍔�'
+ sendBackTask({
+ id: this.sendBackRow.id,
+ target: this.sendBackForm.target,
+ reason: this.sendBackForm.reason.trim()
}).then(() => {
- this.upLoad = true
- sendBackTask({id: row.id}).then(res => {
- this.upLoad = false
- this.$message.success('閫�鍥炴垚鍔燂紒')
- this.refreshTable('page')
- }).catch(err => {
- this.upLoad = false
- })
- }).catch(() => {
- this.$message({
- type: 'info',
- message: '宸插彇娑堝垹闄�'
- });
- });
+ this.$message.success(`宸查��鍥炲埌${targetName}`)
+ this.sendBackVisible = false
+ this.sendBackRow = null
+ this.refreshTable('page')
+ }).finally(() => {
+ this.sendBackLoading = false
+ })
},
// 瀹℃牳閫氳繃
async submitAddApprover() {
@@ -1228,26 +1260,11 @@
handleApprove(row) {
this.currentInfo = row;
this.loadEnabledSeals(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"
- }
- 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
- }
+ this.fullscreen = false;
this.approveVisible = true;
+ },
+ loadApprovalPdf(signal) {
+ return previewReportPdf(this.currentInfo.id, 0, signal)
},
// 鎵瑰噯閫氳繃
subApprove() {
@@ -1368,7 +1385,7 @@
ratifyTell: this.reason
}).then(res => {
this.loadingApproveReason = false
- this.$message.success('鎿嶄綔鎴愬姛')
+ this.$message.success('宸查��鍥炴姤鍛婂鏍�')
this.refreshTable('page')
this.currentInfo = null;
this.reason = '';
diff --git a/tests/attachment-download.test.mjs b/tests/attachment-download.test.mjs
index 48928e2..3e856a8 100644
--- a/tests/attachment-download.test.mjs
+++ b/tests/attachment-download.test.mjs
@@ -149,7 +149,7 @@
.map(n => n.type === 'ImportDeclaration' ? { start: n.start, end: n.end, text: '' }
: { start: n.start, end: n.declaration.start, text: 'globalThis.component = ' })
for (const edit of edits.sort((a, b) => b.start - a.start)) source = source.slice(0, edit.start) + edit.text + source.slice(edit.end)
- const context = vm.createContext({ vueQr: {}, PrintDialog: {}, limsTable: {}, mapGetters: () => ({}), getFileList })
+ const context = vm.createContext({ vueQr: {}, PrintDialog: {}, ReportPreview: {}, limsTable: {}, mapGetters: () => ({}), getFileList })
vm.runInContext(source, context)
return { ...context.component.data(), ...context.component.methods }
}
diff --git a/vite.config.js b/vite.config.js
index 4f1b5a0..2611d74 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -13,6 +13,9 @@
// 榛樿鎯呭喌涓嬶紝vite 浼氬亣璁句綘鐨勫簲鐢ㄦ槸琚儴缃插湪涓�涓煙鍚嶇殑鏍硅矾寰勪笂
// 渚嬪 https://www.ruoyi.vip/銆傚鏋滃簲鐢ㄨ閮ㄧ讲鍦ㄤ竴涓瓙璺緞涓婏紝浣犲氨闇�瑕佺敤杩欎釜閫夐」鎸囧畾杩欎釜瀛愯矾寰勩�備緥濡傦紝濡傛灉浣犵殑搴旂敤琚儴缃插湪 https://www.ruoyi.vip/admin/锛屽垯璁剧疆 baseUrl 涓� /admin/銆�
base: VITE_APP_ENV === 'production' ? '/' : '/',
+ // 涓庝复鏃堕瑙堛�佹牎楠岃剼鏈娇鐢ㄧ殑榛樿 .vite/deps 缂撳瓨闅旂锛岄伩鍏嶈繍琛屼腑鐨�
+ // 鏈嶅姟缁х画寮曠敤宸茶鍙︿竴瀹炰緥鏇挎崲鐨勯鏋勫缓渚濊禆锛�504 Outdated Optimize Dep锛夈��
+ cacheDir: `node_modules/.vite/lims-${command}-${mode}`,
plugins: createVitePlugins(env, command === 'build'),
resolve: {
// https://cn.vitejs.dev/config/#resolve-alias
--
Gitblit v1.9.3