Merge commit '1018707e25082fb5b29b265ed55a9ea3499c44bf' into dev-wdb
This commit is contained in:
commit
5c040e3f55
@ -11,6 +11,11 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-cell-required {
|
||||||
|
margin-left: 4rpx;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
.form-content__wrapper {
|
.form-content__wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="form-row" @click="handleClick">
|
<view class="form-row" @click="handleClick">
|
||||||
<view class="form-row__label">
|
<view class="form-row__label">
|
||||||
{{ name }}<text v-if="required" class="form-cell--required"></text>
|
{{ name }}<text v-if="required" class="form-cell-required">*</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="form-row__content">
|
<view class="form-row__content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="files-wrap">
|
<view class="files-wrap">
|
||||||
<view class="files-label" :class="required ? 'form-cell--required' : ''">{{ name }}</view>
|
<view class="files-label">
|
||||||
|
{{ name }}<text v-if="required" class="form-cell-required">*</text>
|
||||||
|
</view>
|
||||||
<view class="grid">
|
<view class="grid">
|
||||||
<view v-for="(f, idx) in files" :key="idx" class="item" @click="preview(idx)">
|
<view v-for="(f, idx) in files" :key="idx" class="item" @click="preview(idx)">
|
||||||
<image class="thumb" :src="f.url" mode="aspectFill" />
|
<image class="thumb" :src="f.url" mode="aspectFill" />
|
||||||
@ -15,7 +17,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { chooseAndUploadImage } from '@/utils/file';
|
import { chooseAndUploadImage, normalizeFileUrl } from '@/utils/file';
|
||||||
import { toast } from '@/utils/widget';
|
import { toast } from '@/utils/widget';
|
||||||
|
|
||||||
const emits = defineEmits(['change']);
|
const emits = defineEmits(['change']);
|
||||||
@ -38,13 +40,13 @@ const files = computed(() => {
|
|||||||
if (Array.isArray(v)) {
|
if (Array.isArray(v)) {
|
||||||
return v
|
return v
|
||||||
.map((i) => {
|
.map((i) => {
|
||||||
if (typeof i === 'string') return { url: i };
|
if (typeof i === 'string') return { url: normalizeFileUrl(i) };
|
||||||
if (i && typeof i === 'object' && i.url) return { url: String(i.url) };
|
if (i && typeof i === 'object' && i.url) return { url: normalizeFileUrl(String(i.url)) };
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
if (typeof v === 'string' && v) return [{ url: v }];
|
if (typeof v === 'string' && v) return [{ url: normalizeFileUrl(v) }];
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -141,4 +143,3 @@ async function add() {
|
|||||||
line-height: 56rpx;
|
line-height: 56rpx;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="multi-wrap">
|
<view class="multi-wrap">
|
||||||
<view class="label" :class="required ? 'form-cell--required' : ''">{{ name }}</view>
|
<view class="label">
|
||||||
|
{{ name }}<text v-if="required" class="form-cell-required">*</text>
|
||||||
|
</view>
|
||||||
<view class="options" :class="hasOtherSelected ? 'with-other' : ''">
|
<view class="options" :class="hasOtherSelected ? 'with-other' : ''">
|
||||||
<view
|
<view
|
||||||
v-for="opt in displayOptions"
|
v-for="opt in displayOptions"
|
||||||
@ -165,4 +167,3 @@ function onOtherInput(e) {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="form-row">
|
<view class="form-row">
|
||||||
<view class="form-row__label" :class="required ? 'form-cell--required' : ''">{{ name }}</view>
|
<view class="form-row__label">
|
||||||
|
{{ name }}<text v-if="required" class="form-cell-required">*</text>
|
||||||
|
</view>
|
||||||
<view class="form-row__content runtime">
|
<view class="form-row__content runtime">
|
||||||
<input :disabled="disableChange" class="num" type="number" :value="year" @input="onInput($event, 'year')" />
|
<input :disabled="disableChange" class="num" type="number" :value="year" @input="onInput($event, 'year')" />
|
||||||
<text class="unit">年</text>
|
<text class="unit">年</text>
|
||||||
@ -86,4 +88,3 @@ function onInput(e, key) {
|
|||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="form-row">
|
<view class="form-row">
|
||||||
<view class="form-row__label" :class="required ? 'form-cell--required' : ''">{{ name }}</view>
|
<view class="form-row__label">
|
||||||
|
{{ name }}<text v-if="required" class="form-cell-required">*</text>
|
||||||
|
</view>
|
||||||
<view class="form-row__content content">
|
<view class="form-row__content content">
|
||||||
<input
|
<input
|
||||||
:disabled="disableChange"
|
:disabled="disableChange"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="textarea-row">
|
<view class="textarea-row">
|
||||||
<view class="form-row__label">
|
<view class="form-row__label">
|
||||||
{{ name }}<text v-if="required" class="form-cell--required"></text>
|
{{ name }}<text v-if="required" class="form-cell-required">*</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="mt-10">
|
<view class="mt-10">
|
||||||
<textarea
|
<textarea
|
||||||
@ -94,6 +94,8 @@ function change(e) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import '../cell-style.css';
|
||||||
|
|
||||||
.textarea-row {
|
.textarea-row {
|
||||||
padding: 24rpx 30rpx;
|
padding: 24rpx 30rpx;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
@change="change"
|
@change="change"
|
||||||
/>
|
/>
|
||||||
<form-diagnosis-picker
|
<form-diagnosis-picker
|
||||||
v-else-if="attrs.title === 'diagnosis' || attrs.title === 'diagnosisName'"
|
v-else-if="attrs.type === 'diagnosis' || attrs.__originType === 'diagnosis' || attrs.title === 'diagnosis' || attrs.title === 'diagnosisName'"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
:form="form"
|
:form="form"
|
||||||
:disableChange="disableChange"
|
:disableChange="disableChange"
|
||||||
|
|||||||
@ -71,6 +71,8 @@ import dayjs from 'dayjs';
|
|||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { loading, hideLoading, toast } from '@/utils/widget';
|
import { loading, hideLoading, toast } from '@/utils/widget';
|
||||||
import { normalizeTemplate } from '../../utils/template';
|
import { normalizeTemplate } from '../../utils/template';
|
||||||
|
import { normalizeVisitRecordFormData } from '../../utils/visit-record';
|
||||||
|
import { normalizeFileUrl } from '@/utils/file';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: { type: Object, default: () => ({}) },
|
data: { type: Object, default: () => ({}) },
|
||||||
@ -128,16 +130,25 @@ function getCorpId() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadedCorpId = ref('');
|
const loadedCorpId = ref('');
|
||||||
|
let loadVisitTemplatesPromise = null;
|
||||||
|
let loadVisitTemplatesCorpId = '';
|
||||||
async function loadVisitTemplates() {
|
async function loadVisitTemplates() {
|
||||||
const corpId = getCorpId();
|
const corpId = getCorpId();
|
||||||
if (!corpId) return;
|
if (!corpId) return;
|
||||||
if (loadedCorpId.value === corpId && templates.value.length) return;
|
if (loadedCorpId.value === corpId && templates.value.length) return;
|
||||||
loadedCorpId.value = corpId;
|
if (loadVisitTemplatesPromise && loadVisitTemplatesCorpId === corpId) return loadVisitTemplatesPromise;
|
||||||
|
|
||||||
try {
|
loadVisitTemplatesCorpId = corpId;
|
||||||
|
loadVisitTemplatesPromise = (async () => {
|
||||||
const groupRes = await api('getTemplateGroup', { corpId, parentType: 'medicalRecord' });
|
const groupRes = await api('getTemplateGroup', { corpId, parentType: 'medicalRecord' });
|
||||||
const group = groupRes?.data && Array.isArray(groupRes.data?.data) ? groupRes.data.data : Array.isArray(groupRes?.data) ? groupRes.data : [];
|
const group = groupRes?.data && Array.isArray(groupRes.data?.data) ? groupRes.data.data : Array.isArray(groupRes?.data) ? groupRes.data : [];
|
||||||
const list = Array.isArray(group) ? group : [];
|
const list = Array.isArray(group) ? group : [];
|
||||||
|
const groupNameMap = list.reduce((m, i) => {
|
||||||
|
const t = i?.templateType ? String(i.templateType) : '';
|
||||||
|
const name = i?.name ? String(i.name) : '';
|
||||||
|
if (t && name) m[t] = name;
|
||||||
|
return m;
|
||||||
|
}, {});
|
||||||
const enabled = list.filter((i) => i && i.templateType !== 'healthTemplate' && i.templateStatus !== 'disable');
|
const enabled = list.filter((i) => i && i.templateType !== 'healthTemplate' && i.templateStatus !== 'disable');
|
||||||
const typeList = enabled.map((i) => String(i.templateType || '')).filter(Boolean);
|
const typeList = enabled.map((i) => String(i.templateType || '')).filter(Boolean);
|
||||||
if (!typeList.length) return;
|
if (!typeList.length) return;
|
||||||
@ -155,9 +166,10 @@ async function loadVisitTemplates() {
|
|||||||
const next = ordered
|
const next = ordered
|
||||||
.map((t) => {
|
.map((t) => {
|
||||||
const temp = normalizeTemplate(t);
|
const temp = normalizeTemplate(t);
|
||||||
const name = String(temp?.name || temp?.templateName || temp?.templateTypeName || '') || String(temp?.templateType || '');
|
const rawType = String(temp?.templateType || '');
|
||||||
|
const name = String(groupNameMap[rawType] || temp?.name || temp?.templateName || temp?.templateTypeName || '') || rawType;
|
||||||
return {
|
return {
|
||||||
templateType: String(temp?.templateType || ''),
|
templateType: rawType,
|
||||||
name,
|
name,
|
||||||
service: temp?.service || {},
|
service: temp?.service || {},
|
||||||
templateList: Array.isArray(temp?.templateList) ? temp.templateList : [],
|
templateList: Array.isArray(temp?.templateList) ? temp.templateList : [],
|
||||||
@ -165,10 +177,22 @@ async function loadVisitTemplates() {
|
|||||||
})
|
})
|
||||||
.filter((i) => i && i.templateType);
|
.filter((i) => i && i.templateType);
|
||||||
|
|
||||||
if (next.length) templates.value = next;
|
if (next.length) {
|
||||||
} catch (e) {
|
templates.value = next;
|
||||||
console.error('loadVisitTemplates error:', e);
|
loadedCorpId.value = corpId;
|
||||||
}
|
}
|
||||||
|
})()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('loadVisitTemplates error:', e);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (loadVisitTemplatesCorpId === corpId) {
|
||||||
|
loadVisitTemplatesPromise = null;
|
||||||
|
loadVisitTemplatesCorpId = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return loadVisitTemplatesPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userNameMap = ref({});
|
const userNameMap = ref({});
|
||||||
@ -201,12 +225,14 @@ async function loadTeamMembers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSortTimeTitle(templateType) {
|
function getSortTimeTitle(templateType) {
|
||||||
const t = templateMap.value[String(templateType || '')] || {};
|
const rawType = String(templateType || '');
|
||||||
|
const t = templateMap.value[rawType] || {};
|
||||||
if (t?.service?.timeTitle) return String(t.service.timeTitle);
|
if (t?.service?.timeTitle) return String(t.service.timeTitle);
|
||||||
if (templateType === 'outpatient') return 'visitTime';
|
const ui = normalizeMedicalType(rawType);
|
||||||
if (templateType === 'inhospital') return 'inhosDate';
|
if (ui === 'outpatient') return 'visitTime';
|
||||||
if (templateType === 'preConsultation') return 'consultDate';
|
if (ui === 'inhospital') return 'inhosDate';
|
||||||
if (templateType === 'physicalExaminationTemplate') return 'inspectDate';
|
if (ui === 'preConsultation') return 'consultDate';
|
||||||
|
if (ui === 'physicalExaminationTemplate') return 'inspectDate';
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,15 +268,48 @@ function getTemplateName(type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toDateStr(sortTime) {
|
function toDateStr(sortTime) {
|
||||||
if (!sortTime) return '';
|
return formatAnyDate(sortTime, 'YYYY-MM-DD');
|
||||||
const d = dayjs(sortTime);
|
}
|
||||||
return d.isValid() ? d.format('YYYY-MM-DD') : '';
|
|
||||||
|
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 === '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 === '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 === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAnyTimeMs(v) {
|
||||||
|
if (v === null || v === undefined) return 0;
|
||||||
|
if (typeof v === 'number') {
|
||||||
|
// 10位秒级时间戳
|
||||||
|
if (v > 1e9 && v < 1e12) return v * 1000;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
const s = String(v).trim();
|
||||||
|
if (!s) return 0;
|
||||||
|
if (/^\d{10,13}$/.test(s)) return Number(s.length === 10 ? `${s}000` : s);
|
||||||
|
const d = dayjs(s);
|
||||||
|
return d.isValid() ? d.valueOf() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAnyDate(v, fmt = 'YYYY-MM-DD') {
|
||||||
|
const ms = parseAnyTimeMs(v);
|
||||||
|
if (!ms) return '';
|
||||||
|
const d = dayjs(ms);
|
||||||
|
return d.isValid() ? d.format(fmt) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function toDateTimeStr(ts) {
|
function toDateTimeStr(ts) {
|
||||||
if (!ts) return '';
|
return formatAnyDate(ts, 'YYYY-MM-DD HH:mm');
|
||||||
const d = dayjs(ts);
|
|
||||||
return d.isValid() ? d.format('YYYY-MM-DD HH:mm') : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshList() {
|
async function refreshList() {
|
||||||
@ -285,21 +344,31 @@ async function refreshList() {
|
|||||||
? res.list
|
? res.list
|
||||||
: Array.isArray(res?.data?.list)
|
: Array.isArray(res?.data?.list)
|
||||||
? res.data.list
|
? res.data.list
|
||||||
|
: Array.isArray(res?.data?.data)
|
||||||
|
? res.data.data
|
||||||
|
: Array.isArray(res?.data?.data?.list)
|
||||||
|
? res.data.data.list
|
||||||
: Array.isArray(res?.data)
|
: Array.isArray(res?.data)
|
||||||
? res.data
|
? res.data
|
||||||
: [];
|
: [];
|
||||||
if (list.length) {
|
if (list.length) {
|
||||||
const mapped = list.map((r) => {
|
const mapped = list.map((r) => {
|
||||||
const t = String(r?.medicalType || r?.templateType || '') || '';
|
const rawType = String(r?.medicalType || r?.templateType || '') || '';
|
||||||
const timeTitle = getSortTimeTitle(t);
|
const uiType = normalizeMedicalType(rawType);
|
||||||
const dateStr = timeTitle ? normalizeText(r?.[timeTitle]) : '';
|
const normalized = normalizeVisitRecordFormData(uiType, r);
|
||||||
|
const timeTitle = getSortTimeTitle(rawType);
|
||||||
|
const rawTime = timeTitle ? (normalized?.[timeTitle] ?? r?.[timeTitle]) : '';
|
||||||
|
const dateStr = rawTime ? formatAnyDate(rawTime, 'YYYY-MM-DD') : '';
|
||||||
return {
|
return {
|
||||||
...r,
|
...r,
|
||||||
templateType: t,
|
...normalized,
|
||||||
|
medicalType: rawType,
|
||||||
|
templateType: uiType,
|
||||||
|
rawTemplateType: rawType,
|
||||||
dateStr: dateStr || toDateStr(r?.sortTime),
|
dateStr: dateStr || toDateStr(r?.sortTime),
|
||||||
createDateStr: r?.createTime ? dayjs(r.createTime).format('YYYY-MM-DD') : '',
|
createDateStr: r?.createTime ? formatAnyDate(r.createTime, 'YYYY-MM-DD') : '',
|
||||||
createTimeStr: toDateTimeStr(r?.createTime),
|
createTimeStr: toDateTimeStr(r?.createTime),
|
||||||
tempName: r?.tempName || getTemplateName(t) || '病历',
|
tempName: r?.tempName || getTemplateName(rawType) || '病历',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -324,9 +393,21 @@ const tagClass = {
|
|||||||
physicalExaminationTemplate: 'bg-green',
|
physicalExaminationTemplate: 'bg-green',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function resolveRecordType(r) {
|
||||||
|
if (!r) return '';
|
||||||
|
const direct = normalizeMedicalType(r.uiType || r.templateType || r.rawTemplateType || r.medicalType || '');
|
||||||
|
if (direct) return direct;
|
||||||
|
// fallback by known fields
|
||||||
|
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';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function getDiagnosis(r) {
|
function getDiagnosis(r) {
|
||||||
if (!r) return '--';
|
if (!r) return '--';
|
||||||
const t = r.templateType || r.medicalType;
|
const t = resolveRecordType(r);
|
||||||
if (t === 'preConsultation') return normalizeText(r.chiefComplaint) || normalizeText(r.summary) || '--';
|
if (t === 'preConsultation') return normalizeText(r.chiefComplaint) || normalizeText(r.summary) || '--';
|
||||||
if (t === 'physicalExaminationTemplate') return formatPositiveFind(r.positiveFind) || 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) || '--';
|
if (t === 'outpatient' || t === 'inhospital') return normalizeText(r.diagnosisName || r.diagnosis) || normalizeText(r.summary) || '--';
|
||||||
@ -339,7 +420,7 @@ function firstLine(v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayLines(r) {
|
function getDisplayLines(r) {
|
||||||
const t = r?.templateType || r?.medicalType;
|
const t = resolveRecordType(r);
|
||||||
if (t === 'outpatient') {
|
if (t === 'outpatient') {
|
||||||
return [{ label: '门诊诊断:', value: firstLine(r.diagnosisName || r.diagnosis) }];
|
return [{ label: '门诊诊断:', value: firstLine(r.diagnosisName || r.diagnosis) }];
|
||||||
}
|
}
|
||||||
@ -350,12 +431,12 @@ function getDisplayLines(r) {
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
if (t === 'physicalExaminationTemplate') {
|
if (t === 'physicalExaminationTemplate') {
|
||||||
return [{ label: '体检小结:', value: firstLine(r.summary) }];
|
return [{ label: '体检小结:', value: firstLine(r.summary || r.inspectSummary) }];
|
||||||
}
|
}
|
||||||
if (t === 'preConsultation') {
|
if (t === 'preConsultation') {
|
||||||
const lines = [
|
const lines = [
|
||||||
{ label: '主诉:', value: firstLine(r.chiefComplaint) },
|
{ label: '主诉:', value: firstLine(r.chiefComplaint) },
|
||||||
{ label: '现病史:', value: firstLine(r.presentIllness) },
|
{ label: '现病史:', value: firstLine(r.presentIllness || r.presentIllnessHistory) },
|
||||||
];
|
];
|
||||||
const past = normalizeText(r.pastHistory);
|
const past = normalizeText(r.pastHistory);
|
||||||
if (past) lines.push({ label: '既往史:', value: past });
|
if (past) lines.push({ label: '既往史:', value: past });
|
||||||
@ -386,7 +467,11 @@ function pickTimeRange(val) {
|
|||||||
|
|
||||||
function getFiles(r) {
|
function getFiles(r) {
|
||||||
const arr = r?.files;
|
const arr = r?.files;
|
||||||
return Array.isArray(arr) ? arr.filter((i) => i && i.url) : [];
|
return Array.isArray(arr)
|
||||||
|
? arr
|
||||||
|
.filter((i) => i && i.url)
|
||||||
|
.map((i) => ({ ...i, url: normalizeFileUrl(i.url) }))
|
||||||
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function previewFiles(r, idx) {
|
function previewFiles(r, idx) {
|
||||||
@ -455,10 +540,6 @@ function edit(record) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 提前加载模板,避免首次点“+”时 picker 无法立即弹出
|
|
||||||
loadVisitTemplates();
|
|
||||||
// archiveId 可能后置赋值:这里保留一次兜底刷新,主逻辑交给 watch
|
|
||||||
refreshList();
|
|
||||||
uni.$on('archive-detail:visit-record-changed', refreshList);
|
uni.$on('archive-detail:visit-record-changed', refreshList);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,9 @@ export const VISIT_RECORD_TEMPLATES = [
|
|||||||
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请输入或说话录音转录问题', rows: 1, autoHeight: true },
|
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请输入或说话录音转录问题', rows: 1, autoHeight: true },
|
||||||
{ title: 'medicalHistory', name: '病史概要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请简述患者情况,量键既往病史、用药史等', rows: 3, autoHeight: true },
|
{ title: 'medicalHistory', name: '病史概要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请简述患者情况,量键既往病史、用药史等', rows: 3, autoHeight: true },
|
||||||
{ title: 'examination', name: '检查', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请填写关键项目或异常结果描述', rows: 1, autoHeight: true },
|
{ title: 'examination', name: '检查', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请填写关键项目或异常结果描述', rows: 1, autoHeight: true },
|
||||||
{ title: 'diagnosis', name: '诊断', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请填写诊断名称', supportVoice: true, rows: 1, autoHeight: true },
|
{ title: 'diagnosis', name: '诊断', type: 'diagnosis', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请选择诊断', supportVoice: true, rows: 1, autoHeight: true },
|
||||||
{ title: 'treatmentPlan', name: '治疗方案', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000, placeholder: '请简述治疗方案及效果', rows: 3, autoHeight: true },
|
{ title: 'treatmentPlan', name: '治疗方案', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000, placeholder: '请简述治疗方案及效果', rows: 3, autoHeight: true },
|
||||||
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(支持≤5M文件,pdf文件格式)', maxSize: 5, accept: 'pdf' },
|
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(仅支持拍摄/相册选择图片,≤5M)', maxSize: 5, accept: 'image' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -22,11 +22,11 @@ export const VISIT_RECORD_TEMPLATES = [
|
|||||||
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请输入病症与转诊问题', rows: 1, autoHeight: true },
|
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请输入病症与转诊问题', rows: 1, autoHeight: true },
|
||||||
{ title: 'medicalHistory', name: '病史概要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请简述患者情况,量键既往病史、用药史等症状', rows: 3, autoHeight: true },
|
{ title: 'medicalHistory', name: '病史概要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请简述患者情况,量键既往病史、用药史等症状', rows: 3, autoHeight: true },
|
||||||
{ title: 'examination', name: '检查', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请填写关键项目或异常结果描述', rows: 1, autoHeight: true },
|
{ title: 'examination', name: '检查', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请填写关键项目或异常结果描述', rows: 1, autoHeight: true },
|
||||||
{ title: 'diagnosis', name: '住院主诊断', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请填写诊断名称', rows: 1, autoHeight: true },
|
{ title: 'diagnosis', name: '住院主诊断', type: 'diagnosis', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请选择诊断', rows: 1, autoHeight: true },
|
||||||
{ title: 'surgeryDate', name: '手术日期', type: 'date', operateType: 'formCell', required: false, format: 'YYYY-MM-DD', placeholder: '请选择手术日期' },
|
{ title: 'surgeryDate', name: '手术日期', type: 'date', operateType: 'formCell', required: false, format: 'YYYY-MM-DD', placeholder: '请选择手术日期' },
|
||||||
{ title: 'surgeryName', name: '手术名称', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 100, placeholder: '请填写手术名称', rows: 1, autoHeight: true },
|
{ title: 'surgeryName', name: '手术名称', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 100, placeholder: '请填写手术名称', rows: 1, autoHeight: true },
|
||||||
{ title: 'treatmentPlan', name: '治疗方案', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000, placeholder: '请简述治疗方案及效果', rows: 3, autoHeight: true },
|
{ title: 'treatmentPlan', name: '治疗方案', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000, placeholder: '请简述治疗方案及效果', rows: 3, autoHeight: true },
|
||||||
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(支持≤5M文件,pdf文件格式)', maxSize: 5, accept: 'pdf' },
|
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(仅支持拍摄/相册选择图片,≤5M)', maxSize: 5, accept: 'image' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -37,7 +37,7 @@ export const VISIT_RECORD_TEMPLATES = [
|
|||||||
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请输入症状及转诊问题', rows: 1, autoHeight: true },
|
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请输入症状及转诊问题', rows: 1, autoHeight: true },
|
||||||
{ title: 'presentIllness', name: '现病史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800, placeholder: '请述述发病的过程、发展、诊疗经过及当前病情', rows: 3, autoHeight: true },
|
{ title: 'presentIllness', name: '现病史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800, placeholder: '请述述发病的过程、发展、诊疗经过及当前病情', rows: 3, autoHeight: true },
|
||||||
{ title: 'pastHistory', name: '既往史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800, placeholder: '请填写既往疾病、手术/外伤史、药物/食物过敏史', rows: 3, autoHeight: true },
|
{ title: 'pastHistory', name: '既往史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800, placeholder: '请填写既往疾病、手术/外伤史、药物/食物过敏史', rows: 3, autoHeight: true },
|
||||||
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(支持≤5M文件,pdf文件格式)', maxSize: 5, accept: 'pdf' },
|
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(仅支持拍摄/相册选择图片,≤5M)', maxSize: 5, accept: 'image' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -48,7 +48,7 @@ export const VISIT_RECORD_TEMPLATES = [
|
|||||||
{ title: 'inspectDate', name: '体检日期', type: 'date', operateType: 'formCell', required: false, format: 'YYYY-MM-DD', placeholder: '请选择体检日期' },
|
{ title: 'inspectDate', name: '体检日期', type: 'date', operateType: 'formCell', required: false, format: 'YYYY-MM-DD', placeholder: '请选择体检日期' },
|
||||||
{ title: 'summary', name: '体检小结', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请填写本次体检的小结', rows: 3, autoHeight: true },
|
{ title: 'summary', name: '体检小结', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请填写本次体检的小结', rows: 3, autoHeight: true },
|
||||||
{ title: 'positiveFind', name: '阳性发现及处理意见', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '可参照医客通现有模式', rows: 3, refNote: '可参照医客通现有模式', autoHeight: true },
|
{ title: 'positiveFind', name: '阳性发现及处理意见', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '可参照医客通现有模式', rows: 3, refNote: '可参照医客通现有模式', autoHeight: true },
|
||||||
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(支持≤5M文件,pdf文件格式)', maxSize: 5, accept: 'pdf' },
|
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(仅支持拍摄/相册选择图片,≤5M)', maxSize: 5, accept: 'image' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export function normalizeTemplateItem(item) {
|
|||||||
selectWwuser: 'select',
|
selectWwuser: 'select',
|
||||||
files: 'files',
|
files: 'files',
|
||||||
corpProject: 'select',
|
corpProject: 'select',
|
||||||
diagnosis: 'textarea',
|
diagnosis: 'diagnosis',
|
||||||
BMI: 'input',
|
BMI: 'input',
|
||||||
bloodPressure: 'textarea',
|
bloodPressure: 'textarea',
|
||||||
selfMultipleDiseases: 'textarea',
|
selfMultipleDiseases: 'textarea',
|
||||||
@ -92,4 +92,3 @@ export function normalizeTemplate(temp) {
|
|||||||
.map(normalizeTemplateItem);
|
.map(normalizeTemplateItem);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,24 +7,6 @@
|
|||||||
<FormTemplate v-if="temp" ref="formRef" :items="showItems" :form="forms" @change="onChange" />
|
<FormTemplate v-if="temp" ref="formRef" :items="showItems" :form="forms" @change="onChange" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 附件上传(FormTemplate 不支持 files,单独实现) -->
|
|
||||||
<view v-if="hasFilesField" class="upload-wrap">
|
|
||||||
<view class="upload-row">
|
|
||||||
<view class="upload-label">文件上传</view>
|
|
||||||
<view class="upload-desc">(支持≤5M文件,pdf文件格式)</view>
|
|
||||||
</view>
|
|
||||||
<view class="upload-grid">
|
|
||||||
<view v-for="(f, idx) in fileList" :key="idx" class="upload-item" @click="previewFile(idx)">
|
|
||||||
<image v-if="!isPdfUrl(f.url)" class="upload-thumb" :src="f.url" mode="aspectFill" />
|
|
||||||
<view v-else class="upload-pdf">PDF</view>
|
|
||||||
<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: 240rpx;"></view>
|
<view style="height: 240rpx;"></view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
@ -47,7 +29,6 @@ import dayjs from 'dayjs';
|
|||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import FormTemplate from '@/components/form-template/index.vue';
|
import FormTemplate from '@/components/form-template/index.vue';
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { uploadFile } from '@/utils/file';
|
|
||||||
import useAccountStore from '@/store/account';
|
import useAccountStore from '@/store/account';
|
||||||
import { toast, confirm, loading as uniLoading, hideLoading } from '@/utils/widget';
|
import { toast, confirm, loading as uniLoading, hideLoading } from '@/utils/widget';
|
||||||
import { normalizeVisitRecordFormData } from './utils/visit-record';
|
import { normalizeVisitRecordFormData } from './utils/visit-record';
|
||||||
@ -92,11 +73,34 @@ const titleText = computed(() => {
|
|||||||
const detail = ref({});
|
const detail = ref({});
|
||||||
const form = reactive({});
|
const form = reactive({});
|
||||||
const forms = computed(() => ({ ...detail.value, ...form }));
|
const forms = computed(() => ({ ...detail.value, ...form }));
|
||||||
|
const HIDDEN_FIELD_NAMES = {
|
||||||
|
outpatient: ['就诊机构', '就诊科室', '机构名称', '责任医生'],
|
||||||
|
inhospital: ['就诊机构', '机构名称'],
|
||||||
|
};
|
||||||
|
const HIDDEN_FIELD_TITLES = {
|
||||||
|
// 对应 systemFieldName/title(来自模板):
|
||||||
|
// - 就诊机构: corp
|
||||||
|
// - 就诊科室: deptName
|
||||||
|
// - 机构名称: corpName
|
||||||
|
// - 责任医生: doctor
|
||||||
|
outpatient: ['corp', 'deptName', 'corpName', 'doctor'],
|
||||||
|
// - 就诊机构: corp
|
||||||
|
// - 机构名称: corpName
|
||||||
|
inhospital: ['corp', 'corpName'],
|
||||||
|
};
|
||||||
|
function shouldHideField(item) {
|
||||||
|
const t = String(templateType.value || '');
|
||||||
|
const name = item?.name ? String(item.name).trim() : '';
|
||||||
|
const title = item?.title ? String(item.title).trim() : '';
|
||||||
|
const hiddenNames = HIDDEN_FIELD_NAMES[t] || [];
|
||||||
|
const hiddenTitles = HIDDEN_FIELD_TITLES[t] || [];
|
||||||
|
return (name && hiddenNames.includes(name)) || (title && hiddenTitles.includes(title));
|
||||||
|
}
|
||||||
const showItems = computed(() => {
|
const showItems = computed(() => {
|
||||||
const list = temp.value?.templateList || [];
|
const list = temp.value?.templateList || [];
|
||||||
// referenceField 兼容(与 mobile 一致)
|
// referenceField 兼容(与 mobile 一致)
|
||||||
return list.filter((i) => {
|
return list.filter((i) => {
|
||||||
if (i?.type === 'files') return false;
|
if (shouldHideField(i)) return false;
|
||||||
if (i && typeof i.referenceField === 'string') {
|
if (i && typeof i.referenceField === 'string') {
|
||||||
return forms.value[i.referenceField] === i.referenceValue;
|
return forms.value[i.referenceField] === i.referenceValue;
|
||||||
}
|
}
|
||||||
@ -104,16 +108,7 @@ const showItems = computed(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasFilesField = computed(() => {
|
|
||||||
const list = temp.value?.templateList || [];
|
|
||||||
return list.some((i) => i && (i.type === 'files' || i.title === 'files'));
|
|
||||||
});
|
|
||||||
|
|
||||||
const formRef = ref(null);
|
const formRef = ref(null);
|
||||||
const fileList = computed(() => {
|
|
||||||
const arr = forms.value?.files;
|
|
||||||
return Array.isArray(arr) ? arr.filter((i) => i && i.url) : [];
|
|
||||||
});
|
|
||||||
|
|
||||||
function ensureFilesField() {
|
function ensureFilesField() {
|
||||||
if (form.files !== undefined) return;
|
if (form.files !== undefined) return;
|
||||||
@ -300,86 +295,6 @@ async function remove() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPdfUrl(url) {
|
|
||||||
const u = String(url || '').toLowerCase();
|
|
||||||
return u.includes('.pdf') || u.startsWith('data:application/pdf');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addFiles() {
|
|
||||||
const fileConfig = temp.value?.templateList?.find((i) => i && (i.type === 'files' || i.title === 'files')) || {};
|
|
||||||
const maxSize = Number(fileConfig?.maxSize || 5) || 5; // MB
|
|
||||||
const accept = String(fileConfig?.accept || 'pdf') || 'pdf';
|
|
||||||
|
|
||||||
const chooseRes = await new Promise((resolve) => {
|
|
||||||
uni.chooseMessageFile({
|
|
||||||
count: 9,
|
|
||||||
type: 'file',
|
|
||||||
extension: accept ? [accept] : undefined,
|
|
||||||
success: (res) => resolve(res),
|
|
||||||
fail: () => resolve(null),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const files = Array.isArray(chooseRes?.tempFiles) ? chooseRes.tempFiles : [];
|
|
||||||
if (!files.length) return;
|
|
||||||
|
|
||||||
const maxBytes = maxSize * 1024 * 1024;
|
|
||||||
const invalidFiles = files.filter((f) => f && f.size > maxBytes);
|
|
||||||
if (invalidFiles.length > 0) {
|
|
||||||
toast(`文件大小不能超过${maxSize}M`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cur = Array.isArray(forms.value.files) ? forms.value.files : [];
|
|
||||||
uniLoading('上传中...');
|
|
||||||
try {
|
|
||||||
const uploaded = [];
|
|
||||||
for (const f of files) {
|
|
||||||
const url = await uploadFile(f.path);
|
|
||||||
if (!url) {
|
|
||||||
toast('上传失败');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
uploaded.push({ url, name: f.name || '', size: f.size });
|
|
||||||
}
|
|
||||||
form.files = [...cur, ...uploaded];
|
|
||||||
} finally {
|
|
||||||
hideLoading();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeFile(idx) {
|
|
||||||
const cur = Array.isArray(forms.value.files) ? forms.value.files : [];
|
|
||||||
form.files = cur.filter((_, i) => i !== idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
function previewFile(idx) {
|
|
||||||
const f = fileList.value[idx];
|
|
||||||
const url = f?.url ? String(f.url) : '';
|
|
||||||
if (!url) return;
|
|
||||||
if (!isPdfUrl(url)) {
|
|
||||||
const urls = fileList.value.map((i) => i.url);
|
|
||||||
uni.previewImage({ urls, current: url });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uniLoading('打开中...');
|
|
||||||
uni.downloadFile({
|
|
||||||
url,
|
|
||||||
success: (res) => {
|
|
||||||
hideLoading();
|
|
||||||
const filePath = res?.tempFilePath;
|
|
||||||
if (!filePath) return toast('打开失败');
|
|
||||||
uni.openDocument({
|
|
||||||
filePath,
|
|
||||||
showMenu: true,
|
|
||||||
fail: () => toast('打开失败'),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
hideLoading();
|
|
||||||
toast('打开失败');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -412,82 +327,6 @@ function previewFile(idx) {
|
|||||||
padding: 8rpx 0;
|
padding: 8rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-wrap {
|
|
||||||
background: #fff;
|
|
||||||
padding: 24rpx 30rpx;
|
|
||||||
border-bottom: 2rpx solid #eee;
|
|
||||||
}
|
|
||||||
.upload-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
margin-bottom: 18rpx;
|
|
||||||
}
|
|
||||||
.upload-label {
|
|
||||||
font-size: 28rpx;
|
|
||||||
line-height: 42rpx;
|
|
||||||
color: #111827;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.upload-desc {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #9ca3af;
|
|
||||||
margin-left: 8rpx;
|
|
||||||
}
|
|
||||||
.upload-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 18rpx;
|
|
||||||
}
|
|
||||||
.upload-item {
|
|
||||||
width: 180rpx;
|
|
||||||
height: 140rpx;
|
|
||||||
position: relative;
|
|
||||||
border: 2rpx solid #e5e7eb;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #f9fafb;
|
|
||||||
}
|
|
||||||
.upload-thumb {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.upload-pdf {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 34rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #0877F1;
|
|
||||||
background: #eef6ff;
|
|
||||||
}
|
|
||||||
.upload-remove {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
text-align: center;
|
|
||||||
background: rgba(0, 0, 0, 0.55);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 28rpx;
|
|
||||||
}
|
|
||||||
.upload-add {
|
|
||||||
width: 180rpx;
|
|
||||||
height: 140rpx;
|
|
||||||
border: 2rpx dashed #d1d5db;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
.plus {
|
|
||||||
font-size: 52rpx;
|
|
||||||
line-height: 52rpx;
|
|
||||||
}
|
|
||||||
.footer {
|
.footer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@ -12,10 +12,10 @@
|
|||||||
<view class="value">{{ typeLabel }}</view>
|
<view class="value">{{ typeLabel }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="row">
|
<view class="row">
|
||||||
<view class="label">就诊日期</view>
|
<view class="label">{{ visitDateLabel }}</view>
|
||||||
<view class="value">{{ visitDate || '--' }}</view>
|
<view class="value">{{ visitDate || '--' }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="row">
|
<view v-if="showDiagnosisRow" class="row">
|
||||||
<view class="label">诊断</view>
|
<view class="label">诊断</view>
|
||||||
<view class="value">{{ diagnosisText }}</view>
|
<view class="value">{{ diagnosisText }}</view>
|
||||||
</view>
|
</view>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
<view class="p">{{ s.value }}</view>
|
<view class="p">{{ s.value }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="section">
|
<view v-if="showFilesSection" class="section">
|
||||||
<view class="h2">文件上传</view>
|
<view class="h2">文件上传</view>
|
||||||
<view v-if="files.length" class="files">
|
<view v-if="files.length" class="files">
|
||||||
<view v-for="(f, idx) in files" :key="idx" class="file" @click="preview(idx)">
|
<view v-for="(f, idx) in files" :key="idx" class="file" @click="preview(idx)">
|
||||||
@ -55,24 +55,70 @@ import dayjs from 'dayjs';
|
|||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { loading, hideLoading, toast } from '@/utils/widget';
|
import { loading, hideLoading, toast } from '@/utils/widget';
|
||||||
import { getVisitRecordTemplate } from './components/archive-detail/templates';
|
import { getVisitRecordTemplate } from './components/archive-detail/templates';
|
||||||
|
import { normalizeVisitRecordFormData } from './utils/visit-record';
|
||||||
|
import { normalizeTemplate, unwrapTemplateResponse } from './utils/template';
|
||||||
|
import { normalizeFileUrl } from '@/utils/file';
|
||||||
|
|
||||||
const archiveId = ref('');
|
const archiveId = ref('');
|
||||||
const id = ref('');
|
const id = ref('');
|
||||||
const medicalType = ref('');
|
const medicalType = ref('');
|
||||||
|
const rawType = ref('');
|
||||||
const record = ref({});
|
const record = ref({});
|
||||||
|
const temp = ref(null);
|
||||||
|
|
||||||
const files = computed(() => {
|
const files = computed(() => {
|
||||||
const arr = record.value?.files;
|
const arr = record.value?.files;
|
||||||
return Array.isArray(arr) ? arr.filter((i) => i && i.url) : [];
|
return Array.isArray(arr)
|
||||||
|
? arr
|
||||||
|
.filter((i) => i && i.url)
|
||||||
|
.map((i) => ({ ...i, url: normalizeFileUrl(i.url) }))
|
||||||
|
: [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const templateType = computed(() => record.value?.templateType || record.value?.medicalType || '');
|
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 === '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 === '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 === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
const typeLabel = computed(() => record.value?.tempName || getVisitRecordTemplate(templateType.value || medicalType.value)?.templateName || '病历');
|
const templateType = computed(() => normalizeMedicalType(rawType.value || medicalType.value || record.value?.templateType || record.value?.medicalType || ''));
|
||||||
|
|
||||||
|
const typeLabel = computed(() => record.value?.tempName || temp.value?.name || getVisitRecordTemplate(templateType.value || medicalType.value)?.templateName || '病历');
|
||||||
|
|
||||||
|
function getDefaultTimeTitle(t) {
|
||||||
|
if (t === 'outpatient') return 'visitTime';
|
||||||
|
if (t === 'inhospital') return 'inhosDate';
|
||||||
|
if (t === 'preConsultation') return 'consultDate';
|
||||||
|
if (t === 'physicalExaminationTemplate') return 'inspectDate';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultTimeName(t) {
|
||||||
|
if (t === 'outpatient') return '就诊日期';
|
||||||
|
if (t === 'inhospital') return '入院日期';
|
||||||
|
if (t === 'preConsultation') return '问诊日期';
|
||||||
|
if (t === 'physicalExaminationTemplate') return '体检日期';
|
||||||
|
return '日期';
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeText(v) {
|
function normalizeText(v) {
|
||||||
if (Array.isArray(v)) return v.filter((i) => i !== null && i !== undefined && String(i).trim()).join(',');
|
if (Array.isArray(v)) return v.filter((i) => i !== null && i !== undefined && String(i).trim()).join(',');
|
||||||
if (v === 0) return '0';
|
if (v === 0) return '0';
|
||||||
|
if (v && typeof v === 'object') {
|
||||||
|
const o = v;
|
||||||
|
const candidate = o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.code ?? '';
|
||||||
|
return candidate ? String(candidate) : '';
|
||||||
|
}
|
||||||
return v ? String(v) : '';
|
return v ? String(v) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,68 +147,139 @@ function getCorpId() {
|
|||||||
return team?.corpId ? String(team.corpId) : '';
|
return team?.corpId ? String(team.corpId) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseAnyTimeMs(v) {
|
||||||
|
if (v === null || v === undefined) return 0;
|
||||||
|
if (typeof v === 'number') {
|
||||||
|
// 10位秒级时间戳
|
||||||
|
if (v > 1e9 && v < 1e12) return v * 1000;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
const s = String(v).trim();
|
||||||
|
if (!s) return 0;
|
||||||
|
if (/^\d{10,13}$/.test(s)) return Number(s.length === 10 ? `${s}000` : s);
|
||||||
|
const d = dayjs(s);
|
||||||
|
return d.isValid() ? d.valueOf() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAnyDate(v, fmt = 'YYYY-MM-DD') {
|
||||||
|
const ms = parseAnyTimeMs(v);
|
||||||
|
if (!ms) return '';
|
||||||
|
const d = dayjs(ms);
|
||||||
|
return d.isValid() ? d.format(fmt) : '';
|
||||||
|
}
|
||||||
|
|
||||||
const visitDate = computed(() => {
|
const visitDate = computed(() => {
|
||||||
const t = templateType.value;
|
const t = templateType.value;
|
||||||
if (t === 'outpatient') return record.value?.visitTime || '';
|
const timeTitle = temp.value?.service?.timeTitle || getDefaultTimeTitle(t);
|
||||||
if (t === 'inhospital') return record.value?.inhosDate || '';
|
const raw = timeTitle ? record.value?.[timeTitle] : (record.value?.dateStr ?? record.value?.sortTime ?? '');
|
||||||
if (t === 'preConsultation') return record.value?.consultDate || '';
|
return formatAnyDate(raw) || normalizeText(raw) || '';
|
||||||
if (t === 'physicalExaminationTemplate') return record.value?.inspectDate || '';
|
});
|
||||||
return record.value?.dateStr || '';
|
|
||||||
|
const visitDateLabel = computed(() => {
|
||||||
|
const t = templateType.value;
|
||||||
|
return String(temp.value?.service?.timeName || getDefaultTimeName(t) || '日期');
|
||||||
|
});
|
||||||
|
|
||||||
|
const showDiagnosisRow = computed(() => {
|
||||||
|
const list = Array.isArray(temp.value?.templateList)
|
||||||
|
? temp.value.templateList
|
||||||
|
: Array.isArray(getVisitRecordTemplate(templateType.value)?.templateList)
|
||||||
|
? getVisitRecordTemplate(templateType.value).templateList
|
||||||
|
: [];
|
||||||
|
return list.some((i) => i && (i.title === 'diagnosis' || i.title === 'diagnosisName'));
|
||||||
});
|
});
|
||||||
|
|
||||||
const diagnosisText = computed(() => {
|
const diagnosisText = computed(() => {
|
||||||
const t = templateType.value;
|
const t = templateType.value;
|
||||||
if (t === 'preConsultation') return normalizeText(record.value?.chiefComplaint) || normalizeText(record.value?.summary) || '--';
|
if (!showDiagnosisRow.value) return '--';
|
||||||
if (t === 'physicalExaminationTemplate') return formatPositiveFind(record.value?.positiveFind) || normalizeText(record.value?.summary) || '--';
|
|
||||||
if (t === 'outpatient' || t === 'inhospital') return normalizeText(record.value?.diagnosisName || record.value?.diagnosis) || normalizeText(record.value?.summary) || '--';
|
if (t === 'outpatient' || t === 'inhospital') return normalizeText(record.value?.diagnosisName || record.value?.diagnosis) || normalizeText(record.value?.summary) || '--';
|
||||||
return normalizeText(record.value?.diagnosisName || record.value?.diagnosis || record.value?.summary) || '--';
|
return normalizeText(record.value?.diagnosisName || record.value?.diagnosis || record.value?.summary) || '--';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showFilesSection = computed(() => {
|
||||||
|
if (files.value.length) return true;
|
||||||
|
const list = Array.isArray(temp.value?.templateList)
|
||||||
|
? temp.value.templateList
|
||||||
|
: Array.isArray(getVisitRecordTemplate(templateType.value)?.templateList)
|
||||||
|
? getVisitRecordTemplate(templateType.value).templateList
|
||||||
|
: [];
|
||||||
|
return list.some((i) => i && (i.type === 'files' || i.title === 'files'));
|
||||||
|
});
|
||||||
|
|
||||||
const sections = computed(() => {
|
const sections = computed(() => {
|
||||||
const t = templateType.value;
|
const t = templateType.value;
|
||||||
const push = (title, value) => {
|
const hiddenKeys = new Set(t === 'outpatient'
|
||||||
const v = value === 0 ? '0' : value ? String(value) : '';
|
? ['corp', 'deptName', 'corpName', 'doctor']
|
||||||
if (!v.trim()) return;
|
: t === 'inhospital'
|
||||||
return { title, value: v };
|
? ['corp', 'corpName']
|
||||||
};
|
: []);
|
||||||
|
|
||||||
const list = [];
|
const list = [];
|
||||||
if (t === 'outpatient') {
|
const pushedNames = new Set();
|
||||||
const corp = push('就诊机构', record.value?.corpName);
|
|
||||||
const dept = push('科室', record.value?.deptName);
|
const pushRow = (name, value) => {
|
||||||
const doctor = push('医生', record.value?.doctor);
|
const v = normalizeText(value);
|
||||||
const treatment = push('治疗方案', record.value?.treatmentPlan);
|
if (!v.trim()) return;
|
||||||
const dispose = push('处置计划', record.value?.disposePlan);
|
if (pushedNames.has(name)) return;
|
||||||
const summary = push('备注/摘要', record.value?.summary);
|
pushedNames.add(name);
|
||||||
[corp, dept, doctor, treatment, dispose, summary].forEach((i) => i && list.push(i));
|
list.push({ title: name, value: v });
|
||||||
return list;
|
};
|
||||||
}
|
|
||||||
if (t === 'inhospital') {
|
const resolveOptionLabel = (item, candidate) => {
|
||||||
const corp = push('住院机构', record.value?.corpName);
|
const range = Array.isArray(item?.range) ? item.range : [];
|
||||||
const summary = push('摘要', record.value?.summary);
|
if (!range.length) return normalizeText(candidate);
|
||||||
[corp, summary].forEach((i) => i && list.push(i));
|
const isObjectRange = range[0] && typeof range[0] === 'object';
|
||||||
return list;
|
const toLabel = (v) => {
|
||||||
}
|
const s = normalizeText(v);
|
||||||
if (t === 'preConsultation') {
|
if (!s) return '';
|
||||||
const illness = push('现病史', record.value?.presentIllness);
|
if (!isObjectRange) return s;
|
||||||
const past = push('既往史', record.value?.pastHistory);
|
const found = range.find((opt) => opt && typeof opt === 'object' && String(opt.value) === String(s));
|
||||||
const allergy = push('过敏史', record.value?.allergyHistory);
|
return found ? String(found.label ?? found.value ?? s) : s;
|
||||||
const summary = push('摘要', record.value?.summary);
|
};
|
||||||
[illness, past, allergy, summary].forEach((i) => i && list.push(i));
|
if (Array.isArray(candidate)) return candidate.map(toLabel).filter((i) => String(i).trim()).join(',');
|
||||||
return list;
|
return toLabel(candidate);
|
||||||
}
|
};
|
||||||
if (t === 'physicalExaminationTemplate') {
|
|
||||||
const corp = push('体检机构', record.value?.corpName);
|
const templateList = Array.isArray(temp.value?.templateList)
|
||||||
const pkg = push('体检套餐', record.value?.inspectPakageName);
|
? temp.value.templateList
|
||||||
const positive = push('阳性发现及处理意见', formatPositiveFind(record.value?.positiveFind, { withOpinion: true }));
|
: Array.isArray(getVisitRecordTemplate(t)?.templateList)
|
||||||
const summary = push('摘要', record.value?.summary);
|
? getVisitRecordTemplate(t).templateList
|
||||||
[corp, pkg, positive, summary].forEach((i) => i && list.push(i));
|
: [];
|
||||||
return list;
|
const timeTitle = temp.value?.service?.timeTitle || getDefaultTimeTitle(t);
|
||||||
}
|
|
||||||
const summary = push('摘要', record.value?.summary);
|
templateList.forEach((item) => {
|
||||||
if (summary) list.push(summary);
|
const key = item?.title ? String(item.title) : '';
|
||||||
|
if (!key) return;
|
||||||
|
if (key === 'files') return;
|
||||||
|
if (key === 'diagnosis' || key === 'diagnosisName') return;
|
||||||
|
if (timeTitle && key === timeTitle) return;
|
||||||
|
if (hiddenKeys.has(key)) return;
|
||||||
|
|
||||||
|
const raw = record.value?.[key];
|
||||||
|
const display = key === 'positiveFind'
|
||||||
|
? formatPositiveFind(raw, { withOpinion: true })
|
||||||
|
: item?.type === 'date'
|
||||||
|
? (formatAnyDate(raw) || normalizeText(raw))
|
||||||
|
: resolveOptionLabel(item, raw);
|
||||||
|
pushRow(String(item?.name || key), display);
|
||||||
|
});
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function loadTemplate(t) {
|
||||||
|
const corpId = getCorpId();
|
||||||
|
if (!corpId || !t) return null;
|
||||||
|
try {
|
||||||
|
const res = await api('getCurrentTemplate', { corpId, templateType: t });
|
||||||
|
if (!res?.success) return null;
|
||||||
|
const raw = unwrapTemplateResponse(res);
|
||||||
|
return normalizeTemplate(raw);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const topText = computed(() => {
|
const topText = computed(() => {
|
||||||
const time = record.value?.createTime ? dayjs(record.value.createTime).format('YYYY-MM-DD HH:mm') : '';
|
const time = record.value?.createTime ? dayjs(record.value.createTime).format('YYYY-MM-DD HH:mm') : '';
|
||||||
const rawName = record.value?.creatorName ? String(record.value.creatorName) : '';
|
const rawName = record.value?.creatorName ? String(record.value.creatorName) : '';
|
||||||
@ -206,13 +323,11 @@ onLoad(async (opt) => {
|
|||||||
setTimeout(() => uni.navigateBack(), 300);
|
setTimeout(() => uni.navigateBack(), 300);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const raw = String(r?.templateType || r?.medicalType || medicalType.value || '');
|
||||||
// 兼容模板字段:wxapp 使用 diagnosis,但接口通常返回 diagnosisName
|
rawType.value = raw;
|
||||||
if ((r.medicalType === 'outpatient' || r.medicalType === 'inhospital') && !r.diagnosis && r.diagnosisName) {
|
const ui = normalizeMedicalType(raw);
|
||||||
r.diagnosis = r.diagnosisName;
|
record.value = normalizeVisitRecordFormData(ui, r);
|
||||||
}
|
temp.value = await loadTemplate(raw);
|
||||||
|
|
||||||
record.value = r;
|
|
||||||
uni.setNavigationBarTitle({ title: String(typeLabel.value || '病历详情') });
|
uni.setNavigationBarTitle({ title: String(typeLabel.value || '病历详情') });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
|
|||||||
@ -705,40 +705,63 @@ const toggleBatchMode = () => {
|
|||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
if (checkBatchMode()) return;
|
if (checkBatchMode()) return;
|
||||||
// 100上限:无法继续新增 -> 引导联系客服(预留入口)
|
const rawMax = doctorInfo.value?.maxCustomerArchive;
|
||||||
if (managedArchiveCountAllTeams.value >= 100) {
|
const hasMaxField = rawMax !== undefined && rawMax !== null && String(rawMax).trim() !== '';
|
||||||
uni.showModal({
|
const maxCustomerArchive = hasMaxField ? Number(rawMax) : NaN;
|
||||||
title: '提示',
|
|
||||||
content: '当前管理档案数已达 100 个,无法继续新增。如需提升档案管理数,请联系客服处理。',
|
|
||||||
cancelText: '知道了',
|
|
||||||
confirmText: '添加客服',
|
|
||||||
success: (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
openAddCustomerServiceEntry();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 未认证 + 达到10上限:提示去认证
|
// maxCustomerArchive:
|
||||||
if (!isVerified.value && managedArchiveCountAllTeams.value >= 10) {
|
// -1 = 无限;存在该字段则优先按该字段限制;不存在则沿用原有规则(未认证10/已认证100)
|
||||||
if (verifyStatus.value === 'verifying') {
|
if (hasMaxField && Number.isFinite(maxCustomerArchive)) {
|
||||||
toast('信息认证中,请耐心等待!');
|
if (maxCustomerArchive !== -1 && managedArchiveCountAllTeams.value >= maxCustomerArchive) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: `当前管理档案数已达上限 ${maxCustomerArchive} 个,无法继续新增。如需提升档案管理数,请联系客服处理。`,
|
||||||
|
cancelText: '知道了',
|
||||||
|
confirmText: '添加客服',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
openAddCustomerServiceEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uni.showModal({
|
} else {
|
||||||
title: '提示',
|
// 100上限:无法继续新增 -> 引导联系客服(预留入口)
|
||||||
content: '当前管理档案数已达上限 10 个,完成认证即可升级至 100 个。',
|
if (managedArchiveCountAllTeams.value >= 100) {
|
||||||
cancelText: '暂不认证',
|
uni.showModal({
|
||||||
confirmText: '去认证',
|
title: '提示',
|
||||||
success: (res) => {
|
content: '当前管理档案数已达 100 个,无法继续新增。如需提升档案管理数,请联系客服处理。',
|
||||||
if (res.confirm) {
|
cancelText: '知道了',
|
||||||
startVerifyFlow();
|
confirmText: '添加客服',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
openAddCustomerServiceEntry();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未认证 + 达到10上限:提示去认证
|
||||||
|
if (!isVerified.value && managedArchiveCountAllTeams.value >= 10) {
|
||||||
|
if (verifyStatus.value === 'verifying') {
|
||||||
|
toast('信息认证中,请耐心等待!');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
uni.showModal({
|
||||||
return;
|
title: '提示',
|
||||||
|
content: '当前管理档案数已达上限 10 个,完成认证即可升级至 100 个。',
|
||||||
|
cancelText: '暂不认证',
|
||||||
|
confirmText: '去认证',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
startVerifyFlow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未达上限:显示新增入口
|
// 未达上限:显示新增入口
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<view class="label">{{ item.label }}</view>
|
<view class="label">{{ item.label }}</view>
|
||||||
<uni-icons :type="selectedMap[item.label] ? 'checkmarkempty' : ''" size="22" color="#007aff" />
|
<uni-icons :type="selectedMap[item.label] ? 'checkmarkempty' : ''" size="22" color="#007aff" />
|
||||||
</view>
|
</view>
|
||||||
<view v-if="showList.length === 0" class="empty">暂无诊断数据</view>
|
<view v-if="showList.length === 0" class="empty">{{ keyword.trim() ? '暂无诊断数据' : '请输入关键词搜索' }}</view>
|
||||||
<view style="height: 240rpx;"></view>
|
<view style="height: 240rpx;"></view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
@ -31,6 +31,7 @@ const list = ref([]);
|
|||||||
const selections = ref([]);
|
const selections = ref([]);
|
||||||
const mult = ref(false);
|
const mult = ref(false);
|
||||||
const eventName = ref('change-diagnosis');
|
const eventName = ref('change-diagnosis');
|
||||||
|
let lastQueryId = 0;
|
||||||
|
|
||||||
const selectedMap = computed(() => selections.value.reduce((m, i) => ((m[i] = true), m), {}));
|
const selectedMap = computed(() => selections.value.reduce((m, i) => ((m[i] = true), m), {}));
|
||||||
|
|
||||||
@ -38,22 +39,8 @@ function normalizeText(v) {
|
|||||||
return v ? String(v) : '';
|
return v ? String(v) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullMatched = computed(() => {
|
|
||||||
const value = String(keyword.value || '').trim();
|
|
||||||
if (!value) return null;
|
|
||||||
return { label: value, value, key: `full_${value}` };
|
|
||||||
});
|
|
||||||
|
|
||||||
const showList = computed(() => {
|
const showList = computed(() => {
|
||||||
const base = Array.isArray(list.value) ? list.value : [];
|
return Array.isArray(list.value) ? list.value : [];
|
||||||
const arr = [];
|
|
||||||
if (fullMatched.value) arr.push(fullMatched.value);
|
|
||||||
base.forEach((i) => {
|
|
||||||
if (!i || !i.label) return;
|
|
||||||
if (fullMatched.value && i.label === fullMatched.value.label) return;
|
|
||||||
arr.push(i);
|
|
||||||
});
|
|
||||||
return arr;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let timer = null;
|
let timer = null;
|
||||||
@ -89,11 +76,25 @@ onLoad((opt) => {
|
|||||||
async function query() {
|
async function query() {
|
||||||
if (!ready.value) return;
|
if (!ready.value) return;
|
||||||
const value = String(keyword.value || '').trim();
|
const value = String(keyword.value || '').trim();
|
||||||
|
if (!value) {
|
||||||
|
list.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryId = ++lastQueryId;
|
||||||
|
|
||||||
uni.showLoading({ title: '加载中...' });
|
|
||||||
try {
|
try {
|
||||||
|
uni.showLoading({ title: '加载中...' });
|
||||||
const res = await api('getDisease', { diseaseName: value });
|
const res = await api('getDisease', { diseaseName: value });
|
||||||
const arr = Array.isArray(res?.data?.data) ? res.data.data : [];
|
if (queryId !== lastQueryId) return;
|
||||||
|
const arr =
|
||||||
|
Array.isArray(res?.data?.data) ? res.data.data
|
||||||
|
: Array.isArray(res?.data?.list) ? res.data.list
|
||||||
|
: Array.isArray(res?.data?.data?.data) ? res.data.data.data
|
||||||
|
: Array.isArray(res?.data) ? res.data
|
||||||
|
: Array.isArray(res?.list) ? res.list
|
||||||
|
: Array.isArray(res?.data?.data?.list) ? res.data.data.list
|
||||||
|
: [];
|
||||||
list.value = arr
|
list.value = arr
|
||||||
.map((i) => {
|
.map((i) => {
|
||||||
const label = normalizeText(i?.diseaseName);
|
const label = normalizeText(i?.diseaseName);
|
||||||
@ -103,9 +104,9 @@ async function query() {
|
|||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
list.value = [];
|
if (queryId === lastQueryId) list.value = [];
|
||||||
} finally {
|
} finally {
|
||||||
uni.hideLoading();
|
if (queryId === lastQueryId) uni.hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,39 @@
|
|||||||
const env = __VITE_ENV__;
|
const env = __VITE_ENV__;
|
||||||
|
|
||||||
|
function joinBaseAndPath(base, path) {
|
||||||
|
const b = String(base || '').replace(/\/+$/, '');
|
||||||
|
const p = String(path || '');
|
||||||
|
if (!p) return b;
|
||||||
|
if (/^https?:\/\//i.test(p)) return p;
|
||||||
|
return `${b}/${p.replace(/^\/+/, '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容历史数据:某些链接缺少域名后的 /
|
||||||
|
export function normalizeFileUrl(url) {
|
||||||
|
const u = String(url || '').trim();
|
||||||
|
if (!u) return '';
|
||||||
|
if (/^https?:\/\//i.test(u)) {
|
||||||
|
const afterProtocolIndex = u.indexOf('://') + 3;
|
||||||
|
const firstSlashIndex = u.indexOf('/', afterProtocolIndex);
|
||||||
|
if (firstSlashIndex > 0) {
|
||||||
|
const prefix = u.slice(0, afterProtocolIndex);
|
||||||
|
const host = u.slice(afterProtocolIndex, firstSlashIndex);
|
||||||
|
const path = u.slice(firstSlashIndex);
|
||||||
|
if (host.toLowerCase().endsWith('uploads') && !path.toLowerCase().startsWith('/uploads/')) {
|
||||||
|
const fixedHost = host.slice(0, -'uploads'.length);
|
||||||
|
const normalizedPath = `/uploads${path.startsWith('/') ? '' : '/'}${path.replace(/^\/+/, '')}`;
|
||||||
|
return `${prefix}${fixedHost}${normalizedPath}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u.replace(/^(https?:\/\/[^/]+)(uploads\/)/i, '$1/$2');
|
||||||
|
}
|
||||||
|
|
||||||
export async function uploadFile(tempFilePath) {
|
export async function uploadFile(tempFilePath) {
|
||||||
try {
|
try {
|
||||||
const res = await new Promise((resolve, reject) => {
|
const res = await new Promise((resolve, reject) => {
|
||||||
uni.uploadFile({
|
uni.uploadFile({
|
||||||
url: `${env.MP_API_BASE_URL}/upload`,
|
url: joinBaseAndPath(env.MP_API_BASE_URL, 'upload'),
|
||||||
filePath: tempFilePath,
|
filePath: tempFilePath,
|
||||||
name: 'file',
|
name: 'file',
|
||||||
success: (resp) => resolve(resp),
|
success: (resp) => resolve(resp),
|
||||||
@ -14,7 +43,7 @@ export async function uploadFile(tempFilePath) {
|
|||||||
|
|
||||||
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
|
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
|
||||||
if (data && data.success && data.filePath) {
|
if (data && data.success && data.filePath) {
|
||||||
return `${env.MP_API_BASE_URL}${data.filePath}`;
|
return normalizeFileUrl(joinBaseAndPath(env.MP_API_BASE_URL, data.filePath));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('upload file error:', e);
|
console.log('upload file error:', e);
|
||||||
@ -58,5 +87,3 @@ export async function chooseAndUploadImage(options = {}) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user