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:
@@ -10,6 +10,8 @@
|
||||
* 画中文页码需要内嵌字体(体积会明显变大),这一步留给服务端用真字体实现;
|
||||
* ④ 导出前对 flex 容器做一次兼容降级:html2canvas 不实现 flex 布局,
|
||||
* 填写项胶囊里的文字、输入框取值、签名图会被整块丢掉,见 applyExportFlexFallback。
|
||||
* ⑤ 「只读副本」的水印在克隆体上平铺(injectWatermark):屏幕上那份文书不出现水印,
|
||||
* 只有导出的 PDF 带;水印烙进截图,每一页都有,且改不掉文本层。
|
||||
*
|
||||
* 输出为位图 PDF:文本不可选中,仅供原型演示;正式文书以服务端生成的 PDF 为准。
|
||||
*/
|
||||
@@ -36,6 +38,70 @@ export interface SigningPdfInput {
|
||||
element: HTMLElement
|
||||
/** 文件名,不带扩展名 */
|
||||
fileName: string
|
||||
/**
|
||||
* 只读副本水印文案。
|
||||
*
|
||||
* 只有「只读副本」这类对外提供、不可作为正式凭证的副本才传;
|
||||
* 正式文书(PDF 原件)不加水印,传空即原样导出。
|
||||
* 水印直接烙进截图,因此每一页都会带上,且无法通过编辑 PDF 文本层去掉。
|
||||
*/
|
||||
watermark?: string
|
||||
}
|
||||
|
||||
/** 水印平铺的列数:5 列按 20% 步进,再左移 10% 让斜排文字盖住左右两侧 */
|
||||
const WATERMARK_COLUMNS = 5
|
||||
/** 水印行距(px):比文字高度大,斜排后不重叠 */
|
||||
const WATERMARK_ROW_GAP_PX = 90
|
||||
|
||||
/**
|
||||
* 往克隆体里平铺斜向水印。
|
||||
*
|
||||
* 必须在 `onclone` 里做:屏幕上那份文书不能出现水印,
|
||||
* 而 html2canvas 只截克隆体,所以改克隆体既不影响界面也能进 PDF。
|
||||
*
|
||||
* 行数按克隆体的实际高度算(`scrollHeight` 会强制一次重排,这里正是需要的时机),
|
||||
* 写死行数会让长文书的下半部分没有水印。
|
||||
*/
|
||||
function injectWatermark(root: HTMLElement, text: string) {
|
||||
const height = Math.max(root.scrollHeight, root.clientHeight, 1)
|
||||
const rows = Math.ceil(height / WATERMARK_ROW_GAP_PX) + 2
|
||||
|
||||
// 绝对定位的水印层要有定位祖先;容器自身多半是 static,
|
||||
// 这里只改克隆体,屏幕上的版式不受影响
|
||||
root.style.position = 'relative'
|
||||
|
||||
const overlay = document.createElement('div')
|
||||
overlay.style.cssText = [
|
||||
'position:absolute',
|
||||
'inset:0',
|
||||
'z-index:9',
|
||||
'overflow:hidden',
|
||||
'pointer-events:none',
|
||||
].join(';')
|
||||
|
||||
for (let row = 0; row < rows; row += 1) {
|
||||
for (let column = 0; column < WATERMARK_COLUMNS; column += 1) {
|
||||
const item = document.createElement('span')
|
||||
item.textContent = text
|
||||
item.style.cssText = [
|
||||
'position:absolute',
|
||||
'display:block',
|
||||
'width:280px',
|
||||
'color:rgba(14,110,140,0.13)',
|
||||
'font-size:14px',
|
||||
'font-weight:700',
|
||||
'letter-spacing:2px',
|
||||
'white-space:nowrap',
|
||||
`left:${column * 20 - 10}%`,
|
||||
`top:${row * WATERMARK_ROW_GAP_PX + 30}px`,
|
||||
'transform:rotate(-28deg)',
|
||||
'transform-origin:left top',
|
||||
].join(';')
|
||||
overlay.appendChild(item)
|
||||
}
|
||||
}
|
||||
|
||||
root.appendChild(overlay)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,6 +178,11 @@ export async function createSigningPdf(input: SigningPdfInput): Promise<Blob> {
|
||||
clonedElement.style.margin = '0'
|
||||
clonedElement.style.background = '#ffffff'
|
||||
|
||||
// 水印在量页眉之前注入:它是绝对定位层,不参与布局,不会影响页眉高度
|
||||
if (input.watermark) {
|
||||
injectWatermark(clonedElement, input.watermark)
|
||||
}
|
||||
|
||||
// 文书外壳(.sign-doc)在页面里是定高滚动卡片:overflow: hidden 会把超出一屏的内容裁掉,
|
||||
// 纸张阴影与圆角也不该出现在 PDF 页面上,这里一并抹平
|
||||
const paper = clonedElement.closest<HTMLElement>('.sign-doc')
|
||||
|
||||
Reference in New Issue
Block a user