From 3a0c40c068c78bd114f1334edae4579f73613c5c Mon Sep 17 00:00:00 2001 From: yy2205 <2238220225@qq.com> Date: Mon, 21 Jul 2025 09:35:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=BE=E4=BD=B3=E7=82=9C=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=89=80=E6=9C=89=E5=89=8D=E7=AB=AF=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- .env.dev | 6 +- .env.prod | 5 +- docs/examCache-usage.md | 299 ++++++++++++++++++++++++ index.html | 8 + package.json | 7 +- src/api/processImage/index.ts | 2 +- src/api/tblist/patientexamlist/index.ts | 4 +- src/plugins/unocss/index.ts | 2 +- src/store/modules/examCache.ts | 85 +++++++ src/utils/examCacheExample.ts | 108 +++++++++ src/utils/ukey.ts | 134 +++++++---- src/views/ECG/ECGForm.vue | 40 ++-- src/views/ECG/translate/index2.vue | 46 +++- src/views/system/mail/log/log.data.ts | 14 +- vite.config.ts | 23 +- 16 files changed, 701 insertions(+), 84 deletions(-) create mode 100644 docs/examCache-usage.md create mode 100644 src/store/modules/examCache.ts create mode 100644 src/utils/examCacheExample.ts diff --git a/.env b/.env index 9e68b819..26b5170b 100644 --- a/.env +++ b/.env @@ -2,7 +2,7 @@ VITE_APP_TITLE=区域云心电系统 # 项目本地运行端口号 -VITE_PORT=8080 +VITE_PORT=80 # open 运行 npm run dev 时自动打开浏览器 VITE_OPEN=true diff --git a/.env.dev b/.env.dev index b3b4ef9d..637d2329 100644 --- a/.env.dev +++ b/.env.dev @@ -4,8 +4,8 @@ NODE_ENV=production VITE_DEV=false # 请求路径 http://10.55.253.199:8073 -VITE_BASE_URL='http://111.57.76.10:8072' #111服务器 -#VITE_BASE_URL='/adminecg' #222服务器 +#VITE_BASE_URL='http://111.57.76.10:8072' #111服务器 +VITE_BASE_URL='/adminecg' #222服务器 # VITE_BASE_URL='/admin-api' # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 @@ -26,7 +26,7 @@ VITE_DROP_CONSOLE=false VITE_SOURCEMAP=true # 打包路径 -VITE_BASE_PATH=/ecg +VITE_BASE_PATH=/ecg #222服务器 # 输出路径 VITE_OUT_DIR=dist diff --git a/.env.prod b/.env.prod index 2537ed81..8bd73507 100644 --- a/.env.prod +++ b/.env.prod @@ -4,7 +4,8 @@ NODE_ENV=production VITE_DEV=false # 请求路径 -VITE_BASE_URL='http://114.55.171.231:48080' +#VITE_BASE_URL='http://114.55.171.231:48080' +VITE_BASE_URL='http://192.168.1.12:8072' # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 VITE_UPLOAD_TYPE=server @@ -12,7 +13,7 @@ VITE_UPLOAD_TYPE=server VITE_UPLOAD_URL='http://114.55.171.231:48080/admin-api/infra/file/upload' # 接口地址 -VITE_API_URL=/admin-api +VITE_API_URL=/adminecg/admin-api # 是否删除debugger VITE_DROP_DEBUGGER=false diff --git a/docs/examCache-usage.md b/docs/examCache-usage.md new file mode 100644 index 00000000..96477ad8 --- /dev/null +++ b/docs/examCache-usage.md @@ -0,0 +1,299 @@ +# examCache 缓存系统使用指南 + +## 概述 + +`examCache` 是一个基于 Pinia 的缓存系统,用于在页面间存储和获取 `examId`、`orgId` 等检查相关信息。该系统支持自动持久化到 localStorage,并具有缓存有效期管理功能。 + +## 特性 + +- ✅ 基于 Pinia 状态管理 +- ✅ 自动持久化到 localStorage +- ✅ 缓存有效期管理(24小时) +- ✅ 支持从 URL 参数自动更新缓存 +- ✅ TypeScript 类型支持 +- ✅ 优雅的降级处理 + +## 安装和配置 + +### 1. 确保已安装依赖 + +项目已经安装了必要的依赖: +- `pinia` +- `pinia-plugin-persistedstate` + +### 2. 缓存 Store 已配置 + +缓存 Store 位于 `src/store/modules/examCache.ts`,已在主 Store 中注册。 + +## 基本使用 + +### 1. 设置缓存 + +在页面跳转前设置缓存: + +```typescript +import { useExamCacheStore } from '@/store/modules/examCache' + +const examCacheStore = useExamCacheStore() + +// 设置缓存 +examCacheStore.setCache('EXAM001', 'ORG001', 'http://example.com/image.jpg') +``` + +### 2. 获取缓存 + +在目标页面获取缓存: + +```typescript +import { useExamCacheStore } from '@/store/modules/examCache' + +const examCacheStore = useExamCacheStore() + +// 获取有效的 examId 和 orgId +const examId = examCacheStore.getValidExamId +const orgId = examCacheStore.getValidOrgId +const imgUrl = examCacheStore.imgUrl + +// 检查缓存是否有效 +if (examCacheStore.isCacheValid) { + console.log('缓存有效,可以使用') +} else { + console.log('缓存已过期或不存在') +} +``` + +### 3. 从 URL 参数更新缓存 + +当页面通过 URL 参数传递数据时,可以自动更新缓存: + +```typescript +import { useExamCacheStore } from '@/store/modules/examCache' +import { useRoute } from 'vue-router' + +const route = useRoute() +const examCacheStore = useExamCacheStore() + +// 从路由参数更新缓存 +examCacheStore.updateFromRoute(route.query) +``` + +### 4. 清除缓存 + +```typescript +import { useExamCacheStore } from '@/store/modules/examCache' + +const examCacheStore = useExamCacheStore() + +// 清除缓存 +examCacheStore.clearCache() +``` + +## 实际应用场景 + +### 场景1: ECGForm 页面跳转到 translate 页面 + +**在 ECGForm.vue 中:** + +```typescript +// 在跳转前设置缓存 +const examCacheStore = useExamCacheStore() +examCacheStore.setCache(rowinfo.value.examId, rowinfo.value.orgId, imageUrl) + +// 跳转到 translate 页面 +router.push({ + path: '/translate', + query: { + img: imageUrl, + examId: rowinfo.value.examId, + orgId: rowinfo.value.orgId + } +}) +``` + +**在 translate/index.vue 中:** + +```typescript +import { onMounted } from 'vue' +import { useRoute } from 'vue-router' +import { useExamCacheStore } from '@/store/modules/examCache' + +const route = useRoute() +const examCacheStore = useExamCacheStore() + +onMounted(() => { + // 优先从 URL 参数获取,如果没有则从缓存获取 + const examId = (route.query.examId as string) || examCacheStore.getValidExamId + const orgId = (route.query.orgId as string) || examCacheStore.getValidOrgId + const imgUrl = (route.query.img as string) || examCacheStore.imgUrl + + // 如果从 URL 参数获取到数据,更新缓存 + if (route.query.examId || route.query.orgId || route.query.img) { + examCacheStore.updateFromRoute(route.query) + } +}) +``` + +### 场景2: ukey.ts 中使用缓存 + +**修改 ukey.ts 中的 uploadPdf 函数:** + +```typescript +import { useExamCacheStore } from '@/store/modules/examCache' + +const uploadPdf = async (byteArray: number[], filename: string = 'signed.pdf') => { + try { + const fileStream = byteArray.join(',') + + // 获取缓存 store + const examCacheStore = useExamCacheStore() + + // 优先从缓存获取 examId 和 orgId,如果没有则从 route.query 获取 + const examId = examCacheStore.getValidExamId || (route.query.examId as string) || '' + const orgId = examCacheStore.getValidOrgId || (route.query.orgId as string) || '' + + const formData = new FormData() + formData.append('examId', examId) + formData.append('orgId', orgId) + // ... 其他代码 + } catch (error) { + console.error('文件上传失败:', error) + throw error + } +} +``` + +## API 参考 + +### Store 状态 + +```typescript +interface ExamCacheState { + examId: string // 检查编号 + orgId: string // 机构编号 + imgUrl: string // 图片地址 + timestamp: number // 缓存时间戳 +} +``` + +### Getters + +- `isCacheValid`: 检查缓存是否有效(24小时内) +- `getValidExamId`: 获取有效的 examId +- `getValidOrgId`: 获取有效的 orgId + +### Actions + +- `setCache(examId: string, orgId: string, imgUrl?: string)`: 设置缓存 +- `clearCache()`: 清除缓存 +- `updateFromRoute(query: any)`: 从路由参数更新缓存 + +## 缓存策略 + +### 优先级顺序 + +1. **URL 参数** - 最高优先级 +2. **缓存数据** - 次优先级 +3. **空值** - 最低优先级 + +### 有效期管理 + +- 缓存有效期为 **24 小时** +- 过期后需要重新设置 +- 时间戳不持久化,每次页面刷新后重新计算 + +### 持久化配置 + +```typescript +persist: { + key: 'exam-cache', + storage: localStorage, + paths: ['examId', 'orgId', 'imgUrl'] // 只持久化这些字段 +} +``` + +## 最佳实践 + +### 1. 在页面跳转前设置缓存 + +```typescript +// 推荐:在跳转前设置缓存 +const examCacheStore = useExamCacheStore() +examCacheStore.setCache(examId, orgId, imgUrl) +router.push({ path: '/target', query: { examId, orgId, img: imgUrl } }) +``` + +### 2. 在目标页面优先使用 URL 参数 + +```typescript +// 推荐:优先使用 URL 参数,然后回退到缓存 +const examId = (route.query.examId as string) || examCacheStore.getValidExamId +const orgId = (route.query.orgId as string) || examCacheStore.getValidOrgId +``` + +### 3. 定期清理过期缓存 + +```typescript +// 在应用启动时检查缓存有效性 +const examCacheStore = useExamCacheStore() +if (!examCacheStore.isCacheValid) { + examCacheStore.clearCache() +} +``` + +### 4. 错误处理 + +```typescript +// 添加错误处理 +try { + const examId = examCacheStore.getValidExamId + if (!examId) { + throw new Error('未找到有效的检查编号') + } + // 使用 examId +} catch (error) { + console.error('获取缓存失败:', error) + // 处理错误 +} +``` + +## 注意事项 + +1. **缓存有效期**: 缓存有效期为 24 小时,过期后需要重新设置 +2. **类型安全**: 使用 TypeScript 确保类型安全 +3. **降级处理**: 始终提供降级方案,避免因缓存问题导致功能不可用 +4. **调试**: 使用浏览器开发者工具查看 localStorage 中的缓存数据 +5. **清理**: 在适当的时候清理过期缓存 + +## 故障排除 + +### 常见问题 + +1. **缓存不生效** + - 检查 Pinia 是否正确配置 + - 检查 localStorage 是否可用 + - 检查缓存是否已过期 + +2. **类型错误** + - 确保正确导入类型 + - 检查 TypeScript 配置 + +3. **缓存数据丢失** + - 检查 localStorage 存储限制 + - 检查浏览器隐私设置 + +### 调试方法 + +```typescript +// 在浏览器控制台中调试 +const examCacheStore = useExamCacheStore() +console.log('缓存状态:', { + examId: examCacheStore.examId, + orgId: examCacheStore.orgId, + imgUrl: examCacheStore.imgUrl, + timestamp: examCacheStore.timestamp, + isValid: examCacheStore.isCacheValid +}) + +// 查看 localStorage +console.log('localStorage:', localStorage.getItem('exam-cache')) +``` \ No newline at end of file diff --git a/index.html b/index.html index d240e7d6..27f81c74 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,14 @@ content="芋道管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!" /> %VITE_APP_TITLE% +
diff --git a/package.json b/package.json index a19c55c1..1f8c05d6 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "i": "pnpm install", "dev": "vite", + "local": "vite --mode local", "dev-server": "vite --mode dev", "ts:check": "vue-tsc --noEmit", "build:local": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build", @@ -52,7 +53,7 @@ "highlight.js": "^11.9.0", "html2canvas": "^1.4.1", "jsencrypt": "^3.3.2", - "jspdf": "^2.5.1", + "jspdf": "^2.5.2", "lodash-es": "^4.17.21", "min-dash": "^4.1.1", "mitt": "^3.0.1", @@ -68,6 +69,7 @@ "vue-clipboard3": "^2.0.0", "vue-dompurify-html": "^4.1.4", "vue-i18n": "9.10.2", + "vue-pdf-embed": "^2.1.2", "vue-qrcode": "^2.2.2", "vue-router": "^4.3.0", "vue-types": "^5.1.1", @@ -101,6 +103,7 @@ "bpmn-js": "8.9.0", "bpmn-js-properties-panel": "0.46.0", "consola": "^3.2.3", + "esbuild": "^0.25.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-define-config": "^2.1.0", @@ -134,7 +137,7 @@ "vite-plugin-progress": "^0.0.7", "vite-plugin-purge-icons": "^0.10.0", "vite-plugin-svg-icons": "^2.0.1", - "vite-plugin-top-level-await": "^1.3.1", + "vite-plugin-top-level-await": "^1.5.0", "vue-eslint-parser": "^9.3.2", "vue-tsc": "^1.8.27" }, diff --git a/src/api/processImage/index.ts b/src/api/processImage/index.ts index b96b8286..07cedcb7 100644 --- a/src/api/processImage/index.ts +++ b/src/api/processImage/index.ts @@ -5,7 +5,7 @@ const originImageAddress = 'https://zzxmc.gw12320.com/ecgimage/' export const processImageApi = { // const apiUrl = 'https://zzxmc.gw12320.com/processImage' apiUrl222 : 'https://pacs.gw12320.com/adminecg/admin-api/tblist/ecganalysisparas/rpc-processImage', //222服务器 - apiUrl111 : 'http://111.57.76.10:8072/admin-api/tblist/ecganalysisparas/rpc-processImage', //111服务器 + apiUrl111 : 'http://192.168.1.12:8072/admin-api/tblist/ecganalysisparas/rpc-processImage', //111服务器 // const apiUrl = 'https://localhost:48080/admin-api/tblist/ecganalysisparas/rpc-processImage' // const imageAddress = 'F://陕西省咸阳市礼泉县心电图FTP/ecgimage/' diff --git a/src/api/tblist/patientexamlist/index.ts b/src/api/tblist/patientexamlist/index.ts index d88f2b1e..22e8e12b 100644 --- a/src/api/tblist/patientexamlist/index.ts +++ b/src/api/tblist/patientexamlist/index.ts @@ -88,8 +88,8 @@ export const PatientexamlistApi = { }, // 超声审核 - examine: async (id: String, doctorname: string, doctorid: string) => { - return await request.get({ url: `/tblist/patientexamlist/examine?id=${id}&doctorname=${doctorname}&doctorid=${doctorid}` }) + examine: async (id: String, doctorname: string, doctorid: string, ecgId: string, doctorDiagResult: string) => { + return await request.get({ url: `/tblist/patientexamlist/examine?id=${id}&doctorname=${doctorname}&doctorid=${doctorid}&ecgid=${ecgId}&doctorDiagResult=${doctorDiagResult}` }) }, // dicom数据同步 dicomDataSync: async () => { diff --git a/src/plugins/unocss/index.ts b/src/plugins/unocss/index.ts index d366b5a2..390481d6 100644 --- a/src/plugins/unocss/index.ts +++ b/src/plugins/unocss/index.ts @@ -1 +1 @@ -import 'virtual:uno.css' +import 'uno.css' diff --git a/src/store/modules/examCache.ts b/src/store/modules/examCache.ts new file mode 100644 index 00000000..a7f48550 --- /dev/null +++ b/src/store/modules/examCache.ts @@ -0,0 +1,85 @@ +import { defineStore } from 'pinia' + +interface ExamCacheState { + examId: string + orgId: string + imgUrl: string + timestamp: number +} + +export const useExamCacheStore = defineStore('examCache', { + state: (): ExamCacheState => ({ + examId: '', + orgId: '', + imgUrl: '', + timestamp: 0 + }), + + getters: { + // 检查缓存是否有效(24小时内) + isCacheValid(state) { + const now = Date.now() + const cacheAge = now - state.timestamp + const maxAge = 24 * 60 * 60 * 1000 // 24小时 + return state.timestamp > 0 && cacheAge < maxAge + }, + + // 获取有效的examId + getValidExamId(state) { + const now = Date.now() + const cacheAge = now - state.timestamp + const maxAge = 24 * 60 * 60 * 1000 // 24小时 + const isValid = state.timestamp > 0 && cacheAge < maxAge + return state.examId && isValid ? state.examId : '' + }, + + // 获取有效的orgId + getValidOrgId(state) { + const now = Date.now() + const cacheAge = now - state.timestamp + const maxAge = 24 * 60 * 60 * 1000 // 24小时 + const isValid = state.timestamp > 0 && cacheAge < maxAge + return state.orgId && isValid ? state.orgId : '' + } + }, + + actions: { + // 设置缓存 + setCache(examId: string, orgId: string, imgUrl: string = '') { + this.examId = examId + this.orgId = orgId + this.imgUrl = imgUrl + this.timestamp = Date.now() + }, + + // 清除缓存 + clearCache() { + this.examId = '' + this.orgId = '' + this.imgUrl = '' + this.timestamp = 0 + }, + + // 从URL参数更新缓存 + updateFromRoute(query: any) { + if (query.examId) { + this.examId = query.examId + } + if (query.orgId) { + this.orgId = query.orgId + } + if (query.img) { + this.imgUrl = query.img + } + this.timestamp = Date.now() + } + }, + + // 持久化配置 + persist: { + key: 'exam-cache', + storage: localStorage, + // 只持久化examId和orgId,不持久化timestamp + paths: ['examId', 'orgId', 'imgUrl'] + } +}) \ No newline at end of file diff --git a/src/utils/examCacheExample.ts b/src/utils/examCacheExample.ts new file mode 100644 index 00000000..ad86d016 --- /dev/null +++ b/src/utils/examCacheExample.ts @@ -0,0 +1,108 @@ +/** + * examCache 缓存系统使用示例 + * + * 这个文件展示了如何在不同的页面中使用 examCache 来存储和获取 examId 和 orgId + */ + +import { useExamCacheStore } from '@/store/modules/examCache' + +/** + * 示例1: 在页面A中设置缓存 + */ +export const setExamCacheExample = () => { + const examCacheStore = useExamCacheStore() + + // 设置缓存 + examCacheStore.setCache('EXAM001', 'ORG001', 'http://example.com/image.jpg') + + console.log('缓存已设置:', { + examId: examCacheStore.examId, + orgId: examCacheStore.orgId, + imgUrl: examCacheStore.imgUrl, + timestamp: examCacheStore.timestamp + }) +} + +/** + * 示例2: 在页面B中获取缓存 + */ +export const getExamCacheExample = () => { + const examCacheStore = useExamCacheStore() + + // 获取有效的examId和orgId + const examId = examCacheStore.getValidExamId + const orgId = examCacheStore.getValidOrgId + const imgUrl = examCacheStore.imgUrl + + console.log('从缓存获取:', { examId, orgId, imgUrl }) + + // 检查缓存是否有效 + if (examCacheStore.isCacheValid) { + console.log('缓存有效,可以使用') + return { examId, orgId, imgUrl } + } else { + console.log('缓存已过期或不存在') + return null + } +} + +/** + * 示例3: 从URL参数更新缓存 + */ +export const updateFromRouteExample = (query: any) => { + const examCacheStore = useExamCacheStore() + + // 从路由参数更新缓存 + examCacheStore.updateFromRoute(query) + + console.log('缓存已从路由参数更新') +} + +/** + * 示例4: 清除缓存 + */ +export const clearCacheExample = () => { + const examCacheStore = useExamCacheStore() + + // 清除缓存 + examCacheStore.clearCache() + + console.log('缓存已清除') +} + +/** + * 示例5: 在Vue组件中使用 + */ +export const useExamCacheInComponent = () => { + const examCacheStore = useExamCacheStore() + + return { + // 获取缓存数据 + examId: examCacheStore.getValidExamId, + orgId: examCacheStore.getValidOrgId, + imgUrl: examCacheStore.imgUrl, + isCacheValid: examCacheStore.isCacheValid, + + // 缓存操作方法 + setCache: examCacheStore.setCache, + clearCache: examCacheStore.clearCache, + updateFromRoute: examCacheStore.updateFromRoute + } +} + +/** + * 使用说明: + * + * 1. 在页面跳转前设置缓存: + * const examCacheStore = useExamCacheStore() + * examCacheStore.setCache(examId, orgId, imgUrl) + * + * 2. 在目标页面获取缓存: + * const examCacheStore = useExamCacheStore() + * const examId = examCacheStore.getValidExamId + * const orgId = examCacheStore.getValidOrgId + * + * 3. 缓存会自动持久化到localStorage,页面刷新后仍然可用 + * 4. 缓存有效期为24小时,过期后需要重新设置 + * 5. 如果URL参数中有examId/orgId,会优先使用URL参数,并自动更新缓存 + */ \ No newline at end of file diff --git a/src/utils/ukey.ts b/src/utils/ukey.ts index a60d074f..302f9ff4 100644 --- a/src/utils/ukey.ts +++ b/src/utils/ukey.ts @@ -1,5 +1,7 @@ import axios from 'axios' - +import request from "@/config/axios"; +import { useExamCacheStore } from '@/store/modules/examCache' +const route = useRoute() // 类型声明 declare global { interface Window { @@ -10,45 +12,81 @@ declare global { // 动态加载 SDK export const loadUKeySdk = () => { return new Promise((resolve, reject) => { + // 输出调试信息 + // console.log('=== SDK 加载调试信息 ===') + // console.log('当前页面URL:', window.location.href) + // console.log('当前域名:', window.location.origin) + // console.log('当前路径:', window.location.pathname) + // console.log('当前协议:', window.location.protocol) + // console.log('当前主机:', window.location.host) + // console.log('当前端口:', window.location.port) + // console.log('完整域名:', window.location.hostname) + // console.log('========================') + // 如果已经加载过,直接返回 if (window.QysUKeySdk) { resolve() return } - const script = document.createElement('script') - script.src = '/sdk.min.v1.0.0.js' - script.type = 'text/javascript' - - let retryCount = 0 - const maxRetries = 10 - const checkInterval = 100 // 100ms 检查一次 - - const checkSDK = () => { - if (window.QysUKeySdk) { - console.log('SDK 加载成功') - resolve() - } else if (retryCount < maxRetries) { - retryCount++ - console.log(`等待 SDK 初始化... (${retryCount}/${maxRetries})`) - setTimeout(checkSDK, checkInterval) - } else { - reject(new Error('SDK 加载失败:QysUKeySdk 未定义,请检查 SDK 文件是否正确')) + // 尝试多个可能的路径 + const possiblePaths = [ + '/sdk.min.v1.0.0.js', + './sdk.min.v1.0.0.js', + `${window.location.origin}/sdk.min.v1.0.0.js`, + '/ecg/sdk.min.v1.0.0.js', + './ecg/sdk.min.v1.0.0.js' + ] + + let currentPathIndex = 0 + + const tryLoadScript = () => { + if (currentPathIndex >= possiblePaths.length) { + reject(new Error('所有SDK路径都尝试失败,请检查SDK文件是否存在')) + return } + + const script = document.createElement('script') + script.src = possiblePaths[currentPathIndex] + script.type = 'text/javascript' + + let retryCount = 0 + const maxRetries = 10 + const checkInterval = 100 // 100ms 检查一次 + + const checkSDK = () => { + if (window.QysUKeySdk) { + console.log('SDK 加载成功,路径:', possiblePaths[currentPathIndex]) + resolve() + } else if (retryCount < maxRetries) { + retryCount++ + console.log(`等待 SDK 初始化... (${retryCount}/${maxRetries})`) + setTimeout(checkSDK, checkInterval) + } else { + // 当前路径失败,尝试下一个路径 + console.log(`路径 ${possiblePaths[currentPathIndex]} 失败,尝试下一个路径`) + currentPathIndex++ + tryLoadScript() + } + } + + script.onload = () => { + console.log('SDK 脚本加载完成,路径:', possiblePaths[currentPathIndex]) + // 脚本加载完成后,给一点时间让 SDK 初始化 + setTimeout(checkSDK, 50) + } + + script.onerror = (error) => { + console.error('SDK 脚本加载失败,路径:', possiblePaths[currentPathIndex], error) + // 当前路径失败,尝试下一个路径 + currentPathIndex++ + tryLoadScript() + } + + document.head.appendChild(script) } - - script.onload = () => { - console.log('SDK 脚本加载完成,开始检查初始化状态') - // 脚本加载完成后,给一点时间让 SDK 初始化 - setTimeout(checkSDK, 50) - } - - script.onerror = (error) => { - console.error('SDK 脚本加载失败:', error) - reject(new Error('SDK 脚本加载失败:' + error)) - } - - document.head.appendChild(script) + + tryLoadScript() }) } @@ -65,26 +103,39 @@ const downloadPdf = (data: any, filename: string = 'signed.pdf') => { URL.revokeObjectURL(url) } -const uploadPdf = async (byteArray: number[], filename: string = 'signed.pdf') => { +const uploadPdf = async (byteArray: number[], filename) => { // return try { // 将字节数组转换为逗号分隔的字符串 const fileStream = byteArray.join(','); + // 获取缓存store + const examCacheStore = useExamCacheStore() + + // 优先从缓存获取examId和orgId,如果没有则从route.query获取 + const examId = examCacheStore.getValidExamId || (route.query.examId as string) || '' + const orgId = examCacheStore.getValidOrgId || (route.query.orgId as string) || '' + // console.log(examId,orgId) const formData = new FormData(); + formData.append('examId', examId); + formData.append('orgId', orgId); formData.append('fileStream', fileStream); - formData.append('filename', filename); + formData.append('filename', filename??`examId=${examId}&orgId=${orgId}`); formData.append('fileType', 'pdf'); - formData.append('savePath', 'F:/陕西省咸阳市礼泉县心电图FTP/ecgimage/imgOcrAndProcess/'); + formData.append('savePath', 'F:/陕西省咸阳市礼泉县心电图FTP/ecgimage/signature/'); - const response = await fetch('https://zzxmc.gw12320.com/uploadFile', { + // const url = "http://localhost:58080/uploadFile" + // const url = 'http://zzxmc.gw12320.com/uploadFile' + /*const response = await fetch(url, { method: 'POST', body: formData }); +*/ + const response = await request.post({url:`/tblist/ecganalysisparas/rpc-uploadPdf`,data:formData}) - const result = await response.json(); - console.log('上传结果:', result); - return result; + // const result = await response.json(); + console.log('上传结果:', response); + return response; } catch (error) { console.error('文件上传失败:', error); throw error; @@ -97,13 +148,16 @@ export const getUKeySdk = () => { const sdk = new window.QysUKeySdk({ signCb: async function(file) { try { - await uploadPdf(file[0].rawFileData) + console.log(file) + await uploadPdf(file[0].rawFileData,null) + console.log('签署完成,文件已上传到服务器', file); } catch (error) { console.error('文件处理失败:', error) } } }) + console.log(route) return sdk } catch (error) { console.error('创建 SDK 实例失败:', error) diff --git a/src/views/ECG/ECGForm.vue b/src/views/ECG/ECGForm.vue index 508fe7ce..8e12f797 100644 --- a/src/views/ECG/ECGForm.vue +++ b/src/views/ECG/ECGForm.vue @@ -602,6 +602,8 @@ import { processImageApi } from '@/api/processImage' import {encodeBase64} from "@/utils/base64" import Dialog from "@/components/Dialog/src/Dialog.vue"; import { log } from 'console' +import { useExamCacheStore } from '@/store/modules/examCache' +import { getConfig, getConfigKey } from '@/api/infra/config' const { toClipboard } = useClipboard() const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -737,36 +739,48 @@ const open = async (row: any) => { } //审核功能 async function process() { + /* console.log(queryParams.value) + console.log(rowinfo.value) + return */ + uKeyable.value=false if (applyFormVO.value.reportstatus === '已分析') { + // 审核确认 await message.delConfirm('是否进行审核操作', '审核') // uKeyable.value=true - const response = await PatientexamlistApi.examine(keyid.value,Profilevo.value.doctorname,Profilevo.value.doctorID) + const response = await PatientexamlistApi.examine(keyid.value,Profilevo.value.doctorname,Profilevo.value.doctorID,queryParams.value.id,queryParams.value.doctorDiagResult) if (response) { + console.log(response) message.alertSuccess('审核成功') - let data = Object.assign({},processImageApi.paramsList[rowinfo.value.orgName]) + /* let data = Object.assign({},processImageApi.paramsList[rowinfo.value.orgName]) data.imagePath = queryParams.value.ecgJsonDataFilePath data.examId = rowinfo.value.examId data.orgId = rowinfo.value.orgId data.watermarkText = data.step + queryParams.value.doctorDiagResult - // console.log(data) data.imagePath = processImageApi.urlToAddress(data.imagePath) - // const processResponse = await processImageApi.processImg(data,processImageApi.apiUrl111) - const processResponse = await processImageApi.processImg(data,processImageApi.apiUrl222) + const processResponse = await processImageApi.processImg(data,processImageApi.apiUrl111) // 移动项目 + // const processResponse = await processImageApi.processImg(data,processImageApi.apiUrl222) // 礼泉县项目 // 1. 解析外层 JSON 的 data 字符串 const dataObj = JSON.parse(processResponse.data.data); // 2. 直接获取 updateSuccess 的值 - const updateSuccess = dataObj.updateSuccess; - if (!updateSuccess){ - message.warning('诊断图片生成异常') - }else{ - const imageUrl = processImageApi.addressToUrl(dataObj.imagePath) - // 使用 window.open 打开新标签页 - window.open(`/translate?img=${encodeURIComponent(imageUrl)}`, '_blank') - } + const updateSuccess = dataObj.updateSuccess; */ + + // const imageUrl = processImageApi.addressToUrl(dataObj.imagePath) + // console.log(imageUrl) + // 更新缓存 + const examCacheStore = useExamCacheStore() + examCacheStore.setCache(rowinfo.value.examId, rowinfo.value.orgId, response) + + // 使用 window.open 打开新标签页 仅222礼泉县需要开启 + // window.open(`/translate?img=${encodeURIComponent(response)}`, '_blank') + const config = await getConfigKey("pdf.sign.key") + if(config == "1"){ + router.push({path:'/translate',query:{img:response}}) + } + await getPatientexamlist(keyid.value) emit('success') diff --git a/src/views/ECG/translate/index2.vue b/src/views/ECG/translate/index2.vue index 9d2264e0..1a86d162 100644 --- a/src/views/ECG/translate/index2.vue +++ b/src/views/ECG/translate/index2.vue @@ -37,7 +37,7 @@ import { ref, onMounted, nextTick } from 'vue' import { loadUKeySdk, getUKeySdk } from '@/utils/ukey' import { ElLoading, ElMessage } from 'element-plus' import axios from 'axios' -import { log } from 'console' +import request from '@/config/axios' const ukeySdk = ref(null) const container = ref(null) @@ -49,12 +49,33 @@ import { log } from 'console' loading.value = true const pdfParam = route.query.img console.log('PDF参数:', pdfParam) + try { - await loadUKeySdk() - ukeySdk.value = getUKeySdk() - if (!ukeySdk.value) { - throw new Error('SDK 初始化失败') + // 尝试加载 SDK,最多重试 3 次 + let sdkLoaded = false + for (let i = 0; i < 3; i++) { + try { + console.log(`尝试加载 SDK (第 ${i + 1} 次)`) + await loadUKeySdk() + ukeySdk.value = getUKeySdk() + if (ukeySdk.value) { + sdkLoaded = true + break + } + } catch (error) { + console.error(`第 ${i + 1} 次加载失败:`, error) + if (i === 2) { + throw error + } + // 等待 1 秒后重试 + await new Promise(resolve => setTimeout(resolve, 1000)) + } } + + if (!sdkLoaded) { + throw new Error('SDK 初始化失败,请刷新页面重试') + } + ElMessage.success('SDK 加载成功') // 如果有URL参数,使用URL参数;否则使用默认URL @@ -73,7 +94,7 @@ import { log } from 'console' }) } catch (error: any) { console.error('SDK加载失败:', error) - ElMessage.error(error.message || 'SDK 加载失败') + ElMessage.error(error.message || 'SDK 加载失败,请刷新页面重试') } finally { loading.value = false } @@ -131,9 +152,17 @@ import { log } from 'console' console.log('开始设置loading状态') loading.value = true try { - console.log('开始获取PDF文件:', pdf) + // 转换PDF URL,使用代理路径 + let pdfUrl = pdf + if (pdf.includes('zzxmc.gw12320.com')) { + // 将外部域名替换为代理路径 + pdfUrl = pdf.replace('https://zzxmc.gw12320.com', '') + console.log('转换后的PDF URL:', pdfUrl) + } + + console.log('开始获取PDF文件:', pdfUrl) // 获取 PDF 文件 - const response = await axios.get(pdf, { + const response = await axios.get(pdfUrl, { responseType: 'blob', timeout: 30000, // 设置超时时间 headers: { @@ -141,7 +170,6 @@ import { log } from 'console' 'Pragma': 'no-cache' } }) - console.log('PDF文件获取成功,响应状态:', response.status) console.log('开始创建File对象') // 创建 File 对象 diff --git a/src/views/system/mail/log/log.data.ts b/src/views/system/mail/log/log.data.ts index 62cbf516..1bdeae01 100644 --- a/src/views/system/mail/log/log.data.ts +++ b/src/views/system/mail/log/log.data.ts @@ -3,7 +3,12 @@ import { dateFormatter } from '@/utils/formatTime' import * as MailAccountApi from '@/api/system/mail/account' // 邮箱账号的列表 -const accountList = await MailAccountApi.getSimpleMailAccountList() +let accountList: any[] = [] + +// 初始化账号列表 +const initAccountList = async () => { + accountList = await MailAccountApi.getSimpleMailAccountList() +} // CrudSchema:https://doc.iocoder.cn/vue3/crud-schema/ const crudSchemas = reactive([ @@ -74,7 +79,12 @@ const crudSchemas = reactive([ search: { show: true, component: 'Select', - api: () => accountList, + api: async () => { + if (accountList.length === 0) { + await initAccountList() + } + return accountList + }, componentProps: { optionsAlias: { labelField: 'mail', diff --git a/vite.config.ts b/vite.config.ts index 8cba9150..8ba30f1c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -29,14 +29,21 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { host: "0.0.0.0", open: env.VITE_OPEN === 'true', // 本地跨域代理. 目前注释的原因:暂时没有用途,server 端已经支持跨域 - // proxy: { - // ['/admin-api']: { - // target: env.VITE_BASE_URL, - // ws: false, - // changeOrigin: true, - // rewrite: (path) => path.replace(new RegExp(`^/admin-api`), ''), - // }, - // }, + proxy: { + ['/admin-api']: { + target: env.VITE_BASE_URL, + ws: false, + changeOrigin: true, + rewrite: (path) => path.replace(new RegExp(`^/admin-api`), ''), + }, + // 添加PDF文件代理 + ['/ecgimage']: { + target: 'https://zzxmc.gw12320.com', + ws: false, + changeOrigin: true, + secure: false, // 如果是https可能需要设置为false + }, + }, }, // 项目使用的vite插件。 单独提取到build/vite/plugin中管理 plugins: createVitePlugins(),