feat(sign): 补齐医护端与患者端签署流程

This commit is contained in:
xy
2026-09-28 19:57:57 +08:00
parent a25716c861
commit 8024075439
22 changed files with 1082 additions and 163 deletions
@@ -49,10 +49,18 @@ const {
selectMethod,
selectTemplate,
selectVisit,
selectedTemplate,
selectedTemplateId,
selectedVisit,
selectedVisitId,
smsDestination,
staffAnswers,
manualFormFields,
signerType,
signerName,
signerRelation,
staffAnswerHasOption,
toggleStaffAnswerOption,
submit: submitForm,
submitting,
suggestionOpen,
@@ -128,7 +136,11 @@ async function submitTask(keepOpen: boolean) {
emit('created', task)
const smsMessage =
task.method === 'sms' ? (isSigningMockEnabled ? ',已生成短信投递意图' : ',短信已发送') : ''
task.method === 'sms'
? isSigningMockEnabled
? ',已生成短信投递意图'
: ',短信投递已排队'
: ''
ElMessage.success(`已创建 1 个签署任务${smsMessage}`)
if (keepOpen) {
@@ -140,8 +152,8 @@ async function submitTask(keepOpen: boolean) {
}
emit('close')
} catch {
ElMessage.error('签署任务创建失败,请稍后重试')
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '签署任务创建失败,请稍后重试')
}
}
@@ -404,7 +416,10 @@ watch(
type="button"
class="method-option"
:class="{ 'method-option--active': method === option.key }"
:disabled="!isMethodAllowed(option.key)"
:disabled="
!isMethodAllowed(option.key) ||
(!isSigningMockEnabled && (option.key === 'wx' || option.key === 'ipad'))
"
@click="selectMethod(option.key)"
>
<span class="method-icon">{{ option.icon }}</span>
@@ -445,9 +460,88 @@ watch(
</span>
</div>
</div>
<p
v-if="!isSigningMockEnabled && (method === 'wx' || method === 'ipad')"
class="field-error"
>
当前后端尚未接入该渠道的投递或设备适配,请选择短信或已配置的现场手写板。
</p>
</div>
</section>
</div>
<section v-if="selectedTemplate" class="form-section signing-context-section">
<header class="section-header">
<span class="section-index">E</span>
<strong>签署人和文书填写项</strong>
<span class="section-hint">按已选模板采集,提交后固化到签署任务</span>
</header>
<div class="section-body signing-context-grid">
<fieldset class="signer-choice">
<legend>实际签署人</legend>
<label>
<input v-model="signerType" type="radio" value="SELF" />
患者本人
</label>
<label>
<input v-model="signerType" type="radio" value="FAMILY" />
家属代签
</label>
<template v-if="signerType === 'FAMILY'">
<input v-model="signerName" maxlength="255" placeholder="代签人姓名" />
<input v-model="signerRelation" maxlength="64" placeholder="与患者关系" />
</template>
<p v-if="errors.signer" class="field-error">{{ errors.signer }}</p>
</fieldset>
<div v-if="manualFormFields.length" class="staff-answer-fields">
<label v-for="field in manualFormFields" :key="field.id" class="staff-answer-field">
<span>{{ field.label }}<b v-if="field.required"> *</b></span>
<select
v-if="field.kind === 'choice'"
v-model="staffAnswers[field.id]"
:required="field.required"
>
<option value="">请选择</option>
<option v-for="option in field.options ?? []" :key="option" :value="option">
{{ option }}
</option>
</select>
<span
v-else-if="field.kind === 'checkbox' && field.options?.length"
class="staff-answer-options"
>
<label v-for="option in field.options" :key="option">
<input
type="checkbox"
:checked="staffAnswerHasOption(field.id, option)"
@change="
toggleStaffAnswerOption(
field.id,
option,
($event.target as HTMLInputElement).checked,
)
"
/>
{{ option }}
</label>
</span>
<input
v-else
v-model="staffAnswers[field.id]"
:type="field.kind === 'date' ? 'date' : 'text'"
:required="field.required"
maxlength="4000"
:placeholder="`填写${field.label}`"
/>
</label>
<p v-if="errors.staffAnswers" class="field-error">{{ errors.staffAnswers }}</p>
</div>
<p v-else class="section-empty">
当前模板没有需要医护端填写的正文项;患者签名和签署日期将在签署环节生成。
</p>
</div>
</section>
</div>
<footer class="dialog-footer">
@@ -882,6 +976,80 @@ watch(
font-size: 12px;
}
.section-hint {
margin-left: auto;
color: var(--mut);
font-size: 11px;
}
.signing-context-grid {
display: grid;
grid-template-columns: minmax(220px, 0.7fr) minmax(0, 1.3fr);
gap: 14px;
}
.signer-choice {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-content: start;
padding: 10px;
border: 1px solid var(--line);
border-radius: 7px;
}
.signer-choice legend,
.staff-answer-field > span:first-child {
color: var(--mut);
font-size: 12px;
}
.signer-choice label,
.staff-answer-options label {
display: inline-flex;
gap: 5px;
align-items: center;
color: var(--ink);
font-size: 12px;
}
.signer-choice > input,
.staff-answer-field > input,
.staff-answer-field > select {
min-width: 0;
padding: 7px 9px;
border: 1px solid var(--line);
border-radius: 6px;
}
.staff-answer-fields {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.staff-answer-field {
display: grid;
gap: 5px;
min-width: 0;
font-size: 12px;
}
.staff-answer-field b {
color: var(--err);
}
.staff-answer-options {
display: flex;
flex-wrap: wrap;
gap: 9px;
}
.signing-context-grid > .section-empty,
.staff-answer-fields > .field-error {
grid-column: 1 / -1;
}
/* C + D 并排 */
.document-method-grid {
@@ -1292,6 +1460,10 @@ watch(
}
@media (max-width: 1100px) {
.signing-context-grid,
.staff-answer-fields {
grid-template-columns: 1fr;
}
.document-method-grid {
flex-direction: column;
}