From a61946b520575e5c3f9d01c7a6a3dfd018f9129f Mon Sep 17 00:00:00 2001 From: yelan Date: Thu, 27 Aug 2026 17:08:51 +0800 Subject: [PATCH] feat(home): implement workbench dashboard --- clinical-web/src/api/workbench/home.ts | 283 +++++++++++++-- clinical-web/src/api/workbench/types.ts | 47 ++- .../home/components/DocumentRankingPanel.vue | 247 ++++++++++++++ .../home/components/HomeMetricCard.vue | 179 ++++++++++ .../home/components/SigningTrendPanel.vue | 195 +++++++++++ .../workbench/home/components/TodoPanel.vue | 215 ++++++++++++ .../src/views/workbench/home/index.vue | 323 +++++++++++++++++- .../src/views/workbench/home/types.ts | 36 +- 8 files changed, 1477 insertions(+), 48 deletions(-) create mode 100644 clinical-web/src/views/workbench/home/components/DocumentRankingPanel.vue create mode 100644 clinical-web/src/views/workbench/home/components/HomeMetricCard.vue create mode 100644 clinical-web/src/views/workbench/home/components/SigningTrendPanel.vue create mode 100644 clinical-web/src/views/workbench/home/components/TodoPanel.vue diff --git a/clinical-web/src/api/workbench/home.ts b/clinical-web/src/api/workbench/home.ts index 1c1a848..6a54ed5 100644 --- a/clinical-web/src/api/workbench/home.ts +++ b/clinical-web/src/api/workbench/home.ts @@ -1,42 +1,263 @@ import { request } from '@/utils/request' -import type { WorkbenchOverviewResponse } from './types' +import type { + HomeRankingPeriod, + WorkbenchCampus, + WorkbenchDocumentRanking, + WorkbenchOverviewQuery, + WorkbenchOverviewResponse, + WorkbenchTodoTask, + WorkbenchTrendPoint, +} from './types' const useMockData = import.meta.env.VITE_USE_MOCK !== 'false' -const mockOverview: WorkbenchOverviewResponse = { - summary: { - pendingTasks: 18, - todaySigned: 36, - abnormalTasks: 2, - documentCount: 128, - }, - recentTasks: [ - { - id: 'task-001', - patientName: '李某某', - visitNo: 'ZY20260827001', - documentName: '住院患者知情同意书', - department: '消化内科', - status: 'pending', - updatedAt: '2026-08-27 09:30', - }, - { - id: 'task-002', - patientName: '王某某', - visitNo: 'JZ20260827018', - documentName: '急诊留观知情同意书', - department: '儿科急诊', - status: 'signed', - updatedAt: '2026-08-27 09:12', - }, - ], +interface MockCampusOverview { + summary: WorkbenchOverviewResponse['summary'] + trend: WorkbenchTrendPoint[] + todos: WorkbenchTodoTask[] + ranking: Array & { baseCount: number }> } -export function getWorkbenchOverview(): Promise { +const mockCampusOverviews: Record = { + 本部院区: { + summary: { + todaySigned: 36, + pendingPatientSigning: 18, + todayOverdue: 2, + availableTemplates: 128, + coveredDepartments: 9, + }, + trend: [ + { label: '8/21', value: 24 }, + { label: '8/22', value: 31 }, + { label: '8/23', value: 28 }, + { label: '8/24', value: 35 }, + { label: '8/25', value: 32 }, + { label: '8/26', value: 39 }, + { label: '8/27', value: 36 }, + ], + todos: [ + { + id: 'T0818003', + patientId: 'P202610005', + patientName: '陈志强', + documentName: '住院须知及告知书', + department: '消化内科', + status: 'pending', + method: 'pad', + updatedAt: '2026-08-27 11:20', + }, + { + id: 'T0818002', + patientId: 'P202610002', + patientName: '李淑芬', + documentName: 'MRI 磁共振检查知情同意书', + department: '放射科', + status: 'pending', + method: 'sms', + updatedAt: '2026-08-27 10:05', + }, + { + id: 'T0815001', + patientId: 'P202610001', + patientName: '王建国', + documentName: '心包穿刺知情同意书', + department: '心内科', + status: 'overdue', + method: 'sms', + updatedAt: '2026-08-27 09:40', + }, + ], + ranking: [ + { id: 'doc-001', documentName: '住院患者知情同意书', department: '全院通用', baseCount: 58 }, + { id: 'doc-002', documentName: '入院须知及告知书', department: '全院通用', baseCount: 51 }, + { + id: 'doc-003', + documentName: 'MRI 磁共振检查知情同意书', + department: '放射科', + baseCount: 43, + }, + { id: 'doc-004', documentName: '手术知情同意书', department: '普外科', baseCount: 39 }, + { id: 'doc-005', documentName: '麻醉知情同意书', department: '麻醉科', baseCount: 35 }, + { id: 'doc-006', documentName: '患者授权委托书', department: '医务处', baseCount: 31 }, + { id: 'doc-007', documentName: '输血治疗知情同意书', department: '血液科', baseCount: 27 }, + { id: 'doc-008', documentName: '内镜检查知情同意书', department: '消化内科', baseCount: 24 }, + { id: 'doc-009', documentName: '介入治疗知情同意书', department: '心内科', baseCount: 21 }, + { id: 'doc-010', documentName: '特殊检查风险告知书', department: '影像科', baseCount: 18 }, + ], + }, + 东院区: { + summary: { + todaySigned: 22, + pendingPatientSigning: 11, + todayOverdue: 1, + availableTemplates: 76, + coveredDepartments: 6, + }, + trend: [ + { label: '8/21', value: 15 }, + { label: '8/22', value: 18 }, + { label: '8/23', value: 17 }, + { label: '8/24', value: 23 }, + { label: '8/25', value: 20 }, + { label: '8/26', value: 25 }, + { label: '8/27', value: 22 }, + ], + todos: [ + { + id: 'T0818001', + patientId: 'P202610004', + patientName: '赵美英', + documentName: '体检报告发放及知情同意书', + department: '健康管理中心', + status: 'pending', + method: 'pad', + updatedAt: '2026-08-27 09:12', + }, + { + id: 'T0818011', + patientId: 'P202610011', + patientName: '周雅琴', + documentName: '检查前注意事项告知书', + department: '体检中心', + status: 'overdue', + method: 'sms', + updatedAt: '2026-08-27 08:30', + }, + ], + ranking: [ + { + id: 'doc-011', + documentName: '体检报告发放及知情同意书', + department: '健康管理中心', + baseCount: 46, + }, + { id: 'doc-012', documentName: '检前告知书', department: '健康管理中心', baseCount: 39 }, + { + id: 'doc-013', + documentName: '超声检查知情同意书', + department: '超声医学科', + baseCount: 32, + }, + { + id: 'doc-014', + documentName: '胃肠镜检查知情同意书', + department: '消化内科', + baseCount: 28, + }, + { id: 'doc-015', documentName: '采血及检验告知书', department: '检验科', baseCount: 25 }, + { id: 'doc-016', documentName: '影像检查告知书', department: '影像科', baseCount: 21 }, + { id: 'doc-017', documentName: '健康体检知情同意书', department: '体检中心', baseCount: 19 }, + { id: 'doc-018', documentName: '隐私保护告知书', department: '健康管理中心', baseCount: 16 }, + { id: 'doc-019', documentName: '麻醉检查知情同意书', department: '麻醉科', baseCount: 14 }, + { id: 'doc-020', documentName: '报告领取授权书', department: '健康管理中心', baseCount: 12 }, + ], + }, + 西院区: { + summary: { + todaySigned: 17, + pendingPatientSigning: 9, + todayOverdue: 2, + availableTemplates: 54, + coveredDepartments: 5, + }, + trend: [ + { label: '8/21', value: 11 }, + { label: '8/22', value: 14 }, + { label: '8/23', value: 13 }, + { label: '8/24', value: 16 }, + { label: '8/25', value: 15 }, + { label: '8/26', value: 19 }, + { label: '8/27', value: 17 }, + ], + todos: [ + { + id: 'T0818021', + patientId: 'P202610021', + patientName: '孙浩然', + documentName: '心脏介入手术知情同意书', + department: '心内科', + status: 'pending', + method: 'pad', + updatedAt: '2026-08-27 10:15', + }, + { + id: 'T0818022', + patientId: 'P202610022', + patientName: '刘晓琴', + documentName: '骨科手术知情同意书', + department: '骨科', + status: 'pending', + method: 'sms', + updatedAt: '2026-08-27 09:52', + }, + { + id: 'T0818023', + patientId: 'P202610023', + patientName: '何文博', + documentName: '术前麻醉访视告知书', + department: '麻醉科', + status: 'overdue', + method: 'pad', + updatedAt: '2026-08-27 08:05', + }, + ], + ranking: [ + { + id: 'doc-021', + documentName: '心脏介入手术知情同意书', + department: '心内科', + baseCount: 37, + }, + { id: 'doc-022', documentName: '骨科手术知情同意书', department: '骨科', baseCount: 33 }, + { id: 'doc-023', documentName: '术前麻醉访视告知书', department: '麻醉科', baseCount: 28 }, + { id: 'doc-024', documentName: '住院患者知情同意书', department: '全院通用', baseCount: 25 }, + { id: 'doc-025', documentName: '输血治疗知情同意书', department: '血液科', baseCount: 22 }, + { id: 'doc-026', documentName: '介入治疗知情同意书', department: '心内科', baseCount: 20 }, + { id: 'doc-027', documentName: '术后康复告知书', department: '骨科', baseCount: 18 }, + { id: 'doc-028', documentName: '特殊检查风险告知书', department: '影像科', baseCount: 16 }, + { id: 'doc-029', documentName: '患者授权委托书', department: '医务处', baseCount: 14 }, + { id: 'doc-030', documentName: '手术部位标识确认书', department: '手术部', baseCount: 12 }, + ], + }, +} + +function getPeriodFactor(period: HomeRankingPeriod) { + return period === 7 ? 1 : period === 14 ? 2 : 3.5 +} + +function createRanking( + rows: MockCampusOverview['ranking'], + period: HomeRankingPeriod, +): WorkbenchDocumentRanking[] { + const factor = getPeriodFactor(period) + + return rows + .map((row, index) => ({ + id: row.id, + documentName: row.documentName, + department: row.department, + signedCount: Math.round(row.baseCount * factor + (index % 3)), + })) + .sort((left, right) => right.signedCount - left.signedCount) +} + +export function getWorkbenchOverview( + query: WorkbenchOverviewQuery, +): Promise { if (useMockData) { - return Promise.resolve(mockOverview) + const overview = mockCampusOverviews[query.campus] ?? mockCampusOverviews['本部院区'] + + return Promise.resolve({ + summary: { ...overview.summary }, + trend: overview.trend.map((point) => ({ ...point })), + todos: overview.todos.map((task) => ({ ...task })), + documentRanking: createRanking(overview.ranking, query.rankingPeriod), + }) } - return request.get('/workbench/overview') + return request.get('/workbench/overview', { + params: query, + }) } diff --git a/clinical-web/src/api/workbench/types.ts b/clinical-web/src/api/workbench/types.ts index c5ef141..50e2791 100644 --- a/clinical-web/src/api/workbench/types.ts +++ b/clinical-web/src/api/workbench/types.ts @@ -1,12 +1,22 @@ import type { PageQuery, PageResult } from '@/types/common' +export type WorkbenchCampus = '本部院区' | '东院区' | '西院区' + +export type HomeRankingPeriod = 7 | 14 | 30 + +export interface WorkbenchOverviewQuery { + campus: WorkbenchCampus + rankingPeriod: HomeRankingPeriod +} + export type SigningTaskStatus = 'pending' | 'signing' | 'signed' | 'rejected' | 'expired' export interface WorkbenchSummary { - pendingTasks: number todaySigned: number - abnormalTasks: number - documentCount: number + pendingPatientSigning: number + todayOverdue: number + availableTemplates: number + coveredDepartments: number } export interface WorkbenchTask { @@ -19,9 +29,38 @@ export interface WorkbenchTask { updatedAt: string } +export type WorkbenchTodoStatus = 'pending' | 'overdue' + +export type SigningMethod = 'pad' | 'sms' + +export interface WorkbenchTodoTask { + id: string + patientId: string + patientName: string + documentName: string + department: string + status: WorkbenchTodoStatus + method: SigningMethod + updatedAt: string +} + +export interface WorkbenchTrendPoint { + label: string + value: number +} + +export interface WorkbenchDocumentRanking { + id: string + documentName: string + department: string + signedCount: number +} + export interface WorkbenchOverviewResponse { summary: WorkbenchSummary - recentTasks: WorkbenchTask[] + trend: WorkbenchTrendPoint[] + todos: WorkbenchTodoTask[] + documentRanking: WorkbenchDocumentRanking[] } export interface SigningTaskQuery extends PageQuery { diff --git a/clinical-web/src/views/workbench/home/components/DocumentRankingPanel.vue b/clinical-web/src/views/workbench/home/components/DocumentRankingPanel.vue new file mode 100644 index 0000000..75e19fa --- /dev/null +++ b/clinical-web/src/views/workbench/home/components/DocumentRankingPanel.vue @@ -0,0 +1,247 @@ + + + + + diff --git a/clinical-web/src/views/workbench/home/components/HomeMetricCard.vue b/clinical-web/src/views/workbench/home/components/HomeMetricCard.vue new file mode 100644 index 0000000..bf99e6e --- /dev/null +++ b/clinical-web/src/views/workbench/home/components/HomeMetricCard.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/clinical-web/src/views/workbench/home/components/SigningTrendPanel.vue b/clinical-web/src/views/workbench/home/components/SigningTrendPanel.vue new file mode 100644 index 0000000..97a0d87 --- /dev/null +++ b/clinical-web/src/views/workbench/home/components/SigningTrendPanel.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/clinical-web/src/views/workbench/home/components/TodoPanel.vue b/clinical-web/src/views/workbench/home/components/TodoPanel.vue new file mode 100644 index 0000000..060c955 --- /dev/null +++ b/clinical-web/src/views/workbench/home/components/TodoPanel.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/clinical-web/src/views/workbench/home/index.vue b/clinical-web/src/views/workbench/home/index.vue index fb93302..9b95822 100644 --- a/clinical-web/src/views/workbench/home/index.vue +++ b/clinical-web/src/views/workbench/home/index.vue @@ -1,12 +1,321 @@ + + diff --git a/clinical-web/src/views/workbench/home/types.ts b/clinical-web/src/views/workbench/home/types.ts index b17b1a2..91ce3b9 100644 --- a/clinical-web/src/views/workbench/home/types.ts +++ b/clinical-web/src/views/workbench/home/types.ts @@ -1,19 +1,43 @@ export type HomeMetricTone = 'brand' | 'success' | 'warning' | 'danger' +export type HomeMetricKey = 'todaySigned' | 'pending' | 'overdue' | 'templates' + +export type HomeMetricDetailTone = 'up' | 'muted' | 'down' + export interface HomeMetricCard { - key: string + key: HomeMetricKey label: string value: number - unit: string tone: HomeMetricTone - description?: string + detail: string + detailTone: HomeMetricDetailTone } -export interface HomeTaskItem { +export interface HomeTrendPoint { + label: string + value: number +} + +export type HomeTodoStatus = 'pending' | 'overdue' + +export type HomeSigningMethod = 'pad' | 'sms' + +export interface HomeTodoItem { id: string - title: string + patientId: string patientName: string documentName: string - status: string + department: string + method: HomeSigningMethod + status: HomeTodoStatus updatedAt: string } + +export interface HomeDocumentRanking { + id: string + documentName: string + department: string + signedCount: number +} + +export type HomeRankingPeriod = 7 | 14 | 30