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 @@
-
+
@@ -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',