From 755608e569159e19882d0f445daf970fedde875f Mon Sep 17 00:00:00 2001 From: Jafeng <2998840497@qq.com> Date: Fri, 6 Feb 2026 17:15:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=81=A5=E5=BA=B7=E6=A1=A3=E6=A1=88?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../archive-detail/health-profile-tab.vue | 109 ++- pages/case/medical-case-form.vue | 718 +++++++----------- pages/case/utils/template.js | 95 +++ pages/case/utils/visit-record.js | 40 + pages/case/visit-record-detail.vue | 243 +++--- utils/api.js | 2 + 6 files changed, 645 insertions(+), 562 deletions(-) create mode 100644 pages/case/utils/template.js create mode 100644 pages/case/utils/visit-record.js diff --git a/pages/case/components/archive-detail/health-profile-tab.vue b/pages/case/components/archive-detail/health-profile-tab.vue index f0374cb..3342079 100644 --- a/pages/case/components/archive-detail/health-profile-tab.vue +++ b/pages/case/components/archive-detail/health-profile-tab.vue @@ -49,18 +49,28 @@ 暂无数据 - - - + + + + + - - diff --git a/pages/case/utils/template.js b/pages/case/utils/template.js new file mode 100644 index 0000000..5af00f9 --- /dev/null +++ b/pages/case/utils/template.js @@ -0,0 +1,95 @@ +function normalizeOptions(options) { + if (!Array.isArray(options)) return []; + if (!options.length) return []; + if (typeof options[0] === 'string') return options.filter((i) => typeof i === 'string'); + if (typeof options[0] === 'object') { + return options + .map((i) => { + const label = i?.label ?? i?.name ?? i?.text ?? i?.title ?? ''; + const value = i?.value ?? i?.id ?? i?.key ?? label; + if (!label && (value === undefined || value === null || value === '')) return null; + return { label: String(label || value), value: String(value) }; + }) + .filter(Boolean); + } + return []; +} + +export function normalizeTemplateItem(item) { + const next = { ...(item || {}) }; + + if (next.operateType === 'custom') next.operateType = 'formCell'; + + const originalType = next.type; + const customTypeMap = { + customerSource: 'select', + customerStage: 'select', + tag: 'multiSelectAndOther', + reference: 'input', + selectWwuser: 'select', + files: 'files', + corpProject: 'select', + diagnosis: 'textarea', + BMI: 'input', + bloodPressure: 'textarea', + selfMultipleDiseases: 'textarea', + }; + if (originalType && customTypeMap[originalType]) { + next.__originType = originalType; + next.type = customTypeMap[originalType]; + } + + const aliasTypeMap = { + text: 'input', + string: 'input', + number: 'input', + integer: 'input', + int: 'input', + }; + if (next.type && aliasTypeMap[next.type]) { + next.type = aliasTypeMap[next.type]; + if (!next.inputType && (originalType === 'number' || originalType === 'integer' || originalType === 'int')) next.inputType = 'number'; + } + + const rawRange = next.range || next.options || next.optionList || next.values || []; + const range = normalizeOptions(rawRange); + + if (next.type === 'select' || next.type === 'selectAndOther' || next.type === 'selectAndImage') { + next.range = range; + } else if (next.type === 'radio') { + // wxapp 目前 radio 组件只支持字符串列表;模板如为对象选项则降级为 select + if (range.length && typeof range[0] === 'object') { + next.type = 'select'; + next.range = range; + } else { + next.range = Array.isArray(rawRange) ? rawRange : []; + } + } + + if (!next.operateType) next.operateType = 'formCell'; + next.required = Boolean(next.required); + + if (next.type === 'input' && (next.wordLimit === undefined || next.wordLimit === null || next.wordLimit === '')) next.wordLimit = 20; + if (next.type === 'textarea' && (next.wordLimit === undefined || next.wordLimit === null || next.wordLimit === '')) next.wordLimit = 200; + return next; +} + +export function unwrapTemplateResponse(res) { + const d = res?.data; + if (d && typeof d === 'object') { + if (d.data && typeof d.data === 'object') return d.data; + return d; + } + return res && typeof res === 'object' ? res : {}; +} + +export function normalizeTemplate(temp) { + const t = temp && typeof temp === 'object' ? { ...temp } : {}; + const list = Array.isArray(t.templateList) ? t.templateList : []; + t.templateList = list + .filter((i) => i && i.fieldStatus !== 'disable') + .filter((i) => i.operateType !== 'onlyRead') + .map(normalizeTemplateItem); + return t; +} + diff --git a/pages/case/utils/visit-record.js b/pages/case/utils/visit-record.js new file mode 100644 index 0000000..721c1ef --- /dev/null +++ b/pages/case/utils/visit-record.js @@ -0,0 +1,40 @@ +function isEmpty(v) { + if (v === null || v === undefined) return true; + if (Array.isArray(v)) return v.length === 0; + if (typeof v === 'string') return v.trim() === ''; + return false; +} + +const ALIAS_MAP = { + outpatient: { + diagnosisName: 'diagnosis', + medicalHistorySummary: 'medicalHistory', + }, + inhospital: { + diagnosisName: 'diagnosis', + medicalHistorySummary: 'medicalHistory', + operationDate: 'surgeryDate', + operation: 'surgeryName', + }, + physicalExaminationTemplate: { + inspectTime: 'inspectDate', + inspectSummary: 'summary', + }, + preConsultation: { + presentIllnessHistory: 'presentIllness', + pastMedicalHistory: 'pastHistory', + }, +}; + +export function normalizeVisitRecordFormData(templateType, raw) { + const input = raw && typeof raw === 'object' ? raw : {}; + const out = { ...input }; + const map = ALIAS_MAP[String(templateType || '')] || {}; + + Object.entries(map).forEach(([from, to]) => { + if (isEmpty(out[to]) && !isEmpty(out[from])) out[to] = out[from]; + }); + + return out; +} + diff --git a/pages/case/visit-record-detail.vue b/pages/case/visit-record-detail.vue index 0c283cd..d90669c 100644 --- a/pages/case/visit-record-detail.vue +++ b/pages/case/visit-record-detail.vue @@ -4,11 +4,11 @@ - {{ template?.templateName || '健康档案' }} + {{ titleText || '健康档案' }} - + @@ -19,7 +19,8 @@ - + + PDF × @@ -49,10 +50,12 @@ import { onLoad } from '@dcloudio/uni-app'; import dayjs from 'dayjs'; import { storeToRefs } from 'pinia'; import FormTemplate from '@/components/form-template/index.vue'; -import { getVisitRecordTemplate } from './components/archive-detail/templates'; import api from '@/utils/api'; +import { uploadFile } from '@/utils/file'; import useAccountStore from '@/store/account'; import { toast, confirm, loading as uniLoading, hideLoading } from '@/utils/widget'; +import { normalizeVisitRecordFormData } from './utils/visit-record'; +import { normalizeTemplate, unwrapTemplateResponse } from './utils/template'; const accountStore = useAccountStore(); const { account, doctorInfo } = storeToRefs(accountStore); @@ -84,14 +87,17 @@ function getCorpId() { const memberId = ref(''); const recordId = ref(''); const templateType = ref(''); -const customerName = ref(''); -const template = computed(() => getVisitRecordTemplate(templateType.value)); +const temp = ref(null); +const titleText = computed(() => { + const t = temp.value || {}; + return String(t.name || t.templateName || t.templateTypeName || '').trim(); +}); const detail = ref({}); const form = reactive({}); const forms = computed(() => ({ ...detail.value, ...form })); const showItems = computed(() => { - const list = template.value?.templateList || []; + const list = temp.value?.templateList || []; // referenceField 兼容(与 mobile 一致) return list.filter((i) => { if (i?.type === 'files') return false; @@ -103,7 +109,7 @@ const showItems = computed(() => { }); const hasFilesField = computed(() => { - const list = template.value?.templateList || []; + const list = temp.value?.templateList || []; return list.some((i) => i && (i.type === 'files' || i.title === 'files')); }); @@ -119,30 +125,43 @@ function ensureFilesField() { form.files = []; } +async function loadTemplate(t) { + const corpId = getCorpId(); + if (!corpId) return null; + try { + const res = await api('getCurrentTemplate', { corpId, templateType: t }); + if (!res?.success) { + toast(res?.message || '获取模板失败'); + return null; + } + const raw = unwrapTemplateResponse(res); + return normalizeTemplate(raw); + } catch (e) { + console.error('loadTemplate error:', e); + toast('获取模板失败'); + return null; + } +} + onLoad(async (options) => { memberId.value = options?.memberId || options?.archiveId || ''; recordId.value = options?.id || ''; templateType.value = options?.type || ''; - customerName.value = decodeURIComponent(options?.customerName || ''); if (recordId.value) { await getDetail(); } else { if (!templateType.value) templateType.value = 'outpatient'; + temp.value = await loadTemplate(templateType.value); + if (temp.value?.templateType) templateType.value = String(temp.value.templateType); ensureFilesField(); - // 门诊记录默认今日日期 - if (templateType.value === 'outpatient') { - form.visitTime = dayjs().format('YYYY-MM-DD'); - } - // 住院记录默认今日日期 - if (templateType.value === 'inhospital') { - form.inhosDate = dayjs().format('YYYY-MM-DD'); - } - // 体检记录默认今日日期 - if (templateType.value === 'physicalExaminationTemplate') { - form.inspectDate = dayjs().format('YYYY-MM-DD'); - } + + // 默认填充模板时间字段 + const timeKey = temp.value?.service?.timeTitle || ''; + if (timeKey && !form[timeKey]) form[timeKey] = dayjs().format('YYYY-MM-DD'); } + + if (titleText.value) uni.setNavigationBarTitle({ title: titleText.value }); }); async function getDetail() { @@ -162,14 +181,14 @@ async function getDetail() { const record = res?.record || res?.data?.record || null; if (res?.success && record) { templateType.value = record.templateType || record.medicalType || templateType.value; - - // 兼容模板字段:wxapp 使用 diagnosis,但接口通常返回 diagnosisName - if ((record.medicalType === 'outpatient' || record.medicalType === 'inhospital') && !record.diagnosis && record.diagnosisName) { - record.diagnosis = record.diagnosisName; - } - - detail.value = record; + detail.value = normalizeVisitRecordFormData(templateType.value, record); ensureFilesField(); + // 详情可能返回真实 templateType:与模板保持一致 + if (!temp.value || temp.value?.templateType !== templateType.value) { + temp.value = await loadTemplate(templateType.value); + if (temp.value?.templateType) templateType.value = String(temp.value.templateType); + if (titleText.value) uni.setNavigationBarTitle({ title: titleText.value }); + } } else { toast(res.message || '加载失败'); } @@ -185,7 +204,7 @@ function onChange({ title, value }) { const item = showItems.value.find((i) => i.title === title); if (!item) return; // 关联字段变化时清理被关联字段(与 mobile 行为一致) - const relat = (template.value?.templateList || []).filter((i) => i.referenceField === title); + const relat = (temp.value?.templateList || []).filter((i) => i.referenceField === title); relat.forEach((i) => (form[i.title] = '')); } @@ -224,7 +243,7 @@ async function save() { } // sortTime:使用模板中的时间字段 - const sortTimeKey = template.value?.service?.timeTitle || ''; + const sortTimeKey = temp.value?.service?.timeTitle || ''; if (sortTimeKey && form[sortTimeKey] && dayjs(form[sortTimeKey]).isValid()) { params.sortTime = dayjs(form[sortTimeKey]).valueOf(); } else { @@ -252,65 +271,84 @@ async function save() { } } -function remove() { - confirm('确定删除当前记录?', async () => { - if (!memberId.value || !recordId.value) return toast('缺少必要信息'); - await ensureDoctor(); - const corpId = getCorpId(); - if (!corpId) return toast('缺少必要信息'); - uniLoading('删除中...'); - try { - const res = await api('removeMedicalRecord', { - corpId, - memberId: memberId.value, - medicalType: templateType.value, - _id: recordId.value, - }); - hideLoading(); - if (res.success) { - uni.$emit('archive-detail:visit-record-changed'); - toast(res.message || '已删除'); - setTimeout(() => uni.navigateBack(), 300); - } else { - toast(res.message || '删除失败'); - } - } catch (error) { - hideLoading(); - console.error('remove error:', error); - toast('删除失败'); +async function remove() { + try { + await confirm('确定删除当前记录?'); + } catch { + return; + } + if (!memberId.value || !recordId.value) return toast('缺少必要信息'); + await ensureDoctor(); + const corpId = getCorpId(); + if (!corpId) return toast('缺少必要信息'); + uniLoading('删除中...'); + try { + const res = await api('removeMedicalRecord', { + corpId, + memberId: memberId.value, + medicalType: templateType.value, + _id: recordId.value, + }); + hideLoading(); + if (res.success) { + uni.$emit('archive-detail:visit-record-changed'); + toast(res.message || '已删除'); + setTimeout(() => uni.navigateBack(), 300); + } else { + toast(res.message || '删除失败'); } - }); + } catch (error) { + hideLoading(); + console.error('remove error:', error); + toast('删除失败'); + } } -function addFiles() { - const fileConfig = template.value?.templateList?.find(i => i.type === 'files'); - const maxSize = fileConfig?.maxSize || 5; // MB - const accept = fileConfig?.accept || 'pdf'; +function isPdfUrl(url) { + const u = String(url || '').toLowerCase(); + return u.includes('.pdf') || u.startsWith('data:application/pdf'); +} - uni.chooseMessageFile({ - count: 9, - type: 'file', - extension: [accept], - success: (res) => { - const files = Array.isArray(res.tempFiles) ? res.tempFiles : []; - const maxBytes = maxSize * 1024 * 1024; - - // 验证文件大小 - const invalidFiles = files.filter(f => f.size > maxBytes); - if (invalidFiles.length > 0) { - toast(`文件大小不能超过${maxSize}M`); - return; - } +async function addFiles() { + const fileConfig = temp.value?.templateList?.find((i) => i && (i.type === 'files' || i.title === 'files')) || {}; + const maxSize = Number(fileConfig?.maxSize || 5) || 5; // MB + const accept = String(fileConfig?.accept || 'pdf') || 'pdf'; - const next = files.map((f) => ({ - url: f.path, - name: f.name || '', - size: f.size - })); - const cur = Array.isArray(forms.value.files) ? forms.value.files : []; - form.files = [...cur, ...next]; - }, + const chooseRes = await new Promise((resolve) => { + uni.chooseMessageFile({ + count: 9, + type: 'file', + extension: accept ? [accept] : undefined, + success: (res) => resolve(res), + fail: () => resolve(null), + }); }); + const files = Array.isArray(chooseRes?.tempFiles) ? chooseRes.tempFiles : []; + if (!files.length) return; + + const maxBytes = maxSize * 1024 * 1024; + const invalidFiles = files.filter((f) => f && f.size > maxBytes); + if (invalidFiles.length > 0) { + toast(`文件大小不能超过${maxSize}M`); + return; + } + + const cur = Array.isArray(forms.value.files) ? forms.value.files : []; + uniLoading('上传中...'); + try { + const uploaded = []; + for (const f of files) { + const url = await uploadFile(f.path); + if (!url) { + toast('上传失败'); + continue; + } + uploaded.push({ url, name: f.name || '', size: f.size }); + } + form.files = [...cur, ...uploaded]; + } finally { + hideLoading(); + } } function removeFile(idx) { @@ -319,8 +357,32 @@ function removeFile(idx) { } function previewFile(idx) { - const urls = fileList.value.map((i) => i.url); - uni.previewImage({ urls, current: urls[idx] }); + const f = fileList.value[idx]; + const url = f?.url ? String(f.url) : ''; + if (!url) return; + if (!isPdfUrl(url)) { + const urls = fileList.value.map((i) => i.url); + uni.previewImage({ urls, current: url }); + return; + } + uniLoading('打开中...'); + uni.downloadFile({ + url, + success: (res) => { + hideLoading(); + const filePath = res?.tempFilePath; + if (!filePath) return toast('打开失败'); + uni.openDocument({ + filePath, + showMenu: true, + fail: () => toast('打开失败'), + }); + }, + fail: () => { + hideLoading(); + toast('打开失败'); + }, + }); } @@ -393,6 +455,17 @@ function previewFile(idx) { width: 100%; height: 100%; } +.upload-pdf { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + font-size: 34rpx; + font-weight: 700; + color: #0877F1; + background: #eef6ff; +} .upload-remove { position: absolute; right: 0; diff --git a/utils/api.js b/utils/api.js index dd29c2e..687381f 100644 --- a/utils/api.js +++ b/utils/api.js @@ -10,6 +10,8 @@ const urlsConfig = { getTeamData: 'getTeamData', getTeamBymember: 'getTeamBymember', getCurrentTemplate: 'getCurrentTemplate', + getTemplateGroup: 'getTemplateGroup', + getTemplateListByTemptype: 'getTemplateListByTemptype', wxAppLogin: 'wxAppLogin', getDeptList: 'getRealDeptList', getHospitalList: 'getRealHospital',