Merge remote-tracking branch 'origin/dev-hjf' into dev-wdb

This commit is contained in:
huxuejian 2026-02-10 16:11:06 +08:00
commit 01f91d83d8
7 changed files with 205 additions and 130 deletions

View File

@ -376,11 +376,20 @@ function eventTypeLabel(eventType) {
}
function resolveUserName(userId) {
const id = String(userId || "");
if (!id) return "";
const map = userNameMap.value || {};
return String(map[id] || "") || id;
}
function refreshChatRoom() {
if (!props.fromChat) return;
const pending = uni.getStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY);
if (pending && typeof pending === "object") {
chatGroupId.value = String(pending.chatGroupId || pending.groupId || "");
}
}
function formatTodo(todo) {
const status = getStatus(todo);
const plannedExecutionTime = todo?.plannedExecutionTime;
@ -446,13 +455,23 @@ async function getMore() {
toast(res?.message || "获取回访任务失败");
return;
}
const arr = Array.isArray(res.data) ? res.data : [];
// { success, data: { data: [], total } }
const payload = res?.data;
const arr = payload && Array.isArray(payload.data)
? payload.data
: Array.isArray(payload)
? payload
: [];
const next = arr.map(formatTodo);
total.value = typeof res.total === "number" ? res.total : 0;
total.value = payload && typeof payload.total === "number" ? payload.total : 0;
pages.value = Math.ceil(total.value / pageSize) || 0;
list.value = page.value === 1 ? next : [...list.value, ...next];
page.value += 1;
} catch (e) {
console.error("getCustomerTodos failed:", e);
toast("获取回访任务失败");
} finally {
loading.value = false;
}

View File

