feat(签署工作台): 补齐短信/公众号发送弹窗与 Pad 定位签名位流程
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user