6.9 KiB
6.9 KiB
处方组件重复请求修复
问题描述
在预约列表页面点击"开处方"或"查看病历"按钮时,会出现重复请求 /tcm.prescription/detail 接口的问题,导致错误。
问题原因
1. 快速连续点击
用户可能快速连续点击按钮,导致 open() 或 openById() 方法被多次调用。
2. 异步操作未加锁
open() 和 openById() 方法是异步的,但没有加锁机制,导致:
- 第一次点击开始执行
- 第二次点击也开始执行
- 两次请求同时发送
3. 代码流程
const open = async (data: any) => {
// 没有防重复检查
await loadDictOptions()
if (appointmentId) {
const existing = await prescriptionGetByAppointment(...) // 请求1
if (existing?.id) {
const detail = await prescriptionDetail(...) // 请求2
}
}
}
解决方案
添加防重复打开标志
使用 isOpening 标志来防止重复调用:
// 防止重复打开
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)
}
}
关键点
- 检查标志:方法开始时检查
isOpening.value,如果为 true 则直接返回 - 设置标志:开始执行时设置
isOpening.value = true - finally 重置:无论成功还是失败,都在 finally 中重置标志
- 延迟重置:使用
setTimeout延迟 500ms 重置,防止快速连续点击
修改的文件
文件:admin/src/components/tcm-prescription/index.vue
1. 添加防重复标志
// 防止重复打开
const isOpening = ref(false)
2. 修改 open 方法
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 方法
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 状态:
<el-button
:loading="isOpening"
@click="handlePrescription(row)"
>
{{ prescriptionActionLabel(row) }}
</el-button>
但这需要将 isOpening 暴露给父组件,可能不太合适。
2. 防抖处理
可以使用 lodash 的 debounce 函数:
import { debounce } from 'lodash-es'
const debouncedOpen = debounce(open, 500, { leading: true, trailing: false })
但当前的解决方案已经足够简单有效。
3. 请求取消
可以使用 AbortController 取消重复请求:
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() 方法
✅ 添加控制台警告提示
效果:
- 防止重复请求
- 避免接口错误
- 提升用户体验
- 代码简单易维护
下一步:
- 测试快速连续点击
- 测试正常点击流程
- 测试请求失败情况
- 考虑是否需要用户提示