feat(workbench): 扩展签署任务管理功能

This commit is contained in:
yelan
2026-08-28 10:02:51 +08:00
parent b533ab2a58
commit 2de75ed993
14 changed files with 4287 additions and 37 deletions
@@ -0,0 +1,203 @@
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { SigningTaskRecord } from '@/api/workbench/types'
import SignatureCanvas from './SignatureCanvas.vue'
const props = defineProps<{
visible: boolean
task: SigningTaskRecord | null
}>()
const emit = defineEmits<{
close: []
confirm: [signatureDataUrl: string]
}>()
const signatureDataUrl = ref<string | null>(null)
const signatureCanvas = ref<{ clear: () => void } | null>(null)
watch(
() => props.visible,
async (visible) => {
if (visible) {
signatureDataUrl.value = null
await nextTick()
signatureCanvas.value?.clear()
}
},
)
function clearSignature() {
signatureCanvas.value?.clear()
signatureDataUrl.value = null
}
function confirmSignature() {
if (!signatureDataUrl.value) {
ElMessage.warning('请先完成患者签名')
return
}
emit('confirm', signatureDataUrl.value)
}
</script>
<template>
<Teleport to="body">
<div v-if="visible && task" class="dialog-mask" @click.self="emit('close')">
<section
class="dialog-card signature-dialog"
role="dialog"
aria-modal="true"
aria-labelledby="pad-title"
>
<header class="dialog-header">
<strong id="pad-title">手写板签署</strong>
<span>{{ task.patientName }} · {{ task.documentName }}</span>
<button type="button" class="dialog-close" aria-label="关闭" @click="emit('close')">
×
</button>
</header>
<div class="dialog-body">
<p class="dialog-hint">
请患者在下框内签名(当前为 Canvas 演示,真实手写板接入走设备适配器):
</p>
<SignatureCanvas
ref="signatureCanvas"
:height="300"
@update:data-url="signatureDataUrl = $event"
/>
</div>
<footer class="dialog-footer">
<span>签名将实时合成到文档签名域</span>
<span class="footer-spacer" />
<button type="button" class="action-button action-button--ghost" @click="clearSignature">
清空重写
</button>
<button type="button" class="action-button" @click="confirmSignature">确认签署</button>
</footer>
</section>
</div>
</Teleport>
</template>
<style scoped>
.dialog-mask {
position: fixed;
z-index: 50;
inset: 0;
display: flex;
padding: 16px;
overflow-y: auto;
background: rgb(9 40 52 / 45%);
}
.dialog-card {
width: 680px;
max-width: 96vw;
max-height: calc(100vh - 32px);
margin: auto;
background: #fff;
border-radius: 14px;
box-shadow: var(--sh-modal);
animation: dialog-pop 0.18s ease;
}
.signature-dialog {
width: 760px;
}
.dialog-header {
display: flex;
gap: 10px;
align-items: center;
padding: 12px 18px;
border-bottom: 1px solid var(--line);
}
.dialog-header strong {
font-size: 15px;
}
.dialog-header span {
overflow: hidden;
color: var(--mut);
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.dialog-close {
flex-shrink: 0;
padding: 0;
margin-left: auto;
color: var(--mut);
font-size: 20px;
line-height: 1;
background: transparent;
}
.dialog-body {
padding: 12px 18px;
}
.dialog-hint {
margin: 0 0 8px;
color: var(--mut);
font-size: 12px;
}
.dialog-footer {
display: flex;
gap: 10px;
align-items: center;
padding: 10px 18px;
color: var(--mut);
font-size: 12px;
background: #f8fbfc;
border-top: 1px solid var(--line);
border-radius: 0 0 14px 14px;
}
.footer-spacer {
flex: 1;
}
.action-button {
padding: 8px 16px;
color: #fff;
font-size: 13px;
font-weight: 700;
background: var(--brand);
border-radius: 7px;
}
.action-button:hover {
background: var(--brand-d);
}
.action-button--ghost {
color: var(--brand);
background: #fff;
border: 1px solid var(--brand);
}
.action-button--ghost:hover {
background: var(--brand-l);
}
@keyframes dialog-pop {
from {
opacity: 0;
transform: scale(0.96) translateY(8px);
}
to {
opacity: 1;
transform: none;
}
}
</style>