feat:优化、补充页面
This commit is contained in:
parent
bc01ba97ca
commit
1650ccb8f9
@ -226,12 +226,18 @@ function toggleStatus(v) {
|
||||
|
||||
function add() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['+新增任务', '+回访记录'],
|
||||
itemList: ['+新增任务', '+使用模板', '+回访记录'],
|
||||
success: ({ tapIndex }) => {
|
||||
if (tapIndex === 0) {
|
||||
uni.navigateTo({ url: `/pages/case/followup-detail?archiveId=${encodeURIComponent(props.archiveId)}&mode=add` });
|
||||
uni.setStorageSync('new-followup-customer', { _id: props.archiveId, name: props.data?.name || '' });
|
||||
uni.navigateTo({ url: `/pages/case/new-followup?archiveId=${encodeURIComponent(props.archiveId)}` });
|
||||
} else if (tapIndex === 1) {
|
||||
uni.navigateTo({ url: `/pages/case/followup-detail?archiveId=${encodeURIComponent(props.archiveId)}&mode=record` });
|
||||
uni.setStorageSync('new-followup-plan-customer', { _id: props.archiveId, name: props.data?.name || '' });
|
||||
uni.setStorageSync('select-mamagement-plan', '');
|
||||
uni.navigateTo({ url: `/pages/case/plan-list?archiveId=${encodeURIComponent(props.archiveId)}` });
|
||||
} else if (tapIndex === 2) {
|
||||
uni.setStorageSync('new-followup-record-customer', { _id: props.archiveId, name: props.data?.name || '' });
|
||||
uni.navigateTo({ url: `/pages/case/new-followup-record?archiveId=${encodeURIComponent(props.archiveId)}` });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -4,42 +4,51 @@
|
||||
<view class="filters">
|
||||
<picker mode="selector" :range="typeRange" range-key="name" @change="pickType">
|
||||
<view class="filter-pill">
|
||||
<view class="pill-text" :class="{ muted: currentType.value === 'ALL' }">{{ currentType.value === 'ALL' ? '档案类型' : currentType.name }}</view>
|
||||
<view class="pill-text">{{ currentType.value === 'ALL' ? '全部病历' : currentType.name }}</view>
|
||||
<uni-icons type="arrowdown" size="12" color="#666" />
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<view class="filter-pill">
|
||||
<picker mode="date" @change="pickDate">
|
||||
<view class="pill-text" :class="{ muted: !date }">{{ date || '时间筛选' }}</view>
|
||||
</picker>
|
||||
<view class="pill-icon" @click.stop="clearDate">
|
||||
<uni-icons v-if="date" type="closeempty" size="14" color="#666" />
|
||||
<uni-icons v-else type="arrowdown" size="12" color="#666" />
|
||||
<picker mode="selector" :range="timeRangeOptions" range-key="label" @change="pickTimeRange">
|
||||
<view class="filter-pill">
|
||||
<view class="pill-text">{{ currentTimeRange.label }}</view>
|
||||
<uni-icons type="arrowdown" size="12" color="#666" />
|
||||
</view>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view v-if="showShareTip" class="share-tip">
|
||||
<text class="share-tip-text">患者已授权其他团队,病历信息互通</text>
|
||||
</view>
|
||||
|
||||
<view class="list">
|
||||
<view v-for="r in records" :key="r._id" class="card record" @click="edit(r)">
|
||||
<view class="record-head">
|
||||
<view class="record-date">{{ r.dateStr }}</view>
|
||||
<view class="record-tag" :class="tagClass[r.templateType] || 'bg-blue'">{{ r.tempName }}</view>
|
||||
<view v-if="r.corpName === '其他'" class="record-tag bg-rose">外院</view>
|
||||
<view class="record-date">{{ r.dateStr || '--' }}</view>
|
||||
<view class="record-tag" :class="tagClass[r.templateType] || 'bg-blue'">{{ r.tempName || '病历' }}</view>
|
||||
<view v-if="r.corpName === '其他' || r.corp === '其他'" class="record-tag bg-rose">外院</view>
|
||||
</view>
|
||||
|
||||
<view class="record-body">
|
||||
<view v-for="row in buildSummaryRows(r)" :key="row.k" class="kv">
|
||||
<view class="k">{{ row.k }}:</view>
|
||||
<view class="v">{{ row.v }}</view>
|
||||
<view class="line">
|
||||
<text class="line-label">诊断:</text>
|
||||
<text class="line-value">{{ getDiagnosis(r) }}</text>
|
||||
</view>
|
||||
<view v-if="!buildSummaryRows(r).length" class="kv">
|
||||
<view class="k">摘要:</view>
|
||||
<view class="v">{{ r.summary || '暂无内容' }}</view>
|
||||
<view v-if="r.templateType === 'inhospital' && r.surgeryName" class="line">
|
||||
<text class="line-label">手术:</text>
|
||||
<text class="line-value">{{ r.surgeryName }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="getFiles(r).length" class="thumbs">
|
||||
<view v-for="(f, idx) in getFiles(r).slice(0, 3)" :key="idx" class="thumb" @click.stop="previewFiles(r, idx)">
|
||||
<image class="thumb-img" :src="f.url" mode="aspectFill" />
|
||||
</view>
|
||||
<view v-if="getFiles(r).length > 3" class="thumb-more">+{{ getFiles(r).length - 3 }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="record-foot">
|
||||
<view class="foot-left">创建时间:{{ r.createTimeStr }}</view>
|
||||
<view class="foot-right">记录人:{{ r.creatorName || '—' }}</view>
|
||||
<view class="foot-left">创建时间:{{ r.createDateStr || '' }}</view>
|
||||
<view class="foot-right">创建人:{{ r.creatorName || '—' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="records.length === 0" class="empty">暂无数据</view>
|
||||
@ -53,8 +62,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { ensureSeed, getVisitRecordTemplates, queryVisitRecords } from './mock';
|
||||
import { ensureSeed, getCurrentTeamId, getVisitRecordTemplates, queryVisitRecords } from './mock';
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => ({}) },
|
||||
@ -62,8 +70,6 @@ const props = defineProps({
|
||||
floatingBottom: { type: Number, default: 16 },
|
||||
});
|
||||
|
||||
const date = ref('');
|
||||
|
||||
const templates = ref(getVisitRecordTemplates());
|
||||
|
||||
const typeRange = computed(() => [{ name: '全部', value: 'ALL' }, ...templates.value.map((t) => ({ name: t.name, value: t.templateType }))]);
|
||||
@ -71,63 +77,71 @@ const currentType = ref({ name: '全部', value: 'ALL' });
|
||||
|
||||
const records = ref([]);
|
||||
|
||||
const timeRangeOptions = [
|
||||
{ label: '全部时间', value: 'ALL' },
|
||||
{ label: '今天', value: 'today' },
|
||||
{ label: '近7天', value: '7d' },
|
||||
{ label: '近30天', value: '30d' },
|
||||
];
|
||||
const currentTimeRange = ref(timeRangeOptions[0]);
|
||||
|
||||
const teamId = ref(getCurrentTeamId());
|
||||
const showShareTip = computed(() => {
|
||||
if (props.data && typeof props.data.shareAllTeams === 'boolean') return props.data.shareAllTeams;
|
||||
const list = props.data?.authorizedTeams;
|
||||
return Array.isArray(list) && list.length > 1;
|
||||
});
|
||||
|
||||
const shareAllTeamsForQuery = computed(() => {
|
||||
if (props.data && typeof props.data.shareAllTeams === 'boolean') return props.data.shareAllTeams;
|
||||
const list = props.data?.authorizedTeams;
|
||||
if (Array.isArray(list)) return list.length > 1;
|
||||
// mock 场景:没有授权信息时默认互通,避免误隐藏数据
|
||||
return true;
|
||||
});
|
||||
|
||||
function refreshList() {
|
||||
if (!props.archiveId) return;
|
||||
records.value = queryVisitRecords({
|
||||
archiveId: props.archiveId,
|
||||
medicalType: currentType.value.value,
|
||||
date: date.value,
|
||||
}).map((r) => ({
|
||||
...r,
|
||||
createTimeStr: r.createTime ? dayjs(r.createTime).format('YYYY-MM-DD') : '',
|
||||
}));
|
||||
timeRange: currentTimeRange.value.value,
|
||||
teamId: teamId.value,
|
||||
shareAllTeams: shareAllTeamsForQuery.value,
|
||||
});
|
||||
}
|
||||
|
||||
const tagClass = {
|
||||
outpatient: 'bg-amber',
|
||||
inhospital: 'bg-teal',
|
||||
preConsultation: 'bg-indigo',
|
||||
physicalExaminationTemplate: 'bg-green',
|
||||
};
|
||||
|
||||
function buildSummaryRows(r) {
|
||||
const rows = [];
|
||||
if (r.templateType === 'outpatient') {
|
||||
if (r.corpName) rows.push({ k: '机构', v: r.corpName });
|
||||
if (r.deptName) rows.push({ k: '科室', v: r.deptName });
|
||||
if (r.doctor) rows.push({ k: '医生', v: r.doctor });
|
||||
if (r.diagnosisName) rows.push({ k: '诊断', v: r.diagnosisName });
|
||||
if (r.summary) rows.push({ k: '摘要', v: r.summary });
|
||||
return rows;
|
||||
}
|
||||
if (r.templateType === 'inhospital') {
|
||||
if (r.corpName) rows.push({ k: '机构', v: r.corpName });
|
||||
if (r.diagnosisName) rows.push({ k: '诊断', v: r.diagnosisName });
|
||||
if (r.summary) rows.push({ k: '摘要', v: r.summary });
|
||||
return rows;
|
||||
}
|
||||
if (r.templateType === 'physicalExaminationTemplate') {
|
||||
if (r.corpName) rows.push({ k: '机构', v: r.corpName });
|
||||
if (r.inspectPakageName) rows.push({ k: '套餐', v: r.inspectPakageName });
|
||||
if (r.positiveFind) rows.push({ k: '阳性', v: r.positiveFind });
|
||||
if (r.summary) rows.push({ k: '摘要', v: r.summary });
|
||||
return rows;
|
||||
}
|
||||
if (r.summary) rows.push({ k: '摘要', v: r.summary });
|
||||
return rows;
|
||||
function getDiagnosis(r) {
|
||||
if (!r) return '--';
|
||||
if (r.templateType === 'preConsultation') return r.chiefComplaint || r.summary || '--';
|
||||
return r.diagnosisName || r.summary || '--';
|
||||
}
|
||||
|
||||
function pickType(e) {
|
||||
currentType.value = typeRange.value[e.detail.value] || { name: '全部', value: 'ALL' };
|
||||
refreshList();
|
||||
}
|
||||
function pickDate(e) {
|
||||
date.value = e.detail.value || '';
|
||||
function pickTimeRange(e) {
|
||||
currentTimeRange.value = timeRangeOptions[e.detail.value] || timeRangeOptions[0];
|
||||
refreshList();
|
||||
}
|
||||
function clearDate() {
|
||||
if (!date.value) return;
|
||||
date.value = '';
|
||||
refreshList();
|
||||
|
||||
function getFiles(r) {
|
||||
const arr = r?.files;
|
||||
return Array.isArray(arr) ? arr.filter((i) => i && i.url) : [];
|
||||
}
|
||||
|
||||
function previewFiles(r, idx) {
|
||||
const urls = getFiles(r).map((i) => i.url);
|
||||
if (!urls.length) return;
|
||||
uni.previewImage({ urls, current: urls[idx] });
|
||||
}
|
||||
|
||||
function add() {
|
||||
@ -144,7 +158,7 @@ function add() {
|
||||
|
||||
function edit(record) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/case/visit-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&id=${encodeURIComponent(record._id)}&type=${encodeURIComponent(record.templateType || record.medicalType || '')}`,
|
||||
url: `/pages/case/visit-record-view?archiveId=${encodeURIComponent(props.archiveId)}&id=${encodeURIComponent(record._id)}`,
|
||||
});
|
||||
}
|
||||
|
||||
@ -175,26 +189,32 @@ onUnmounted(() => {
|
||||
background: #fff;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 6px;
|
||||
padding: 10px 10px;
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
min-width: 110px;
|
||||
flex: 1;
|
||||
}
|
||||
.pill-text {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
max-width: 140px;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pill-text.muted {
|
||||
color: #999;
|
||||
|
||||
.share-tip {
|
||||
padding: 10px 14px 0;
|
||||
}
|
||||
.pill-icon {
|
||||
padding-left: 8px;
|
||||
.share-tip-text {
|
||||
display: inline-block;
|
||||
background: #eef2ff;
|
||||
color: #4338ca;
|
||||
font-size: 12px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.list {
|
||||
@ -229,21 +249,49 @@ onUnmounted(() => {
|
||||
.record-body {
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
.kv {
|
||||
.line {
|
||||
display: flex;
|
||||
padding-top: 10px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
line-height: 18px;
|
||||
}
|
||||
.k {
|
||||
.line-label {
|
||||
flex-shrink: 0;
|
||||
color: #666;
|
||||
}
|
||||
.v {
|
||||
.line-value {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.thumbs {
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.thumb {
|
||||
width: 84px;
|
||||
height: 64px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
.thumb-img {
|
||||
width: 84px;
|
||||
height: 64px;
|
||||
}
|
||||
.thumb-more {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
.record-foot {
|
||||
display: flex;
|
||||
@ -281,6 +329,9 @@ onUnmounted(() => {
|
||||
.bg-teal {
|
||||
background: #0f766e;
|
||||
}
|
||||
.bg-indigo {
|
||||
background: #4f46e5;
|
||||
}
|
||||
.bg-green {
|
||||
background: #16a34a;
|
||||
}
|
||||
|
||||
@ -7,33 +7,52 @@ 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 },
|
||||
{ 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: '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 },
|
||||
{ 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', 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 },
|
||||
{ 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 },
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -84,29 +103,53 @@ export function ensureSeed(archiveId, archive) {
|
||||
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: '口腔科',
|
||||
deptName: '呼吸内科',
|
||||
doctor: '李医生',
|
||||
diagnosisName: '牙列不齐(mock)',
|
||||
summary: '初诊:拍片、取模,制定治疗方案。',
|
||||
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: '李医生',
|
||||
creatorName: '李珊珊',
|
||||
},
|
||||
{
|
||||
_id: uid('mr'),
|
||||
medicalType: 'inhospital',
|
||||
tempName: '住院记录',
|
||||
templateType: 'inhospital',
|
||||
teamId: 'team_2',
|
||||
sortTime: now - 1000 * 60 * 60 * 24 * 15,
|
||||
inhospitalDate: dayjs(now - 1000 * 60 * 60 * 24 * 15).format('YYYY-MM-DD'),
|
||||
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: '李珊珊',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -183,18 +226,52 @@ export function ensureSeed(archiveId, archive) {
|
||||
setDb(db);
|
||||
}
|
||||
|
||||
export function queryVisitRecords({ archiveId, medicalType = 'ALL', date = '' }) {
|
||||
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) => ({
|
||||
...i,
|
||||
dateStr: i.sortTime ? dayjs(i.sortTime).format('YYYY-MM-DD') : '',
|
||||
}));
|
||||
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') : '',
|
||||
};
|
||||
});
|
||||
|
||||
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));
|
||||
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 }) {
|
||||
|
||||
51
pages.json
51
pages.json
@ -1,7 +1,26 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/redirect-page",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/message/message",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/message/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息"
|
||||
}
|
||||
@ -72,6 +91,12 @@
|
||||
"navigationBarTitleText": "健康档案"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/visit-record-view",
|
||||
"style": {
|
||||
"navigationBarTitleText": "病历详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/service-record-detail",
|
||||
"style": {
|
||||
@ -84,6 +109,24 @@
|
||||
"navigationBarTitleText": "回访详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/new-followup",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增回访"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/new-followup-record",
|
||||
"style": {
|
||||
"navigationBarTitleText": "回访记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/plan-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "回访计划"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/work/work",
|
||||
"style": {
|
||||
@ -101,12 +144,6 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择科室"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
@ -142,4 +179,4 @@
|
||||
]
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
}
|
||||
@ -217,8 +217,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { onLoad, onReachBottom, onShow } from '@dcloudio/uni-app';
|
||||
import { computed, getCurrentInstance, ref } from 'vue';
|
||||
import { onLoad, onPullDownRefresh, onReachBottom, onReady, onShow } from '@dcloudio/uni-app';
|
||||
|
||||
import HealthProfileTab from '@/components/archive-detail/health-profile-tab.vue';
|
||||
import ServiceInfoTab from '@/components/archive-detail/service-info-tab.vue';
|
||||
@ -236,6 +236,8 @@ const tabs = [
|
||||
const currentTab = ref('visitRecord');
|
||||
const reachBottomTime = ref(0);
|
||||
const archiveId = ref('');
|
||||
const tabsScrollTop = ref(0);
|
||||
const instanceProxy = getCurrentInstance()?.proxy;
|
||||
|
||||
const archive = ref({
|
||||
name: '',
|
||||
@ -279,6 +281,22 @@ onLoad((options) => {
|
||||
ensureSeed(archiveId.value, archive.value);
|
||||
});
|
||||
|
||||
function measureTabsTop() {
|
||||
const q = instanceProxy ? uni.createSelectorQuery().in(instanceProxy) : uni.createSelectorQuery();
|
||||
q.select('.tabs').boundingClientRect();
|
||||
q.selectViewport().scrollOffset();
|
||||
q.exec((res) => {
|
||||
const rect = res && res[0];
|
||||
const viewport = res && res[1];
|
||||
if (!rect || !viewport) return;
|
||||
tabsScrollTop.value = (rect.top || 0) + (viewport.scrollTop || 0);
|
||||
});
|
||||
}
|
||||
|
||||
onReady(() => {
|
||||
setTimeout(measureTabsTop, 30);
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
const cached = uni.getStorageSync(STORAGE_KEY);
|
||||
if (cached && typeof cached === 'object') {
|
||||
@ -289,6 +307,15 @@ onShow(() => {
|
||||
groupOptions: Array.isArray(cached.groupOptions) ? cached.groupOptions : archive.value.groupOptions,
|
||||
};
|
||||
}
|
||||
setTimeout(measureTabsTop, 30);
|
||||
});
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: tabsScrollTop.value || 0,
|
||||
duration: 0,
|
||||
});
|
||||
setTimeout(() => uni.stopPullDownRefresh(), 150);
|
||||
});
|
||||
|
||||
onReachBottom(() => {
|
||||
@ -667,6 +694,9 @@ const saveAddGroup = () => {
|
||||
display: flex;
|
||||
border-top: 1px solid #f2f2f2;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.tab {
|
||||
|
||||
344
pages/case/new-followup-record.vue
Normal file
344
pages/case/new-followup-record.vue
Normal file
@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/new-followup-record/new-followup-record.vue(简化移植:去除 pinia/接口) -->
|
||||
<view class="page">
|
||||
<view class="card">
|
||||
<picker mode="date" :value="form.plannedExecutionTime" @change="changeDate">
|
||||
<view class="row clickable">
|
||||
<view class="label">回访日期</view>
|
||||
<view class="right">
|
||||
<view class="value" :class="{ muted: !form.plannedExecutionTime }">{{ form.plannedExecutionTime || '请选择回访日期' }}</view>
|
||||
<uni-icons type="arrowright" size="16" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<view class="block">
|
||||
<view class="block-title">回访方式</view>
|
||||
<view class="method-row">
|
||||
<view class="method" @click="selectMethod('phone')">
|
||||
<image class="radio" :src="`/static/circle${form.todoMethod === 'phone' ? 'd' : ''}.svg`" />
|
||||
<view class="m-label">电话</view>
|
||||
<view v-if="form.todoMethod === 'phone'" class="m-value" :class="{ muted: !form.phoneNumber }">{{ form.phoneNumber || '请选择电话号码' }}</view>
|
||||
<uni-icons v-if="form.todoMethod === 'phone'" type="arrowright" size="16" color="#999" />
|
||||
</view>
|
||||
<view class="method" @click="selectMethod('wechat')">
|
||||
<image class="radio" :src="`/static/circle${form.todoMethod === 'wechat' ? 'd' : ''}.svg`" />
|
||||
<view class="m-label">微信</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="row clickable" @click="selectType">
|
||||
<view class="label">回访类型</view>
|
||||
<view class="right">
|
||||
<view class="value" :class="{ muted: !form.eventType }">{{ eventTypeLabel || '请选择回访类型' }}</view>
|
||||
<uni-icons type="arrowright" size="16" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="row clickable" @click="selectTeam">
|
||||
<view class="label">所在团队</view>
|
||||
<view class="right">
|
||||
<view class="value" :class="{ muted: !form.teamId }">{{ form.teamName || '请选择团队' }}</view>
|
||||
<uni-icons type="arrowright" size="16" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="block">
|
||||
<view class="block-title">回访结果</view>
|
||||
<view class="textarea-box">
|
||||
<textarea v-model="form.result" class="textarea tall" placeholder="请输入回访结果" maxlength="500" />
|
||||
<view class="counter">{{ (form.result || '').length }}/500</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<button class="btn plain" @click="cancel">取消</button>
|
||||
<button class="btn primary" @click="save">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import dayjs from 'dayjs';
|
||||
import { ensureSeed, upsertFollowup } from '@/components/archive-detail/mock';
|
||||
|
||||
const archiveId = ref('');
|
||||
const archiveName = ref('');
|
||||
const archiveMobile = ref('');
|
||||
|
||||
const form = reactive({
|
||||
plannedExecutionTime: '',
|
||||
todoMethod: '',
|
||||
phoneNumber: '',
|
||||
eventType: '',
|
||||
teamId: '',
|
||||
teamName: '',
|
||||
result: '',
|
||||
});
|
||||
|
||||
const eventTypeList = [
|
||||
{ label: '回访', value: 'followup' },
|
||||
{ label: '复诊提醒', value: 'revisit' },
|
||||
{ label: '问卷', value: 'questionnaire' },
|
||||
{ label: '其他', value: 'other' },
|
||||
];
|
||||
const eventTypeLabel = computed(() => eventTypeList.find((i) => i.value === form.eventType)?.label || '');
|
||||
|
||||
const teamOptions = [
|
||||
{ label: '口腔一科(示例)', value: 'team_1' },
|
||||
{ label: '正畸团队(示例)', value: 'team_2' },
|
||||
];
|
||||
|
||||
const mobiles = computed(() => {
|
||||
const arr = [];
|
||||
if (archiveMobile.value) arr.push(String(archiveMobile.value));
|
||||
if (!arr.includes('13800000000')) arr.push('13800000000');
|
||||
return arr;
|
||||
});
|
||||
|
||||
onLoad((options) => {
|
||||
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
|
||||
const c = uni.getStorageSync('new-followup-record-customer');
|
||||
if (c && typeof c === 'object') {
|
||||
archiveId.value = archiveId.value || String(c._id || '');
|
||||
archiveName.value = String(c.name || '');
|
||||
}
|
||||
const cached = uni.getStorageSync('ykt_case_archive_detail');
|
||||
if (cached && typeof cached === 'object') archiveMobile.value = String(cached.mobile || '');
|
||||
|
||||
if (!archiveId.value) {
|
||||
uni.showToast({ title: '缺少 archiveId', icon: 'none' });
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
return;
|
||||
}
|
||||
ensureSeed(archiveId.value, { name: archiveName.value });
|
||||
form.plannedExecutionTime = dayjs().format('YYYY-MM-DD');
|
||||
});
|
||||
|
||||
function changeDate(e) {
|
||||
form.plannedExecutionTime = e.detail.value || '';
|
||||
}
|
||||
|
||||
function selectMethod(method) {
|
||||
if (method === 'phone') {
|
||||
if (mobiles.value.length === 0) {
|
||||
uni.showToast({ title: '暂无可用电话号码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.showActionSheet({
|
||||
itemList: mobiles.value,
|
||||
success: ({ tapIndex }) => {
|
||||
form.todoMethod = 'phone';
|
||||
form.phoneNumber = mobiles.value[tapIndex] || '';
|
||||
},
|
||||
});
|
||||
} else {
|
||||
form.todoMethod = 'wechat';
|
||||
form.phoneNumber = '';
|
||||
}
|
||||
}
|
||||
|
||||
function selectType() {
|
||||
uni.showActionSheet({
|
||||
itemList: eventTypeList.map((i) => i.label),
|
||||
success: ({ tapIndex }) => {
|
||||
form.eventType = eventTypeList[tapIndex]?.value || '';
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function selectTeam() {
|
||||
uni.showActionSheet({
|
||||
itemList: teamOptions.map((i) => i.label),
|
||||
success: ({ tapIndex }) => {
|
||||
const t = teamOptions[tapIndex];
|
||||
form.teamId = t.value;
|
||||
form.teamName = t.label;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
uni.showModal({
|
||||
title: '确认取消',
|
||||
content: '确定要取消新建回访记录吗?',
|
||||
success: (res) => res.confirm && uni.navigateBack(),
|
||||
});
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (!form.plannedExecutionTime) return uni.showToast({ title: '请选择回访日期', icon: 'none' });
|
||||
if (!form.todoMethod) return uni.showToast({ title: '请选择回访方式', icon: 'none' });
|
||||
if (!form.eventType) return uni.showToast({ title: '请选择回访类型', icon: 'none' });
|
||||
if (!form.teamId) return uni.showToast({ title: '请选择所在团队', icon: 'none' });
|
||||
if (!String(form.result || '').trim()) return uni.showToast({ title: '请输入回访结果', icon: 'none' });
|
||||
|
||||
const phoneValue = form.phoneNumber ? `phone:${form.phoneNumber}` : 'phone';
|
||||
const plannedExecutionTime = dayjs(form.plannedExecutionTime).valueOf();
|
||||
|
||||
upsertFollowup({
|
||||
archiveId: archiveId.value,
|
||||
followup: {
|
||||
plannedExecutionTime,
|
||||
endTime: plannedExecutionTime,
|
||||
status: 'treated',
|
||||
eventStatusLabel: '已完成',
|
||||
eventType: form.eventType,
|
||||
eventTypeLabel: eventTypeLabel.value || '回访',
|
||||
executeTeamId: form.teamId,
|
||||
executeTeamName: form.teamName,
|
||||
executorName: '我',
|
||||
creatorName: '我',
|
||||
taskContent: '',
|
||||
result: form.result,
|
||||
todoMethod: form.todoMethod === 'phone' ? phoneValue : form.todoMethod,
|
||||
createTime: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
uni.$emit('archive-detail:followup-changed');
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
padding-bottom: calc(76px + env(safe-area-inset-bottom));
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
margin: 10px 14px 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 14px;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
.row.clickable:active {
|
||||
background: #fafafa;
|
||||
}
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.value.muted {
|
||||
color: #999;
|
||||
}
|
||||
.block {
|
||||
padding: 14px 14px;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
.block:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.block-title {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.method-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
}
|
||||
.method {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.radio {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.m-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
.m-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
max-width: 140px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.m-value.muted {
|
||||
color: #999;
|
||||
}
|
||||
.textarea-box {
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
}
|
||||
.textarea {
|
||||
width: 100%;
|
||||
min-height: 140px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.textarea.tall {
|
||||
min-height: 160px;
|
||||
}
|
||||
.counter {
|
||||
margin-top: 6px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.btn::after {
|
||||
border: none;
|
||||
}
|
||||
.btn.plain {
|
||||
background: #fff;
|
||||
color: #4f6ef7;
|
||||
border: 1px solid #4f6ef7;
|
||||
}
|
||||
.btn.primary {
|
||||
background: #4f6ef7;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
408
pages/case/new-followup.vue
Normal file
408
pages/case/new-followup.vue
Normal file
@ -0,0 +1,408 @@
|
||||
<template>
|
||||
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/new-followup/new-followup.vue(简化移植:去除员工组件/群发附件/接口) -->
|
||||
<view class="page">
|
||||
<view class="card">
|
||||
<picker mode="date" :value="form.planExecutionTime" @change="changeDate">
|
||||
<view class="row clickable">
|
||||
<view class="left">
|
||||
<view class="label">回访日期</view>
|
||||
<view class="req">*</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="value" :class="{ muted: !form.planExecutionTime }">{{ form.planExecutionTime || '请选择回访日期' }}</view>
|
||||
<uni-icons type="arrowright" size="16" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<view class="row clickable" @click="selectExecutor">
|
||||
<view class="left">
|
||||
<view class="label">处理人</view>
|
||||
<view class="req">*</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="value" :class="{ muted: !form.executorName }">
|
||||
{{ form.executorName ? `${form.executorName}${form.executeTeamName ? `(${form.executeTeamName})` : ''}` : '请选择处理人' }}
|
||||
</view>
|
||||
<uni-icons type="arrowright" size="16" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="row clickable" @click="selectType">
|
||||
<view class="left">
|
||||
<view class="label">类型</view>
|
||||
<view class="req">*</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="value" :class="{ muted: !form.eventType }">{{ eventTypeLabel || '请选择类型' }}</view>
|
||||
<uni-icons type="arrowright" size="16" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="block">
|
||||
<view class="block-title">目的</view>
|
||||
<view class="textarea-box">
|
||||
<textarea v-model="form.taskContent" class="textarea" placeholder="请输入文字提醒" maxlength="200" />
|
||||
<view class="counter">{{ (form.taskContent || '').length }}/200</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="block">
|
||||
<view class="block-title">跟进方式</view>
|
||||
<view class="toggle-row">
|
||||
<view class="pill" :class="{ active: form.executeMethod === 'todo' }" @click="form.executeMethod = 'todo'">待办</view>
|
||||
<view class="pill" :class="{ active: form.executeMethod === 'groupMessage' }" @click="form.executeMethod = 'groupMessage'">群发</view>
|
||||
<view class="info" @click="showInfo">i</view>
|
||||
</view>
|
||||
<view v-if="form.executeMethod === 'groupMessage'" class="block">
|
||||
<view class="block-title">发送内容</view>
|
||||
<view class="textarea-box">
|
||||
<textarea v-model="form.sendContent" class="textarea" placeholder="请输入群发内容(mock)" maxlength="500" />
|
||||
<view class="counter">{{ (form.sendContent || '').length }}/500</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<button class="btn plain" @click="cancel">取消</button>
|
||||
<button class="btn primary" @click="save">保存</button>
|
||||
</view>
|
||||
|
||||
<uni-popup ref="infoPopup" type="center">
|
||||
<view class="modal">
|
||||
<view class="modal-title">跟进方式说明</view>
|
||||
<view class="modal-body">
|
||||
<view class="modal-text">待办:生成待办单,需员工手动进行处理</view>
|
||||
<view class="modal-text">群发:生成群发单,员工可批量进行处理(wxapp mock 不接入群发)</view>
|
||||
</view>
|
||||
<view class="modal-actions">
|
||||
<view class="modal-btn save" @click="closeInfo">关闭</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import dayjs from 'dayjs';
|
||||
import { ensureSeed, upsertFollowup } from '@/components/archive-detail/mock';
|
||||
|
||||
const archiveId = ref('');
|
||||
const archiveName = ref('');
|
||||
|
||||
const eventTypeList = [
|
||||
{ label: '回访', value: 'followup' },
|
||||
{ label: '复诊提醒', value: 'revisit' },
|
||||
{ label: '问卷', value: 'questionnaire' },
|
||||
{ label: '其他', value: 'other' },
|
||||
];
|
||||
|
||||
const teamOptions = [
|
||||
{ label: '口腔一科(示例)', value: 'team_1' },
|
||||
{ label: '正畸团队(示例)', value: 'team_2' },
|
||||
];
|
||||
|
||||
const form = reactive({
|
||||
planExecutionTime: '',
|
||||
executeTeamId: '',
|
||||
executeTeamName: '',
|
||||
executorName: '',
|
||||
eventType: '',
|
||||
taskContent: '',
|
||||
executeMethod: 'todo', // todo | groupMessage
|
||||
sendContent: '',
|
||||
});
|
||||
|
||||
const eventTypeLabel = computed(() => eventTypeList.find((i) => i.value === form.eventType)?.label || '');
|
||||
|
||||
onLoad((options) => {
|
||||
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
|
||||
const c = uni.getStorageSync('new-followup-customer');
|
||||
if (c && typeof c === 'object') {
|
||||
archiveId.value = archiveId.value || String(c._id || '');
|
||||
archiveName.value = String(c.name || '');
|
||||
}
|
||||
if (!archiveId.value) {
|
||||
uni.showToast({ title: '缺少 archiveId', icon: 'none' });
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
return;
|
||||
}
|
||||
|
||||
ensureSeed(archiveId.value, { name: archiveName.value });
|
||||
|
||||
// 使用模板:从 plan-list 选择后写入 select-mamagement-plan
|
||||
const plan = uni.getStorageSync('select-mamagement-plan');
|
||||
if (plan && typeof plan === 'object' && plan.planName) {
|
||||
form.eventType = plan.eventType || 'followup';
|
||||
form.taskContent = plan.taskContent || `执行回访计划:${plan.planName}`;
|
||||
uni.setNavigationBarTitle({ title: '使用模板新增任务' });
|
||||
}
|
||||
|
||||
if (!form.planExecutionTime) form.planExecutionTime = dayjs().add(1, 'day').format('YYYY-MM-DD');
|
||||
});
|
||||
|
||||
function changeDate(e) {
|
||||
const date = String(e.detail.value || '');
|
||||
if (dayjs().startOf('day').isAfter(dayjs(date))) {
|
||||
uni.showToast({ title: '请选择有效的回访日期', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
form.planExecutionTime = date;
|
||||
}
|
||||
|
||||
function selectType() {
|
||||
uni.showActionSheet({
|
||||
itemList: eventTypeList.map((i) => i.label),
|
||||
success: ({ tapIndex }) => {
|
||||
form.eventType = eventTypeList[tapIndex]?.value || '';
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function selectExecutor() {
|
||||
// wxapp 先用 mock:选择团队 + 固定执行人
|
||||
uni.showActionSheet({
|
||||
itemList: teamOptions.map((i) => i.label),
|
||||
success: ({ tapIndex }) => {
|
||||
const t = teamOptions[tapIndex];
|
||||
form.executeTeamId = t.value;
|
||||
form.executeTeamName = t.label;
|
||||
form.executorName = '李医生';
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
uni.showModal({
|
||||
title: '确认取消',
|
||||
content: '确定要取消新建回访任务吗?',
|
||||
success: (res) => res.confirm && uni.navigateBack(),
|
||||
});
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (!form.planExecutionTime) return uni.showToast({ title: '请选择回访日期', icon: 'none' });
|
||||
if (!form.executorName) return uni.showToast({ title: '请选择处理人', icon: 'none' });
|
||||
if (!form.eventType) return uni.showToast({ title: '请选择类型', icon: 'none' });
|
||||
if (!String(form.taskContent || '').trim()) return uni.showToast({ title: '请输入目的', icon: 'none' });
|
||||
if (form.executeMethod === 'groupMessage' && !String(form.sendContent || '').trim()) {
|
||||
return uni.showToast({ title: '请输入发送内容', icon: 'none' });
|
||||
}
|
||||
|
||||
const plannedExecutionTime = dayjs(form.planExecutionTime).valueOf();
|
||||
upsertFollowup({
|
||||
archiveId: archiveId.value,
|
||||
followup: {
|
||||
plannedExecutionTime,
|
||||
status: 'processing',
|
||||
eventStatusLabel: '待处理',
|
||||
eventType: form.eventType,
|
||||
eventTypeLabel: eventTypeLabel.value || '回访',
|
||||
executeTeamId: form.executeTeamId || 'team_1',
|
||||
executeTeamName: form.executeTeamName || '口腔一科(示例)',
|
||||
executorName: form.executorName,
|
||||
creatorName: '我',
|
||||
taskContent: form.taskContent,
|
||||
result: '',
|
||||
executeMethod: form.executeMethod,
|
||||
sendContent: form.executeMethod === 'groupMessage' ? form.sendContent : '',
|
||||
createTime: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
uni.$emit('archive-detail:followup-changed');
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
}
|
||||
|
||||
const infoPopup = ref(null);
|
||||
function showInfo() {
|
||||
infoPopup.value?.open?.();
|
||||
}
|
||||
function closeInfo() {
|
||||
infoPopup.value?.close?.();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
padding-bottom: calc(76px + env(safe-area-inset-bottom));
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
margin: 10px 14px 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 14px;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
.row.clickable:active {
|
||||
background: #fafafa;
|
||||
}
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.req {
|
||||
margin-left: 6px;
|
||||
color: #ff4d4f;
|
||||
font-size: 14px;
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.value.muted {
|
||||
color: #999;
|
||||
}
|
||||
.block {
|
||||
padding: 14px 14px;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
.block:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.block-title {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.textarea-box {
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
}
|
||||
.textarea {
|
||||
width: 100%;
|
||||
min-height: 90px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.counter {
|
||||
margin-top: 6px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.toggle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.pill {
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: #eaecef;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
}
|
||||
.pill.active {
|
||||
background: #4f6ef7;
|
||||
color: #fff;
|
||||
}
|
||||
.info {
|
||||
margin-left: auto;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 9px;
|
||||
background: #cfd3dc;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.btn::after {
|
||||
border: none;
|
||||
}
|
||||
.btn.plain {
|
||||
background: #fff;
|
||||
color: #4f6ef7;
|
||||
border: 1px solid #4f6ef7;
|
||||
}
|
||||
.btn.primary {
|
||||
background: #4f6ef7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.modal {
|
||||
width: 320px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.modal-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
padding: 14px 12px;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.modal-body {
|
||||
padding: 14px;
|
||||
}
|
||||
.modal-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.modal-actions {
|
||||
padding: 12px 14px 14px;
|
||||
}
|
||||
.modal-btn {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.modal-btn.save {
|
||||
background: #4f6ef7;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
150
pages/case/plan-list.vue
Normal file
150
pages/case/plan-list.vue
Normal file
@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<!-- Mobile 来源: ykt-management-mobile/src/pages/manage-plan/plan-list/plan-list.vue(wxapp mock:仅选择模板并回到 new-followup) -->
|
||||
<view class="page">
|
||||
<view v-if="list.length === 0" class="empty">
|
||||
<image class="empty-img" src="/static/empty.svg" mode="aspectFit" />
|
||||
<view class="empty-text">暂无回访计划</view>
|
||||
</view>
|
||||
|
||||
<scroll-view v-else scroll-y class="scroll">
|
||||
<view v-for="(p, idx) in list" :key="p.id" class="item">
|
||||
<view class="left">
|
||||
<view class="name-row">
|
||||
<view class="name">{{ p.planName }}</view>
|
||||
<view v-if="p.planType === 'corp'" class="tag corp">机构</view>
|
||||
<view class="tag outline" @click.stop="preview(p)">详情</view>
|
||||
</view>
|
||||
<view class="desc">应用范围:{{ p.planDetail }}</view>
|
||||
</view>
|
||||
<view class="btn" @click="select(p)">选择</view>
|
||||
</view>
|
||||
<view style="height: 24px;"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
const archiveId = ref('');
|
||||
|
||||
const list = ref([
|
||||
{
|
||||
id: 'p1',
|
||||
planName: '复诊提醒模板',
|
||||
planType: 'corp',
|
||||
planDetail: '适用于复诊提醒人群',
|
||||
eventType: 'revisit',
|
||||
taskContent: '请于本周内完成复诊预约与提醒。',
|
||||
},
|
||||
{
|
||||
id: 'p2',
|
||||
planName: '随访回访模板',
|
||||
planType: 'team',
|
||||
planDetail: '适用于普通随访',
|
||||
eventType: 'followup',
|
||||
taskContent: '请电话回访患者,确认恢复情况并记录结果。',
|
||||
},
|
||||
]);
|
||||
|
||||
onLoad((options) => {
|
||||
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
|
||||
});
|
||||
|
||||
function select(plan) {
|
||||
uni.setStorageSync('select-mamagement-plan', plan);
|
||||
uni.navigateTo({ url: `/pages/case/new-followup?archiveId=${encodeURIComponent(archiveId.value)}&fromPlan=1` });
|
||||
}
|
||||
|
||||
function preview(plan) {
|
||||
uni.showModal({
|
||||
title: plan.planName || '回访计划',
|
||||
content: plan.taskContent || plan.planDetail || '',
|
||||
showCancel: false,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
}
|
||||
.scroll {
|
||||
height: 100vh;
|
||||
}
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
.left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tag {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.tag.corp {
|
||||
background: #4f6ef7;
|
||||
color: #fff;
|
||||
}
|
||||
.tag.outline {
|
||||
border: 1px solid #4f6ef7;
|
||||
color: #4f6ef7;
|
||||
background: #fff;
|
||||
}
|
||||
.desc {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #4f6ef7;
|
||||
color: #4f6ef7;
|
||||
font-size: 13px;
|
||||
}
|
||||
.empty {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.empty-img {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.empty-text {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: #9aa0a6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="page">
|
||||
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/visit-record-detail/visit-record-detail.vue -->
|
||||
<view class="body">
|
||||
<scroll-view scroll-y class="scroll">
|
||||
@ -11,6 +11,20 @@
|
||||
<FormTemplate v-if="template" ref="formRef" :items="showItems" :form="forms" @change="onChange" />
|
||||
</view>
|
||||
|
||||
<!-- 附件上传(FormTemplate 不支持 files,单独实现) -->
|
||||
<view v-if="hasFilesField" class="upload-wrap">
|
||||
<view class="upload-title">文件上传</view>
|
||||
<view class="upload-grid">
|
||||
<view v-for="(f, idx) in fileList" :key="idx" class="upload-item" @click="previewFile(idx)">
|
||||
<image class="upload-thumb" :src="f.url" mode="aspectFill" />
|
||||
<view class="upload-remove" @click.stop="removeFile(idx)">×</view>
|
||||
</view>
|
||||
<view class="upload-add" @click="addFiles">
|
||||
<view class="plus">+</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="height: 120px;"></view>
|
||||
</scroll-view>
|
||||
|
||||
@ -31,7 +45,7 @@ import { computed, reactive, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import dayjs from 'dayjs';
|
||||
import FormTemplate from '@/components/form-template/index.vue';
|
||||
import { ensureSeed, getVisitRecord, getVisitRecordTemplate, removeVisitRecord, upsertVisitRecord } from '@/components/archive-detail/mock';
|
||||
import { ensureSeed, getCurrentTeamId, getVisitRecord, getVisitRecordTemplate, removeVisitRecord, upsertVisitRecord } from '@/components/archive-detail/mock';
|
||||
|
||||
const archiveId = ref('');
|
||||
const recordId = ref('');
|
||||
@ -45,6 +59,7 @@ const showItems = computed(() => {
|
||||
const list = template.value?.templateList || [];
|
||||
// referenceField 兼容(与 mobile 一致)
|
||||
return list.filter((i) => {
|
||||
if (i?.type === 'files') return false;
|
||||
if (i && typeof i.referenceField === 'string') {
|
||||
return forms.value[i.referenceField] === i.referenceValue;
|
||||
}
|
||||
@ -52,7 +67,22 @@ const showItems = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const hasFilesField = computed(() => {
|
||||
const list = template.value?.templateList || [];
|
||||
return list.some((i) => i && (i.type === 'files' || i.title === 'files'));
|
||||
});
|
||||
|
||||
const formRef = ref(null);
|
||||
const fileList = computed(() => {
|
||||
const arr = forms.value?.files;
|
||||
return Array.isArray(arr) ? arr.filter((i) => i && i.url) : [];
|
||||
});
|
||||
|
||||
function ensureFilesField() {
|
||||
if (form.files !== undefined) return;
|
||||
if (detail.value && detail.value.files !== undefined) return;
|
||||
form.files = [];
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
|
||||
@ -66,11 +96,13 @@ onLoad((options) => {
|
||||
if (record) {
|
||||
templateType.value = record.templateType || record.medicalType || templateType.value;
|
||||
detail.value = record;
|
||||
ensureFilesField();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!templateType.value) templateType.value = 'outpatient';
|
||||
ensureFilesField();
|
||||
});
|
||||
|
||||
function onChange({ title, value }) {
|
||||
@ -98,7 +130,9 @@ function save() {
|
||||
templateType.value === 'outpatient'
|
||||
? 'visitTime'
|
||||
: templateType.value === 'inhospital'
|
||||
? 'inhospitalDate'
|
||||
? 'inhosDate'
|
||||
: templateType.value === 'preConsultation'
|
||||
? 'consultDate'
|
||||
: templateType.value === 'physicalExaminationTemplate'
|
||||
? 'inspectDate'
|
||||
: '';
|
||||
@ -113,7 +147,9 @@ function save() {
|
||||
templateType: templateType.value,
|
||||
tempName: template.value?.templateName || '健康档案',
|
||||
sortTime,
|
||||
teamId: detail.value?.teamId || getCurrentTeamId(),
|
||||
...form,
|
||||
files: fileList.value,
|
||||
createTime: detail.value?.createTime || Date.now(),
|
||||
creatorName: detail.value?.creatorName || '我',
|
||||
},
|
||||
@ -136,6 +172,28 @@ function remove() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function addFiles() {
|
||||
uni.chooseImage({
|
||||
count: 9,
|
||||
success: (res) => {
|
||||
const paths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [];
|
||||
const next = paths.map((p) => ({ url: p, name: '' }));
|
||||
const cur = Array.isArray(forms.value.files) ? forms.value.files : [];
|
||||
form.files = [...cur, ...next];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function removeFile(idx) {
|
||||
const cur = Array.isArray(forms.value.files) ? forms.value.files : [];
|
||||
form.files = cur.filter((_, i) => i !== idx);
|
||||
}
|
||||
|
||||
function previewFile(idx) {
|
||||
const urls = fileList.value.map((i) => i.url);
|
||||
uni.previewImage({ urls, current: urls[idx] });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -167,6 +225,61 @@ function remove() {
|
||||
margin-top: 10px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.upload-wrap {
|
||||
background: #fff;
|
||||
margin: 10px 14px 0;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.upload-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.upload-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.upload-item {
|
||||
width: 90px;
|
||||
height: 70px;
|
||||
position: relative;
|
||||
border: 1px solid #d1d5db;
|
||||
background: #f9fafb;
|
||||
}
|
||||
.upload-thumb {
|
||||
width: 90px;
|
||||
height: 70px;
|
||||
}
|
||||
.upload-remove {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
.upload-add {
|
||||
width: 90px;
|
||||
height: 70px;
|
||||
border: 1px dashed #c7c7c7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #666;
|
||||
}
|
||||
.plus {
|
||||
font-size: 26px;
|
||||
line-height: 26px;
|
||||
}
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
|
||||
282
pages/case/visit-record-view.vue
Normal file
282
pages/case/visit-record-view.vue
Normal file
@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<!-- 详情页参考截图:顶部蓝条显示创建信息,支持编辑/删除;黄底提示不渲染 -->
|
||||
<view class="page">
|
||||
<view class="topbar">
|
||||
<view class="topbar-text">{{ topText }}</view>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<view class="section">
|
||||
<view class="row">
|
||||
<view class="label">病历类型</view>
|
||||
<view class="value">{{ typeLabel }}</view>
|
||||
</view>
|
||||
<view class="row">
|
||||
<view class="label">就诊日期</view>
|
||||
<view class="value">{{ visitDate || '--' }}</view>
|
||||
</view>
|
||||
<view class="row">
|
||||
<view class="label">诊断</view>
|
||||
<view class="value">{{ diagnosisText }}</view>
|
||||
</view>
|
||||
<view v-if="templateType === 'inhospital' && record.surgeryName" class="row">
|
||||
<view class="label">手术名称</view>
|
||||
<view class="value">{{ record.surgeryName }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-for="(s, idx) in sections" :key="idx" class="section">
|
||||
<view class="h2">{{ s.title }}</view>
|
||||
<view class="p">{{ s.value }}</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="h2">文件上传</view>
|
||||
<view v-if="files.length" class="files">
|
||||
<view v-for="(f, idx) in files" :key="idx" class="file" @click="preview(idx)">
|
||||
<image class="thumb" :src="f.url" mode="aspectFill" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="files-empty">暂无附件</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<button class="btn danger" @click="remove">删除</button>
|
||||
<button class="btn primary" @click="edit">编辑</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import dayjs from 'dayjs';
|
||||
import { getVisitRecord, removeVisitRecord } from '@/components/archive-detail/mock';
|
||||
|
||||
const archiveId = ref('');
|
||||
const id = ref('');
|
||||
const record = ref({});
|
||||
|
||||
const files = computed(() => {
|
||||
const arr = record.value?.files;
|
||||
return Array.isArray(arr) ? arr.filter((i) => i && i.url) : [];
|
||||
});
|
||||
|
||||
const templateType = computed(() => record.value?.templateType || record.value?.medicalType || '');
|
||||
|
||||
const typeLabel = computed(() => record.value?.tempName || '病历');
|
||||
|
||||
const visitDate = computed(() => {
|
||||
const t = templateType.value;
|
||||
if (t === 'outpatient') return record.value?.visitTime || '';
|
||||
if (t === 'inhospital') return record.value?.inhosDate || '';
|
||||
if (t === 'preConsultation') return record.value?.consultDate || '';
|
||||
if (t === 'physicalExaminationTemplate') return record.value?.inspectDate || '';
|
||||
return record.value?.dateStr || '';
|
||||
});
|
||||
|
||||
const diagnosisText = computed(() => {
|
||||
const t = templateType.value;
|
||||
if (t === 'preConsultation') return record.value?.chiefComplaint || record.value?.summary || '--';
|
||||
if (t === 'physicalExaminationTemplate') return record.value?.positiveFind || record.value?.summary || '--';
|
||||
return record.value?.diagnosisName || record.value?.summary || '--';
|
||||
});
|
||||
|
||||
const sections = computed(() => {
|
||||
const t = templateType.value;
|
||||
const push = (title, value) => {
|
||||
const v = value === 0 ? '0' : value ? String(value) : '';
|
||||
if (!v.trim()) return;
|
||||
return { title, value: v };
|
||||
};
|
||||
const list = [];
|
||||
if (t === 'outpatient') {
|
||||
const corp = push('就诊机构', record.value?.corpName);
|
||||
const dept = push('科室', record.value?.deptName);
|
||||
const doctor = push('医生', record.value?.doctor);
|
||||
const treatment = push('治疗方案', record.value?.treatmentPlan);
|
||||
const dispose = push('处置计划', record.value?.disposePlan);
|
||||
const summary = push('备注/摘要', record.value?.summary);
|
||||
[corp, dept, doctor, treatment, dispose, summary].forEach((i) => i && list.push(i));
|
||||
return list;
|
||||
}
|
||||
if (t === 'inhospital') {
|
||||
const corp = push('住院机构', record.value?.corpName);
|
||||
const summary = push('摘要', record.value?.summary);
|
||||
[corp, summary].forEach((i) => i && list.push(i));
|
||||
return list;
|
||||
}
|
||||
if (t === 'preConsultation') {
|
||||
const illness = push('现病史', record.value?.presentIllness);
|
||||
const past = push('既往史', record.value?.pastHistory);
|
||||
const allergy = push('过敏史', record.value?.allergyHistory);
|
||||
const summary = push('摘要', record.value?.summary);
|
||||
[illness, past, allergy, summary].forEach((i) => i && list.push(i));
|
||||
return list;
|
||||
}
|
||||
if (t === 'physicalExaminationTemplate') {
|
||||
const corp = push('体检机构', record.value?.corpName);
|
||||
const pkg = push('体检套餐', record.value?.inspectPakageName);
|
||||
const positive = push('阳性发现', record.value?.positiveFind);
|
||||
const summary = push('摘要', record.value?.summary);
|
||||
[corp, pkg, positive, summary].forEach((i) => i && list.push(i));
|
||||
return list;
|
||||
}
|
||||
const summary = push('摘要', record.value?.summary);
|
||||
if (summary) list.push(summary);
|
||||
return list;
|
||||
});
|
||||
|
||||
const topText = computed(() => {
|
||||
const time = record.value?.createTime ? dayjs(record.value.createTime).format('YYYY-MM-DD HH:mm') : '';
|
||||
const name = record.value?.creatorName ? String(record.value.creatorName) : '';
|
||||
return `${time || '--'} ${name || '--'}创建`;
|
||||
});
|
||||
|
||||
onLoad((opt) => {
|
||||
archiveId.value = opt?.archiveId ? String(opt.archiveId) : '';
|
||||
id.value = opt?.id ? String(opt.id) : '';
|
||||
if (!archiveId.value || !id.value) {
|
||||
uni.showToast({ title: '参数缺失', icon: 'none' });
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
return;
|
||||
}
|
||||
const r = getVisitRecord({ archiveId: archiveId.value, id: id.value });
|
||||
if (!r) {
|
||||
uni.showToast({ title: '记录不存在', icon: 'none' });
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
return;
|
||||
}
|
||||
record.value = r;
|
||||
uni.setNavigationBarTitle({ title: r?.tempName ? String(r.tempName) : '病历详情' });
|
||||
});
|
||||
|
||||
function preview(idx) {
|
||||
const urls = files.value.map((i) => i.url);
|
||||
uni.previewImage({ urls, current: urls[idx] });
|
||||
}
|
||||
|
||||
function edit() {
|
||||
const type = record.value?.templateType || record.value?.medicalType || '';
|
||||
uni.navigateTo({
|
||||
url: `/pages/case/visit-record-detail?archiveId=${encodeURIComponent(archiveId.value)}&id=${encodeURIComponent(id.value)}&type=${encodeURIComponent(type)}`,
|
||||
});
|
||||
}
|
||||
|
||||
function remove() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定删除当前记录?',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
removeVisitRecord({ archiveId: archiveId.value, id: id.value });
|
||||
uni.$emit('archive-detail:visit-record-changed');
|
||||
uni.showToast({ title: '已删除', icon: 'success' });
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #fff;
|
||||
padding-bottom: calc(76px + env(safe-area-inset-bottom));
|
||||
}
|
||||
.topbar {
|
||||
background: #5d6df0;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
.topbar-text {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.content {
|
||||
padding: 14px 14px 0;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.label {
|
||||
width: 90px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
.value {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #111827;
|
||||
word-break: break-all;
|
||||
}
|
||||
.h2 {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.p {
|
||||
font-size: 14px;
|
||||
color: #111827;
|
||||
line-height: 20px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.files {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.file {
|
||||
width: 90px;
|
||||
height: 70px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: #f9fafb;
|
||||
}
|
||||
.thumb {
|
||||
width: 90px;
|
||||
height: 70px;
|
||||
}
|
||||
.files-empty {
|
||||
font-size: 13px;
|
||||
color: #9aa0a6;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 14px;
|
||||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.btn {
|
||||
width: 120px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.btn::after {
|
||||
border: none;
|
||||
}
|
||||
.btn.danger {
|
||||
background: #fff;
|
||||
color: #ff4d4f;
|
||||
border: 1px solid #ff4d4f;
|
||||
}
|
||||
.btn.primary {
|
||||
background: #4f6ef7;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@ -1,9 +1,89 @@
|
||||
export default [
|
||||
{
|
||||
path: 'pages/login/login',
|
||||
meta: { title: '登录', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/login/redirect-page',
|
||||
meta: { title: '登录', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/message/message',
|
||||
meta: { title: '首页', login: false },
|
||||
meta: { title: '消息', login: false },
|
||||
style: { navigationStyle: 'custom' }
|
||||
},
|
||||
{
|
||||
path: 'pages/message/index',
|
||||
meta: { title: '消息', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/case',
|
||||
meta: { title: '病例', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/search',
|
||||
meta: { title: '搜索患者', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/group-manage',
|
||||
meta: { title: '分组管理', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/batch-transfer',
|
||||
meta: { title: '转移客户给其他团队', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/batch-share',
|
||||
meta: { title: '共享客户', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/patient-invite',
|
||||
meta: { title: '邀请患者', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/patient-create',
|
||||
meta: { title: '新增患者', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/patient-inner-info',
|
||||
meta: { title: '内部信息', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/archive-detail',
|
||||
meta: { title: '档案详情', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/archive-edit',
|
||||
meta: { title: '档案编辑', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/visit-record-detail',
|
||||
meta: { title: '健康档案', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/visit-record-view',
|
||||
meta: { title: '病历详情', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/service-record-detail',
|
||||
meta: { title: '服务记录', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/followup-detail',
|
||||
meta: { title: '回访详情', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/new-followup',
|
||||
meta: { title: '新增回访', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/new-followup-record',
|
||||
meta: { title: '回访记录', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/plan-list',
|
||||
meta: { title: '回访计划', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/work/work',
|
||||
meta: { title: '工作台', login: false }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user