feat(患者签署): 接入签署上下文接口并补齐文书渲染与手写签名链路
This commit is contained in:
@@ -1,23 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
/**
|
||||
* 签署页。
|
||||
*
|
||||
* 按业务口径,**患者只签自己的名**:文书里的选项、医师签名都由医生在医护端填好,
|
||||
* 因此这一页去掉了原来的「签署人姓名 + 与患者关系」填写 —— 那两项现在由医生填,
|
||||
* 这里只做只读展示。
|
||||
*
|
||||
* 签名仍走 Canvas(`SignatureCanvas`)。将来接真实手写板设备时,换的是采集层
|
||||
* (设备适配器),这一页的交互不用动。
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import DocumentPaper from '@/components/DocumentPaper.vue'
|
||||
import SignatureCanvas from '@/components/SignatureCanvas.vue'
|
||||
import { signingState, submitRealSignature } from '@/composables/useSignFlow'
|
||||
import { relationOptions, type SignerRelation } from '@/types/signing'
|
||||
|
||||
interface SignatureCanvasExpose {
|
||||
clearSignature: () => void
|
||||
isEmpty: () => boolean
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const signerName = ref('')
|
||||
const relation = ref<SignerRelation | ''>('')
|
||||
const errorMessage = ref('')
|
||||
const signatureDataUrl = ref('')
|
||||
const errorMessage = ref('')
|
||||
const submitting = ref(false)
|
||||
const signatureCanvas = ref<SignatureCanvasExpose | null>(null)
|
||||
|
||||
/** 签署人由医生在医护端填好,这里只展示 */
|
||||
const signerLine = computed(() => {
|
||||
const name = signingState.signerName || signingState.patientName
|
||||
const relation = signingState.relation || '本人'
|
||||
|
||||
return `${name}(${relation})`
|
||||
})
|
||||
|
||||
function dataUrlToBlob(dataUrl: string) {
|
||||
const [header, encoded] = dataUrl.split(',')
|
||||
@@ -29,40 +39,18 @@ function dataUrlToBlob(dataUrl: string) {
|
||||
}
|
||||
|
||||
async function submitSigning() {
|
||||
const trimmedName = signerName.value.trim()
|
||||
if (!trimmedName) {
|
||||
errorMessage.value = '请输入实际签署人姓名。'
|
||||
return
|
||||
}
|
||||
if (!relation.value) {
|
||||
errorMessage.value = '请选择签署人与患者的关系。'
|
||||
return
|
||||
}
|
||||
if (signatureCanvas.value?.isEmpty()) {
|
||||
if (!signatureDataUrl.value) {
|
||||
errorMessage.value = '请完成手写签名。'
|
||||
return
|
||||
}
|
||||
|
||||
const selectedRelation = relation.value
|
||||
|
||||
if (!selectedRelation) {
|
||||
return
|
||||
}
|
||||
|
||||
if (signingState.mode === 'real') {
|
||||
if (!signatureDataUrl.value) {
|
||||
errorMessage.value = '未读取到签名图片,请重新签名。'
|
||||
return
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
await submitRealSignature({
|
||||
file: dataUrlToBlob(signatureDataUrl.value),
|
||||
signerName: trimmedName,
|
||||
relation: selectedRelation,
|
||||
consentAccepted: signingState.consentAccepted,
|
||||
})
|
||||
await router.push('/result')
|
||||
@@ -71,11 +59,10 @@ async function submitSigning() {
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
signingState.signerName = trimmedName
|
||||
signingState.relation = selectedRelation
|
||||
signingState.signatureCaptured = true
|
||||
signingState.submittedAt = new Date().toLocaleString('zh-CN')
|
||||
void router.push('/result')
|
||||
@@ -93,25 +80,38 @@ async function submitSigning() {
|
||||
</div>
|
||||
|
||||
<section class="mobile-heading">
|
||||
<p class="eyebrow">签署人确认</p>
|
||||
<h1>请确认实际签署人</h1>
|
||||
<p>签署人信息将与本次知情同意记录一并保存。</p>
|
||||
<p class="eyebrow">签署确认</p>
|
||||
<h1>请在文书签名处签名</h1>
|
||||
<p>文书内容已由医务人员填写完成,您只需完成患者签名。</p>
|
||||
</section>
|
||||
|
||||
<section class="mobile-card signer-form">
|
||||
<label class="field-label" for="signer-name">签署人姓名</label>
|
||||
<input id="signer-name" v-model="signerName" class="mobile-input" placeholder="请输入姓名" />
|
||||
|
||||
<label class="field-label" for="signer-relation">与患者关系</label>
|
||||
<select id="signer-relation" v-model="relation" class="mobile-input">
|
||||
<option value="" disabled>请选择关系</option>
|
||||
<option v-for="option in relationOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
<section class="mobile-card signer-summary">
|
||||
<div>
|
||||
<span>患者</span>
|
||||
<strong>{{ signingState.patientName }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>签署人</span>
|
||||
<strong>{{ signerLine }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>文书</span>
|
||||
<strong>{{ signingState.documentTitle }} · {{ signingState.templateVersion }}</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SignatureCanvas ref="signatureCanvas" @update:data-url="signatureDataUrl = $event" />
|
||||
<!-- 签完的字会实时回填到文书里的签名位,患者能立刻看到自己签在哪一格 -->
|
||||
<DocumentPaper
|
||||
:content-html="signingState.documentContentHtml"
|
||||
:answers="signingState.staffAnswers"
|
||||
:patient-signature="signatureDataUrl || null"
|
||||
/>
|
||||
|
||||
<SignatureCanvas
|
||||
:height="200"
|
||||
placeholder="请在此签名"
|
||||
@update:data-url="signatureDataUrl = $event ?? ''"
|
||||
/>
|
||||
|
||||
<p v-if="errorMessage" class="form-error">{{ errorMessage }}</p>
|
||||
<button
|
||||
@@ -125,3 +125,32 @@ async function submitSigning() {
|
||||
<button class="mobile-text-button" type="button" @click="router.back()">返回修改</button>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.signer-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 18px 20px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.signer-summary div {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.signer-summary span {
|
||||
flex-shrink: 0;
|
||||
color: #8aa0aa;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.signer-summary strong {
|
||||
color: #173b56;
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user