diff --git a/clinical-web/docs/workbench-home-api.md b/clinical-web/docs/workbench-home-api.md index a15b382..bf4c857 100644 --- a/clinical-web/docs/workbench-home-api.md +++ b/clinical-web/docs/workbench-home-api.md @@ -13,16 +13,16 @@ ## 2. 已接入的请求 ```http -GET /api/v1/workbench/overview?campusId={UUID}&period={7|14|30}&todoLimit=8&rankingLimit=10 +GET /api/v1/workbench/overview?campusId={UUID}&period={7|14|30}&todoLimit=3&rankingLimit=10 X-Token: {登录令牌} ``` -| 参数 | 必填 | 前端传值 | 契约说明 | -| ------------ | ---- | ------------------------- | ------------------------------ | -| campusId | 是 | 当前工作台院区对应的 UUID | 必须在当前用户数据范围内 | -| period | 是 | 7、14 或 30 | 同时决定趋势和文档排名统计周期 | -| todoLimit | 否 | 8 | 待办返回数量,范围 1–20 | -| rankingLimit | 否 | 10 | 排名返回数量,范围 1–20 | +| 参数 | 必填 | 前端传值 | 契约说明 | +| ------------ | ---- | ------------------------- | ------------------------------------------------------------------------------------ | +| campusId | 是 | 当前工作台院区对应的 UUID | 必须在当前用户数据范围内 | +| period | 是 | 7、14 或 30 | 同时决定趋势和文档排名统计周期 | +| todoLimit | 否 | 3 | 待办返回数量,范围 1–20;原型首页展示上限是 5(`.slice(0, 5)`),仓库按布局收成 3 条 | +| rankingLimit | 否 | 10 | 排名返回数量,范围 1–20 | 请求通过 utils/request.ts 的单例发送,实际浏览器路径为 /api/v1/workbench/overview,由 Vite 开发代理转发到后端。 @@ -31,12 +31,14 @@ X-Token: {登录令牌} | 页面区域 | 后端字段 | 前端展示 | | ---------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | 四张指标卡 | summary.todaySigned、todaySignedChange、pendingPatientSigning、todayOverdue、availableTemplates、coveredDepartments | 今日签署、较昨日变化、待签署、今日超时、可用模板及覆盖科室 | -| 签署趋势 | trend[].label、trend[].signedCount | 按后端业务时区生成的近 7、14 或 30 天趋势 | +| 签署趋势 | trend[].label、trend[].signedCount | 取返回窗口**末 7 天**,固定标题「近一周签署客户趋势」 | | 我的待办 | todos[].taskId、patientId、patientName、documentName、departmentName、status、signMethod、updatedAt | 待办列表;点击 taskId 跳转签署工作台 | | 文档排名 | documentRanking[].templateId、documentName、departmentName、signedCount | Top10 文档签署排名 | 趋势和排名都直接使用后端按 signedAt、医院业务时区聚合的结果;前端不再用 createdAt 或浏览器时区重新计算。 +**趋势窗口固定近一周**(原型 V10.2 首页只有这一档,7/14/30 开关挂在 Top10 排名卡上):请求里的 `period` 同时决定趋势与排名窗口,所以前端取 `trend` 的**最后 7 天**展示,标题固定「近一周签署客户趋势」—— 选「近 30 天」时排名窗口变、趋势图仍是近一周,与原型一致。若后端后续把两个周期拆成两个参数,这里可以去掉截断。 + ## 4. 待办状态展示 后端 TodoItem.status 使用签署任务状态枚举。前端按下列方式展示,避免把生成中、失败或已作废任务误标为待签署: @@ -63,7 +65,7 @@ pendingPatientSigning 的后端口径只统计 WAITING_SIGN;CREATED 和 GENERA ## 6. 联调验收标准 - 切换院区后,请求携带新 campusId,四张指标卡、趋势、待办和排名同步更新。 -- 切换 7、14、30 天后,趋势标题、趋势数据和文档排名同步变化。 +- 切换 7、14、30 天后,**文档排名**的统计窗口与数值同步变化;趋势图保持「近一周」(末 7 天)不变,与原型一致。 - 返回空数组时,趋势、待办和排名显示各自空状态;指标卡显示 0。 - 服务端返回 GENERATING、FAILED、VOIDED 等状态时,页面标签与状态含义一致。 - Network 中不再出现首页为统计目的而循环拉取全部签署任务或模板的请求。 diff --git a/clinical-web/src/api/workbench/home.ts b/clinical-web/src/api/workbench/home.ts index d3a711c..305a0a0 100644 --- a/clinical-web/src/api/workbench/home.ts +++ b/clinical-web/src/api/workbench/home.ts @@ -24,7 +24,8 @@ const useMockData = mockFlags.workbenchHome interface MockCampusOverview { summary: WorkbenchOverviewResponse['summary'] - trend: WorkbenchTrendPoint[] + /** 趋势的日均人次基数(按「院区 + 日期」播种出每天的具体值,见 createTrend) */ + trendBase: number todos: WorkbenchTodoTask[] ranking: Array & { baseCount: number }> } @@ -39,15 +40,7 @@ const mockCampusOverviews: Record = { 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 }, - ], + trendBase: 32, todos: [ { id: 'T0818003', @@ -128,15 +121,7 @@ const mockCampusOverviews: Record = { 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 }, - ], + trendBase: 20, todos: [ { id: 'T0818001', @@ -216,15 +201,7 @@ const mockCampusOverviews: Record = { 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 }, - ], + trendBase: 15, todos: [ { id: 'T0818021', @@ -301,6 +278,62 @@ function getPeriodFactor(period: HomeRankingPeriod) { return period === 7 ? 1 : period === 14 ? 2 : 3.5 } +/** 「院区 + 日期」→ 固定种子:同一天同一院区的数值不随刷新、切换时段或翻页而变 */ +function hashSeed(text: string) { + let hash = 2166136261 + + for (let index = 0; index < text.length; index += 1) { + hash ^= text.charCodeAt(index) + hash = Math.imul(hash, 16777619) + } + + return hash >>> 0 +} + +/** mulberry32:小、快、同种子可复现(与 api/workbench/report-analytics.ts 同一套做法) */ +function createRandom(seed: number) { + let state = seed >>> 0 + + return () => { + state = (state + 0x6d2b79f5) >>> 0 + let t = state + t = Math.imul(t ^ (t >>> 15), t | 1) + t ^= t + Math.imul(t ^ (t >>> 7), t | 61) + + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} + +/** + * 演示趋势:近 period 天(含今天)的已签署人次。 + * + * 为什么不用写死的日期数组:写死 8/21–8/27 之后,演示日一过「近 7 天」窗口就全是过期日期 + * (仓库的演示数据铁律:**日期相对** + **确定性**,不要 Math.random)。 + * 末点取今日已签署,与指标卡同源 —— 原型里趋势最后一点就是今天已签署的任务数。 + */ +function createTrend( + campus: WorkbenchCampus, + summary: WorkbenchOverviewResponse['summary'], + period: HomeRankingPeriod, +): WorkbenchTrendPoint[] { + const points: WorkbenchTrendPoint[] = [] + const base = (mockCampusOverviews[campus] ?? mockCampusOverviews['本部院区']).trendBase + + for (let offset = period - 1; offset >= 0; offset -= 1) { + const day = new Date() + day.setDate(day.getDate() - offset) + const label = `${day.getMonth() + 1}/${day.getDate()}` + const value = + offset === 0 + ? summary.todaySigned + : Math.max(base + Math.round(createRandom(hashSeed(`${campus}-${label}`))() * 12 - 5), 0) + + points.push({ label, value }) + } + + return points +} + function createRanking( rows: MockCampusOverview['ranking'], period: HomeRankingPeriod, @@ -317,7 +350,15 @@ function createRanking( .sort((left, right) => right.signedCount - left.signedCount) } -const TODO_LIMIT = 8 +/** + * 首页「我的待办」最多几条。 + * + * 原型 `renderDash()` 是 `tasks.slice(0, 5)`(最多 5 条)。 + * 仓库收成 **3 条**(2026-09-30 定):趋势卡与待办卡同行,待办越多行高越高, + * 图是按宽度定高的、贴底后会在标题与图之间留大片空白 —— 3 条时两张卡高度基本齐平。 + * 该上限同时作为 `todoLimit` 传给后端,避免取回一批再丢掉。 + */ +const TODO_LIMIT = 3 const RANKING_LIMIT = 10 let campusIds: Map | null = null let campusIdsRequest: Promise> | null = null @@ -423,11 +464,12 @@ export async function getWorkbenchOverview( ): Promise { if (useMockData) { const overview = mockCampusOverviews[query.campus] ?? mockCampusOverviews['本部院区'] + const summary = { ...overview.summary } return { - summary: { ...overview.summary }, - trend: overview.trend.map((point) => ({ ...point })), - todos: overview.todos.map((task) => ({ ...task })), + summary, + trend: createTrend(query.campus, summary, query.rankingPeriod), + todos: overview.todos.slice(0, TODO_LIMIT).map((task) => ({ ...task })), documentRanking: createRanking(overview.ranking, query.rankingPeriod), } } diff --git a/clinical-web/src/assets/hospital-logo.png b/clinical-web/src/assets/hospital-logo.png new file mode 100644 index 0000000..4df2798 Binary files /dev/null and b/clinical-web/src/assets/hospital-logo.png differ diff --git a/clinical-web/src/assets/login-bg.jpg b/clinical-web/src/assets/login-bg.jpg new file mode 100644 index 0000000..2426d76 Binary files /dev/null and b/clinical-web/src/assets/login-bg.jpg differ diff --git a/clinical-web/src/components/signing/NewSigningTaskDialog.vue b/clinical-web/src/components/signing/NewSigningTaskDialog.vue index 755a81f..238528a 100644 --- a/clinical-web/src/components/signing/NewSigningTaskDialog.vue +++ b/clinical-web/src/components/signing/NewSigningTaskDialog.vue @@ -49,18 +49,10 @@ const { selectMethod, selectTemplate, selectVisit, - selectedTemplate, selectedTemplateId, selectedVisit, selectedVisitId, smsDestination, - staffAnswers, - manualFormFields, - signerType, - signerName, - signerRelation, - staffAnswerHasOption, - toggleStaffAnswerOption, submit: submitForm, submitting, suggestionOpen, @@ -478,79 +470,6 @@ watch( - -
-
- E - 签署人和文书填写项 - 按已选模板采集,提交后固化到签署任务 -
-
-
- 实际签署人 - - - -

{{ errors.signer }}

-
- -
- -

{{ errors.staffAnswers }}

-
-

- 当前模板没有需要医护端填写的正文项;患者签名和签署日期将在签署环节生成。 -

-
-