560 lines
16 KiB
Vue
560 lines
16 KiB
Vue
<template>
|
||
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/customer-detail/health-profile/health-profile.vue -->
|
||
<view class="wrap">
|
||
<view class="filters">
|
||
<picker mode="selector" :range="typeRange" range-key="name" @change="pickType">
|
||
<view class="filter-pill">
|
||
<view class="pill-text">{{ currentType.value === 'ALL' ? '全部病历' : currentType.name }}</view>
|
||
<uni-icons type="arrowdown" size="12" color="#666" />
|
||
</view>
|
||
</picker>
|
||
|
||
<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>
|
||
</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 === '其他' || r.corp === '其他'" class="record-tag bg-rose">外院</view>
|
||
</view>
|
||
|
||
<view class="record-body">
|
||
<view v-for="(l, idx) in getDisplayLines(r)" :key="`${r._id}_${idx}`" class="line">
|
||
<text class="line-label">{{ l.label }}</text>
|
||
<text class="line-value">{{ l.value }}</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">{{ getCreateFooter(r) }}</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="records.length === 0" class="empty">暂无数据</view>
|
||
</view>
|
||
|
||
<view class="fab" :style="{ bottom: `${floatingBottom}px` }" @click="add">
|
||
<uni-icons type="plusempty" size="24" color="#fff" />
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||
import dayjs from 'dayjs';
|
||
import { VISIT_RECORD_TEMPLATES } from './templates';
|
||
import api from '@/utils/api';
|
||
import { loading, hideLoading } from '@/utils/widget';
|
||
|
||
const props = defineProps({
|
||
data: { type: Object, default: () => ({}) },
|
||
archiveId: { type: String, default: '' },
|
||
floatingBottom: { type: Number, default: 16 },
|
||
});
|
||
|
||
const templates = ref(VISIT_RECORD_TEMPLATES.map(t => ({ name: t.templateName, templateType: t.templateType })));
|
||
|
||
const typeRange = computed(() => [{ name: '全部', value: 'ALL' }, ...templates.value.map((t) => ({ name: t.name, value: t.templateType }))]);
|
||
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]);
|
||
|
||
function getCurrentTeamId() {
|
||
const team = uni.getStorageSync('ykt_case_current_team') || {};
|
||
return team?.teamId ? String(team.teamId) : '';
|
||
}
|
||
|
||
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 getCorpId() {
|
||
const team = uni.getStorageSync('ykt_case_current_team') || {};
|
||
return team?.corpId ? String(team.corpId) : '';
|
||
}
|
||
|
||
const userNameMap = ref({});
|
||
const loadedTeamId = ref('');
|
||
function resolveUserName(userId) {
|
||
const id = String(userId || '');
|
||
if (!id) return '';
|
||
const map = userNameMap.value || {};
|
||
return String(map[id] || '') || id;
|
||
}
|
||
|
||
async function loadTeamMembers() {
|
||
const team = uni.getStorageSync('ykt_case_current_team') || {};
|
||
const teamId = team?.teamId ? String(team.teamId) : '';
|
||
const corpId = getCorpId();
|
||
if (!teamId || !corpId) return;
|
||
if (loadedTeamId.value === teamId && Object.keys(userNameMap.value || {}).length > 0) return;
|
||
loadedTeamId.value = teamId;
|
||
|
||
const res = await api('getTeamData', { corpId, teamId });
|
||
if (!res?.success) return;
|
||
const t = res?.data && typeof res.data === 'object' ? res.data : {};
|
||
const members = Array.isArray(t.memberList) ? t.memberList : [];
|
||
userNameMap.value = members.reduce((acc, m) => {
|
||
const uid = String(m?.userid || '');
|
||
if (!uid) return acc;
|
||
acc[uid] = String(m?.anotherName || m?.name || m?.userid || '') || uid;
|
||
return acc;
|
||
}, {});
|
||
}
|
||
|
||
function getSortTimeTitle(templateType) {
|
||
if (templateType === 'outpatient') return 'visitTime';
|
||
if (templateType === 'inhospital') return 'inhosDate';
|
||
if (templateType === 'preConsultation') return 'consultDate';
|
||
if (templateType === 'physicalExaminationTemplate') return 'inspectDate';
|
||
return '';
|
||
}
|
||
|
||
function normalizeText(v) {
|
||
if (Array.isArray(v)) return v.filter((i) => i !== null && i !== undefined && String(i).trim()).join(',');
|
||
if (v === 0) return '0';
|
||
return v ? String(v) : '';
|
||
}
|
||
|
||
function formatPositiveFind(v, { withOpinion = false } = {}) {
|
||
if (Array.isArray(v)) {
|
||
const list = v
|
||
.map((i) => (i && typeof i === 'object' ? { category: i.category, opinion: i.opinion } : null))
|
||
.filter((i) => i && (i.category || i.opinion));
|
||
if (!list.length) return '';
|
||
if (!withOpinion) return list.map((i) => String(i.category || '').trim()).filter(Boolean).join(':');
|
||
return list
|
||
.map((i) => {
|
||
const c = String(i.category || '').trim();
|
||
const o = String(i.opinion || '').trim();
|
||
if (c && o) return `${c}:${o}`;
|
||
return c || o;
|
||
})
|
||
.filter(Boolean)
|
||
.join(';');
|
||
}
|
||
return normalizeText(v);
|
||
}
|
||
|
||
function getTemplateName(type) {
|
||
const t = VISIT_RECORD_TEMPLATES.find((i) => i && i.templateType === type);
|
||
return t?.templateName ? String(t.templateName) : '';
|
||
}
|
||
|
||
function toDateStr(sortTime) {
|
||
if (!sortTime) return '';
|
||
const d = dayjs(sortTime);
|
||
return d.isValid() ? d.format('YYYY-MM-DD') : '';
|
||
}
|
||
|
||
function toDateTimeStr(ts) {
|
||
if (!ts) return '';
|
||
const d = dayjs(ts);
|
||
return d.isValid() ? d.format('YYYY-MM-DD HH:mm') : '';
|
||
}
|
||
|
||
async function refreshList() {
|
||
if (!props.archiveId) return;
|
||
|
||
const corpId = getCorpId();
|
||
if (!corpId) return;
|
||
loadTeamMembers();
|
||
|
||
loading('加载中...');
|
||
try {
|
||
const params = { memberId: props.archiveId, corpId };
|
||
|
||
// 添加类型筛选
|
||
params.medicalType =
|
||
currentType.value.value === 'ALL' ? templates.value.map((i) => i.templateType) : currentType.value.value;
|
||
|
||
// 添加时间筛选
|
||
if (currentTimeRange.value.value !== 'ALL') {
|
||
const now = Date.now();
|
||
if (currentTimeRange.value.value === 'today') {
|
||
const todayStart = new Date(new Date().setHours(0, 0, 0, 0)).getTime();
|
||
params.startTime = todayStart;
|
||
params.endTime = now;
|
||
} else if (currentTimeRange.value.value === '7d') {
|
||
params.startTime = now - 7 * 24 * 60 * 60 * 1000;
|
||
params.endTime = now;
|
||
} else if (currentTimeRange.value.value === '30d') {
|
||
params.startTime = now - 30 * 24 * 60 * 60 * 1000;
|
||
params.endTime = now;
|
||
}
|
||
}
|
||
|
||
const res = await api('getCustomerMedicalRecord', params);
|
||
hideLoading();
|
||
|
||
// 接口返回结构兼容:
|
||
// - { success, message, list: [] }
|
||
// - { success, message, data: { list: [] } }
|
||
const list = Array.isArray(res?.list)
|
||
? res.list
|
||
: Array.isArray(res?.data?.list)
|
||
? res.data.list
|
||
: Array.isArray(res?.data)
|
||
? res.data
|
||
: [];
|
||
if (list.length) {
|
||
const mapped = list.map((r) => {
|
||
const t = String(r?.medicalType || r?.templateType || '') || '';
|
||
const timeTitle = getSortTimeTitle(t);
|
||
const dateStr = timeTitle ? normalizeText(r?.[timeTitle]) : '';
|
||
return {
|
||
...r,
|
||
templateType: t,
|
||
dateStr: dateStr || toDateStr(r?.sortTime),
|
||
createDateStr: r?.createTime ? dayjs(r.createTime).format('YYYY-MM-DD') : '',
|
||
createTimeStr: toDateTimeStr(r?.createTime),
|
||
tempName: r?.tempName || getTemplateName(t) || '病历',
|
||
};
|
||
});
|
||
|
||
// 未互通时,仅展示本团队数据
|
||
records.value = !shareAllTeamsForQuery.value && teamId.value
|
||
? mapped.filter((i) => String(i?.teamId || '') === String(teamId.value))
|
||
: mapped;
|
||
} else {
|
||
records.value = [];
|
||
}
|
||
} catch (error) {
|
||
hideLoading();
|
||
console.error('获取病历记录失败:', error);
|
||
records.value = [];
|
||
}
|
||
}
|
||
|
||
const tagClass = {
|
||
outpatient: 'bg-amber',
|
||
inhospital: 'bg-teal',
|
||
preConsultation: 'bg-indigo',
|
||
physicalExaminationTemplate: 'bg-green',
|
||
};
|
||
|
||
function getDiagnosis(r) {
|
||
if (!r) return '--';
|
||
const t = r.templateType || r.medicalType;
|
||
if (t === 'preConsultation') return normalizeText(r.chiefComplaint) || normalizeText(r.summary) || '--';
|
||
if (t === 'physicalExaminationTemplate') return formatPositiveFind(r.positiveFind) || normalizeText(r.summary) || '--';
|
||
if (t === 'outpatient' || t === 'inhospital') return normalizeText(r.diagnosisName || r.diagnosis) || normalizeText(r.summary) || '--';
|
||
return normalizeText(r.diagnosisName || r.diagnosis || r.summary) || '--';
|
||
}
|
||
|
||
function firstLine(v) {
|
||
const s = normalizeText(v);
|
||
return s || '--';
|
||
}
|
||
|
||
function getDisplayLines(r) {
|
||
const t = r?.templateType || r?.medicalType;
|
||
if (t === 'outpatient') {
|
||
return [{ label: '门诊诊断:', value: firstLine(r.diagnosisName || r.diagnosis) }];
|
||
}
|
||
if (t === 'inhospital') {
|
||
const lines = [{ label: '入院诊断:', value: firstLine(r.diagnosisName || r.diagnosis) }];
|
||
const surgery = normalizeText(r.surgeryName);
|
||
if (surgery) lines.push({ label: '手术名称:', value: surgery });
|
||
return lines;
|
||
}
|
||
if (t === 'physicalExaminationTemplate') {
|
||
return [{ label: '体检小结:', value: firstLine(r.summary) }];
|
||
}
|
||
if (t === 'preConsultation') {
|
||
const lines = [
|
||
{ label: '主诉:', value: firstLine(r.chiefComplaint) },
|
||
{ label: '现病史:', value: firstLine(r.presentIllness) },
|
||
];
|
||
const past = normalizeText(r.pastHistory);
|
||
if (past) lines.push({ label: '既往史:', value: past });
|
||
return lines;
|
||
}
|
||
return [{ label: '摘要:', value: firstLine(r.summary) }];
|
||
}
|
||
|
||
function getCreateFooter(r) {
|
||
const time = r?.createTimeStr || r?.createDateStr || '';
|
||
const byCustomer = r?.ignore === 'checkIn';
|
||
if (byCustomer) return time ? `${time} 患者自建` : '患者自建';
|
||
|
||
const creatorId = String(r?.creator || '');
|
||
if (!creatorId) return time ? `创建时间:${time}` : '';
|
||
const name = resolveUserName(creatorId);
|
||
return time ? `${time} ${name}代建` : `${name}代建`;
|
||
}
|
||
|
||
function pickType(e) {
|
||
currentType.value = typeRange.value[e.detail.value] || { name: '全部', value: 'ALL' };
|
||
refreshList();
|
||
}
|
||
function pickTimeRange(e) {
|
||
currentTimeRange.value = timeRangeOptions[e.detail.value] || timeRangeOptions[0];
|
||
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() {
|
||
uni.showActionSheet({
|
||
itemList: templates.value.map((i) => i.name),
|
||
success: ({ tapIndex }) => {
|
||
const t = templates.value[tapIndex];
|
||
uni.navigateTo({
|
||
url: `/pages/case/visit-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&type=${encodeURIComponent(t.templateType)}&name=${encodeURIComponent(props.data?.name || '')}`,
|
||
});
|
||
},
|
||
});
|
||
}
|
||
|
||
function edit(record) {
|
||
const type = String(record?.medicalType || record?.templateType || '') || '';
|
||
uni.navigateTo({
|
||
url: `/pages/case/visit-record-view?archiveId=${encodeURIComponent(props.archiveId)}&id=${encodeURIComponent(record._id)}&type=${encodeURIComponent(type)}`,
|
||
});
|
||
}
|
||
|
||
onMounted(() => {
|
||
// archiveId 可能后置赋值:这里保留一次兜底刷新,主逻辑交给 watch
|
||
refreshList();
|
||
uni.$on('archive-detail:visit-record-changed', refreshList);
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
uni.$off('archive-detail:visit-record-changed', refreshList);
|
||
});
|
||
|
||
watch(
|
||
() => props.archiveId,
|
||
(v, old) => {
|
||
const next = v ? String(v) : '';
|
||
const prev = old ? String(old) : '';
|
||
if (next && next !== prev) refreshList();
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
</script>
|
||
|
||
<style scoped>
|
||
.wrap {
|
||
padding: 12px 0 96px;
|
||
}
|
||
|
||
.filters {
|
||
display: flex;
|
||
gap: 10px;
|
||
padding: 10px 14px;
|
||
background: #f5f6f8;
|
||
border-bottom: 1px solid #f2f2f2;
|
||
}
|
||
.filter-pill {
|
||
background: #fff;
|
||
border: 1px solid #e6e6e6;
|
||
border-radius: 6px;
|
||
padding: 10px 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
flex: 1;
|
||
}
|
||
.pill-text {
|
||
font-size: 13px;
|
||
color: #333;
|
||
max-width: 180px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.share-tip {
|
||
padding: 10px 14px 0;
|
||
}
|
||
.share-tip-text {
|
||
display: inline-block;
|
||
background: #eef2ff;
|
||
color: #4338ca;
|
||
font-size: 12px;
|
||
padding: 6px 10px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.list {
|
||
padding: 0 14px;
|
||
}
|
||
.card {
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
margin-top: 10px;
|
||
overflow: hidden;
|
||
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.06);
|
||
}
|
||
.record {
|
||
padding: 0;
|
||
}
|
||
.record-head {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px 12px 10px;
|
||
gap: 8px;
|
||
}
|
||
.record-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #1f1f1f;
|
||
}
|
||
.record-date {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
.record-body {
|
||
padding: 0 12px 12px;
|
||
}
|
||
.line {
|
||
display: flex;
|
||
padding-top: 10px;
|
||
font-size: 13px;
|
||
color: #333;
|
||
line-height: 18px;
|
||
}
|
||
.line-label {
|
||
flex-shrink: 0;
|
||
color: #666;
|
||
}
|
||
.line-value {
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.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;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 12px;
|
||
border-top: 1px solid #f2f2f2;
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
.foot-left {
|
||
flex-shrink: 0;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.record-tag {
|
||
font-size: 12px;
|
||
color: #fff;
|
||
padding: 4px 8px;
|
||
border-radius: 8px;
|
||
}
|
||
.bg-blue {
|
||
background: #4f6ef7;
|
||
}
|
||
.bg-amber {
|
||
background: #d97706;
|
||
}
|
||
.bg-teal {
|
||
background: #0f766e;
|
||
}
|
||
.bg-indigo {
|
||
background: #4f46e5;
|
||
}
|
||
.bg-green {
|
||
background: #16a34a;
|
||
}
|
||
.bg-rose {
|
||
background: #f43f5e;
|
||
}
|
||
|
||
.empty {
|
||
padding: 120px 0;
|
||
text-align: center;
|
||
color: #9aa0a6;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.fab {
|
||
position: fixed;
|
||
right: 16px;
|
||
width: 52px;
|
||
height: 52px;
|
||
border-radius: 26px;
|
||
background: #4f6ef7;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 10px 18px rgba(79, 110, 247, 0.35);
|
||
z-index: 20;
|
||
}
|
||
</style>
|