diff --git a/.env.localhost b/.env.localhost
index 24011fd..9928b9d 100644
--- a/.env.localhost
+++ b/.env.localhost
@@ -1,3 +1,3 @@
-MP_API_BASE_URL=http://192.168.60.2:8080
+MP_API_BASE_URL=http://localhost:8080
MP_CACHE_PREFIX=development
MP_WX_APP_ID=wx93af55767423938e
diff --git a/components/archive-detail/customer-profile-tab.vue b/components/archive-detail/customer-profile-tab.vue
new file mode 100644
index 0000000..6b95bfc
--- /dev/null
+++ b/components/archive-detail/customer-profile-tab.vue
@@ -0,0 +1,410 @@
+
+
+
+
+
+ {{ t.label }}
+
+
+
+
+
+ 基本信息
+
+
+
+
+ 姓名
+ {{ data.name || '-' }}
+
+
+
+ 性别
+ {{ data.sex || '-' }}
+
+ {{ draft.sex || '请选择' }}
+
+
+
+ 年龄
+ {{ data.age || '-' }}
+
+
+
+ 联系电话
+ {{ data.mobile || '-' }}
+
+
+
+
+
+
+
+ 内部信息
+
+
+
+
+ 院内来源
+
+ {{ data.creator || '点击查看' }}
+
+
+
+
+ 备注
+ {{ data.notes || '-' }}
+
+
+
+
+
+
+
+ 行为画像
+
+
+
+ 门诊号
+ {{ data.outpatientNo || '-' }}
+
+
+ 住院号
+ {{ data.inpatientNo || '-' }}
+
+
+ 病案号
+ {{ data.medicalRecordNo || '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/archive-detail/follow-up-manage-tab.vue b/components/archive-detail/follow-up-manage-tab.vue
new file mode 100644
index 0000000..c357dbe
--- /dev/null
+++ b/components/archive-detail/follow-up-manage-tab.vue
@@ -0,0 +1,623 @@
+
+
+
+
+
+
+
+ 我的
+
+
+
+
+
+
+ {{ t.label }}
+
+
+
+
+
+
+
+
+
+
+ 共{{ total }}条
+
+
+
+
+
+ 计划日期: {{ i.planDate }}
+ {{ i.executorName }}({{ i.executeTeamName }})
+
+
+
+ {{ i.eventTypeLabel }}
+ {{ i.eventStatusLabel }}
+
+ {{ i.taskContent || '暂无内容' }}
+ 【处理结果】 {{ i.result || '' }}
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/archive-detail/health-profile-tab.vue b/components/archive-detail/health-profile-tab.vue
new file mode 100644
index 0000000..3c2d17b
--- /dev/null
+++ b/components/archive-detail/health-profile-tab.vue
@@ -0,0 +1,311 @@
+
+
+
+
+
+
+ {{ currentType.value === 'ALL' ? '档案类型' : currentType.name }}
+
+
+
+
+
+
+ {{ date || '时间筛选' }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ r.dateStr }}
+ {{ r.tempName }}
+ 外院
+
+
+
+ {{ row.k }}:
+ {{ row.v }}
+
+
+ 摘要:
+ {{ r.summary || '暂无内容' }}
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/archive-detail/mock.js b/components/archive-detail/mock.js
new file mode 100644
index 0000000..c139225
--- /dev/null
+++ b/components/archive-detail/mock.js
@@ -0,0 +1,333 @@
+import dayjs from 'dayjs';
+
+const DB_KEY = 'ykt_case_archive_detail_mockdb_v1';
+
+export const VISIT_RECORD_TEMPLATES = [
+ {
+ templateType: 'outpatient',
+ templateName: '门诊记录',
+ templateList: [
+ { title: 'visitTime', name: '就诊日期', type: 'date', required: true, format: 'YYYY-MM-DD' },
+ { title: 'corpName', name: '就诊机构', type: 'input', required: false, wordLimit: 30, inputType: 'text' },
+ { title: 'deptName', name: '科室', type: 'input', required: false, wordLimit: 30, inputType: 'text' },
+ { title: 'doctor', name: '医生', type: 'input', required: false, wordLimit: 30, inputType: 'text' },
+ { title: 'diagnosisName', name: '诊断', type: 'textarea', required: false, wordLimit: 200 },
+ { title: 'summary', name: '摘要', type: 'textarea', required: false, wordLimit: 200 },
+ ],
+ },
+ {
+ templateType: 'inhospital',
+ templateName: '住院记录',
+ templateList: [
+ { title: 'inhospitalDate', name: '入院日期', type: 'date', required: true, format: 'YYYY-MM-DD' },
+ { title: 'corpName', name: '住院机构', type: 'input', required: false, wordLimit: 30, inputType: 'text' },
+ { title: 'diagnosisName', name: '诊断', type: 'textarea', required: false, wordLimit: 200 },
+ { title: 'summary', name: '摘要', type: 'textarea', required: false, wordLimit: 200 },
+ ],
+ },
+ {
+ templateType: 'physicalExaminationTemplate',
+ templateName: '体检记录',
+ templateList: [
+ { title: 'inspectDate', name: '体检日期', type: 'date', required: true, format: 'YYYY-MM-DD' },
+ { title: 'corpName', name: '体检机构', type: 'input', required: false, wordLimit: 30, inputType: 'text' },
+ { title: 'inspectPakageName', name: '体检套餐', type: 'input', required: false, wordLimit: 50, inputType: 'text' },
+ { title: 'positiveFind', name: '阳性发现', type: 'textarea', required: false, wordLimit: 300 },
+ { title: 'summary', name: '摘要', type: 'textarea', required: false, wordLimit: 200 },
+ ],
+ },
+];
+
+export function getVisitRecordTemplates() {
+ return VISIT_RECORD_TEMPLATES.map((i) => ({ templateType: i.templateType, name: i.templateName, templateList: i.templateList }));
+}
+
+export function getVisitRecordTemplate(templateType) {
+ return VISIT_RECORD_TEMPLATES.find((i) => i.templateType === templateType) || null;
+}
+
+function safeParse(json) {
+ try {
+ return JSON.parse(json);
+ } catch {
+ return null;
+ }
+}
+
+function getDb() {
+ const raw = uni.getStorageSync(DB_KEY);
+ const db = raw && typeof raw === 'string' ? safeParse(raw) : raw;
+ const next = db && typeof db === 'object' ? db : {};
+ next.visitRecordsByArchiveId = next.visitRecordsByArchiveId || {};
+ next.serviceRecordsByArchiveId = next.serviceRecordsByArchiveId || {};
+ next.followupsByArchiveId = next.followupsByArchiveId || {};
+ return next;
+}
+
+function setDb(db) {
+ uni.setStorageSync(DB_KEY, JSON.stringify(db));
+}
+
+function uid(prefix) {
+ return `${prefix}_${Date.now()}_${Math.random().toString(16).slice(2)}`;
+}
+
+export function ensureSeed(archiveId, archive) {
+ if (!archiveId) return;
+ const db = getDb();
+
+ if (!Array.isArray(db.visitRecordsByArchiveId[archiveId]) || db.visitRecordsByArchiveId[archiveId].length === 0) {
+ const now = Date.now();
+ db.visitRecordsByArchiveId[archiveId] = [
+ {
+ _id: uid('mr'),
+ medicalType: 'outpatient',
+ tempName: '门诊记录',
+ templateType: 'outpatient',
+ sortTime: now - 1000 * 60 * 60 * 24 * 2,
+ visitTime: dayjs(now - 1000 * 60 * 60 * 24 * 2).format('YYYY-MM-DD'),
+ corpName: '某某医院',
+ deptName: '口腔科',
+ doctor: '李医生',
+ diagnosisName: '牙列不齐(mock)',
+ summary: '初诊:拍片、取模,制定治疗方案。',
+ createTime: now - 1000 * 60 * 60 * 24 * 2,
+ creatorName: '李医生',
+ },
+ {
+ _id: uid('mr'),
+ medicalType: 'inhospital',
+ tempName: '住院记录',
+ templateType: 'inhospital',
+ sortTime: now - 1000 * 60 * 60 * 24 * 15,
+ inhospitalDate: dayjs(now - 1000 * 60 * 60 * 24 * 15).format('YYYY-MM-DD'),
+ corpName: '某某医院',
+ diagnosisName: '术后复查(mock)',
+ summary: '复诊:术后复查,恢复良好。',
+ createTime: now - 1000 * 60 * 60 * 24 * 15,
+ creatorName: '王护士',
+ },
+ ];
+ }
+
+ if (!Array.isArray(db.serviceRecordsByArchiveId[archiveId]) || db.serviceRecordsByArchiveId[archiveId].length === 0) {
+ const now = Date.now();
+ db.serviceRecordsByArchiveId[archiveId] = Array.from({ length: 18 }).map((_, idx) => {
+ const eventType =
+ idx % 5 === 0 ? 'questionnaire' : idx % 4 === 0 ? 'article' : idx % 3 === 0 ? 'sms' : 'phone';
+ const executionTime = now - 1000 * 60 * 60 * (idx * 6 + 3);
+ const executeTeamId = idx % 2 === 0 ? 'team_1' : 'team_2';
+ const executeTeamName = executeTeamId === 'team_1' ? '口腔一科(示例)' : '正畸团队(示例)';
+ return {
+ _id: uid('sr'),
+ eventType,
+ taskContent: `服务内容示例 #${idx + 1}:这里是任务描述,支持长文本展开收起。`,
+ result: idx % 7 === 0 ? '已联系患者,已确认到院时间。' : '',
+ executorName: idx % 2 === 0 ? '李医生' : '王护士',
+ executeTeamId,
+ executeTeamName,
+ executionTime,
+ pannedEventSendFile:
+ eventType === 'article'
+ ? { type: 'article', url: 'https://example.com/article/1' }
+ : eventType === 'questionnaire'
+ ? { type: 'questionnaire', surveryId: 'q_1' }
+ : null,
+ archiveName: archive?.name || '',
+ };
+ });
+ }
+
+ if (!Array.isArray(db.followupsByArchiveId[archiveId]) || db.followupsByArchiveId[archiveId].length === 0) {
+ const now = Date.now();
+ db.followupsByArchiveId[archiveId] = Array.from({ length: 22 }).map((_, idx) => {
+ const plannedExecutionTime = now + 1000 * 60 * 60 * 24 * ((idx % 9) - 2);
+ const createTime = now - 1000 * 60 * 60 * (idx * 5 + 2);
+ const statusPool = ['processing', 'notStart', 'treated', 'cancelled', 'expired'];
+ const status = statusPool[idx % statusPool.length];
+ const eventTypePool = ['followup', 'revisit', 'questionnaire', 'other'];
+ const eventType = eventTypePool[idx % eventTypePool.length];
+ const executeTeamId = idx % 2 === 0 ? 'team_1' : 'team_2';
+ const executeTeamName = executeTeamId === 'team_1' ? '口腔一科(示例)' : '正畸团队(示例)';
+ return {
+ _id: uid('td'),
+ plannedExecutionTime,
+ planDate: dayjs(plannedExecutionTime).format('YYYY-MM-DD'),
+ createTime,
+ createTimeStr: dayjs(createTime).format('YYYY-MM-DD HH:mm'),
+ executorName: idx % 2 === 0 ? '李医生' : '王护士',
+ executeTeamId,
+ executeTeamName,
+ creatorName: idx % 3 === 0 ? '系统' : '管理员A',
+ status,
+ eventType,
+ eventTypeLabel:
+ eventType === 'followup' ? '回访' : eventType === 'revisit' ? '复诊提醒' : eventType === 'questionnaire' ? '问卷' : '其他',
+ eventStatusLabel:
+ status === 'processing'
+ ? '待处理'
+ : status === 'notStart'
+ ? '未开始'
+ : status === 'treated'
+ ? '已完成'
+ : status === 'cancelled'
+ ? '已取消'
+ : '已过期',
+ taskContent: `回访任务示例 #${idx + 1}:电话回访/提醒到院等。`,
+ result: status === 'treated' ? '已完成回访,患者反馈良好。' : '',
+ archiveName: archive?.name || '',
+ };
+ });
+ }
+
+ setDb(db);
+}
+
+export function queryVisitRecords({ archiveId, medicalType = 'ALL', date = '' }) {
+ const db = getDb();
+ const list = Array.isArray(db.visitRecordsByArchiveId[archiveId]) ? db.visitRecordsByArchiveId[archiveId] : [];
+
+ const withDate = list.map((i) => ({
+ ...i,
+ dateStr: i.sortTime ? dayjs(i.sortTime).format('YYYY-MM-DD') : '',
+ }));
+
+ const matchType = medicalType === 'ALL' ? withDate : withDate.filter((i) => i.medicalType === medicalType);
+ const matchDate = date ? matchType.filter((i) => i.dateStr === date) : matchType;
+ return matchDate.sort((a, b) => (b.sortTime || 0) - (a.sortTime || 0));
+}
+
+export function getVisitRecord({ archiveId, id }) {
+ const db = getDb();
+ const list = Array.isArray(db.visitRecordsByArchiveId[archiveId]) ? db.visitRecordsByArchiveId[archiveId] : [];
+ return list.find((i) => i._id === id) || null;
+}
+
+export function upsertVisitRecord({ archiveId, record }) {
+ const db = getDb();
+ const list = Array.isArray(db.visitRecordsByArchiveId[archiveId]) ? db.visitRecordsByArchiveId[archiveId] : [];
+ const next = { ...record };
+ if (!next._id) next._id = uid('mr');
+ if (!next.sortTime) next.sortTime = Date.now();
+ if (!next.createTime) next.createTime = Date.now();
+
+ const idx = list.findIndex((i) => i._id === next._id);
+ if (idx >= 0) list[idx] = { ...list[idx], ...next };
+ else list.unshift(next);
+ db.visitRecordsByArchiveId[archiveId] = list;
+ setDb(db);
+ return next;
+}
+
+export function removeVisitRecord({ archiveId, id }) {
+ const db = getDb();
+ const list = Array.isArray(db.visitRecordsByArchiveId[archiveId]) ? db.visitRecordsByArchiveId[archiveId] : [];
+ db.visitRecordsByArchiveId[archiveId] = list.filter((i) => i._id !== id);
+ setDb(db);
+}
+
+export function queryServiceRecords({ archiveId, page = 1, pageSize = 10, eventType = 'ALL', teamId = 'ALL', dateRange = [] }) {
+ const db = getDb();
+ const list = Array.isArray(db.serviceRecordsByArchiveId[archiveId]) ? db.serviceRecordsByArchiveId[archiveId] : [];
+
+ let filtered = [...list];
+ if (eventType !== 'ALL') filtered = filtered.filter((i) => i.eventType === eventType);
+ if (teamId !== 'ALL') filtered = filtered.filter((i) => i.executeTeamId === teamId);
+
+ if (Array.isArray(dateRange) && dateRange.length === 2 && dateRange[0] && dateRange[1]) {
+ filtered = filtered.filter((i) => {
+ const d = i.executionTime ? dayjs(i.executionTime).format('YYYY-MM-DD') : '';
+ return d >= dateRange[0] && d <= dateRange[1];
+ });
+ }
+
+ filtered.sort((a, b) => (b.executionTime || 0) - (a.executionTime || 0));
+ const total = filtered.length;
+ const pages = Math.ceil(total / pageSize) || 1;
+ const start = (page - 1) * pageSize;
+ const slice = filtered.slice(start, start + pageSize);
+ return { list: slice, total, pages };
+}
+
+export function getServiceRecord({ archiveId, id }) {
+ const db = getDb();
+ const list = Array.isArray(db.serviceRecordsByArchiveId[archiveId]) ? db.serviceRecordsByArchiveId[archiveId] : [];
+ return list.find((i) => i._id === id) || null;
+}
+
+export function upsertServiceRecord({ archiveId, record }) {
+ const db = getDb();
+ const list = Array.isArray(db.serviceRecordsByArchiveId[archiveId]) ? db.serviceRecordsByArchiveId[archiveId] : [];
+ const next = { ...record };
+ if (!next._id) next._id = uid('sr');
+ if (!next.executionTime) next.executionTime = Date.now();
+
+ const idx = list.findIndex((i) => i._id === next._id);
+ if (idx >= 0) list[idx] = { ...list[idx], ...next };
+ else list.unshift(next);
+ db.serviceRecordsByArchiveId[archiveId] = list;
+ setDb(db);
+ return next;
+}
+
+export function removeServiceRecord({ archiveId, id }) {
+ const db = getDb();
+ const list = Array.isArray(db.serviceRecordsByArchiveId[archiveId]) ? db.serviceRecordsByArchiveId[archiveId] : [];
+ db.serviceRecordsByArchiveId[archiveId] = list.filter((i) => i._id !== id);
+ setDb(db);
+}
+
+export function queryFollowups({ archiveId, page = 1, pageSize = 10, status = 'all', isMy = false, eventTypes = [], teamId = 'ALL', planRange = ['', ''] }) {
+ const db = getDb();
+ const list = Array.isArray(db.followupsByArchiveId[archiveId]) ? db.followupsByArchiveId[archiveId] : [];
+
+ let filtered = [...list];
+ if (status !== 'all') filtered = filtered.filter((i) => i.status === status);
+ if (isMy) filtered = filtered.filter((i) => i.executorName === '李医生');
+ if (Array.isArray(eventTypes) && eventTypes.length) filtered = filtered.filter((i) => eventTypes.includes(i.eventType));
+ if (teamId !== 'ALL') filtered = filtered.filter((i) => i.executeTeamId === teamId);
+ if (planRange && (planRange[0] || planRange[1])) {
+ if (planRange[0]) filtered = filtered.filter((i) => i.planDate >= planRange[0]);
+ if (planRange[1]) filtered = filtered.filter((i) => i.planDate <= planRange[1]);
+ }
+
+ filtered.sort((a, b) => (b.plannedExecutionTime || 0) - (a.plannedExecutionTime || 0));
+
+ const total = filtered.length;
+ const pages = Math.ceil(total / pageSize) || 1;
+ const start = (page - 1) * pageSize;
+ const slice = filtered.slice(start, start + pageSize);
+ return { list: slice, total, pages };
+}
+
+export function getFollowup({ archiveId, id }) {
+ const db = getDb();
+ const list = Array.isArray(db.followupsByArchiveId[archiveId]) ? db.followupsByArchiveId[archiveId] : [];
+ return list.find((i) => i._id === id) || null;
+}
+
+export function upsertFollowup({ archiveId, followup }) {
+ const db = getDb();
+ const list = Array.isArray(db.followupsByArchiveId[archiveId]) ? db.followupsByArchiveId[archiveId] : [];
+ const next = { ...followup };
+ if (!next._id) next._id = uid('td');
+ if (!next.plannedExecutionTime) next.plannedExecutionTime = Date.now();
+ next.planDate = dayjs(next.plannedExecutionTime).format('YYYY-MM-DD');
+ if (!next.createTime) next.createTime = Date.now();
+ next.createTimeStr = dayjs(next.createTime).format('YYYY-MM-DD HH:mm');
+
+ const idx = list.findIndex((i) => i._id === next._id);
+ if (idx >= 0) list[idx] = { ...list[idx], ...next };
+ else list.unshift(next);
+
+ db.followupsByArchiveId[archiveId] = list;
+ setDb(db);
+ return next;
+}
+
+export function removeFollowup({ archiveId, id }) {
+ const db = getDb();
+ const list = Array.isArray(db.followupsByArchiveId[archiveId]) ? db.followupsByArchiveId[archiveId] : [];
+ db.followupsByArchiveId[archiveId] = list.filter((i) => i._id !== id);
+ setDb(db);
+}
diff --git a/components/archive-detail/service-info-tab.vue b/components/archive-detail/service-info-tab.vue
new file mode 100644
index 0000000..4878839
--- /dev/null
+++ b/components/archive-detail/service-info-tab.vue
@@ -0,0 +1,460 @@
+
+
+
+
+
+
+ {{ currentType.value === 'ALL' ? '服务类型' : currentType.label }}
+
+
+
+
+
+
+
+ {{ dateRange.length ? dateRange.join('~') : '服务时间' }}
+
+
+
+
+
+
+
+
+
+
+ {{ currentTeam.label }}
+
+
+
+
+
+
+
+
+
+ {{ i.timeStr }}
+
+ {{ i.fileType === 'article' ? '查看文章' : '查看问卷' }}
+
+
+
+ {{ i.typeStr }}
+ {{ i.executorName }}
+ {{ i.executeTeamName }}
+
+
+
+ {{ i.taskContent || '暂无内容' }}(处理结果: {{ i.result }})
+
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages.json b/pages.json
index c555ad9..eabcc8b 100644
--- a/pages.json
+++ b/pages.json
@@ -60,6 +60,30 @@
"navigationBarTitleText": "档案详情"
}
},
+ {
+ "path": "pages/case/archive-edit",
+ "style": {
+ "navigationBarTitleText": "档案编辑"
+ }
+ },
+ {
+ "path": "pages/case/visit-record-detail",
+ "style": {
+ "navigationBarTitleText": "健康档案"
+ }
+ },
+ {
+ "path": "pages/case/service-record-detail",
+ "style": {
+ "navigationBarTitleText": "服务记录"
+ }
+ },
+ {
+ "path": "pages/case/followup-detail",
+ "style": {
+ "navigationBarTitleText": "回访详情"
+ }
+ },
{
"path": "pages/work/work",
"style": {
@@ -118,4 +142,4 @@
]
},
"uniIdRouter": {}
-}
\ No newline at end of file
+}
diff --git a/pages/case/archive-detail.vue b/pages/case/archive-detail.vue
index 05e17d1..40d6836 100644
--- a/pages/case/archive-detail.vue
+++ b/pages/case/archive-detail.vue
@@ -102,10 +102,26 @@
-
-
- 暂无记录
-
+
+
+