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 算,写死行数会让长文书下半部分没水印。
This commit is contained in:
yelan
2026-09-26 19:45:40 +08:00
parent f871613abe
commit 82acc03442
11 changed files with 882 additions and 67 deletions
@@ -14,6 +14,7 @@ import { formatSigningMethodTitle, readSigningMethod } from '@/utils/signing-met
import SigningDocumentInteractive from '@/components/signing/SigningDocumentInteractive.vue'
import SigningDocumentPreview from '@/components/signing/SigningDocumentPreview.vue'
import TaskAuditTimeline from '@/components/signing/TaskAuditTimeline.vue'
import LinkedPrescriptionCard from './LinkedPrescriptionCard.vue'
import SigningArtifactList from './SigningArtifactList.vue'
import type { SigningTaskAction } from '../types'
@@ -68,6 +69,25 @@ function isOnsiteMethod(method: SigningMethod): boolean {
return method === 'pad' || method === 'ipad'
}
/**
* 线上方式(短信 / 公众号)的等待态提示(原型 renderPreview 里那条 `.tg-wait`)。
*
* 说的是「任务正等患者签」,这是任务状态本身能确认的事;
* 短信再把发送目标带出来 —— 号码取后端返回的**脱敏值**,
* 后端没给就只写「已发送」,不拿患者档案里的号码冒充发送目标。
*/
function waitingHint(task: SigningTaskRecord): string {
const { icon, short } = readSigningMethod(task.method)
if (task.method === 'wx') {
return `${icon} ${short}签署消息已推送 · 等待患者签署…`
}
const mobile = task.smsMobileMasked?.trim()
return `${icon} ${short}${mobile ? `已发送至 ${mobile}` : '已发送'} · 等待患者签署…`
}
/** 供父级「导出 PDF」使用:转发到实际渲染文书的那一层 */
function getDocumentElement() {
return documentRef.value?.getDocumentElement() ?? null
@@ -113,6 +133,8 @@ defineExpose({ getDocumentElement })
</template>
</template>
<template v-else>
<!-- 线上方式先给等待态:这条任务此刻等的是患者,不是操作人 -->
<span class="action-wait">{{ waitingHint(task) }}</span>
<button
v-if="allowLocalSigning"
type="button"
@@ -152,28 +174,48 @@ defineExpose({ getDocumentElement })
</button>
</template>
<template v-else-if="task.status === 'signed' && canDownloadArtifact">
<!-- 已签署:预览是主入口(原型主按钮就是「在线预览(PDF)」),
下载类动作在后;只读副本带水印与审计编号,对外提供时用那一份 -->
<template v-else-if="task.status === 'signed'">
<button
v-if="canViewArtifacts"
type="button"
class="action-button action-button--ghost"
@click="emit('action', 'download-pdf')"
class="action-button"
@click="emit('action', 'preview-signed')"
>
下载签署后 PDF
</button>
<button
type="button"
class="action-button action-button--ghost"
@click="emit('action', 'download-signature')"
>
签名原图
</button>
<button
type="button"
class="action-button action-button--ghost"
@click="emit('action', 'print')"
>
打印
在线预览(PDF)
</button>
<template v-if="canDownloadArtifact">
<button
type="button"
class="action-button action-button--ghost"
@click="emit('action', 'download-pdf')"
>
下载 PDF 原件
</button>
<button
type="button"
class="action-button action-button--ghost"
title="生成带水印与审计编号的副本,对外提供时使用"
@click="emit('action', 'download-readonly-copy')"
>
只读副本
</button>
<button
type="button"
class="action-button action-button--ghost"
@click="emit('action', 'download-signature')"
>
签名原图
</button>
<button
type="button"
class="action-button action-button--ghost"
@click="emit('action', 'print')"
>
打印
</button>
</template>
</template>
<button
@@ -208,12 +250,7 @@ defineExpose({ getDocumentElement })
@signature-request="emit('signatureRequest', $event)"
/>
<SigningDocumentPreview v-else :task="task" />
<TaskAuditTimeline
v-if="canViewAudit"
:events="auditEvents"
:loading="auditLoading"
:error="auditError"
/>
<!-- 顺序照原型:文书 → 签署文件(归档结果)→ 关联处方 → 操作留痕 -->
<SigningArtifactList
v-if="canViewArtifacts"
:artifacts="artifacts"
@@ -222,6 +259,13 @@ defineExpose({ getDocumentElement })
:can-download="canDownloadArtifact"
@download="emit('downloadArtifact', $event)"
/>
<LinkedPrescriptionCard :task="task" />
<TaskAuditTimeline
v-if="canViewAudit"
:events="auditEvents"
:loading="auditLoading"
:error="auditError"
/>
</div>
</template>
@@ -275,6 +319,17 @@ defineExpose({ getDocumentElement })
border-radius: 7px;
}
/* 等待态:原型 .tag.tg-wait(信息蓝),与旁边的主按钮区分开 */
.action-wait {
align-self: center;
padding: 5px 11px;
color: var(--info);
font-size: 12px;
font-weight: 700;
background: var(--info-l);
border-radius: 14px;
}
.action-button {
flex-shrink: 0;
padding: 8px 16px;