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 @@ + + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ - - - 暂无记录 - + + + @@ -202,7 +218,12 @@ + + diff --git a/pages/case/followup-detail.vue b/pages/case/followup-detail.vue new file mode 100644 index 0000000..d49e0ea --- /dev/null +++ b/pages/case/followup-detail.vue @@ -0,0 +1,430 @@ +