feat(report): 新增PDF报告预览功能并重构报告下载流程
| | |
| | | "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", |
| | |
| | | } |
| | | |
| | | // å份æ¥åä¸è½½ |
| | | 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' |
| | | }) |
| | | } |
| | | |
| | |
| | | data: { id } |
| | | }) |
| | | } |
| | | // æ¥åéåå°æ£éªä»»å¡ |
| | | // æ¥åéåå°æ£éªä»»å¡ææ¥åå®¡æ ¸ |
| | | export function sendBackTask(query) { |
| | | return request({ |
| | | url: '/insReport/sendBackTask', |
| | |
| | | } |
| | | |
| | | // ä¸è½½æ¹ååçæçæç« æ¥å |
| | | export function downloadStampedReport(id, format) { |
| | | export function downloadStampedReport(id, format, signal) { |
| | | return request({ |
| | | url: "/processReport/downloadStampedReport", |
| | | method: "get", |
| | | params: { id, format }, |
| | | signal, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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="æ£å¨å è½½æ¥åâ¦" 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ï¼A4 纸é»è®¤çº¦ 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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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="æ£å¨å è½½æ¥åâ¦" 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ï¼å
¶ä»å
¥å£å¤ç¨å·²å è½½ç 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> |
| | |
| | | router.afterEach(() => { |
| | | NProgress.done() |
| | | }) |
| | | |
| | | router.onError(error => { |
| | | NProgress.done() |
| | | console.error('[router] 页é¢å 载失败ï¼', error) |
| | | ElMessage.error('页é¢å 载失败ï¼è¯·å·æ°é¡µé¢åéè¯') |
| | | }) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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') |
| | | } |
| | | |
| | | // ä¸å页沿ç¨å·²ææ¥åæä»¶å
¥å£ï¼é´æä¿¡æ¯åªåéç»æ¬ç³»ç»ã |
| | | 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ï¼PDF é
读å¨éè¦æç¡®ç MIME ç±»åã |
| | | return new Blob([blob], { type: format === 'pdf' ? 'application/pdf' |
| | | : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }) |
| | | } |
| | |
| | | } |
| | | }, |
| | | error => { |
| | | // é¢è§çªå£å
³éæåæ¢æ¥åä¼ä¸»å¨åæ¶è¯»åï¼ä¸æç¤ºä¸ºæ¥å£å¼å¸¸ã |
| | | if (axios.isCancel(error)) return Promise.reject(error) |
| | | console.log('err' + error) |
| | | let { message } = error |
| | | if (message == "Network Error") { |
| | |
| | | </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, |
| | |
| | | export default { |
| | | name: 'ReportResults', |
| | | components: { |
| | | limsTable |
| | | limsTable, |
| | | ReportPreview |
| | | }, |
| | | data() { |
| | | return { |
| | |
| | | dataType: "action", |
| | | fixed: "right", |
| | | label: "æä½", |
| | | width: "180px", |
| | | width: "230px", |
| | | operation: [ |
| | | { |
| | | name: "é¢è§", |
| | | type: "text", |
| | | clickFun: row => this.previewReport(row), |
| | | }, |
| | | { |
| | | name: "ä¸è½½", |
| | | type: "text", |
| | |
| | | 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' |
| | |
| | | }, |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | label: "æ£éªç¶æ", |
| | | prop: "insState", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | |
| | | 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" }, |
| | |
| | | } |
| | | }, |
| | | { |
| | | name: 'æ¥åä¸è½½', |
| | | name: 'æ¥åé¢è§/ä¸è½½', |
| | | type: 'text', |
| | | clickFun: (row) => { |
| | | this.download(row); |
| | |
| | | }, |
| | | }, |
| | | { |
| | | name: 'æ¥åä¸è½½', |
| | | name: 'æ¥åé¢è§/ä¸è½½', |
| | | type: 'text', |
| | | clickFun: (row) => { |
| | | this.download(row); |
| | |
| | | <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;">å£åº¦æ£éªæ¥åä¸è½½</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: 'å£åº¦æ£éªæ¥å', 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) |
| | | }]) |
| | | }, |
| | | // å£åº¦æ£éªæ¥åä¸è½½ |
| | | 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> |
| | |
| | | <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' |
| | |
| | | components: { |
| | | limsTable, |
| | | PrintDialog, |
| | | ReportPreview, |
| | | vueQr |
| | | }, |
| | | data() { |
| | |
| | | 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: 'æ¥åä¸è½½', |
| | |
| | | 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'; |
| | |
| | | :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'> |
| | |
| | | <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> |
| | |
| | | @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" |
| | |
| | | </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"> |
| | |
| | | <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"> |
| | |
| | | </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"> |
| | |
| | | </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> |
| | |
| | | </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, |
| | |
| | | |
| | | 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, |
| | |
| | | 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页左ä¸è§' }, |
| | |
| | | customsInspection: {}, |
| | | currentId: null, |
| | | examine: null, |
| | | viewIssuedVisible: false, |
| | | queryStatusList: [ |
| | | {label: 'å¾
æäº¤', value: 0}, |
| | | {label: 'å¾
å®¡æ ¸', value: 1}, |
| | | {label: 'å¾
æ¹å', value: 2}, |
| | | {label: 'å·²éå', value: 3}, |
| | | ], |
| | | state: 0, |
| | | orderId: 0, |
| | |
| | | return "ä¸éè¿"; |
| | | } else if (params == 1) { |
| | | return "éè¿"; |
| | | } else if (params == 2) { |
| | | return "å·²éå"; |
| | | } else { |
| | | return null |
| | | } |
| | |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "success"; |
| | | } else if (params == 2) { |
| | | return "warning"; |
| | | } else { |
| | | return undefined |
| | | } |
| | |
| | | return "䏿¹å"; |
| | | } else if (params == 1) { |
| | | return "æ¹å"; |
| | | } else if (params == 2) { |
| | | return "å·²éå"; |
| | | } else { |
| | | return null |
| | | } |
| | |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "success"; |
| | | } else if (params == 2) { |
| | | return "warning"; |
| | | } else { |
| | | return undefined |
| | | } |
| | |
| | | }, |
| | | mounted() { |
| | | this.refreshTable() |
| | | }, |
| | | activated() { |
| | | // 馿¬¡è¿å
¥ç± mounted å è½½ï¼ç¼å页é¢åæ¬¡æ¿æ´»æ¶å·æ°å表ã |
| | | if (this.hasActivated) { |
| | | this.refreshTable() |
| | | } |
| | | this.hasActivated = true |
| | | }, |
| | | methods: { |
| | | refreshTable() { |
| | |
| | | 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() { |
| | |
| | | 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() { |
| | |
| | | ratifyTell: this.reason |
| | | }).then(res => { |
| | | this.loadingApproveReason = false |
| | | this.$message.success('æä½æå') |
| | | this.$message.success('å·²é忥åå®¡æ ¸') |
| | | this.refreshTable('page') |
| | | this.currentInfo = null; |
| | | this.reason = ''; |
| | |
| | | .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 } |
| | | } |
| | |
| | | // é»è®¤æ
åµä¸ï¼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 |