|
|
@@ -0,0 +1,360 @@
|
|
|
+<route lang="json5" type="page">
|
|
|
+{
|
|
|
+ layout: 'default',
|
|
|
+ style: {
|
|
|
+ navigationBarTitleText: '安全检查记录详情',
|
|
|
+ navigationStyle: 'custom',
|
|
|
+ },
|
|
|
+}
|
|
|
+</route>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <view class="detail-container">
|
|
|
+ <!-- 导航栏 -->
|
|
|
+ <view class="navigate-view">
|
|
|
+ <view class="navigate-left" @click="goBack">
|
|
|
+ <image class="back-icon" src="/static/images/back.png" />
|
|
|
+ </view>
|
|
|
+ <view class="navigate-center">
|
|
|
+ <HeadView v-if="renderCenter" title="操作指导书批准详情" :btn-array="btnArray" />
|
|
|
+ <text v-else class="navigate-title">安全检查记录详情</text>
|
|
|
+ </view>
|
|
|
+ <view class="navigate-right"></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- PDF 预览区域 -->
|
|
|
+ <view class="pdf-container">
|
|
|
+ <view v-if="loading" class="loading-container">
|
|
|
+ <text class="loading-text">加载中...</text>
|
|
|
+ </view>
|
|
|
+ <!-- #ifdef H5 -->
|
|
|
+ <view v-else-if="pdfUrl" id="pdf-viewer-container" class="pdf-viewer-container"></view>
|
|
|
+ <!-- #endif -->
|
|
|
+ <!-- #ifndef H5 -->
|
|
|
+ <web-view v-else-if="pdfUrl" :src="pdfUrl" />
|
|
|
+ <!-- #endif -->
|
|
|
+ <view v-else class="empty-container">
|
|
|
+ <text class="empty-text">暂无PDF</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import { ref, computed, onMounted, watch, nextTick } from 'vue'
|
|
|
+import { onLoad } from '@dcloudio/uni-app'
|
|
|
+import { getSafetyCheckRecordTemplate, getServiceOrderPDF } from '@/api/task'
|
|
|
+import { useUserStore } from '@/store/user'
|
|
|
+import { isH5 } from '@/utils/platform'
|
|
|
+import dayjs from 'dayjs'
|
|
|
+import HeadView from '../components/HeadView.vue'
|
|
|
+
|
|
|
+// 路由参数
|
|
|
+const detailItem = ref<any>({})
|
|
|
+const orderId = ref('')
|
|
|
+const unitContact = ref('')
|
|
|
+const unitPhone = ref('')
|
|
|
+const receiverEmail = ref('')
|
|
|
+const templateId = ref('')
|
|
|
+
|
|
|
+onLoad((options) => {
|
|
|
+ orderId.value = options.orderId || ''
|
|
|
+ unitContact.value = options.unitContact || ''
|
|
|
+ unitPhone.value = options.unitPhone || ''
|
|
|
+ receiverEmail.value = options.receiverEmail || ''
|
|
|
+ templateId.value = options.templateId || ''
|
|
|
+
|
|
|
+ if (options.detailItem) {
|
|
|
+ try {
|
|
|
+ detailItem.value = JSON.parse(decodeURIComponent(options.detailItem))
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析 detailItem 失败:', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 用户信息
|
|
|
+const userStore = useUserStore()
|
|
|
+
|
|
|
+// 状态
|
|
|
+const loading = ref(false)
|
|
|
+const pdfUrl = ref('')
|
|
|
+const templateParams = ref<any>({})
|
|
|
+
|
|
|
+// 是否显示操作按钮 (对应 PJ 中的 useMemo 计算)
|
|
|
+const renderCenter = computed(() => {
|
|
|
+ const { id } = detailItem.value || {}
|
|
|
+ if (!id) return false
|
|
|
+ return true
|
|
|
+})
|
|
|
+
|
|
|
+// 操作按钮数组 (与 PJ 版本一致,空数组)
|
|
|
+const btnArray = computed(() => {
|
|
|
+ const { signFilePdf, id } = detailItem.value || {}
|
|
|
+ if (!id) return []
|
|
|
+
|
|
|
+ return [
|
|
|
+ // { value: signFilePdf ? '重新签名' : '签名', color: 'rgb(47,142,255)', click: handleToSign },
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
+// Blob 转 Base64
|
|
|
+const blobToBase64 = (blob: Blob): Promise<string> => {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const reader = new FileReader()
|
|
|
+ reader.onloadend = () => {
|
|
|
+ const base64data = reader.result as string
|
|
|
+ resolve(base64data.split(',')[1])
|
|
|
+ }
|
|
|
+ reader.onerror = reject
|
|
|
+ reader.readAsDataURL(blob)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 获取模板
|
|
|
+const handleGetTemplate = async () => {
|
|
|
+ const { nickname } = userStore.userInfo || {}
|
|
|
+
|
|
|
+ const response = await getSafetyCheckRecordTemplate({ orderId: orderId.value })
|
|
|
+ const result = response?.data || {}
|
|
|
+ const dataStr = result.dataSource ? JSON.parse(result.dataSource) : {}
|
|
|
+
|
|
|
+ const temPar = {
|
|
|
+ ...result,
|
|
|
+ templateUrl: result.recordTemplateUrl,
|
|
|
+ dataSource: JSON.stringify({
|
|
|
+ ...dataStr,
|
|
|
+ signName: nickname,
|
|
|
+ signDate: dayjs().format('YYYY年MM月DD日'),
|
|
|
+ }),
|
|
|
+ }
|
|
|
+
|
|
|
+ templateParams.value = temPar
|
|
|
+ return temPar
|
|
|
+}
|
|
|
+
|
|
|
+// 获取安全检查记录 PDF
|
|
|
+const getAppServiceOrderPDF = async (tplParams: any) => {
|
|
|
+ const { signFilePdf, id } = detailItem.value || {}
|
|
|
+ const { dataSource, templateUrl } = tplParams || {}
|
|
|
+
|
|
|
+ if (signFilePdf) {
|
|
|
+ pdfUrl.value = buildFileUrl(signFilePdf)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ dataStr: dataSource,
|
|
|
+ templateUrl,
|
|
|
+ orderId: orderId.value,
|
|
|
+ securityCheckId: id,
|
|
|
+ }
|
|
|
+
|
|
|
+ const response: any = await getServiceOrderPDF(params)
|
|
|
+ const base64Data = await blobToBase64(response)
|
|
|
+ const filePath = `data:application/pdf;base64,${base64Data}`
|
|
|
+ if (filePath) {
|
|
|
+ pdfUrl.value = filePath
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 构建文件 URL
|
|
|
+const buildFileUrl = (fileId: string) => {
|
|
|
+ // const baseUrl = uni.getStorageSync('BASE_URL') || 'http://192.168.0.113:40080'
|
|
|
+ const baseUrl = 'https://youdao.hofo.co/dexdev/'
|
|
|
+ return `${baseUrl}${fileId}`
|
|
|
+}
|
|
|
+
|
|
|
+// 签名 (保留但不使用,与 PJ 版本一致)
|
|
|
+const handleToSign = async () => {
|
|
|
+ const { signFilePdf, id } = detailItem.value || {}
|
|
|
+ if (!id) return
|
|
|
+
|
|
|
+ const url = signFilePdf ? '/pages/sign-detail/index' : '/pages/sign/index'
|
|
|
+ uni.redirectTo({
|
|
|
+ url: `${url}?orderId=${orderId.value}&type=AQJC&securityCheckId=${id}&unitContact=${unitContact.value}&unitPhone=${unitPhone.value}&receiverEmail=${receiverEmail.value}&templateId=${templateId.value}`,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化
|
|
|
+const init = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const tplParams = await handleGetTemplate()
|
|
|
+ await getAppServiceOrderPDF(tplParams)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[Page] 初始化失败:', error)
|
|
|
+ uni.showToast({ title: 'PDF 加载失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 返回
|
|
|
+const goBack = () => {
|
|
|
+ uni.navigateBack()
|
|
|
+}
|
|
|
+
|
|
|
+// H5 端 PDF 渲染
|
|
|
+const initH5PdfViewer = async () => {
|
|
|
+ if (!pdfUrl.value) return
|
|
|
+
|
|
|
+ const container = document.getElementById('pdf-viewer-container')
|
|
|
+ if (!container) return
|
|
|
+
|
|
|
+ container.innerHTML = ''
|
|
|
+
|
|
|
+ const isBase64 = pdfUrl.value.startsWith('data:application/pdf;base64,')
|
|
|
+
|
|
|
+ if (!(window as any).pdfjsLib) {
|
|
|
+ const script = document.createElement('script')
|
|
|
+ script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js'
|
|
|
+ script.onerror = () => uni.showToast({ title: 'PDF.js 加载失败', icon: 'none' })
|
|
|
+ document.head.appendChild(script)
|
|
|
+ await new Promise<void>((resolve, reject) => {
|
|
|
+ script.onload = () => resolve()
|
|
|
+ script.onerror = reject
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const pdfjsLib = (window as any).pdfjsLib
|
|
|
+ pdfjsLib.GlobalWorkerOptions.workerSrc =
|
|
|
+ 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'
|
|
|
+
|
|
|
+ let loadingTask
|
|
|
+ if (isBase64) {
|
|
|
+ const base64Data = pdfUrl.value.replace(/^data:application\/pdf;base64,/, '')
|
|
|
+ const binaryString = atob(base64Data)
|
|
|
+ const bytes = new Uint8Array(binaryString.length)
|
|
|
+ for (let i = 0; i < binaryString.length; i++) {
|
|
|
+ bytes[i] = binaryString.charCodeAt(i)
|
|
|
+ }
|
|
|
+ loadingTask = pdfjsLib.getDocument({ data: bytes })
|
|
|
+ } else {
|
|
|
+ loadingTask = pdfjsLib.getDocument(pdfUrl.value)
|
|
|
+ }
|
|
|
+
|
|
|
+ const pdf = await loadingTask.promise
|
|
|
+ container.style.overflow = 'auto'
|
|
|
+
|
|
|
+ for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
|
|
+ const page = await pdf.getPage(pageNum)
|
|
|
+ const scale = 1.5
|
|
|
+ const viewport = page.getViewport({ scale })
|
|
|
+
|
|
|
+ const canvas = document.createElement('canvas')
|
|
|
+ canvas.id = `pdf-page-${pageNum}`
|
|
|
+ canvas.style.display = 'block'
|
|
|
+ canvas.style.marginBottom = '10px'
|
|
|
+
|
|
|
+ const context = canvas.getContext('2d')
|
|
|
+ canvas.height = viewport.height
|
|
|
+ canvas.width = viewport.width
|
|
|
+
|
|
|
+ container.appendChild(canvas)
|
|
|
+
|
|
|
+ await page.render({ canvasContext: context, viewport }).promise
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[PDF] Error rendering PDF:', error)
|
|
|
+ uni.showToast({ title: 'PDF 渲染失败', icon: 'none' })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await init()
|
|
|
+
|
|
|
+ if (isH5) {
|
|
|
+ await nextTick()
|
|
|
+ if (pdfUrl.value) {
|
|
|
+ setTimeout(() => initH5PdfViewer(), 100)
|
|
|
+ } else {
|
|
|
+ watch(pdfUrl, (val) => {
|
|
|
+ if (val) {
|
|
|
+ setTimeout(() => initH5PdfViewer(), 100)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.detail-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100vh;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.navigate-view {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ height: 44px;
|
|
|
+ background-color: #fff;
|
|
|
+ border-bottom: 1px solid #eee;
|
|
|
+ padding: 0 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.navigate-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 44px;
|
|
|
+}
|
|
|
+
|
|
|
+.back-icon {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.navigate-center {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.navigate-title {
|
|
|
+ font-size: 17px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.navigate-right {
|
|
|
+ width: 44px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.pdf-container {
|
|
|
+ flex: 1;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+/* #ifdef H5 */
|
|
|
+.pdf-viewer-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: #fff;
|
|
|
+ text-align: center;
|
|
|
+ padding: 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+/* #endif */
|
|
|
+
|
|
|
+.loading-container,
|
|
|
+.empty-container {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-text,
|
|
|
+.empty-text {
|
|
|
+ color: #999;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+</style>
|