From c7da24cef6eaddb03d1879a4d1b3c0b674ecc38f Mon Sep 17 00:00:00 2001 From: yelan Date: Mon, 14 Sep 2026 15:36:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=AD=BE=E7=BD=B2):=20=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E5=B9=B6=E8=BF=98=E5=8E=9F=E6=82=A3=E8=80=85=E5=8B=BE=E9=80=89?= =?UTF-8?q?=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 签署完成时将患者勾选的条目固化到任务记录,重新打开已签署文书时还原勾选状态,重新发起签署则清空历史勾选。 --- clinical-web/src/api/workbench/signing.ts | 11 +++++- clinical-web/src/api/workbench/types.ts | 2 ++ clinical-web/src/utils/signing-document.ts | 31 +++++++++++++--- .../components/SigningDocumentInteractive.vue | 36 ++++++++++++++++++- .../signing/components/SigningTaskDetail.vue | 2 ++ .../src/views/workbench/signing/index.vue | 20 +++++++++-- 6 files changed, 93 insertions(+), 9 deletions(-) diff --git a/clinical-web/src/api/workbench/signing.ts b/clinical-web/src/api/workbench/signing.ts index ac9bd65..d7ff525 100644 --- a/clinical-web/src/api/workbench/signing.ts +++ b/clinical-web/src/api/workbench/signing.ts @@ -346,7 +346,11 @@ function clonePatient(patient: PatientProfile): PatientProfile { } function cloneTask(task: SigningTaskRecord): SigningTaskRecord { - return { ...task } + return { + ...task, + // 勾选结果是数组,浅拷贝会让调用方和内存数据共享同一个引用 + selectedItems: task.selectedItems ? [...task.selectedItems] : task.selectedItems, + } } function getRangeStart(range: SigningTaskQuery['dateRange']) { @@ -1002,6 +1006,7 @@ export function updateSigningTaskMethod( export function completeSigningTask( id: string, signatureDataUrl: string, + selectedItems: string[] = [], ): Promise { if (!useMockData) { throw new Error('真实签署需要通过签署投递与文件上传接口完成') @@ -1017,6 +1022,8 @@ export function completeSigningTask( task.signatureDataUrl = signatureDataUrl task.signedAt = formatMockDate() task.updatedAt = task.signedAt + // 勾选结果随签署一起固化,否则重新打开文书会丢掉患者勾了哪些项 + task.selectedItems = [...selectedItems] persistMockRecords() return Promise.resolve(cloneTask(task)) } @@ -1042,6 +1049,8 @@ export async function reopenSigningTask( task.status = 'pending' task.signatureDataUrl = undefined task.signedAt = undefined + // 重新发起等于重新签,上一次的勾选不再沿用 + task.selectedItems = [] task.updatedAt = formatMockDate() persistMockRecords() return Promise.resolve(cloneTask(task)) diff --git a/clinical-web/src/api/workbench/types.ts b/clinical-web/src/api/workbench/types.ts index 23b0241..0a6e740 100644 --- a/clinical-web/src/api/workbench/types.ts +++ b/clinical-web/src/api/workbench/types.ts @@ -190,6 +190,8 @@ export interface SigningTaskRecord extends WorkbenchTask { voidReason?: string /** 原型:任务创建时固化的文书 HTML(对应真实实现的模板版本快照 + contentSha256)。 */ documentContentHtml?: string + /** 原型:患者勾选结果,随签署一起留存(对应真实实现里随签署证据提交的勾选摘要)。 */ + selectedItems?: string[] } export type SigningTaskListResponse = PageResult diff --git a/clinical-web/src/utils/signing-document.ts b/clinical-web/src/utils/signing-document.ts index c9d3792..d4a36d2 100644 --- a/clinical-web/src/utils/signing-document.ts +++ b/clinical-web/src/utils/signing-document.ts @@ -21,6 +21,8 @@ export interface SigningDocumentData { visitDate: string signedAt?: string signatureDataUrl?: string + /** 已勾选的条目名:签署完成后重新打开文书时用来还原勾选状态 */ + selectedItems?: string[] } const SIGNATURE_PLACEHOLDER = '{患者签名}' @@ -105,7 +107,7 @@ function fillPatientPlaceholders(root: Element, data: SigningDocumentData) { }) } -function convertCheckboxes(root: Element, interactive: boolean) { +function convertCheckboxes(root: Element, interactive: boolean, selected: Set) { root.querySelectorAll('table').forEach((table) => { // 不能只认 :WangEditor 的表格节点是 ,它导出时不会生成 // thead,粘贴带 thead 的表格时其 preParseHtml 也把 thead 拆掉;.docx 经 mammoth @@ -148,6 +150,13 @@ function convertCheckboxes(root: Element, interactive: boolean) { checkbox.setAttribute('data-sign-item', itemName) checkbox.disabled = !interactive + // 必须写成 attribute:innerHTML 只序列化 HTML 属性,只设 .checked 这个 + // property 会在生成 HTML 时被丢掉,渲染回来就成了未勾选 + if (selected.has(itemName)) { + checkbox.checked = true + checkbox.setAttribute('checked', '') + } + checkCell.textContent = '' checkCell.appendChild(checkbox) }) @@ -180,6 +189,7 @@ function replaceOptionMarkers( type: 'radio' | 'checkbox', groupName: string, interactive: boolean, + selected: Set, ) { const parts = (textNode.textContent ?? '').split(OPTION_SPLIT_PATTERN) const fragment = document.createDocumentFragment() @@ -206,6 +216,12 @@ function replaceOptionMarkers( input.disabled = !interactive input.setAttribute('data-sign-item', label) + // 同表格勾选:只设 property 不会出现在生成的 HTML 里,必须落成属性 + if (label !== '' && selected.has(label)) { + input.checked = true + input.setAttribute('checked', '') + } + if (type === 'radio') { input.name = groupName input.value = label @@ -234,7 +250,7 @@ function replaceOptionMarkers( * 同一段/同一单元格里出现两个及以上方框时按「二选一」处理成互斥单选; * 只有一个方框则保持多选语义,避免把「□高血压」这类单项确认误判成互斥组。 */ -function convertInlineOptions(root: Element, interactive: boolean) { +function convertInlineOptions(root: Element, interactive: boolean, selected: Set) { const groups = new Map() collectOptionTextNodes(root).forEach((textNode) => { @@ -261,7 +277,9 @@ function convertInlineOptions(root: Element, interactive: boolean) { const type = markerCount > 1 ? 'radio' : 'checkbox' const groupName = `sign-option-${groupSeq}` - textNodes.forEach((textNode) => replaceOptionMarkers(textNode, type, groupName, interactive)) + textNodes.forEach((textNode) => + replaceOptionMarkers(textNode, type, groupName, interactive, selected), + ) }) } @@ -297,8 +315,11 @@ export function buildSigningDocumentHtml( replaceTextPlaceholder(root, SIGNATURE_PLACEHOLDER, '(待患者签署)') } - convertCheckboxes(root, interactive) - convertInlineOptions(root, interactive) + // 已签署的文书再次打开时,把上次记录的勾选状态还原出来 + const selected = new Set(data.selectedItems ?? []) + + convertCheckboxes(root, interactive, selected) + convertInlineOptions(root, interactive, selected) return root.innerHTML } diff --git a/clinical-web/src/views/workbench/signing/components/SigningDocumentInteractive.vue b/clinical-web/src/views/workbench/signing/components/SigningDocumentInteractive.vue index 8c576bb..48adf0f 100644 --- a/clinical-web/src/views/workbench/signing/components/SigningDocumentInteractive.vue +++ b/clinical-web/src/views/workbench/signing/components/SigningDocumentInteractive.vue @@ -1,5 +1,5 @@ @@ -144,6 +145,7 @@ const emit = defineEmits<{ v-if="task.documentContentHtml" :task="task" :interactive="allowLocalSigning && task.status !== 'signed' && task.status !== 'void'" + @selection-change="emit('selectionChange', $event)" /> diff --git a/clinical-web/src/views/workbench/signing/index.vue b/clinical-web/src/views/workbench/signing/index.vue index afe13f3..488a18d 100644 --- a/clinical-web/src/views/workbench/signing/index.vue +++ b/clinical-web/src/views/workbench/signing/index.vue @@ -80,6 +80,8 @@ const page = ref(1) const pageSize = ref(20) const selectedTaskId = ref(null) const selectedTask = ref(null) +/** 当前任务里患者已勾选的条目,签署完成时随签名一起固化到任务上 */ +const signingSelection = ref([]) const taskEvents = ref([]) const artifacts = ref([]) const templates = ref([]) @@ -213,6 +215,9 @@ async function loadOrganizationOptions() { async function loadTaskDetail(id: string | null) { const requestId = ++detailRequestId + // 换任务时先清空勾选,避免把上一个任务的勾选带到新任务上; + // 文书组件挂载后会用任务自身的勾选重新上报 + signingSelection.value = [] if (!id) { selectedTask.value = null @@ -339,6 +344,10 @@ function selectTask(id: string) { void loadTaskDetail(id) } +function handleSelectionChange(items: string[]) { + signingSelection.value = items +} + function handlePageChange(nextPage: number) { const pageCount = Math.max(Math.ceil(total.value / pageSize.value), 1) @@ -504,7 +513,10 @@ async function confirmPadSignature(signatureDataUrl: string) { } try { - await refreshTask(await completeSigningTask(task.id, signatureDataUrl), '手写签署已完成') + await refreshTask( + await completeSigningTask(task.id, signatureDataUrl, signingSelection.value), + '手写签署已完成', + ) signatureDialogVisible.value = false } catch { ElMessage.error('手写签署失败,请稍后重试') @@ -519,7 +531,10 @@ async function completeOnlineSignature(signatureDataUrl: string) { } try { - await refreshTask(await completeSigningTask(task.id, signatureDataUrl), '线上签署已完成') + await refreshTask( + await completeSigningTask(task.id, signatureDataUrl, signingSelection.value), + '线上签署已完成', + ) onlineDialogVisible.value = false } catch { ElMessage.error('线上签署失败,请稍后重试') @@ -639,6 +654,7 @@ onMounted(() => { :pad-session-loading="padSessionLoading" @action="handleTaskAction" @download-artifact="downloadArtifact" + @selection-change="handleSelectionChange" />