Files
xh-medical-sign-web/clinical-web/src/api/workbench/prescriptions.ts
T
yelan 82acc03442 feat(签署工作台): 补代签标签、关联处方卡、线上等待态与只读副本
按原型 V10.2 详情页补齐四块:

- 任务列表「代签」标签(原型 .vtag.chk 橙底):SigningTaskRecord 增加
  signerType / signerRelation,仅签署人非「本人」时显示。
  有意偏离:关系为空时不输出空括号(原型会渲染成「家属代签()」)。
- 「关联处方」可折叠卡(原型 rxCard):新建 api/workbench/prescriptions.ts
  统一处方来源,把弹窗里那份搬过来消除重复,并新增详情用的 7 列明细。
  原型两处处方口径本就不同(弹窗按就诊号哈希、详情按任务号派生),分开维护不合并。
  真实模式返回空 → 整卡不渲染,不显示空表(会被读成「系统查过了,确实没处方」)。
  顺带按原型调了详情卡片顺序:文书 → 签署文件 → 关联处方 → 操作留痕。
- 短信 / 公众号待签署的等待态(原型 .tg-wait):号码取后端 smsMobileMasked,
  没有值时不拼号码,不拿患者档案里的号码冒充发送目标。
- 已签署的「在线预览(PDF)」与「只读副本」:
  预览弹窗复用 SigningDocumentInteractive(只读态),保证预览 / 工作台 / 导出 PDF
  三处版式一致;只读副本走 signing-pdf 新增的 watermark 选项,
  在 html2canvas 克隆体上平铺斜排水印,审计编号直接用任务号。
  水印行数按克隆体 scrollHeight 算,写死行数会让长文书下半部分没水印。
2026-09-26 19:45:40 +08:00

143 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 关联处方(演示数据)。
*
* 原型 V10.2 里「关联处方」出现在两处,**两处口径本来就不一样,不要合并**:
*
* ① 新增签署任务弹窗 B 区(原型 `rxOfVisit(v)`):一行摘要,按**就诊号**哈希
* 从 `RX_POOL` 里取药,同一就诊号每次打开必须带出同一条;
* ② 签署工作台详情(原型 `taskRxs(t)`):一张 7 列的明细表,按**任务号**派生处方号,
* 三条固定药品(麻精类,与麻精知情同意书配套)。
*
* 后端没有处方接口(处方由 HIS 带出),因此两份都只在演示模式下生成;
* 真实模式一律返回空,**绝不凭空造一条看起来真实的处方**——
* 那会让医务人员以为系统真的从 HIS 拿到了这张处方。
*/
import type { PatientVisit, SigningTaskRecord } from './types'
import { isSigningMockEnabled } from './signing'
/**
* 演示处方池。原型 RX_POOL 逐条对齐。
* 只在弹窗摘要里用;工作台明细表用的是下面的 `TASK_PRESCRIPTION_ROWS`。
*/
const PRESCRIPTION_POOL = [
{ item: '碘佛醇注射液 100ml', usage: '静脉注射 · 检查用' },
{ item: '阿莫西林胶囊 0.25g×24', usage: '口服 每日3次' },
{ item: '布洛芬缓释胶囊 0.3g×20', usage: '口服 每日2次' },
{ item: '奥美拉唑肠溶片 20mg×14', usage: '口服 每日1次 · 晨服' },
{ item: '0.9%氯化钠注射液 250ml', usage: '静脉滴注 · 检查前水化' },
]
/** 弹窗摘要形态的关联处方(原型 rxOfVisit 的 `{no, desc}`) */
export interface LinkedPrescription {
no: string
description: string
}
/** 工作台明细表形态的处方行(原型 rxCard 表格的 7 列) */
export interface TaskPrescription {
/** 处方号 */
no: string
/** 处方类别,如「门诊处方」「麻醉处方」 */
category: string
/** 药品名称 */
drug: string
/** 规格 */
spec: string
/** 数量 */
quantity: string
/** 用法用量 */
usage: string
/** 状态,如「已开立」 */
status: string
}
/** 就诊号 → 稳定哈希(原型 rxOfVisit 的 seed 算法,改了口径两处会对不上) */
function hashVisitNo(visitNo: string): number {
let seed = 0
for (let index = 0; index < visitNo.length; index += 1) {
seed = (seed * 31 + visitNo.charCodeAt(index)) % 9973
}
return seed
}
/**
* 按就诊号确定性生成演示处方摘要(原型 rxOfVisit)。
*
* 用哈希而不是随机:同一就诊号每次打开弹窗必须带出同一条处方,
* 否则「关联处方」会随刷新变来变去,演示时没法解释。
*/
export function buildLinkedPrescription(visit: PatientVisit | null): LinkedPrescription | null {
if (!visit) {
return null
}
const seed = hashVisitNo(visit.visitNo)
const first = PRESCRIPTION_POOL[seed % PRESCRIPTION_POOL.length]
const second = PRESCRIPTION_POOL[(seed + 2) % PRESCRIPTION_POOL.length]
const extra = seed % 3 === 0 ? `;${second.item}(${second.usage})` : ''
return {
no: `Rx${visit.visitNo.replace(/\D/g, '').slice(-8)}`,
description: `${first.item}(${first.usage})${extra}`,
}
}
/**
* 演示处方明细(原型 taskRxs 的三条麻精类药品)。
*
* 处方号 = `Rx` + 任务号里的数字后 6 位,再拼 `-1/-2/-3`;
* 任务号里没有数字时拼出来会是 `Rx-1` 这种残缺编号,
* 这时退回用任务号本身做尾缀,保证每条处方的编号可读且唯一。
*/
const TASK_PRESCRIPTION_ROWS: ReadonlyArray<Omit<TaskPrescription, 'no'>> = [
{
category: '门诊处方',
drug: '盐酸羟考酮缓释片(精一)',
spec: '5mg×10片/盒',
quantity: '1盒',
usage: '口服,每12小时一次,整片吞服,遵医嘱',
status: '已开立',
},
{
category: '门诊处方',
drug: '地佐辛注射液',
spec: '5mg:1ml×5支/盒',
quantity: '2支',
usage: '肌内注射,必要时,单次不超过10mg',
status: '已开立',
},
{
category: '麻醉处方',
drug: '磷酸可待因片(精二)',
spec: '30mg×20片/瓶',
quantity: '1瓶',
usage: '口服,每次1片,每日3次(精神药品不得自开自用)',
status: '已开立',
},
]
/**
* 读取某个签署任务的关联处方。
*
* 真实模式返回空数组:处方由 HIS 带出,前端没有接口就拿不到,
* 详情页据此整卡不渲染(而不是显示一张空表)。
*/
export function readTaskPrescriptions(
task: Pick<SigningTaskRecord, 'id' | 'visitNo'> | null,
): TaskPrescription[] {
if (!task || !isSigningMockEnabled) {
return []
}
const digits = task.id.replace(/\D/g, '').slice(-6)
const suffix = digits || task.id.replace(/\W/g, '').slice(-6) || '0'
return TASK_PRESCRIPTION_ROWS.map((row, index) => ({
...row,
no: `Rx${suffix}-${index + 1}`,
}))
}