From c1eee2d28ae90c5e01ff75ae02eb822d87f469d2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 17 Aug 2026 09:06:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/first_visit/conversion/index.vue | 184 +- .../stats/performance-dashboard/index.vue | 93 +- admin/tsconfig.tsbuildinfo | 2 +- app/artifacts/_design_crops/ai.png | Bin 0 -> 75870 bytes app/artifacts/_design_crops/card_selected.png | Bin 0 -> 29083 bytes app/artifacts/_design_crops/clinical.png | Bin 0 -> 110492 bytes app/artifacts/_design_crops/filters.png | Bin 0 -> 20518 bytes app/artifacts/_design_crops/full.png | Bin 0 -> 431734 bytes app/artifacts/_design_crops/hero.png | Bin 0 -> 38399 bytes app/artifacts/_design_crops/q_ai.png | Bin 0 -> 292474 bytes app/artifacts/_design_crops/q_card.png | Bin 0 -> 53470 bytes app/artifacts/_design_crops/q_clinical.png | Bin 0 -> 404662 bytes app/artifacts/_design_crops/q_filters.png | Bin 0 -> 27227 bytes app/artifacts/_design_crops/q_hero.png | Bin 0 -> 104793 bytes app/artifacts/_design_crops/q_history.png | Bin 0 -> 162871 bytes app/artifacts/_design_crops/q_labs.png | Bin 0 -> 120269 bytes app/artifacts/_design_crops/q_queue.png | Bin 0 -> 223451 bytes app/artifacts/_design_crops/q_sidebar.png | Bin 0 -> 170322 bytes app/artifacts/_design_crops/q_topbar.png | Bin 0 -> 85956 bytes app/artifacts/_design_crops/q_vitals.png | Bin 0 -> 78511 bytes app/artifacts/_design_crops/queue.png | Bin 0 -> 114453 bytes app/artifacts/_design_crops/render_ai.png | Bin 0 -> 76264 bytes app/artifacts/_design_crops/render_hero.png | Bin 0 -> 17012 bytes .../_design_crops/render_history.png | Bin 0 -> 16677 bytes app/artifacts/_design_crops/render_labs.png | Bin 0 -> 12505 bytes app/artifacts/_design_crops/render_queue.png | Bin 0 -> 15361 bytes app/artifacts/_design_crops/render_vitals.png | Bin 0 -> 13654 bytes app/artifacts/_design_crops/sidebar.png | Bin 0 -> 67939 bytes app/artifacts/_design_crops/topbar.png | Bin 0 -> 31255 bytes .../reception_ai_exact/reception.png | Bin 185109 -> 192769 bytes .../services/mock_repository.py | 693 +++---- .../ui/pages/appointments.py | 2 +- .../doctor_workstation/ui/pages/reception.py | 1742 +++++++++++++---- app/src/doctor_workstation/ui/shell.py | 52 +- app/tests/test_patient_ai_report_desktop.py | 1 + app/tests/test_reception_parity_ui.py | 201 +- app/tests/test_shell_contract.py | 6 +- q.png | Bin 0 -> 1239186 bytes server/app/adminapi/logic/auth/AdminLogic.php | 48 +- .../firstvisit/FirstVisitConversionLogic.php | 126 +- .../logic/stats/PerformanceDashboardLogic.php | 834 ++++++-- .../logic/stats/PerformanceDashboardScope.php | 407 ++++ server/app/common/cache/AdminAuthCache.php | 7 +- .../service/DataScope/DataScopeService.php | 64 +- server/config/project.php | 3 +- server/tests/AdminMultiRoleRegressionTest.php | 56 + server/tests/DataScopeMultiRoleTest.php | 50 + .../tests/FirstVisitConversionRankingTest.php | 91 + 48 files changed, 3646 insertions(+), 1016 deletions(-) create mode 100644 app/artifacts/_design_crops/ai.png create mode 100644 app/artifacts/_design_crops/card_selected.png create mode 100644 app/artifacts/_design_crops/clinical.png create mode 100644 app/artifacts/_design_crops/filters.png create mode 100644 app/artifacts/_design_crops/full.png create mode 100644 app/artifacts/_design_crops/hero.png create mode 100644 app/artifacts/_design_crops/q_ai.png create mode 100644 app/artifacts/_design_crops/q_card.png create mode 100644 app/artifacts/_design_crops/q_clinical.png create mode 100644 app/artifacts/_design_crops/q_filters.png create mode 100644 app/artifacts/_design_crops/q_hero.png create mode 100644 app/artifacts/_design_crops/q_history.png create mode 100644 app/artifacts/_design_crops/q_labs.png create mode 100644 app/artifacts/_design_crops/q_queue.png create mode 100644 app/artifacts/_design_crops/q_sidebar.png create mode 100644 app/artifacts/_design_crops/q_topbar.png create mode 100644 app/artifacts/_design_crops/q_vitals.png create mode 100644 app/artifacts/_design_crops/queue.png create mode 100644 app/artifacts/_design_crops/render_ai.png create mode 100644 app/artifacts/_design_crops/render_hero.png create mode 100644 app/artifacts/_design_crops/render_history.png create mode 100644 app/artifacts/_design_crops/render_labs.png create mode 100644 app/artifacts/_design_crops/render_queue.png create mode 100644 app/artifacts/_design_crops/render_vitals.png create mode 100644 app/artifacts/_design_crops/sidebar.png create mode 100644 app/artifacts/_design_crops/topbar.png create mode 100644 q.png create mode 100644 server/app/adminapi/logic/stats/PerformanceDashboardScope.php create mode 100644 server/tests/AdminMultiRoleRegressionTest.php create mode 100644 server/tests/DataScopeMultiRoleTest.php create mode 100644 server/tests/FirstVisitConversionRankingTest.php diff --git a/admin/src/views/first_visit/conversion/index.vue b/admin/src/views/first_visit/conversion/index.vue index 756c68411..6f42661bd 100644 --- a/admin/src/views/first_visit/conversion/index.vue +++ b/admin/src/views/first_visit/conversion/index.vue @@ -103,40 +103,40 @@ -
-
-
-
-

