| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898 |
- <template>
- <div v-loading="loading">
- <div class="btn-group">
- <div class="left">
- <div v-if="checkRecordList.length" class="left-content">
- <span class="title">检验记录列表:</span>
- <el-button
- :type="index === activeButtonIndex ? 'primary' : 'default'"
- v-for="(report, index) in checkRecordList"
- plain
- :key="report.id"
- @click="() => handleBtnClick(index, report.id)"
- >
- {{ report.label }}
- </el-button>
- </div>
- <div v-if="isTypeTestFlag" class="left-content">
- <span class="title">报告内容:</span>
- <el-button
- :type="activeContentButtonFlag == '1' ? 'primary' : 'default'"
- @click="handleContentBtnClick('1')"
- >
- 检验报告
- </el-button>
- <el-button
- :type="activeContentButtonFlag == '2' ? 'primary' : 'default'"
- @click="handleContentBtnClick('2')"
- >
- 型式试验报告
- </el-button>
- </div>
- </div>
- <div class="right">
- <el-button v-if="route.query.status === '0'" type="primary" @click="handleSubmitVerify">
- <el-icon><Check /></el-icon> 提交
- </el-button>
- <el-button v-if="route.query.status === '0'" type="warning" @click="handleRevertReport">
- 回退
- </el-button>
- <el-button type="success" @click="handleDownloadFn"><el-icon><Download /></el-icon> 下载报告内容</el-button>
- <el-button type="danger" @click="handleClose">退出</el-button>
- </div>
- </div>
- <el-row class="task-detail-layout" :gutter="16">
- <el-col :span="12" class="wrapperContainer">
- <ContentWrap title="报告内容" v-loading="reportSourceLoading">
- <el-scrollbar ref="reportDetailscrollbarRef" always>
- <div
- v-for="(report, index) in lazyRenderReportFileList"
- :key="report.key"
- class="file-viewer-container"
- >
- <!-- PDF预览 -->
- <VuePdfEmbed
- v-if="report.type === 'pdf'"
- class="reportPDFViewer"
- :height="wrapperContainerHeight"
- :width="pdfContentWidth"
- :source="report.url"
- :text-layer="false"
- :annotation-layer="false"
- @rendered="() => handleReportFileRendered(report.url, index, 'pdf')"
- @rendering-failed="() => handleFileRenderError(report.url, index, 'pdf')"
- />
- </div>
- </el-scrollbar>
- </ContentWrap>
- </el-col>
-
- <el-col :span="12" ref="col24HalfRef" class="wrapperContainer">
- <ContentWrap title="检验记录" v-loading="recordSourceLoading">
- <el-scrollbar
- ref="recordDetailscrollbarRef"
- always
- @scroll="handleRecordScroll"
- v-if="checkRecordList.length"
- >
- <div
- v-for="(record, index) in lazyRenderRecordFileList"
- :key="record.key"
- class="file-viewer-container"
- :data-record-id="record.reportId"
- :data-record-index="index"
- >
- <!-- PDF预览 -->
- <VuePdfEmbed
- v-if="record.type === 'pdf'"
- class="recordPDFViewer"
- :height="wrapperContainerHeight"
- :width="pdfContentWidth"
- :source="record.url"
- :text-layer="false"
- :annotation-layer="false"
- @rendered="() => handleRecordFileRendered(record.url, index, 'pdf')"
- @rendering-failed="() => handleFileRenderError(record.url, index, 'pdf')"
- />
-
- <!-- 图片预览 -->
- <div v-else-if="record.type === 'image'" class="image-viewer">
- <img
- :src="record.url"
- :style="{ maxWidth: pdfContentWidth + 'px', height: 'auto' }"
- @load="() => handleRecordFileRendered(record.url, index, 'image')"
- @error="() => handleFileRenderError(record.url, index, 'image')"
- alt="图片预览"
- />
- </div>
-
- <!-- Word/Excel等Office文档预览 -->
- <div v-else-if="['word', 'excel', 'ppt'].includes(record.type)" class="office-viewer">
- <div class="office-preview-container">
- <iframe
- v-if="record.previewUrl"
- :src="record.previewUrl"
- :width="pdfContentWidth"
- :height="wrapperContainerHeight"
- frameborder="0"
- @load="() => handleRecordFileRendered(record.url, index, record.type)"
- ></iframe>
- <div v-else class="office-fallback">
- <el-icon size="48"><Document /></el-icon>
- <p>{{ record.fileName || '文档预览' }}</p>
- <el-button type="primary" @click="downloadFile(record.url, record.fileName)">
- 下载查看
- </el-button>
- </div>
- </div>
- </div>
-
- <!-- 其他格式文件 -->
- <div v-else class="unsupported-file">
- <el-icon size="48"><Document /></el-icon>
- <p>不支持的文件格式: {{ record.type }}</p>
- <p>{{ record.fileName }}</p>
- <el-button type="primary" @click="downloadFile(record.url, record.fileName)">
- 下载文件
- </el-button>
- </div>
- </div>
- </el-scrollbar>
- <div v-else class="no-data">暂无检验记录</div>
- </ContentWrap>
- </el-col>
- </el-row>
- </div>
- <AuditUserDialog
- v-if="isShowApproveByDialog"
- v-model="isShowApproveByDialog"
- :apiFn="getAuditList"
- :apiParams="{ roleCode: 'sysbgpzr' }"
- title="请选择报告批准人"
- selectedAlertText="已选择报告批准人"
- :columns="auditDialogColumns"
- :deptIdDefaultFlag="true"
- :searchFormProps="labelWidthDefault"
- @confirm="handleApproveBySelectConfirm"
- />
- <RejectDialog
- v-if="rejectDialogVisible"
- v-model:modelValue="rejectDialogVisible"
- title="回退"
- :apiParams="rejectParams"
- :apiFn="returnReportRatify"
- reasonLabel="回退原因"
- reasonProp="ratifyRollbackReason"
- @success="handleClose"
- />
- </template>
- <script setup lang="ts">
- import { Document, Download, Check} from '@element-plus/icons-vue'
- const AuditUserDialog = defineAsyncComponent(
- () => import('@/views/Functional/components/AuditUserDialog.vue')
- )
- const RejectDialog = defineAsyncComponent(
- () => import('@/views/pressure/components/RejectDialog.vue')
- )
- import VuePdfEmbed from 'vue-pdf-embed'
- import 'vue-pdf-embed/dist/styles/annotationLayer.css'
- import 'vue-pdf-embed/dist/styles/textLayer.css'
- import { debounce, throttle } from 'lodash-es'
- import {
- getReportCheckRecordList,
- getReportCheckPdf,
- getCheckRecordPdf,
- getReportPDFPdf,
- submitReportCheck,
- submitReportRatify,
- returnReportRatify,
- reviewFallbackReportCheck
- } from '@/api/laboratory/functional/report'
- import { useRoute, useRouter } from 'vue-router'
- import { getAuditList } from '@/api/laboratory/functional'
- import { useTagsViewStore } from '@/store/modules/tagsView'
- import dayjs from 'dayjs'
- import { useEmitt } from '@/hooks/web/useEmitt'
- const { emitter } = useEmitt()
- const tagsViewStore = useTagsViewStore()
- const route = useRoute()
- const router = useRouter()
- const getTaskId = computed(() => route.query?.id || '')
- const getReportType = computed(() => route.query?.reportType || '')
- const loading = ref(true)
- const recordLoading = ref(false)
- const recordContentRef = ref()
- const contentWidth = ref(0)
- const getContentWidth = computed(() => contentWidth.value)
- const activeReportIndex = ref<number>(0)
- const activeRecordIndex = ref<number>(0)
- const btnClickRender = ref<number>(0)
- const reportIdMaps = ref(new Map())
- const recordSourceLoading = ref(false)
- const reportSourceLoading = ref(false)
- const lazyRenderRecordFileList = ref<any[]>([])
- const lazyRenderReportFileList = ref<any[]>([])
- const pdfViewerHeight = ref(0)
- const pdfContentWidth = ref(0)
- const activeRecordUrl = ref('')
- const activeReportUrl = ref('')
- const recordDetailscrollbarRef = ref()
- const reportDetailscrollbarRef = ref()
- const recordLastScrollTop = ref(0)
- const reportLastScrollTop = ref(0)
- const wrapperContainerHeight = ref(0)
- const recordFileUrlMaps = ref(new Map()) // 修复:用于存储检验记录文件的DOM映射
- const activeContentButtonFlag = ref('1')
- // 按钮高亮相关变量
- const activeButtonIndex = ref<number>(0)
- const isManualClick = ref(false)
- const isTypeTestFlag = computed(() => route.query.isTypeTest === '1')
- const checkRecordList = ref([
- {
- label: '测试的',
- value: 'https://yudao-admin.hofo.co/dexdev/b05b962cb88589e006c50d4f548f4f9a51597ae45b923a2677f31561d55d9205'
- }
- ])
- const labelWidthDefault = ref({labelWidth: 'auto'})
- const auditDialogColumns = computed(() => [
- {
- type: 'selection',
- fieldProps: {
- reserveSelection: true
- }
- },
- {
- label: '姓名',
- prop: 'nickName',
- search: {
- type: 'input',
- span: 12,
- placeholder: '请输入姓名'
- },
- render: (row) => {
- return row?.nickname || '-'
- }
- },
- {
- label: '部门',
- prop: 'deptId',
- search: {
- type: 'DeptSelect',
- span: 12,
- placeholder: '请选择部门'
- },
- render: (row) => {
- return row?.deptName || '-'
- }
- }
- ])
- // 文件类型检测函数
- const detectFileType = async (blob: Blob, fileName?: string): Promise<string> => {
- const fileExtension = fileName?.split('.').pop()?.toLowerCase()
- const mimeType = blob.type.toLowerCase()
- if (mimeType.includes('pdf')) return 'pdf'
- if (mimeType.includes('image')) return 'image'
- if (mimeType.includes('word') || mimeType.includes('msword') || mimeType.includes('openxmlformats-officedocument.wordprocessingml')) return 'word'
- if (mimeType.includes('excel') || mimeType.includes('spreadsheet') || mimeType.includes('openxmlformats-officedocument.spreadsheetml')) return 'excel'
- if (mimeType.includes('powerpoint') || mimeType.includes('presentation') || mimeType.includes('openxmlformats-officedocument.presentationml')) return 'ppt'
- if (fileExtension) {
- const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']
- const pdfExts = ['pdf']
- const wordExts = ['doc', 'docx']
- const excelExts = ['xls', 'xlsx', 'csv']
- const pptExts = ['ppt', 'pptx']
- if (imageExts.includes(fileExtension)) return 'image'
- if (pdfExts.includes(fileExtension)) return 'pdf'
- if (wordExts.includes(fileExtension)) return 'word'
- if (excelExts.includes(fileExtension)) return 'excel'
- if (pptExts.includes(fileExtension)) return 'ppt'
- }
- try {
- const arrayBuffer = await blob.slice(0, 20).arrayBuffer()
- const uint8Array = new Uint8Array(arrayBuffer)
- const header = Array.from(uint8Array).map(b => b.toString(16).padStart(2, '0')).join('')
- if (header.startsWith('25504446')) return 'pdf'
- if (header.startsWith('ffd8ff')) return 'image'
- if (header.startsWith('89504e47')) return 'image'
- if (header.startsWith('47494638')) return 'image'
- if (header.startsWith('424d')) return 'image'
- if (header.startsWith('504b0304')) {
- if (fileName?.includes('word') || fileName?.includes('.docx')) return 'word'
- if (fileName?.includes('excel') || fileName?.includes('.xlsx')) return 'excel'
- if (fileName?.includes('powerpoint') || fileName?.includes('.pptx')) return 'ppt'
- }
- } catch (error) {
- console.warn('文件类型检测失败:', error)
- }
- return 'unknown'
- }
- // 生成Office文档预览链接(如果需要)
- const generateOfficePreviewUrl = (fileUrl: string, fileType: string): string | null => {
- return null
- }
- // 下载文件
- const downloadFile = (url: string, fileName?: string) => {
- const link = document.createElement('a')
- link.href = url
- link.download = fileName || '文件'
- document.body.appendChild(link)
- link.click()
- document.body.removeChild(link)
- }
- // 获取检验项目填写记录
- const getCheckRecordList = async () => {
- const result = await getReportCheckRecordList({
- taskId: getTaskId.value
- })
- checkRecordList.value = result
- .filter((x) => x.itemUrl || x.attachmentUrl)
- .map((x) => ({ label: x.name, value: x.id, id: x.id }))
- loading.value = false
- }
- // 选择检验项目
- const checkedRecord = ref('')
- // 提交审批
- const isShowApproveByDialog = ref(false)
- const handleSubmitVerify = async () => {
- const result = await submitReportRatify({
- id: route.query.id
- })
- if (result) {
- ElMessage.success('提交成功')
- handleClose()
- }
- }
- const handleApproveBySelectConfirm = async (res) => {
- const result = await submitReportCheck({
- id: route.query.id,
- reportRatifyBy: res[0]
- })
- if (result) {
- ElMessage.success('提交成功')
- handleClose()
- }
- }
- // 回退报告
- const rejectDialogVisible = ref(false)
- const rejectParams = ref({})
- const handleRevertReport = () => {
- rejectDialogVisible.value = true
- rejectParams.value = {
- ids: [route.query.id]
- }
- }
- // 退出详情页
- const handleClose = () => {
- tagsViewStore.closeSelectedTag(route)
- emitter.emit('refresh-ReportPreparationIndex-list')
- router.push({
- name: 'ReportApproveList'
- })
- }
- // 修复:文件渲染成功处理 - 更新检验记录文件的DOM映射
- const handleRecordFileRendered = (url: string, index: number, fileType: string) => {
- console.log(`检验记录文件渲染完成: ${fileType}`)
- recordSourceLoading.value = false
-
- // 等待DOM更新后再获取元素
- nextTick(() => {
- const containerElements = document.querySelectorAll('.file-viewer-container[data-record-index]')
- containerElements.forEach((element, domIndex) => {
- const recordIndex = parseInt(element.getAttribute('data-record-index') || '0')
- const recordId = element.getAttribute('data-record-id')
- const fileItem = lazyRenderRecordFileList.value[recordIndex]
-
- if (fileItem) {
- recordFileUrlMaps.value.set(fileItem.url, {
- index: recordIndex,
- dom: element as HTMLElement,
- recordId: recordId
- })
- }
- })
- })
- }
- // 报告文件渲染成功处理
- const handleReportFileRendered = (url: string, index: number, fileType: string) => {
- console.log(`报告文件渲染完成: ${fileType}`)
- reportSourceLoading.value = false
- }
- // 文件渲染失败处理
- const handleFileRenderError = (url: string, index: number, fileType: string) => {
- console.error(`文件渲染失败: ${fileType}, URL: ${url}`)
- recordSourceLoading.value = false
- reportSourceLoading.value = false
- ElMessage.error(`${fileType}文件加载失败`)
- }
- // 处理文件数据
- const processFileData = async (blob: Blob, reportId: string, index: number, fileName?: string) => {
- const fileType = await detectFileType(blob, fileName)
- const fileUrl = URL.createObjectURL(blob)
-
- const fileData = {
- url: fileUrl,
- type: fileType,
- fileName: fileName,
- key: `${dayjs().valueOf()}-${index}`,
- index,
- reportId
- }
- // 如果是Office文档,尝试生成预览链接
- if (['word', 'excel', 'ppt'].includes(fileType)) {
- fileData.previewUrl = generateOfficePreviewUrl(fileUrl, fileType)
- }
- return fileData
- }
- // 下载报告内容功能(复用已加载的文件)
- const handleDownloadFn = async () => {
- try {
- // 检查是否已经有加载的报告文件
- const reportFile = lazyRenderReportFileList.value[0]
-
- if (reportFile && reportFile.url) {
- // 如果已经有加载的文件,直接下载
- const response = await fetch(reportFile.url)
- const blob = await response.blob()
-
- const link = document.createElement('a')
- link.href = URL.createObjectURL(blob)
- link.download = `报告内容_${getTaskId.value}_${dayjs().format('YYYY-MM-DD')}.pdf`
-
- document.body.appendChild(link)
- link.click()
- document.body.removeChild(link)
-
- URL.revokeObjectURL(link.href)
- ElMessage.success('下载成功')
- } else {
- // 如果还没有加载,重新获取
- loading.value = true
- const fileBlob = await getReportPDFPdf({ id: getTaskId.value })
- activeContentButtonFlag.value = '1'
- const url = URL.createObjectURL(fileBlob)
- const link = document.createElement('a')
- link.href = url
- link.download = `报告内容_${getTaskId.value}_${dayjs().format('YYYY-MM-DD')}.pdf`
-
- document.body.appendChild(link)
- link.click()
- document.body.removeChild(link)
-
- URL.revokeObjectURL(url)
- ElMessage.success('下载成功')
- }
- } catch (error) {
- console.error('下载报告失败:', error)
- ElMessage.error('下载失败,请重试')
- } finally {
- loading.value = false
- }
- }
- // 修复:按钮点击处理 - 点击按钮时滚动到对应文件
- const handleBtnClick = async (index: number, reportId: string) => {
- isManualClick.value = true
- activeButtonIndex.value = index
-
- // 查找对应的文件
- const targetFile = lazyRenderRecordFileList.value.find(file => file.reportId === reportId)
-
- if (targetFile) {
- // 如果文件已经加载,直接滚动到对应位置
- const fileMapping = recordFileUrlMaps.value.get(targetFile.url)
- if (fileMapping?.dom) {
- recordDetailscrollbarRef.value?.setScrollTop(fileMapping.dom.offsetTop)
- }
- } else {
- // 如果文件还没有加载,先加载文件
- try {
- recordSourceLoading.value = true
- const fileBlob = await getCheckRecordPdf({ itemId: reportId })
- const fileData = await processFileData(fileBlob, reportId, index)
- // 插入到正确的位置
- const recordUrllist = [...lazyRenderRecordFileList.value]
- recordUrllist.push(fileData)
- lazyRenderRecordFileList.value = recordUrllist.sort((a, b) => a.index - b.index)
- // 等待文件渲染完成后滚动
- await nextTick()
-
- // 设置一个延时等待DOM完全更新
- setTimeout(() => {
- const fileMapping = recordFileUrlMaps.value.get(fileData.url)
- if (fileMapping?.dom) {
- recordDetailscrollbarRef.value?.setScrollTop(fileMapping.dom.offsetTop)
- }
- }, 300)
-
- } catch (error) {
- console.error('动态加载文件失败:', error)
- recordSourceLoading.value = false
- ElMessage.error('加载文件失败')
- }
- }
- setTimeout(() => {
- isManualClick.value = false
- }, 500)
- }
- // 修复:处理检验记录滚动事件 - 根据滚动位置高亮对应按钮
- const handleRecordScroll = throttle((event) => {
- if (isManualClick.value) return
- const scrollTop = event.scrollTop
- const viewportHeight = recordDetailscrollbarRef.value?.$refs?.wrap?.clientHeight || 0
- const scrollCenter = scrollTop + viewportHeight / 2
-
- let targetButtonIndex = 0
- let minDistance = Infinity
- // 遍历所有文件容器,找到距离滚动中心最近的文件
- recordFileUrlMaps.value.forEach((mapping, url) => {
- const { dom, index } = mapping
- if (dom) {
- const elementTop = dom.offsetTop
- const elementBottom = elementTop + dom.offsetHeight
- const elementCenter = elementTop + dom.offsetHeight / 2
-
- // 计算元素中心与滚动中心的距离
- const distance = Math.abs(elementCenter - scrollCenter)
-
- // 如果元素在视口内或距离最近,更新目标按钮索引
- if (distance < minDistance || (scrollCenter >= elementTop && scrollCenter <= elementBottom)) {
- minDistance = distance
- targetButtonIndex = index
- }
- }
- })
- // 更新按钮高亮
- if (activeButtonIndex.value !== targetButtonIndex) {
- activeButtonIndex.value = targetButtonIndex
- }
- }, 100)
- // 获取PDF宽度
- const col24HalfRef = ref(null)
- const handleWindowResize = debounce(() => {
- pdfContentWidth.value = col24HalfRef.value?.$el.clientWidth - 28
- }, 100)
- const handleContentBtnClick = async (flag: string) => {
- if (activeContentButtonFlag.value === flag) return
- activeContentButtonFlag.value = flag
- reportSourceLoading.value = true
- if (flag === '2') {
- lazyRenderReportFileList.value = []
- // 并行获取报告内容
- const reportFilePromise = getReportPDFPdf({ id: getTaskId.value, type: '1' })
- .then(async (fileBlob) => {
- const fileData = await processFileData(fileBlob, getTaskId.value, 0, '报告内容')
- reportIdMaps.value.set('record-' + getTaskId.value, { index: 0, url: fileData.url })
- lazyRenderReportFileList.value.push(fileData)
- activeRecordUrl.value = fileData.url
- return fileData.url
- })
- .catch((error) => {
- console.error('获取报告文件失败:', error)
- reportSourceLoading.value = false
- throw error
- })
- await reportFilePromise
- }
- if (flag === '1') {
- lazyRenderReportFileList.value = []
- // 并行获取报告内容
- const reportFilePromise = getReportPDFPdf({ id: getTaskId.value })
- .then(async (fileBlob) => {
- const fileData = await processFileData(fileBlob, getTaskId.value, 0, '报告内容')
- reportIdMaps.value.set('record-' + getTaskId.value, { index: 0, url: fileData.url })
- lazyRenderReportFileList.value.push(fileData)
- activeRecordUrl.value = fileData.url
- return fileData.url
- })
- .catch((error) => {
- console.error('获取报告文件失败:', error)
- reportSourceLoading.value = false
- throw error
- })
- await reportFilePromise
- }
- }
- // 优化后的 watch 函数
- watch(
- () => [getTaskId.value],
- async ([taskId]: [string]) => {
- if (!taskId) return
- try {
- await getCheckRecordList()
-
- loading.value = true
- lazyRenderRecordFileList.value = []
- lazyRenderReportFileList.value = []
- reportIdMaps.value.clear()
- recordFileUrlMaps.value.clear() // 清理检验记录文件映射
- // 并行获取报告内容
- const reportFilePromise = getReportPDFPdf({ id: getTaskId.value })
- .then(async (fileBlob) => {
- const fileData = await processFileData(fileBlob, taskId, 0, '报告内容')
- reportIdMaps.value.set('record-' + taskId, { index: 0, url: fileData.url })
- lazyRenderReportFileList.value.push(fileData)
- activeRecordUrl.value = fileData.url
- return fileData.url
- })
- .catch((error) => {
- console.error('获取报告文件失败:', error)
- reportSourceLoading.value = false
- throw error
- })
- if (checkRecordList.value.length) recordSourceLoading.value = true
- // 并行获取检验记录文件
- const recordFilePromises = checkRecordList.value.map(async (report, index) => {
- try {
- const fileBlob = await getCheckRecordPdf({ itemId: report.value })
- const fileData = await processFileData(fileBlob, report.id, index, report.label)
-
- reportIdMaps.value.set('report-' + report.id, {
- index,
- url: fileData.url
- })
- return fileData
- } catch (error) {
- console.error(`获取检验记录文件失败 (${report.label}):`, error)
- return null
- }
- })
- // 等待所有检验记录文件加载完成
- const recordFileResults = await Promise.allSettled(recordFilePromises)
- // 过滤成功的结果并按索引排序
- const successfulRecords = recordFileResults
- .filter((result) => result.status === 'fulfilled' && result.value !== null)
- .map((result) => result.value)
- .sort((a, b) => a.index - b.index)
- // 设置检验记录文件列表
- lazyRenderRecordFileList.value = successfulRecords
- // 设置活动的记录URL和按钮高亮
- if (successfulRecords.length > 0) {
- activeReportUrl.value = successfulRecords[0].url
- activeButtonIndex.value = 0
- }
- // 等待右侧报告文件完成
- await reportFilePromise
- loading.value = false
- } catch (error) {
- console.error('初始化文件加载失败:', error)
- loading.value = false
- recordSourceLoading.value = false
- reportSourceLoading.value = false
- ElMessage.error('文件加载失败,请重试')
- }
- },
- {
- immediate: true
- }
- )
- onMounted(() => {
- wrapperContainerHeight.value = col24HalfRef.value?.$el.clientHeight - 42
- handleWindowResize()
- window.addEventListener('resize', handleWindowResize)
- })
- onUnmounted(() => {
- window.removeEventListener('resize', handleWindowResize)
- // 清理创建的URL对象
- lazyRenderRecordFileList.value.forEach(file => {
- if (file.url) URL.revokeObjectURL(file.url)
- })
- lazyRenderReportFileList.value.forEach(file => {
- if (file.url) URL.revokeObjectURL(file.url)
- })
- })
- </script>
- <style lang="scss" scoped>
- .task-detail-layout {
- display: flex;
- height: calc(100vh - 210px);
- margin: 0 !important;
- gap: 16px;
- flex-wrap: nowrap;
-
- .no-data {
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 16px;
- color: #999;
- background-color: #f5f5f5;
- }
-
- .el-col-12 {
- padding: 0 !important;
- max-width: 100% !important;
- overflow-y: auto;
- }
- .wrapperContainer {
- position: relative;
- overflow: hidden;
- }
- :deep(.v-content-wrap) {
- position: relative;
- width: 100%;
- height: 100%;
- background: #8e8e9d;
- .el-card__header {
- position: sticky;
- left: 0;
- top: 0;
- padding: 10px !important;
- background-color: #fff;
- }
- .el-card__body {
- position: relative;
- width: 100%;
- height: calc(100% - 62px);
- // margin: 10px;
- overflow-y: auto;
- }
- }
- }
- .btn-group {
- display: flex;
- justify-content: space-between;
- padding-bottom: 12px;
- max-width: 100%;
- overflow: hidden;
- // gap: 16px;
- .left {
- display: flex;
- flex-direction: column;
- gap: 10px;
- flex: 1;
- min-width: 0; // 重要:允许flex子项收缩
- padding-right: 20px;
- overflow: hidden;
- margin-top: 10px;
- cursor: pointer;
-
- > div {
- display: flex;
- align-items: center;
- gap: 8px;
- overflow-x: auto;
- overflow-y: hidden;
- padding-bottom: 4px; // 给滚动条留空间
-
- // 自定义滚动条样式(可选)
- &::-webkit-scrollbar {
- height: 6px;
- }
-
- &::-webkit-scrollbar-thumb {
- background-color: #dcdfe6;
- border-radius: 3px;
-
- &:hover {
- background-color: #c0c4cc;
- }
- }
-
- &::-webkit-scrollbar-track {
- background-color: #f5f7fa;
- border-radius: 3px;
- }
- .title {
- white-space: nowrap;
- flex-shrink: 0; // 标题不收缩
- font-weight: 500;
- margin-bottom: 0;
- }
- .el-button {
- flex-shrink: 0; // 按钮不收缩
- white-space: nowrap;
- }
- }
- .left-content {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- }
- .right {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-shrink: 0; // 右侧按钮组不收缩,始终可见
-
- .el-button {
- white-space: nowrap;
- }
- }
- }
- .file-viewer-container {
- margin-bottom: 20px;
- // border: 1px solid #e4e7ed;
- border-radius: 4px;
- overflow: hidden;
- background: #fff;
- }
- // 为PDF的每一页canvas添加间距
- .reportPDFViewer {
- :deep(canvas) {
- display: block !important;
- width: 100% !important;
- box-sizing: border-box !important;
- // position: relative;
- border-bottom: 20px solid #8e8e9d;
- overflow: hidden;
- // &::after {
- // position: absolute;
- // }
- &:last-child {
- margin-bottom: 0;
- }
- }
- }
- .image-viewer {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 20px;
- background: #f5f7fa;
-
- img {
- max-width: 100%;
- height: auto;
- border-radius: 4px;
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
- }
- }
- </style>
|