Files
zyt/PRESCRIPTION_DUPLICATE_REQUEST_FIX.md
T
2026-04-10 14:43:58 +08:00

322 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 处方组件重复请求修复
## 问题描述
在预约列表页面点击"开处方"或"查看病历"按钮时,会出现重复请求 `/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
<el-button
:loading="isOpening"
@click="handlePrescription(row)"
>
{{ prescriptionActionLabel(row) }}
</el-button>
```
但这需要将 `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. 考虑是否需要用户提示