部门订单量占比

-

按订单创建人归属,排除取消、拒收及退款

-
- 单位:单 -
-
-
- {{ item.name }} -
- {{ formatNumber(item.value) }} 单 -
-
- +
+
+
+
+

{{ rankingSubject }}订单量占比排名

+

按{{ rankingSubject }}归属统计,排除取消、拒收及退款

+
+ 单位:单 +
+
+
+ {{ index + 1 }}{{ item.name }} +
+ {{ formatNumber(item.value) }} 单{{ formatShare(item.value, totalOrderValue) }} +
+
+
-
-
-

部门金额占比

-

仅统计未取消、未拒收且未退款的有效金额

-
- 单位:元 -
-
-
- {{ item.name }} -
- {{ formatMoney(item.value) }} -
-
+
+
+

{{ rankingSubject }}金额占比排名

+

按{{ rankingSubject }}归属统计,仅含未取消、未拒收且未退款的有效金额

+
+ 单位:元 +
+
+
+ {{ index + 1 }}{{ item.name }} +
+ {{ formatMoney(item.value) }}{{ formatShare(item.value, totalAmountValue) }} +
+
@@ -156,7 +156,7 @@ default-expand-all class="detail-table" > - + - - - - - - - + + + + + + + - + - + - + - + - + - + - + @@ -291,9 +291,9 @@ type MediaChannelOption = { const emptyDashboard = () => ({ meta: { - time_type: 'today', time_label: '今日', start_date: '', end_date: '', generated_at: '', - scope_value: 4, scope_label: '', selected_dept_name: '', selected_assistant_name: '', - selected_media_channel_code: '', selected_media_channel_name: '', open_count_source: '' + time_type: 'today', time_label: '今日', start_date: '', end_date: '', generated_at: '', + scope_value: 4, scope_label: '', selected_dept_name: '', selected_assistant_name: '', + selected_media_channel_code: '', selected_media_channel_name: '', open_count_source: '', ranking_kind: 'hidden' }, filters: { departments: [] as any[], @@ -364,8 +364,13 @@ const mediaChannelGroups = computed(() => { } return Array.from(groups.values()) }) -const maxOrderValue = computed(() => Math.max(0, ...dashboard.rankings.orders.map(item => Number(item.value || 0)))) -const maxAmountValue = computed(() => Math.max(0, ...dashboard.rankings.amounts.map(item => Number(item.value || 0)))) +const rankingKind = computed(() => dashboard.meta.ranking_kind || ( + Number(dashboard.meta.scope_value) === 4 ? 'hidden' : Number(dashboard.meta.scope_value) === 3 ? 'member' : 'group' +)) +const showRankings = computed(() => rankingKind.value !== 'hidden') +const rankingSubject = computed(() => rankingKind.value === 'member' ? '组内成员' : '小组') +const totalOrderValue = computed(() => dashboard.rankings.orders.reduce((total, item) => total + Number(item.value || 0), 0)) +const totalAmountValue = computed(() => dashboard.rankings.amounts.reduce((total, item) => total + Number(item.value || 0), 0)) const targetChartOption = computed(() => ({ animationDuration: 450, color: ['#0f9185', '#2f78df'], @@ -447,9 +452,14 @@ function formatMoney(value: any) { return `¥${Number(value || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` } -function formatPercent(value: any) { - return `${Number(value || 0).toFixed(1)}%` -} +function formatPercent(value: any) { + return `${Number(value || 0).toFixed(1)}%` +} + +function formatShare(value: any, total: number) { + if (total <= 0) return '0.0%' + return `${(Number(value || 0) / total * 100).toFixed(1)}%` +} function nullablePercent(value: any) { return value === null || value === undefined ? '未设置' : formatPercent(value) @@ -464,10 +474,11 @@ function compactNumber(value: number) { return String(Math.round(value)) } -function barWidth(value: any, maximum: number) { - if (maximum <= 0) return '0%' - return `${Math.max(4, Math.min(100, Number(value || 0) / maximum * 100))}%` -} +function barWidth(value: any, total: number) { + const numericValue = Number(value || 0) + if (total <= 0 || numericValue <= 0) return '0%' + return `${Math.max(4, Math.min(100, numericValue / total * 100))}%` +} function progressValue(value: any) { return Math.max(0, Math.min(100, Number(value || 0))) @@ -477,22 +488,29 @@ onMounted(loadDashboard) diff --git a/admin/src/views/stats/performance-dashboard/index.vue b/admin/src/views/stats/performance-dashboard/index.vue index 7cc64e37a..ad7eea991 100644 --- a/admin/src/views/stats/performance-dashboard/index.vue +++ b/admin/src/views/stats/performance-dashboard/index.vue @@ -98,7 +98,7 @@
-
+
本人本月业绩 @@ -151,11 +151,11 @@

{{ dashboard.rankings.appointments.title }}

-

{{ appointmentRankingSubtitle }}

+

{{ appointmentRankingSubtitle }}

@@ -176,12 +176,16 @@ v-for="(item, index) in dashboard.rankings.appointments.items" :key="`appointment-${item.id}-${index}`" class="ranking-row" + :class="{ 'is-self': item.is_self }" > - {{ index + 1 }} + {{ index + 1 }}
- {{ item.name || '未命名成员' }} - {{ formatNumber(item.count) }} 挂号 + + {{ item.name || '未命名成员' }} + + + {{ formatNumber(item.count) }} {{ rankingCountUnit }}
- +

{{ dashboard.rankings.performance.title }}

-

{{ dashboard.rankings.performance.scope_label }}内今日计入业绩的业务订单

+

+ {{ performanceRankingSubtitle }} +

今日
@@ -206,11 +212,15 @@ v-for="(item, index) in dashboard.rankings.performance.items" :key="`performance-${item.id}-${index}`" class="ranking-row" + :class="{ 'is-self': item.is_self }" > - {{ index + 1 }} + {{ index + 1 }}
- {{ item.name || '未命名部门' }} + + {{ item.name || (dashboard.rankings.performance.kind === 'person' ? '未命名成员' : '未命名部门') }} + + {{ formatNumber(item.count) }} 个诊单