feat(clinical-web): 新增签署任务弹窗与文档库管理页面
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
import { computed, reactive, ref, toValue, type MaybeRefOrGetter } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import { createSigningTask, getPatientProfile, getSigningTemplates } 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 smsPhone = ref('')
|
||||
const patientPhoneRevealed = ref(false)
|
||||
const loadingTemplates = ref(false)
|
||||
const locating = ref(false)
|
||||
const submitting = ref(false)
|
||||
|
||||
const errors = reactive({
|
||||
patient: '',
|
||||
visit: '',
|
||||
document: '',
|
||||
phone: '',
|
||||
})
|
||||
|
||||
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.phone = ''
|
||||
}
|
||||
|
||||
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'
|
||||
smsPhone.value = ''
|
||||
patientPhoneRevealed.value = false
|
||||
clearErrors()
|
||||
}
|
||||
|
||||
function resetForNextTask() {
|
||||
const initialTemplate = getInitialTemplate()
|
||||
|
||||
documentSearch.value = ''
|
||||
selectedTemplateId.value = initialTemplate?.id ?? ''
|
||||
method.value = initialTemplate?.supportedMethods[0] ?? method.value
|
||||
errors.document = ''
|
||||
}
|
||||
|
||||
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 = '未找到该患者,请核对输入内容'
|
||||
smsPhone.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
patient.value = profile
|
||||
visits.value = profile.visits
|
||||
selectedVisitId.value =
|
||||
profile.visits.find((visit) => visit.isCurrent)?.id ?? profile.visits[0]?.id ?? ''
|
||||
smsPhone.value = profile.phone
|
||||
} 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
|
||||
errors.phone = ''
|
||||
}
|
||||
|
||||
function maskPhone(value: string) {
|
||||
if (value.length < 7) {
|
||||
return value
|
||||
}
|
||||
|
||||
return `${value.slice(0, 3)}****${value.slice(-4)}`
|
||||
}
|
||||
|
||||
function togglePhone() {
|
||||
patientPhoneRevealed.value = !patientPhoneRevealed.value
|
||||
ElMessage.info('明文查看已记录审计日志')
|
||||
}
|
||||
|
||||
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 (method.value === 'sms' && !smsPhone.value.trim()) {
|
||||
errors.phone = '请确认短信接收手机号'
|
||||
valid = false
|
||||
}
|
||||
|
||||
return valid
|
||||
}
|
||||
|
||||
async function submit(): Promise<SigningTaskRecord | null> {
|
||||
if (!validate() || !patient.value || !selectedVisit.value || !selectedTemplate.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
|
||||
try {
|
||||
return await createSigningTask({
|
||||
campus: toValue(options.campus),
|
||||
patientId: patient.value.id,
|
||||
visitId: selectedVisit.value.id,
|
||||
documentId: selectedTemplate.value.id,
|
||||
documentName: selectedTemplate.value.name,
|
||||
method: method.value,
|
||||
visitType: selectedVisit.value.type,
|
||||
visitNo: selectedVisit.value.visitNo,
|
||||
department: selectedVisit.value.department,
|
||||
phone: method.value === 'sms' ? smsPhone.value.trim() : undefined,
|
||||
})
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
documentSearch,
|
||||
errors,
|
||||
filteredTemplateGroups,
|
||||
loadingTemplates,
|
||||
loadTemplates,
|
||||
locatePatient,
|
||||
locating,
|
||||
maskPhone,
|
||||
method,
|
||||
patient,
|
||||
patientId,
|
||||
patientPhoneRevealed,
|
||||
resetForNextTask,
|
||||
resetForm,
|
||||
selectMethod,
|
||||
selectTemplate,
|
||||
selectVisit,
|
||||
selectedTemplate,
|
||||
selectedTemplateId,
|
||||
selectedVisit,
|
||||
selectedVisitId,
|
||||
smsPhone,
|
||||
submit,
|
||||
submitting,
|
||||
templates,
|
||||
togglePhone,
|
||||
visits,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user