feat(签署方式): 扩展为手写板/短信/Pad/公众号四种并集中口径
This commit is contained in:
@@ -28,6 +28,7 @@ import { useLoginStore } from '@/stores/login'
|
||||
import { demoCampuses, demoDepartments } from '@/utils/mock-document-library'
|
||||
import { mockFlags } from '@/utils/mock-flags'
|
||||
import { clearMockState } from '@/utils/mock-storage'
|
||||
import { ALL_SIGNING_METHODS } from '@/utils/signing-method'
|
||||
|
||||
import DocumentFilterTabs from './components/DocumentFilterTabs.vue'
|
||||
import DocumentHistoryDialog from './components/DocumentHistoryDialog.vue'
|
||||
@@ -987,7 +988,7 @@ async function toSigningTemplate(template: DocumentTemplate): Promise<SigningTem
|
||||
campusId: template.campusId ?? undefined,
|
||||
category: template.category,
|
||||
description: template.description ?? '',
|
||||
supportedMethods: ['pad', 'sms'],
|
||||
supportedMethods: [...ALL_SIGNING_METHODS],
|
||||
contentHtml,
|
||||
contentSha256,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { formatSigningMethod, readSigningMethod } from '@/utils/signing-method'
|
||||
|
||||
import type { ReportTask, ReportTaskStatus } from '../types'
|
||||
|
||||
defineProps<{
|
||||
@@ -30,7 +32,7 @@ const statusLabels: Record<ReportTaskStatus, string> = {
|
||||
|
||||
function getStatusLabel(row: ReportTask) {
|
||||
if (row.status === 'pending') {
|
||||
return `${statusLabels[row.status]}·${row.method === 'pad' ? '手写板' : '短信'}`
|
||||
return `${statusLabels[row.status]}·${readSigningMethod(row.method).short}`
|
||||
}
|
||||
|
||||
return statusLabels[row.status]
|
||||
@@ -131,7 +133,7 @@ function handlePageSizeChange(event: Event) {
|
||||
</td>
|
||||
<td class="document-name" :title="row.documentName">{{ row.documentName }}</td>
|
||||
<td>{{ row.department }}</td>
|
||||
<td>{{ row.method === 'pad' ? '✍️ 手写板' : '📱 短信' }}</td>
|
||||
<td>{{ formatSigningMethod(row.method) }}</td>
|
||||
<td>
|
||||
<span class="status-tag" :class="getStatusClass(row.status)">
|
||||
{{ getStatusLabel(row) }}
|
||||
|
||||
@@ -22,6 +22,11 @@ import type {
|
||||
ReportTasksQueryDto,
|
||||
} from '@/api/management/types'
|
||||
import { useLoginStore } from '@/stores/login'
|
||||
import {
|
||||
formatSigningMethodTitle,
|
||||
REPORT_METHOD_FILTERS,
|
||||
toBackendSigningMethod,
|
||||
} from '@/utils/signing-method'
|
||||
|
||||
import DepartmentDistributionChart from './components/DepartmentDistributionChart.vue'
|
||||
import ReportDetailTable from './components/ReportDetailTable.vue'
|
||||
@@ -102,8 +107,7 @@ const filters = reactive<ReportFilterForm>({
|
||||
|
||||
const methodOptions: ReportSelectOption<ReportMethodFilter>[] = [
|
||||
{ value: 'all', label: '全部方式' },
|
||||
{ value: 'pad', label: '手写板' },
|
||||
{ value: 'sms', label: '短信' },
|
||||
...REPORT_METHOD_FILTERS.map((option) => ({ value: option.value, label: option.label })),
|
||||
]
|
||||
|
||||
interface ReportDateRange {
|
||||
@@ -571,7 +575,7 @@ function getScopeFilter(): ReportFilterDto {
|
||||
}
|
||||
|
||||
if (filters.method !== 'all') {
|
||||
filter.signMethod = filters.method === 'sms' ? 'SMS' : 'PAD'
|
||||
filter.signMethod = toBackendSigningMethod(filters.method)
|
||||
}
|
||||
|
||||
const keyword = filters.keyword.trim()
|
||||
@@ -994,7 +998,7 @@ function downloadMockReport() {
|
||||
task.documentName,
|
||||
task.department,
|
||||
task.campus,
|
||||
task.method === 'pad' ? '手写板' : '短信',
|
||||
formatSigningMethodTitle(task.method),
|
||||
getReportStatusLabel(task.status),
|
||||
formatExportDate(task.createdAt),
|
||||
formatExportDate(task.signedAt),
|
||||
|
||||
@@ -169,6 +169,38 @@ export const reportTasks: ReportTask[] = [
|
||||
status: 'void',
|
||||
createdAt: daysAgo(5, 11, 45),
|
||||
},
|
||||
// Pad 与公众号两条:四种方式都要有样本,否则「方式」列与筛选选到 Pad / 公众号时
|
||||
// 永远只能得到空结果,看着像筛选坏了(其实是演示数据没覆盖)。
|
||||
{
|
||||
id: 'T0813002',
|
||||
patientName: '罗文静',
|
||||
patientId: 'P202610007',
|
||||
documentName: '床旁超声检查知情同意书',
|
||||
department: '放射科',
|
||||
campus: '东院区',
|
||||
category: 'consent',
|
||||
method: 'ipad',
|
||||
status: 'signed',
|
||||
createdAt: daysAgo(2, 8, 50),
|
||||
signedAt: daysAgo(2, 8, 58),
|
||||
expiredAt: daysAgo(1, 8, 50),
|
||||
durationMinutes: 8,
|
||||
},
|
||||
{
|
||||
id: 'T0813001',
|
||||
patientName: '梁俊豪',
|
||||
patientId: 'P202610008',
|
||||
documentName: '体检报告发放及知情同意书',
|
||||
department: '健康管理中心',
|
||||
campus: '东院区',
|
||||
category: 'consent',
|
||||
method: 'wx',
|
||||
status: 'signed',
|
||||
createdAt: daysAgo(3, 16, 10),
|
||||
signedAt: daysAgo(3, 16, 26),
|
||||
expiredAt: daysAgo(2, 16, 10),
|
||||
durationMinutes: 16,
|
||||
},
|
||||
]
|
||||
|
||||
export const reportDepartmentColors: Record<string, string> = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { SigningMethod } from '@/api/workbench/types'
|
||||
|
||||
export type ReportPeriodOption = '7d' | '14d' | 'month' | 'custom'
|
||||
|
||||
/** 院区由后端字典提供,保留 all 作为全院筛选值。 */
|
||||
@@ -5,7 +7,7 @@ export type ReportCampus = 'all' | (string & {})
|
||||
|
||||
export type ReportDocumentCategory = 'all' | (string & {})
|
||||
|
||||
export type ReportMethodFilter = 'all' | 'pad' | 'sms'
|
||||
export type ReportMethodFilter = 'all' | SigningMethod
|
||||
|
||||
export interface ReportFilterForm {
|
||||
period: ReportPeriodOption
|
||||
@@ -30,7 +32,7 @@ export interface ReportMetricCard {
|
||||
|
||||
export type ReportTaskStatus = 'signed' | 'pending' | 'signing' | 'expired' | 'void' | 'failed'
|
||||
|
||||
export type ReportSigningMethod = 'pad' | 'sms'
|
||||
export type ReportSigningMethod = SigningMethod
|
||||
|
||||
export interface ReportTask {
|
||||
id: string
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
import type { ReportTaskRow } from '@/api/workbench/report-analytics'
|
||||
import type { SigningMethod, SigningTaskStatus } from '@/api/workbench/types'
|
||||
import { formatSigningMethodTitle, SIGNING_METHODS } from '@/utils/signing-method'
|
||||
|
||||
import type {
|
||||
ReportBarItem,
|
||||
@@ -31,10 +32,21 @@ export const OVERDUE_MINUTES = 2880
|
||||
/** 趋势图最多画多少根柱子:自定义区间可能跨好几个月,画不下也没人看 */
|
||||
const MAX_TREND_DAYS = 60
|
||||
|
||||
export const METHOD_LABELS: Record<SigningMethod, string> = {
|
||||
pad: '手写板',
|
||||
sms: '短信线上',
|
||||
}
|
||||
/**
|
||||
* 签署方式维度文案。
|
||||
* 口径统一走 utils/signing-method:四种方式各成一行,不能只列 pad / sms——
|
||||
* 报表维度是按任务实际取值聚合的,缺一行会让 Pad / 公众号的任务在图表里显示成空白。
|
||||
* 文案取原型的 methodLabel(`✍️ 手写板签署` 这种带图标的完整写法),
|
||||
* 与报表明细表的「方式」列一致。
|
||||
*
|
||||
* ⚠️ 这是**查表**,没有 `readSigningMethod` 那样的未知取值兜底:`METHOD_LABELS[未知值]`
|
||||
* 会得到 undefined,汇总矩阵会多出一行没有名字的分组。今天不会触发(`SigningMethod`
|
||||
* 是封闭联合,取值只来自本仓库的演示数据),但接真实接口时必须在边界上先把后端取值
|
||||
* 归一到这四个(见 api/workbench/signing.ts 的 normalizeSigningMethod),不要指望这里兜底。
|
||||
*/
|
||||
export const METHOD_LABELS: Record<SigningMethod, string> = Object.fromEntries(
|
||||
SIGNING_METHODS.map((option) => [option.key, formatSigningMethodTitle(option.key)]),
|
||||
) as Record<SigningMethod, string>
|
||||
|
||||
/**
|
||||
* 状态文案。六种状态与后端枚举一一对应,不映射成原型里的「待科室确认」——
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ElMessage } from 'element-plus'
|
||||
import type { ReportTaskRow } from '@/api/workbench/report-analytics'
|
||||
import type { SigningMethod, WorkbenchCampus } from '@/api/workbench/types'
|
||||
import { downloadCsv, fileStamp } from '@/utils/csv-export'
|
||||
import { REPORT_METHOD_FILTERS } from '@/utils/signing-method'
|
||||
|
||||
import { filterReportTasks, formatDuration, METHOD_LABELS, STATUS_LABELS } from '../aggregate'
|
||||
import type { ReportDateRange, ReportExportColumn, ReportFilterForm, ReportFilterOption } from '../types'
|
||||
@@ -44,8 +45,7 @@ const dateRangeOptions: ReportFilterOption<ReportDateRange>[] = [
|
||||
|
||||
const methodOptions: ReportFilterOption<SigningMethod | 'all'>[] = [
|
||||
{ label: '全部方式', value: 'all' },
|
||||
{ label: '手写板', value: 'pad' },
|
||||
{ label: '短信线上', value: 'sms' },
|
||||
...REPORT_METHOD_FILTERS.map((option) => ({ label: option.label, value: option.value })),
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { SigningMethod, WorkbenchCampus } from '@/api/workbench/types'
|
||||
import { REPORT_METHOD_FILTERS } from '@/utils/signing-method'
|
||||
|
||||
import { reportFilter, resetReportFilter } from '../filter-store'
|
||||
import type { ReportDateRange, ReportFilterOption } from '../types'
|
||||
@@ -32,10 +33,10 @@ const dateRangeOptions: ReportFilterOption<ReportDateRange>[] = [
|
||||
{ label: '自定义…', value: 'custom' },
|
||||
]
|
||||
|
||||
/** 原型 #repMethod 是 4 项(手写板 / Pad / 短信 / 公众号),与记录查询的 3 项不同 */
|
||||
const methodOptions: ReportFilterOption<SigningMethod | 'all'>[] = [
|
||||
{ label: '全部方式', value: 'all' },
|
||||
{ label: '手写板', value: 'pad' },
|
||||
{ label: '短信线上', value: 'sms' },
|
||||
...REPORT_METHOD_FILTERS.map((option) => ({ label: option.label, value: option.value })),
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { readPendingTag } from '@/utils/signing-method'
|
||||
|
||||
import type { HomeTodoItem } from '../types'
|
||||
|
||||
defineProps<{
|
||||
@@ -15,7 +17,7 @@ function getStatusLabel(item: HomeTodoItem) {
|
||||
case 'created':
|
||||
return '待发送'
|
||||
case 'pending':
|
||||
return item.method === 'pad' ? '待签署·手写板' : '待签署·短信'
|
||||
return readPendingTag(item.method).text
|
||||
case 'generating':
|
||||
return '文书生成中'
|
||||
case 'overdue':
|
||||
@@ -35,7 +37,8 @@ function getStatusClass(item: HomeTodoItem) {
|
||||
}
|
||||
|
||||
if (item.status === 'pending') {
|
||||
return item.method === 'pad' ? 'todo-status--pad' : 'todo-status--sms'
|
||||
// 与签署工作台同口径:待签署徽标按具体方式分色
|
||||
return `todo-status--${readPendingTag(item.method).className.replace('tg-', '')}`
|
||||
}
|
||||
|
||||
if (item.status === 'signed') {
|
||||
@@ -203,6 +206,11 @@ function getHint(item: HomeTodoItem) {
|
||||
background: var(--brand-l);
|
||||
}
|
||||
|
||||
.todo-status--ipad {
|
||||
color: #2b6cb0;
|
||||
background: #e8f1fb;
|
||||
}
|
||||
|
||||
.todo-status--sms {
|
||||
color: #7a4dbb;
|
||||
background: #f3ecfb;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { SigningMethod } from '@/api/workbench/types'
|
||||
|
||||
export type HomeMetricTone = 'brand' | 'success' | 'warning' | 'danger'
|
||||
|
||||
export type HomeMetricKey = 'todaySigned' | 'pending' | 'overdue' | 'templates'
|
||||
@@ -21,7 +23,8 @@ export interface HomeTrendPoint {
|
||||
export type HomeTodoStatus =
|
||||
'created' | 'pending' | 'generating' | 'overdue' | 'signed' | 'voided' | 'failed'
|
||||
|
||||
export type HomeSigningMethod = 'pad' | 'sms'
|
||||
/** 与签署任务同一套方式口径,否则新通道的任务映射到首页待办会被 TS 挡下 */
|
||||
export type HomeSigningMethod = SigningMethod
|
||||
|
||||
export interface HomeTodoItem {
|
||||
id: string
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SigningTaskEvent, SigningTaskStatus } from '@/api/workbench/types'
|
||||
import SigningDocumentInteractive from '@/components/signing/SigningDocumentInteractive.vue'
|
||||
import SigningDocumentPreview from '@/components/signing/SigningDocumentPreview.vue'
|
||||
import TaskAuditTimeline from '@/components/signing/TaskAuditTimeline.vue'
|
||||
import { formatSigningMethod } from '@/utils/signing-method'
|
||||
|
||||
import type { SigningRecordRow } from '../types'
|
||||
|
||||
@@ -95,7 +96,7 @@ function value(value: string | undefined) {
|
||||
<div><dt>院区</dt><dd>{{ value(record.campus) }}</dd></div>
|
||||
<div>
|
||||
<dt>签署方式</dt>
|
||||
<dd>{{ record.method === 'pad' ? '✍️ 手写板' : '📱 短信' }}</dd>
|
||||
<dd>{{ formatSigningMethod(record.method) }}</dd>
|
||||
</div>
|
||||
<div><dt>发起时间</dt><dd>{{ value(record.createdAt) }}</dd></div>
|
||||
<div><dt>签署完成</dt><dd>{{ value(record.signedAt) }}</dd></div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { SigningTaskStatus } from '@/api/workbench/types'
|
||||
import { formatSigningMethod, readSigningMethod } from '@/utils/signing-method'
|
||||
|
||||
import type { SigningRecordRow } from '../types'
|
||||
|
||||
@@ -38,14 +39,14 @@ const statusLabels: Record<SigningTaskStatus, string> = {
|
||||
/** 待签署要带出渠道:现场等着签的是手写板,等患者点链接的是短信,处理方式完全不同 */
|
||||
function getStatusLabel(row: SigningRecordRow) {
|
||||
if (row.status === 'pending') {
|
||||
return `${statusLabels[row.status]}·${row.method === 'pad' ? '手写板' : '短信'}`
|
||||
return `${statusLabels[row.status]}·${readSigningMethod(row.method).short}`
|
||||
}
|
||||
|
||||
return statusLabels[row.status]
|
||||
}
|
||||
|
||||
function getMethodLabel(row: SigningRecordRow) {
|
||||
return row.method === 'pad' ? '✍️ 手写板' : '📱 短信'
|
||||
return formatSigningMethod(row.method)
|
||||
}
|
||||
|
||||
function formatTime(value: string | undefined) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useLoginStore } from '@/stores/login'
|
||||
import { downloadCsv, fileStamp, saveBlob } from '@/utils/csv-export'
|
||||
import { readSigningMethod, WORKBENCH_METHOD_FILTERS } from '@/utils/signing-method'
|
||||
import { downloadSigningPdf } from '@/utils/signing-pdf'
|
||||
|
||||
import SigningRecordDetailDialog from './components/SigningRecordDetailDialog.vue'
|
||||
@@ -65,7 +66,7 @@ const EXPORT_COLUMNS: SigningRecordExportColumn[] = [
|
||||
{ label: '文档类别', value: (record) => record.category },
|
||||
{ label: '科室', value: (record) => record.department },
|
||||
{ label: '院区', value: (record) => record.campus },
|
||||
{ label: '签署方式', value: (record) => (record.method === 'pad' ? '手写板' : '短信线上') },
|
||||
{ label: '签署方式', value: (record) => readSigningMethod(record.method).filterLabel },
|
||||
{ label: '状态', value: (record) => statusLabels[record.status] },
|
||||
{ label: '发起时间', value: (record) => record.createdAt ?? '' },
|
||||
{ label: '签署完成时间', value: (record) => record.signedAt ?? '' },
|
||||
@@ -132,10 +133,10 @@ const statusOptions: SigningRecordFilterOption<SigningRecordFilterForm['status']
|
||||
),
|
||||
]
|
||||
|
||||
/** 原型 #qMethod 只有 3 项(手写板 / 短信线上 / Pad),没有公众号 */
|
||||
const methodOptions: SigningRecordFilterOption<SigningMethod | 'all'>[] = [
|
||||
{ label: '全部方式', value: 'all' },
|
||||
{ label: '手写板', value: 'pad' },
|
||||
{ label: '短信线上', value: 'sms' },
|
||||
...WORKBENCH_METHOD_FILTERS.map((option) => ({ label: option.label, value: option.value })),
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,10 +4,12 @@ import { ref } from 'vue'
|
||||
import type {
|
||||
SigningAnswers,
|
||||
SigningArtifact,
|
||||
SigningMethod,
|
||||
SigningTaskEvent,
|
||||
SigningTaskRecord,
|
||||
} from '@/api/workbench/types'
|
||||
import type { SigningFieldProgress, SigningSignatureRequest } from '@/utils/signing-document'
|
||||
import { formatSigningMethodTitle, readSigningMethod } from '@/utils/signing-method'
|
||||
|
||||
import SigningDocumentInteractive from '@/components/signing/SigningDocumentInteractive.vue'
|
||||
import SigningDocumentPreview from '@/components/signing/SigningDocumentPreview.vue'
|
||||
@@ -38,7 +40,7 @@ defineProps<{
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
action: [action: SigningTaskAction]
|
||||
action: [action: SigningTaskAction, targetMethod?: SigningMethod]
|
||||
downloadArtifact: [artifact: SigningArtifact]
|
||||
selectionChange: [items: string[]]
|
||||
fieldChange: [answers: SigningAnswers, progress: SigningFieldProgress]
|
||||
@@ -47,6 +49,25 @@ const emit = defineEmits<{
|
||||
|
||||
const documentRef = ref<InstanceType<typeof SigningDocumentInteractive> | null>(null)
|
||||
|
||||
/**
|
||||
* 原型任务详情:主按钮 = 当前方式的签署入口,另外三种方式各给一个「转为 X」的次按钮。
|
||||
* 原型的四种方式都走这一套(`待签署` 下四个 if/else 分支,主按钮与其余三个各不相同),
|
||||
* 所以这里按**具体取值**分支,不做「现场 / 线上」归并。
|
||||
*/
|
||||
const ALTERNATE_METHODS: readonly SigningMethod[] = ['pad', 'sms', 'ipad', 'wx']
|
||||
|
||||
function alternateMethods(current: SigningMethod): SigningMethod[] {
|
||||
return ALTERNATE_METHODS.filter((item) => item !== current)
|
||||
}
|
||||
|
||||
/**
|
||||
* 现场设备签署(手写板 / Pad)走本地采集入口;线上(短信 / 公众号)走患者端页面。
|
||||
* 这只是**入口形态**的分组,不是取值归并 —— 按钮文案、状态标签仍按具体取值区分。
|
||||
*/
|
||||
function isOnsiteMethod(method: SigningMethod): boolean {
|
||||
return method === 'pad' || method === 'ipad'
|
||||
}
|
||||
|
||||
/** 供父级「导出 PDF」使用:转发到实际渲染文书的那一层 */
|
||||
function getDocumentElement() {
|
||||
return documentRef.value?.getDocumentElement() ?? null
|
||||
@@ -66,61 +87,63 @@ defineExpose({ getDocumentElement })
|
||||
<div class="task-actions">
|
||||
<span class="task-action-meta">任务 {{ task.id }} · {{ task.patientName }}</span>
|
||||
|
||||
<template v-if="task.status === 'pending' && task.method === 'pad'">
|
||||
<template v-if="allowLocalSigning">
|
||||
<button type="button" class="action-button" @click="emit('action', 'pad-sign')">
|
||||
✍ 手写板签署
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="action-button action-button--ghost"
|
||||
@click="emit('action', 'switch-method')"
|
||||
>
|
||||
转为短信发送
|
||||
</button>
|
||||
<template v-if="task.status === 'pending'">
|
||||
<!-- 主按钮 = 当前方式的签署入口(原型:手写板/Pad 走现场设备,短信/公众号走线上页面) -->
|
||||
<template v-if="isOnsiteMethod(task.method)">
|
||||
<template v-if="allowLocalSigning">
|
||||
<button type="button" class="action-button" @click="emit('action', 'pad-sign')">
|
||||
{{ formatSigningMethodTitle(task.method) }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="canDeliveryWrite">
|
||||
<button
|
||||
type="button"
|
||||
class="action-button"
|
||||
:disabled="padSessionLoading"
|
||||
@click="emit('action', 'create-pad-session')"
|
||||
>
|
||||
{{
|
||||
padSessionLoading ? '创建会话中…' : `创建${readSigningMethod(task.method).short}会话`
|
||||
}}
|
||||
</button>
|
||||
<span v-if="padSessionStatus" class="action-hint">
|
||||
会话状态:{{ padSessionStatus }};等待设备适配器读取 challenge
|
||||
</span>
|
||||
<span v-else class="action-hint">创建会话后仍需接入厂商设备适配器完成签名采集</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="canDeliveryWrite">
|
||||
<template v-else>
|
||||
<button
|
||||
v-if="allowLocalSigning"
|
||||
type="button"
|
||||
class="action-button"
|
||||
:disabled="padSessionLoading"
|
||||
@click="emit('action', 'create-pad-session')"
|
||||
@click="emit('action', 'online-sign')"
|
||||
>
|
||||
{{ padSessionLoading ? '创建会话中…' : '创建手写板会话' }}
|
||||
查看{{ readSigningMethod(task.method).short }}签署
|
||||
</button>
|
||||
<span v-if="padSessionStatus" class="action-hint">
|
||||
会话状态:{{ padSessionStatus }};等待设备适配器读取 challenge
|
||||
</span>
|
||||
<span v-else class="action-hint">创建会话后仍需接入厂商设备适配器完成签名采集</span>
|
||||
<span v-else class="action-hint">线上签署页面接口待接入</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-else-if="task.status === 'pending' && task.method === 'sms'">
|
||||
<!-- 其余三种方式:一键转换(原型四种方式互为切换入口) -->
|
||||
<button
|
||||
v-if="allowLocalSigning"
|
||||
type="button"
|
||||
class="action-button"
|
||||
@click="emit('action', 'online-sign')"
|
||||
>
|
||||
📱 查看线上签署
|
||||
</button>
|
||||
<span v-else class="action-hint">线上签署页面接口待接入</span>
|
||||
<button
|
||||
v-if="allowLocalSigning"
|
||||
v-for="target in alternateMethods(task.method)"
|
||||
:key="target"
|
||||
type="button"
|
||||
class="action-button action-button--ghost"
|
||||
@click="emit('action', 'switch-method')"
|
||||
@click="emit('action', 'switch-method', target)"
|
||||
>
|
||||
转手写板签署
|
||||
{{ readSigningMethod(target).short }}签署
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="canDeliveryWrite"
|
||||
v-if="canDeliveryWrite && task.method === 'sms'"
|
||||
type="button"
|
||||
class="action-button action-button--ghost"
|
||||
@click="emit('action', 'resend-sms')"
|
||||
>
|
||||
重发短信
|
||||
</button>
|
||||
<span v-else-if="task.method === 'wx'" class="action-hint">公众号推送接口待接入</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="task.status === 'expired' && canReopen">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { SigningTaskRecord } from '@/api/workbench/types'
|
||||
import { formatSigningMethod, readPendingTag } from '@/utils/signing-method'
|
||||
|
||||
const props = defineProps<{
|
||||
tasks: SigningTaskRecord[]
|
||||
@@ -30,14 +31,20 @@ const statusLabels: Record<SigningTaskRecord['status'], string> = {
|
||||
|
||||
function getStatusClass(task: SigningTaskRecord) {
|
||||
if (task.status === 'pending') {
|
||||
return task.method === 'pad' ? 'task-status--pad' : 'task-status--sms'
|
||||
// 待签署徽标按具体方式分色(原型 tg-pad / tg-ipad / tg-sms 三分支)
|
||||
return `task-status--${readPendingTag(task.method).className.replace('tg-', '')}`
|
||||
}
|
||||
|
||||
return `task-status--${task.status}`
|
||||
}
|
||||
|
||||
function getMethodLabel(task: SigningTaskRecord) {
|
||||
return task.method === 'pad' ? '✍ 手写板' : '📱 短信'
|
||||
return formatSigningMethod(task.method)
|
||||
}
|
||||
|
||||
/** 待签署徽标带出具体方式:现场等着签的是手写板/Pad,等患者点链接的是短信/公众号 */
|
||||
function getPendingLabel(task: SigningTaskRecord) {
|
||||
return readPendingTag(task.method).text
|
||||
}
|
||||
|
||||
function getVisitClass(type: SigningTaskRecord['visitType']) {
|
||||
@@ -79,13 +86,7 @@ function getVisitClass(type: SigningTaskRecord['visitType']) {
|
||||
<span class="task-basic">{{ task.sex }} · {{ task.age }}岁</span>
|
||||
<span class="task-spacer" />
|
||||
<span class="task-status" :class="getStatusClass(task)">
|
||||
{{
|
||||
task.status === 'pending'
|
||||
? task.method === 'pad'
|
||||
? '待签署·手写板'
|
||||
: '待签署·短信'
|
||||
: statusLabels[task.status]
|
||||
}}
|
||||
{{ task.status === 'pending' ? getPendingLabel(task) : statusLabels[task.status] }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="task-row task-row--document">📄 {{ task.documentName }}</span>
|
||||
@@ -224,6 +225,12 @@ function getVisitClass(type: SigningTaskRecord['visitType']) {
|
||||
background: var(--brand-l);
|
||||
}
|
||||
|
||||
/* 原型 .tg-ipad:平板方式单独一色,不与手写板混用 */
|
||||
.task-status--ipad {
|
||||
color: #2b6cb0;
|
||||
background: #e8f1fb;
|
||||
}
|
||||
|
||||
.task-status--sms {
|
||||
color: #7a4dbb;
|
||||
background: #f3ecfb;
|
||||
|
||||
@@ -39,6 +39,12 @@ import NewSigningTaskDialog from '@/components/signing/NewSigningTaskDialog.vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useLoginStore } from '@/stores/login'
|
||||
import type { SigningFieldProgress, SigningSignatureRequest } from '@/utils/signing-document'
|
||||
import {
|
||||
formatSigningMethodTitle,
|
||||
readSigningMethod,
|
||||
toggleSigningMethod,
|
||||
WORKBENCH_METHOD_FILTERS,
|
||||
} from '@/utils/signing-method'
|
||||
import { downloadSigningPdf } from '@/utils/signing-pdf'
|
||||
|
||||
import OnlineSigningDialog from './components/OnlineSigningDialog.vue'
|
||||
@@ -156,10 +162,10 @@ const statusOptions: SigningFilterOption<SigningFilterForm['status']>[] = [
|
||||
...STATUS_VALUES.map((status) => ({ label: statusLabels[status], value: status })),
|
||||
]
|
||||
|
||||
/** 原型 #qMethod 只有 3 项(手写板 / 短信线上 / Pad),没有公众号 */
|
||||
const methodOptions: SigningFilterOption<SigningFilterForm['method']>[] = [
|
||||
{ label: '全部方式', value: 'all' },
|
||||
{ label: '手写板', value: 'pad' },
|
||||
{ label: '短信线上', value: 'sms' },
|
||||
...WORKBENCH_METHOD_FILTERS.map((option) => ({ label: option.label, value: option.value })),
|
||||
]
|
||||
|
||||
const visitTypeOptions: SigningFilterOption<VisitType | 'all'>[] = [
|
||||
@@ -512,7 +518,7 @@ async function refreshTask(updatedTask: SigningTaskRecord | null, successMessage
|
||||
ElMessage.success(successMessage)
|
||||
}
|
||||
|
||||
async function handleTaskAction(action: SigningTaskAction) {
|
||||
async function handleTaskAction(action: SigningTaskAction, targetMethod?: SigningMethod) {
|
||||
const task = selectedTask.value
|
||||
|
||||
if (!task) {
|
||||
@@ -535,7 +541,7 @@ async function handleTaskAction(action: SigningTaskAction) {
|
||||
|
||||
if (action === 'pad-sign') {
|
||||
if (!isSigningMockEnabled) {
|
||||
ElMessage.info('手写板设备签署接口待接入')
|
||||
ElMessage.info(`${readSigningMethod(task.method).short}设备签署接口待接入`)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -553,9 +559,11 @@ async function handleTaskAction(action: SigningTaskAction) {
|
||||
try {
|
||||
const session: SignDeliveryResponseDto = await createPadSigningSession(task.id)
|
||||
padSessionStatus.value = session.status
|
||||
ElMessage.success('手写板签署会话已创建,请由设备适配器继续采集签名')
|
||||
ElMessage.success(
|
||||
`${readSigningMethod(task.method).short}签署会话已创建,请由设备适配器继续采集签名`,
|
||||
)
|
||||
} catch {
|
||||
ElMessage.error('手写板签署会话创建失败,请稍后重试')
|
||||
ElMessage.error(`${readSigningMethod(task.method).short}签署会话创建失败,请稍后重试`)
|
||||
} finally {
|
||||
padSessionLoading.value = false
|
||||
}
|
||||
@@ -623,10 +631,11 @@ async function handleTaskAction(action: SigningTaskAction) {
|
||||
return
|
||||
}
|
||||
|
||||
const method: SigningMethod = task.method === 'pad' ? 'sms' : 'pad'
|
||||
// 详情页的次按钮会带上目标方式;没有目标时退回原型 switchMethod 的二选一
|
||||
const method: SigningMethod = targetMethod ?? toggleSigningMethod(task.method)
|
||||
await refreshTask(
|
||||
await updateSigningTaskMethod(task.id, method),
|
||||
method === 'sms' ? '已切换为短信线上签署' : '已切换为手写板签署',
|
||||
`已切换为${formatSigningMethodTitle(method)}`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user