From 8dff4f0f78ac6d5a47d7866c88ce2f33a9d7c3e0 Mon Sep 17 00:00:00 2001 From: ShengLong <452591453@qq.com> Date: Thu, 30 Apr 2026 15:36:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E8=AF=8A=E5=8F=B0=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/views/patient/reception/index.vue | 669 +++++++++++++++----- admin/src/views/tcm/diagnosis/edit.vue | 4 +- 2 files changed, 524 insertions(+), 149 deletions(-) diff --git a/admin/src/views/patient/reception/index.vue b/admin/src/views/patient/reception/index.vue index 3f70c49d..aab5a29a 100644 --- a/admin/src/views/patient/reception/index.vue +++ b/admin/src/views/patient/reception/index.vue @@ -154,13 +154,11 @@
- - - + + - - - + +
@@ -169,9 +167,9 @@
主诉
- + - +
@@ -217,16 +215,6 @@ - -
-
中医四诊与辨证
-
- - - -
-
-
病史补充
@@ -250,7 +238,7 @@
@@ -271,16 +259,36 @@ - + - + - + - + @@ -303,18 +311,19 @@
备注 - + 编辑病历 { + const empty = props.value === undefined || props.value === null || props.value === '' + return h( + 'div', + { + class: ['kv-item', 'metric-kv-item', empty ? 'empty' : ''], + style: props.span ? `grid-column: span ${props.span}` : '' + }, + [ + h('span', { class: 'kv-label' }, props.label), + h( + 'span', + { class: ['kv-value', 'metric-kv-value', props.high ? 'high' : ''] }, + empty + ? '—' + : [ + `${props.value}${props.unit || ''}`, + props.high ? h('span', { class: 'metric-up' }, '↑') : null + ] + ) + ] + ) +} + const POLL_MS = 5_000 const PAGE_SIZE = 15 @@ -422,6 +461,8 @@ let countdownTimer: ReturnType | null = null let queueInflight = false let detailInflight = false let scrollObserver: IntersectionObserver | null = null +let queueRequestSeq = 0 +let detailRequestSeq = 0 const queueScrollRef = ref(null) const sentinelRef = ref(null) @@ -499,19 +540,67 @@ const formatBp = (row: any) => { return `${sys ?? '—'} / ${dia ?? '—'}` } +const toNumeric = (value: any) => { + if (value === null || value === undefined || value === '') return null + const num = Number(value) + return Number.isFinite(num) ? num : null +} + +const getBloodSugarThresholds = (ageValue: any) => { + const age = toNumeric(ageValue) + if (age === null) return null + if (age < 50) return { fasting: 7, postprandial: 9 } + if (age >= 50) return { fasting: 8, postprandial: 10 } + return null +} + +const isHighFastingBloodSugar = (value: any, ageValue: any) => { + const num = toNumeric(value) + const thresholds = getBloodSugarThresholds(ageValue) + return num !== null && thresholds !== null && num >= thresholds.fasting +} + +const isHighPostprandialBloodSugar = (value: any, ageValue: any) => { + const num = toNumeric(value) + const thresholds = getBloodSugarThresholds(ageValue) + return num !== null && thresholds !== null && num >= thresholds.postprandial +} + +const isHighOtherBloodSugar = (value: any, ageValue: any) => { + const num = toNumeric(value) + const thresholds = getBloodSugarThresholds(ageValue) + return num !== null && thresholds !== null && num >= thresholds.postprandial +} + +const isHighBloodPressure = (row: any) => { + const sys = toNumeric(row?.systolic_pressure) + const dia = toNumeric(row?.diastolic_pressure) + return (sys !== null && sys > 140) || (dia !== null && dia > 90) +} + +const isHighSystolicPressure = (value: any) => { + const num = toNumeric(value) + return num !== null && num > 140 +} + +const isHighDiastolicPressure = (value: any) => { + const num = toNumeric(value) + return num !== null && num > 90 +} + const joinArray = (arr: any): string => { if (!arr) return '' if (Array.isArray(arr)) return arr.filter(Boolean).join('、') return String(arr) } -const fetchQueuePage = async (pageNo: number, status?: number) => { +const fetchQueuePage = async (pageNo: number, status?: number, patientName?: string) => { const today = todayStr() const res: any = await receptionQueue({ status: status ?? activeStatus.value, start_date: today, end_date: today, - patient_name: searchName.value || undefined, + patient_name: (patientName ?? searchName.value) || undefined, page_no: pageNo, page_size: PAGE_SIZE }) @@ -520,54 +609,63 @@ const fetchQueuePage = async (pageNo: number, status?: number) => { return { list, count } } -const fetchOtherCount = async () => { - const otherStatus = activeStatus.value === 1 ? 4 : 1 +const fetchOtherCount = async (baseStatus: 1 | 4, patientName: string, requestSeq: number) => { + const otherStatus = baseStatus === 1 ? 4 : 1 try { - const { count } = await fetchQueuePage(1, otherStatus) + const { count } = await fetchQueuePage(1, otherStatus, patientName || undefined) + if (requestSeq !== queueRequestSeq || activeStatus.value !== baseStatus || searchName.value !== patientName) return if (otherStatus === 1) waitingTotal.value = count else passedTotal.value = count } catch { /* 统计计数失败不阻塞 */ } } -const loadQueue = async (opts?: { silent?: boolean; keepSelection?: boolean }) => { +const loadQueue = async (opts?: { silent?: boolean; autoSelect?: boolean }) => { const silent = opts?.silent === true if (silent && queueInflight) return + const requestSeq = ++queueRequestSeq + const statusSnapshot = activeStatus.value + const searchSnapshot = searchName.value + const autoSelect = opts?.autoSelect ?? !silent queueInflight = true if (!silent) queueLoading.value = true try { page.value = 1 - const { list, count } = await fetchQueuePage(1) + const { list, count } = await fetchQueuePage(1, statusSnapshot, searchSnapshot || undefined) + if (requestSeq !== queueRequestSeq || activeStatus.value !== statusSnapshot || searchName.value !== searchSnapshot) return queue.value = list total.value = count || list.length - if (activeStatus.value === 1) waitingTotal.value = count + if (statusSnapshot === 1) waitingTotal.value = count else passedTotal.value = count hasMore.value = list.length >= PAGE_SIZE && queue.value.length < (count || 0) - fetchOtherCount() + void fetchOtherCount(statusSnapshot, searchSnapshot, requestSeq) if (selectedId.value && !list.some((r) => r.id === selectedId.value)) { - if (opts?.keepSelection === false) { - selectedId.value = null - detail.value = null - } + selectedId.value = null + detail.value = null } - if (!selectedId.value && list.length > 0) { + if (!selectedId.value && autoSelect && list.length > 0) { await selectRow(list[0]) } } catch (e) { if (!silent) console.error('加载队列失败:', e) } finally { - queueLoading.value = false - queueInflight = false + if (requestSeq === queueRequestSeq) { + queueLoading.value = false + queueInflight = false + } } } const loadMore = async () => { if (loadingMore.value || !hasMore.value) return + const statusSnapshot = activeStatus.value + const searchSnapshot = searchName.value loadingMore.value = true try { const nextPage = page.value + 1 - const { list, count } = await fetchQueuePage(nextPage) + const { list, count } = await fetchQueuePage(nextPage, statusSnapshot, searchSnapshot || undefined) + if (activeStatus.value !== statusSnapshot || searchName.value !== searchSnapshot) return // 去重合并(防止轮询时上方有新插入导致重复) const existing = new Set(queue.value.map((r) => r.id)) const appended = list.filter((r) => !existing.has(r.id)) @@ -584,33 +682,41 @@ const loadMore = async () => { const refreshDetailSilently = async () => { if (!selectedId.value || detailInflight) return + const currentId = selectedId.value + const requestSeq = ++detailRequestSeq detailInflight = true try { - const res: any = await receptionDetail({ id: selectedId.value }) + const res: any = await receptionDetail({ id: currentId }) + if (requestSeq !== detailRequestSeq || selectedId.value !== currentId) return detail.value = res || detail.value } catch (e) { /* 静默失败 */ } finally { - detailInflight = false + if (requestSeq === detailRequestSeq) detailInflight = false } } const selectRow = async (row: QueueRow) => { if (selectedId.value === row.id) return + const currentId = row.id + const requestSeq = ++detailRequestSeq selectedId.value = row.id detail.value = null detailLoading.value = true detailInflight = true - resetCountdown() + restartPolling() try { - const res: any = await receptionDetail({ id: row.id }) + const res: any = await receptionDetail({ id: currentId }) + if (requestSeq !== detailRequestSeq || selectedId.value !== currentId) return detail.value = res || null } catch (e) { console.error('加载详情失败:', e) feedback.msgError('加载患者详情失败') } finally { - detailLoading.value = false - detailInflight = false + if (requestSeq === detailRequestSeq) { + detailLoading.value = false + detailInflight = false + } } } @@ -620,12 +726,14 @@ const switchStatus = async (status: 1 | 4) => { selectedId.value = null detail.value = null await loadQueue() + restartPolling() } const handleSearch = async () => { selectedId.value = null detail.value = null await loadQueue() + restartPolling() } const handleCall = async (row: QueueRow) => { @@ -656,6 +764,7 @@ const handleNotify = async (row: QueueRow) => { try { await notifyAssistant({ id: row.id }) feedback.msgSuccess(`已通知医助 ${row.assistant_name || ''}`) + await handleDeskOperationRefresh() } catch (e: any) { feedback.msgError(e?.message || '通知失败') } finally { @@ -669,7 +778,16 @@ const handleEditDiagnosis = () => { } const onDiagnosisEditSuccess = async () => { - await refreshDetailSilently() + await handleDeskOperationRefresh() +} + +const handleDeskOperationRefresh = async () => { + const currentId = selectedId.value + await loadQueue({ silent: true, autoSelect: false }) + if (currentId && selectedId.value === currentId) { + await refreshDetailSilently() + } + restartPolling() } const handleFinish = async () => { @@ -681,21 +799,12 @@ const handleFinish = async () => { } finishLoading.value = true try { - const removedId = apt.value.id - await completeAppointment({ id: removedId }) + await completeAppointment({ id: apt.value.id }) feedback.msgSuccess('已完成接诊') - queue.value = queue.value.filter((r) => r.id !== removedId) - total.value = Math.max(0, total.value - 1) - if (activeStatus.value === 1) waitingTotal.value = Math.max(0, waitingTotal.value - 1) - else passedTotal.value = Math.max(0, passedTotal.value - 1) selectedId.value = null detail.value = null - resetCountdown() - if (queue.value.length > 0) { - await selectRow(queue.value[0]) - } else { - await loadQueue() - } + await loadQueue({ autoSelect: true }) + restartPolling() } catch (e: any) { feedback.msgError(e?.message || '操作失败') } finally { @@ -720,7 +829,7 @@ const handleSaveNote = async () => { feedback.msgSuccess('备注保存成功') noteContent.value = '' showNoteDialog.value = false - await refreshDetailSilently() + await handleDeskOperationRefresh() } catch (e: any) { feedback.msgError(e?.message || '保存失败') } finally { @@ -732,31 +841,40 @@ const resetCountdown = () => { countdown.value = POLL_MS / 1000 } -const startPolling = () => { - stopPolling() +const startCountdown = () => { + if (countdownTimer) clearInterval(countdownTimer) resetCountdown() - // 1s 倒计时 countdownTimer = setInterval(() => { if (pollPaused.value) return if (countdown.value > 1) { countdown.value -= 1 } else { - countdown.value = POLL_MS / 1000 + countdown.value = 1 } }, 1000) - // 5s 实际轮询 - pollTimer = setInterval(async () => { - if (pollPaused.value) return - await loadQueue({ silent: true, keepSelection: true }) - if (!detailLoading.value) { - await refreshDetailSilently() - } +} + +const scheduleNextPoll = () => { + if (pollTimer) clearTimeout(pollTimer) + if (pollPaused.value) return + startCountdown() + pollTimer = setTimeout(async () => { + await handleDeskOperationRefresh() }, POLL_MS) } +const restartPolling = () => { + scheduleNextPoll() +} + +const startPolling = () => { + stopPolling() + scheduleNextPoll() +} + const stopPolling = () => { if (pollTimer) { - clearInterval(pollTimer) + clearTimeout(pollTimer) pollTimer = null } if (countdownTimer) { @@ -768,9 +886,9 @@ const stopPolling = () => { const handleVisibilityChange = () => { pollPaused.value = document.hidden if (!document.hidden) { - resetCountdown() - loadQueue({ silent: true, keepSelection: true }) - refreshDetailSilently() + void handleDeskOperationRefresh() + } else { + stopPolling() } } @@ -820,20 +938,30 @@ onUnmounted(() => {