@ -9,11 +9,16 @@
</view>
</picker>
<uni-datetime-picker type="daterange" :value="dateRange" @change="pickTimeRange">
<uni-datetime-picker type="daterange" v-model="dateRange" @change="pickTimeRange">
<view class="filter-pill">
<view class="pill-text">{{ dateRangeLabel }}</view>
<view class="pill-icons">
<view v-if="isDateRangeActive" class="pill-clear" @tap.stop="clearTimeRange" @click.stop="clearTimeRange">
<uni-icons type="closeempty" size="16" color="#666" />
</view>
<uni-icons type="arrowdown" size="12" color="#666" />
</view>
</view>
</uni-datetime-picker>
</view>
@ -80,7 +85,7 @@ const props = defineProps({
floatingBottom: { type: Number, default: 16 },
});
const FALLBACK_TEMPLATE_TYPES = ['outpatient', 'inhospital', 'preConsultation', 'physicalExaminationTemplate'];
const FALLBACK_TEMPLATE_TYPES = ['outpatient', 'inhospital', 'preConsultationRecord', 'physicalExaminationTemplate'];
const templates = ref([]);
const selectableTemplates = computed(() => templates.value.filter((i) => i && i.templateType && typeof i.name === 'string' && i.name.trim()));
const useActionSheet = computed(() => selectableTemplates.value.length > 0 && selectableTemplates.value.length <= 6);
@ -97,6 +102,9 @@ const currentType = ref({ name: '全部', value: 'ALL' });
const records = ref([]);
const dateRange = ref([]);
const isDateRangeActive = computed(
() => Array.isArray(dateRange.value) && dateRange.value.length === 2 && dateRange.value[0] && dateRange.value[1]
);
const dateRangeLabel = computed(() => {
if (Array.isArray(dateRange.value) && dateRange.value.length === 2 && dateRange.value[0] && dateRange.value[1]) {
return `${dateRange.value[0]}${dateRange.value[1]}`;
@ -226,12 +234,13 @@ async function loadTeamMembers() {
function getSortTimeTitle(templateType) {
const rawType = String(templateType || '');
const ui = normalizeMedicalType(rawType);
// 使 timeTitle
if (ui === 'preConsultationRecord') return 'consultationDate';
const t = templateMap.value[rawType] || {};
if (t?.service?.timeTitle) return String(t.service.timeTitle);
const ui = normalizeMedicalType(rawType);
if (ui === 'outpatient') return 'visitTime';
if (ui === 'inhospital') return 'inhosDate';
if (ui === 'preConsultation') return 'consultDate';
if (ui === 'physicalExaminationTemplate') return 'inspectDate';
return '';
}
@ -275,14 +284,15 @@ function normalizeMedicalType(raw) {
const s = String(raw || '').trim();
if (!s) return '';
const lower = s.toLowerCase();
if (lower.includes('preconsult') || (lower.includes('pre') && lower.includes('consult'))) return 'preConsultation';
if (lower.includes('preconsult') || (lower.includes('pre') && lower.includes('consult'))) return 'preConsultationRecord';
if (lower === 'outpatient' || lower === 'out_patient' || lower === 'out-patient') return 'outpatient';
if (lower === 'inhospital' || lower === 'in_hospital' || lower === 'in-hospital' || lower === 'inpatient') return 'inhospital';
if (lower === 'preconsultation' || lower === 'pre_consultation' || lower === 'pre-consultation') return 'preConsultation';
if (lower === 'preconsultation' || lower === 'pre_consultation' || lower === 'pre-consultation') return 'preConsultationRecord';
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
if (s === 'outPatient') return 'outpatient';
if (s === 'inHospital') return 'inhospital';
if (s === 'preConsultation') return 'preConsultation';
if (s === 'preConsultation') return 'preConsultationRecord';
if (s === 'preConsultationRecord') return 'preConsultationRecord';
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
return s;
}
@ -389,7 +399,7 @@ async function refreshList() {
const tagClass = {
outpatient: 'bg-amber',
inhospital: 'bg-teal',
preConsultation: 'bg-indigo',
preConsultationRecord: 'bg-indigo',
physicalExaminationTemplate: 'bg-green',
};
@ -401,14 +411,14 @@ function resolveRecordType(r) {
if (r.inspectDate || r.positiveFind || r.inspectSummary) return 'physicalExaminationTemplate';
if (r.inhosDate || r.surgeryName || r.surgeryDate || r.operationDate) return 'inhospital';
if (r.visitTime || r.disposePlan || r.treatmentPlan) return 'outpatient';
if (r.consultDate || r.presentIllness || r.presentIllnessHistory || r.pastHistory) return 'preConsultation';
if (r.consultationDate || r.consultDate || r.presentIllness || r.presentIllnessHistory || r.pastHistory) return 'preConsultationRecord';
return '';
}
function getDiagnosis(r) {
if (!r) return '--';
const t = resolveRecordType(r);
if (t === 'preConsultation') return normalizeText(r.chiefComplaint) || normalizeText(r.summary) || '--';
if (t === 'preConsultationRecord') 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) || '--';
@ -433,7 +443,7 @@ function getDisplayLines(r) {
if (t === 'physicalExaminationTemplate') {
return [{ label: '体检小结:', value: firstLine(r.summary || r.inspectSummary) }];
}
if (t === 'preConsultation') {
if (t === 'preConsultationRecord') {
const lines = [
{ label: '主诉:', value: firstLine(r.chiefComplaint) },
{ label: '现病史:', value: firstLine(r.presentIllness || r.presentIllnessHistory) },
@ -461,7 +471,19 @@ function pickType(e) {
refreshList();
}
function pickTimeRange(val) {
if (Array.isArray(val)) {
dateRange.value = val;
} else if (val && typeof val === 'object' && Array.isArray(val.value)) {
dateRange.value = val.value;
} else if (val && typeof val === 'object' && val.detail && Array.isArray(val.detail.value)) {
dateRange.value = val.detail.value;
} else {
dateRange.value = [];
}
refreshList();
}
function clearTimeRange() {
dateRange.value = [];
refreshList();
}
@ -589,6 +611,17 @@ watch(
text-overflow: ellipsis;
white-space: nowrap;
}
.pill-icons {
display: flex;
align-items: center;
gap: 12rpx;
flex-shrink: 0;
}
.pill-clear {
display: flex;
align-items: center;
justify-content: center;
}
.share-tip {
padding: 20rpx 28rpx 0;

View File

@ -136,7 +136,7 @@ function formatPatient(raw) {
const lr = raw.latestRecord;
const type = normalizeRecordTypeLabel(lr.type || lr.medicalTypeName || lr.medicalType || '');
const date = lr.date || '';
const diagnosis = lr.diagnosis || '';
const diagnosis = normalizeBriefText(lr.diagnosisName || lr.inspectSummary || lr.chiefComplaint || lr.diagnosis || '');
if (type || date || diagnosis) {
record = {
type: type || '-',
@ -169,6 +169,34 @@ function normalizeText(value) {
return String(value || '').trim();
}
function normalizeBriefText(value) {
if (value === null || value === undefined) return '';
if (Array.isArray(value)) {
const parts = value.map((i) => normalizeBriefText(i)).filter((i) => String(i).trim());
return parts.join('');
}
let s = String(value || '').trim();
if (!s) return '';
// "[]" JSON
if (s === '[]' || s === '[ ]') return '';
if (s.startsWith('[') && s.endsWith(']')) {
try {
const parsed = JSON.parse(s);
if (Array.isArray(parsed)) {
s = parsed.map((i) => normalizeBriefText(i)).filter((i) => String(i).trim()).join('');
}
} catch {
// JSON []
s = s.replace(/^\[\s*/, '').replace(/\s*\]$/, '');
}
}
s = String(s || '').replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ').trim();
if (s === '[]' || s === '[ ]') return '';
return s;
}
function normalizeDigits(value) {
const s = normalizeText(value);
return s.replace(/\D/g, '');

View File

@ -24,6 +24,10 @@ const ALIAS_MAP = {
presentIllnessHistory: 'presentIllness',
pastMedicalHistory: 'pastHistory',
},
preConsultationRecord: {
presentIllnessHistory: 'presentIllness',
pastMedicalHistory: 'pastHistory',
},
};
export function normalizeVisitRecordFormData(templateType, raw) {
@ -37,4 +41,3 @@ export function normalizeVisitRecordFormData(templateType, raw) {
return out;
}

View File

@ -76,6 +76,7 @@ const forms = computed(() => ({ ...detail.value, ...form }));
const HIDDEN_FIELD_NAMES = {
outpatient: ['就诊机构', '就诊科室', '机构名称', '责任医生'],
inhospital: ['就诊机构', '机构名称'],
physicalExaminationTemplate: ['体检套餐名称'],
};
const HIDDEN_FIELD_TITLES = {
// systemFieldName/title
@ -87,6 +88,8 @@ const HIDDEN_FIELD_TITLES = {
// - : corp
// - : corpName
inhospital: ['corp', 'corpName'],
// - : inspectPakageName
physicalExaminationTemplate: ['inspectPakageName'],
};
function shouldHideField(item) {
const t = String(templateType.value || '');
@ -306,10 +309,13 @@ async function remove() {
height: 100vh;
display: flex;
flex-direction: column;
position: relative;
}
.scroll {
flex: 1;
min-height: 0;
position: relative;
z-index: 1;
}
.scroll-gap {
height: calc(240rpx + env(safe-area-inset-bottom));
@ -340,6 +346,7 @@ async function remove() {
display: flex;
gap: 24rpx;
box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
z-index: 50;
}
.btn {
flex: 1;

View File

@ -49,8 +49,8 @@
</template>
<script setup>
import { computed, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import dayjs from 'dayjs';
import api from '@/utils/api';
import { loading, hideLoading, toast } from '@/utils/widget';
@ -65,6 +65,8 @@ const medicalType = ref('');
const rawType = ref('');
const record = ref({});
const temp = ref(null);
const needReload = ref(false);
let recordChangedHandler = null;
const files = computed(() => {
const arr = record.value?.files;
@ -79,14 +81,16 @@ function normalizeMedicalType(raw) {
const s = String(raw || '').trim();
if (!s) return '';
const lower = s.toLowerCase();
if (lower.includes('preconsult') || (lower.includes('pre') && lower.includes('consult'))) return 'preConsultation';
if (lower.includes('preconsultationrecord')) return 'preConsultationRecord';
if (lower.includes('preconsult') || (lower.includes('pre') && lower.includes('consult'))) return 'preConsultationRecord';
if (lower === 'outpatient' || lower === 'out_patient' || lower === 'out-patient') return 'outpatient';
if (lower === 'inhospital' || lower === 'in_hospital' || lower === 'in-hospital' || lower === 'inpatient') return 'inhospital';
if (lower === 'preconsultation' || lower === 'pre_consultation' || lower === 'pre-consultation') return 'preConsultation';
if (lower === 'preconsultation' || lower === 'pre_consultation' || lower === 'pre-consultation') return 'preConsultationRecord';
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
if (s === 'outPatient') return 'outpatient';
if (s === 'inHospital') return 'inhospital';
if (s === 'preConsultation') return 'preConsultation';
if (s === 'preConsultation') return 'preConsultationRecord';
if (s === 'preConsultationRecord') return 'preConsultationRecord';
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
return s;
}
@ -98,7 +102,7 @@ const typeLabel = computed(() => record.value?.tempName || temp.value?.name || g
function getDefaultTimeTitle(t) {
if (t === 'outpatient') return 'visitTime';
if (t === 'inhospital') return 'inhosDate';
if (t === 'preConsultation') return 'consultDate';
if (t === 'preConsultationRecord') return 'consultationDate';
if (t === 'physicalExaminationTemplate') return 'inspectDate';
return '';
}
@ -106,7 +110,7 @@ function getDefaultTimeTitle(t) {
function getDefaultTimeName(t) {
if (t === 'outpatient') return '就诊日期';
if (t === 'inhospital') return '入院日期';
if (t === 'preConsultation') return '问诊日期';
if (t === 'preConsultationRecord') return '问诊日期';
if (t === 'physicalExaminationTemplate') return '体检日期';
return '日期';
}
@ -212,6 +216,8 @@ const sections = computed(() => {
? ['corp', 'deptName', 'corpName', 'doctor']
: t === 'inhospital'
? ['corp', 'corpName']
: t === 'physicalExaminationTemplate'
? ['inspectPakageName']
: []);
const list = [];
@ -289,6 +295,41 @@ const topText = computed(() => {
return suffix ? `${time || '--'} ${suffix}` : `${time || '--'}`;
});
async function fetchRecord({ silent = false } = {}) {
if (!archiveId.value || !id.value || !medicalType.value) return false;
const corpId = getCorpId();
if (!corpId) {
if (!silent) toast('缺少 corpId');
return false;
}
if (!silent) loading('加载中...');
try {
const res = await api('getMedicalRecordById', {
_id: id.value,
corpId,
memberId: archiveId.value,
medicalType: medicalType.value,
});
const r = res?.record || res?.data?.record || null;
if (!res?.success || !r) return false;
const raw = String(r?.templateType || r?.medicalType || medicalType.value || '');
rawType.value = raw;
const ui = normalizeMedicalType(raw);
record.value = normalizeVisitRecordFormData(ui, r);
temp.value = await loadTemplate(raw);
uni.setNavigationBarTitle({ title: String(typeLabel.value || '病历详情') });
return true;
} catch (error) {
console.error('获取病历记录失败:', error);
if (!silent) toast('加载失败');
return false;
} finally {
if (!silent) hideLoading();
}
}
onLoad(async (opt) => {
archiveId.value = opt?.archiveId ? String(opt.archiveId) : '';
id.value = opt?.id ? String(opt.id) : '';
@ -298,45 +339,32 @@ onLoad(async (opt) => {
setTimeout(() => uni.navigateBack(), 300);
return;
}
// 使 API
loading('加载中...');
try {
const corpId = getCorpId();
if (!corpId) {
hideLoading();
toast('缺少 corpId');
setTimeout(() => uni.navigateBack(), 300);
return;
}
const res = await api('getMedicalRecordById', {
_id: id.value,
corpId,
memberId: archiveId.value,
medicalType: medicalType.value,
});
hideLoading();
const r = res?.record || res?.data?.record || null;
if (!res?.success || !r) {
const ok = await fetchRecord();
if (!ok) {
toast('记录不存在');
setTimeout(() => uni.navigateBack(), 300);
return;
}
const raw = String(r?.templateType || r?.medicalType || medicalType.value || '');
rawType.value = raw;
const ui = normalizeMedicalType(raw);
record.value = normalizeVisitRecordFormData(ui, r);
temp.value = await loadTemplate(raw);
uni.setNavigationBarTitle({ title: String(typeLabel.value || '病历详情') });
} catch (error) {
hideLoading();
console.error('获取病历记录失败:', error);
toast('加载失败');
setTimeout(() => uni.navigateBack(), 300);
}
});
onMounted(() => {
recordChangedHandler = () => {
needReload.value = true;
};
//
uni.$on('archive-detail:visit-record-changed', recordChangedHandler);
});
onUnmounted(() => {
if (recordChangedHandler) uni.$off('archive-detail:visit-record-changed', recordChangedHandler);
recordChangedHandler = null;
});
onShow(async () => {
if (!needReload.value) return;
needReload.value = false;
await fetchRecord({ silent: true });
});
function preview(idx) {
const urls = files.value.map((i) => i.url);
uni.previewImage({ urls, current: urls[idx] });

View File

@ -212,6 +212,7 @@ const lastPatientsLoadOk = ref(true);
const loadedGroupsTeamId = ref('');
let enterRefreshInflight = null;
const hasEnteredOnce = ref(false);
let lastReloadStartedAt = 0;
const accountStore = useAccountStore();
const { account, doctorInfo } = storeToRefs(accountStore);
@ -852,15 +853,16 @@ function normalizeMedicalType(raw) {
//
if (s.includes('门诊')) return 'outpatient';
if (s.includes('住院') || s.includes('入院')) return 'inhospital';
if (s.includes('预问诊') || s.includes('问诊')) return 'preConsultation';
if (s.includes('预问诊') || s.includes('问诊')) return 'preConsultationRecord';
if (s.includes('体检')) return 'physicalExaminationTemplate';
if (lower.includes('preconsult') || (lower.includes('pre') && lower.includes('consult'))) return 'preConsultation';
if (lower.includes('preconsult') || (lower.includes('pre') && lower.includes('consult'))) return 'preConsultationRecord';
if (lower === 'outpatient' || lower === 'out_patient' || lower === 'out-patient') return 'outpatient';
if (lower === 'inhospital' || lower === 'in_hospital' || lower === 'in-hospital' || lower === 'inpatient') return 'inhospital';
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
if (s === 'outPatient') return 'outpatient';
if (s === 'inHospital') return 'inhospital';
if (s === 'preConsultation') return 'preConsultation';
if (s === 'preConsultation') return 'preConsultationRecord';
if (s === 'preConsultationRecord') return 'preConsultationRecord';
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
return s;
}
@ -940,6 +942,7 @@ function resolveLatestRecord(lr) {
'date',
'visitTime',
'inhosDate',
'consultationDate',
'consultDate',
'inspectDate',
'sortTime',
@ -951,77 +954,22 @@ function resolveLatestRecord(lr) {
);
const rawDateStr = String(rawDate ?? '').trim();
const date = (/^\d{10,13}$/.test(rawDateStr) ? (formatAnyDate(rawDateStr, 'YYYY-MM-DD') || rawDateStr) : rawDateStr)
|| formatAnyDate(pick('visitTime', 'inhosDate', 'consultDate', 'inspectDate', 'sortTime', 'createTime', 'updateTime'), 'YYYY-MM-DD')
|| formatAnyDate(pick('visitTime', 'inhosDate', 'consultationDate', 'consultDate', 'inspectDate', 'sortTime', 'createTime', 'updateTime'), 'YYYY-MM-DD')
|| '-';
let third = '';
// diagnosis使
const directDiagnosis = normalizeText(pick('diagnosis', 'diagnosisName'));
if (String(directDiagnosis || '').trim()) {
third = directDiagnosis;
} else if (uiType === 'outpatient' || uiType === 'inhospital') {
third = normalizeText(pick(
'diagnosisName',
'diagnosis',
'diagnosisList',
'diagnosisNames',
'mainDiagnosis',
'admissionDiagnosis',
'inDiagnosis',
'outDiagnosis',
'outPatientDiagnosis',
'inHospitalDiagnosis'
));
} else if (uiType === 'preConsultation') {
third = normalizeText(
pick(
'diagnosis',
'diagnosisName',
'chiefComplaint',
'chiefComplain',
'chiefComplaintText',
'chiefComplaintContent',
'complaint',
'complaintDesc',
'complaintText',
'mainComplaint',
'mainSuit',
'mainSuitText',
'mainSuitContent',
'chief',
'zs',
'zhuSu',
'cc',
'presentIllness',
'historyOfPresentIllness',
'currentIllness'
)
);
// searchCorpCustomerForCaseList
// - chiefComplaint
// - inspectSummary
// - /diagnosisName
if (uiType === 'outpatient' || uiType === 'inhospital') {
third = normalizeText(pick('diagnosisName', 'diagnosis'));
} else if (uiType === 'physicalExaminationTemplate') {
third = normalizeText(
pick(
'diagnosis',
'diagnosisName',
'summary',
'summaryText',
'inspectSummary',
'checkSummary',
'examSummary',
'physicalSummary',
'briefSummary',
'resultSummary',
'conclusion',
'conclusionText',
'inspectConclusion',
'inspectResult',
'finalConclusion',
'finalSummary',
'reportConclusion',
'reportSummary'
)
);
third = normalizeText(pick('inspectSummary', 'summary', 'summaryText', 'inspectConclusion', 'inspectResult'));
} else if (uiType === 'preConsultationRecord') {
third = normalizeText(pick('chiefComplaint', 'chiefComplain', 'chiefComplaintText', 'chiefComplaintContent'));
} else {
third = normalizeText(pick('diagnosis', 'diagnosisName', 'summary', 'chiefComplaint'));
third = normalizeText(pick('diagnosisName', 'diagnosis', 'inspectSummary', 'summary', 'chiefComplaint'));
}
third = String(third || '').replace(/\s+/g, ' ').trim();
if (!third) {
@ -1033,7 +981,7 @@ function resolveLatestRecord(lr) {
const type = typeLabel || (uiType === 'outpatient' ? '门诊记录'
: uiType === 'inhospital' ? '住院记录'
: uiType === 'preConsultation' ? '预问诊记录'
: uiType === 'preConsultationRecord' ? '预问诊记录'
: uiType === 'physicalExaminationTemplate' ? '体检档案'
: '-');
@ -1217,6 +1165,7 @@ async function reload(reset = true, opts = {}) {
query.includeRecentAddTime = true;
}
lastReloadStartedAt = Date.now();
loading.value = true;
let res;
try {
@ -1769,6 +1718,8 @@ onLoad(async () => {
});
onShow(async () => {
const showAt = Date.now();
const wasEnteredOnce = hasEnteredOnce.value;
const need = uni.getStorageSync(NEED_RELOAD_STORAGE_KEY);
if (need) {
uni.removeStorageSync(NEED_RELOAD_STORAGE_KEY);
@ -1781,6 +1732,12 @@ onShow(async () => {
if (needGroups) uni.removeStorageSync(GROUPS_RELOAD_KEY);
await refreshOnEnter();
// inflight
// onShow reload reload
if (wasEnteredOnce && lastReloadStartedAt && lastReloadStartedAt < showAt) {
await reload(true, { silent: true, keepOnFail: true });
}
});
</script>