feat(signing): 前端生成签署后 PDF 原型

This commit is contained in:
yelan
2026-09-16 17:24:02 +08:00
parent 2279f7a624
commit 51ec0f393d
6 changed files with 487 additions and 1 deletions
@@ -164,6 +164,13 @@ onMounted(() => {
applyStoredValues()
syncFromDom()
})
/** 供「导出 PDF」使用:拿到已渲染的文书容器(含固定页眉与签署态取值) */
function getDocumentElement() {
return containerRef.value
}
defineExpose({ getDocumentElement })
</script>
<template>
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { ref } from 'vue'
import type {
SigningAnswers,
SigningArtifact,
@@ -33,6 +35,15 @@ const emit = defineEmits<{
selectionChange: [items: string[]]
fieldChange: [answers: SigningAnswers, progress: SigningFieldProgress]
}>()
const documentRef = ref<InstanceType<typeof SigningDocumentInteractive> | null>(null)
/** 供父级「导出 PDF」使用:转发到实际渲染文书的那一层 */
function getDocumentElement() {
return documentRef.value?.getDocumentElement() ?? null
}
defineExpose({ getDocumentElement })
</script>
<template>
@@ -150,6 +161,7 @@ const emit = defineEmits<{
<!-- 任务创建时固化了文书 HTML 的走交互化渲染;老任务回退到静态纸样预览 -->
<SigningDocumentInteractive
v-if="task.documentContentHtml"
ref="documentRef"
:task="task"
:interactive="allowLocalSigning && task.status !== 'signed' && task.status !== 'void'"
@selection-change="emit('selectionChange', $event)"
@@ -34,6 +34,7 @@ import type {
import NewSigningTaskDialog from '@/components/signing/NewSigningTaskDialog.vue'
import { useAppStore } from '@/stores/app'
import type { SigningFieldProgress } from '@/utils/signing-document'
import { downloadSigningPdf } from '@/utils/signing-pdf'
import OnlineSigningDialog from './components/OnlineSigningDialog.vue'
import SignaturePadDialog from './components/SignaturePadDialog.vue'
@@ -106,6 +107,8 @@ const newDialogVisible = ref(false)
const signatureDialogVisible = ref(false)
const onlineDialogVisible = ref(false)
const voidDialogVisible = ref(false)
const taskDetailRef = ref<InstanceType<typeof SigningTaskDetail> | null>(null)
const pdfExporting = ref(false)
let listRequestId = 0
let detailRequestId = 0
@@ -467,7 +470,7 @@ async function handleTaskAction(action: SigningTaskAction) {
if (artifact) {
await downloadArtifact(artifact)
} else if (isSigningMockEnabled) {
ElMessage.info('原型环境不生成 PDF,可在下方「签署文件」中下载签名原图')
await exportSignedPdf()
} else {
ElMessage.info('当前任务暂无签署后 PDF')
}
@@ -530,6 +533,36 @@ async function handleTaskAction(action: SigningTaskAction) {
}
}
/**
* 原型阶段:后端没有签署后 PDF 时,前端按当前渲染结果现场生成一份。
* 真实实现里 PDF 由服务端生成(正式文书以服务端产物为准),
* 这里只是让前端能先看到成品效果,因此仅在演示模式下兜底。
*/
async function exportSignedPdf() {
const task = selectedTask.value
const element = taskDetailRef.value?.getDocumentElement()
if (!task || !element) {
ElMessage.info('该任务没有固化的文书正文,无法导出 PDF')
return
}
if (pdfExporting.value) {
return
}
pdfExporting.value = true
try {
await downloadSigningPdf({ element, fileName: `${task.documentName}-${task.id}` })
ElMessage.success('签署后 PDF 已生成')
} catch {
ElMessage.error('PDF 生成失败,请稍后重试')
} finally {
pdfExporting.value = false
}
}
async function downloadArtifact(artifact: SigningArtifact) {
try {
const blob = await downloadSigningArtifact(artifact.id)
@@ -691,6 +724,7 @@ onMounted(() => {
@page-change="handlePageChange"
/>
<SigningTaskDetail
ref="taskDetailRef"
:task="selectedTask"
:loading="detailLoading"
:audit-events="taskEvents"