|
- {% extends "./layout.html" %}
-
- {% block title %}患者详情{% endblock %}
-
- {% block css %}
- <style>
- .detail-panel { background: #fff; border-radius: 8px; padding: 24px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); margin-bottom: 16px; }
- .detail-panel h3 { font-size: 16px; color: #303133; margin-bottom: 20px; padding-bottom: 12px; border-bottom: 1px solid #EBEEF5; }
- .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
- .info-item { display: flex; }
- .info-item .label { width: 100px; color: #909399; font-size: 14px; flex-shrink: 0; }
- .info-item .value { color: #303133; font-size: 14px; }
- .doc-images { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px; }
- .sign-doc-card { width: 200px; border: 1px solid #EBEEF5; border-radius: 8px; padding: 16px; text-align: center; cursor: pointer; transition: all 0.2s; }
- .sign-doc-card:hover { border-color: var(--el-color-primary); box-shadow: 0 2px 12px rgba(255,120,0,0.15); }
- .sign-img-wrap .el-image__inner { object-position: center bottom !important; }
- .timeline-list { padding: 0; list-style: none; }
- .timeline-list li { position: relative; padding: 0 0 20px 24px; border-left: 2px solid #EBEEF5; }
- .timeline-list li:last-child { border-left-color: transparent; padding-bottom: 0; }
- .timeline-list li::before { content: ''; position: absolute; left: -6px; top: 4px; width: 10px; height: 10px; border-radius: 50%; background: var(--el-color-primary); }
- .timeline-list li .time { font-size: 12px; color: #909399; margin-bottom: 4px; }
- .timeline-list li .desc { font-size: 14px; color: #303133; }
- .timeline-list li .subdesc { font-size: 13px; color: #606266; margin-top: 4px; }
- .timeline-list li .reason { font-size: 13px; color: #F56C6C; margin-top: 4px; }
- .fixed-bottom-bar { position: fixed; bottom: 0; left: 220px; right: 0; height: 64px; background: #fff; box-shadow: 0 -2px 8px rgba(0,0,0,0.08); display: flex; align-items: center; justify-content: center; gap: 16px; z-index: 10; padding: 0 24px; }
- .sample-photo-edit { position: relative; width: 80px; height: 80px; }
- .sample-photo-edit .el-image { width: 80px; height: 80px; border-radius: 6px; border: 1px solid #eee; }
- .sample-photo-edit .del { position: absolute; top: -6px; right: -6px; cursor: pointer; background: rgba(0,0,0,0.6); color: #fff; border-radius: 50%; width: 20px; height: 20px; display:flex; align-items:center; justify-content:center; font-size:12px; z-index:10; line-height:1; }
- </style>
- {% endblock %}
-
- {% block content %}
- <div id="detailApp" v-cloak>
- <div v-loading="loading" style="padding-bottom:80px;">
- <!-- 页头 -->
- <el-page-header @back="goBack" style="margin-bottom:16px;">
- <template #content>
- <span style="font-size:16px;font-weight:600;">患者详情</span>
- <el-tag v-if="patient.status === -1" type="info" size="small" style="margin-left:12px;">待提交</el-tag>
- <el-tag v-else-if="patient.status === 0" type="warning" size="small" style="margin-left:12px;">待审核</el-tag>
- <el-tag v-else-if="patient.status === 1" type="success" size="small" style="margin-left:12px;">审核通过</el-tag>
- <el-tag v-else-if="patient.status === 2" type="danger" size="small" style="margin-left:12px;">已驳回</el-tag>
- </template>
- </el-page-header>
-
- <!-- 基本信息 -->
- <div class="detail-panel">
- <h3>基本信息</h3>
- <div class="info-grid">
- <div class="info-item"><span class="label">患者编号:</span><span class="value">${ patient.patient_no }</span></div>
- <div class="info-item"><span class="label">姓名:</span><span class="value">${ patient.name }</span></div>
- <div class="info-item"><span class="label">性别:</span><span class="value">${ patient.gender }</span></div>
- <div class="info-item"><span class="label">手机号:</span><span class="value">${ patient.phone }</span></div>
- <div class="info-item"><span class="label">身份证号:</span><span class="value">${ patient.id_card }</span></div>
- <div class="info-item"><span class="label">出生日期:</span><span class="value">${ patient.birth_date }</span></div>
- <div class="info-item">
- <span class="label">所在地区:</span>
- <span class="value">${ patient.province_name } ${ patient.city_name } ${ patient.district_name }</span>
- </div>
- <div class="info-item"><span class="label">详细地址:</span><span class="value">${ patient.address }</span></div>
- <div class="info-item">
- <span class="label">瘤种:</span>
- <span class="value">
- <el-tag v-if="patient.tag" type="danger" size="small">${ patient.tag }</el-tag>
- <span v-else style="color:#999;">无</span>
- </span>
- </div>
- <div class="info-item"><span class="label">提交时间:</span><span class="value">${ patient.create_time }</span></div>
- <div class="info-item"><span class="label">医院名称:</span><span class="value">${ patient.hospital || '—' }</span></div>
- <div class="info-item">
- <span class="label">医院所在地:</span>
- <span class="value">${ hospitalRegionText || '—' }</span>
- </div>
- <div class="info-item"><span class="label">紧急联系人:</span><span class="value">${ patient.emergency_contact || '—' }</span></div>
- <div class="info-item"><span class="label">紧急联系电话:</span><span class="value">${ patient.emergency_phone || '—' }</span></div>
- <div class="info-item" v-if="patient.income_amount"><span class="label">年可支配收入:</span><span class="value">${ patient.income_amount } 元</span></div>
- <div class="info-item" v-if="patient.guardian_name"><span class="label">监护人姓名:</span><span class="value">${ patient.guardian_name }</span></div>
- <div class="info-item" v-if="patient.guardian_id_card"><span class="label">监护人身份证:</span><span class="value">${ patient.guardian_id_card }</span></div>
- <div class="info-item" v-if="patient.guardian_relation"><span class="label">与患者关系:</span><span class="value">${ patient.guardian_relation }</span></div>
- </div>
- </div>
-
- <!-- 实名认证照片 -->
- <div class="detail-panel" v-if="patient.id_card_front || patient.id_card_back || patient.photo">
- <h3>实名认证照片</h3>
- <p style="font-size:13px;color:#909399;margin-bottom:12px;">
- <template v-if="patient.id_card_type === 2">无证件儿童免冠照片</template>
- <template v-else>身份证人像面 / 国徽面照片</template>
- </p>
- <div class="doc-images">
- <div v-if="patient.id_card_front">
- <el-image :src="patient.id_card_front" fit="cover" :preview-src-list="authImageList"
- :initial-index="0" style="width:260px;height:170px;border-radius:8px;border:1px solid #EBEEF5;" />
- <div style="font-size:12px;color:#909399;text-align:center;margin-top:6px;">人像面</div>
- </div>
- <div v-if="patient.id_card_back">
- <el-image :src="patient.id_card_back" fit="cover" :preview-src-list="authImageList"
- :initial-index="patient.id_card_front ? 1 : 0" style="width:260px;height:170px;border-radius:8px;border:1px solid #EBEEF5;" />
- <div style="font-size:12px;color:#909399;text-align:center;margin-top:6px;">国徽面</div>
- </div>
- <div v-if="patient.photo">
- <el-image :src="patient.photo" fit="cover" :preview-src-list="[patient.photo]"
- :initial-index="0" style="width:170px;height:170px;border-radius:8px;border:1px solid #EBEEF5;" />
- <div style="font-size:12px;color:#909399;text-align:center;margin-top:6px;">免冠照片</div>
- </div>
- </div>
- </div>
-
- <!-- 上传资料 -->
- <div class="detail-panel">
- <h3>上传资料</h3>
- <p style="font-size:13px;color:#909399;margin-bottom:12px;">患者上传的检查报告单或出院诊断证明书</p>
- <div class="doc-images" v-if="patient.documents && patient.documents.length">
- <div v-for="(doc, idx) in patient.documents" :key="idx">
- <el-image :src="doc" fit="cover" :preview-src-list="patient.documents" :initial-index="idx"
- style="width:200px;height:140px;border-radius:8px;border:1px solid #EBEEF5;" />
- <div style="font-size:12px;color:#909399;text-align:center;margin-top:6px;">资料 ${ idx + 1 }</div>
- </div>
- </div>
- <el-empty v-else description="暂无上传资料" :image-size="60" />
- </div>
-
- <!-- 签字材料 -->
- <div class="detail-panel">
- <h3>签字材料</h3>
- <p style="font-size:13px;color:#909399;margin-bottom:16px;">患者签署的授权材料</p>
- <div style="display:flex;gap:20px;flex-wrap:wrap;">
- <div v-for="item in signDocs" :key="item.key" style="width:220px;">
- <div style="font-size:13px;color:#303133;font-weight:500;margin-bottom:8px;">${ item.label }</div>
- <template v-if="item.url && isImageUrl(item.url)">
- <el-image :src="item.url" fit="cover" :preview-src-list="signImageList" :initial-index="signImageList.indexOf(item.url)"
- class="sign-img-wrap" style="width:220px;height:160px;border-radius:8px;border:1px solid #EBEEF5;cursor:pointer;" />
- </template>
- <template v-else-if="item.url">
- <div class="sign-doc-card" @click="downloadSign(item.url, item.label)">
- <div style="font-size:36px;margin-bottom:8px;">📝</div>
- <div style="font-size:12px;color:#67C23A;margin-bottom:8px;">已签署</div>
- <div style="font-size:13px;color:var(--el-color-primary);font-weight:500;">⬇ 下载</div>
- </div>
- </template>
- <template v-else>
- <div style="width:220px;height:160px;border:1px dashed #DCDFE6;border-radius:8px;display:flex;align-items:center;justify-content:center;">
- <span style="font-size:13px;color:#909399;">未上传</span>
- </div>
- </template>
- </div>
- </div>
- </div>
-
- <!-- 送检信息 -->
- <div class="detail-panel">
- <h3>送检信息</h3>
- <div class="info-grid">
- <div class="info-item"><span class="label">状态:</span>
- <span class="value">
- <el-tag :type="sampleInfoStatusType(patient.sample_info_status)" size="small">
- ${ sampleInfoStatusText(patient.sample_info_status) }
- </el-tag>
- </span>
- </div>
- <div class="info-item"><span class="label">送检样本:</span>
- <span class="value">
- <template v-if="patient.sample_types && patient.sample_types.length">
- <el-tag v-for="st in patient.sample_types" :key="st" size="small" style="margin-right:4px;">${ st }</el-tag>
- </template>
- <span v-else style="color:#999;">未选择</span>
- </span>
- </div>
- <div class="info-item" v-if="patient.wax_return"><span class="label">样本需寄回:</span><span class="value">是</span></div>
- <div class="info-item" v-if="patient.return_name"><span class="label">收件人:</span><span class="value">${ patient.return_name }</span></div>
- <div class="info-item" v-if="patient.return_phone"><span class="label">收件电话:</span><span class="value">${ patient.return_phone }</span></div>
- <div class="info-item" v-if="patient.return_address"><span class="label">收件地址:</span>
- <span class="value">${ patient.return_province_name || '' } ${ patient.return_city_name || '' } ${ patient.return_district_name || '' } ${ patient.return_address }</span>
- </div>
- <div class="info-item" v-if="patient.report_email"><span class="label">报告邮箱:</span><span class="value">${ patient.report_email }</span></div>
- <div class="info-item" v-if="patient.sample_tracking_no"><span class="label">物流单号:</span><span class="value">${ patient.sample_tracking_no }</span></div>
- <div class="info-item" v-if="patient.sample_info_status == 2 && patient.return_tracking_no"><span class="label">回寄单号:</span><span class="value">${ patient.return_tracking_no }</span></div>
- <div class="info-item" v-if="patient.sample_info_status == 2 && patient.return_time"><span class="label">回寄时间:</span><span class="value">${ patient.return_time }</span></div>
- <div class="info-item" v-if="patient.sample_edit_reason"><span class="label">申请原因:</span><span class="value">${ patient.sample_edit_reason }</span></div>
- <div class="info-item" v-if="patient.sample_edit_apply_time"><span class="label">申请时间:</span><span class="value">${ patient.sample_edit_apply_time }</span></div>
- <div class="info-item" v-if="patient.sample_edit_reject_reason"><span class="label">驳回原因:</span><span class="value">${ patient.sample_edit_reject_reason }</span></div>
- <div class="info-item" v-if="patient.sample_edit_audit_time"><span class="label">处理时间:</span><span class="value">${ patient.sample_edit_audit_time }</span></div>
- </div>
- <div v-if="patient.sample_photos && patient.sample_photos.length" style="margin-top:16px;">
- <div style="font-size:13px;color:#909399;margin-bottom:8px;">送检单照片</div>
- <div class="doc-images">
- <div v-for="(photo, idx) in patient.sample_photos" :key="idx">
- <el-image :src="photo" fit="cover" :preview-src-list="patient.sample_photos" :initial-index="idx"
- style="width:200px;height:140px;border-radius:8px;border:1px solid #EBEEF5;" />
- </div>
- </div>
- </div>
- </div>
-
- <!-- 流转日志 -->
- <div class="detail-panel">
- <h3>流转日志</h3>
- <ul class="timeline-list" v-if="audits.length">
- <li v-for="(item, idx) in audits" :key="idx">
- <div class="time">${ item.create_time }</div>
- <div class="desc">${ item.title || item.content || '流转记录' }</div>
- <div class="subdesc" v-if="item.content && item.title">${ item.content }</div>
- <div class="reason" v-if="item.reason">${ item.action === 'reject' || item.action === 'sample_reject_edit' ? '驳回原因' : '原因' }:${ item.reason }</div>
- </li>
- </ul>
- <el-empty v-else description="暂无流转日志" :image-size="60" />
- </div>
- </div>
-
- <!-- 固定底部操作栏 -->
- <div class="fixed-bottom-bar">
- <el-button @click="goBack">返 回</el-button>
- <el-button v-if="(patient.status === 0 || patient.status === 2) && canAudit" type="success" @click="handleApprove">审核通过</el-button>
- <el-button v-if="(patient.status === 0 || patient.status === 1) && canAudit" type="danger" @click="showRejectDialog">驳 回</el-button>
- <el-button v-if="canEdit" type="primary" @click="showSampleEditDialog">编辑送检信息</el-button>
- <el-button v-if="patient.sample_info_status == 3 && canEdit" type="success" @click="approveSampleEdit">通过送检修改申请</el-button>
- <el-button v-if="patient.sample_info_status == 3 && canEdit" type="danger" @click="showRejectSampleEditDialog">驳回送检修改申请</el-button>
- <el-button v-if="patient.sample_info_status == 1 && canEdit" type="warning" @click="resetSampleInfo">重置送检信息编辑</el-button>
- <el-button v-if="patient.wax_return && (patient.sample_info_status == 1 || patient.sample_info_status == 2) && canEdit" type="primary" @click="showReturnTrackingDialog">
- ${ patient.sample_info_status == 2 ? '修改回寄物流单号' : '填写回寄物流单号' }
- </el-button>
- </div>
-
- <!-- 驳回弹窗 -->
- <el-dialog v-model="rejectVisible" title="驳回审核" width="540px" destroy-on-close :close-on-click-modal="false" :z-index="2000">
-
- <p style="font-weight:500;color:#303133;margin-bottom:12px;">请选择或填写驳回原因(必填):</p>
- <div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;">
- <el-check-tag v-for="(r, i) in commonReasons" :key="i" :checked="selectedReasons.includes(i)"
- @change="toggleReason(i)">${ r }</el-check-tag>
- </div>
- <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入驳回原因,或点击上方常见原因快速选择"></el-input>
- <div style="text-align:right;margin-top:20px;">
- <el-button @click="rejectVisible = false">取消</el-button>
- <el-button type="danger" @click="doReject" :loading="rejectSaving">确认驳回</el-button>
- </div>
- </el-dialog>
-
- <!-- 回寄物流单号弹窗 -->
- <el-dialog v-model="returnTrackingVisible" :title="patient.sample_info_status == 2 ? '修改回寄物流单号' : '填写回寄物流单号'" width="460px" destroy-on-close :close-on-click-modal="false" :z-index="2000">
- <el-form label-width="110px">
- <el-form-item label="回寄物流单号" required>
- <el-input v-model="returnTrackingNo" placeholder="请输入回寄物流单号" maxlength="100" clearable></el-input>
- </el-form-item>
- </el-form>
- <div style="text-align:right;margin-top:20px;">
- <el-button @click="returnTrackingVisible = false">取消</el-button>
- <el-button type="primary" @click="saveReturnTrackingNo" :loading="returnTrackingSaving">保存</el-button>
- </div>
- </el-dialog>
-
- <!-- 驳回送检修改申请弹窗 -->
- <el-dialog v-model="sampleRejectVisible" title="驳回送检修改申请" width="520px" destroy-on-close :close-on-click-modal="false" :z-index="2000">
- <el-form label-width="90px">
- <el-form-item label="驳回原因" required>
- <el-input v-model="sampleRejectReason" type="textarea" :rows="4" placeholder="请输入驳回原因" maxlength="500" show-word-limit></el-input>
- </el-form-item>
- </el-form>
- <div style="text-align:right;margin-top:20px;">
- <el-button @click="sampleRejectVisible = false">取消</el-button>
- <el-button type="danger" @click="rejectSampleEdit" :loading="sampleRejectSaving">确认驳回</el-button>
- </div>
- </el-dialog>
-
- <!-- 编辑送检信息弹窗 -->
- <el-dialog v-model="sampleEditVisible" title="编辑送检信息" width="760px" destroy-on-close draggable :close-on-click-modal="false" :z-index="2000">
- <el-form :model="sampleEditForm" label-width="120px">
- <el-form-item label="送检样本类型">
- <el-checkbox-group v-model="sampleEditForm.sample_types" @change="onSampleEditTypesChange">
- <el-checkbox v-for="st in sampleTypeList" :key="st.id" :label="st.name">${ st.name }</el-checkbox>
- </el-checkbox-group>
- </el-form-item>
- <el-form-item v-if="sampleEditShowWaxReturn" :label="sampleEditNeedReturnNames + '是否需寄回'">
- <el-radio-group v-model="sampleEditForm.wax_return">
- <el-radio :label="1">是</el-radio>
- <el-radio :label="0">否</el-radio>
- </el-radio-group>
- </el-form-item>
- <template v-if="sampleEditShowWaxReturn && sampleEditForm.wax_return === 1">
- <el-form-item label="回寄收件人" required>
- <el-input v-model="sampleEditForm.return_name" placeholder="请输入回寄收件人"></el-input>
- </el-form-item>
- <el-form-item label="回寄电话" required>
- <el-input v-model="sampleEditForm.return_phone" placeholder="请输入回寄电话" maxlength="20"></el-input>
- </el-form-item>
- <el-form-item label="回寄地区" required>
- <el-cascader v-model="sampleEditForm.returnRegionCodes" :options="regionTree"
- :props="{ value: 'code', label: 'name', children: 'children' }"
- placeholder="请选择省/市/区" clearable style="width:100%;" />
- </el-form-item>
- <el-form-item label="详细地址" required>
- <el-input v-model="sampleEditForm.return_address" placeholder="请输入详细地址"></el-input>
- </el-form-item>
- </template>
- <template v-if="sampleEditForm.sample_types && sampleEditForm.sample_types.length">
- <el-form-item label="报告接收邮箱">
- <el-input v-model="sampleEditForm.report_email" placeholder="请输入邮箱地址"></el-input>
- </el-form-item>
- <el-form-item label="送检物流单号">
- <el-input v-model="sampleEditForm.sample_tracking_no" placeholder="请输入送检物流单号"></el-input>
- </el-form-item>
- <el-form-item label="送检单照片">
- <div style="display:flex;flex-wrap:wrap;gap:10px;">
- <div class="sample-photo-edit" v-for="(photo, idx) in sampleEditForm.sample_photos" :key="idx">
- <el-image :src="photo" fit="cover" :preview-src-list="sampleEditForm.sample_photos" :initial-index="idx"></el-image>
- <div class="del" @click="sampleEditForm.sample_photos.splice(idx, 1)">×</div>
- </div>
- <el-upload action="/admin/upload" :show-file-list="false" accept="image/*" :headers="uploadHeaders" :on-success="onSampleEditPhotoUpload">
- <div style="width:80px;height:80px;border:1px dashed #DCDFE6;border-radius:6px;display:flex;align-items:center;justify-content:center;color:#909399;font-size:24px;cursor:pointer;">+</div>
- </el-upload>
- </div>
- </el-form-item>
- </template>
- </el-form>
- <div style="text-align:right;margin-top:20px;">
- <el-button @click="sampleEditVisible = false">取消</el-button>
- <el-button type="primary" @click="saveSampleEdit" :loading="sampleEditSaving">保存并生效</el-button>
- </div>
- </el-dialog>
-
- </div>{% endblock %}
-
- {% block js %}
- <script>
- var patientId = '{{ patientId }}';
- var canAuditVal = {{ canAudit | dump | safe }};
- var canEditVal = {{ canEdit | dump | safe }};
- var { createApp, ref, reactive, onMounted } = Vue;
-
- var app = createApp({
- delimiters: ['${', '}'],
- setup() {
- var loading = ref(true);
- var canAudit = ref(canAuditVal);
- var canEdit = ref(canEditVal);
- var patient = reactive({
- id: '', patient_no: '', name: '', phone: '', id_card: '', gender: '', birth_date: '',
- province_code: '', city_code: '', district_code: '',
- province_name: '', city_name: '', district_name: '',
- address: '', hospital: '',
- hospital_province_code: '', hospital_city_code: '', hospital_district_code: '',
- hospital_province_name: '', hospital_city_name: '', hospital_district_name: '',
- tag: '', documents: [],
- sample_types: [], wax_return: 0,
- return_name: '', return_phone: '',
- return_province_code: '', return_city_code: '', return_district_code: '',
- return_province_name: '', return_city_name: '', return_district_name: '',
- return_address: '',
- report_email: '', sample_tracking_no: '', sample_photos: [],
- sample_info_status: 0, return_tracking_no: '', return_time: '',
- sample_edit_reason: '', sample_edit_reject_reason: '',
- sample_edit_apply_time: '', sample_edit_audit_time: '',
- sign_income: '', sign_privacy: '', sign_promise: '',
- emergency_contact: '', emergency_phone: '',
- status: -1, create_time: ''
- });
- var audits = ref([]);
- var rejectVisible = ref(false);
- var rejectSaving = ref(false);
- var rejectReason = ref('');
- var returnTrackingVisible = ref(false);
- var returnTrackingSaving = ref(false);
- var returnTrackingNo = ref('');
- var uploadHeaders = {};
- var sampleRejectVisible = ref(false);
- var sampleRejectSaving = ref(false);
- var sampleRejectReason = ref('');
- var sampleEditVisible = ref(false);
- var sampleEditSaving = ref(false);
- var sampleTypeList = ref([]);
- var regionTree = ref([]);
- var sampleEditForm = reactive({
- sample_types: [],
- wax_return: 0,
- return_name: '',
- return_phone: '',
- returnRegionCodes: [],
- return_address: '',
- report_email: '',
- sample_tracking_no: '',
- sample_photos: []
- });
- var selectedReasons = ref([]);
- var commonReasons = [
- '身份证照片模糊,请重新上传',
- '病历资料不完整,请补充',
- '检查报告缺失,请上传',
- '姓名与身份证信息不一致',
- '手机号无法联系,请核实',
- '提交信息存在明显错误',
- '资料过期,请提供最新版本'
- ];
-
- async function loadDetail() {
- loading.value = true;
- try {
- var res = await fetch('/admin/patient/info?id=' + patientId).then(function(r) { return r.json(); });
- if (res.code === 0) {
- Object.assign(patient, res.data.patient);
- audits.value = res.data.audits || [];
- } else {
- ElementPlus.ElMessage.error(res.msg || '加载失败');
- }
- } finally {
- loading.value = false;
- }
- }
-
- async function loadSampleTypes() {
- try {
- var res = await fetch('/common/sampleTypes').then(function(r) { return r.json(); });
- if (res.code === 0) sampleTypeList.value = (res.data && res.data.list) || [];
- } catch(e) {}
- }
-
- async function loadRegions() {
- try {
- var res = await fetch('/common/regions').then(function(r) { return r.json(); });
- if (res.code === 0) regionTree.value = res.data || [];
- } catch(e) {}
- }
-
- function goBack() {
- window.location.href = '/admin/patient.html';
- }
-
- function downloadSign(url, name) {
- if (!url) { ElementPlus.ElMessage.warning('该材料未上传'); return; }
- var a = document.createElement('a');
- a.href = url; a.target = '_blank'; a.download = name; a.click();
- }
-
- function isImageUrl(url) {
- if (!url) return false;
- var lower = url.split('?')[0].toLowerCase();
- return /\.(png|jpg|jpeg|gif|bmp|webp|svg)$/.test(lower);
- }
-
- var hospitalRegionText = Vue.computed(function() {
- return [
- patient.hospital_province_name,
- patient.hospital_city_name,
- patient.hospital_district_name
- ].filter(Boolean).join(' ');
- });
-
- var signDocs = Vue.computed(function() {
- var docs = [
- { key: 'income', label: '个人可支配收入声明', url: patient.sign_income },
- { key: 'privacy', label: '个人信息处理同意书', url: patient.sign_privacy },
- { key: 'promise', label: '声明与承诺', url: patient.sign_promise }
- ];
- if (patient.sign_privacy_jhr) {
- docs.push({ key: 'privacy_jhr', label: '监护人个人信息处理同意书', url: patient.sign_privacy_jhr });
- }
- return docs;
- });
-
- var signImageList = Vue.computed(function() {
- return [patient.sign_income, patient.sign_privacy, patient.sign_promise, patient.sign_privacy_jhr].filter(function(u) { return u && isImageUrl(u); });
- });
-
- var authImageList = Vue.computed(function() {
- return [patient.id_card_front, patient.id_card_back, patient.photo].filter(Boolean);
- });
-
- var sampleEditShowWaxReturn = Vue.computed(function() {
- if (!sampleEditForm.sample_types || !sampleEditForm.sample_types.length) return false;
- return sampleEditForm.sample_types.some(function(name) {
- var st = sampleTypeList.value.find(function(item) { return item.name === name; });
- return st && st.need_return;
- });
- });
-
- var sampleEditNeedReturnNames = Vue.computed(function() {
- if (!sampleEditForm.sample_types || !sampleEditForm.sample_types.length) return '';
- return sampleEditForm.sample_types.filter(function(name) {
- var st = sampleTypeList.value.find(function(item) { return item.name === name; });
- return st && st.need_return;
- }).join('、');
- });
-
- function sampleInfoStatusText(status) {
- if (Number(status) === 3) return '修改申请待审核';
- if (Number(status) === 2) return '已寄回';
- if (Number(status) === 1) return '已生效';
- return '可修改';
- }
-
- function sampleInfoStatusType(status) {
- if (Number(status) === 3) return 'warning';
- if (Number(status) === 2) return 'primary';
- if (Number(status) === 1) return 'success';
- return 'warning';
- }
-
- async function handleApprove() {
- try {
- await ElementPlus.ElMessageBox.confirm(
- '确定要通过该患者的审核吗?通过后患者将收到审核通过通知。',
- '确认审核通过',
- { confirmButtonText: '确认通过', cancelButtonText: '取消', type: 'success' }
- );
- var res = await fetch('/admin/patient/approve', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: patient.id })
- }).then(function(r) { return r.json(); });
- if (res.code === 0) {
- ElementPlus.ElMessage.success('审核已通过');
- loadDetail();
- } else {
- ElementPlus.ElMessage.error(res.msg || '操作失败');
- }
- } catch(e) {}
- }
-
- function showRejectDialog() {
- rejectReason.value = '';
- selectedReasons.value = [];
- rejectVisible.value = true;
- }
-
- function toggleReason(index) {
- var pos = selectedReasons.value.indexOf(index);
- if (pos > -1) { selectedReasons.value.splice(pos, 1); }
- else { selectedReasons.value.push(index); }
- rejectReason.value = selectedReasons.value.map(function(i) { return commonReasons[i]; }).join(';');
- }
-
- async function doReject() {
- if (!rejectReason.value.trim()) { ElementPlus.ElMessage.warning('请填写驳回原因'); return; }
- rejectSaving.value = true;
- try {
- var res = await fetch('/admin/patient/reject', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: patient.id, reason: rejectReason.value.trim() })
- }).then(function(r) { return r.json(); });
- if (res.code === 0) {
- ElementPlus.ElMessage.success('已驳回');
- rejectVisible.value = false;
- loadDetail();
- } else {
- ElementPlus.ElMessage.error(res.msg || '操作失败');
- }
- } finally {
- rejectSaving.value = false;
- }
- }
-
- async function resetSampleInfo() {
- try {
- await ElementPlus.ElMessageBox.confirm(
- '确认将该患者送检信息状态重置为可修改吗?重置后患者可在小程序重新编辑并提交送检信息。',
- '重置送检信息编辑',
- { confirmButtonText: '确认重置', cancelButtonText: '取消', type: 'warning' }
- );
- var res = await fetch('/admin/patient/resetSampleInfo', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: patient.id })
- }).then(function(r) { return r.json(); });
- if (res.code === 0) {
- ElementPlus.ElMessage.success('已重置为可修改');
- loadDetail();
- } else {
- ElementPlus.ElMessage.error(res.msg || '重置失败');
- }
- } catch(e) {}
- }
-
- function showReturnTrackingDialog() {
- returnTrackingNo.value = patient.return_tracking_no || '';
- returnTrackingVisible.value = true;
- }
-
- async function saveReturnTrackingNo() {
- if (!returnTrackingNo.value.trim()) {
- ElementPlus.ElMessage.warning('请填写回寄物流单号');
- return;
- }
- returnTrackingSaving.value = true;
- try {
- var res = await fetch('/admin/patient/saveReturnTrackingNo', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: patient.id, return_tracking_no: returnTrackingNo.value.trim() })
- }).then(function(r) { return r.json(); });
- if (res.code === 0) {
- ElementPlus.ElMessage.success('保存成功');
- returnTrackingVisible.value = false;
- loadDetail();
- } else {
- ElementPlus.ElMessage.error(res.msg || '保存失败');
- }
- } finally {
- returnTrackingSaving.value = false;
- }
- }
-
- async function approveSampleEdit() {
- try {
- await ElementPlus.ElMessageBox.confirm(
- '确认通过该患者的送检信息修改申请吗?通过后患者可在小程序重新编辑并提交送检信息。',
- '通过送检修改申请',
- { confirmButtonText: '确认通过', cancelButtonText: '取消', type: 'success' }
- );
- var res = await fetch('/admin/patient/approveSampleEdit', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: patient.id })
- }).then(function(r) { return r.json(); });
- if (res.code === 0) {
- ElementPlus.ElMessage.success('已通过申请');
- loadDetail();
- } else {
- ElementPlus.ElMessage.error(res.msg || '操作失败');
- }
- } catch(e) {}
- }
-
- function showRejectSampleEditDialog() {
- sampleRejectReason.value = '';
- sampleRejectVisible.value = true;
- }
-
- async function rejectSampleEdit() {
- if (!sampleRejectReason.value.trim()) {
- ElementPlus.ElMessage.warning('请填写驳回原因');
- return;
- }
- sampleRejectSaving.value = true;
- try {
- var res = await fetch('/admin/patient/rejectSampleEdit', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ id: patient.id, reason: sampleRejectReason.value.trim() })
- }).then(function(r) { return r.json(); });
- if (res.code === 0) {
- ElementPlus.ElMessage.success('已驳回申请');
- sampleRejectVisible.value = false;
- loadDetail();
- } else {
- ElementPlus.ElMessage.error(res.msg || '操作失败');
- }
- } finally {
- sampleRejectSaving.value = false;
- }
- }
-
- function onSampleEditTypesChange() {
- if (!sampleEditShowWaxReturn.value) {
- sampleEditForm.wax_return = 0;
- sampleEditForm.return_name = '';
- sampleEditForm.return_phone = '';
- sampleEditForm.returnRegionCodes = [];
- sampleEditForm.return_address = '';
- }
- if (!sampleEditForm.sample_types || !sampleEditForm.sample_types.length) {
- sampleEditForm.report_email = '';
- sampleEditForm.sample_tracking_no = '';
- sampleEditForm.sample_photos = [];
- }
- }
-
- function showSampleEditDialog() {
- Object.assign(sampleEditForm, {
- sample_types: (patient.sample_types || []).slice(),
- wax_return: patient.wax_return ? 1 : 0,
- return_name: patient.return_name || '',
- return_phone: patient.return_phone || '',
- returnRegionCodes: [patient.return_province_code, patient.return_city_code, patient.return_district_code].filter(Boolean),
- return_address: patient.return_address || '',
- report_email: patient.report_email || '',
- sample_tracking_no: patient.sample_tracking_no || '',
- sample_photos: (patient.sample_photos || []).slice()
- });
- sampleEditVisible.value = true;
- }
-
- function onSampleEditPhotoUpload(res) {
- if (res.code === 0 && res.data && res.data.url) {
- sampleEditForm.sample_photos.push(res.data.url);
- } else {
- ElementPlus.ElMessage.error(res.msg || '上传失败');
- }
- }
-
- async function saveSampleEdit() {
- var regionCodes = sampleEditForm.returnRegionCodes || [];
- if (sampleEditShowWaxReturn.value && sampleEditForm.wax_return === 1) {
- if (!sampleEditForm.return_name.trim()) return ElementPlus.ElMessage.warning('请填写回寄收件人');
- if (!sampleEditForm.return_phone.trim()) return ElementPlus.ElMessage.warning('请填写回寄电话');
- if (regionCodes.length !== 3) return ElementPlus.ElMessage.warning('请选择回寄地区');
- if (!sampleEditForm.return_address.trim()) return ElementPlus.ElMessage.warning('请填写回寄详细地址');
- }
- if (sampleEditForm.report_email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(sampleEditForm.report_email)) {
- return ElementPlus.ElMessage.warning('邮箱格式不正确');
- }
- sampleEditSaving.value = true;
- try {
- var res = await fetch('/admin/patient/editSampleInfo', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- id: patient.id,
- sample_types: sampleEditForm.sample_types || [],
- wax_return: sampleEditShowWaxReturn.value ? sampleEditForm.wax_return : 0,
- return_name: sampleEditForm.return_name.trim(),
- return_phone: sampleEditForm.return_phone.trim(),
- return_province_code: regionCodes[0] || '',
- return_city_code: regionCodes[1] || '',
- return_district_code: regionCodes[2] || '',
- return_address: sampleEditForm.return_address.trim(),
- report_email: sampleEditForm.report_email.trim(),
- sample_tracking_no: sampleEditForm.sample_tracking_no.trim(),
- sample_photos: sampleEditForm.sample_photos || []
- })
- }).then(function(r) { return r.json(); });
- if (res.code === 0) {
- ElementPlus.ElMessage.success('送检信息已保存并生效');
- sampleEditVisible.value = false;
- loadDetail();
- } else {
- ElementPlus.ElMessage.error(res.msg || '保存失败');
- }
- } finally {
- sampleEditSaving.value = false;
- }
- }
-
- onMounted(function() {
- loadDetail();
- loadSampleTypes();
- loadRegions();
- });
-
- return {
- loading, patient, audits, canAudit, canEdit,
- returnTrackingVisible, returnTrackingSaving, returnTrackingNo,
- sampleRejectVisible, sampleRejectSaving, sampleRejectReason,
- sampleEditVisible, sampleEditSaving, sampleEditForm, sampleTypeList, regionTree, uploadHeaders,
- rejectVisible, rejectSaving, rejectReason, selectedReasons, commonReasons,
- hospitalRegionText, goBack, downloadSign, isImageUrl, signDocs, signImageList,
- authImageList, sampleInfoStatusText, sampleInfoStatusType, sampleEditShowWaxReturn, sampleEditNeedReturnNames,
- handleApprove, showRejectDialog, toggleReason, doReject, resetSampleInfo,
- showReturnTrackingDialog, saveReturnTrackingNo,
- approveSampleEdit, showRejectSampleEditDialog, rejectSampleEdit,
- showSampleEditDialog, onSampleEditTypesChange, onSampleEditPhotoUpload, saveSampleEdit
- };
- }
- });
-
- app.use(ElementPlus, { locale: ElementPlusLocaleZhCn });
- app.mount('#detailApp');
- </script>
- {% endblock %}
|