| | |
| | | <view class="case-header"> |
| | | <view class="hospital-info"> |
| | | <image |
| | | :src="caseDetail.hospitalLogo" |
| | | :src="caseDetail.hospitalLogo || '/static/hospital-default.png'" |
| | | mode="aspectFit" |
| | | class="hospital-logo" |
| | | /> |
| | |
| | | <text |
| | | v-for="tab in tabs" |
| | | :key="tab.id" |
| | | :class="{ active: activeTab === tab.id }" |
| | | :class="{ active: activeTab == tab.id }" |
| | | @tap="switchTab(tab.id)" |
| | | class="tab-item" |
| | | > |
| | |
| | | </text> |
| | | </view> |
| | | |
| | | <!-- 捐献者基本信息 --> |
| | | <view v-if="activeTab === 'basic'" class="info-section fade-in-up"> |
| | | <!-- ==================== 基本信息 ==================== --> |
| | | <view v-if="activeTab == 'basic'" class="info-section fade-in-up"> |
| | | <view class="section-card"> |
| | | <view class="section-header"> |
| | | <text class="section-title">捐献者基本信息</text> |
| | | <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">{{ getGenderText(caseDetail.sex) }}</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" }} |
| | | {{ getAgeUnitText(caseDetail.ageunit) }}</text |
| | | {{ 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">{{ |
| | | getBloodTypeText(caseDetail.bloodType) |
| | | }}</text> |
| | | </view> |
| | | |
| | | <!-- 第三行:民族、国籍、籍贯 --> |
| | | <view class="info-item"> |
| | | <text class="label">民族</text> |
| | | <text class="value">{{ caseDetail.nation || "未填写" }}</text> |
| | | <text class="value">{{ |
| | | getDictLabel("sys_Nation", caseDetail.nation) |
| | | }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">国籍</text> |
| | |
| | | <text class="label">籍贯</text> |
| | | <text class="value">{{ caseDetail.nativeplace || "未填写" }}</text> |
| | | </view> |
| | | |
| | | <!-- 第四行:学历、职业 --> |
| | | <view class="info-item"> |
| | | <text class="label">学历</text> |
| | | <text class="value">{{ caseDetail.education || "未填写" }}</text> |
| | | <text class="value">{{ |
| | | getDictLabel("sys_education", caseDetail.education) |
| | | }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">职业</text> |
| | | <text class="value">{{ caseDetail.occupation || "未填写" }}</text> |
| | | <text class="value">{{ |
| | | getDictLabel("sys_occupation", caseDetail.occupation) |
| | | }}</text> |
| | | </view> |
| | | <view class="info-item full-width"> |
| | | |
| | | <!-- 第五行:联系电话 --> |
| | | <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"> |
| | | <view class="info-item full-width address-item"> |
| | | <text class="label">现住地址</text> |
| | | <text class="value">{{ |
| | | getFullResidenceAddress() || "未填写" |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 医疗信息 --> |
| | | <view v-if="activeTab === 'medical'" class="info-section fade-in-up"> |
| | | <!-- ==================== 医疗信息 ==================== --> |
| | | <view v-if="activeTab == 'medical'" class="info-section fade-in-up"> |
| | | <view class="section-card"> |
| | | <view class="section-header"> |
| | | <text class="section-title">医疗信息</text> |
| | | <text class="section-title">🏥 医疗信息</text> |
| | | </view> |
| | | <view class="info-content"> |
| | | <view class="info-group"> |
| | | <text class="group-title">疾病诊断</text> |
| | | <text class="group-content">{{ |
| | | <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-group"> |
| | | <text class="group-title">病情概况</text> |
| | | <text class="group-content">{{ |
| | | |
| | | <!-- 病情概况:占据整行 --> |
| | | <view class="info-item full-width"> |
| | | <text class="label">病情概况</text> |
| | | <text class="value">{{ |
| | | caseDetail.illnessoverview || "未填写" |
| | | }}</text> |
| | | </view> |
| | | <view class="info-group"> |
| | | <text class="group-title">病人状况</text> |
| | | <text class="group-content">{{ |
| | | caseDetail.patientstate || "未填写" |
| | | }}</text> |
| | | </view> |
| | | <view class="info-group"> |
| | | <text class="group-title">住院号</text> |
| | | <text class="group-content">{{ |
| | | caseDetail.inpatientno || "未填写" |
| | | }}</text> |
| | | </view> |
| | | <view class="info-group"> |
| | | <text class="group-title">GCS评分</text> |
| | | <text class="group-content">{{ |
| | | caseDetail.gcsScore || "未评估" |
| | | }}</text> |
| | | </view> |
| | | <view class="info-group"> |
| | | <text class="group-title">传染病情况</text> |
| | | <text class="group-content" |
| | | >{{ caseDetail.infectious || "无" |
| | | }}{{ |
| | | caseDetail.infectiousOther |
| | | ? `(${caseDetail.infectiousOther})` |
| | | : "" |
| | | }}</text |
| | | > |
| | | </view> |
| | | <view class="info-group"> |
| | | <text class="group-title">Rh阴性</text> |
| | | <text class="group-content">{{ |
| | | caseDetail.rhYin === "1" ? "是" : "否" |
| | | }}</text> |
| | | |
| | | <!-- 病人状况:占据整行 --> |
| | | <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 v-if="activeTab == 'contact'" class="info-section fade-in-up"> |
| | | <view class="section-card"> |
| | | <view class="section-header"> |
| | | <text class="section-title">医院与联系信息</text> |
| | | <text class="section-title">📞 医院与联系信息</text> |
| | | </view> |
| | | <view class="info-grid"> |
| | | <!-- 医院信息 --> |
| | | <view class="info-item full-width"> |
| | | <text class="label">治疗医院</text> |
| | | <text class="value">{{ |
| | |
| | | <text class="label">上报医院</text> |
| | | <text class="value">{{ caseDetail.toHospital || "未填写" }}</text> |
| | | </view> |
| | | <view class="info-item full-width"> |
| | | <text class="label">联系电话</text> |
| | | <text class="value">{{ caseDetail.phone || "未填写" }}</text> |
| | | </view> |
| | | |
| | | <!-- 联系人信息 --> |
| | | <view class="info-item"> |
| | | <text class="label">信息员</text> |
| | | <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"> |
| | | |
| | | <!-- <view class="info-item"> |
| | | <text class="label">ICU评估医生</text> |
| | | <text class="value">{{ caseDetail.icuDoctor || "未填写" }}</text> |
| | | </view> |
| | |
| | | <text class="value">{{ |
| | | caseDetail.icuDoctorPhone || "未填写" |
| | | }}</text> |
| | | </view> |
| | | </view> --> |
| | | |
| | | <view class="info-item"> |
| | | <text class="label">协调员</text> |
| | | <text class="value">{{ |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 转运信息 --> |
| | | <view v-if="activeTab === 'transport'" class="info-section fade-in-up"> |
| | | <!-- ==================== 转运信息 ==================== --> |
| | | <view v-if="activeTab == 'transport'" class="info-section fade-in-up"> |
| | | <view class="section-card"> |
| | | <view class="section-header"> |
| | | <view |
| | |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | width: 100%; |
| | | " |
| | | > |
| | | <text class="section-title">转运信息</text> |
| | | <!-- 转运操作按钮 --> |
| | | <text class="section-title">🚑 转运信息</text> |
| | | <view |
| | | v-if="caseDetail.reportStatus === '3'" |
| | | style="display: flex; gap: 20rpx" |
| | | v-if="caseDetail.reportStatus == '3'" |
| | | style="display: flex; gap: 16rpx" |
| | | > |
| | | <button |
| | | v-if="caseDetail.isTransport === '2' && !hasTransport" |
| | | v-if="caseDetail.isTransport == '2' && !hasTransport" |
| | | class="small-btn primary" |
| | | @tap.stop="createTransport" |
| | | > |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="info-content"> |
| | | <view class="info-group"> |
| | | <text class="group-title">是否需要转运</text> |
| | | <text class="group-content">{{ |
| | | caseDetail.isTransport === "2" ? "需要" : "不需要" |
| | | <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 |
| | | v-if="hasTransport && caseDetail.serviceTransport" |
| | | class="transport-info" |
| | | class="transport-item" |
| | | v-for="transport in caseDetail.serviceTransport" |
| | | :key="transport.id" |
| | | > |
| | | <view |
| | | class="info-group" |
| | | v-for="transport in caseDetail.serviceTransport" |
| | | :key="transport.id" |
| | | > |
| | | <view class="sub-group"> |
| | | <text class="sub-label">转运单号:</text> |
| | | <text class="sub-value">{{ |
| | | transport.reportId || 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="sub-group"> |
| | | <text class="sub-label">出发地点:</text> |
| | | <text class="sub-value">{{ |
| | | transport.transportStartPlace |
| | | }}</text> |
| | | </view> |
| | | <view class="sub-group"> |
| | | <text class="sub-label">出发时间:</text> |
| | | <text class="sub-value">{{ |
| | | <view class="transport-row"> |
| | | <text class="t-label">出发时间</text> |
| | | <text class="t-value">{{ |
| | | formatDateTime(transport.transportStartTime) |
| | | }}</text> |
| | | </view> |
| | | <view class="sub-group"> |
| | | <text class="sub-label">负责协调员:</text> |
| | | <text class="sub-value">{{ transport.contactPerson }}</text> |
| | | <view class="transport-row"> |
| | | <text class="t-label">负责协调员</text> |
| | | <text class="t-value">{{ |
| | | transport.contactPerson || "未填写" |
| | | }}</text> |
| | | </view> |
| | | <view class="sub-group"> |
| | | <text class="sub-label">转运状态:</text> |
| | | <text |
| | | class="sub-value status" |
| | | :class="getTransportStatusClass(transport.transitStatus)" |
| | | > |
| | | {{ getTransportStatusText(transport.transitStatus) }} |
| | | </text> |
| | | <view v-if="transport.doctor" class="transport-row"> |
| | | <text class="t-label">出诊医生</text> |
| | | <text class="t-value">{{ transport.doctor }}</text> |
| | | </view> |
| | | <view class="sub-group" v-if="transport.doctor"> |
| | | <text class="sub-label">急诊科医生:</text> |
| | | <text class="sub-value">{{ transport.doctor }}</text> |
| | | <view v-if="transport.nurse" class="transport-row"> |
| | | <text class="t-label">出诊护士</text> |
| | | <text class="t-value">{{ transport.nurse }}</text> |
| | | </view> |
| | | <view class="sub-group" v-if="transport.nurse"> |
| | | <text class="sub-label">护士:</text> |
| | | <text class="sub-value">{{ transport.nurse }}</text> |
| | | <view v-if="transport.driver" class="transport-row"> |
| | | <text class="t-label">驾驶员</text> |
| | | <text class="t-value">{{ transport.driver }}</text> |
| | | </view> |
| | | <view class="sub-group" v-if="transport.driver"> |
| | | <text class="sub-label">驾驶员:</text> |
| | | <text class="sub-value">{{ transport.driver }}</text> |
| | | </view> |
| | | <view class="sub-group" v-if="transport.icuDoctor"> |
| | | <text class="sub-label">ICU评估医生:</text> |
| | | <text class="sub-value">{{ transport.icuDoctor }}</text> |
| | | <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="transport-info" |
| | | <!-- 无转运信息但需要转运 --> |
| | | <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" |
| | | > |
| | | <view class="empty-transport"> |
| | | <text>该案例需要转运,但尚未创建转运单</text> |
| | | <text |
| | | v-if="caseDetail.reportStatus !== '3'" |
| | | style="color: #f0ad4e; font-size: 24rpx; margin-top: 10rpx" |
| | | > |
| | | 需先审批同意才能创建转运单 |
| | | </text> |
| | | </view> |
| | | </view> |
| | | 需先审批同意才能创建转运单 |
| | | </text> |
| | | </view> |
| | | |
| | | <!-- 无需转运 --> |
| | | <view v-else class="transport-info"> |
| | | <view class="empty-transport"> |
| | | <text>该案例无需转运</text> |
| | | </view> |
| | | </view> |
| | | <!-- 无需转运 --> |
| | | <view v-else class="empty-transport"> |
| | | <text>该案例无需转运</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 操作按钮 --> |
| | | <view class="action-bar"> |
| | | <button class="action-btn secondary" @tap="goBack">返回</button> |
| | | |
| | | <!-- 根据状态显示不同操作 --> |
| | | <button |
| | | v-if=" |
| | | caseDetail.reportStatus === '1' || caseDetail.reportStatus === '2' |
| | | " |
| | | class="action-btn primary" |
| | | @tap="handleEdit" |
| | | > |
| | | 编辑案例 |
| | | </button> |
| | | |
| | | <!-- <button v-if="canDelete" |
| | | class="action-btn error" |
| | | @tap="handleDelete"> |
| | | 删除案例 |
| | | </button> --> |
| | | </view> |
| | | <!-- 附件 --> |
| | | <attachment-upload |
| | | ref="attachment" |
| | |
| | | :maxCount="5" |
| | | @preview="handlePreview" |
| | | /> |
| | | <!-- <view v-if="showHomeButton" class="home-btn" @click="goHome">首页</view> --> |
| | | |
| | | <!-- 操作按钮 --> |
| | | <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> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import { useDict } from "@/utils/dict"; |
| | | import { onLoad, onShow } from "@dcloudio/uni-app"; |
| | | import { useHomeButton } from "@/stores/useHomeButton"; |
| | | import attachmentUpload from "@/components/attachment"; |
| | | const { showHomeButton, checkAutoLogin, goHome } = useHomeButton(); |
| | | // 字典数据 |
| | | const dict = ref({}); |
| | | 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: "contact", label: "联系信息" }, |
| | | { id: "transport", label: "转运信息" }, |
| | | ]); |
| | | const attachments = ref([]); |
| | | |
| | | // 计算属性 |
| | | const hasTransport = computed(() => { |
| | | return caseDetail.value.serviceTransport; |
| | | }); |
| | | const hasTransport = computed( |
| | | () => |
| | | caseDetail.value.serviceTransport && |
| | | caseDetail.value.serviceTransport.length > 0, |
| | | ); |
| | | |
| | | const canDelete = computed(() => { |
| | | // 允许删除未审批或已驳回的案例 |
| | | return ( |
| | | caseDetail.value.reportStatus === "1" || |
| | | caseDetail.value.reportStatus === "2" || |
| | | caseDetail.value.reportStatus === "4" |
| | | ); |
| | | // ==================== 生命周期 ==================== |
| | | onShow(() => { |
| | | if (caseId.value) { |
| | | loadCaseDetail(caseId.value); |
| | | } |
| | | }); |
| | | |
| | | onLoad(async (options) => { |
| | | checkAutoLogin(options); |
| | | if (options.id) { |
| | | caseId.value = options.id; |
| | | // 获取字典数据 |
| | | dict.value = await useDict( |
| | | "sys_user_sex", |
| | | "sys_BloodType", |
| | | "sys_IDType", |
| | | "sys_AgeUnit", |
| | | ); |
| | | // 加载案例详情 |
| | | 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; |
| | | attachments.value.forEach((item) => { |
| | | item.url = item.path; |
| | | item.name = item.fileName; |
| | | }); |
| | | attachments.value = res.data.annexfilesList.map((item) => ({ |
| | | ...item, |
| | | url: item.path, |
| | | name: item.fileName, |
| | | })); |
| | | } |
| | | // 如果状态是"已上报"(1),自动更新为"已阅读"(2) |
| | | // if (caseDetail.value.reportStatus === '1') { |
| | | // await updateCaseStatus('2') |
| | | // } |
| | | } else { |
| | | throw new Error(res.msg || "数据加载失败"); |
| | | } |
| | | } catch (error) { |
| | | console.error("加载案例详情失败:", error); |
| | | uni.showToast({ |
| | | title: "数据加载失败,请重试", |
| | | icon: "none", |
| | | }); |
| | | uni.showToast({ title: "数据加载失败,请重试", icon: "none" }); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | // 预览文件 - 修改为使用完整URL |
| | | const handlePreview = (file) => { |
| | | const fullUrl = file.url.startsWith("http") |
| | | ? file.url |
| | | : baseUrlHt + (file.url.startsWith("/") ? "" : "/") + file.url; |
| | | |
| | | if (file.type.includes("image")) { |
| | | uni.previewImage({ |
| | | urls: attachments.value |
| | | .filter((f) => f.type.includes("image")) |
| | | .map((f) => |
| | | f.url.startsWith("http") |
| | | ? f.url |
| | | : baseUrlHt + (f.url.startsWith("/") ? "" : "/") + f.url, |
| | | ), |
| | | current: fullUrl, |
| | | }); |
| | | } else if (file.type.includes("pdf")) { |
| | | uni.downloadFile({ |
| | | url: fullUrl, |
| | | success: (res) => { |
| | | uni.openDocument({ |
| | | filePath: res.tempFilePath, |
| | | fileType: "pdf", |
| | | showMenu: true, |
| | | }); |
| | | }, |
| | | fail: (err) => { |
| | | console.error("打开文档失败:", err); |
| | | uni.showToast({ title: "打开文件失败", icon: "none" }); |
| | | }, |
| | | }); |
| | | } else { |
| | | uni.showToast({ title: "暂不支持此文件类型预览", icon: "none" }); |
| | | } |
| | | }; |
| | | // 更新案例状态 |
| | | const updateCaseStatus = async (newStatus) => { |
| | | try { |
| | | const updateData = { |
| | | ...caseDetail.value, |
| | | reportStatus: newStatus, |
| | | updateTime: new Date().toISOString().replace("T", " ").substring(0, 19), |
| | | updateBy: "移动端用户", |
| | | }; |
| | | |
| | | const res = await uni.$uapi.post( |
| | | "/project/donatebaseinforeport/edit", |
| | | updateData, |
| | | ); |
| | | |
| | | if (res.code === 200) { |
| | | caseDetail.value.reportStatus = newStatus; |
| | | } |
| | | } catch (error) { |
| | | console.error("更新状态失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 获取状态样式 |
| | | 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 getGenderText = (gender) => { |
| | | if (!dict.value.sys_user_sex) return gender; |
| | | const genderItem = dict.value.sys_user_sex.find( |
| | | (item) => item.dictValue === gender, |
| | | ); |
| | | return genderItem ? genderItem.dictLabel : gender; |
| | | }; |
| | | |
| | | // 获取血型文本 |
| | | const getBloodTypeText = (bloodType) => { |
| | | if (!dict.value.sys_BloodType) return bloodType; |
| | | const bloodTypeItem = dict.value.sys_BloodType.find( |
| | | (item) => item.dictValue === bloodType, |
| | | ); |
| | | return bloodTypeItem ? bloodTypeItem.dictLabel : bloodType; |
| | | }; |
| | | |
| | | // 获取年龄单位文本 |
| | | const getAgeUnitText = (ageunit) => { |
| | | if (!ageunit) return ""; |
| | | const unitMap = { |
| | | year: "岁", |
| | | month: "个月", |
| | | day: "天", |
| | | }; |
| | | return unitMap[ageunit] || ageunit; |
| | | }; |
| | | |
| | | // 获取完整户籍地址 |
| | | // ==================== 地址拼接 ==================== |
| | | const getFullRegisterAddress = () => { |
| | | const { |
| | | registerprovincename, |
| | | registercityname, |
| | | registertownname, |
| | | registercommunityname, |
| | | registeraddress, |
| | | } = caseDetail.value; |
| | | |
| | | const parts = [ |
| | | registerprovincename, |
| | | registercityname, |
| | | registertownname, |
| | | registercommunityname, |
| | | registeraddress, |
| | | ]; |
| | | return parts.filter((part) => part).join(""); |
| | | return parts.filter((p) => p && p.trim()).join(""); |
| | | }; |
| | | |
| | | // 获取完整现住地址 |
| | | const getFullResidenceAddress = () => { |
| | | const { |
| | | residenceprovincename, |
| | | residencecountyname, |
| | | residencecityname, |
| | | residencetownname, |
| | | residencecommunityname, |
| | | residenceaddress, |
| | | } = caseDetail.value; |
| | | |
| | | const parts = [ |
| | | residenceprovincename, |
| | | residencecountyname, |
| | | residencecityname, |
| | | residencetownname, |
| | | residencecommunityname, |
| | | residenceaddress, |
| | | ]; |
| | | return parts.filter((part) => part).join(""); |
| | | 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: "待转运", |
| | |
| | | return map[status] || "未知"; |
| | | }; |
| | | |
| | | // 获取转运状态样式 |
| | | const getTransportStatusClass = (status) => { |
| | | const map = { |
| | | 1: "pending", |
| | |
| | | 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" }); |
| | |
| | | } |
| | | }; |
| | | |
| | | // 编辑案例 |
| | | const handleEdit = () => { |
| | | uni.navigateTo({ |
| | | url: `/pagesSub/case/CaseDetails?id=${caseId.value}&edit=true`, |
| | | }); |
| | | }; |
| | | |
| | | // 删除案例 |
| | | const handleDelete = () => { |
| | | uni.showModal({ |
| | | title: "确认删除", |
| | | content: `确定要删除案例 ${caseDetail.value.caseNo} 吗?`, |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | try { |
| | | const result = await uni.$uapi.delete( |
| | | `/project/donatebaseinforeport/${caseId.value}`, |
| | | ); |
| | | if (result.code === 200) { |
| | | uni.showToast({ title: "删除成功", icon: "success" }); |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | } else { |
| | | uni.showToast({ title: result.msg || "删除失败", icon: "none" }); |
| | | } |
| | | } catch (error) { |
| | | console.error("删除失败:", error); |
| | | uni.showToast({ title: "删除失败", icon: "none" }); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // 创建转运单 |
| | | 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 && caseDetail.value.serviceTransport[0]) { |
| | | if (hasTransport.value) { |
| | | const transport = caseDetail.value.serviceTransport[0]; |
| | | uni.navigateTo({ |
| | | url: `/pages/transport/detail?id=${transport.id}`, |
| | | 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: linear-gradient(135deg, #fafdff 0%, #e3f0ff 100%); |
| | | background: #f5f7fa; |
| | | padding: 20rpx; |
| | | padding-bottom: 120rpx; /* 为操作栏留出空间 */ |
| | | padding-bottom: 180rpx; /* 增加底部间距给操作栏 */ |
| | | } |
| | | |
| | | /* ========== 头部卡片 ========== */ |
| | | .header-card { |
| | | background: linear-gradient(135deg, #fff 60%, #f5f6fa 100%); |
| | | border-radius: 32rpx; |
| | | padding: 40rpx 30rpx; |
| | | margin-bottom: 30rpx; |
| | | box-shadow: 0 12px 48px 0 rgba(0, 113, 227, 0.1); |
| | | border: 1.5px solid #e5eaf0; |
| | | 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: 30rpx; |
| | | margin-bottom: 24rpx; |
| | | } |
| | | |
| | | .hospital-info { |
| | | display: flex; |
| | | align-items: center; |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .hospital-logo { |
| | | width: 80rpx; |
| | | height: 80rpx; |
| | | width: 72rpx; |
| | | height: 72rpx; |
| | | border-radius: 16rpx; |
| | | margin-right: 20rpx; |
| | | background: #f5f5f7; |
| | | margin-right: 16rpx; |
| | | background: #f0f2f5; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .hospital-details { |
| | | display: flex; |
| | | flex-direction: column; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .hospital-name { |
| | | font-size: 32rpx; |
| | | font-size: 30rpx; |
| | | font-weight: 600; |
| | | color: #1d1d1f; |
| | | margin-bottom: 8rpx; |
| | | max-width: 400rpx; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .case-type { |
| | | font-size: 24rpx; |
| | | color: #86868b; |
| | | font-size: 22rpx; |
| | | color: #8e8e93; |
| | | margin-top: 4rpx; |
| | | } |
| | | |
| | | .case-status { |
| | | padding: 8rpx 20rpx; |
| | | padding: 6rpx 18rpx; |
| | | border-radius: 20rpx; |
| | | font-size: 24rpx; |
| | | font-size: 22rpx; |
| | | font-weight: 500; |
| | | |
| | | flex-shrink: 0; |
| | | &.reported { |
| | | background: rgba(255, 149, 0, 0.1); |
| | | background: rgba(255, 149, 0, 0.12); |
| | | color: #ff9500; |
| | | } |
| | | |
| | | &.read { |
| | | background: rgba(0, 122, 255, 0.1); |
| | | background: rgba(0, 122, 255, 0.12); |
| | | color: #007aff; |
| | | } |
| | | |
| | | &.agreed { |
| | | background: rgba(52, 199, 89, 0.1); |
| | | background: rgba(52, 199, 89, 0.12); |
| | | color: #34c759; |
| | | } |
| | | |
| | | &.rejected { |
| | | background: rgba(255, 59, 48, 0.1); |
| | | background: rgba(255, 59, 48, 0.12); |
| | | color: #ff3b30; |
| | | } |
| | | } |
| | |
| | | .info-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | gap: 20rpx; |
| | | } |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | } |
| | | |
| | | .label { |
| | | font-size: 24rpx; |
| | | color: #86868b; |
| | | margin-bottom: 8rpx; |
| | | font-size: 22rpx; |
| | | color: #8e8e93; |
| | | margin-bottom: 4rpx; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 28rpx; |
| | | font-size: 26rpx; |
| | | color: #1d1d1f; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | |
| | | /* ========== 选项卡 ========== */ |
| | | .tab-navigation { |
| | | display: flex; |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | padding: 8rpx; |
| | | margin-bottom: 30rpx; |
| | | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.05); |
| | | padding: 6rpx; |
| | | margin-bottom: 24rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04); |
| | | overflow-x: auto; |
| | | white-space: nowrap; |
| | | |
| | | &::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | |
| | | |
| | | .tab-item { |
| | | flex: 1; |
| | | min-width: 140rpx; |
| | | min-width: 120rpx; |
| | | text-align: center; |
| | | padding: 20rpx; |
| | | font-size: 28rpx; |
| | | color: #86868b; |
| | | padding: 16rpx 10rpx; |
| | | font-size: 26rpx; |
| | | color: #8e8e93; |
| | | border-radius: 12rpx; |
| | | transition: all 0.3s ease; |
| | | |
| | | &.active { |
| | | color: #007aff; |
| | | background: #f0f7ff; |
| | | border-radius: 12rpx; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | |
| | | /* ========== 内容卡片 ========== */ |
| | | .section-card { |
| | | background: linear-gradient(135deg, #fff 60%, #f5f6fa 100%); |
| | | border-radius: 32rpx; |
| | | padding: 40rpx 30rpx; |
| | | margin-bottom: 30rpx; |
| | | box-shadow: 0 12px 48px 0 rgba(0, 113, 227, 0.1); |
| | | border: 1.5px solid #e5eaf0; |
| | | 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: 30rpx; |
| | | margin-bottom: 24rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 32rpx; |
| | | font-size: 30rpx; |
| | | font-weight: 600; |
| | | background: linear-gradient(90deg, #0071e3 0%, #2997ff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | color: #0071e3; |
| | | color: #1d1d1f; |
| | | } |
| | | |
| | | /* ========== 信息网格 ========== */ |
| | | .info-grid { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1fr; |
| | | gap: 24rpx; |
| | | 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: 24rpx; |
| | | color: #86868b; |
| | | margin-bottom: 8rpx; |
| | | font-size: 22rpx; |
| | | color: #8e8e93; |
| | | margin-bottom: 4rpx; |
| | | font-weight: 400; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 28rpx; |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | .info-content { |
| | | .info-group { |
| | | margin-bottom: 32rpx; |
| | | padding-bottom: 20rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | /* 表单组 */ |
| | | .form-group { |
| | | margin-bottom: 28rpx; |
| | | } |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | padding-bottom: 0; |
| | | border-bottom: none; |
| | | } |
| | | } |
| | | .form-label { |
| | | display: block; |
| | | font-size: 28rpx; |
| | | font-weight: 600; |
| | | color: #1d1d1f; |
| | | margin-bottom: 16rpx; |
| | | |
| | | .group-title { |
| | | font-size: 26rpx; |
| | | color: #86868b; |
| | | margin-bottom: 12rpx; |
| | | display: block; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .group-content { |
| | | font-size: 28rpx; |
| | | color: #1d1d1f; |
| | | line-height: 1.6; |
| | | .required { |
| | | color: #ff3b30; |
| | | margin-left: 4rpx; |
| | | } |
| | | } |
| | | |
| | | /* 转运信息样式 */ |
| | | .transport-info { |
| | | .info-group { |
| | | border: 2rpx solid #f0f0f0; |
| | | border-radius: 12rpx; |
| | | padding: 20rpx; |
| | | margin-bottom: 20rpx; |
| | | background: #fafafa; |
| | | /* 单选组 - 水平排列 */ |
| | | .radio-group { |
| | | display: flex; |
| | | gap: 16rpx; |
| | | flex-wrap: wrap; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | .sub-group { |
| | | display: flex; |
| | | margin-bottom: 12rpx; |
| | | align-items: center; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | .sub-label { |
| | | font-size: 26rpx; |
| | | color: #606266; |
| | | min-width: 160rpx; |
| | | margin-right: 10rpx; |
| | | } |
| | | |
| | | .sub-value { |
| | | font-size: 26rpx; |
| | | color: #303133; |
| | | flex: 1; |
| | | |
| | | &.status { |
| | | padding: 4rpx 12rpx; |
| | | border-radius: 6rpx; |
| | | font-size: 24rpx; |
| | | |
| | | &.pending { |
| | | background: rgba(240, 173, 78, 0.1); |
| | | color: #f0ad4e; |
| | | } |
| | | |
| | | &.transporting { |
| | | background: rgba(0, 122, 255, 0.1); |
| | | color: #007aff; |
| | | } |
| | | |
| | | &.completed { |
| | | background: rgba(76, 217, 100, 0.1); |
| | | color: #4cd964; |
| | | } |
| | | |
| | | &.cancelled { |
| | | background: rgba(220, 223, 230, 0.1); |
| | | color: #dcdfe6; |
| | | } |
| | | |
| | | &.draft { |
| | | background: rgba(144, 147, 153, 0.1); |
| | | color: #909399; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .empty-transport { |
| | | text-align: center; |
| | | padding: 40rpx 0; |
| | | color: #909399; |
| | | font-size: 28rpx; |
| | | &.vertical { |
| | | flex-direction: column; |
| | | gap: 12rpx; |
| | | } |
| | | } |
| | | |
| | | /* 小型按钮 */ |
| | | .small-btn { |
| | | padding: 8rpx 20rpx; |
| | | font-size: 24rpx; |
| | | border-radius: 6rpx; |
| | | border: none; |
| | | .radio-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 16rpx 24rpx; |
| | | border-radius: 12rpx; |
| | | border: 2rpx solid #e0e0e0; |
| | | background: #fafafa; |
| | | transition: all 0.2s ease; |
| | | |
| | | &.primary { |
| | | background: #007aff; |
| | | color: #fff; |
| | | } |
| | | &.active { |
| | | border-color: #007aff; |
| | | background: #f0f7ff; |
| | | |
| | | &.secondary { |
| | | background: #ecf5ff; |
| | | color: #007aff; |
| | | border: 1rpx solid #007aff; |
| | | .radio-text { |
| | | color: #007aff; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | |
| | | &:active { |
| | | opacity: 0.8; |
| | | transform: scale(0.97); |
| | | } |
| | | } |
| | | |
| | | .action-bar { |
| | | .radio-icon { |
| | | width: 38rpx; |
| | | height: 38rpx; |
| | | border-radius: 19rpx; |
| | | border: 2rpx solid #c8c8cc; |
| | | display: flex; |
| | | gap: 20rpx; |
| | | padding: 20rpx 0; |
| | | margin-top: 40rpx; |
| | | background: #fff; |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | padding: 20rpx 30rpx; |
| | | padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); |
| | | box-shadow: 0 -2rpx 20rpx rgba(0, 0, 0, 0.08); |
| | | z-index: 9; |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | .action-btn { |
| | | flex: 1; |
| | | height: 80rpx; |
| | | border-radius: 20rpx; |
| | | font-size: 28rpx; |
| | | font-weight: 600; |
| | | border: none; |
| | | transition: all 0.3s ease; |
| | | .radio-text { |
| | | font-size: 26rpx; |
| | | color: #3a3a3c; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | &.secondary { |
| | | /* 文本域 */ |
| | | .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; |
| | | } |
| | | } |
| | | |
| | | &.primary { |
| | | background: linear-gradient(90deg, #0071e3 0%, #2997ff 100%); |
| | | &.submit { |
| | | background: linear-gradient(90deg, #0071e3, #2997ff); |
| | | color: #fff; |
| | | |
| | | box-shadow: 0 4rpx 16rpx rgba(0, 113, 227, 0.3); |
| | | &:active { |
| | | transform: scale(0.98); |
| | | } |
| | | } |
| | | |
| | | &.success { |
| | | background: linear-gradient(90deg, #34c759 0%, #4cd964 100%); |
| | | color: #fff; |
| | | |
| | | &:active { |
| | | transform: scale(0.98); |
| | | } |
| | | } |
| | | |
| | | &.error { |
| | | background: linear-gradient(90deg, #ff3b30 0%, #ff5a5a 100%); |
| | | color: #fff; |
| | | |
| | | &:active { |
| | | transform: scale(0.98); |
| | | transform: scale(0.97); |
| | | box-shadow: 0 2rpx 8rpx rgba(0, 113, 227, 0.2); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /* ========== 动画 ========== */ |
| | | .fade-in-up { |
| | | opacity: 0; |
| | | transform: translateY(40rpx); |
| | | animation: fadeInUp 0.6s ease forwards; |
| | | transform: translateY(30rpx); |
| | | animation: fadeInUp 0.4s ease forwards; |
| | | } |
| | | |
| | | @keyframes fadeInUp { |
| | |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | |
| | | @keyframes fadeIn { |
| | | from { |
| | | opacity: 0; |
| | | } |
| | | to { |
| | | opacity: 1; |
| | | } |
| | | } |
| | | </style> |