feat(患者签署): 接入签署上下文接口并补齐文书渲染与手写签名链路

This commit is contained in:
yelan
2026-09-28 10:38:22 +08:00
parent d711e03a4e
commit 28c45dc7b2
13 changed files with 1126 additions and 277 deletions
+16
View File
@@ -5,6 +5,7 @@ import type {
ApiResponse,
SignatureUploadInput,
SignatureUploadResponse,
SigningContextResponse,
TokenConsumeRequest,
TokenConsumeResponse,
} from './types'
@@ -19,6 +20,21 @@ export function consumeSigningToken(payload: TokenConsumeRequest): Promise<Token
.then(unwrapApiResponse)
}
/**
* 读取本次签署的完整上下文:患者信息 / 签署人 / 文书正文 / 医生已填结果。
*
* 契约见 `.workbuddy-ai/患者端H5真实接入方案-2026-09-28.md` 第三节。
* **后端就绪前这个调用会失败**,所以调用方必须能优雅降级 ——
* 拿不到正文就不能放行签署(见 `useSignFlow` 的 `documentError`)。
*/
export function fetchSigningContext(token: string): Promise<SigningContextResponse> {
return request
.get<ApiResponse<SigningContextResponse>>('/v1/sign-deliveries/token/context', {
params: { token },
})
.then(unwrapApiResponse)
}
function createIdempotencyKey(value?: string) {
if (value) {
return value
+87 -1
View File
@@ -6,10 +6,20 @@ export interface ApiResponse<T> {
timestamp?: string
}
export type SignDeliveryChannel = 'PAD' | 'SMS'
/**
* 投递渠道。
*
* - `PAD`:现场手写板,设备上跑的就是本 H5;
* - `SMS`:短信链接;
* - `WX`:微信公众号(2026-09-28 补 —— 原契约只有 PAD / SMS,公众号一直没落点)。
*
* 注意 **Pad 移动端签署不走这里**:那是在医护端工作台里用平板现场签的,不产生投递。
*/
export type SignDeliveryChannel = 'PAD' | 'SMS' | 'WX'
export interface TokenConsumeRequest {
token: string
/** 允许不传:渠道由 token 决定,前端不该替后端写死一个 */
channel?: SignDeliveryChannel
taskId?: string
}
@@ -23,6 +33,82 @@ export interface TokenConsumeResponse {
timestamp?: string | null
}
/** 患者端看到的任务状态。不是 `WAITING_SIGN` 时不该放行签署 */
export type SigningTaskSignStatus =
| 'WAITING_SIGN'
| 'SIGNED'
| 'EXPIRED'
| 'VOIDED'
| 'FAILED'
| string
export interface SigningContextPatient {
name?: string | null
sex?: string | null
age?: number | null
visitNo?: string | null
department?: string | null
}
/** 签署人信息。按业务口径由医生在医护端填好,患者端只展示、不修改 */
export interface SigningContextSigner {
/** `SELF` = 本人;`FAMILY` = 家属代签 */
type?: string | null
name?: string | null
relation?: string | null
}
export interface SigningContextTemplate {
name?: string | null
category?: string | null
versionNo?: string | null
}
export interface SigningContextDocument {
contentHtml?: string | null
contentSha256?: string | null
}
export interface SigningContextFieldOption {
value: string
label?: string | null
}
export interface SigningContextField {
id: string
label?: string | null
kind?: string | null
required?: boolean | null
options?: SigningContextFieldOption[] | null
}
/**
* 患者端读取本次签署的完整上下文。
*
* 与 `token/consume` 的职责**分开**:consume 是「消费一次」换投递凭证,
* 这个接口要能**重复调用**(患者刷新、返回上一步都要能重进)。
*/
export interface SigningContextResponse {
taskId: string
deliveryId?: string | null
channel?: SignDeliveryChannel | null
status?: SigningTaskSignStatus | null
expiresAt?: string | null
patient?: SigningContextPatient | null
signer?: SigningContextSigner | null
template?: SigningContextTemplate | null
document?: SigningContextDocument | null
/**
* 医生已填的结果(含医师签名图),患者端**只读**渲染进正文占位符。
*
* 刻意与 `document.contentHtml` 分开:医生填了什么要能单独追溯,
* 烙进正文 HTML 就查不出「谁在什么时候填了什么」。
*/
staffAnswers?: Record<string, string> | null
/** 患者端可交互的字段。按约定只有主签名位(`id === 'signature'`) */
fields?: SigningContextField[] | null
}
export type SignatureUploadStatus =
'CREATED' | 'WAITING_SIGN' | 'SIGNED' | 'EXPIRED' | 'VOIDED' | 'GENERATING' | 'FAILED' | string