Merge commit '1018707e25082fb5b29b265ed55a9ea3499c44bf' into dev-wdb
This commit is contained in:
commit
5c040e3f55
@ -11,6 +11,11 @@
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.form-cell-required {
|
||||
margin-left: 4rpx;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.form-content__wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@ -58,4 +63,4 @@
|
||||
.form-row__content {
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view class="form-row" @click="handleClick">
|
||||
<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 class="form-row__content">
|
||||
<slot></slot>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<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 v-for="(f, idx) in files" :key="idx" class="item" @click="preview(idx)">
|
||||
<image class="thumb" :src="f.url" mode="aspectFill" />
|
||||
@ -15,7 +17,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { chooseAndUploadImage } from '@/utils/file';
|
||||
import { chooseAndUploadImage, normalizeFileUrl } from '@/utils/file';
|
||||
import { toast } from '@/utils/widget';
|
||||
|
||||
const emits = defineEmits(['change']);
|
||||
@ -38,13 +40,13 @@ const files = computed(() => {
|
||||
if (Array.isArray(v)) {
|
||||
return v
|
||||
.map((i) => {
|
||||
if (typeof i === 'string') return { url: i };
|
||||
if (i && typeof i === 'object' && i.url) return { url: String(i.url) };
|
||||
if (typeof i === 'string') return { url: normalizeFileUrl(i) };
|
||||
if (i && typeof i === 'object' && i.url) return { url: normalizeFileUrl(String(i.url)) };
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
if (typeof v === 'string' && v) return [{ url: v }];
|
||||
if (typeof v === 'string' && v) return [{ url: normalizeFileUrl(v) }];
|
||||
return [];
|
||||
});
|
||||
|
||||
@ -141,4 +143,3 @@ async function add() {
|
||||
line-height: 56rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<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
|
||||
v-for="opt in displayOptions"
|
||||
@ -165,4 +167,3 @@ function onOtherInput(e) {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<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">
|
||||
<input :disabled="disableChange" class="num" type="number" :value="year" @input="onInput($event, 'year')" />
|
||||
<text class="unit">年</text>
|
||||
@ -86,4 +88,3 @@ function onInput(e, key) {
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<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">
|
||||
<input
|
||||
:disabled="disableChange"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view class="textarea-row">
|
||||
<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 class="mt-10">
|
||||
<textarea
|
||||
@ -94,6 +94,8 @@ function change(e) {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../cell-style.css';
|
||||
|
||||
.textarea-row {
|
||||
padding: 24rpx 30rpx;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
@change="change"
|
||||
/>
|
||||
<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"
|
||||
:form="form"
|
||||
:disableChange="disableChange"
|
||||
|
||||
@ -71,6 +71,8 @@ import dayjs from 'dayjs';
|
||||
import api from '@/utils/api';
|
||||
import { loading, hideLoading, toast } from '@/utils/widget';
|
||||
import { normalizeTemplate } from '../../utils/template';
|
||||
import { normalizeVisitRecordFormData } from '../../utils/visit-record';
|
||||
import { normalizeFileUrl } from '@/utils/file';
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => ({}) },
|
||||
@ -128,16 +130,25 @@ function getCorpId() {
|
||||
}
|
||||
|
||||
const loadedCorpId = ref('');
|
||||
let loadVisitTemplatesPromise = null;
|
||||
let loadVisitTemplatesCorpId = '';
|
||||
async function loadVisitTemplates() {
|
||||
const corpId = getCorpId();
|
||||
if (!corpId) 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 group = groupRes?.data && Array.isArray(groupRes.data?.data) ? groupRes.data.data : Array.isArray(groupRes?.data) ? groupRes.data : [];
|
||||
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 typeList = enabled.map((i) => String(i.templateType || '')).filter(Boolean);
|
||||
if (!typeList.length) return;
|
||||
@ -155,9 +166,10 @@ async function loadVisitTemplates() {
|
||||
const next = ordered
|
||||
.map((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 {
|
||||
templateType: String(temp?.templateType || ''),
|
||||
templateType: rawType,
|
||||
name,
|
||||
service: temp?.service || {},
|
||||
templateList: Array.isArray(temp?.templateList) ? temp.templateList : [],
|
||||
@ -165,10 +177,22 @@ async function loadVisitTemplates() {
|
||||
})
|
||||
.filter((i) => i && i.templateType);
|
||||
|
||||
if (next.length) templates.value = next;
|
||||
} catch (e) {
|
||||
console.error('loadVisitTemplates error:', e);
|
||||
}
|
||||
if (next.length) {
|
||||
templates.value = next;
|
||||
loadedCorpId.value = corpId;
|
||||
}
|
||||
})()
|
||||
.catch((e) => {
|
||||
console.error('loadVisitTemplates error:', e);
|
||||
})
|
||||
.finally(() => {
|
||||
if (loadVisitTemplatesCorpId === corpId) {
|
||||
loadVisitTemplatesPromise = null;
|
||||
loadVisitTemplatesCorpId = '';
|
||||
}
|
||||
});
|
||||
|
||||
return loadVisitTemplatesPromise;
|
||||
}
|
||||
|
||||
const userNameMap = ref({});
|
||||
@ -201,12 +225,14 @@ async function loadTeamMembers() {
|
||||
}
|
||||
|
||||
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 (templateType === 'outpatient') return 'visitTime';
|
||||
if (templateType === 'inhospital') return 'inhosDate';
|
||||
if (templateType === 'preConsultation') return 'consultDate';
|
||||
if (templateType === 'physicalExaminationTemplate') return 'inspectDate';
|
||||
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 '';
|
||||
}
|
||||
|
||||
@ -242,15 +268,48 @@ function getTemplateName(type) {
|
||||
}
|
||||
|
||||
function toDateStr(sortTime) {
|
||||
if (!sortTime) return '';
|
||||
const d = dayjs(sortTime);
|
||||
return d.isValid() ? d.format('YYYY-MM-DD') : '';
|
||||
return formatAnyDate(sortTime, '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) {
|
||||
if (!ts) return '';
|
||||
const d = dayjs(ts);
|
||||
return d.isValid() ? d.format('YYYY-MM-DD HH:mm') : '';
|
||||
return formatAnyDate(ts, 'YYYY-MM-DD HH:mm');
|
||||
}
|
||||
|
||||
async function refreshList() {
|
||||
@ -285,21 +344,31 @@ async function refreshList() {
|
||||
? res.list
|
||||
: Array.isArray(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)
|
||||
? 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]) : '';
|
||||
const rawType = String(r?.medicalType || r?.templateType || '') || '';
|
||||
const uiType = normalizeMedicalType(rawType);
|
||||
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 {
|
||||
...r,
|
||||
templateType: t,
|
||||
...normalized,
|
||||
medicalType: rawType,
|
||||
templateType: uiType,
|
||||
rawTemplateType: rawType,
|
||||
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),
|
||||
tempName: r?.tempName || getTemplateName(t) || '病历',
|
||||
tempName: r?.tempName || getTemplateName(rawType) || '病历',
|
||||
};
|
||||
});
|
||||
|
||||
@ -324,9 +393,21 @@ const tagClass = {
|
||||
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) {
|
||||
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 === 'physicalExaminationTemplate') return formatPositiveFind(r.positiveFind) || 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) {
|
||||
const t = r?.templateType || r?.medicalType;
|
||||
const t = resolveRecordType(r);
|
||||
if (t === 'outpatient') {
|
||||
return [{ label: '门诊诊断:', value: firstLine(r.diagnosisName || r.diagnosis) }];
|
||||
}
|
||||
@ -350,12 +431,12 @@ function getDisplayLines(r) {
|
||||
return lines;
|
||||
}
|
||||
if (t === 'physicalExaminationTemplate') {
|
||||
return [{ label: '体检小结:', value: firstLine(r.summary) }];
|
||||
return [{ label: '体检小结:', value: firstLine(r.summary || r.inspectSummary) }];
|
||||
}
|
||||
if (t === 'preConsultation') {
|
||||
const lines = [
|
||||
{ label: '主诉:', value: firstLine(r.chiefComplaint) },
|
||||
{ label: '现病史:', value: firstLine(r.presentIllness) },
|
||||
{ label: '现病史:', value: firstLine(r.presentIllness || r.presentIllnessHistory) },
|
||||
];
|
||||
const past = normalizeText(r.pastHistory);
|
||||
if (past) lines.push({ label: '既往史:', value: past });
|
||||
@ -386,7 +467,11 @@ function pickTimeRange(val) {
|
||||
|
||||
function getFiles(r) {
|
||||
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) {
|
||||
@ -455,10 +540,6 @@ function edit(record) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 提前加载模板,避免首次点“+”时 picker 无法立即弹出
|
||||
loadVisitTemplates();
|
||||
// archiveId 可能后置赋值:这里保留一次兜底刷新,主逻辑交给 watch
|
||||
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: '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: '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: '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: '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: '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: '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: '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: '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: '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: '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: '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',
|
||||
files: 'files',
|
||||
corpProject: 'select',
|
||||
diagnosis: 'textarea',
|
||||
diagnosis: 'diagnosis',
|
||||
BMI: 'input',
|
||||
bloodPressure: 'textarea',
|
||||
selfMultipleDiseases: 'textarea',
|
||||
@ -92,4 +92,3 @@ export function normalizeTemplate(temp) {
|
||||
.map(normalizeTemplateItem);
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
@ -7,24 +7,6 @@
|
||||
<FormTemplate v-if="temp" ref="formRef" :items="showItems" :form="forms" @change="onChange" />
|
||||
</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>
|
||||
</scroll-view>
|
||||
|
||||
@ -47,7 +29,6 @@ import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import FormTemplate from '@/components/form-template/index.vue';
|
||||
import api from '@/utils/api';
|
||||
import { uploadFile } from '@/utils/file';
|
||||
import useAccountStore from '@/store/account';
|
||||
import { toast, confirm, loading as uniLoading, hideLoading } from '@/utils/widget';
|
||||
import { normalizeVisitRecordFormData } from './utils/visit-record';
|
||||
@ -92,11 +73,34 @@ const titleText = computed(() => {
|
||||
const detail = ref({});
|
||||
const form = reactive({});
|
||||
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 list = temp.value?.templateList || [];
|
||||
// referenceField 兼容(与 mobile 一致)
|
||||
return list.filter((i) => {
|
||||
if (i?.type === 'files') return false;
|
||||
if (shouldHideField(i)) return false;
|
||||
if (i && typeof i.referenceField === 'string') {
|
||||
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 fileList = computed(() => {
|
||||
const arr = forms.value?.files;
|
||||
return Array.isArray(arr) ? arr.filter((i) => i && i.url) : [];
|
||||
});
|
||||
|
||||
function ensureFilesField() {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
@ -412,82 +327,6 @@ function previewFile(idx) {
|
||||
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 {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
|
||||
@ -12,10 +12,10 @@
|
||||
<view class="value">{{ typeLabel }}</view>
|
||||
</view>
|
||||
<view class="row">
|
||||
<view class="label">就诊日期</view>
|
||||
<view class="label">{{ visitDateLabel }}</view>
|
||||
<view class="value">{{ visitDate || '--' }}</view>
|
||||
</view>
|
||||
<view class="row">
|
||||
<view v-if="showDiagnosisRow" class="row">
|
||||
<view class="label">诊断</view>
|
||||
<view class="value">{{ diagnosisText }}</view>
|
||||
</view>
|
||||
@ -30,7 +30,7 @@
|
||||
<view class="p">{{ s.value }}</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view v-if="showFilesSection" class="section">
|
||||
<view class="h2">文件上传</view>
|
||||
<view v-if="files.length" class="files">
|
||||
<view v-for="(f, idx) in files" :key="idx" class="file" @click="preview(idx)">
|
||||
@ -55,24 +55,70 @@ import dayjs from 'dayjs';
|
||||
import api from '@/utils/api';
|
||||
import { loading, hideLoading, toast } from '@/utils/widget';
|
||||
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 id = ref('');
|
||||
const medicalType = ref('');
|
||||
const rawType = ref('');
|
||||
const record = ref({});
|
||||
const temp = ref(null);
|
||||
|
||||
const files = computed(() => {
|
||||
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) {
|
||||
if (Array.isArray(v)) return v.filter((i) => i !== null && i !== undefined && String(i).trim()).join(',');
|
||||
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) : '';
|
||||
}
|
||||
|
||||
@ -101,68 +147,139 @@ function getCorpId() {
|
||||
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 t = templateType.value;
|
||||
if (t === 'outpatient') return record.value?.visitTime || '';
|
||||
if (t === 'inhospital') return record.value?.inhosDate || '';
|
||||
if (t === 'preConsultation') return record.value?.consultDate || '';
|
||||
if (t === 'physicalExaminationTemplate') return record.value?.inspectDate || '';
|
||||
return record.value?.dateStr || '';
|
||||
const timeTitle = temp.value?.service?.timeTitle || getDefaultTimeTitle(t);
|
||||
const raw = timeTitle ? record.value?.[timeTitle] : (record.value?.dateStr ?? record.value?.sortTime ?? '');
|
||||
return formatAnyDate(raw) || normalizeText(raw) || '';
|
||||
});
|
||||
|
||||
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 t = templateType.value;
|
||||
if (t === 'preConsultation') return normalizeText(record.value?.chiefComplaint) || normalizeText(record.value?.summary) || '--';
|
||||
if (t === 'physicalExaminationTemplate') return formatPositiveFind(record.value?.positiveFind) || normalizeText(record.value?.summary) || '--';
|
||||
if (!showDiagnosisRow.value) return '--';
|
||||
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) || '--';
|
||||
});
|
||||
|
||||
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 t = templateType.value;
|
||||
const push = (title, value) => {
|
||||
const v = value === 0 ? '0' : value ? String(value) : '';
|
||||
if (!v.trim()) return;
|
||||
return { title, value: v };
|
||||
};
|
||||
const hiddenKeys = new Set(t === 'outpatient'
|
||||
? ['corp', 'deptName', 'corpName', 'doctor']
|
||||
: t === 'inhospital'
|
||||
? ['corp', 'corpName']
|
||||
: []);
|
||||
|
||||
const list = [];
|
||||
if (t === 'outpatient') {
|
||||
const corp = push('就诊机构', record.value?.corpName);
|
||||
const dept = push('科室', record.value?.deptName);
|
||||
const doctor = push('医生', record.value?.doctor);
|
||||
const treatment = push('治疗方案', record.value?.treatmentPlan);
|
||||
const dispose = push('处置计划', record.value?.disposePlan);
|
||||
const summary = push('备注/摘要', record.value?.summary);
|
||||
[corp, dept, doctor, treatment, dispose, summary].forEach((i) => i && list.push(i));
|
||||
return list;
|
||||
}
|
||||
if (t === 'inhospital') {
|
||||
const corp = push('住院机构', record.value?.corpName);
|
||||
const summary = push('摘要', record.value?.summary);
|
||||
[corp, summary].forEach((i) => i && list.push(i));
|
||||
return list;
|
||||
}
|
||||
if (t === 'preConsultation') {
|
||||
const illness = push('现病史', record.value?.presentIllness);
|
||||
const past = push('既往史', record.value?.pastHistory);
|
||||
const allergy = push('过敏史', record.value?.allergyHistory);
|
||||
const summary = push('摘要', record.value?.summary);
|
||||
[illness, past, allergy, summary].forEach((i) => i && list.push(i));
|
||||
return list;
|
||||
}
|
||||
if (t === 'physicalExaminationTemplate') {
|
||||
const corp = push('体检机构', record.value?.corpName);
|
||||
const pkg = push('体检套餐', record.value?.inspectPakageName);
|
||||
const positive = push('阳性发现及处理意见', formatPositiveFind(record.value?.positiveFind, { withOpinion: true }));
|
||||
const summary = push('摘要', record.value?.summary);
|
||||
[corp, pkg, positive, summary].forEach((i) => i && list.push(i));
|
||||
return list;
|
||||
}
|
||||
const summary = push('摘要', record.value?.summary);
|
||||
if (summary) list.push(summary);
|
||||
const pushedNames = new Set();
|
||||
|
||||
const pushRow = (name, value) => {
|
||||
const v = normalizeText(value);
|
||||
if (!v.trim()) return;
|
||||
if (pushedNames.has(name)) return;
|
||||
pushedNames.add(name);
|
||||
list.push({ title: name, value: v });
|
||||
};
|
||||
|
||||
const resolveOptionLabel = (item, candidate) => {
|
||||
const range = Array.isArray(item?.range) ? item.range : [];
|
||||
if (!range.length) return normalizeText(candidate);
|
||||
const isObjectRange = range[0] && typeof range[0] === 'object';
|
||||
const toLabel = (v) => {
|
||||
const s = normalizeText(v);
|
||||
if (!s) return '';
|
||||
if (!isObjectRange) return s;
|
||||
const found = range.find((opt) => opt && typeof opt === 'object' && String(opt.value) === String(s));
|
||||
return found ? String(found.label ?? found.value ?? s) : s;
|
||||
};
|
||||
if (Array.isArray(candidate)) return candidate.map(toLabel).filter((i) => String(i).trim()).join(',');
|
||||
return toLabel(candidate);
|
||||
};
|
||||
|
||||
const templateList = Array.isArray(temp.value?.templateList)
|
||||
? temp.value.templateList
|
||||
: Array.isArray(getVisitRecordTemplate(t)?.templateList)
|
||||
? getVisitRecordTemplate(t).templateList
|
||||
: [];
|
||||
const timeTitle = temp.value?.service?.timeTitle || getDefaultTimeTitle(t);
|
||||
|
||||
templateList.forEach((item) => {
|
||||
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;
|
||||
});
|
||||
|
||||
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 time = record.value?.createTime ? dayjs(record.value.createTime).format('YYYY-MM-DD HH:mm') : '';
|
||||
const rawName = record.value?.creatorName ? String(record.value.creatorName) : '';
|
||||
@ -206,13 +323,11 @@ onLoad(async (opt) => {
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
return;
|
||||
}
|
||||
|
||||
// 兼容模板字段:wxapp 使用 diagnosis,但接口通常返回 diagnosisName
|
||||
if ((r.medicalType === 'outpatient' || r.medicalType === 'inhospital') && !r.diagnosis && r.diagnosisName) {
|
||||
r.diagnosis = r.diagnosisName;
|
||||
}
|
||||
|
||||
record.value = r;
|
||||
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();
|
||||
|
||||
@ -705,40 +705,63 @@ const toggleBatchMode = () => {
|
||||
|
||||
const handleCreate = () => {
|
||||
if (checkBatchMode()) return;
|
||||
// 100上限:无法继续新增 -> 引导联系客服(预留入口)
|
||||
if (managedArchiveCountAllTeams.value >= 100) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '当前管理档案数已达 100 个,无法继续新增。如需提升档案管理数,请联系客服处理。',
|
||||
cancelText: '知道了',
|
||||
confirmText: '添加客服',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
openAddCustomerServiceEntry();
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const rawMax = doctorInfo.value?.maxCustomerArchive;
|
||||
const hasMaxField = rawMax !== undefined && rawMax !== null && String(rawMax).trim() !== '';
|
||||
const maxCustomerArchive = hasMaxField ? Number(rawMax) : NaN;
|
||||
|
||||
// 未认证 + 达到10上限:提示去认证
|
||||
if (!isVerified.value && managedArchiveCountAllTeams.value >= 10) {
|
||||
if (verifyStatus.value === 'verifying') {
|
||||
toast('信息认证中,请耐心等待!');
|
||||
// maxCustomerArchive:
|
||||
// -1 = 无限;存在该字段则优先按该字段限制;不存在则沿用原有规则(未认证10/已认证100)
|
||||
if (hasMaxField && Number.isFinite(maxCustomerArchive)) {
|
||||
if (maxCustomerArchive !== -1 && managedArchiveCountAllTeams.value >= maxCustomerArchive) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `当前管理档案数已达上限 ${maxCustomerArchive} 个,无法继续新增。如需提升档案管理数,请联系客服处理。`,
|
||||
cancelText: '知道了',
|
||||
confirmText: '添加客服',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
openAddCustomerServiceEntry();
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '当前管理档案数已达上限 10 个,完成认证即可升级至 100 个。',
|
||||
cancelText: '暂不认证',
|
||||
confirmText: '去认证',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
startVerifyFlow();
|
||||
} else {
|
||||
// 100上限:无法继续新增 -> 引导联系客服(预留入口)
|
||||
if (managedArchiveCountAllTeams.value >= 100) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '当前管理档案数已达 100 个,无法继续新增。如需提升档案管理数,请联系客服处理。',
|
||||
cancelText: '知道了',
|
||||
confirmText: '添加客服',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
openAddCustomerServiceEntry();
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 未认证 + 达到10上限:提示去认证
|
||||
if (!isVerified.value && managedArchiveCountAllTeams.value >= 10) {
|
||||
if (verifyStatus.value === 'verifying') {
|
||||
toast('信息认证中,请耐心等待!');
|
||||
return;
|
||||
}
|
||||
});
|
||||
return;
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '当前管理档案数已达上限 10 个,完成认证即可升级至 100 个。',
|
||||
cancelText: '暂不认证',
|
||||
confirmText: '去认证',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
startVerifyFlow();
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 未达上限:显示新增入口
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<view class="label">{{ item.label }}</view>
|
||||
<uni-icons :type="selectedMap[item.label] ? 'checkmarkempty' : ''" size="22" color="#007aff" />
|
||||
</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>
|
||||
</scroll-view>
|
||||
|
||||
@ -31,6 +31,7 @@ const list = ref([]);
|
||||
const selections = ref([]);
|
||||
const mult = ref(false);
|
||||
const eventName = ref('change-diagnosis');
|
||||
let lastQueryId = 0;
|
||||
|
||||
const selectedMap = computed(() => selections.value.reduce((m, i) => ((m[i] = true), m), {}));
|
||||
|
||||
@ -38,22 +39,8 @@ function normalizeText(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 base = 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;
|
||||
return Array.isArray(list.value) ? list.value : [];
|
||||
});
|
||||
|
||||
let timer = null;
|
||||
@ -89,11 +76,25 @@ onLoad((opt) => {
|
||||
async function query() {
|
||||
if (!ready.value) return;
|
||||
const value = String(keyword.value || '').trim();
|
||||
if (!value) {
|
||||
list.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const queryId = ++lastQueryId;
|
||||
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
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
|
||||
.map((i) => {
|
||||
const label = normalizeText(i?.diseaseName);
|
||||
@ -103,9 +104,9 @@ async function query() {
|
||||
})
|
||||
.filter(Boolean);
|
||||
} catch (e) {
|
||||
list.value = [];
|
||||
if (queryId === lastQueryId) list.value = [];
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
if (queryId === lastQueryId) uni.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,39 @@
|
||||
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) {
|
||||
try {
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: `${env.MP_API_BASE_URL}/upload`,
|
||||
url: joinBaseAndPath(env.MP_API_BASE_URL, 'upload'),
|
||||
filePath: tempFilePath,
|
||||
name: 'file',
|
||||
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;
|
||||
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) {
|
||||
console.log('upload file error:', e);
|
||||
@ -58,5 +87,3 @@ export async function chooseAndUploadImage(options = {}) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user