feat(签署工作台): 补齐短信/公众号发送弹窗与 Pad 定位签名位流程

This commit is contained in:
yelan
2026-09-27 16:50:13 +08:00
parent 39d27ae5e7
commit ea5a248a0c
9 changed files with 981 additions and 20 deletions
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRoute } from 'vue-router'
@@ -18,8 +18,12 @@ import {
getSigningTasks,
getSigningTemplates,
isSigningMockEnabled,
pushSigningWxMessage,
reopenSigningTask,
resendSigningSms,
resendSigningWxMessage,
sendSigningSms,
simulatePatientSigning,
updateSigningTaskMethod,
voidSigningTask,
} from '@/api/workbench/signing'
@@ -48,7 +52,9 @@ import SignedDocumentDialog from './components/SignedDocumentDialog.vue'
import SigningFilterBar from './components/SigningFilterBar.vue'
import SigningTaskDetail from './components/SigningTaskDetail.vue'
import SigningTaskList from './components/SigningTaskList.vue'
import SmsSendDialog from './components/SmsSendDialog.vue'
import VoidTaskDialog from './components/VoidTaskDialog.vue'
import WxPushDialog from './components/WxPushDialog.vue'
import type { SigningFilterForm, SigningFilterOption, SigningTaskAction } from './types'
const route = useRoute()
@@ -111,6 +117,10 @@ const departmentIds = ref<Record<string, string>>({})
const newDialogVisible = ref(false)
const signatureDialogVisible = ref(false)
const onlineDialogVisible = ref(false)
/** 「发送签署短信」弹窗(原型 #mdSmsFlow) */
const smsSendVisible = ref(false)
/** 「公众号消息推送」弹窗(原型 #mdWxPush) */
const wxPushVisible = ref(false)
const voidDialogVisible = ref(false)
/** 已签署文书的在线预览弹窗(原型 openPdfView) */
const signedPreviewVisible = ref(false)
@@ -142,6 +152,26 @@ const canViewEvents = computed(() => canRead('sign:task:event:query'))
const canViewArtifacts = computed(() => canRead('sign:artifact:query'))
const canDownloadArtifacts = computed(() => canRead('sign:artifact:download'))
/**
* 「投递类」动作:签署本身、方式切换、签署消息的发送与重发。
*
* 它们动的是对外可见的东西(患者会收到短信 / 推送,或任务状态直接变),
* 与「创建签署会话」同级 —— 都要 `sign:delivery:write`。
* 归到一处是因为原来逐个 `action === 'x' && ...` 地列,加一个方式就漏一个
* (漏掉的表现是「切到 Pad 就提示没有权限」)。
*/
const DELIVERY_ACTIONS: SigningTaskAction[] = [
'pad-sign',
'online-sign',
'switch-method',
'switch-to-ipad',
'send-sms',
'send-wx-push',
'simulate-patient-sign',
'resend-sms',
'resend-wx-push',
]
/**
* 状态筛选的取值:原型 `#fStat` 只有 6 项
* (全部 / 待签署 / 待科室确认 / 已签署 / 已超时 / 已作废)。
@@ -501,7 +531,7 @@ async function handleTaskAction(action: SigningTaskAction, targetMethod?: Signin
const allowed =
isSigningMockEnabled ||
(action === 'create-pad-session' && canDeliveryWrite.value) ||
(action === 'resend-sms' && canDeliveryWrite.value) ||
(DELIVERY_ACTIONS.includes(action) && canDeliveryWrite.value) ||
(action === 'reopen' && canReopenTask.value) ||
(action === 'void' && canVoidTask.value) ||
(action === 'confirm-dept' && canConfirmTask.value) ||
@@ -524,6 +554,22 @@ async function handleTaskAction(action: SigningTaskAction, targetMethod?: Signin
return
}
/**
* 原型把两种现场方式分得很清楚:
* - 手写板 → `openPad()`,直接开签名板;
* - Pad → `openIpad()`,**不弹窗**,只把文书滚到第一个签名位并高亮,
* 提示「点击签名框弹出签名板」,由操作人自己点那个框。
* 差别就在这里:一个直接签,一个是「在文书上找到该签的地方」再签。
*/
if (task.method === 'ipad') {
if (taskDetailRef.value?.locateSignatureSlot()) {
ElMessage.info('已下拉到签名位置,点击签名框弹出签名板完成签署')
} else {
ElMessage.error('当前文书未找到签名位置')
}
return
}
if (!(await assertSigningReady())) {
return
}
@@ -532,6 +578,81 @@ async function handleTaskAction(action: SigningTaskAction, targetMethod?: Signin
return
}
// 短信 / 公众号:先开「发送」弹窗(原型 openSmsFlow / openWxPush)。
// 发送成功才算把任务切成那个方式 —— 见 handleSmsSent / handleWxPushed
if (action === 'send-sms') {
if (!isSigningMockEnabled) {
ElMessage.info('发送签署短信接口待接入')
return
}
smsSendVisible.value = true
return
}
if (action === 'send-wx-push') {
if (!isSigningMockEnabled) {
ElMessage.info('公众号签署推送接口待接入')
return
}
wxPushVisible.value = true
return
}
// 演示入口:没人真的去点短信链接 / 打开公众号消息,不给它整条链路走不完
if (action === 'simulate-patient-sign') {
if (!isSigningMockEnabled) {
ElMessage.info('演示入口,真实模式请由患者在签署页完成')
return
}
try {
const updated = await simulatePatientSigning(task.id)
await refreshTask(updated, signingCompletedMessage(updated))
} catch {
ElMessage.error('模拟患者签署失败,请稍后重试')
}
return
}
if (action === 'resend-wx-push') {
if (!isSigningMockEnabled) {
ElMessage.info('公众号签署推送接口待接入')
return
}
try {
await refreshTask(await resendSigningWxMessage(task.id), '公众号签署消息已重新推送')
} catch {
ElMessage.error('重发推送失败,请稍后重试')
}
return
}
// 原型 switchToIpad:先切成 Pad 方式,再定位到签名位(两步是一件事)
if (action === 'switch-to-ipad') {
if (!isSigningMockEnabled) {
ElMessage.info('当前后端未提供切换签署方式接口,请作废后重新创建任务')
return
}
try {
await refreshTask(await updateSigningTaskMethod(task.id, 'ipad'), '已切换为 📲 Pad 签署')
await nextTick()
if (taskDetailRef.value?.locateSignatureSlot()) {
ElMessage.info('已下拉到签名位置,点击签名框弹出签名板完成签署')
}
} catch {
ElMessage.error('任务操作失败,请稍后重试')
}
return
}
if (action === 'create-pad-session') {
padSessionLoading.value = true
@@ -867,6 +988,46 @@ async function handleOnlineResend() {
await handleTaskAction('resend-sms')
}
/**
* 发送签署短信成功(原型 `sendSmsFlow`):方式切换在这里完成 ——
* 发送前那条任务可能还是手写板 / Pad,发送后它才是一条「等待患者签署」的短信任务。
*/
async function handleSmsSent(mobile: string | null) {
const task = selectedTask.value
if (!task) {
return
}
try {
const updated = await sendSigningSms(task.id, mobile)
smsSendVisible.value = false
await refreshTask(
updated,
`签署短信已发送${updated?.smsMobileMasked ? `至 ${updated.smsMobileMasked}` : ''},等待患者签署`,
)
} catch {
ElMessage.error('发送签署短信失败,请稍后重试')
}
}
/** 确认推送公众号签署消息(原型 `sendWxPush`):方式同样在这一步切成 wx */
async function handleWxPushed() {
const task = selectedTask.value
if (!task) {
return
}
try {
const updated = await pushSigningWxMessage(task.id)
wxPushVisible.value = false
await refreshTask(updated, '公众号签署消息已推送给患者,等待患者签署')
} catch {
ElMessage.error('公众号推送失败,请稍后重试')
}
}
async function initializePage() {
await appStore.loadCampuses()
syncFilterFromRoute()
@@ -982,6 +1143,19 @@ onMounted(() => {
@complete="completeOnlineSignature"
@resend="handleOnlineResend"
/>
<!-- 短信 / 公众号链路的**第一步**:先把消息发出去(原型 #mdSmsFlow / #mdWxPush) -->
<SmsSendDialog
:visible="smsSendVisible"
:task="selectedTask"
@close="smsSendVisible = false"
@send="handleSmsSent"
/>
<WxPushDialog
:visible="wxPushVisible"
:task="selectedTask"
@close="wxPushVisible = false"
@push="handleWxPushed"
/>
<SignedDocumentDialog
:visible="signedPreviewVisible"
:task="selectedTask"