import { getCampuses } from '@/api/management/organization' import { unwrapApiResponse } from '@/utils/api-response' import { mockFlags } from '@/utils/mock-flags' import { request } from '@/utils/request' import type { ApiResponse } from '@/types/common' import type { BackendSigningMethod, BackendSigningTaskStatus, HomeRankingPeriod, SigningMethod, WorkbenchCampus, WorkbenchDocumentRanking, WorkbenchOverviewQuery, WorkbenchOverviewRequestDto, WorkbenchOverviewResponse, WorkbenchOverviewResponseDto, WorkbenchTodoStatus, WorkbenchTodoTask, WorkbenchTrendPoint, } from './types' const useMockData = mockFlags.workbenchHome interface MockCampusOverview { summary: WorkbenchOverviewResponse['summary'] trend: WorkbenchTrendPoint[] todos: WorkbenchTodoTask[] ranking: Array & { baseCount: number }> } const mockCampusOverviews: Record = { 本部院区: { summary: { todaySigned: 36, todaySignedChange: 2, 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', }, { // Pad 移动端:待办里要四种方式都有样本,徽标配色才看得出区别 id: 'T0818004', patientId: 'P202610003', patientName: '赵某某', documentName: '住院须知及告知书', department: '消化内科', status: 'pending', method: 'ipad', updatedAt: '2026-08-27 09:05', }, { id: 'T0818005', patientId: 'P202610007', patientName: '孙浩然', documentName: '内镜检查知情同意书', department: '普外科', status: 'pending', method: 'wx', updatedAt: '2026-08-27 08:50', }, ], 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, todaySignedChange: 1, 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', }, { id: 'T0818012', patientId: 'P202610003', patientName: '赵某某', documentName: '住院须知及告知书', department: '消化内科', status: 'pending', method: 'ipad', updatedAt: '2026-08-27 09:05', }, { id: 'T0818013', patientId: 'P202610007', patientName: '孙浩然', documentName: '内镜检查知情同意书', department: '普外科', status: 'pending', method: 'wx', updatedAt: '2026-08-27 08:50', }, ], 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, todaySignedChange: 2, 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', }, { id: 'T0818024', patientId: 'P202610003', patientName: '赵某某', documentName: '住院须知及告知书', department: '消化内科', status: 'pending', method: 'ipad', updatedAt: '2026-08-27 09:05', }, { id: 'T0818025', patientId: 'P202610007', patientName: '孙浩然', documentName: '内镜检查知情同意书', department: '普外科', status: 'pending', method: 'wx', updatedAt: '2026-08-27 08:50', }, ], 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) } const TODO_LIMIT = 8 const RANKING_LIMIT = 10 let campusIds: Map | null = null let campusIdsRequest: Promise> | null = null function mapTodoStatus(status: BackendSigningTaskStatus): WorkbenchTodoStatus { switch (status) { case 'CREATED': return 'created' case 'WAITING_SIGN': return 'pending' case 'GENERATING': return 'generating' case 'EXPIRED': return 'overdue' case 'SIGNED': return 'signed' case 'VOIDED': return 'voided' case 'FAILED': default: return 'failed' } } const METHOD_BY_BACKEND: Record = { PAD: 'pad', SMS: 'sms', IPAD: 'ipad', WX: 'wx', } function mapSigningMethod(method: BackendSigningMethod): SigningMethod { // 后端理论上只回这四个;未知取值按手写板兜底,避免整行渲染不出来 return METHOD_BY_BACKEND[method] ?? 'pad' } function mapWorkbenchOverview(dto: WorkbenchOverviewResponseDto): WorkbenchOverviewResponse { const summary = dto.summary return { summary: { todaySigned: summary?.todaySigned ?? 0, todaySignedChange: summary?.todaySignedChange ?? 0, pendingPatientSigning: summary?.pendingPatientSigning ?? 0, todayOverdue: summary?.todayOverdue ?? 0, availableTemplates: summary?.availableTemplates ?? 0, coveredDepartments: summary?.coveredDepartments ?? 0, }, trend: (dto.trend ?? []).map((point) => ({ label: point.label || point.date, value: point.signedCount, })), todos: (dto.todos ?? []).map((task) => ({ id: task.taskId, patientId: task.patientId, patientName: task.patientName, documentName: task.documentName, department: task.departmentName, status: mapTodoStatus(task.status), method: mapSigningMethod(task.signMethod), updatedAt: task.updatedAt, })), documentRanking: (dto.documentRanking ?? []).map((item) => ({ id: item.templateId, documentName: item.documentName, department: item.departmentName, signedCount: item.signedCount, })), } } function getCampusIds() { if (campusIds) { return Promise.resolve(campusIds) } if (!campusIdsRequest) { campusIdsRequest = getCampuses() .then((campuses) => { campusIds = new Map(campuses.map((campus) => [campus.name, campus.id])) return campusIds }) .finally(() => { campusIdsRequest = null }) } return campusIdsRequest } async function resolveCampusId(campusName: WorkbenchCampus) { const campusId = (await getCampusIds()).get(campusName) if (!campusId) { throw new Error('未找到院区:' + campusName) } return campusId } export async function getWorkbenchOverview( query: WorkbenchOverviewQuery, ): Promise { if (useMockData) { const overview = mockCampusOverviews[query.campus] ?? mockCampusOverviews['本部院区'] return { summary: { ...overview.summary }, trend: overview.trend.map((point) => ({ ...point })), todos: overview.todos.map((task) => ({ ...task })), documentRanking: createRanking(overview.ranking, query.rankingPeriod), } } const campusId = query.campusId || (await resolveCampusId(query.campus)) const params: WorkbenchOverviewRequestDto = { campusId, period: query.rankingPeriod, todoLimit: TODO_LIMIT, rankingLimit: RANKING_LIMIT, } const response = await request.get>( '/v1/workbench/overview', { params }, ) return mapWorkbenchOverview(unwrapApiResponse(response)) }