ykt-wxapp/components/archive-detail/health-profile-tab.vue
2026-01-28 20:01:28 +08:00

560 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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: 24rpx 0 192rpx;
}
.filters {
display: flex;
gap: 20rpx;
padding: 20rpx 28rpx;
background: #f5f6f8;
border-bottom: 2rpx solid #f2f2f2;
}
.filter-pill {
background: #fff;
border: 2rpx solid #e6e6e6;
border-radius: 12rpx;
padding: 20rpx 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
flex: 1;
}
.pill-text {
font-size: 26rpx;
color: #333;
max-width: 360rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.share-tip {
padding: 20rpx 28rpx 0;
}
.share-tip-text {
display: inline-block;
background: #eef2ff;
color: #4338ca;
font-size: 24rpx;
padding: 12rpx 20rpx;
border-radius: 12rpx;
}
.list {
padding: 0 28rpx;
}
.card {
background: #fff;
border-radius: 20rpx;
margin-top: 20rpx;
overflow: hidden;
box-shadow: 0 12rpx 28rpx rgba(0, 0, 0, 0.06);
}
.record {
padding: 0;
}
.record-head {
display: flex;
align-items: center;
padding: 24rpx 24rpx 20rpx;
gap: 16rpx;
}
.record-title {
font-size: 30rpx;
font-weight: 600;
color: #1f1f1f;
}
.record-date {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.record-body {
padding: 0 24rpx 24rpx;
}
.line {
display: flex;
padding-top: 20rpx;
font-size: 26rpx;
color: #333;
line-height: 36rpx;
}
.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: 20rpx;
display: flex;
gap: 20rpx;
align-items: center;
flex-wrap: wrap;
}
.thumb {
width: 168rpx;
height: 128rpx;
border-radius: 12rpx;
overflow: hidden;
background: #f3f4f6;
border: 2rpx solid #e5e7eb;
}
.thumb-img {
width: 168rpx;
height: 128rpx;
}
.thumb-more {
font-size: 24rpx;
color: #6b7280;
}
.record-foot {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 24rpx;
border-top: 2rpx solid #f2f2f2;
font-size: 24rpx;
color: #999;
}
.foot-left {
flex-shrink: 0;
margin-right: 20rpx;
}
.record-tag {
font-size: 24rpx;
color: #fff;
padding: 8rpx 16rpx;
border-radius: 16rpx;
}
.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: 240rpx 0;
text-align: center;
color: #9aa0a6;
font-size: 26rpx;
}
.fab {
position: fixed;
right: 32rpx;
width: 104rpx;
height: 104rpx;
border-radius: 52rpx;
background: #4f6ef7;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 20rpx 36rpx rgba(79, 110, 247, 0.35);
z-index: 20;
}
</style>