feat:健康档案使用模板
This commit is contained in:
parent
a300cb4760
commit
755608e569
@ -49,18 +49,28 @@
|
||||
<view v-if="records.length === 0" class="empty">暂无数据</view>
|
||||
</view>
|
||||
|
||||
<view class="fab" :style="{ bottom: `${floatingBottom}px` }" @click="add">
|
||||
<uni-icons type="plusempty" size="24" color="#fff" />
|
||||
</view>
|
||||
<picker
|
||||
class="fab-picker"
|
||||
mode="selector"
|
||||
:range="selectableTemplates"
|
||||
range-key="name"
|
||||
:disabled="selectableTemplates.length === 0"
|
||||
:style="{ bottom: `${floatingBottom}px` }"
|
||||
@change="pickAddType"
|
||||
>
|
||||
<view class="fab" :class="{ 'fab--disabled': selectableTemplates.length === 0 }">
|
||||
<uni-icons type="plusempty" size="24" color="#fff" />
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { VISIT_RECORD_TEMPLATES } from './templates';
|
||||
import api from '@/utils/api';
|
||||
import { loading, hideLoading } from '@/utils/widget';
|
||||
import { loading, hideLoading, toast } from '@/utils/widget';
|
||||
import { normalizeTemplate } from '../../utils/template';
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => ({}) },
|
||||
@ -68,7 +78,14 @@ const props = defineProps({
|
||||
floatingBottom: { type: Number, default: 16 },
|
||||
});
|
||||
|
||||
const templates = ref(VISIT_RECORD_TEMPLATES.map(t => ({ name: t.templateName, templateType: t.templateType })));
|
||||
const FALLBACK_TEMPLATE_TYPES = ['outpatient', 'inhospital', 'preConsultation', 'physicalExaminationTemplate'];
|
||||
const templates = ref([]);
|
||||
const selectableTemplates = computed(() => templates.value.filter((i) => i && i.templateType && typeof i.name === 'string' && i.name.trim()));
|
||||
const templateMap = computed(() => templates.value.reduce((m, t) => {
|
||||
if (t?.templateType) m[String(t.templateType)] = t;
|
||||
return m;
|
||||
}, {}));
|
||||
const availableTypes = computed(() => (templates.value.length ? templates.value.map((i) => i.templateType) : FALLBACK_TEMPLATE_TYPES));
|
||||
|
||||
const typeRange = computed(() => [{ name: '全部', value: 'ALL' }, ...templates.value.map((t) => ({ name: t.name, value: t.templateType }))]);
|
||||
const currentType = ref({ name: '全部', value: 'ALL' });
|
||||
@ -108,6 +125,50 @@ function getCorpId() {
|
||||
return team?.corpId ? String(team.corpId) : '';
|
||||
}
|
||||
|
||||
const loadedCorpId = ref('');
|
||||
async function loadVisitTemplates() {
|
||||
const corpId = getCorpId();
|
||||
if (!corpId) return;
|
||||
if (loadedCorpId.value === corpId && templates.value.length) return;
|
||||
loadedCorpId.value = corpId;
|
||||
|
||||
try {
|
||||
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 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;
|
||||
|
||||
const detailRes = await api('getTemplateListByTemptype', { corpId, templateTypeList: typeList });
|
||||
const detail = detailRes?.data && Array.isArray(detailRes.data?.data) ? detailRes.data.data : Array.isArray(detailRes?.data) ? detailRes.data : [];
|
||||
const temps = Array.isArray(detail) ? detail : [];
|
||||
const byType = temps.reduce((m, t) => {
|
||||
const k = t?.templateType ? String(t.templateType) : '';
|
||||
if (k) m[k] = t;
|
||||
return m;
|
||||
}, {});
|
||||
const ordered = typeList.map((t) => byType[String(t)]).filter(Boolean);
|
||||
|
||||
const next = ordered
|
||||
.map((t) => {
|
||||
const temp = normalizeTemplate(t);
|
||||
const name = String(temp?.name || temp?.templateName || temp?.templateTypeName || '') || String(temp?.templateType || '');
|
||||
return {
|
||||
templateType: String(temp?.templateType || ''),
|
||||
name,
|
||||
service: temp?.service || {},
|
||||
templateList: Array.isArray(temp?.templateList) ? temp.templateList : [],
|
||||
};
|
||||
})
|
||||
.filter((i) => i && i.templateType);
|
||||
|
||||
if (next.length) templates.value = next;
|
||||
} catch (e) {
|
||||
console.error('loadVisitTemplates error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
const userNameMap = ref({});
|
||||
const loadedTeamId = ref('');
|
||||
function resolveUserName(userId) {
|
||||
@ -138,6 +199,8 @@ async function loadTeamMembers() {
|
||||
}
|
||||
|
||||
function getSortTimeTitle(templateType) {
|
||||
const t = templateMap.value[String(templateType || '')] || {};
|
||||
if (t?.service?.timeTitle) return String(t.service.timeTitle);
|
||||
if (templateType === 'outpatient') return 'visitTime';
|
||||
if (templateType === 'inhospital') return 'inhosDate';
|
||||
if (templateType === 'preConsultation') return 'consultDate';
|
||||
@ -172,8 +235,8 @@ function formatPositiveFind(v, { withOpinion = false } = {}) {
|
||||
}
|
||||
|
||||
function getTemplateName(type) {
|
||||
const t = VISIT_RECORD_TEMPLATES.find((i) => i && i.templateType === type);
|
||||
return t?.templateName ? String(t.templateName) : '';
|
||||
const t = templateMap.value[String(type || '')];
|
||||
return t?.name ? String(t.name) : '';
|
||||
}
|
||||
|
||||
function toDateStr(sortTime) {
|
||||
@ -193,6 +256,7 @@ async function refreshList() {
|
||||
|
||||
const corpId = getCorpId();
|
||||
if (!corpId) return;
|
||||
await loadVisitTemplates();
|
||||
loadTeamMembers();
|
||||
|
||||
loading('加载中...');
|
||||
@ -201,7 +265,7 @@ async function refreshList() {
|
||||
|
||||
// 添加类型筛选
|
||||
params.medicalType =
|
||||
currentType.value.value === 'ALL' ? templates.value.map((i) => i.templateType) : currentType.value.value;
|
||||
currentType.value.value === 'ALL' ? availableTypes.value : currentType.value.value;
|
||||
|
||||
// 添加时间筛选
|
||||
if (Array.isArray(dateRange.value) && dateRange.value.length === 2 && dateRange.value[0] && dateRange.value[1]) {
|
||||
@ -329,15 +393,13 @@ function previewFiles(r, idx) {
|
||||
uni.previewImage({ urls, current: urls[idx] });
|
||||
}
|
||||
|
||||
function add() {
|
||||
uni.showActionSheet({
|
||||
itemList: templates.value.map((i) => i.name),
|
||||
success: ({ tapIndex }) => {
|
||||
const t = templates.value[tapIndex];
|
||||
uni.navigateTo({
|
||||
url: `/pages/case/visit-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&type=${encodeURIComponent(t.templateType)}&name=${encodeURIComponent(props.data?.name || '')}`,
|
||||
});
|
||||
},
|
||||
function pickAddType(e) {
|
||||
if (!props.archiveId) return toast('缺少档案信息');
|
||||
const idx = Number(e?.detail?.value ?? -1);
|
||||
const t = selectableTemplates.value[idx];
|
||||
if (!t?.templateType) return;
|
||||
uni.navigateTo({
|
||||
url: `/pages/case/visit-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&type=${encodeURIComponent(t.templateType)}&name=${encodeURIComponent(props.data?.name || '')}`,
|
||||
});
|
||||
}
|
||||
|
||||
@ -533,9 +595,14 @@ watch(
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.fab {
|
||||
.fab-picker {
|
||||
position: fixed;
|
||||
right: 32rpx;
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
z-index: 20;
|
||||
}
|
||||
.fab {
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
border-radius: 52rpx;
|
||||
@ -544,6 +611,8 @@ watch(
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 20rpx 36rpx rgba(79, 110, 247, 0.35);
|
||||
z-index: 20;
|
||||
}
|
||||
.fab--disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,473 +1,277 @@
|
||||
<template>
|
||||
<view class="medical-case-form">
|
||||
<view class="form-container">
|
||||
<!-- 动态渲染表单字段 -->
|
||||
<view
|
||||
v-for="field in currentFields"
|
||||
:key="field.key"
|
||||
class="form-item"
|
||||
:class="{ required: field.required }"
|
||||
>
|
||||
<view class="item-label">{{ field.label }}</view>
|
||||
<view class="page">
|
||||
<view class="body">
|
||||
<scroll-view scroll-y class="scroll">
|
||||
<view class="header">
|
||||
<view class="header-title">{{ pageTitle }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 日期选择器 -->
|
||||
<picker
|
||||
v-if="field.type === 'date'"
|
||||
mode="date"
|
||||
:value="formData[field.key]"
|
||||
@change="onDateChange(field.key, $event)"
|
||||
:disabled="!isEditing"
|
||||
>
|
||||
<view class="picker-value">
|
||||
{{ formData[field.key] || "暂无" }}
|
||||
</view>
|
||||
</picker>
|
||||
<view class="form-wrap">
|
||||
<FormTemplate v-if="temp" ref="formRef" :items="showItems" :form="form" @change="onChange" />
|
||||
</view>
|
||||
|
||||
<!-- 多行文本 -->
|
||||
<textarea
|
||||
v-else-if="field.type === 'textarea'"
|
||||
class="item-textarea"
|
||||
v-model="formData[field.key]"
|
||||
placeholder="请输入"
|
||||
:disabled="!isEditing"
|
||||
/>
|
||||
<view style="height: 240rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 单行文本 -->
|
||||
<input
|
||||
v-else
|
||||
class="item-input"
|
||||
v-model="formData[field.key]"
|
||||
placeholder="暂无"
|
||||
:disabled="!isEditing"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-buttons">
|
||||
<view class="btn-regenerate" @click="handleRegenerate">
|
||||
<text class="btn-text">重新生成</text>
|
||||
</view>
|
||||
<view class="btn-save" @click="handleSave">
|
||||
<text class="btn-text">保存至档案</text>
|
||||
<view class="footer">
|
||||
<button class="btn plain" @click="handleRegenerate">重新生成</button>
|
||||
<button class="btn primary" @click="handleSave">保存至档案</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import useAccountStore from "@/store/account";
|
||||
import api from "@/utils/api.js";
|
||||
const caseType = ref("");
|
||||
const formData = ref({});
|
||||
const isEditing = ref(true);
|
||||
const customerId = ref("");
|
||||
const groupId = ref("");
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import FormTemplate from '@/components/form-template/index.vue';
|
||||
import api from '@/utils/api.js';
|
||||
import useAccountStore from '@/store/account';
|
||||
import { toast, confirm, loading as uniLoading, hideLoading } from '@/utils/widget';
|
||||
import { normalizeVisitRecordFormData } from './utils/visit-record';
|
||||
import { normalizeTemplate, unwrapTemplateResponse } from './utils/template';
|
||||
|
||||
const caseType = ref('');
|
||||
const customerId = ref('');
|
||||
const groupId = ref('');
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const { doctorInfo } = storeToRefs(accountStore);
|
||||
// 病历类型名称
|
||||
const CASE_TYPE_NAMES = {
|
||||
outpatient: "门诊病历",
|
||||
inhospital: "住院病历",
|
||||
physicalExaminationTemplate: "体检记录",
|
||||
preConsultation: "预问诊记录",
|
||||
};
|
||||
|
||||
// 字段标签
|
||||
const FIELD_LABELS = {
|
||||
// 门诊病历
|
||||
visitTime: "就诊日期",
|
||||
chiefComplaint: "主诉",
|
||||
medicalHistorySummary: "病史概要",
|
||||
examination: "检查",
|
||||
diagnosisName: "门诊诊断",
|
||||
// 住院病历
|
||||
inhosDate: "入院日期",
|
||||
operation: "手术记录",
|
||||
operationDate: "手术日期",
|
||||
treatmentPlan: "治疗方案",
|
||||
// 体检记录
|
||||
inspectTime: "体检日期",
|
||||
inspectSummary: "体检小结",
|
||||
positiveFind: "阳性发现及处理意见",
|
||||
// 预问诊记录
|
||||
presentIllnessHistory: "现病史",
|
||||
pastMedicalHistory: "既往史",
|
||||
};
|
||||
|
||||
// 字段配置:根据病历类型定义字段
|
||||
const FIELD_CONFIG = {
|
||||
outpatient: [
|
||||
{
|
||||
key: "visitTime",
|
||||
label: FIELD_LABELS.visitTime,
|
||||
type: "date",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "diagnosisName",
|
||||
label: FIELD_LABELS.diagnosisName,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "chiefComplaint",
|
||||
label: FIELD_LABELS.chiefComplaint,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "medicalHistorySummary",
|
||||
label: FIELD_LABELS.medicalHistorySummary,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "examination",
|
||||
label: FIELD_LABELS.examination,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "treatmentPlan",
|
||||
label: "治疗方案",
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
inhospital: [
|
||||
{
|
||||
key: "inhosDate",
|
||||
label: FIELD_LABELS.inhosDate,
|
||||
type: "date",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "diagnosisName",
|
||||
label: "住院主诊断",
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "operation",
|
||||
label: FIELD_LABELS.operation,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "operationDate",
|
||||
label: FIELD_LABELS.operationDate,
|
||||
type: "date",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "treatmentPlan",
|
||||
label: FIELD_LABELS.treatmentPlan,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "chiefComplaint",
|
||||
label: FIELD_LABELS.chiefComplaint,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "medicalHistorySummary",
|
||||
label: FIELD_LABELS.medicalHistorySummary,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "examination",
|
||||
label: FIELD_LABELS.examination,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "treatmentPlan",
|
||||
label: "治疗方案",
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
physicalExaminationTemplate: [
|
||||
{
|
||||
key: "inspectTime",
|
||||
label: FIELD_LABELS.inspectTime,
|
||||
type: "date",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "inspectSummary",
|
||||
label: FIELD_LABELS.inspectSummary,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "positiveFind",
|
||||
label: FIELD_LABELS.positiveFind,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
preConsultation: [
|
||||
{
|
||||
key: "chiefComplaint",
|
||||
label: FIELD_LABELS.chiefComplaint,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "presentIllnessHistory",
|
||||
label: FIELD_LABELS.presentIllnessHistory,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "pastMedicalHistory",
|
||||
label: FIELD_LABELS.pastMedicalHistory,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 当前病历类型的字段配置
|
||||
const currentFields = computed(() => {
|
||||
return FIELD_CONFIG[caseType.value] || [];
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const pages = getCurrentPages();
|
||||
const currentPage = pages[pages.length - 1];
|
||||
const options = currentPage.options;
|
||||
|
||||
caseType.value = options.caseType || "";
|
||||
customerId.value = options.patientId || "";
|
||||
groupId.value = options.groupId || "";
|
||||
// 从 options 中解析表单数据
|
||||
if (options.formData) {
|
||||
try {
|
||||
formData.value = JSON.parse(decodeURIComponent(options.formData));
|
||||
} catch (e) {
|
||||
console.error("解析表单数据失败:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置页面标题
|
||||
const title = CASE_TYPE_NAMES[caseType.value]
|
||||
? `添加${CASE_TYPE_NAMES[caseType.value]}`
|
||||
: "添加病历";
|
||||
uni.setNavigationBarTitle({ title });
|
||||
});
|
||||
|
||||
const onDateChange = (field, event) => {
|
||||
formData.value[field] = event.detail.value;
|
||||
};
|
||||
|
||||
const handleRegenerate = () => {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "确定要重新生成吗?当前编辑的内容将被覆盖",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 返回上一页并触发重新生成
|
||||
uni.navigateBack({
|
||||
success: () => {
|
||||
uni.$emit("regenerateMedicalCase", {
|
||||
caseType: caseType.value,
|
||||
customerId: customerId.value,
|
||||
groupId: groupId.value,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
// 验证必填项
|
||||
const requiredFields = getRequiredFields();
|
||||
const missingFields = requiredFields.filter(
|
||||
(field) => !formData.value[field.key]
|
||||
);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
uni.showToast({
|
||||
title: `请填写${missingFields[0].label}`,
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { account, doctorInfo } = storeToRefs(accountStore);
|
||||
const { getDoctorInfo } = accountStore;
|
||||
|
||||
async function ensureDoctor() {
|
||||
if (doctorInfo.value) return;
|
||||
if (!account.value?.openid) return;
|
||||
try {
|
||||
uni.showLoading({ title: "保存中..." });
|
||||
const result = await api("addMedicalRecord", {
|
||||
medicalType: caseType.value,
|
||||
memberId: customerId.value,
|
||||
creator: doctorInfo.value.userid,
|
||||
...formData.value,
|
||||
});
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (result.success) {
|
||||
uni.showToast({
|
||||
title: "保存成功",
|
||||
icon: "success",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.message || "保存失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error("保存病历失败:", error);
|
||||
uni.showToast({
|
||||
title: "保存失败,请重试",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getRequiredFields = () => {
|
||||
return currentFields.value.filter((field) => field.required);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.medical-case-form {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
|
||||
.form-container {
|
||||
background-color: #ffffff;
|
||||
padding: 32rpx;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
&.required .item-label::before {
|
||||
content: "*";
|
||||
color: #ff4d4f;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.item-input,
|
||||
.picker-value {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
|
||||
&[disabled] {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.picker-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.item-textarea {
|
||||
width: 100%;
|
||||
min-height: 100rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
height: 100px;
|
||||
&[disabled] {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips-box {
|
||||
margin-top: 32rpx;
|
||||
padding: 24rpx;
|
||||
background-color: #fffbe6;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.tips-text {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.btn-regenerate,
|
||||
.btn-save {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 44rpx;
|
||||
|
||||
.btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-regenerate {
|
||||
background-color: #ffffff;
|
||||
border: 2rpx solid #1890ff;
|
||||
|
||||
.btn-text {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #1890ff;
|
||||
|
||||
.btn-text {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
await getDoctorInfo();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function getUserId() {
|
||||
const d = doctorInfo.value || {};
|
||||
const a = account.value || {};
|
||||
return String(d.userid || d.userId || d.corpUserId || a.userid || a.userId || '') || '';
|
||||
}
|
||||
|
||||
function getCorpId() {
|
||||
const d = doctorInfo.value || {};
|
||||
const a = account.value || {};
|
||||
const t = uni.getStorageSync('ykt_case_current_team') || {};
|
||||
return String(d.corpId || a.corpId || t.corpId || '') || '';
|
||||
}
|
||||
|
||||
const temp = ref(null);
|
||||
const titleText = computed(() => {
|
||||
const t = temp.value || {};
|
||||
return String(t.name || t.templateName || t.templateTypeName || '').trim();
|
||||
});
|
||||
const pageTitle = computed(() => {
|
||||
const name = titleText.value;
|
||||
return name ? `添加${name}` : '添加病历';
|
||||
});
|
||||
|
||||
const form = reactive({});
|
||||
const forms = computed(() => form);
|
||||
const showItems = computed(() => {
|
||||
const list = temp.value?.templateList || [];
|
||||
return list.filter((i) => {
|
||||
if (i?.type === 'files') return false;
|
||||
if (i && typeof i.referenceField === 'string') {
|
||||
return forms.value[i.referenceField] === i.referenceValue;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
const formRef = ref(null);
|
||||
|
||||
function onChange({ title, value }) {
|
||||
form[title] = value;
|
||||
const item = showItems.value.find((i) => i.title === title);
|
||||
if (!item) return;
|
||||
const relat = (temp.value?.templateList || []).filter((i) => i.referenceField === title);
|
||||
relat.forEach((i) => (form[i.title] = ''));
|
||||
}
|
||||
|
||||
function setDefaultDates() {
|
||||
const timeKey = temp.value?.service?.timeTitle || '';
|
||||
if (timeKey && !form[timeKey]) form[timeKey] = dayjs().format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
function applyInitialFormData(raw) {
|
||||
const normalized = normalizeVisitRecordFormData(caseType.value, raw);
|
||||
Object.assign(form, normalized);
|
||||
setDefaultDates();
|
||||
}
|
||||
|
||||
async function loadTemplate(t) {
|
||||
const corpId = getCorpId();
|
||||
if (!corpId) return null;
|
||||
try {
|
||||
const res = await api('getCurrentTemplate', { corpId, templateType: t });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '获取模板失败');
|
||||
return null;
|
||||
}
|
||||
const raw = unwrapTemplateResponse(res);
|
||||
return normalizeTemplate(raw);
|
||||
} catch (e) {
|
||||
console.error('loadTemplate error:', e);
|
||||
toast('获取模板失败');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
caseType.value = options?.caseType || options?.type || '';
|
||||
customerId.value = options?.patientId || options?.memberId || '';
|
||||
groupId.value = options?.groupId || '';
|
||||
|
||||
if (!caseType.value) caseType.value = 'outpatient';
|
||||
|
||||
temp.value = await loadTemplate(caseType.value);
|
||||
if (temp.value?.templateType) caseType.value = String(temp.value.templateType);
|
||||
|
||||
if (options?.formData) {
|
||||
try {
|
||||
const parsed = JSON.parse(decodeURIComponent(options.formData));
|
||||
applyInitialFormData(parsed);
|
||||
} catch (e) {
|
||||
console.error('解析表单数据失败:', e);
|
||||
applyInitialFormData({});
|
||||
}
|
||||
} else {
|
||||
applyInitialFormData({});
|
||||
}
|
||||
|
||||
uni.setNavigationBarTitle({ title: pageTitle.value });
|
||||
});
|
||||
|
||||
async function handleRegenerate() {
|
||||
try {
|
||||
await confirm('确定要重新生成吗?当前编辑的内容将被覆盖');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
uni.navigateBack({
|
||||
success: () => {
|
||||
uni.$emit('regenerateMedicalCase', {
|
||||
caseType: caseType.value,
|
||||
customerId: customerId.value,
|
||||
groupId: groupId.value,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!customerId.value) return toast('缺少患者信息');
|
||||
await ensureDoctor();
|
||||
const corpId = getCorpId();
|
||||
const userId = getUserId();
|
||||
if (!corpId || !userId) return toast('缺少用户信息');
|
||||
if (formRef.value?.verify && !formRef.value.verify()) return;
|
||||
|
||||
const params = {
|
||||
...form,
|
||||
corpId,
|
||||
memberId: customerId.value,
|
||||
medicalType: caseType.value,
|
||||
creator: userId,
|
||||
};
|
||||
|
||||
// 门诊/住院:与模板字段对齐(diagnosisName)
|
||||
if ((caseType.value === 'outpatient' || caseType.value === 'inhospital') && form.diagnosis && !form.diagnosisName) {
|
||||
params.diagnosisName = form.diagnosis;
|
||||
}
|
||||
|
||||
const sortTimeKey = temp.value?.service?.timeTitle || '';
|
||||
if (sortTimeKey && form[sortTimeKey] && dayjs(form[sortTimeKey]).isValid()) {
|
||||
params.sortTime = dayjs(form[sortTimeKey]).valueOf();
|
||||
} else {
|
||||
params.sortTime = Date.now();
|
||||
}
|
||||
|
||||
uniLoading('保存中...');
|
||||
try {
|
||||
const res = await api('addMedicalRecord', params);
|
||||
hideLoading();
|
||||
if (res.success) {
|
||||
uni.$emit('archive-detail:visit-record-changed');
|
||||
toast(res.message || '保存成功');
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
} else {
|
||||
toast(res.message || '保存失败');
|
||||
}
|
||||
} catch (error) {
|
||||
hideLoading();
|
||||
console.error('保存病历失败:', error);
|
||||
toast('保存失败,请重试');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
padding-bottom: calc(152rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.scroll {
|
||||
flex: 1;
|
||||
}
|
||||
.header {
|
||||
background: #fff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.header-title {
|
||||
padding: 28rpx 28rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.form-wrap {
|
||||
background: #fff;
|
||||
margin-top: 20rpx;
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
padding: 24rpx 28rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.btn::after {
|
||||
border: none;
|
||||
}
|
||||
.btn.plain {
|
||||
background: #fff;
|
||||
color: #0877F1;
|
||||
border: 2rpx solid #0877F1;
|
||||
}
|
||||
.btn.primary {
|
||||
background: #0877F1;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
95
pages/case/utils/template.js
Normal file
95
pages/case/utils/template.js
Normal file
@ -0,0 +1,95 @@
|
||||
function normalizeOptions(options) {
|
||||
if (!Array.isArray(options)) return [];
|
||||
if (!options.length) return [];
|
||||
if (typeof options[0] === 'string') return options.filter((i) => typeof i === 'string');
|
||||
if (typeof options[0] === 'object') {
|
||||
return options
|
||||
.map((i) => {
|
||||
const label = i?.label ?? i?.name ?? i?.text ?? i?.title ?? '';
|
||||
const value = i?.value ?? i?.id ?? i?.key ?? label;
|
||||
if (!label && (value === undefined || value === null || value === '')) return null;
|
||||
return { label: String(label || value), value: String(value) };
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function normalizeTemplateItem(item) {
|
||||
const next = { ...(item || {}) };
|
||||
|
||||
if (next.operateType === 'custom') next.operateType = 'formCell';
|
||||
|
||||
const originalType = next.type;
|
||||
const customTypeMap = {
|
||||
customerSource: 'select',
|
||||
customerStage: 'select',
|
||||
tag: 'multiSelectAndOther',
|
||||
reference: 'input',
|
||||
selectWwuser: 'select',
|
||||
files: 'files',
|
||||
corpProject: 'select',
|
||||
diagnosis: 'textarea',
|
||||
BMI: 'input',
|
||||
bloodPressure: 'textarea',
|
||||
selfMultipleDiseases: 'textarea',
|
||||
};
|
||||
if (originalType && customTypeMap[originalType]) {
|
||||
next.__originType = originalType;
|
||||
next.type = customTypeMap[originalType];
|
||||
}
|
||||
|
||||
const aliasTypeMap = {
|
||||
text: 'input',
|
||||
string: 'input',
|
||||
number: 'input',
|
||||
integer: 'input',
|
||||
int: 'input',
|
||||
};
|
||||
if (next.type && aliasTypeMap[next.type]) {
|
||||
next.type = aliasTypeMap[next.type];
|
||||
if (!next.inputType && (originalType === 'number' || originalType === 'integer' || originalType === 'int')) next.inputType = 'number';
|
||||
}
|
||||
|
||||
const rawRange = next.range || next.options || next.optionList || next.values || [];
|
||||
const range = normalizeOptions(rawRange);
|
||||
|
||||
if (next.type === 'select' || next.type === 'selectAndOther' || next.type === 'selectAndImage') {
|
||||
next.range = range;
|
||||
} else if (next.type === 'radio') {
|
||||
// wxapp 目前 radio 组件只支持字符串列表;模板如为对象选项则降级为 select
|
||||
if (range.length && typeof range[0] === 'object') {
|
||||
next.type = 'select';
|
||||
next.range = range;
|
||||
} else {
|
||||
next.range = Array.isArray(rawRange) ? rawRange : [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!next.operateType) next.operateType = 'formCell';
|
||||
next.required = Boolean(next.required);
|
||||
|
||||
if (next.type === 'input' && (next.wordLimit === undefined || next.wordLimit === null || next.wordLimit === '')) next.wordLimit = 20;
|
||||
if (next.type === 'textarea' && (next.wordLimit === undefined || next.wordLimit === null || next.wordLimit === '')) next.wordLimit = 200;
|
||||
return next;
|
||||
}
|
||||
|
||||
export function unwrapTemplateResponse(res) {
|
||||
const d = res?.data;
|
||||
if (d && typeof d === 'object') {
|
||||
if (d.data && typeof d.data === 'object') return d.data;
|
||||
return d;
|
||||
}
|
||||
return res && typeof res === 'object' ? res : {};
|
||||
}
|
||||
|
||||
export function normalizeTemplate(temp) {
|
||||
const t = temp && typeof temp === 'object' ? { ...temp } : {};
|
||||
const list = Array.isArray(t.templateList) ? t.templateList : [];
|
||||
t.templateList = list
|
||||
.filter((i) => i && i.fieldStatus !== 'disable')
|
||||
.filter((i) => i.operateType !== 'onlyRead')
|
||||
.map(normalizeTemplateItem);
|
||||
return t;
|
||||
}
|
||||
|
||||
40
pages/case/utils/visit-record.js
Normal file
40
pages/case/utils/visit-record.js
Normal file
@ -0,0 +1,40 @@
|
||||
function isEmpty(v) {
|
||||
if (v === null || v === undefined) return true;
|
||||
if (Array.isArray(v)) return v.length === 0;
|
||||
if (typeof v === 'string') return v.trim() === '';
|
||||
return false;
|
||||
}
|
||||
|
||||
const ALIAS_MAP = {
|
||||
outpatient: {
|
||||
diagnosisName: 'diagnosis',
|
||||
medicalHistorySummary: 'medicalHistory',
|
||||
},
|
||||
inhospital: {
|
||||
diagnosisName: 'diagnosis',
|
||||
medicalHistorySummary: 'medicalHistory',
|
||||
operationDate: 'surgeryDate',
|
||||
operation: 'surgeryName',
|
||||
},
|
||||
physicalExaminationTemplate: {
|
||||
inspectTime: 'inspectDate',
|
||||
inspectSummary: 'summary',
|
||||
},
|
||||
preConsultation: {
|
||||
presentIllnessHistory: 'presentIllness',
|
||||
pastMedicalHistory: 'pastHistory',
|
||||
},
|
||||
};
|
||||
|
||||
export function normalizeVisitRecordFormData(templateType, raw) {
|
||||
const input = raw && typeof raw === 'object' ? raw : {};
|
||||
const out = { ...input };
|
||||
const map = ALIAS_MAP[String(templateType || '')] || {};
|
||||
|
||||
Object.entries(map).forEach(([from, to]) => {
|
||||
if (isEmpty(out[to]) && !isEmpty(out[from])) out[to] = out[from];
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
<view class="body">
|
||||
<scroll-view scroll-y class="scroll">
|
||||
<view class="header">
|
||||
<view class="header-title">{{ template?.templateName || '健康档案' }}</view>
|
||||
<view class="header-title">{{ titleText || '健康档案' }}</view>
|
||||
</view>
|
||||
|
||||
<view class="form-wrap">
|
||||
<FormTemplate v-if="template" ref="formRef" :items="showItems" :form="forms" @change="onChange" />
|
||||
<FormTemplate v-if="temp" ref="formRef" :items="showItems" :form="forms" @change="onChange" />
|
||||
</view>
|
||||
|
||||
<!-- 附件上传(FormTemplate 不支持 files,单独实现) -->
|
||||
@ -19,7 +19,8 @@
|
||||
</view>
|
||||
<view class="upload-grid">
|
||||
<view v-for="(f, idx) in fileList" :key="idx" class="upload-item" @click="previewFile(idx)">
|
||||
<image class="upload-thumb" :src="f.url" mode="aspectFill" />
|
||||
<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">
|
||||
@ -49,10 +50,12 @@ import { onLoad } from '@dcloudio/uni-app';
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import FormTemplate from '@/components/form-template/index.vue';
|
||||
import { getVisitRecordTemplate } from './components/archive-detail/templates';
|
||||
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';
|
||||
import { normalizeTemplate, unwrapTemplateResponse } from './utils/template';
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const { account, doctorInfo } = storeToRefs(accountStore);
|
||||
@ -84,14 +87,17 @@ function getCorpId() {
|
||||
const memberId = ref('');
|
||||
const recordId = ref('');
|
||||
const templateType = ref('');
|
||||
const customerName = ref('');
|
||||
|
||||
const template = computed(() => getVisitRecordTemplate(templateType.value));
|
||||
const temp = ref(null);
|
||||
const titleText = computed(() => {
|
||||
const t = temp.value || {};
|
||||
return String(t.name || t.templateName || t.templateTypeName || '').trim();
|
||||
});
|
||||
const detail = ref({});
|
||||
const form = reactive({});
|
||||
const forms = computed(() => ({ ...detail.value, ...form }));
|
||||
const showItems = computed(() => {
|
||||
const list = template.value?.templateList || [];
|
||||
const list = temp.value?.templateList || [];
|
||||
// referenceField 兼容(与 mobile 一致)
|
||||
return list.filter((i) => {
|
||||
if (i?.type === 'files') return false;
|
||||
@ -103,7 +109,7 @@ const showItems = computed(() => {
|
||||
});
|
||||
|
||||
const hasFilesField = computed(() => {
|
||||
const list = template.value?.templateList || [];
|
||||
const list = temp.value?.templateList || [];
|
||||
return list.some((i) => i && (i.type === 'files' || i.title === 'files'));
|
||||
});
|
||||
|
||||
@ -119,30 +125,43 @@ function ensureFilesField() {
|
||||
form.files = [];
|
||||
}
|
||||
|
||||
async function loadTemplate(t) {
|
||||
const corpId = getCorpId();
|
||||
if (!corpId) return null;
|
||||
try {
|
||||
const res = await api('getCurrentTemplate', { corpId, templateType: t });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '获取模板失败');
|
||||
return null;
|
||||
}
|
||||
const raw = unwrapTemplateResponse(res);
|
||||
return normalizeTemplate(raw);
|
||||
} catch (e) {
|
||||
console.error('loadTemplate error:', e);
|
||||
toast('获取模板失败');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
memberId.value = options?.memberId || options?.archiveId || '';
|
||||
recordId.value = options?.id || '';
|
||||
templateType.value = options?.type || '';
|
||||
customerName.value = decodeURIComponent(options?.customerName || '');
|
||||
|
||||
if (recordId.value) {
|
||||
await getDetail();
|
||||
} else {
|
||||
if (!templateType.value) templateType.value = 'outpatient';
|
||||
temp.value = await loadTemplate(templateType.value);
|
||||
if (temp.value?.templateType) templateType.value = String(temp.value.templateType);
|
||||
ensureFilesField();
|
||||
// 门诊记录默认今日日期
|
||||
if (templateType.value === 'outpatient') {
|
||||
form.visitTime = dayjs().format('YYYY-MM-DD');
|
||||
}
|
||||
// 住院记录默认今日日期
|
||||
if (templateType.value === 'inhospital') {
|
||||
form.inhosDate = dayjs().format('YYYY-MM-DD');
|
||||
}
|
||||
// 体检记录默认今日日期
|
||||
if (templateType.value === 'physicalExaminationTemplate') {
|
||||
form.inspectDate = dayjs().format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
// 默认填充模板时间字段
|
||||
const timeKey = temp.value?.service?.timeTitle || '';
|
||||
if (timeKey && !form[timeKey]) form[timeKey] = dayjs().format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
if (titleText.value) uni.setNavigationBarTitle({ title: titleText.value });
|
||||
});
|
||||
|
||||
async function getDetail() {
|
||||
@ -162,14 +181,14 @@ async function getDetail() {
|
||||
const record = res?.record || res?.data?.record || null;
|
||||
if (res?.success && record) {
|
||||
templateType.value = record.templateType || record.medicalType || templateType.value;
|
||||
|
||||
// 兼容模板字段:wxapp 使用 diagnosis,但接口通常返回 diagnosisName
|
||||
if ((record.medicalType === 'outpatient' || record.medicalType === 'inhospital') && !record.diagnosis && record.diagnosisName) {
|
||||
record.diagnosis = record.diagnosisName;
|
||||
}
|
||||
|
||||
detail.value = record;
|
||||
detail.value = normalizeVisitRecordFormData(templateType.value, record);
|
||||
ensureFilesField();
|
||||
// 详情可能返回真实 templateType:与模板保持一致
|
||||
if (!temp.value || temp.value?.templateType !== templateType.value) {
|
||||
temp.value = await loadTemplate(templateType.value);
|
||||
if (temp.value?.templateType) templateType.value = String(temp.value.templateType);
|
||||
if (titleText.value) uni.setNavigationBarTitle({ title: titleText.value });
|
||||
}
|
||||
} else {
|
||||
toast(res.message || '加载失败');
|
||||
}
|
||||
@ -185,7 +204,7 @@ function onChange({ title, value }) {
|
||||
const item = showItems.value.find((i) => i.title === title);
|
||||
if (!item) return;
|
||||
// 关联字段变化时清理被关联字段(与 mobile 行为一致)
|
||||
const relat = (template.value?.templateList || []).filter((i) => i.referenceField === title);
|
||||
const relat = (temp.value?.templateList || []).filter((i) => i.referenceField === title);
|
||||
relat.forEach((i) => (form[i.title] = ''));
|
||||
}
|
||||
|
||||
@ -224,7 +243,7 @@ async function save() {
|
||||
}
|
||||
|
||||
// sortTime:使用模板中的时间字段
|
||||
const sortTimeKey = template.value?.service?.timeTitle || '';
|
||||
const sortTimeKey = temp.value?.service?.timeTitle || '';
|
||||
if (sortTimeKey && form[sortTimeKey] && dayjs(form[sortTimeKey]).isValid()) {
|
||||
params.sortTime = dayjs(form[sortTimeKey]).valueOf();
|
||||
} else {
|
||||
@ -252,65 +271,84 @@ async function save() {
|
||||
}
|
||||
}
|
||||
|
||||
function remove() {
|
||||
confirm('确定删除当前记录?', async () => {
|
||||
if (!memberId.value || !recordId.value) return toast('缺少必要信息');
|
||||
await ensureDoctor();
|
||||
const corpId = getCorpId();
|
||||
if (!corpId) return toast('缺少必要信息');
|
||||
uniLoading('删除中...');
|
||||
try {
|
||||
const res = await api('removeMedicalRecord', {
|
||||
corpId,
|
||||
memberId: memberId.value,
|
||||
medicalType: templateType.value,
|
||||
_id: recordId.value,
|
||||
});
|
||||
hideLoading();
|
||||
if (res.success) {
|
||||
uni.$emit('archive-detail:visit-record-changed');
|
||||
toast(res.message || '已删除');
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
} else {
|
||||
toast(res.message || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
hideLoading();
|
||||
console.error('remove error:', error);
|
||||
toast('删除失败');
|
||||
async function remove() {
|
||||
try {
|
||||
await confirm('确定删除当前记录?');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!memberId.value || !recordId.value) return toast('缺少必要信息');
|
||||
await ensureDoctor();
|
||||
const corpId = getCorpId();
|
||||
if (!corpId) return toast('缺少必要信息');
|
||||
uniLoading('删除中...');
|
||||
try {
|
||||
const res = await api('removeMedicalRecord', {
|
||||
corpId,
|
||||
memberId: memberId.value,
|
||||
medicalType: templateType.value,
|
||||
_id: recordId.value,
|
||||
});
|
||||
hideLoading();
|
||||
if (res.success) {
|
||||
uni.$emit('archive-detail:visit-record-changed');
|
||||
toast(res.message || '已删除');
|
||||
setTimeout(() => uni.navigateBack(), 300);
|
||||
} else {
|
||||
toast(res.message || '删除失败');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
hideLoading();
|
||||
console.error('remove error:', error);
|
||||
toast('删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
function addFiles() {
|
||||
const fileConfig = template.value?.templateList?.find(i => i.type === 'files');
|
||||
const maxSize = fileConfig?.maxSize || 5; // MB
|
||||
const accept = fileConfig?.accept || 'pdf';
|
||||
function isPdfUrl(url) {
|
||||
const u = String(url || '').toLowerCase();
|
||||
return u.includes('.pdf') || u.startsWith('data:application/pdf');
|
||||
}
|
||||
|
||||
uni.chooseMessageFile({
|
||||
count: 9,
|
||||
type: 'file',
|
||||
extension: [accept],
|
||||
success: (res) => {
|
||||
const files = Array.isArray(res.tempFiles) ? res.tempFiles : [];
|
||||
const maxBytes = maxSize * 1024 * 1024;
|
||||
|
||||
// 验证文件大小
|
||||
const invalidFiles = files.filter(f => f.size > maxBytes);
|
||||
if (invalidFiles.length > 0) {
|
||||
toast(`文件大小不能超过${maxSize}M`);
|
||||
return;
|
||||
}
|
||||
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 next = files.map((f) => ({
|
||||
url: f.path,
|
||||
name: f.name || '',
|
||||
size: f.size
|
||||
}));
|
||||
const cur = Array.isArray(forms.value.files) ? forms.value.files : [];
|
||||
form.files = [...cur, ...next];
|
||||
},
|
||||
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) {
|
||||
@ -319,8 +357,32 @@ function removeFile(idx) {
|
||||
}
|
||||
|
||||
function previewFile(idx) {
|
||||
const urls = fileList.value.map((i) => i.url);
|
||||
uni.previewImage({ urls, current: urls[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>
|
||||
|
||||
@ -393,6 +455,17 @@ function previewFile(idx) {
|
||||
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;
|
||||
|
||||
@ -10,6 +10,8 @@ const urlsConfig = {
|
||||
getTeamData: 'getTeamData',
|
||||
getTeamBymember: 'getTeamBymember',
|
||||
getCurrentTemplate: 'getCurrentTemplate',
|
||||
getTemplateGroup: 'getTemplateGroup',
|
||||
getTemplateListByTemptype: 'getTemplateListByTemptype',
|
||||
wxAppLogin: 'wxAppLogin',
|
||||
getDeptList: 'getRealDeptList',
|
||||
getHospitalList: 'getRealHospital',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user