This commit is contained in:
Your Name
2026-10-27 16:46:50 +08:00
parent 24130b4245
commit 5f1e362698
6 changed files with 695 additions and 13 deletions
+25 -8
View File
@@ -84,10 +84,18 @@
<el-icon v-else><ChatDotRound /></el-icon>
</button>
</el-tooltip>
<el-tooltip content="发起通话" placement="top">
<button class="row-icon-btn call" :disabled="webConsultationBlocked" @click="handleCall(row)">
<el-icon><Phone /></el-icon>
</button>
<el-tooltip :content="webConsultationBlocked ? webConsultationMessage : '发起通话'" placement="top">
<span class="row-call-action">
<button
class="row-icon-btn call"
:disabled="webConsultationBlocked"
:aria-label="webConsultationBlocked ? webConsultationMessage : '发起通话'"
@click="handleCall(row)"
>
<el-icon><Phone /></el-icon>
<span v-if="webConsultationBlocked">已停用</span>
</button>
</span>
</el-tooltip>
</div>
</div>
@@ -214,7 +222,7 @@ const DiagnosisEdit = defineAsyncComponent(() => import('@/views/tcm/diagnosis/e
const POLL_MS = 5_000
const PAGE_SIZE = 15
const { webConsultationBlocked, ensureWebConsultationAllowed } = useDoctorWebConsultation()
const { webConsultationBlocked, webConsultationMessage, ensureWebConsultationAllowed } = useDoctorWebConsultation()
interface QueueRow {
id: number
@@ -1010,16 +1018,25 @@ onUnmounted(() => {
font-size: 15px;
padding: 0;
}
.row-icon-btn:hover {
.row-call-action {
display: inline-flex;
}
.row-icon-btn.call:disabled {
width: auto;
gap: 4px;
padding: 0 6px;
font-size: 12px;
}
.row-icon-btn:hover:not(:disabled) {
background: var(--rx-accent-soft);
color: var(--rx-accent-strong);
border-color: var(--rx-accent);
transform: translateY(-1px);
}
.row-icon-btn:active {
.row-icon-btn:active:not(:disabled) {
transform: scale(0.92);
}
.row-icon-btn.notify:hover {
.row-icon-btn.notify:hover:not(:disabled) {
background: #fff7e6;
color: var(--rx-warning);
border-color: var(--rx-warning);
-2
View File
@@ -1,6 +1,5 @@
<template>
<div class="tcm-diagnosis">
<DoctorWebConsultationNotice />
<el-card class="filter-card !border-none" shadow="never">
<!-- 第一行:日期 + 患者搜索 -->
<div class="filter-main">
@@ -768,7 +767,6 @@
</template>
<script setup lang="ts" name="tcmDiagnosis">
import DoctorWebConsultationNotice from '@/components/doctor-web-consultation-notice/index.vue'
import { tcmDiagnosisLists, tcmDiagnosisDelete, tcmDiagnosisAssign, tcmDiagnosisGuahaoLogList, getAssistants, generateMiniProgramQrcode, generateOrderQrcode, getWechatChatRecords, addWechatChatRecord, deleteWechatChatRecord, getWechatExternalContact, fillIdCard, diagnosisReadonlyDetail } from '@/api/tcm'
import { orderCreate } from '@/api/order'
import { cancelAppointment } from '@/api/doctor'
+77 -2
View File
@@ -333,12 +333,87 @@ test('installer requests are public, deduplicated across notices, cached, and re
assert.equal(transport[1].withToken, false)
})
test('the actual reception, diagnosis and both appointment list entries compile with the shared notice', () => {
test('consultation entries retain the shared notice while the patient list compiles without it', () => {
for (const name of ['views/patient/reception/index.vue', 'views/tcm/diagnosis/index.vue', 'views/tcm/appointment/list.vue', 'views/tcm/appointment/list_h5.vue']) {
const { descriptor, errors } = parse(read(name), { filename: name })
assert.deepEqual(errors, [])
const script = compileScript(descriptor, { id: name })
assert.equal(script.bindings.DoctorWebConsultationNotice, 'setup-const')
assert.equal(script.bindings.DoctorWebConsultationNotice, name === 'views/tcm/diagnosis/index.vue' ? undefined : 'setup-const')
assert.deepEqual(compileTemplate({ source: descriptor.template.content, filename: name, id: name, compilerOptions: { bindingMetadata: script.bindings } }).errors, [])
}
})
test('reception makes doctor cutoff visible and blocks calls while preserving administrator and assistant access', async () => {
const { descriptor } = parse(read('views/patient/reception/index.vue'))
const script = compileScript(descriptor, { id: 'reception-cutoff' })
const template = compileTemplate({ source: descriptor.template.content, filename: 'reception.vue', id: 'reception-cutoff', compilerOptions: { bindingMetadata: script.bindings } })
assert.deepEqual(template.errors, [])
const render = moduleFrom(template.code).render
const passthrough = { setup: (_, { slots }) => () => vue.h('div', slots.default?.()) }
const lifecycle = { ...vue, onMounted() {}, onUnmounted() {}, defineAsyncComponent: () => passthrough }
let clock = cutoff - 1
const store = vue.reactive({ userInfo: { ...doctor, web_consultation_policy: schedule }, serverTimeOffsetMs: 0 })
const warnings = [], requests = [], opened = []
const feedback = { msgWarning: message => warnings.push(message) }
const hook = moduleFrom(read('hooks/useDoctorWebConsultation.ts'), name => {
if (name === 'vue') return lifecycle
if (name === '@/stores/modules/user') return { default: () => store }
if (name === '@/api/app') return {}
if (name === '@/utils/feedback') return { default: feedback }
if (name === '@/utils/doctor-web-consultation') return policy
throw new Error(name)
}, { Date: { now: () => clock } })
let onSignature = () => ({})
const state = moduleFrom(script.content, name => {
if (name === 'vue') return lifecycle
if (name === '@/hooks/useDoctorWebConsultation') return hook
if (name === '@/api/patient' || name === '@/api/doctor') return {}
if (name === '@/api/tcm') return { getCallSignature: async params => { requests.push(params); return onSignature() } }
if (name === '@/utils/feedback') return { default: feedback }
if (name === '@/utils/diag-display') return { formatGender: () => '男' }
if (name === '@/utils/diabetes-discovery-display') return {}
if (name === '@element-plus/icons-vue') return Object.fromEntries(['Phone', 'Search', 'ChatDotRound', 'Loading'].map(key => [key, passthrough]))
if (name.endsWith('.vue')) return { default: passthrough }
throw new Error(name)
}, { ref: vue.ref, computed: vue.computed, watch: vue.watch }).default.setup({}, { expose() {} })
const row = { id: 90, patient_id: 50, source_patient_id: 50, diagnosis_id: 70, patient_name: '测试患者', appointment_type: 'video' }
state.queue.value = [row]
state.chatDialogRef.value = { open: value => opened.push(value) }
async function markup() {
const app = vue.createSSRApp({ render: () => render({}, [], {}, vue.proxyRefs(state)) })
app.config.warnHandler = () => {}
for (const name of ['el-input', 'el-icon', 'el-tag', 'el-empty', 'el-dialog', 'el-button']) app.component(name, passthrough)
app.component('el-tooltip', { props: ['content'], setup: (props, { slots }) => () => vue.h('div', { 'data-tooltip': props.content }, slots.default?.()) })
for (const name of ['loading', 'perms']) app.directive(name, { getSSRProps: () => ({}) })
return renderToString(app)
}
assert.match(await markup(), /class="row-icon-btn call" aria-label="发起通话"/)
await state.handleCall(row)
assert.equal(requests.length, 1)
assert.equal(opened.length, 1)
// A signature that returns after the deadline must never open the calling window.
onSignature = () => { clock = cutoff; return {} }
await state.handleCall(row)
assert.equal(requests.length, 2)
assert.equal(opened.length, 1)
const expired = await markup()
assert.match(expired, /class="row-icon-btn call" disabled aria-label="[^"]*桌面应用/)
assert.match(expired, /<span>已停用<\/span>/)
assert.match(expired, /data-tooltip="[^"]*桌面应用/)
assert.doesNotMatch(expired, /class="row-icon-btn notify" disabled/)
await state.handleCall(row)
assert.equal(requests.length, 2)
assert.equal(opened.length, 1)
assert.equal(warnings.length, 2)
for (const identity of [{ root: 1, role_ids: [1] }, { root: 0, role_ids: [2] }]) {
Object.assign(store.userInfo, identity)
assert.match(await markup(), /class="row-icon-btn call" aria-label="发起通话"/)
assert.doesNotMatch(await markup(), /<span>已停用<\/span>/)
await state.handleCall(row)
}
assert.equal(requests.length, 4)
assert.equal(opened.length, 3)
})
@@ -0,0 +1,521 @@
vite v6.4.2 building for production...
transforming...
WARN Browserslist: browsers data (caniuse-lite) is 6 months old. Please run:
npx update-browserslist-db@latest
Why you should do it regularly: https://github.com/browserslist/update-db#readme
✓ 4118 modules transformed.
WARN node_modules/.pnpm/tcplayer.js@5.3.4-beta.32/node_modules/tcplayer.js/dist/tcplayer.v5.3.4.min.js (3199:26): Use of eval in "node_modules/.pnpm/tcplayer.js@5.3.4-beta.32/node_modules/tcplayer.js/dist/tcplayer.v5.3.4.min.js" is strongly discouraged as it poses security risks and may cause issues with minification.
rendering chunks...
computing gzip size...
dist/assets/default_avatar-C6VB7PGm.png 6.09 kB
dist/assets/no_perms-jDxcYpYC.png 14.62 kB
dist/index.html 31.87 kB │ gzip: 16.16 kB
dist/assets/login_bg-BkIjQ0FB.png 59.27 kB
dist/assets/red3-NOuWP8DK.png 105.00 kB
dist/assets/pink3-BxZ4Y6CS.png 108.36 kB
dist/assets/blue3-D3K9OqGO.png 108.98 kB
dist/assets/yellow3-C_qd9cqN.png 109.27 kB
dist/assets/green3-CPorIQiC.png 109.99 kB
dist/assets/purple3-BGd0LxTa.png 110.15 kB
dist/assets/my_topbg-BiU0PleK.png 142.47 kB
dist/assets/red2-Dw8p71sP.png 750.50 kB
dist/assets/purple2-C0tQkldV.png 752.33 kB
dist/assets/green2-C-VRKLSN.png 756.86 kB
dist/assets/yellow2-B-WtqITJ.png 762.86 kB
dist/assets/pink2-BpEp33zy.png 806.48 kB
dist/assets/blue2-CRQPdLZd.png 806.81 kB
dist/assets/red1-C6Y3UuNB.png 1,660.63 kB
dist/assets/yellow1-Ebw0T5sw.png 1,662.23 kB
dist/assets/pink1-BWNZrP7C.png 1,668.56 kB
dist/assets/blue1-gLOo1H0w.png 1,676.54 kB
dist/assets/purple1-BpMq9FWz.png 1,680.01 kB
dist/assets/green1-h1zqes95.png 1,688.20 kB
dist/assets/index-BCAIVenS.css 0.04 kB │ gzip: 0.06 kB
dist/assets/index-CjSHFu-R.css 0.04 kB │ gzip: 0.06 kB
dist/assets/index-Oppei429.css 0.04 kB │ gzip: 0.06 kB
dist/assets/CaseRecordList-Cl-c3q7S.css 0.05 kB │ gzip: 0.07 kB
dist/assets/DietRecordList-Cc-xUr2B.css 0.05 kB │ gzip: 0.07 kB
dist/assets/roster-CqNlv_rj.css 0.05 kB │ gzip: 0.07 kB
dist/assets/BloodRecordList-DhVK-Y_e.css 0.05 kB │ gzip: 0.07 kB
dist/assets/index-B3imPWFk.css 0.05 kB │ gzip: 0.07 kB
dist/assets/list-BluNBZln.css 0.05 kB │ gzip: 0.07 kB
dist/assets/ExerciseRecordList-DGtgtzou.css 0.05 kB │ gzip: 0.07 kB
dist/assets/edit-CObVTcPU.css 0.09 kB │ gzip: 0.10 kB
dist/assets/edit-D-D8bVPM.css 0.09 kB │ gzip: 0.10 kB
dist/assets/pc_details-nlJo1_D0.css 0.09 kB │ gzip: 0.10 kB
dist/assets/AppointmentRecordPanel-DC22GDgn.css 0.09 kB │ gzip: 0.10 kB
dist/assets/FollowupAudioField-CgGB4JHa.css 0.09 kB │ gzip: 0.11 kB
dist/assets/theme-picker-BsELUxM9.css 0.11 kB │ gzip: 0.09 kB
dist/assets/content-Cl-9UIip.css 0.13 kB │ gzip: 0.13 kB
dist/assets/content-DXAsZ7EV.css 0.13 kB │ gzip: 0.13 kB
dist/assets/content-DaxRy47P.css 0.14 kB │ gzip: 0.14 kB
dist/assets/edit-CAJZpU7c.css 0.14 kB │ gzip: 0.12 kB
dist/assets/content-DxNvUNZR.css 0.15 kB │ gzip: 0.15 kB
dist/assets/useListTimeFilter-DI6SIumd.css 0.16 kB │ gzip: 0.14 kB
dist/assets/edit-C1NtFi7N.css 0.16 kB │ gzip: 0.11 kB
dist/assets/content-Bd3rHJ5J.css 0.18 kB │ gzip: 0.15 kB
dist/assets/index-C-i6mrdU.css 0.18 kB │ gzip: 0.13 kB
dist/assets/change-password-DRfsLJ26.css 0.19 kB │ gzip: 0.16 kB
dist/assets/content-Dy_alH9u.css 0.19 kB │ gzip: 0.16 kB
dist/assets/decoration-img-C5XvHl9_.css 0.19 kB │ gzip: 0.16 kB
dist/assets/account_cost-fbCWUO9k.css 0.20 kB │ gzip: 0.16 kB
dist/assets/index-57RdOFNo.css 0.23 kB │ gzip: 0.16 kB
dist/assets/error-Cz3CexuM.css 0.24 kB │ gzip: 0.18 kB
dist/assets/edit-D4atl_7z.css 0.25 kB │ gzip: 0.17 kB
dist/assets/AssignLogPanel-CZ6Lj7JU.css 0.25 kB │ gzip: 0.17 kB
dist/assets/attr-lNortPsv.css 0.27 kB │ gzip: 0.19 kB
dist/assets/setting-CxqqGetv.css 0.27 kB │ gzip: 0.17 kB
dist/assets/index-DX96drV3.css 0.28 kB │ gzip: 0.17 kB
dist/assets/menu-DYFusokT.css 0.29 kB │ gzip: 0.16 kB
dist/assets/OrderActionHost-DiqybThz.css 0.31 kB │ gzip: 0.22 kB
dist/assets/index-Bk2rMrqw.css 0.32 kB │ gzip: 0.20 kB
dist/assets/index-CE10PP7-.css 0.32 kB │ gzip: 0.20 kB
dist/assets/index-D0f1Mn00.css 0.33 kB │ gzip: 0.22 kB
dist/assets/prescription-drawer-BjwiqvPc.css 0.35 kB │ gzip: 0.16 kB
dist/assets/bind-work-wechat-BX1gqwq1.css 0.35 kB │ gzip: 0.22 kB
dist/assets/index-DhR2yeiO.css 0.39 kB │ gzip: 0.19 kB
dist/assets/index-DoFDMZdP.css 0.40 kB │ gzip: 0.23 kB
dist/assets/index-DtoUZmC0.css 0.44 kB │ gzip: 0.21 kB
dist/assets/PatientOrderList-D8-uh2iu.css 0.45 kB │ gzip: 0.25 kB
dist/assets/DiagnosisTodoList-D0SBmTHG.css 0.46 kB │ gzip: 0.23 kB
dist/assets/CallRecordPanel-TsjZtTO8.css 0.47 kB │ gzip: 0.23 kB
dist/assets/index-C_LxAEkS.css 0.48 kB │ gzip: 0.27 kB
dist/assets/content-sMbqkta2.css 0.55 kB │ gzip: 0.29 kB
dist/assets/RecordingPlaybackBlock-B3KYFgvg.css 0.58 kB │ gzip: 0.26 kB
dist/assets/index-DRNnKTyK.css 0.58 kB │ gzip: 0.24 kB
dist/assets/medicine-DO6x6zrS.css 0.58 kB │ gzip: 0.29 kB
dist/assets/preview-pc-BRQDo0AR.css 0.67 kB │ gzip: 0.36 kB
dist/assets/index-BzrSkGWL.css 0.67 kB │ gzip: 0.27 kB
dist/assets/tabbar-DJsOahKR.css 0.69 kB │ gzip: 0.31 kB
dist/assets/qywx-Dym8iFe0.css 0.74 kB │ gzip: 0.37 kB
dist/assets/AssistantWatchCallDialog-BIPZRgaH.css 0.76 kB │ gzip: 0.38 kB
dist/assets/index-D9_kM0OM.css 0.79 kB │ gzip: 0.36 kB
dist/assets/picker-JBYDNsl5.css 0.84 kB │ gzip: 0.32 kB
dist/assets/oa-phone-CctIyraX.css 0.94 kB │ gzip: 0.34 kB
dist/assets/preview-C7oaKmYo.css 0.98 kB │ gzip: 0.41 kB
dist/assets/TrackingNoteTimeline-B9T8pFso.css 1.18 kB │ gzip: 0.47 kB
dist/assets/RecordingVideoPlayer-4DegHAwm.css 1.19 kB │ gzip: 0.53 kB
dist/assets/index-55HtuLpn.css 1.21 kB │ gzip: 0.55 kB
dist/assets/PatientInfoCard-n6_UPo9e.css 1.32 kB │ gzip: 0.55 kB
dist/assets/TrackingMatrix-zyxtXYPb.css 1.32 kB │ gzip: 0.44 kB
dist/assets/SendPanel-DaGPWlQG.css 1.39 kB │ gzip: 0.50 kB
dist/assets/picker-XZbUgFct.css 1.57 kB │ gzip: 0.50 kB
dist/assets/PrescriptionOrderDetailDrawer-bXx-K7GP.css 1.73 kB │ gzip: 0.71 kB
dist/assets/ImChatRecordPanel-CNRZQxnD.css 1.85 kB │ gzip: 0.61 kB
dist/assets/index-BYOmK9R5.css 1.93 kB │ gzip: 0.58 kB
dist/assets/index-CPXyam6B.css 2.03 kB │ gzip: 0.65 kB
dist/assets/MessageBubble-BwtdPM6K.css 2.04 kB │ gzip: 0.60 kB
dist/assets/login-C0033_nd.css 2.16 kB │ gzip: 0.71 kB
dist/assets/index-DybA0GjF.css 2.40 kB │ gzip: 0.63 kB
dist/assets/index-BmDW9vcs.css 2.44 kB │ gzip: 0.86 kB
dist/assets/CohortReport-DhJXlpZE.css 2.49 kB │ gzip: 0.64 kB
dist/assets/PatientCaseCard-C3Mob6yG.css 2.49 kB │ gzip: 0.81 kB
dist/assets/tongji-C0VnzFxy.css 2.49 kB │ gzip: 0.77 kB
dist/assets/dept-tongji-ky7rOYoi.css 2.67 kB │ gzip: 0.72 kB
dist/assets/NoteTimeline-I2FSxX4s.css 2.74 kB │ gzip: 0.84 kB
dist/assets/readonly-CwfK8LKG.css 2.79 kB │ gzip: 0.94 kB
dist/assets/patient-call-k8V9hV2G.css 2.85 kB │ gzip: 0.91 kB
dist/assets/RevisitNodeCell-B01IyXwO.css 2.85 kB │ gzip: 0.67 kB
dist/assets/ConsultationTranscriptPanel-BzX4qmTD.css 3.21 kB │ gzip: 0.89 kB
dist/assets/PaibanPanel-aqm8azGc.css 3.35 kB │ gzip: 0.88 kB
dist/assets/paiban-BTUHWpUu.css 3.35 kB │ gzip: 0.88 kB
dist/assets/appointment-g8BrHfhi.css 3.43 kB │ gzip: 0.93 kB
dist/assets/PrescriptionAiBatchGenerateDialog-CKkykmdr.css 3.45 kB │ gzip: 0.86 kB
dist/assets/h5-BSFcFSny.css 3.52 kB │ gzip: 0.99 kB
dist/assets/index-Bk7b_sL2.css 3.57 kB │ gzip: 0.96 kB
dist/assets/add-ByXRFm8M.css 3.60 kB │ gzip: 0.87 kB
dist/assets/PromotionAutomationForm-BMhNCJa-.css 3.64 kB │ gzip: 1.03 kB
dist/assets/mubiao-B6IOi2bJ.css 3.78 kB │ gzip: 1.08 kB
dist/assets/index-DKHKZuC3.css 4.03 kB │ gzip: 0.96 kB
dist/assets/DailyMatrix-UrdT3nb4.css 4.28 kB │ gzip: 1.11 kB
dist/assets/WelcomeMessageEditor-DD5mXWP2.css 4.41 kB │ gzip: 1.23 kB
dist/assets/PrescriptionAiReportDialog-CV6AuHV3.css 4.46 kB │ gzip: 1.11 kB
dist/assets/index-CMsGEvq_.css 5.19 kB │ gzip: 1.18 kB
dist/assets/FollowupAudioReview-FDacm0-w.css 5.27 kB │ gzip: 1.39 kB
dist/assets/mubiao-dept-node-ChUPkaxq.css 5.47 kB │ gzip: 1.29 kB
dist/assets/OrderPanel-BzElZPEq.css 5.67 kB │ gzip: 1.46 kB
dist/assets/index-Co57tWbg.css 6.11 kB │ gzip: 1.70 kB
dist/assets/FollowupAudioPanel-DxwHebla.css 6.40 kB │ gzip: 1.47 kB
dist/assets/list-CcE_vGhq.css 6.51 kB │ gzip: 1.45 kB
dist/assets/index-DtDaTXOB.css 7.01 kB │ gzip: 1.69 kB
dist/assets/mubiao-dept-card-naMqyghz.css 7.09 kB │ gzip: 1.53 kB
dist/assets/index-AnyUcdTT.css 7.21 kB │ gzip: 1.86 kB
dist/assets/index-Bw7BJ7QR.css 8.03 kB │ gzip: 1.99 kB
dist/assets/index-CTiFbUC2.css 8.04 kB │ gzip: 2.32 kB
dist/assets/progress-99NT9ysL.css 8.14 kB │ gzip: 1.91 kB
dist/assets/index-Cmf8QBsF.css 8.75 kB │ gzip: 1.78 kB
dist/assets/index-CSIcxqFk.css 8.85 kB │ gzip: 2.21 kB
dist/assets/index-B_6Taw4k.css 8.97 kB │ gzip: 1.91 kB
dist/assets/edit-DDArRBf_.css 9.47 kB │ gzip: 2.05 kB
dist/assets/index-D2xY0L6n.css 9.49 kB │ gzip: 2.20 kB
dist/assets/index-D9Td5Y2a.css 9.52 kB │ gzip: 2.10 kB
dist/assets/ProgressPanel-C_aSqDJh.css 10.10 kB │ gzip: 2.10 kB
dist/assets/index-V-6wNRcg.css 11.20 kB │ gzip: 2.09 kB
dist/assets/index-DV5JwFnL.css 11.95 kB │ gzip: 2.61 kB
dist/assets/index-D5vIRkpq.css 13.85 kB │ gzip: 3.09 kB
dist/assets/commission-settlement-Gy2UsUPr.css 15.55 kB │ gzip: 3.19 kB
dist/assets/list_h5-DJiktk5n.css 17.70 kB │ gzip: 3.23 kB
dist/assets/order_list-JA7OnSCw.css 18.15 kB │ gzip: 3.76 kB
dist/assets/index-Dgjf9Hxv.css 18.86 kB │ gzip: 3.83 kB
dist/assets/WecomFloatingWidgetBuilder-DRWt4rvh.css 19.20 kB │ gzip: 4.00 kB
dist/assets/index_h5-Dxw3PrMR.css 20.00 kB │ gzip: 3.60 kB
dist/assets/yeji-7-gSVFmd.css 25.24 kB │ gzip: 4.60 kB
dist/assets/order_list_h5-lx3Qci6J.css 48.13 kB │ gzip: 7.68 kB
dist/assets/index-deCJ4ja-.css 51.95 kB │ gzip: 11.00 kB
dist/assets/.pnpm-gju-uIcq.css 723.50 kB │ gzip: 100.51 kB
dist/assets/getExposeType-BkPR_d5H.js 0.07 kB │ gzip: 0.09 kB
dist/assets/attr-fJstkaKx.js 0.13 kB │ gzip: 0.13 kB
dist/assets/attr-DbCbv7ST.js 0.13 kB │ gzip: 0.13 kB
dist/assets/attr-D22y8__F.js 0.13 kB │ gzip: 0.13 kB
dist/assets/MediaSourceSelect-nnTL1Ya1.js 0.14 kB │ gzip: 0.14 kB
dist/assets/code-preview-Ccpu8Isa.js 0.16 kB │ gzip: 0.15 kB
dist/assets/edit-Caa6SDFh.js 0.18 kB │ gzip: 0.16 kB
dist/assets/refund-log-BGoc-Cu_.js 0.19 kB │ gzip: 0.16 kB
dist/assets/account-adjust-DN8XgfQ1.js 0.19 kB │ gzip: 0.16 kB
dist/assets/content-w-4FiAyI.js 0.19 kB │ gzip: 0.17 kB
dist/assets/content-Bt6S-jvN.js 0.19 kB │ gzip: 0.17 kB
dist/assets/content-U1E-0plA.js 0.19 kB │ gzip: 0.17 kB
dist/assets/content-DJoopImh.js 0.19 kB │ gzip: 0.17 kB
dist/assets/perm-DcfUk_vi.js 0.20 kB │ gzip: 0.18 kB
dist/assets/diabetes-discovery-display-B_wmGXQJ.js 0.20 kB │ gzip: 0.17 kB
dist/assets/PrescriptionOrderTimeDialog-C3rGGCna.js 0.20 kB │ gzip: 0.17 kB
dist/assets/weapp-B-YcjqJt.js 0.20 kB │ gzip: 0.15 kB
dist/assets/GancaoSubmissionReconcileButton-CQ0daExH.js 0.21 kB │ gzip: 0.18 kB
dist/assets/content-Cve-Zg0X.js 0.21 kB │ gzip: 0.19 kB
dist/assets/edit-PHy_4w6v.js 0.21 kB │ gzip: 0.17 kB
dist/assets/edit-DTfFRhac.js 0.21 kB │ gzip: 0.17 kB
dist/assets/edit-CRsBUzw2.js 0.21 kB │ gzip: 0.17 kB
dist/assets/edit-C8nCtSI8.js 0.21 kB │ gzip: 0.17 kB
dist/assets/edit-bEHJye5Q.js 0.21 kB │ gzip: 0.17 kB
dist/assets/edit-Cq0-XwbN.js 0.21 kB │ gzip: 0.17 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-Bkj1p-jl.js 0.22 kB │ gzip: 0.18 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-jrtitgMf.js 0.22 kB │ gzip: 0.18 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-ObQLkrtE.js 0.22 kB │ gzip: 0.18 kB
dist/assets/oa-menu-form-DnnpUbwg.js 0.22 kB │ gzip: 0.18 kB
dist/assets/useLockFn-d8ytjraO.js 0.22 kB │ gzip: 0.18 kB
dist/assets/appointment-type-DJT6wPaT.js 0.23 kB │ gzip: 0.17 kB
dist/assets/edit-ZRPGVwkq.js 0.23 kB │ gzip: 0.18 kB
dist/assets/auth-D7eWMzIW.js 0.24 kB │ gzip: 0.18 kB
dist/assets/edit-C7Qpxmx2.js 0.25 kB │ gzip: 0.19 kB
dist/assets/relations-add-Bc54velW.js 0.25 kB │ gzip: 0.19 kB
dist/assets/mobile-style--hpGGb7h.js 0.26 kB │ gzip: 0.19 kB
dist/assets/edit-eh_FUcov.js 0.27 kB │ gzip: 0.19 kB
dist/assets/cost-edit-Bww2YY3O.js 0.30 kB │ gzip: 0.20 kB
dist/assets/yeji-edit-Bgm44nJm.js 0.30 kB │ gzip: 0.21 kB
dist/assets/404-C-oK5RsP.js 0.31 kB │ gzip: 0.29 kB
dist/assets/data-table-DGjhtBJ0.js 0.31 kB │ gzip: 0.20 kB
dist/assets/index-CIJ5WKsj.js 0.32 kB │ gzip: 0.25 kB
dist/assets/desktop_workstation-Dj5y8sUE.js 0.32 kB │ gzip: 0.19 kB
dist/assets/index-BOgDA07t.js 0.32 kB │ gzip: 0.25 kB
dist/assets/oa-menu-form-edit-BI2TXVnT.js 0.33 kB │ gzip: 0.21 kB
dist/assets/ai_mcp-d9bftEAi.js 0.34 kB │ gzip: 0.19 kB
dist/assets/medicine-BXzQyrlK.js 0.35 kB │ gzip: 0.17 kB
dist/assets/consumer-Dplg2rm5.js 0.35 kB │ gzip: 0.19 kB
dist/assets/user-78L8IYIR.js 0.37 kB │ gzip: 0.18 kB
dist/assets/role-DQnXXdYS.js 0.39 kB │ gzip: 0.18 kB
dist/assets/useDictOptions-CHqnKGtD.js 0.42 kB │ gzip: 0.32 kB
dist/assets/index-DXsZzNGS.js 0.42 kB │ gzip: 0.32 kB
dist/assets/admin-BFQB7XeR.js 0.42 kB │ gzip: 0.21 kB
dist/assets/content-DpERbizG.js 0.44 kB │ gzip: 0.36 kB
dist/assets/pay-CqmMpZSe.js 0.45 kB │ gzip: 0.20 kB
dist/assets/department-CNNrIxaE.js 0.46 kB │ gzip: 0.19 kB
dist/assets/menu-DTsuDP65.js 0.46 kB │ gzip: 0.19 kB
dist/assets/content-Chb7CJNq.js 0.47 kB │ gzip: 0.37 kB
dist/assets/attr-BpC0QL4H.js 0.48 kB │ gzip: 0.25 kB
dist/assets/post-DEUes3lb.js 0.49 kB │ gzip: 0.21 kB
dist/assets/overflow-CCyjnrJM.js 0.49 kB │ gzip: 0.37 kB
dist/assets/decoration-4l7rKWzA.js 0.50 kB │ gzip: 0.22 kB
dist/assets/message-DAP7vDfv.js 0.50 kB │ gzip: 0.21 kB
dist/assets/403-DxaRcCmi.js 0.52 kB │ gzip: 0.44 kB
dist/assets/footer.vue_vue_type_script_setup_true_lang-DuH5hjbK.js 0.53 kB │ gzip: 0.38 kB
dist/assets/attr-B9zlhC3D.js 0.54 kB │ gzip: 0.27 kB
dist/assets/attr-CBuzIlBs.js 0.54 kB │ gzip: 0.27 kB
dist/assets/add-nav-Dgri8_dR.js 0.54 kB │ gzip: 0.27 kB
dist/assets/content-Bbd9dvh6.js 0.54 kB │ gzip: 0.27 kB
dist/assets/menu-set-CM7skjKY.js 0.54 kB │ gzip: 0.28 kB
dist/assets/attr-BhEmxN8m.js 0.55 kB │ gzip: 0.26 kB
dist/assets/patient-DMp-Zof4.js 0.56 kB │ gzip: 0.22 kB
dist/assets/fans-oDj5BJ1z.js 0.60 kB │ gzip: 0.21 kB
dist/assets/attr-IDZvw7kQ.js 0.60 kB │ gzip: 0.28 kB
dist/assets/attr-Bl01QSLz.js 0.61 kB │ gzip: 0.29 kB
dist/assets/attr-DwcuT80B.js 0.61 kB │ gzip: 0.29 kB
dist/assets/diag-display-DCz_VAqj.js 0.61 kB │ gzip: 0.39 kB
dist/assets/content.vue_vue_type_script_setup_true_lang-CqjcTf_G.js 0.61 kB │ gzip: 0.41 kB
dist/assets/index-B7Dn0C2d.js 0.63 kB │ gzip: 0.43 kB
dist/assets/edit-Cm00DxhR.js 0.63 kB │ gzip: 0.32 kB
dist/assets/link-CuTgyA8w.js 0.64 kB │ gzip: 0.44 kB
dist/assets/asset-D9Oocfqg.js 0.67 kB │ gzip: 0.22 kB
dist/assets/content.vue_vue_type_script_setup_true_lang-BoX3GgoK.js 0.69 kB │ gzip: 0.44 kB
dist/assets/useMediaSourceOptions-CgepfvtS.js 0.70 kB │ gzip: 0.42 kB
dist/assets/content.vue_vue_type_script_setup_true_lang-uJ7nWeFw.js 0.70 kB │ gzip: 0.45 kB
dist/assets/website-DRvH2ZlD.js 0.74 kB │ gzip: 0.24 kB
dist/assets/menu-Dslc8i8M.js 0.76 kB │ gzip: 0.49 kB
dist/assets/usePaging-CghUEVfD.js 0.77 kB │ gzip: 0.47 kB
dist/assets/self_input_stats-D7h2tX7V.js 0.79 kB │ gzip: 0.27 kB
dist/assets/decoration-img-DpI2nI3K.js 0.81 kB │ gzip: 0.50 kB
dist/assets/dict-C5x6WPBP.js 0.81 kB │ gzip: 0.25 kB
dist/assets/code-CT_qc7FA.js 0.82 kB │ gzip: 0.25 kB
dist/assets/blood-thresholds-QpDuElbH.js 0.83 kB │ gzip: 0.34 kB
dist/assets/content-BmDWAJvL.js 0.84 kB │ gzip: 0.55 kB
dist/assets/content.vue_vue_type_script_setup_true_lang-Dk7GdfX8.js 0.86 kB │ gzip: 0.55 kB
dist/assets/index.vue_vue_type_script_setup_true_lang-BaZ2PwlC.js 0.86 kB │ gzip: 0.51 kB
dist/assets/index.vue_vue_type_script_setup_true_lang-DHxCVAKu.js 0.88 kB │ gzip: 0.51 kB
dist/assets/error-B64camZJ.js 0.89 kB │ gzip: 0.60 kB
dist/assets/theme-picker-BApH-Sv7.js 0.92 kB │ gzip: 0.59 kB
dist/assets/wecomOauthPostMessage-C_e6VGrO.js 0.95 kB │ gzip: 0.53 kB
dist/assets/index.vue_vue_type_script_setup_true_lang-DO2j0LDx.js 0.98 kB │ gzip: 0.53 kB
dist/assets/wx_oa-BMfh8-mo.js 1.04 kB │ gzip: 0.29 kB
dist/assets/article-BKnlZ8BH.js 1.07 kB │ gzip: 0.26 kB
dist/assets/attr-setting.vue_vue_type_script_setup_true_lang-7Iu9TgfU.js 1.08 kB │ gzip: 0.61 kB
dist/assets/rich_text-B1LJvcln.js 1.10 kB │ gzip: 0.58 kB
dist/assets/cache-DX9-nRQS.js 1.13 kB │ gzip: 0.69 kB
dist/assets/index-D5hX9MdV.js 1.21 kB │ gzip: 0.70 kB
dist/assets/popover_input-CHl485Vl.js 1.25 kB │ gzip: 0.59 kB
dist/assets/finance-CxJQLtAw.js 1.30 kB │ gzip: 0.37 kB
dist/assets/pc-DkeuFYPA.js 1.31 kB │ gzip: 0.78 kB
dist/assets/doctor-DbXVWy8P.js 1.32 kB │ gzip: 0.37 kB
dist/assets/oa-menu-form-edit.vue_vue_type_script_setup_true_lang-9dbXDh_X.js 1.33 kB │ gzip: 0.78 kB
dist/assets/content-BJqlhKnX.js 1.33 kB │ gzip: 0.72 kB
dist/assets/content-jaUeh_y1.js 1.37 kB │ gzip: 0.70 kB
dist/assets/content-TBNKAEZq.js 1.38 kB │ gzip: 0.79 kB
dist/assets/file-BnWbAF8R.js 1.41 kB │ gzip: 0.64 kB
dist/assets/upload-BQFcP2BL.js 1.41 kB │ gzip: 0.64 kB
dist/assets/content-DXeByrH0.js 1.42 kB │ gzip: 0.79 kB
dist/assets/code-preview.vue_vue_type_script_setup_true_lang-BIUkji9-.js 1.44 kB │ gzip: 0.85 kB
dist/assets/index-CkM3iXQw.js 1.44 kB │ gzip: 0.86 kB
dist/assets/index.vue_vue_type_style_index_0_lang-DNRsIFxW.js 1.46 kB │ gzip: 0.79 kB
dist/assets/oa-phone-Box2uE6n.js 1.47 kB │ gzip: 0.79 kB
dist/assets/attr-aAsc1AzQ.js 1.50 kB │ gzip: 0.76 kB
dist/assets/menu-D1ggUota.js 1.55 kB │ gzip: 0.87 kB
dist/assets/MediaSourceSelect.vue_vue_type_script_setup_true_lang-NXWg0oJL.js 1.58 kB │ gzip: 0.80 kB
dist/assets/PatientInfoCard-BbIl2HmL.js 1.60 kB │ gzip: 0.82 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-Bvw-Y8Fm.js 1.62 kB │ gzip: 0.70 kB
dist/assets/style-r61AGBZ5.js 1.62 kB │ gzip: 0.88 kB
dist/assets/refund-log.vue_vue_type_script_setup_true_lang-D3oucrIl.js 1.66 kB │ gzip: 0.87 kB
dist/assets/statistics-DWzpFFtL.js 1.69 kB │ gzip: 1.01 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-pmI0s_wm.js 1.71 kB │ gzip: 0.80 kB
dist/assets/order-BeRSHZRH.js 1.74 kB │ gzip: 0.44 kB
dist/assets/environment-CdmEwPCD.js 1.74 kB │ gzip: 0.74 kB
dist/assets/setup-Dit4i15I.js 1.77 kB │ gzip: 1.00 kB
dist/assets/useMenuOa-CHcd7WxG.js 1.82 kB │ gzip: 0.85 kB
dist/assets/attr-setting-Cb02e4Di.js 1.88 kB │ gzip: 0.53 kB
dist/assets/index-BPzvs467.js 1.90 kB │ gzip: 0.99 kB
dist/assets/edit.vue_vue_type_script_setup_true_name_articleColumnEdit_lang-B0FO66gX.js 1.94 kB │ gzip: 1.06 kB
dist/assets/index-DHH8q7VG.js 2.03 kB │ gzip: 0.98 kB
dist/assets/index-BQLDX13S.js 2.04 kB │ gzip: 1.14 kB
dist/assets/TrackingNoteTimeline-DTPqRTrR.js 2.08 kB │ gzip: 1.17 kB
dist/assets/index-B8miBXrq.js 2.09 kB │ gzip: 1.17 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-DZLpUkMh.js 2.10 kB │ gzip: 0.95 kB
dist/assets/open_setting-5pytpTsL.js 2.10 kB │ gzip: 1.12 kB
dist/assets/index-DN1LXH8z.js 2.12 kB │ gzip: 1.23 kB
dist/assets/account-adjust.vue_vue_type_script_setup_true_lang-Bdb_zlNf.js 2.16 kB │ gzip: 1.12 kB
dist/assets/filing-7zy1THqy.js 2.17 kB │ gzip: 1.17 kB
dist/assets/index-C4o3_nxl.js 2.18 kB │ gzip: 1.28 kB
dist/assets/icon-Ce8zjLB8.js 2.23 kB │ gzip: 0.82 kB
dist/assets/content.vue_vue_type_script_setup_true_lang-DocDrgvi.js 2.24 kB │ gzip: 1.16 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-DR2cgc6Y.js 2.28 kB │ gzip: 1.16 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-CTWV6r8t.js 2.28 kB │ gzip: 1.08 kB
dist/assets/index.vue_vue_type_script_setup_true_lang-jh5rtxiA.js 2.37 kB │ gzip: 1.14 kB
dist/assets/RecordingPlaybackBlock-TN51sqME.js 2.40 kB │ gzip: 1.19 kB
dist/assets/auth.vue_vue_type_script_setup_true_lang-BRMaFi-b.js 2.40 kB │ gzip: 1.33 kB
dist/assets/data-table.vue_vue_type_script_setup_true_lang-B0qqxZzz.js 2.51 kB │ gzip: 1.28 kB
dist/assets/oa-attr-D03xCoUk.js 2.51 kB │ gzip: 1.25 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-Cy67jkMh.js 2.52 kB │ gzip: 1.20 kB
dist/assets/h5-D9EtVi-k.js 2.55 kB │ gzip: 1.22 kB
dist/assets/change-password-DqYpO4dJ.js 2.57 kB │ gzip: 1.35 kB
dist/assets/im-business-message-parse-DdA9BRrQ.js 2.60 kB │ gzip: 1.28 kB
dist/assets/add-nav.vue_vue_type_script_setup_true_lang-DnG9oFAm.js 2.62 kB │ gzip: 1.27 kB
dist/assets/tabbar-DfjwGzmR.js 2.66 kB │ gzip: 1.33 kB
dist/assets/file-BVI45Ti8.js 2.73 kB │ gzip: 1.06 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-CyP7s8Wf.js 2.74 kB │ gzip: 1.25 kB
dist/assets/PrescriptionOrderTimeDialog.vue_vue_type_script_setup_true_lang-CPGjMf4a.js 2.77 kB │ gzip: 1.53 kB
dist/assets/oa-menu-form.vue_vue_type_script_setup_true_lang-DoNIriKj.js 2.77 kB │ gzip: 1.07 kB
dist/assets/CaseRecordList-V5Or2HPO.js 2.80 kB │ gzip: 1.52 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-BB8w16a4.js 2.81 kB │ gzip: 1.06 kB
dist/assets/index-m_ZlEruX.js 2.85 kB │ gzip: 1.34 kB
dist/assets/follow_reply-C1ZmeekO.js 2.85 kB │ gzip: 1.49 kB
dist/assets/index-Dv5zCNc2.js 2.86 kB │ gzip: 1.44 kB
dist/assets/cost-edit.vue_vue_type_script_setup_true_lang-BlcF5Vt7.js 2.86 kB │ gzip: 1.35 kB
dist/assets/default_reply-Dyq6xHoV.js 2.91 kB │ gzip: 1.54 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-DulDB_wp.js 2.91 kB │ gzip: 1.42 kB
dist/assets/stats-Dn1GCuH0.js 2.91 kB │ gzip: 0.70 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-98t9Kapo.js 2.96 kB │ gzip: 1.13 kB
dist/assets/protocol-BXEP4wmQ.js 2.98 kB │ gzip: 1.10 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-BOR7W84g.js 3.00 kB │ gzip: 1.45 kB
dist/assets/mubiao-dept-node-B2CdWh1R.js 3.03 kB │ gzip: 1.25 kB
dist/assets/RevisitNodeCell-C7g2eW_7.js 3.04 kB │ gzip: 1.23 kB
dist/assets/relations-add.vue_vue_type_script_setup_true_lang-CW-4Ha3F.js 3.04 kB │ gzip: 1.26 kB
dist/assets/picker.vue_vue_type_script_setup_true_lang-DNgH46ld.js 3.05 kB │ gzip: 1.49 kB
dist/assets/keyword_reply-LnjMSU03.js 3.11 kB │ gzip: 1.60 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-UnLlGsqk.js 3.12 kB │ gzip: 1.51 kB
dist/assets/index-BeHZLskc.js 3.21 kB │ gzip: 1.52 kB
dist/assets/edit-DgZX2Ztd.js 3.25 kB │ gzip: 1.47 kB
dist/assets/menu-set.vue_vue_type_script_setup_true_lang-JqVT2ZjX.js 3.26 kB │ gzip: 1.40 kB
dist/assets/pc_details-CCFadkbK.js 3.39 kB │ gzip: 1.49 kB
dist/assets/index-BzAwmoFV.js 3.39 kB │ gzip: 1.46 kB
dist/assets/index-C-ANTK_d.js 3.42 kB │ gzip: 1.53 kB
dist/assets/index-C7cpTcnr.js 3.43 kB │ gzip: 1.64 kB
dist/assets/GancaoSubmissionReconcileButton.vue_vue_type_script_setup_true_lang-xEm7AOwZ.js 3.44 kB │ gzip: 1.70 kB
dist/assets/balance_details-OZK0LZx5.js 3.48 kB │ gzip: 1.62 kB
dist/assets/index-BAf0bHIM.js 3.48 kB │ gzip: 1.64 kB
dist/assets/index.vue_vue_type_script_setup_true_lang-BvbAPsap.js 3.52 kB │ gzip: 1.56 kB
dist/assets/journal-C1g1c6_N.js 3.72 kB │ gzip: 1.46 kB
dist/assets/edit-Bs6CPj8_.js 3.73 kB │ gzip: 1.57 kB
dist/assets/detail-CLtpsjqk.js 3.76 kB │ gzip: 1.55 kB
dist/assets/AssistantWatchCallDialog-CQDR5023.js 3.78 kB │ gzip: 1.85 kB
dist/assets/CallRecordPanel-C1nYSBvx.js 3.78 kB │ gzip: 1.77 kB
dist/assets/AssignLogPanel-B0ADSPNt.js 3.83 kB │ gzip: 1.78 kB
dist/assets/edit-pbG9AJwB.js 3.91 kB │ gzip: 1.71 kB
dist/assets/preview-pc-D2hcxhzb.js 3.98 kB │ gzip: 1.63 kB
dist/assets/AppointmentRecordPanel-lwOCtwB-.js 4.02 kB │ gzip: 1.73 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-BIkqXjk1.js 4.11 kB │ gzip: 1.65 kB
dist/assets/index-DTLqLCt8.js 4.11 kB │ gzip: 1.79 kB
dist/assets/first_visit-CPd_K1zk.js 4.13 kB │ gzip: 0.89 kB
dist/assets/detail-CyB-Ved_.js 4.15 kB │ gzip: 1.56 kB
dist/assets/MessageBubble-BwZcwsRK.js 4.17 kB │ gzip: 1.79 kB
dist/assets/index-ChnWKGOK.js 4.22 kB │ gzip: 1.92 kB
dist/assets/useListTimeFilter-CDHwdMVI.js 4.26 kB │ gzip: 1.52 kB
dist/assets/bind-work-wechat-BcuQY97p.js 4.40 kB │ gzip: 2.27 kB
dist/assets/recharge_record-D-i2lsiR.js 4.43 kB │ gzip: 1.87 kB
dist/assets/index-C24Szps7.js 4.45 kB │ gzip: 1.79 kB
dist/assets/index-CgU5lHkh.js 4.47 kB │ gzip: 1.91 kB
dist/assets/index-CdkVkm9I.js 4.48 kB │ gzip: 2.07 kB
dist/assets/index-BR4uZ0bR.js 4.49 kB │ gzip: 1.96 kB
dist/assets/index-OUyl1YWh.js 4.49 kB │ gzip: 1.96 kB
dist/assets/preview-By3qHvcB.js 4.50 kB │ gzip: 1.70 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-DLSkXgJL.js 4.51 kB │ gzip: 1.74 kB
dist/assets/RecordingVideoPlayer-CunckXfW.js 4.53 kB │ gzip: 2.26 kB
dist/assets/index-DXPcEviw.js 4.55 kB │ gzip: 1.92 kB
dist/assets/paiban-BZCtCPru.js 4.56 kB │ gzip: 2.13 kB
dist/assets/index-BQNpgDtU.js 4.57 kB │ gzip: 1.33 kB
dist/assets/index-DCwkYXUE.js 4.58 kB │ gzip: 2.28 kB
dist/assets/login_register-B2eJmi13.js 4.59 kB │ gzip: 2.01 kB
dist/assets/dept-tongji-u1hMXQ5h.js 4.66 kB │ gzip: 2.04 kB
dist/assets/index-o-zPrppN.js 4.76 kB │ gzip: 2.05 kB
dist/assets/attr.vue_vue_type_script_setup_true_lang-C2-_l49b.js 4.92 kB │ gzip: 2.04 kB
dist/assets/index-BwmXiMM2.js 4.99 kB │ gzip: 2.13 kB
dist/assets/edit-DQ-1Mh9Q.js 5.04 kB │ gzip: 1.99 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-CcNQ6zfj.js 5.18 kB │ gzip: 2.02 kB
dist/assets/PaibanPanel-5VOJLhv0.js 5.28 kB │ gzip: 2.42 kB
dist/assets/index-BOoIemz7.js 5.32 kB │ gzip: 2.20 kB
dist/assets/index-h898ijqB.js 5.53 kB │ gzip: 2.20 kB
dist/assets/mubiao-DyllWsjg.js 5.55 kB │ gzip: 2.56 kB
dist/assets/yeji-edit.vue_vue_type_script_setup_true_lang-DT4Nneii.js 5.56 kB │ gzip: 1.81 kB
dist/assets/mobile-style.vue_vue_type_script_setup_true_lang-CML-t7Bf.js 5.59 kB │ gzip: 1.86 kB
dist/assets/ExerciseRecordList-DsQBkV6J.js 5.62 kB │ gzip: 2.16 kB
dist/assets/attr-DWBSFL5X.js 5.62 kB │ gzip: 2.24 kB
dist/assets/tongji-Ba8eZfAb.js 5.66 kB │ gzip: 2.31 kB
dist/assets/PatientOrderList-BOEddIug.js 5.82 kB │ gzip: 2.84 kB
dist/assets/DietRecordList-i41_ZoC0.js 5.84 kB │ gzip: 2.02 kB
dist/assets/mubiao-dept-card-B2fWcpHe.js 5.91 kB │ gzip: 2.38 kB
dist/assets/index-PUxtBtN9.js 6.07 kB │ gzip: 2.68 kB
dist/assets/refund_record-Gb1oqb85.js 6.27 kB │ gzip: 2.33 kB
dist/assets/DiagnosisTodoList-BOdL42ZO.js 6.34 kB │ gzip: 2.89 kB
dist/assets/index-Bja_zMA-.js 6.35 kB │ gzip: 3.20 kB
dist/assets/information-DjT_pMCD.js 6.36 kB │ gzip: 1.80 kB
dist/assets/NoteTimeline-BAYwM6HX.js 6.48 kB │ gzip: 2.60 kB
dist/assets/account_cost-CG8rxaLp.js 6.56 kB │ gzip: 2.86 kB
dist/assets/PatientCaseCard-DwF1N82F.js 6.61 kB │ gzip: 2.34 kB
dist/assets/index-By6V6lzN.js 6.61 kB │ gzip: 2.62 kB
dist/assets/index-BEvZAYQq.js 6.77 kB │ gzip: 2.89 kB
dist/assets/picker-mZAqwkgm.js 7.32 kB │ gzip: 3.17 kB
dist/assets/edit.vue_vue_type_script_setup_true_lang-Cu4w-uou.js 7.35 kB │ gzip: 2.66 kB
dist/assets/setting-BsXxUiDW.js 7.35 kB │ gzip: 2.93 kB
dist/assets/weapp-CLBYXDPD.js 7.39 kB │ gzip: 2.13 kB
dist/assets/readonly-BBVwkkpe.js 7.51 kB │ gzip: 2.85 kB
dist/assets/index-BU-KCNb_.js 7.55 kB │ gzip: 3.52 kB
dist/assets/ConsultationTranscriptPanel-DtOBvSSH.js 7.58 kB │ gzip: 3.40 kB
dist/assets/prescription-order-utils-CXfs2Bra.js 7.62 kB │ gzip: 3.11 kB
dist/assets/config-BrmELIzg.js 7.77 kB │ gzip: 2.59 kB
dist/assets/TrackingMatrix-BCS78O3a.js 7.84 kB │ gzip: 3.06 kB
dist/assets/patient-call-Y14jLwE9.js 7.85 kB │ gzip: 3.24 kB
dist/assets/login-zU1WQuy1.js 7.86 kB │ gzip: 3.36 kB
dist/assets/dayjs-B2bPhvDy.js 8.07 kB │ gzip: 3.45 kB
dist/assets/index-Cgbnoffl.js 8.24 kB │ gzip: 3.63 kB
dist/assets/edit-D_dQIHMQ.js 9.00 kB │ gzip: 2.83 kB
dist/assets/index-BjCKpiTq.js 9.10 kB │ gzip: 3.66 kB
dist/assets/FollowupAudioField-Cz9k7G05.js 9.15 kB │ gzip: 4.21 kB
dist/assets/tim-upload-plugin-CK05mjrC.js 9.47 kB │ gzip: 3.61 kB
dist/assets/index-B-d3w-2x.js 9.49 kB │ gzip: 3.84 kB
dist/assets/tcm-dnR2Y9cm.js 9.56 kB │ gzip: 1.80 kB
dist/assets/BloodRecordList-BXFBcM5W.js 9.58 kB │ gzip: 3.33 kB
dist/assets/medicine-CXLjyZ4z.js 9.74 kB │ gzip: 3.56 kB
dist/assets/index-CdH10N8y.js 9.94 kB │ gzip: 3.95 kB
dist/assets/edit-BgZga51B.js 9.99 kB │ gzip: 3.55 kB
dist/assets/index-DfPa1B4C.js 10.33 kB │ gzip: 3.89 kB
dist/assets/PrescriptionAiBatchGenerateDialog-CbDochyc.js 10.36 kB │ gzip: 4.29 kB
dist/assets/SendPanel-B-3zzc4t.js 10.37 kB │ gzip: 3.57 kB
dist/assets/ImChatRecordPanel-DcLbpkkt.js 10.42 kB │ gzip: 4.54 kB
dist/assets/progress-DiE2qijv.js 11.18 kB │ gzip: 4.59 kB
dist/assets/list-H08fgP0v.js 11.19 kB │ gzip: 4.02 kB
dist/assets/index-DhSLdU1b.js 11.30 kB │ gzip: 4.22 kB
dist/assets/appointment-CnaXK73X.js 11.34 kB │ gzip: 4.24 kB
dist/assets/edit-Z9u99AZX.js 11.44 kB │ gzip: 3.76 kB
dist/assets/OrderPanel-CrjieJtr.js 11.74 kB │ gzip: 4.22 kB
dist/assets/picker-y_YvYlYE.js 12.15 kB │ gzip: 4.49 kB
dist/assets/ProgressPanel-D9vermBp.js 12.59 kB │ gzip: 4.48 kB
dist/assets/prescription-drawer-oTPBp0J1.js 12.76 kB │ gzip: 3.87 kB
dist/assets/index-Dhjv-_tt.js 13.00 kB │ gzip: 4.23 kB
dist/assets/index-hO8O9Bj_.js 13.11 kB │ gzip: 3.91 kB
dist/assets/h5-DjIcZ3Wl.js 13.83 kB │ gzip: 4.40 kB
dist/assets/index-ByPOhPi4.js 14.92 kB │ gzip: 6.02 kB
dist/assets/WecomFloatingWidgetBuilder-Cq4JYCH3.js 15.22 kB │ gzip: 6.01 kB
dist/assets/guahao-Ce57OUp7.js 15.46 kB │ gzip: 4.48 kB
dist/assets/index-BqYURLhc.js 15.90 kB │ gzip: 5.20 kB
dist/assets/edit-DEBGa06y.js 15.90 kB │ gzip: 4.71 kB
dist/assets/FollowupAudioReview-XRd7flbh.js 16.29 kB │ gzip: 6.00 kB
dist/assets/index-DQMBzM3S.js 16.34 kB │ gzip: 6.06 kB
dist/assets/index-D03yXUGT.js 16.35 kB │ gzip: 5.11 kB
dist/assets/index-Rq6XWAZo.js 16.52 kB │ gzip: 6.77 kB
dist/assets/index-DpaOulPk.js 16.60 kB │ gzip: 6.07 kB
dist/assets/PrescriptionAiReportDialog-Bb4rhlvC.js 17.15 kB │ gzip: 6.47 kB
dist/assets/index-Y9egu_n2.js 17.84 kB │ gzip: 6.02 kB
dist/assets/roster-D__QiTct.js 18.09 kB │ gzip: 5.71 kB
dist/assets/index-ClK5E1wh.js 18.19 kB │ gzip: 6.88 kB
dist/assets/PromotionAutomationForm-DpIJF2Ks.js 18.23 kB │ gzip: 6.37 kB
dist/assets/index-CM98eneU.js 18.60 kB │ gzip: 5.23 kB
dist/assets/WelcomeMessageEditor-vSxsqYSM.js 19.11 kB │ gzip: 6.95 kB
dist/assets/CohortReport-CoddHOyB.js 21.02 kB │ gzip: 7.73 kB
dist/assets/OrderActionHost-BUyzxVKo.js 21.16 kB │ gzip: 6.01 kB
dist/assets/index-Bt2lezPH.js 22.09 kB │ gzip: 7.90 kB
dist/assets/add-DVMQSAHo.js 22.29 kB │ gzip: 5.36 kB
dist/assets/index-FjDUth3-.js 22.71 kB │ gzip: 8.12 kB
dist/assets/index-DIoBcUrd.js 23.32 kB │ gzip: 8.16 kB
dist/assets/list_h5-Op8V2m2c.js 28.42 kB │ gzip: 9.63 kB
dist/assets/qywx-D5gZ8lqf.js 30.35 kB │ gzip: 10.01 kB
dist/assets/list-DO2UogcS.js 30.91 kB │ gzip: 9.95 kB
dist/assets/DailyMatrix-BXJZ1KYC.js 32.09 kB │ gzip: 9.41 kB
dist/assets/commission-settlement-Odi7exf_.js 35.83 kB │ gzip: 9.75 kB
dist/assets/index-Dy219vFN.js 37.07 kB │ gzip: 11.95 kB
dist/assets/index_h5-DWwU1NfB.js 39.10 kB │ gzip: 12.00 kB
dist/assets/index-CffyyijM.js 39.74 kB │ gzip: 14.17 kB
dist/assets/edit-BK9aVUU9.js 40.66 kB │ gzip: 10.43 kB
dist/assets/index-DoZILwmE.js 40.84 kB │ gzip: 12.04 kB
dist/assets/FollowupAudioPanel-gy_PJ1Jr.js 41.09 kB │ gzip: 14.27 kB
dist/assets/PrescriptionOrderDetailDrawer-cJCbiOUM.js 51.01 kB │ gzip: 13.81 kB
dist/assets/index-GJ1RFLDS.js 54.54 kB │ gzip: 16.03 kB
dist/assets/index-BKi_L6wO.js 55.03 kB │ gzip: 15.88 kB
dist/assets/tim-profanity-filter-plugin-CIiDZ6v0.js 55.90 kB │ gzip: 21.04 kB
dist/assets/index-z_v0TN7_.js 69.54 kB │ gzip: 21.09 kB
dist/assets/rtc-detect-TVc6uI0Q.js 74.80 kB │ gzip: 25.93 kB
dist/assets/yeji-DqnSaEKO.js 87.40 kB │ gzip: 23.27 kB
dist/assets/index-aBE94aCk.js 88.79 kB │ gzip: 25.80 kB
dist/assets/order_list-DBooVgyU.js 115.82 kB │ gzip: 32.11 kB
dist/assets/order_list_h5-DVRiCbpO.js 142.56 kB │ gzip: 37.43 kB
dist/assets/@tencentcloud/chat-uikit-engine-BOxL0feI.js 162.84 kB │ gzip: 41.71 kB
dist/assets/index-BJHYjIjI.js 312.52 kB │ gzip: 98.81 kB
dist/assets/@tencentcloud/chat-Dqh0gV5b.js 725.88 kB │ gzip: 178.89 kB
dist/assets/@tencentcloud/call-engine-js-DCEY106C.js 2,290.42 kB │ gzip: 749.64 kB
dist/assets/.pnpm-CyuAWpdG.js 18,525.72 kB │ gzip: 5,519.46 kB
WARN
(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 2m 40s
@@ -0,0 +1,71 @@
# 医生网页面诊停用策略审计
日期:2026-10-27。范围:只读审计前端共享策略、接诊台调用链、服务端门禁和既有测试;未修改业务代码、配置或测试,未访问真实患者接口或数据库。已读取根 `AGENTS.md`;本仓库当前没有 `.trellis/` 目录。
本次确认的产品规则:**仅医生停止网页拨号,管理员和医助保留;患者列表移除整块公告。** 以下结论按该规则评估。
## 结论
现有前后端门禁对“非 root,且角色数组包含医生角色 1”的账号一致生效,接诊台患者行的常规调用链没有发现遗漏守卫。看到“已停用”公告本身不能证明当前账号应被禁止拨号:公告只看截止日期,管理员及医助按明确要求仍可拨号。改善接诊台停用按钮的可见反馈、移除患者列表公告,不需要扩大角色限制。
没有当前实际登录账号身份或线上请求证据,不能把此次反馈确定归因为某个账号角色错误。下述结论证明代码层面的受众和入口覆盖,不等同于线上浏览器端到端验证。
## 角色与时间规则
- `admin/src/utils/doctor-web-consultation.ts:83`:`isConsultationDoctor()` 要求 `Number(root) !== 1`、`role_ids` 为数组且包含数值化后的 `1`。混合角色 `[2, 1]` 仍视为医生;root 始终豁免。
- 同文件 `:87`、`:91`:截止当刻起到期;只有医生且到期时 `isDoctorWebConsultationBlocked()` 为真。
- `admin/src/hooks/useDoctorWebConsultation.ts:72`–`:76`:公告阶段、全局到期状态与账号阻断状态分开计算。公告不按账号角色隐藏,因此管理员/医助看到公告但可拨号是现有设计的直接结果。
- Hook 在挂载、每秒时钟、每分钟配置刷新、窗口聚焦/重新可见时更新;点击守卫会立即校准时间。服务器时间偏移用于避免本机时钟影响截止判断。刷新失败时保留上一份有效配置;完全没有有效配置时前端放行,服务端仍逐次判断。
- `server/app/adminapi/logic/auth/AdminLogic.php:320` 从数据库给个人信息返回整数数组 `role_ids`;`server/app/common/cache/AdminTokenCache.php:90` 给可信 token 身份返回 `role_id`。前后端字段名不同是各自数据契约,不是发现的字段拼写错误。
- `server/app/adminapi/service/DoctorWebConsultationPolicy.php:48`、`:54`:服务端同样豁免 root,数值化可信 token 中的 `role_id` 后检查医生角色 1。角色来自登录身份,不取请求参数。
- 当前仓库配置 `server/config/doctor_web_consultation.php:13` 是北京时间 **2026-10-25 00:00:00**,已经过期。前端从服务端取得策略,不另写截止日期。
角色判断的实际边界:只有医助角色 `[2]`、管理角色 `[3,7,8]`、root 均应保留通话;含医生角色的多角色账号受限。`role_ids` 缺失或非数组时前端不认定为医生,不过正常个人信息端点已经返回规范字段。此次没有证据表明正常返回格式异常,不建议额外改变角色规则。
## 接诊台和服务端门禁覆盖
接诊台 `admin/src/views/patient/reception/index.vue` 的患者行在审计开始时已经具备:
1. 按钮 `:disabled="webConsultationBlocked"`。
2. `handleCall()` 首行调用 `ensureWebConsultationAllowed()`,阻止直接调用事件处理函数绕过禁用按钮。
3. 仅在允许后调用 `getCallSignature()`;异步响应回来再检查一次,防止请求跨过截止时间后打开聊天。
这说明本次需要补足的重点是按钮“已停用”的直观状态和回归覆盖,而不是为该入口首次增加门禁。主代理正在负责界面修改,本审计未参与改动。
服务端调用链:
| 层 | 覆盖 |
| --- | --- |
| `admin/src/api/tcm.ts:205` | 接诊台取签名请求使用 `/tcm.diagnosis/getCallSignature`。 |
| `server/app/adminapi/config/route.php:15` | 顺序为 Init → Login → Auth → DoctorWebConsultation;正常身份和权限认证在迁移门禁之前。 |
| `server/app/adminapi/http/middleware/DoctorWebConsultationMiddleware.php:18` | 命中后返回 `code=0`、`reason=doctor_web_consultation_expired` 和截止时间;不进入控制器,不触发退出登录。 |
| `server/app/adminapi/service/DoctorWebConsultationPolicy.php:45` | 精确覆盖 `tcm.diagnosis` 的 `getCallSignature`、`startCall`、`getDoctorSignature`、`getPatientSignature`,不区分动作大小写。 |
| `server/app/adminapi/controller/tcm/DiagnosisController.php:337`、`:353` | 常规取签名/发起通话都走上述控制器与全局中间件。 |
| `admin/src/components/chat-dialog/index.vue:260` | 通话预检前后均检查迁移状态;SDK `call/calls/groupCall/accept` 还有守卫,已打开的聊天也不能继续发起医生网页通话。 |
`endCall`、绑定房间、录音和文字收尾保持可用;医助 `watchCall` 保持可用,其业务逻辑要求当前管理员是该诊单指派的医助。桌面请求按明确的工作站/旧 HTTPX UA 兼容放行,但只要带 Origin、Referer 或 Sec-Fetch 浏览器信号就按网页处理。`terminal=1` 不作为网页判据,因此不会误停旧工作站。
既有边界仍然存在:UA 只是兼容识别,不是设备身份认证;后端不会撤销截止前已签发的腾讯签名,已打开网页的通话预检和 SDK 守卫仍有必要。本次正常产品流程不需要调整这些范围。
## 实际验证结果
| 命令/检查 | 结果 |
| --- | --- |
| 在 `admin/` 执行 `node --test tests/doctor-web-consultation.test.cjs tests/appointment-call-mode.test.cjs` | **19 项通过,0 失败**。这是本轮审计开始时的基线;主代理随后正在修改界面/对应测试,集成后应重跑。 |
| 在 `server/` 执行 `php tests/DoctorWebConsultationPolicyTest.php` | **未完成**。首个默认配置读取即因 `is_readable()` 为 false 抛出“配置文件不存在或不可读”。`php -n` 同样失败。 |
| PHP 文件检查 | `is_file(config/doctor_web_consultation.php) === true`、`is_readable(...) === false`,但 `file_get_contents(...)` 和显式 `require` 成功。属于本机 PHP/Windows 文件可读性检查现象,未修改 ACL、配置或代码,不据此判断线上异常。 |
| 对真实 Policy 类显式传入仓库配置执行独立只读矩阵 | **34 条断言通过**:四个受限动作的截止前/当刻/后一秒、root/医助/管理豁免、混合字符串医生角色、新旧桌面 UA、浏览器头优先级以及只读/收尾路由。实际 cutoff 输出 `2026-10-25T00:00:00+08:00`。 |
集成补充:主代理随后回报修改后的 **20 项前端回归全部通过**,包括真实接诊台 setup/template 与 Hook 的医生停用、签名跨截止、root/医助继续可用;这些新增验证由主代理执行。
额外发现:`server/tests/DoctorWebConsultationPolicyTest.php:57`–`:70` 仍把真实默认配置断言写死为 **2026-10-24**,而当前配置是 **2026-10-25**。即使解决本机 `is_readable()` 问题,这个默认日期断言也会不匹配。该测试应使用注入的固定 fixture 验证固定日期,另从实际默认配置派生预期值验证热加载;本审计按只读分工未修复。
## 建议最小回归集
1. 保留现有纯策略和 Hook 测试:截止前/当刻、服务器时间偏移、配置刷新失败恢复、root 和医助豁免、混合医生角色。它们已经覆盖核心逻辑,不必重复新增同义单元测试。
2. 增加真实接诊台 SFC 的患者行回归:医生过期时按钮实际 disabled 且明确显示“已停用”及桌面提示;直接调用 `handleCall` 不请求签名、不打开聊天;管理员和医助按钮保留可用。
3. 在同一接诊台测试中覆盖一个异步边界:取签名时尚可用、返回前截止,返回后不打开聊天。这是当前已有代码但之前没有接诊台专属测试的保护点。
4. 页面组合测试更新为患者列表不含整块公告;接诊台和预约列表按当前产品需求保留。原有“所有页面必须有公告”的编译断言应随主代理修改调整,不能只删除模板而保留旧断言。
5. 重跑上述两份前端测试;后端规则未改,无需扩大服务端改动。报告完整 PHP 测试的本机可读性限制和默认日期断言问题,避免把它记为已通过。
审计产物仅本文件。未更改其他文件。
+1 -1
View File
@@ -10,7 +10,7 @@
* 修改本文件后下一次接口请求生效,无须重新打包前端或清理框架配置缓存。
*/
return [
'cutoff_at' => '2026-10-25 00:00:00',
'cutoff_at' => '2026-10-28 00:00:00',
'approaching_hours' => 48,
'urgent_hours' => 24,
];