# 处方组件重复请求修复 ## 问题描述 在预约列表页面点击"开处方"或"查看病历"按钮时,会出现重复请求 `/tcm.prescription/detail` 接口的问题,导致错误。 ## 问题原因 ### 1. 快速连续点击 用户可能快速连续点击按钮,导致 `open()` 或 `openById()` 方法被多次调用。 ### 2. 异步操作未加锁 `open()` 和 `openById()` 方法是异步的,但没有加锁机制,导致: - 第一次点击开始执行 - 第二次点击也开始执行 - 两次请求同时发送 ### 3. 代码流程 ```javascript const open = async (data: any) => { // 没有防重复检查 await loadDictOptions() if (appointmentId) { const existing = await prescriptionGetByAppointment(...) // 请求1 if (existing?.id) { const detail = await prescriptionDetail(...) // 请求2 } } } ``` ## 解决方案 ### 添加防重复打开标志 使用 `isOpening` 标志来防止重复调用: ```typescript // 防止重复打开 const isOpening = ref(false) const open = async (data: any, options?: { templateId?: number; stationName?: string }) => { // 防止重复打开 if (isOpening.value) { console.warn('处方正在打开中,请勿重复点击') return } isOpening.value = true try { // 原有逻辑... } finally { // 延迟重置,避免快速连续点击 setTimeout(() => { isOpening.value = false }, 500) } } ``` ### 关键点 1. **检查标志**:方法开始时检查 `isOpening.value`,如果为 true 则直接返回 2. **设置标志**:开始执行时设置 `isOpening.value = true` 3. **finally 重置**:无论成功还是失败,都在 finally 中重置标志 4. **延迟重置**:使用 `setTimeout` 延迟 500ms 重置,防止快速连续点击 ## 修改的文件 **文件**:`admin/src/components/tcm-prescription/index.vue` ### 1. 添加防重复标志 ```typescript // 防止重复打开 const isOpening = ref(false) ``` ### 2. 修改 open 方法 ```typescript const open = async (data: any, options?: { templateId?: number; stationName?: string }) => { // 防止重复打开 if (isOpening.value) { console.warn('处方正在打开中,请勿重复点击') return } isOpening.value = true try { // 原有逻辑... } finally { setTimeout(() => { isOpening.value = false }, 500) } } ``` ### 3. 修改 openById 方法 ```typescript const openById = async (prescriptionId: number) => { // 防止重复打开 if (isOpening.value) { console.warn('处方正在打开中,请勿重复点击') return } isOpening.value = true try { const res = await prescriptionDetail({ id: prescriptionId }) savedPrescription.value = res if (res?.case_record && Object.keys(res.case_record).length > 0) { await loadDictOptions() } visible.value = true } catch (e) { feedback.msgError('加载处方失败') } finally { setTimeout(() => { isOpening.value = false }, 500) } } ``` ## 工作流程 ### 修复前 ``` 用户点击"开处方"按钮 ↓ 调用 open() 方法 ↓ 用户再次点击(快速连续点击) ↓ 再次调用 open() 方法 ↓ 两次请求同时发送 ↓ ❌ 重复请求错误 ``` ### 修复后 ``` 用户点击"开处方"按钮 ↓ 调用 open() 方法 ↓ 设置 isOpening = true ↓ 用户再次点击(快速连续点击) ↓ 检查 isOpening = true ↓ ✓ 直接返回,不执行 ↓ 第一次请求完成 ↓ 500ms 后重置 isOpening = false ``` ## 测试场景 ### 场景1:快速连续点击 **操作**:快速连续点击"开处方"按钮 2-3 次 **预期结果**: - 只发送一次请求 - 控制台显示警告:"处方正在打开中,请勿重复点击" - 处方正常打开 --- ### 场景2:正常点击 **操作**:正常点击"开处方"按钮 **预期结果**: - 发送请求 - 处方正常打开 - 无警告信息 --- ### 场景3:间隔点击 **操作**:点击"开处方",等待 1 秒后再次点击 **预期结果**: - 第一次点击正常打开 - 第二次点击也正常打开(因为已过 500ms) --- ### 场景4:查看已有处方 **操作**:点击"查看病历"按钮 **预期结果**: - 调用 `prescriptionGetByAppointment` 查询 - 如果有处方,调用 `prescriptionDetail` 获取详情 - 只发送一次 detail 请求 --- ### 场景5:请求失败 **操作**:点击"开处方",但请求失败 **预期结果**: - 显示错误提示 - 500ms 后 `isOpening` 重置 - 可以再次点击 ## 其他优化建议 ### 1. 按钮禁用状态 可以在按钮上添加 loading 状态: ```vue {{ prescriptionActionLabel(row) }} ``` 但这需要将 `isOpening` 暴露给父组件,可能不太合适。 ### 2. 防抖处理 可以使用 lodash 的 debounce 函数: ```typescript import { debounce } from 'lodash-es' const debouncedOpen = debounce(open, 500, { leading: true, trailing: false }) ``` 但当前的解决方案已经足够简单有效。 ### 3. 请求取消 可以使用 AbortController 取消重复请求: ```typescript let abortController: AbortController | null = null const open = async (data: any) => { if (abortController) { abortController.abort() } abortController = new AbortController() // 在请求中使用 signal await fetch(url, { signal: abortController.signal }) } ``` 但这需要修改 API 调用层,改动较大。 ## 注意事项 ### 1. 延迟时间 当前设置为 500ms,可以根据实际情况调整: - 太短(< 300ms):可能无法有效防止快速连续点击 - 太长(> 1000ms):用户体验不好,需要等待较长时间 ### 2. 控制台警告 `console.warn` 只在开发环境有用,生产环境可以考虑: - 移除警告 - 或者改为用户提示:`feedback.msgWarning('请勿重复点击')` ### 3. 多个入口 如果有多个地方调用 `open()` 方法,都会受到这个锁的保护。 ## 相关文件 ### 修改的文件 - `admin/src/components/tcm-prescription/index.vue` - 处方组件 ### 调用的文件 - `admin/src/views/tcm/appointment/list.vue` - 预约列表 - 其他可能调用处方组件的页面 ## 总结 ✅ 添加 `isOpening` 标志防止重复打开 ✅ 使用 try-finally 确保标志正确重置 ✅ 延迟 500ms 重置,防止快速连续点击 ✅ 同时修复 `open()` 和 `openById()` 方法 ✅ 添加控制台警告提示 **效果**: - 防止重复请求 - 避免接口错误 - 提升用户体验 - 代码简单易维护 **下一步**: 1. 测试快速连续点击 2. 测试正常点击流程 3. 测试请求失败情况 4. 考虑是否需要用户提示