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', operateType: 'formCell', required: true, format: 'YYYY-MM-DD' }, { title: 'corpName', name: '就诊机构', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' }, { title: 'deptName', name: '科室', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' }, { title: 'doctor', name: '医生', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' }, { title: 'diagnosisName', name: '门诊诊断', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 }, { title: 'treatmentPlan', name: '治疗方案', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000 }, { title: 'disposePlan', name: '处置计划', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000 }, { title: 'summary', name: '备注/摘要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 }, { title: 'files', name: '文件上传', type: 'files', required: false }, ], }, { templateType: 'inhospital', templateName: '住院记录', templateList: [ { title: 'inhosDate', name: '入院日期', type: 'date', operateType: 'formCell', required: true, format: 'YYYY-MM-DD' }, { title: 'corpName', name: '住院机构', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' }, { title: 'diagnosisName', name: '入院诊断', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 }, { title: 'surgeryName', name: '手术名称', type: 'input', operateType: 'formCell', required: false, wordLimit: 50, inputType: 'text' }, { title: 'summary', name: '摘要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 }, { title: 'files', name: '文件上传', type: 'files', required: false }, ], }, { templateType: 'preConsultation', templateName: '预问诊记录', templateList: [ { title: 'consultDate', name: '问诊日期', type: 'date', operateType: 'formCell', required: true, format: 'YYYY-MM-DD' }, { title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 300 }, { title: 'presentIllness', name: '现病史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800 }, { title: 'pastHistory', name: '既往史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800 }, { title: 'allergyHistory', name: '过敏史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 300 }, { title: 'summary', name: '摘要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 }, { title: 'files', name: '文件上传', type: 'files', required: false }, ], }, { templateType: 'physicalExaminationTemplate', templateName: '体检记录', templateList: [ { title: 'inspectDate', name: '体检日期', type: 'date', operateType: 'formCell', required: true, format: 'YYYY-MM-DD' }, { title: 'corpName', name: '体检机构', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' }, { title: 'inspectPakageName', name: '体检套餐', type: 'input', operateType: 'formCell', required: false, wordLimit: 50, inputType: 'text' }, { title: 'positiveFind', name: '阳性发现', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 300 }, { title: 'summary', name: '摘要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 }, { title: 'files', name: '文件上传', type: 'files', required: false }, ], }, ]; 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', teamId: 'team_1', sortTime: now - 1000 * 60 * 60 * 24 * 2, visitTime: dayjs(now - 1000 * 60 * 60 * 24 * 2).format('YYYY-MM-DD'), corpName: '某某医院', deptName: '呼吸内科', doctor: '李医生', diagnosisName: '急性上呼吸道感染(mock)', treatmentPlan: '建议:1)对症处理退热;2)多饮水休息;3)如出现呼吸困难及时就医。', disposePlan: '建议:1)继续对症治疗;2)监测体温与血压;3)如3天无缓解或加重立即复诊。', summary: '初诊:对症处理并随访。', files: [{ url: '/static/tabbar/home.png', name: '示例图片1' }], createTime: now - 1000 * 60 * 60 * 24 * 2, creatorName: '李珊珊', }, { _id: uid('mr'), medicalType: 'inhospital', tempName: '住院记录', templateType: 'inhospital', teamId: 'team_2', sortTime: now - 1000 * 60 * 60 * 24 * 15, inhosDate: dayjs(now - 1000 * 60 * 60 * 24 * 15).format('YYYY-MM-DD'), corpName: '某某医院', diagnosisName: '术后复查(mock)', surgeryName: '阑尾切除术', summary: '复诊:术后复查,恢复良好。', files: [], createTime: now - 1000 * 60 * 60 * 24 * 15, creatorName: '王护士', }, { _id: uid('mr'), medicalType: 'preConsultation', tempName: '预问诊记录', templateType: 'preConsultation', teamId: 'team_1', sortTime: now - 1000 * 60 * 60 * 6, consultDate: dayjs(now - 1000 * 60 * 60 * 6).format('YYYY-MM-DD'), chiefComplaint: '咽痛、流涕 2 天(mock)', presentIllness: '近2天受凉后出现咽痛、流涕,体温最高 38.2℃。', pastHistory: '既往体健。', allergyHistory: '无明确过敏史。', summary: '建议对症处理,必要时线下就医。', files: [], createTime: now - 1000 * 60 * 60 * 6, 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 getCurrentTeamId() { const v = uni.getStorageSync('ykt_mock_current_team_id'); return v ? String(v) : 'team_1'; } function getSortTimeTitle(templateType) { if (templateType === 'outpatient') return 'visitTime'; if (templateType === 'inhospital') return 'inhosDate'; if (templateType === 'preConsultation') return 'consultDate'; if (templateType === 'physicalExaminationTemplate') return 'inspectDate'; return ''; } export function queryVisitRecords({ archiveId, medicalType = 'ALL', timeRange = 'ALL', teamId = '', shareAllTeams = false }) { const db = getDb(); const list = Array.isArray(db.visitRecordsByArchiveId[archiveId]) ? db.visitRecordsByArchiveId[archiveId] : []; const withDate = list.map((i) => { const type = i.templateType || i.medicalType || ''; const timeTitle = getSortTimeTitle(type); const rawDate = timeTitle ? i[timeTitle] : ''; const fallback = i.sortTime ? dayjs(i.sortTime).format('YYYY-MM-DD') : ''; const date = rawDate || fallback; return { ...i, dateStr: date ? String(date) : '', date: date ? String(date) : '', createDateStr: i.createTime ? dayjs(i.createTime).format('YYYY-MM-DD') : '', }; }); let filtered = [...withDate]; if (medicalType !== 'ALL') filtered = filtered.filter((i) => (i.medicalType || i.templateType) === medicalType); if (!shareAllTeams && teamId) filtered = filtered.filter((i) => !i.teamId || i.teamId === teamId); if (timeRange && timeRange !== 'ALL') { const days = timeRange === 'today' ? 0 : timeRange === '7d' ? 7 : timeRange === '30d' ? 30 : null; if (days !== null) { const start = days === 0 ? dayjs().startOf('day') : dayjs().subtract(days, 'day').startOf('day'); const startMs = start.valueOf(); filtered = filtered.filter((i) => (i.sortTime || 0) >= startMs); } } filtered.sort((a, b) => (b.sortTime || 0) - (a.sortTime || 0)); return filtered; } 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); }