317 lines
8.0 KiB
TypeScript
317 lines
8.0 KiB
TypeScript
import { computed, reactive, ref, toValue, type MaybeRefOrGetter } from 'vue'
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
import { sendSigningSms } from '@/api/workbench/deliveries'
|
|
import {
|
|
createSigningTask,
|
|
getPatientProfile,
|
|
getSigningTemplates,
|
|
isSigningMockEnabled,
|
|
} from '@/api/workbench/signing'
|
|
import type {
|
|
PatientProfile,
|
|
PatientVisit,
|
|
SigningMethod,
|
|
SigningTaskRecord,
|
|
SigningTemplate,
|
|
WorkbenchCampus,
|
|
} from '@/api/workbench/types'
|
|
|
|
export interface UseSigningTaskFormOptions {
|
|
campus: MaybeRefOrGetter<WorkbenchCampus>
|
|
initialTemplate?: MaybeRefOrGetter<SigningTemplate | null | undefined>
|
|
}
|
|
|
|
interface TemplateCategoryGroup {
|
|
name: string
|
|
templates: SigningTemplate[]
|
|
}
|
|
|
|
interface TemplateDepartmentGroup {
|
|
name: string
|
|
categories: TemplateCategoryGroup[]
|
|
}
|
|
|
|
export function useSigningTaskForm(options: UseSigningTaskFormOptions) {
|
|
const patientId = ref('')
|
|
const patient = ref<PatientProfile | null>(null)
|
|
const visits = ref<PatientVisit[]>([])
|
|
const templates = ref<SigningTemplate[]>([])
|
|
const documentSearch = ref('')
|
|
const selectedTemplateId = ref('')
|
|
const selectedVisitId = ref('')
|
|
const method = ref<SigningMethod>('pad')
|
|
const smsDestination = ref('')
|
|
const loadingTemplates = ref(false)
|
|
const locating = ref(false)
|
|
const submitting = ref(false)
|
|
|
|
const errors = reactive({
|
|
patient: '',
|
|
visit: '',
|
|
document: '',
|
|
sms: '',
|
|
})
|
|
|
|
const selectedVisit = computed(
|
|
() => visits.value.find((visit) => visit.id === selectedVisitId.value) ?? null,
|
|
)
|
|
|
|
const selectedTemplate = computed(
|
|
() => templates.value.find((template) => template.id === selectedTemplateId.value) ?? null,
|
|
)
|
|
|
|
const filteredTemplateGroups = computed<TemplateDepartmentGroup[]>(() => {
|
|
const keyword = documentSearch.value.trim().toLowerCase()
|
|
const grouped = new Map<string, Map<string, SigningTemplate[]>>()
|
|
|
|
templates.value.forEach((template) => {
|
|
const matches =
|
|
!keyword ||
|
|
[template.name, template.code, template.department, template.category]
|
|
.join(' ')
|
|
.toLowerCase()
|
|
.includes(keyword)
|
|
|
|
if (!matches) {
|
|
return
|
|
}
|
|
|
|
const categories = grouped.get(template.department) ?? new Map<string, SigningTemplate[]>()
|
|
const categoryTemplates = categories.get(template.category) ?? []
|
|
categoryTemplates.push(template)
|
|
categories.set(template.category, categoryTemplates)
|
|
grouped.set(template.department, categories)
|
|
})
|
|
|
|
return [...grouped.entries()].map(([department, categories]) => ({
|
|
name: department,
|
|
categories: [...categories.entries()].map(([category, categoryTemplates]) => ({
|
|
name: category,
|
|
templates: categoryTemplates,
|
|
})),
|
|
}))
|
|
})
|
|
|
|
function getInitialTemplate() {
|
|
return options.initialTemplate ? (toValue(options.initialTemplate) ?? null) : null
|
|
}
|
|
|
|
function includeInitialTemplate() {
|
|
const initialTemplate = getInitialTemplate()
|
|
|
|
if (
|
|
initialTemplate &&
|
|
!templates.value.some((template) => template.id === initialTemplate.id)
|
|
) {
|
|
templates.value = [initialTemplate, ...templates.value]
|
|
}
|
|
}
|
|
|
|
function clearErrors() {
|
|
errors.patient = ''
|
|
errors.visit = ''
|
|
errors.document = ''
|
|
errors.sms = ''
|
|
}
|
|
|
|
function resetForm() {
|
|
const initialTemplate = getInitialTemplate()
|
|
|
|
patientId.value = ''
|
|
patient.value = null
|
|
visits.value = []
|
|
documentSearch.value = ''
|
|
selectedTemplateId.value = initialTemplate?.id ?? ''
|
|
selectedVisitId.value = ''
|
|
method.value = initialTemplate?.supportedMethods[0] ?? 'pad'
|
|
smsDestination.value = ''
|
|
clearErrors()
|
|
}
|
|
|
|
function resetForNextTask() {
|
|
const initialTemplate = getInitialTemplate()
|
|
|
|
documentSearch.value = ''
|
|
selectedTemplateId.value = initialTemplate?.id ?? ''
|
|
method.value = initialTemplate?.supportedMethods[0] ?? method.value
|
|
smsDestination.value = ''
|
|
errors.document = ''
|
|
errors.sms = ''
|
|
}
|
|
|
|
async function loadTemplates() {
|
|
if (!templates.value.length) {
|
|
loadingTemplates.value = true
|
|
|
|
try {
|
|
templates.value = await getSigningTemplates()
|
|
} catch {
|
|
ElMessage.error('文档模板加载失败,请稍后重试')
|
|
} finally {
|
|
loadingTemplates.value = false
|
|
}
|
|
}
|
|
|
|
includeInitialTemplate()
|
|
}
|
|
|
|
async function locatePatient() {
|
|
const keyword = patientId.value.trim()
|
|
errors.patient = ''
|
|
errors.visit = ''
|
|
patient.value = null
|
|
visits.value = []
|
|
selectedVisitId.value = ''
|
|
selectedTemplateId.value = getInitialTemplate()?.id ?? ''
|
|
|
|
if (!keyword) {
|
|
errors.patient = '请输入患者 ID、门诊号或住院号'
|
|
return
|
|
}
|
|
|
|
locating.value = true
|
|
|
|
try {
|
|
const profile = await getPatientProfile(keyword)
|
|
|
|
if (!profile) {
|
|
errors.patient = '未找到该患者,请核对输入内容'
|
|
return
|
|
}
|
|
|
|
patient.value = profile
|
|
visits.value = profile.visits
|
|
selectedVisitId.value =
|
|
profile.visits.find((visit) => visit.isCurrent)?.id ?? profile.visits[0]?.id ?? ''
|
|
} catch {
|
|
errors.patient = '患者信息查询失败,请稍后重试'
|
|
} finally {
|
|
locating.value = false
|
|
}
|
|
}
|
|
|
|
function selectVisit(visit: PatientVisit) {
|
|
selectedVisitId.value = visit.id
|
|
errors.visit = ''
|
|
}
|
|
|
|
function selectTemplate(template: SigningTemplate) {
|
|
selectedTemplateId.value = template.id
|
|
errors.document = ''
|
|
|
|
if (!template.supportedMethods.includes(method.value)) {
|
|
method.value = template.supportedMethods[0] ?? 'pad'
|
|
}
|
|
}
|
|
|
|
function selectMethod(nextMethod: SigningMethod) {
|
|
if (selectedTemplate.value && !selectedTemplate.value.supportedMethods.includes(nextMethod)) {
|
|
return
|
|
}
|
|
|
|
method.value = nextMethod
|
|
}
|
|
|
|
function maskPhone(value: string) {
|
|
if (value.length < 7) {
|
|
return value
|
|
}
|
|
|
|
return `${value.slice(0, 3)}****${value.slice(-4)}`
|
|
}
|
|
|
|
function validate() {
|
|
clearErrors()
|
|
let valid = true
|
|
|
|
if (!patient.value) {
|
|
errors.patient = '请先定位患者'
|
|
valid = false
|
|
}
|
|
|
|
if (!selectedVisit.value) {
|
|
errors.visit = '请选择关联就诊'
|
|
valid = false
|
|
}
|
|
|
|
if (!selectedTemplate.value) {
|
|
errors.document = '请至少选择一份文档'
|
|
valid = false
|
|
}
|
|
|
|
if (!isSigningMockEnabled && method.value === 'sms') {
|
|
const destination = smsDestination.value.trim().replace(/\s+/g, '')
|
|
|
|
if (!/^\+?[1-9]\d{6,14}$/.test(destination)) {
|
|
errors.sms = '请输入完整的手机号或 E.164 地址,才能发送短信'
|
|
valid = false
|
|
}
|
|
}
|
|
|
|
return valid
|
|
}
|
|
|
|
async function submit(): Promise<SigningTaskRecord | null> {
|
|
if (!validate() || !patient.value || !selectedVisit.value || !selectedTemplate.value) {
|
|
return null
|
|
}
|
|
|
|
submitting.value = true
|
|
|
|
try {
|
|
const task = await createSigningTask({
|
|
campus: toValue(options.campus),
|
|
patientId: patient.value.id,
|
|
visitId: selectedVisit.value.id,
|
|
documentId: selectedTemplate.value.id,
|
|
documentName: selectedTemplate.value.name,
|
|
templateVersionId: selectedTemplate.value.versionId,
|
|
method: method.value,
|
|
visitType: selectedVisit.value.type,
|
|
visitNo: selectedVisit.value.visitNo,
|
|
department: selectedVisit.value.department,
|
|
})
|
|
|
|
if (!isSigningMockEnabled && method.value === 'sms') {
|
|
await sendSigningSms(task.id, {
|
|
destination: smsDestination.value.trim().replace(/\s+/g, ''),
|
|
templateCode: 'SIGN_LINK',
|
|
})
|
|
}
|
|
|
|
return task
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
documentSearch,
|
|
errors,
|
|
filteredTemplateGroups,
|
|
loadingTemplates,
|
|
loadTemplates,
|
|
locatePatient,
|
|
locating,
|
|
maskPhone,
|
|
method,
|
|
patient,
|
|
patientId,
|
|
resetForNextTask,
|
|
resetForm,
|
|
selectMethod,
|
|
selectTemplate,
|
|
selectVisit,
|
|
selectedTemplate,
|
|
selectedTemplateId,
|
|
selectedVisit,
|
|
selectedVisitId,
|
|
smsDestination,
|
|
submit,
|
|
submitting,
|
|
templates,
|
|
visits,
|
|
}
|
|
}
|