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

6.9 KiB
Raw Permalink Blame History

处方组件重复请求修复

问题描述

在预约列表页面点击"开处方"或"查看病历"按钮时,会出现重复请求 /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)
    }
}

关键点

  1. 检查标志:方法开始时检查 isOpening.value,如果为 true 则直接返回
  2. 设置标志:开始执行时设置 isOpening.value = true
  3. finally 重置:无论成功还是失败,都在 finally 中重置标志
  4. 延迟重置:使用 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() 方法 添加控制台警告提示

效果

  • 防止重复请求
  • 避免接口错误
  • 提升用户体验
  • 代码简单易维护

下一步

  1. 测试快速连续点击
  2. 测试正常点击流程
  3. 测试请求失败情况
  4. 考虑是否需要用户提示