<template>
|
<view class="case-detail">
|
<!-- 头部信息卡片 -->
|
<view class="header-card">
|
<view class="case-header">
|
<view class="hospital-info">
|
<image
|
:src="caseDetail.hospitalLogo || '/static/hospital-default.png'"
|
mode="aspectFit"
|
class="hospital-logo"
|
/>
|
<view class="hospital-details">
|
<text class="hospital-name">{{
|
caseDetail.treatmenthospitalname || "未填写治疗医院"
|
}}</text>
|
<text class="case-type">{{
|
caseDetail.donorNo ? "器官捐献案例" : "案例详情"
|
}}</text>
|
</view>
|
</view>
|
<view
|
class="case-status"
|
:class="getStatusClass(caseDetail.reportStatus)"
|
>
|
{{ getStatusText(caseDetail.reportStatus) }}
|
</view>
|
</view>
|
|
<view class="case-basic-info">
|
<view class="info-row">
|
<view class="info-item">
|
<text class="label">案例编号</text>
|
<text class="value">{{ caseDetail.caseNo || "未生成" }}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">上报时间</text>
|
<text class="value">{{
|
formatDateTime(caseDetail.reporttime)
|
}}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<!-- 选项卡导航 -->
|
<view class="tab-navigation">
|
<text
|
v-for="tab in tabs"
|
:key="tab.id"
|
:class="{ active: activeTab == tab.id }"
|
@tap="switchTab(tab.id)"
|
class="tab-item"
|
>
|
{{ tab.label }}
|
</text>
|
</view>
|
|
<!-- ==================== 基本信息 ==================== -->
|
<view v-if="activeTab == 'basic'" class="info-section fade-in-up">
|
<view class="section-card">
|
<view class="section-header">
|
<text class="section-title">📋 捐献者基本信息</text>
|
</view>
|
<view class="info-grid">
|
<!-- 第一行:姓名、性别、年龄 -->
|
<view class="info-item">
|
<text class="label">姓名</text>
|
<text class="value">{{ caseDetail.name || "未填写" }}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">性别</text>
|
<text class="value">{{
|
getDictLabel("sys_user_sex", caseDetail.sex)
|
}}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">年龄</text>
|
<text class="value"
|
>{{ caseDetail.age || "0" }}
|
{{ getDictLabel("sys_AgeUnit", caseDetail.ageunit) }}</text
|
>
|
</view>
|
|
<!-- 第二行:证件类型、证件号码 -->
|
<view class="info-item">
|
<text class="label">证件类型</text>
|
<text class="value">{{
|
getDictLabel("sys_IDType", caseDetail.idcardtype)
|
}}</text>
|
</view>
|
<view class="info-item full-width">
|
<text class="label">证件号码</text>
|
<text class="value">{{ caseDetail.idcardno || "未填写" }}</text>
|
</view>
|
|
<!-- 第三行:民族、国籍、籍贯 -->
|
<view class="info-item">
|
<text class="label">民族</text>
|
<text class="value">{{
|
getDictLabel("sys_Nation", caseDetail.nation)
|
}}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">国籍</text>
|
<text class="value">{{ caseDetail.nationality || "未填写" }}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">籍贯</text>
|
<text class="value">{{ caseDetail.nativeplace || "未填写" }}</text>
|
</view>
|
|
<!-- 第四行:学历、职业 -->
|
<view class="info-item">
|
<text class="label">学历</text>
|
<text class="value">{{
|
getDictLabel("sys_education", caseDetail.education)
|
}}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">职业</text>
|
<text class="value">{{
|
getDictLabel("sys_occupation", caseDetail.occupation)
|
}}</text>
|
</view>
|
|
<!-- 第五行:联系电话 -->
|
<view class="info-item">
|
<text class="label">联系电话</text>
|
<text class="value">{{ caseDetail.phone || "未填写" }}</text>
|
</view>
|
|
<!-- 地址信息:单独一行,占据整行 -->
|
<view class="info-item full-width address-item">
|
<text class="label">户籍地址</text>
|
<text class="value">{{
|
getFullRegisterAddress() || "未填写"
|
}}</text>
|
</view>
|
<view class="info-item full-width address-item">
|
<text class="label">现住地址</text>
|
<text class="value">{{
|
getFullResidenceAddress() || "未填写"
|
}}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<!-- ==================== 医疗信息 ==================== -->
|
<view v-if="activeTab == 'medical'" class="info-section fade-in-up">
|
<view class="section-card">
|
<view class="section-header">
|
<text class="section-title">🏥 医疗信息</text>
|
</view>
|
<view class="info-grid">
|
<!-- 住院号、GCS评分 -->
|
<view class="info-item">
|
<text class="label">住院号</text>
|
<text class="value">{{ caseDetail.inpatientno || "未填写" }}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">GCS评分</text>
|
<text class="value">{{ caseDetail.gcsScore || "未评估" }}</text>
|
</view>
|
|
<!-- 血型、Rh阴性 -->
|
<view class="info-item">
|
<text class="label">血型</text>
|
<text class="value">{{
|
getDictLabel("sys_BloodType", caseDetail.bloodType)
|
}}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">Rh(D)阴性</text>
|
<text class="value">{{
|
caseDetail.rhYin == "1" ? "是" : "否"
|
}}</text>
|
</view>
|
|
<!-- 传染病:占据整行 -->
|
<view class="info-item full-width">
|
<text class="label">传染病</text>
|
<text class="value">{{ getInfectiousText() || "无" }}</text>
|
</view>
|
|
<!-- 疾病诊断:占据整行 -->
|
<view class="info-item full-width">
|
<text class="label">疾病诊断</text>
|
<text class="value diagnosis-text">{{
|
caseDetail.diagnosisname || "未填写"
|
}}</text>
|
</view>
|
|
<!-- 病情概况:占据整行 -->
|
<view class="info-item full-width">
|
<text class="label">病情概况</text>
|
<text class="value">{{
|
caseDetail.illnessoverview || "未填写"
|
}}</text>
|
</view>
|
|
<!-- 病人状况:占据整行 -->
|
<view class="info-item full-width">
|
<text class="label">病人状况</text>
|
<text class="value">{{ caseDetail.patientstate || "未填写" }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<!-- ==================== 联系信息 ==================== -->
|
<view v-if="activeTab == 'contact'" class="info-section fade-in-up">
|
<view class="section-card">
|
<view class="section-header">
|
<text class="section-title">📞 医院与联系信息</text>
|
</view>
|
<view class="info-grid">
|
<!-- 医院信息 -->
|
<view class="info-item full-width">
|
<text class="label">治疗医院</text>
|
<text class="value">{{
|
caseDetail.treatmenthospitalname || "未填写"
|
}}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">治疗科室</text>
|
<text class="value">{{
|
caseDetail.treatmentdeptname || "未填写"
|
}}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">上报医院</text>
|
<text class="value">{{ caseDetail.toHospital || "未填写" }}</text>
|
</view>
|
|
<!-- 联系人信息 -->
|
<view class="info-item">
|
<text class="label">上报信息员</text>
|
<text class="value">{{ caseDetail.infoName || "未填写" }}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">信息员编号</text>
|
<text class="value">{{ caseDetail.infoNo || "未填写" }}</text>
|
</view>
|
|
<!-- <view class="info-item">
|
<text class="label">ICU评估医生</text>
|
<text class="value">{{ caseDetail.icuDoctor || "未填写" }}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">ICU医生电话</text>
|
<text class="value">{{
|
caseDetail.icuDoctorPhone || "未填写"
|
}}</text>
|
</view> -->
|
|
<view class="info-item">
|
<text class="label">协调员</text>
|
<text class="value">{{
|
caseDetail.coordinatorName || "未填写"
|
}}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">协调员编号</text>
|
<text class="value">{{
|
caseDetail.coordinatorNo || "未填写"
|
}}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<!-- ==================== 转运信息 ==================== -->
|
<view v-if="activeTab == 'transport'" class="info-section fade-in-up">
|
<view class="section-card">
|
<view class="section-header">
|
<view
|
style="
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
width: 100%;
|
"
|
>
|
<text class="section-title">🚑 转运信息</text>
|
<view
|
v-if="caseDetail.reportStatus == '3'"
|
style="display: flex; gap: 16rpx"
|
>
|
<button
|
v-if="caseDetail.isTransport == '2' && !hasTransport"
|
class="small-btn primary"
|
@tap.stop="createTransport"
|
>
|
创建转运单
|
</button>
|
<button
|
v-if="hasTransport"
|
class="small-btn secondary"
|
@tap.stop="viewTransportDetail"
|
>
|
查看转运单
|
</button>
|
</view>
|
</view>
|
</view>
|
|
<view class="info-grid">
|
<view class="info-item">
|
<text class="label">是否需要转运</text>
|
<text class="value">{{
|
caseDetail.isTransport == "2" ? "需要" : "不需要"
|
}}</text>
|
</view>
|
</view>
|
|
<!-- 有转运信息时显示 -->
|
<view
|
v-if="hasTransport && caseDetail.serviceTransport"
|
class="transport-list"
|
>
|
<view
|
class="transport-item"
|
v-for="transport in caseDetail.serviceTransport"
|
:key="transport.id"
|
>
|
<view class="transport-header">
|
<text class="transport-no"
|
>转运单号:{{ transport.reportId || transport.id }}</text
|
>
|
<text
|
class="transport-status"
|
:class="getTransportStatusClass(transport.transitStatus)"
|
>
|
{{ getTransportStatusText(transport.transitStatus) }}
|
</text>
|
</view>
|
<view class="transport-body">
|
<view class="transport-row">
|
<text class="t-label">出发地点</text>
|
<text class="t-value">{{
|
transport.transportStartPlace || "未填写"
|
}}</text>
|
</view>
|
<view class="transport-row">
|
<text class="t-label">出发时间</text>
|
<text class="t-value">{{
|
formatDateTime(transport.transportStartTime)
|
}}</text>
|
</view>
|
<view class="transport-row">
|
<text class="t-label">负责协调员</text>
|
<text class="t-value">{{
|
transport.contactPerson || "未填写"
|
}}</text>
|
</view>
|
<view v-if="transport.doctor" class="transport-row">
|
<text class="t-label">出诊医生</text>
|
<text class="t-value">{{ transport.doctor }}</text>
|
</view>
|
<view v-if="transport.nurse" class="transport-row">
|
<text class="t-label">出诊护士</text>
|
<text class="t-value">{{ transport.nurse }}</text>
|
</view>
|
<view v-if="transport.driver" class="transport-row">
|
<text class="t-label">驾驶员</text>
|
<text class="t-value">{{ transport.driver }}</text>
|
</view>
|
<view v-if="transport.icuDoctor" class="transport-row">
|
<text class="t-label">ICU评估医生</text>
|
<text class="t-value">{{ transport.icuDoctor }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<!-- 无转运信息但需要转运 -->
|
<view v-else-if="caseDetail.isTransport == '2'" class="empty-transport">
|
<text>该案例需要转运,但尚未创建转运单</text>
|
<text
|
v-if="caseDetail.reportStatus !== '3'"
|
style="color: #f0ad4e; font-size: 24rpx; margin-top: 10rpx"
|
>
|
需先审批同意才能创建转运单
|
</text>
|
</view>
|
|
<!-- 无需转运 -->
|
<view v-else class="empty-transport">
|
<text>该案例无需转运</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 附件 -->
|
<attachment-upload
|
ref="attachment"
|
:files="attachments"
|
:readonly="true"
|
:maxCount="5"
|
@preview="handlePreview"
|
/>
|
|
<!-- 操作按钮 -->
|
<view class="action-bar">
|
<button class="action-btn secondary" @tap="goBack">返回</button>
|
|
<!-- 确认按钮:仅admin角色可见,且状态为"已阅读"(2)时显示 -->
|
<button
|
v-if="canApprove && caseDetail.delFlag == 0"
|
class="action-btn approve"
|
@tap="openApprovePanel"
|
>
|
确认案例
|
</button>
|
|
<button
|
v-if="caseDetail.reportStatus == '1' || caseDetail.reportStatus == '2'"
|
class="action-btn primary"
|
@tap="handleEdit"
|
>
|
编辑案例
|
</button>
|
</view>
|
|
<!-- ==================== 确认底部弹出面板 ==================== -->
|
<view
|
class="approve-mask"
|
v-if="showApprovePanel"
|
@tap="closeApprovePanel"
|
></view>
|
<view class="approve-panel" :class="{ 'panel-show': showApprovePanel }">
|
<view class="panel-header">
|
<text class="panel-title">案例确认</text>
|
<text class="panel-close" @tap="closeApprovePanel">✕</text>
|
</view>
|
|
<scroll-view scroll-y class="panel-body">
|
<!-- 案例摘要 -->
|
<view class="summary-card">
|
<view class="summary-row">
|
<text class="s-label">案例编号</text>
|
<text class="s-value">{{ caseDetail.caseNo }}</text>
|
</view>
|
<view class="summary-row">
|
<text class="s-label">患者姓名</text>
|
<text class="s-value">{{ caseDetail.name }}</text>
|
</view>
|
<view class="summary-row">
|
<text class="s-label">疾病诊断</text>
|
<text class="s-value">{{ caseDetail.diagnosisname }}</text>
|
</view>
|
<view class="summary-row">
|
<text class="s-label">上报医院</text>
|
<text class="s-value">{{ caseDetail.treatmenthospitalname }}</text>
|
</view>
|
</view>
|
|
<!-- 确认结果 -->
|
<view class="form-group">
|
<text class="form-label"
|
>确认结果 <text class="required">*</text></text
|
>
|
<view class="radio-group">
|
<view
|
class="radio-item"
|
:class="{ active: approveForm.approveResult == '3' }"
|
@tap="
|
approveForm.approveResult = '3';
|
handleApproveResultChange('3');
|
"
|
>
|
<text
|
class="radio-icon"
|
:class="{ checked: approveForm.approveResult == '3' }"
|
>✓</text
|
>
|
<text class="radio-text">同意</text>
|
</view>
|
<view
|
class="radio-item"
|
:class="{ active: approveForm.approveResult == '4' }"
|
@tap="
|
approveForm.approveResult = '4';
|
handleApproveResultChange('4');
|
"
|
>
|
<text
|
class="radio-icon"
|
:class="{ checked: approveForm.approveResult == '4' }"
|
>✓</text
|
>
|
<text class="radio-text">驳回</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 同意时的转运选项 -->
|
<view class="form-group" v-if="approveForm.approveResult == '3'">
|
<text class="form-label"
|
>是否需要转运 <text class="required">*</text></text
|
>
|
<view class="radio-group">
|
<view
|
class="radio-item"
|
:class="{ active: approveForm.isTransport == '1' }"
|
@tap="approveForm.isTransport = '1'"
|
>
|
<text
|
class="radio-icon"
|
:class="{ checked: approveForm.isTransport == '1' }"
|
>✓</text
|
>
|
<text class="radio-text">不需要</text>
|
</view>
|
<view
|
class="radio-item"
|
:class="{ active: approveForm.isTransport == '2' }"
|
@tap="approveForm.isTransport = '2'"
|
>
|
<text
|
class="radio-icon"
|
:class="{ checked: approveForm.isTransport == '2' }"
|
>✓</text
|
>
|
<text class="radio-text">需要</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 驳回原因 -->
|
<view class="form-group" v-if="approveForm.approveResult == '4'">
|
<text class="form-label"
|
>驳回原因 <text class="required">*</text></text
|
>
|
<view class="radio-group vertical">
|
<view
|
v-for="reason in rejectReasons"
|
:key="reason.value"
|
class="radio-item"
|
:class="{ active: approveForm.rejectType == reason.value }"
|
@tap="approveForm.rejectType = reason.value"
|
>
|
<text
|
class="radio-icon"
|
:class="{ checked: approveForm.rejectType == reason.value }"
|
>✓</text
|
>
|
<text class="radio-text">{{ reason.label }}</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 确认/驳回意见 -->
|
<view class="form-group">
|
<text class="form-label">
|
{{ approveForm.approveResult == "4" ? "驳回意见" : "确认意见" }}
|
<text class="required" v-if="approveForm.approveResult == '4'"
|
>*</text
|
>
|
</text>
|
<textarea
|
v-model="approveForm.confirmResult"
|
class="form-textarea"
|
:placeholder="
|
approveForm.approveResult == '4'
|
? '请输入详细的驳回意见'
|
: '请输入确认意见'
|
"
|
maxlength="500"
|
/>
|
<text class="word-count"
|
>{{ approveForm.confirmResult.length }}/500</text
|
>
|
</view>
|
</scroll-view>
|
|
<!-- 底部按钮 -->
|
<view class="panel-footer">
|
<button class="panel-btn cancel" @tap="closeApprovePanel">取消</button>
|
<button
|
class="panel-btn submit"
|
@tap="submitApprove"
|
:loading="approveLoading"
|
>
|
确定
|
</button>
|
</view>
|
</view>
|
|
<!-- 加载状态 -->
|
<u-loading-icon v-if="loading" :show="loading" text="加载中..." />
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted } from "vue";
|
import { onLoad, onShow } from "@dcloudio/uni-app";
|
import { useHomeButton } from "@/stores/useHomeButton";
|
import attachmentUpload from "@/components/attachment";
|
import { useUserStore } from "@/stores/user";
|
import { useDictMapper } from "@/utils/useDictMapper";
|
import dayjs from "dayjs";
|
|
const { showHomeButton, checkAutoLogin, goHome } = useHomeButton();
|
|
// ==================== 字典映射 ====================
|
const requiredDictTypes = [
|
"sys_user_sex",
|
"sys_BloodType",
|
"sys_AgeUnit",
|
"sys_IDType",
|
"sys_Nation",
|
"sys_education",
|
"sys_occupation",
|
"sys_Infectious",
|
];
|
|
const { dictData, loading: dictLoading } = useDictMapper(requiredDictTypes);
|
|
// 通用字典查询方法
|
const getDictList = (dictType) => {
|
return dictData.value[dictType] || [];
|
};
|
|
const getDictLabel = (dictType, value) => {
|
if (!value) return "未填写";
|
const dictList = getDictList(dictType);
|
const found = dictList.find(
|
(item) => item.value == value || item.value === value,
|
);
|
return found ? found.label : value;
|
};
|
|
// 传染病特殊处理(多个值用逗号分隔)
|
const getInfectiousText = () => {
|
const infectious = caseDetail.value.infectious;
|
if (!infectious) return "无";
|
const values = infectious.split(",").filter((v) => v.trim());
|
if (values.length === 0) return "无";
|
const labels = values.map((v) => getDictLabel("sys_Infectious", v.trim()));
|
const other = caseDetail.value.infectiousOther;
|
return labels.join("、") + (other ? `(${other})` : "");
|
};
|
|
// ==================== 数据 ====================
|
const caseDetail = ref({});
|
const userStore = useUserStore();
|
const loading = ref(false);
|
const approveLoading = ref(false);
|
const activeTab = ref("basic");
|
const caseId = ref(null);
|
const attachments = ref([]);
|
const showApprovePanel = ref(false);
|
|
const canApprove = computed(() => {
|
console.log(userStore.userInfo.roles);
|
|
const roles = userStore.userInfo?.roles || [];
|
return roles.some((role) => role.roleKey === "admin" || "opoadmin"|| "leader"|| "business");
|
});
|
|
// 驳回原因选项
|
const rejectReasons = [
|
{ label: "好转", value: "1" },
|
{ label: "死亡", value: "2" },
|
{ label: "不符合捐献标准", value: "3" },
|
{ label: "家属放弃捐献", value: "4" },
|
{ label: "其他", value: "5" },
|
];
|
|
// 确认表单
|
const approveForm = ref({
|
id: null,
|
approveResult: "3",
|
confirmResult: "",
|
rejectType: "",
|
isTransport: "2",
|
isDonate: "0",
|
});
|
|
// 选项卡
|
const tabs = ref([
|
{ id: "basic", label: "基本信息" },
|
{ id: "medical", label: "医疗信息" },
|
{ id: "contact", label: "联系信息" },
|
{ id: "transport", label: "转运信息" },
|
]);
|
|
// 计算属性
|
const hasTransport = computed(
|
() =>
|
caseDetail.value.serviceTransport &&
|
caseDetail.value.serviceTransport.length > 0,
|
);
|
|
// ==================== 生命周期 ====================
|
onShow(() => {
|
if (caseId.value) {
|
loadCaseDetail(caseId.value);
|
}
|
});
|
|
onLoad(async (options) => {
|
checkAutoLogin(options);
|
if (options.id) {
|
caseId.value = options.id;
|
await loadCaseDetail(options.id);
|
}
|
});
|
|
// ==================== 加载数据 ====================
|
const loadCaseDetail = async (id) => {
|
loading.value = true;
|
try {
|
const res = await uni.$uapi.get(
|
`/project/donatebaseinforeport/getInfo/${id}`,
|
);
|
if (res.data) {
|
caseDetail.value = res.data;
|
if (res.data.annexfilesList) {
|
attachments.value = res.data.annexfilesList.map((item) => ({
|
...item,
|
url: item.path,
|
name: item.fileName,
|
}));
|
}
|
} else {
|
throw new Error(res.msg || "数据加载失败");
|
}
|
} catch (error) {
|
console.error("加载案例详情失败:", error);
|
uni.showToast({ title: "数据加载失败,请重试", icon: "none" });
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
// ==================== 地址拼接 ====================
|
const getFullRegisterAddress = () => {
|
const {
|
registerprovincename,
|
registercityname,
|
registertownname,
|
registeraddress,
|
} = caseDetail.value;
|
const parts = [
|
registerprovincename,
|
registercityname,
|
registertownname,
|
registeraddress,
|
];
|
return parts.filter((p) => p && p.trim()).join("");
|
};
|
|
const getFullResidenceAddress = () => {
|
const {
|
residenceprovincename,
|
residencecityname,
|
residencetownname,
|
residenceaddress,
|
} = caseDetail.value;
|
const parts = [
|
residenceprovincename,
|
residencecityname,
|
residencetownname,
|
residenceaddress,
|
];
|
return parts.filter((p) => p && p.trim()).join("");
|
};
|
|
// ==================== 状态相关 ====================
|
const getStatusClass = (status) => {
|
const map = { 1: "reported", 2: "read", 3: "agreed", 4: "rejected" };
|
return map[status] || "reported";
|
};
|
|
const getStatusText = (status) => {
|
const map = { 1: "已上报", 2: "已阅读", 3: "已同意", 4: "已驳回" };
|
return map[status] || "已上报";
|
};
|
|
// ==================== 转运相关 ====================
|
const getTransportStatusText = (status) => {
|
const map = {
|
1: "待转运",
|
2: "转运中",
|
3: "转运完成",
|
4: "转运取消",
|
5: "暂存",
|
};
|
return map[status] || "未知";
|
};
|
|
const getTransportStatusClass = (status) => {
|
const map = {
|
1: "pending",
|
2: "transporting",
|
3: "completed",
|
4: "cancelled",
|
5: "draft",
|
};
|
return map[status] || "pending";
|
};
|
|
// ==================== 工具方法 ====================
|
const formatDateTime = (dateTime) => {
|
if (!dateTime) return "";
|
return dateTime.replace("T", " ").substring(0, 16);
|
};
|
|
// ==================== 确认功能 ====================
|
/** 打开确认面板 */
|
const openApprovePanel = () => {
|
// 初始化表单
|
approveForm.value = {
|
id: caseDetail.value.id,
|
approveResult: "3",
|
confirmResult: "",
|
rejectType: "",
|
isTransport: caseDetail.value.isTransport || "2",
|
isDonate: "0",
|
};
|
showApprovePanel.value = true;
|
};
|
|
/** 关闭确认面板 */
|
const closeApprovePanel = () => {
|
showApprovePanel.value = false;
|
};
|
|
/** 确认结果变化处理 */
|
const handleApproveResultChange = (value) => {
|
if (value == "3") {
|
// 选择同意时,重置驳回原因
|
approveForm.value.rejectType = "";
|
approveForm.value.isDonate = "0";
|
} else if (value == "4") {
|
// 选择驳回时,重置转运选项
|
approveForm.value.isTransport = "1";
|
}
|
};
|
|
/** 表单校验 */
|
const validateApproveForm = () => {
|
if (!approveForm.value.approveResult) {
|
uni.showToast({ title: "请选择确认结果", icon: "none" });
|
return false;
|
}
|
if (
|
approveForm.value.approveResult == "3" &&
|
!approveForm.value.isTransport
|
) {
|
uni.showToast({ title: "请选择是否需要转运", icon: "none" });
|
return false;
|
}
|
if (approveForm.value.approveResult == "4") {
|
if (!approveForm.value.rejectType) {
|
uni.showToast({ title: "请选择驳回原因", icon: "none" });
|
return false;
|
}
|
if (!approveForm.value.confirmResult) {
|
uni.showToast({ title: "驳回时必须填写驳回意见", icon: "none" });
|
return false;
|
}
|
}
|
return true;
|
};
|
|
/** 提交确认 */
|
const submitApprove = async () => {
|
if (!validateApproveForm()) return;
|
|
approveLoading.value = true;
|
try {
|
// 准备确认数据
|
const approveData = {
|
...caseDetail.value,
|
reportStatus: approveForm.value.approveResult,
|
confirmResult: approveForm.value.confirmResult,
|
approvername: userStore.user?.name || "当前用户",
|
approvetime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
updateTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
updateBy: userStore.user?.name || "当前用户",
|
};
|
|
// 处理确认结果
|
if (approveForm.value.approveResult == "3") {
|
// 同意
|
approveData.isTransport = approveForm.value.isTransport;
|
// 如果不需要转运,设置isDonate为1
|
if (approveForm.value.isTransport == "1") {
|
approveData.isDonate = "1";
|
} else {
|
approveData.isDonate = "0";
|
}
|
} else if (approveForm.value.approveResult == "4") {
|
// 驳回
|
approveData.rejectType = approveForm.value.rejectType;
|
approveData.isTransport = "1"; // 驳回的案例不需要转运
|
approveData.isDonate = "0"; // 驳回的案例标记为已捐献
|
}
|
|
// 调用编辑接口更新状态
|
const res = await uni.$uapi.post(
|
"/project/donatebaseinforeport/edit",
|
approveData,
|
);
|
|
if (res.code == 200) {
|
uni.showToast({ title: "确认成功", icon: "success" });
|
showApprovePanel.value = false;
|
|
// 重新加载详情
|
await loadCaseDetail(caseId.value);
|
|
// 如果同意且需要转运,跳转到转运单创建页面
|
if (
|
approveForm.value.approveResult == "3" &&
|
approveForm.value.isTransport == "2"
|
) {
|
console.log(caseId.value);
|
|
setTimeout(() => {
|
uni.navigateTo({
|
url: `/pagesSub/case/transferinfo?caseId=${caseId.value}&caseNo=${caseDetail.value.caseNo}&autoCreate=true`,
|
});
|
}, 800);
|
}
|
} else {
|
uni.showToast({ title: res.msg || "确认失败", icon: "none" });
|
}
|
} catch (error) {
|
console.error("确认失败:", error);
|
uni.showToast({ title: "确认失败,请重试", icon: "none" });
|
} finally {
|
approveLoading.value = false;
|
}
|
};
|
|
// ==================== 事件处理 ====================
|
const switchTab = (tabId) => {
|
activeTab.value = tabId;
|
};
|
|
const goBack = () => {
|
if (showHomeButton) {
|
uni.switchTab({ url: "/pages/index/index" });
|
} else {
|
uni.navigateBack();
|
}
|
};
|
|
const handleEdit = () => {
|
uni.navigateTo({
|
url: `/pagesSub/case/CaseDetails?id=${caseId.value}&edit=true`,
|
});
|
};
|
|
const createTransport = () => {
|
if (caseDetail.value.reportStatus !== "3") {
|
uni.showToast({ title: "案例需先审批同意", icon: "none" });
|
return;
|
}
|
uni.navigateTo({
|
url: `/pages/transport/create?caseId=${caseId.value}&caseNo=${caseDetail.value.caseNo}`,
|
});
|
};
|
|
const viewTransportDetail = () => {
|
if (hasTransport.value) {
|
const transport = caseDetail.value.serviceTransport[0];
|
uni.navigateTo({ url: `/pages/transport/detail?id=${transport.id}` });
|
}
|
};
|
|
// 附件预览
|
const handlePreview = (file) => {
|
const fullUrl = file.url.startsWith("http")
|
? file.url
|
: (file.url.startsWith("/") ? "" : "/") + file.url;
|
if (file.type && file.type.includes("image")) {
|
uni.previewImage({
|
urls: attachments.value
|
.filter((f) => f.type && f.type.includes("image"))
|
.map((f) =>
|
f.url.startsWith("http")
|
? f.url
|
: (f.url.startsWith("/") ? "" : "/") + f.url,
|
),
|
current: fullUrl,
|
});
|
} else if (file.type && file.type.includes("pdf")) {
|
uni.downloadFile({
|
url: fullUrl,
|
success: (res) => {
|
uni.openDocument({
|
filePath: res.tempFilePath,
|
fileType: "pdf",
|
showMenu: true,
|
});
|
},
|
fail: () => uni.showToast({ title: "打开文件失败", icon: "none" }),
|
});
|
} else {
|
uni.showToast({ title: "暂不支持此文件类型预览", icon: "none" });
|
}
|
};
|
</script>
|
|
<style lang="scss" scoped>
|
.case-detail {
|
min-height: 100vh;
|
background: #f5f7fa;
|
padding: 20rpx;
|
padding-bottom: 180rpx; /* 增加底部间距给操作栏 */
|
}
|
|
/* ========== 头部卡片 ========== */
|
.header-card {
|
background: #fff;
|
border-radius: 24rpx;
|
padding: 30rpx 28rpx;
|
margin-bottom: 24rpx;
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
}
|
|
.case-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 24rpx;
|
}
|
|
.hospital-info {
|
display: flex;
|
align-items: center;
|
flex: 1;
|
min-width: 0;
|
}
|
|
.hospital-logo {
|
width: 72rpx;
|
height: 72rpx;
|
border-radius: 16rpx;
|
margin-right: 16rpx;
|
background: #f0f2f5;
|
flex-shrink: 0;
|
}
|
|
.hospital-details {
|
display: flex;
|
flex-direction: column;
|
min-width: 0;
|
}
|
|
.hospital-name {
|
font-size: 30rpx;
|
font-weight: 600;
|
color: #1d1d1f;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.case-type {
|
font-size: 22rpx;
|
color: #8e8e93;
|
margin-top: 4rpx;
|
}
|
|
.case-status {
|
padding: 6rpx 18rpx;
|
border-radius: 20rpx;
|
font-size: 22rpx;
|
font-weight: 500;
|
flex-shrink: 0;
|
&.reported {
|
background: rgba(255, 149, 0, 0.12);
|
color: #ff9500;
|
}
|
&.read {
|
background: rgba(0, 122, 255, 0.12);
|
color: #007aff;
|
}
|
&.agreed {
|
background: rgba(52, 199, 89, 0.12);
|
color: #34c759;
|
}
|
&.rejected {
|
background: rgba(255, 59, 48, 0.12);
|
color: #ff3b30;
|
}
|
}
|
|
.case-basic-info {
|
.info-row {
|
display: flex;
|
justify-content: space-between;
|
gap: 20rpx;
|
}
|
.info-item {
|
display: flex;
|
flex-direction: column;
|
flex: 1;
|
}
|
.label {
|
font-size: 22rpx;
|
color: #8e8e93;
|
margin-bottom: 4rpx;
|
}
|
.value {
|
font-size: 26rpx;
|
color: #1d1d1f;
|
font-weight: 500;
|
}
|
}
|
|
/* ========== 选项卡 ========== */
|
.tab-navigation {
|
display: flex;
|
background: #fff;
|
border-radius: 16rpx;
|
padding: 6rpx;
|
margin-bottom: 24rpx;
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
overflow-x: auto;
|
&::-webkit-scrollbar {
|
display: none;
|
}
|
}
|
|
.tab-item {
|
flex: 1;
|
min-width: 120rpx;
|
text-align: center;
|
padding: 16rpx 10rpx;
|
font-size: 26rpx;
|
color: #8e8e93;
|
border-radius: 12rpx;
|
transition: all 0.3s ease;
|
&.active {
|
color: #007aff;
|
background: #f0f7ff;
|
font-weight: 600;
|
}
|
}
|
|
/* ========== 内容卡片 ========== */
|
.section-card {
|
background: #fff;
|
border-radius: 24rpx;
|
padding: 28rpx 24rpx;
|
margin-bottom: 24rpx;
|
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
}
|
|
.section-header {
|
margin-bottom: 24rpx;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
.section-title {
|
font-size: 30rpx;
|
font-weight: 600;
|
color: #1d1d1f;
|
}
|
|
/* ========== 信息网格 ========== */
|
.info-grid {
|
display: grid;
|
grid-template-columns: 1fr 1fr;
|
gap: 16rpx 24rpx;
|
}
|
|
.info-item {
|
display: flex;
|
flex-direction: column;
|
padding: 12rpx 0;
|
border-bottom: 1rpx solid #f5f5f7;
|
|
&.full-width {
|
grid-column: 1 / -1;
|
}
|
|
&.address-item {
|
padding-bottom: 8rpx;
|
}
|
|
.label {
|
font-size: 22rpx;
|
color: #8e8e93;
|
margin-bottom: 4rpx;
|
font-weight: 400;
|
}
|
|
.value {
|
font-size: 27rpx;
|
color: #1d1d1f;
|
font-weight: 500;
|
word-break: break-all;
|
line-height: 1.5;
|
}
|
|
.diagnosis-text {
|
color: #007aff;
|
font-weight: 600;
|
}
|
}
|
|
/* 地址项特殊处理:去掉边框更清爽 */
|
.address-item {
|
border-bottom: none !important;
|
padding-bottom: 4rpx !important;
|
}
|
|
/* ========== 转运信息 ========== */
|
.transport-list {
|
margin-top: 16rpx;
|
}
|
|
.transport-item {
|
background: #f8f9fc;
|
border-radius: 16rpx;
|
padding: 20rpx;
|
margin-bottom: 16rpx;
|
border: 1rpx solid #e8ecf0;
|
&:last-child {
|
margin-bottom: 0;
|
}
|
}
|
|
.transport-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding-bottom: 14rpx;
|
border-bottom: 1rpx solid #e8ecf0;
|
margin-bottom: 14rpx;
|
}
|
|
.transport-no {
|
font-size: 26rpx;
|
font-weight: 600;
|
color: #1d1d1f;
|
}
|
|
.transport-status {
|
font-size: 22rpx;
|
padding: 4rpx 16rpx;
|
border-radius: 12rpx;
|
font-weight: 500;
|
&.pending {
|
background: rgba(240, 173, 78, 0.15);
|
color: #f0ad4e;
|
}
|
&.transporting {
|
background: rgba(0, 122, 255, 0.12);
|
color: #007aff;
|
}
|
&.completed {
|
background: rgba(52, 199, 89, 0.12);
|
color: #34c759;
|
}
|
&.cancelled {
|
background: rgba(142, 142, 147, 0.12);
|
color: #8e8e93;
|
}
|
&.draft {
|
background: rgba(142, 142, 147, 0.08);
|
color: #8e8e93;
|
}
|
}
|
|
.transport-body {
|
display: flex;
|
flex-direction: column;
|
gap: 8rpx;
|
}
|
|
.transport-row {
|
display: flex;
|
align-items: baseline;
|
.t-label {
|
font-size: 24rpx;
|
color: #8e8e93;
|
width: 140rpx;
|
flex-shrink: 0;
|
}
|
.t-value {
|
font-size: 26rpx;
|
color: #1d1d1f;
|
word-break: break-all;
|
}
|
}
|
|
.empty-transport {
|
text-align: center;
|
padding: 40rpx 0;
|
color: #8e8e93;
|
font-size: 26rpx;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
}
|
|
/* ========== 小型按钮 ========== */
|
.small-btn {
|
padding: 8rpx 20rpx;
|
font-size: 22rpx;
|
border-radius: 8rpx;
|
border: none;
|
&.primary {
|
background: #007aff;
|
color: #fff;
|
}
|
&.secondary {
|
background: #ecf5ff;
|
color: #007aff;
|
border: 1rpx solid #007aff;
|
}
|
&:active {
|
opacity: 0.8;
|
}
|
}
|
|
/* ========== 底部操作栏 ========== */
|
.action-bar {
|
position: fixed;
|
bottom: 0;
|
left: 0;
|
right: 0;
|
background: #fff;
|
padding: 18rpx 30rpx calc(18rpx + env(safe-area-inset-bottom));
|
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
|
z-index: 2;
|
display: flex;
|
gap: 16rpx;
|
}
|
|
.action-btn {
|
flex: 1;
|
height: 88rpx;
|
border-radius: 44rpx;
|
font-size: 32rpx;
|
font-weight: 700;
|
border: none;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
letter-spacing: 1rpx;
|
|
&.secondary {
|
background: #f5f5f7;
|
color: #1d1d1f;
|
&:active {
|
background: #e5e5e7;
|
}
|
}
|
&.primary {
|
background: linear-gradient(90deg, #0071e3, #2997ff);
|
color: #fff;
|
&:active {
|
transform: scale(0.96);
|
}
|
}
|
&.approve {
|
background: linear-gradient(135deg, #34c759, #30b350);
|
color: #fff;
|
box-shadow: 0 4rpx 16rpx rgba(52, 199, 89, 0.35);
|
&:active {
|
transform: scale(0.96);
|
box-shadow: 0 2rpx 8rpx rgba(52, 199, 89, 0.25);
|
}
|
}
|
}
|
|
/* ========== 确认底部弹出面板 ========== */
|
.approve-mask {
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: rgba(0, 0, 0, 0.45);
|
z-index: 999;
|
animation: fadeIn 0.25s ease;
|
}
|
|
.approve-panel {
|
position: fixed;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: #ffffff;
|
border-radius: 32rpx 32rpx 0 0;
|
z-index: 1000;
|
max-height: 82vh;
|
display: flex;
|
flex-direction: column;
|
transform: translateY(100%);
|
transition: transform 0.35s cubic-bezier(0.32, 0.94, 0.6, 1);
|
box-shadow: 0 -8rpx 40rpx rgba(0, 0, 0, 0.12);
|
|
&.panel-show {
|
transform: translateY(0);
|
}
|
}
|
|
.panel-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 36rpx 32rpx 20rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
flex-shrink: 0;
|
}
|
|
.panel-title {
|
font-size: 34rpx;
|
font-weight: 700;
|
color: #1d1d1f;
|
}
|
|
.panel-close {
|
width: 50rpx;
|
height: 50rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 32rpx;
|
color: #8e8e93;
|
background: #f5f5f7;
|
border-radius: 50%;
|
&:active {
|
background: #e5e5e7;
|
}
|
}
|
|
.panel-body {
|
flex: 1;
|
padding: 24rpx 32rpx 20rpx;
|
overflow-y: auto;
|
-webkit-overflow-scrolling: touch;
|
}
|
|
/* 案例摘要卡片 */
|
.summary-card {
|
background: #f8f9fc;
|
border-radius: 16rpx;
|
padding: 20rpx 24rpx;
|
margin-bottom: 28rpx;
|
border: 1rpx solid #e8ecf0;
|
}
|
|
.summary-row {
|
display: flex;
|
align-items: baseline;
|
padding: 8rpx 0;
|
|
.s-label {
|
font-size: 24rpx;
|
color: #8e8e93;
|
width: 130rpx;
|
flex-shrink: 0;
|
}
|
.s-value {
|
font-size: 26rpx;
|
color: #1d1d1f;
|
font-weight: 500;
|
word-break: break-all;
|
}
|
}
|
|
/* 表单组 */
|
.form-group {
|
margin-bottom: 28rpx;
|
}
|
|
.form-label {
|
display: block;
|
font-size: 28rpx;
|
font-weight: 600;
|
color: #1d1d1f;
|
margin-bottom: 16rpx;
|
|
.required {
|
color: #ff3b30;
|
margin-left: 4rpx;
|
}
|
}
|
|
/* 单选组 - 水平排列 */
|
.radio-group {
|
display: flex;
|
gap: 16rpx;
|
flex-wrap: wrap;
|
|
&.vertical {
|
flex-direction: column;
|
gap: 12rpx;
|
}
|
}
|
|
.radio-item {
|
display: flex;
|
align-items: center;
|
padding: 16rpx 24rpx;
|
border-radius: 12rpx;
|
border: 2rpx solid #e0e0e0;
|
background: #fafafa;
|
transition: all 0.2s ease;
|
|
&.active {
|
border-color: #007aff;
|
background: #f0f7ff;
|
|
.radio-text {
|
color: #007aff;
|
font-weight: 600;
|
}
|
}
|
|
&:active {
|
transform: scale(0.97);
|
}
|
}
|
|
.radio-icon {
|
width: 38rpx;
|
height: 38rpx;
|
border-radius: 19rpx;
|
border: 2rpx solid #c8c8cc;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 22rpx;
|
color: transparent;
|
margin-right: 12rpx;
|
flex-shrink: 0;
|
transition: all 0.2s ease;
|
|
&.checked {
|
background: #007aff;
|
border-color: #007aff;
|
color: #ffffff;
|
}
|
}
|
|
.radio-text {
|
font-size: 26rpx;
|
color: #3a3a3c;
|
white-space: nowrap;
|
}
|
|
/* 文本域 */
|
.form-textarea {
|
width: 92%;
|
min-height: 170rpx;
|
padding: 20rpx 24rpx;
|
border-radius: 14rpx;
|
border: 2rpx solid #e0e0e0;
|
background: #fafafa;
|
font-size: 26rpx;
|
color: #1d1d1f;
|
line-height: 1.6;
|
resize: none;
|
|
&:focus {
|
border-color: #007aff;
|
background: #ffffff;
|
outline: none;
|
}
|
|
&::placeholder {
|
color: #aeaeb2;
|
}
|
}
|
|
.word-count {
|
display: block;
|
text-align: right;
|
font-size: 22rpx;
|
color: #aeaeb2;
|
margin-top: 8rpx;
|
padding-right: 8rpx;
|
}
|
|
/* 底部按钮 */
|
.panel-footer {
|
display: flex;
|
gap: 20rpx;
|
padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom));
|
border-top: 1rpx solid #f0f0f0;
|
flex-shrink: 0;
|
}
|
|
.panel-btn {
|
flex: 1;
|
height: 84rpx;
|
border-radius: 42rpx;
|
font-size: 30rpx;
|
font-weight: 700;
|
border: none;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
letter-spacing: 1rpx;
|
|
&.cancel {
|
background: #f5f5f7;
|
color: #1d1d1f;
|
&:active {
|
background: #e5e5e7;
|
}
|
}
|
|
&.submit {
|
background: linear-gradient(90deg, #0071e3, #2997ff);
|
color: #fff;
|
box-shadow: 0 4rpx 16rpx rgba(0, 113, 227, 0.3);
|
&:active {
|
transform: scale(0.97);
|
box-shadow: 0 2rpx 8rpx rgba(0, 113, 227, 0.2);
|
}
|
}
|
}
|
|
/* ========== 动画 ========== */
|
.fade-in-up {
|
opacity: 0;
|
transform: translateY(30rpx);
|
animation: fadeInUp 0.4s ease forwards;
|
}
|
|
@keyframes fadeInUp {
|
to {
|
opacity: 1;
|
transform: translateY(0);
|
}
|
}
|
|
@keyframes fadeIn {
|
from {
|
opacity: 0;
|
}
|
to {
|
opacity: 1;
|
}
|
}
|
</style>
|