| @@ -2,7 +2,7 @@ | |||||
| CREATE TABLE `message` ( | CREATE TABLE `message` ( | ||||
| `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, | `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, | ||||
| `patient_id` INT UNSIGNED NOT NULL COMMENT '患者ID', | `patient_id` INT UNSIGNED NOT NULL COMMENT '患者ID', | ||||
| `type` TINYINT NOT NULL DEFAULT 0 COMMENT '类型:1=审核通过 2=审核拒绝', | |||||
| `type` TINYINT NOT NULL DEFAULT 0 COMMENT '类型:1=审核通过 2=审核拒绝 3=回寄信息', | |||||
| `title` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '标题', | `title` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '标题', | ||||
| `content` TEXT COMMENT '详细内容', | `content` TEXT COMMENT '详细内容', | ||||
| `reason` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '驳回原因', | `reason` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '驳回原因', | ||||
| @@ -0,0 +1,18 @@ | |||||
| -- 送检信息配置与编辑状态 | |||||
| ALTER TABLE `patient` | |||||
| ADD COLUMN `sample_info_status` tinyint(1) DEFAULT 0 COMMENT '送检信息状态: 0可修改 1已生效' AFTER `sample_photos`; | |||||
| INSERT INTO `sys_config` (`config_key`, `config_value`, `remark`) VALUES | |||||
| ('sample_receiver_info', '{"address":"武汉东湖新技术开发区花城大道8号武汉软件新城三期D2栋17层001室","receiver":"样本中心","phone":"027-6552 6665","contact_phone":""}', '送检收件信息配置') | |||||
| ON DUPLICATE KEY UPDATE `remark` = VALUES(`remark`); | |||||
| UPDATE `patient` | |||||
| SET `sample_info_status` = CASE | |||||
| WHEN COALESCE(`sample_types`, '') <> '' | |||||
| OR COALESCE(`report_email`, '') <> '' | |||||
| OR COALESCE(`sample_tracking_no`, '') <> '' | |||||
| OR COALESCE(JSON_LENGTH(`sample_photos`), 0) > 0 | |||||
| THEN 1 | |||||
| ELSE 0 | |||||
| END | |||||
| WHERE `is_deleted` = 0; | |||||
| @@ -0,0 +1,5 @@ | |||||
| -- 送检样本回寄物流信息 | |||||
| ALTER TABLE `patient` | |||||
| MODIFY COLUMN `sample_info_status` tinyint(1) DEFAULT 0 COMMENT '送检信息状态: 0可修改 1已生效 2已寄回', | |||||
| ADD COLUMN `return_tracking_no` varchar(100) DEFAULT '' COMMENT '回寄物流单号' AFTER `sample_info_status`, | |||||
| ADD COLUMN `return_time` datetime DEFAULT NULL COMMENT '回寄时间' AFTER `return_tracking_no`; | |||||
| @@ -50,6 +50,10 @@ module.exports = [ | |||||
| ['/admin/patient/edit', 'admin/patient/edit', 'post'], | ['/admin/patient/edit', 'admin/patient/edit', 'post'], | ||||
| ['/admin/patient/approve', 'admin/patient/approve', 'post'], | ['/admin/patient/approve', 'admin/patient/approve', 'post'], | ||||
| ['/admin/patient/reject', 'admin/patient/reject', 'post'], | ['/admin/patient/reject', 'admin/patient/reject', 'post'], | ||||
| ['/admin/patient/sampleReceiverConfig', 'admin/patient/sampleReceiverConfig'], | |||||
| ['/admin/patient/saveSampleReceiverConfig', 'admin/patient/saveSampleReceiverConfig', 'post'], | |||||
| ['/admin/patient/resetSampleInfo', 'admin/patient/resetSampleInfo', 'post'], | |||||
| ['/admin/patient/saveReturnTrackingNo', 'admin/patient/saveReturnTrackingNo', 'post'], | |||||
| ['/admin/patient/export', 'admin/patient/export'], | ['/admin/patient/export', 'admin/patient/export'], | ||||
| // 公共接口 | // 公共接口 | ||||
| ['/common/regions', 'common/regions'], | ['/common/regions', 'common/regions'], | ||||
| @@ -1,7 +1,13 @@ | |||||
| const Base = require('../base.js'); | const Base = require('../base.js'); | ||||
| const dayjs = require('dayjs'); | const dayjs = require('dayjs'); | ||||
| const AUDIT_APPROVED_CONTENT = '您提交的肠愈同行患者关爱项目资料已审核通过,请于7天内在小程序-个人中心-送检信息中提交相关信息。'; | |||||
| module.exports = class extends Base { | module.exports = class extends Base { | ||||
| _canEditPatient() { | |||||
| return this.isSuperAdmin || (this.userPermissions || []).includes('patient:edit'); | |||||
| } | |||||
| // 患者列表页面 | // 患者列表页面 | ||||
| async indexAction() { | async indexAction() { | ||||
| this.assign('currentPage', 'patient'); | this.assign('currentPage', 'patient'); | ||||
| @@ -74,6 +80,7 @@ module.exports = class extends Base { | |||||
| this.assign('patientId', id); | this.assign('patientId', id); | ||||
| this.assign('adminUser', this.adminUser || {}); | this.assign('adminUser', this.adminUser || {}); | ||||
| this.assign('canAudit', this.isSuperAdmin || (this.userPermissions || []).includes('patient:audit')); | this.assign('canAudit', this.isSuperAdmin || (this.userPermissions || []).includes('patient:audit')); | ||||
| this.assign('canEdit', this._canEditPatient()); | |||||
| return this.display(); | return this.display(); | ||||
| } | } | ||||
| @@ -127,6 +134,25 @@ module.exports = class extends Base { | |||||
| return this.success({ patient, audits }); | return this.success({ patient, audits }); | ||||
| } | } | ||||
| async sampleReceiverConfigAction() { | |||||
| const config = await this.model('sys_config').getSampleReceiverInfo(); | |||||
| return this.success(config); | |||||
| } | |||||
| async saveSampleReceiverConfigAction() { | |||||
| if (!this._canEditPatient()) return this.fail('暂无权限'); | |||||
| const { address, receiver, phone, contact_phone } = this.post(); | |||||
| if (!address || !address.trim()) return this.fail('请填写收件地址'); | |||||
| if (!receiver || !receiver.trim()) return this.fail('请填写收件人'); | |||||
| if (!phone || !phone.trim()) return this.fail('请填写电话'); | |||||
| await this.model('sys_config').setSampleReceiverInfo({ address, receiver, phone, contact_phone }); | |||||
| await this.log('edit', '患者管理', '编辑送检信息配置'); | |||||
| const config = await this.model('sys_config').getSampleReceiverInfo(); | |||||
| return this.success(config); | |||||
| } | |||||
| // 新增患者 | // 新增患者 | ||||
| async addAction() { | async addAction() { | ||||
| const data = this.post(); | const data = this.post(); | ||||
| @@ -253,7 +279,7 @@ module.exports = class extends Base { | |||||
| patient_id: id, | patient_id: id, | ||||
| type: 1, | type: 1, | ||||
| title: '审核通过', | title: '审核通过', | ||||
| content: '您提交的个人资料已审核通过。', | |||||
| content: AUDIT_APPROVED_CONTENT, | |||||
| reason: '' | reason: '' | ||||
| }); | }); | ||||
| @@ -336,6 +362,57 @@ module.exports = class extends Base { | |||||
| return this.success(); | return this.success(); | ||||
| } | } | ||||
| async resetSampleInfoAction() { | |||||
| if (!this._canEditPatient()) return this.fail('暂无权限'); | |||||
| const { id } = this.post(); | |||||
| if (!id) return this.fail('参数错误'); | |||||
| const patient = await this.model('patient').where({ id, is_deleted: 0 }).find(); | |||||
| if (think.isEmpty(patient)) return this.fail('患者不存在'); | |||||
| await this.model('patient').where({ id }).update({ | |||||
| sample_info_status: 0, | |||||
| update_by: this.adminUser?.id || 0 | |||||
| }); | |||||
| await this.log('edit', '患者管理', `重置送检信息编辑权限(ID:${id})`); | |||||
| return this.success(); | |||||
| } | |||||
| async saveReturnTrackingNoAction() { | |||||
| if (!this._canEditPatient()) return this.fail('暂无权限'); | |||||
| const { id, return_tracking_no } = this.post(); | |||||
| if (!id) return this.fail('参数错误'); | |||||
| if (!return_tracking_no || !return_tracking_no.trim()) return this.fail('请填写回寄物流单号'); | |||||
| const patient = await this.model('patient').where({ id, is_deleted: 0 }).find(); | |||||
| if (think.isEmpty(patient)) return this.fail('患者不存在'); | |||||
| if (!patient.wax_return) return this.fail('该患者未选择样本寄回'); | |||||
| if (![1, 2].includes(Number(patient.sample_info_status))) return this.fail('送检信息未生效,暂不能填写回寄物流单号'); | |||||
| const trackingNo = return_tracking_no.trim(); | |||||
| const now = think.datetime(new Date()); | |||||
| await this.model('patient').where({ id }).update({ | |||||
| sample_info_status: 2, | |||||
| return_tracking_no: trackingNo, | |||||
| return_time: now, | |||||
| update_by: this.adminUser?.id || 0 | |||||
| }); | |||||
| await this.model('message').where({ patient_id: id, type: 3 }).delete(); | |||||
| await this.model('message').add({ | |||||
| patient_id: id, | |||||
| type: 3, | |||||
| title: '回寄信息', | |||||
| content: '样品已经寄回,请注意查收。', | |||||
| reason: trackingNo, | |||||
| is_read: 0 | |||||
| }); | |||||
| await this.log('edit', '患者管理', `填写回寄物流单号(ID:${id}):${trackingNo}`); | |||||
| return this.success(); | |||||
| } | |||||
| // 编辑患者 | // 编辑患者 | ||||
| async editAction() { | async editAction() { | ||||
| const data = this.post(); | const data = this.post(); | ||||
| @@ -6,6 +6,7 @@ const path = require('path'); | |||||
| const cosConfig = require('../config/cos.js'); | const cosConfig = require('../config/cos.js'); | ||||
| const APP_REMARK = 'pap_mini_cytx'; | const APP_REMARK = 'pap_mini_cytx'; | ||||
| const AUDIT_APPROVED_CONTENT = '您提交的肠愈同行患者关爱项目资料已审核通过,请于7天内在小程序-个人中心-送检信息中提交相关信息。'; | |||||
| module.exports = class extends Base { | module.exports = class extends Base { | ||||
| // POST /api/mp/login | // POST /api/mp/login | ||||
| @@ -637,6 +638,10 @@ module.exports = class extends Base { | |||||
| // 查患者姓名 | // 查患者姓名 | ||||
| const patient = await this.model('patient').field('name').where({ id: user.patient_id }).find(); | const patient = await this.model('patient').field('name').where({ id: user.patient_id }).find(); | ||||
| msg.patient_name = patient ? patient.name : ''; | msg.patient_name = patient ? patient.name : ''; | ||||
| if (msg.type === 1) { | |||||
| msg.content = AUDIT_APPROVED_CONTENT; | |||||
| msg.sample_receiver_info = await this.model('sys_config').getSampleReceiverInfo(); | |||||
| } | |||||
| return this.json({ code: 0, data: msg }); | return this.json({ code: 0, data: msg }); | ||||
| } catch (error) { | } catch (error) { | ||||
| think.logger.error('messageDetail error:', error); | think.logger.error('messageDetail error:', error); | ||||
| @@ -698,6 +703,7 @@ module.exports = class extends Base { | |||||
| try { sampleTypes = JSON.parse(patient.sample_types || '[]'); } catch (e) { sampleTypes = []; } | try { sampleTypes = JSON.parse(patient.sample_types || '[]'); } catch (e) { sampleTypes = []; } | ||||
| let samplePhotos = []; | let samplePhotos = []; | ||||
| try { samplePhotos = JSON.parse(patient.sample_photos || '[]'); } catch (e) { samplePhotos = []; } | try { samplePhotos = JSON.parse(patient.sample_photos || '[]'); } catch (e) { samplePhotos = []; } | ||||
| const sampleReceiverInfo = await this.model('sys_config').getSampleReceiverInfo(); | |||||
| return this.json({ code: 0, data: { | return this.json({ code: 0, data: { | ||||
| patient: { | patient: { | ||||
| @@ -716,7 +722,11 @@ module.exports = class extends Base { | |||||
| return_address: patient.return_address || '', | return_address: patient.return_address || '', | ||||
| report_email: patient.report_email || '', | report_email: patient.report_email || '', | ||||
| sample_tracking_no: patient.sample_tracking_no || '', | sample_tracking_no: patient.sample_tracking_no || '', | ||||
| sample_photos: samplePhotos | |||||
| sample_photos: samplePhotos, | |||||
| sample_info_status: Number(patient.sample_info_status) || 0, | |||||
| return_tracking_no: patient.return_tracking_no || '', | |||||
| return_time: patient.return_time || '', | |||||
| sample_receiver_info: sampleReceiverInfo | |||||
| }}); | }}); | ||||
| } catch (error) { | } catch (error) { | ||||
| think.logger.error('sampleInfo error:', error); | think.logger.error('sampleInfo error:', error); | ||||
| @@ -739,6 +749,30 @@ module.exports = class extends Base { | |||||
| const patient = await this.model('patient').where({ id: user.patient_id, is_deleted: 0 }).find(); | const patient = await this.model('patient').where({ id: user.patient_id, is_deleted: 0 }).find(); | ||||
| if (think.isEmpty(patient)) return this.json({ code: 1, msg: '患者信息不存在' }); | if (think.isEmpty(patient)) return this.json({ code: 1, msg: '患者信息不存在' }); | ||||
| if (patient.status !== 1) return this.json({ code: 1, msg: '请先通过审核' }); | if (patient.status !== 1) return this.json({ code: 1, msg: '请先通过审核' }); | ||||
| const sampleReceiverInfo = await this.model('sys_config').getSampleReceiverInfo(); | |||||
| const sampleInfoStatus = Number(patient.sample_info_status) || 0; | |||||
| if (sampleInfoStatus === 2) { | |||||
| return this.json({ | |||||
| code: 1010, | |||||
| data: { | |||||
| sample_info_status: 2, | |||||
| return_tracking_no: patient.return_tracking_no || '', | |||||
| return_time: patient.return_time || '', | |||||
| sample_receiver_info: sampleReceiverInfo | |||||
| }, | |||||
| msg: `样品已经寄回,回寄物流单号:${patient.return_tracking_no || '—'}。` | |||||
| }); | |||||
| } | |||||
| if (sampleInfoStatus === 1) { | |||||
| const contactPhone = sampleReceiverInfo.contact_phone || ''; | |||||
| return this.json({ | |||||
| code: 1010, | |||||
| data: { sample_info_status: 1, sample_receiver_info: sampleReceiverInfo }, | |||||
| msg: contactPhone | |||||
| ? `送检信息已生效,如需修改请联系平台【${contactPhone}】重置修改权限。` | |||||
| : '送检信息已生效,如需修改请联系平台重置修改权限。' | |||||
| }); | |||||
| } | |||||
| const hasSample = sample_types && sample_types.length > 0; | const hasSample = sample_types && sample_types.length > 0; | ||||
| @@ -764,7 +798,8 @@ module.exports = class extends Base { | |||||
| return_address: (hasSample && wax_return) ? (return_address || '') : '', | return_address: (hasSample && wax_return) ? (return_address || '') : '', | ||||
| report_email: hasSample ? (report_email || '') : '', | report_email: hasSample ? (report_email || '') : '', | ||||
| sample_tracking_no: hasSample ? (sample_tracking_no || '') : '', | sample_tracking_no: hasSample ? (sample_tracking_no || '') : '', | ||||
| sample_photos: hasSample ? JSON.stringify(sample_photos || []) : '[]' | |||||
| sample_photos: hasSample ? JSON.stringify(sample_photos || []) : '[]', | |||||
| sample_info_status: 1 | |||||
| }); | }); | ||||
| return this.json({ code: 0, data: {}, msg: '提交成功' }); | return this.json({ code: 0, data: {}, msg: '提交成功' }); | ||||
| @@ -1,3 +1,10 @@ | |||||
| const DEFAULT_SAMPLE_RECEIVER_INFO = { | |||||
| address: '武汉东湖新技术开发区花城大道8号武汉软件新城三期D2栋17层001室', | |||||
| receiver: '样本中心', | |||||
| phone: '027-6552 6665', | |||||
| contact_phone: '' | |||||
| }; | |||||
| module.exports = class extends think.Model { | module.exports = class extends think.Model { | ||||
| get tableName() { | get tableName() { | ||||
| return 'sys_config'; | return 'sys_config'; | ||||
| @@ -27,4 +34,22 @@ module.exports = class extends think.Model { | |||||
| } | } | ||||
| return this.where({ config_key: key }).update({ config_value: val }); | return this.where({ config_key: key }).update({ config_value: val }); | ||||
| } | } | ||||
| async getSampleReceiverInfo() { | |||||
| const info = await this.getByKey('sample_receiver_info'); | |||||
| return { | |||||
| ...DEFAULT_SAMPLE_RECEIVER_INFO, | |||||
| ...(info && typeof info === 'object' ? info : {}) | |||||
| }; | |||||
| } | |||||
| async setSampleReceiverInfo(info) { | |||||
| const value = { | |||||
| address: (info.address || '').trim(), | |||||
| receiver: (info.receiver || '').trim(), | |||||
| phone: (info.phone || '').trim(), | |||||
| contact_phone: (info.contact_phone || '').trim() | |||||
| }; | |||||
| return this.setByKey('sample_receiver_info', value, '送检收件信息配置'); | |||||
| } | |||||
| }; | }; | ||||
| @@ -147,6 +147,13 @@ | |||||
| <div class="detail-panel"> | <div class="detail-panel"> | ||||
| <h3>送检信息</h3> | <h3>送检信息</h3> | ||||
| <div class="info-grid"> | <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> | <div class="info-item"><span class="label">送检样本:</span> | ||||
| <span class="value"> | <span class="value"> | ||||
| <template v-if="patient.sample_types && patient.sample_types.length"> | <template v-if="patient.sample_types && patient.sample_types.length"> | ||||
| @@ -163,6 +170,8 @@ | |||||
| </div> | </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.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_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> | </div> | ||||
| <div v-if="patient.sample_photos && patient.sample_photos.length" style="margin-top:16px;"> | <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 style="font-size:13px;color:#909399;margin-bottom:8px;">送检单照片</div> | ||||
| @@ -196,6 +205,10 @@ | |||||
| <el-button @click="goBack">返 回</el-button> | <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 === 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="(patient.status === 0 || patient.status === 1) && canAudit" type="danger" @click="showRejectDialog">驳 回</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> | </div> | ||||
| <!-- 驳回弹窗 --> | <!-- 驳回弹窗 --> | ||||
| @@ -213,12 +226,26 @@ | |||||
| </div> | </div> | ||||
| </el-dialog> | </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> | |||||
| </div>{% endblock %} | </div>{% endblock %} | ||||
| {% block js %} | {% block js %} | ||||
| <script> | <script> | ||||
| var patientId = '{{ patientId }}'; | var patientId = '{{ patientId }}'; | ||||
| var canAuditVal = {{ canAudit | dump | safe }}; | var canAuditVal = {{ canAudit | dump | safe }}; | ||||
| var canEditVal = {{ canEdit | dump | safe }}; | |||||
| var { createApp, ref, reactive, onMounted } = Vue; | var { createApp, ref, reactive, onMounted } = Vue; | ||||
| var app = createApp({ | var app = createApp({ | ||||
| @@ -226,6 +253,7 @@ var app = createApp({ | |||||
| setup() { | setup() { | ||||
| var loading = ref(true); | var loading = ref(true); | ||||
| var canAudit = ref(canAuditVal); | var canAudit = ref(canAuditVal); | ||||
| var canEdit = ref(canEditVal); | |||||
| var patient = reactive({ | var patient = reactive({ | ||||
| id: '', patient_no: '', name: '', phone: '', id_card: '', gender: '', birth_date: '', | id: '', patient_no: '', name: '', phone: '', id_card: '', gender: '', birth_date: '', | ||||
| province_code: '', city_code: '', district_code: '', | province_code: '', city_code: '', district_code: '', | ||||
| @@ -240,6 +268,7 @@ var app = createApp({ | |||||
| return_province_name: '', return_city_name: '', return_district_name: '', | return_province_name: '', return_city_name: '', return_district_name: '', | ||||
| return_address: '', | return_address: '', | ||||
| report_email: '', sample_tracking_no: '', sample_photos: [], | report_email: '', sample_tracking_no: '', sample_photos: [], | ||||
| sample_info_status: 0, return_tracking_no: '', return_time: '', | |||||
| sign_income: '', sign_privacy: '', sign_promise: '', | sign_income: '', sign_privacy: '', sign_promise: '', | ||||
| emergency_contact: '', emergency_phone: '', | emergency_contact: '', emergency_phone: '', | ||||
| status: -1, create_time: '' | status: -1, create_time: '' | ||||
| @@ -248,6 +277,9 @@ var app = createApp({ | |||||
| var rejectVisible = ref(false); | var rejectVisible = ref(false); | ||||
| var rejectSaving = ref(false); | var rejectSaving = ref(false); | ||||
| var rejectReason = ref(''); | var rejectReason = ref(''); | ||||
| var returnTrackingVisible = ref(false); | |||||
| var returnTrackingSaving = ref(false); | |||||
| var returnTrackingNo = ref(''); | |||||
| var selectedReasons = ref([]); var commonReasons = [ | var selectedReasons = ref([]); var commonReasons = [ | ||||
| '身份证照片模糊,请重新上传', | '身份证照片模糊,请重新上传', | ||||
| '病历资料不完整,请补充', | '病历资料不完整,请补充', | ||||
| @@ -317,6 +349,18 @@ var app = createApp({ | |||||
| return [patient.id_card_front, patient.id_card_back, patient.photo].filter(Boolean); | return [patient.id_card_front, patient.id_card_back, patient.photo].filter(Boolean); | ||||
| }); | }); | ||||
| function sampleInfoStatusText(status) { | |||||
| if (Number(status) === 2) return '已寄回'; | |||||
| if (Number(status) === 1) return '已生效'; | |||||
| return '可修改'; | |||||
| } | |||||
| function sampleInfoStatusType(status) { | |||||
| if (Number(status) === 2) return 'primary'; | |||||
| if (Number(status) === 1) return 'success'; | |||||
| return 'warning'; | |||||
| } | |||||
| async function handleApprove() { | async function handleApprove() { | ||||
| try { | try { | ||||
| await ElementPlus.ElMessageBox.confirm( | await ElementPlus.ElMessageBox.confirm( | ||||
| @@ -372,13 +416,66 @@ var app = createApp({ | |||||
| } | } | ||||
| } | } | ||||
| 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; | |||||
| } | |||||
| } | |||||
| onMounted(function() { loadDetail(); }); | onMounted(function() { loadDetail(); }); | ||||
| return { | return { | ||||
| loading, patient, audits, canAudit, | |||||
| loading, patient, audits, canAudit, canEdit, | |||||
| returnTrackingVisible, returnTrackingSaving, returnTrackingNo, | |||||
| rejectVisible, rejectSaving, rejectReason, selectedReasons, commonReasons, | rejectVisible, rejectSaving, rejectReason, selectedReasons, commonReasons, | ||||
| hospitalRegionText, goBack, downloadSign, isImageUrl, signDocs, signImageList, | hospitalRegionText, goBack, downloadSign, isImageUrl, signDocs, signImageList, | ||||
| authImageList, handleApprove, showRejectDialog, toggleReason, doReject | |||||
| authImageList, sampleInfoStatusText, sampleInfoStatusType, | |||||
| handleApprove, showRejectDialog, toggleReason, doReject, resetSampleInfo, | |||||
| showReturnTrackingDialog, saveReturnTrackingNo | |||||
| }; | }; | ||||
| } | } | ||||
| }); | }); | ||||
| @@ -35,6 +35,7 @@ | |||||
| <el-button v-if="perms.canAdd" type="success" :icon="Plus" @click="showAddDialog">新增患者</el-button> | <el-button v-if="perms.canAdd" type="success" :icon="Plus" @click="showAddDialog">新增患者</el-button> | ||||
| <el-button v-if="perms.canExport" :icon="Download" @click="handleExport" :loading="exporting">导出</el-button> | <el-button v-if="perms.canExport" :icon="Download" @click="handleExport" :loading="exporting">导出</el-button> | ||||
| <el-button v-if="perms.canExport" type="warning" :icon="Download" @click="showExportFilesDialog">异步下载附件</el-button> | <el-button v-if="perms.canExport" type="warning" :icon="Download" @click="showExportFilesDialog">异步下载附件</el-button> | ||||
| <el-button v-if="perms.canEdit" type="primary" plain @click="showSampleReceiverDialog">送检信息配置</el-button> | |||||
| <el-button v-if="perms.canDelete && selectedIds.length" type="danger" :icon="Delete" @click="handleBatchDelete">批量删除 (${ selectedIds.length })</el-button> | <el-button v-if="perms.canDelete && selectedIds.length" type="danger" :icon="Delete" @click="handleBatchDelete">批量删除 (${ selectedIds.length })</el-button> | ||||
| </div> | </div> | ||||
| </el-card> | </el-card> | ||||
| @@ -265,6 +266,28 @@ | |||||
| </template> | </template> | ||||
| </el-dialog> | </el-dialog> | ||||
| <!-- 送检信息配置弹窗 --> | |||||
| <el-dialog v-model="sampleReceiverVisible" title="送检信息配置" width="560px" destroy-on-close :close-on-click-modal="false"> | |||||
| <el-form :model="sampleReceiverForm" label-width="90px"> | |||||
| <el-form-item label="收件地址" required> | |||||
| <el-input v-model="sampleReceiverForm.address" type="textarea" :rows="3" placeholder="请输入收件地址" /> | |||||
| </el-form-item> | |||||
| <el-form-item label="收件人" required> | |||||
| <el-input v-model="sampleReceiverForm.receiver" placeholder="请输入收件人" /> | |||||
| </el-form-item> | |||||
| <el-form-item label="电话" required> | |||||
| <el-input v-model="sampleReceiverForm.phone" placeholder="请输入联系电话" /> | |||||
| </el-form-item> | |||||
| <el-form-item label="平台电话"> | |||||
| <el-input v-model="sampleReceiverForm.contact_phone" placeholder="选填,用于小程序修改权限联系平台" /> | |||||
| </el-form-item> | |||||
| </el-form> | |||||
| <template #footer> | |||||
| <el-button @click="sampleReceiverVisible = false">取消</el-button> | |||||
| <el-button type="primary" @click="saveSampleReceiverConfig" :loading="sampleReceiverSaving">保存</el-button> | |||||
| </template> | |||||
| </el-dialog> | |||||
| <!-- 导出附件弹窗 --> | <!-- 导出附件弹窗 --> | ||||
| <el-dialog v-model="exportFilesVisible" title="异步下载附件" width="480px" destroy-on-close :close-on-click-modal="false"> | <el-dialog v-model="exportFilesVisible" title="异步下载附件" width="480px" destroy-on-close :close-on-click-modal="false"> | ||||
| <p style="color:#606266;margin-bottom:16px;">将按当前筛选条件导出患者附件,打包为 ZIP 文件。请选择需要导出的附件类型:</p> | <p style="color:#606266;margin-bottom:16px;">将按当前筛选条件导出患者附件,打包为 ZIP 文件。请选择需要导出的附件类型:</p> | ||||
| @@ -362,6 +385,9 @@ const app = createApp({ | |||||
| const regionFilter = ref([]); | const regionFilter = ref([]); | ||||
| const exporting = ref(false); | const exporting = ref(false); | ||||
| const sampleReceiverVisible = ref(false); | |||||
| const sampleReceiverSaving = ref(false); | |||||
| const sampleReceiverForm = reactive({ address: '', receiver: '', phone: '', contact_phone: '' }); | |||||
| const tabStatusMap = { all: '', draft: '-1', pending: '0', rejected: '2', approved: '1' }; | const tabStatusMap = { all: '', draft: '-1', pending: '0', rejected: '2', approved: '1' }; | ||||
| @@ -647,6 +673,54 @@ const app = createApp({ | |||||
| window.open('/admin/patient/export?' + params.toString(), '_blank'); | window.open('/admin/patient/export?' + params.toString(), '_blank'); | ||||
| } | } | ||||
| async function showSampleReceiverDialog() { | |||||
| try { | |||||
| var res = await fetch('/admin/patient/sampleReceiverConfig').then(function(r) { return r.json(); }); | |||||
| if (res.code === 0) { | |||||
| Object.assign(sampleReceiverForm, { | |||||
| address: res.data.address || '', | |||||
| receiver: res.data.receiver || '', | |||||
| phone: res.data.phone || '', | |||||
| contact_phone: res.data.contact_phone || '' | |||||
| }); | |||||
| sampleReceiverVisible.value = true; | |||||
| } else { | |||||
| ElementPlus.ElMessage.error(res.msg || '获取配置失败'); | |||||
| } | |||||
| } catch(e) { | |||||
| ElementPlus.ElMessage.error('获取配置失败'); | |||||
| } | |||||
| } | |||||
| async function saveSampleReceiverConfig() { | |||||
| if (!sampleReceiverForm.address.trim()) return ElementPlus.ElMessage.warning('请填写收件地址'); | |||||
| if (!sampleReceiverForm.receiver.trim()) return ElementPlus.ElMessage.warning('请填写收件人'); | |||||
| if (!sampleReceiverForm.phone.trim()) return ElementPlus.ElMessage.warning('请填写电话'); | |||||
| sampleReceiverSaving.value = true; | |||||
| try { | |||||
| var res = await fetch('/admin/patient/saveSampleReceiverConfig', { | |||||
| method: 'POST', | |||||
| headers: { 'Content-Type': 'application/json' }, | |||||
| body: JSON.stringify({ | |||||
| address: sampleReceiverForm.address.trim(), | |||||
| receiver: sampleReceiverForm.receiver.trim(), | |||||
| phone: sampleReceiverForm.phone.trim(), | |||||
| contact_phone: sampleReceiverForm.contact_phone.trim() | |||||
| }) | |||||
| }).then(function(r) { return r.json(); }); | |||||
| if (res.code === 0) { | |||||
| ElementPlus.ElMessage.success('保存成功'); | |||||
| sampleReceiverVisible.value = false; | |||||
| } else { | |||||
| ElementPlus.ElMessage.error(res.msg || '保存失败'); | |||||
| } | |||||
| } finally { | |||||
| sampleReceiverSaving.value = false; | |||||
| } | |||||
| } | |||||
| // 导出附件 | // 导出附件 | ||||
| const exportFilesVisible = ref(false); | const exportFilesVisible = ref(false); | ||||
| const exportFilesLoading = ref(false); | const exportFilesLoading = ref(false); | ||||
| @@ -720,10 +794,11 @@ const app = createApp({ | |||||
| keyword, dateRange, tagFilter, regionFilter, activeTab, loading, tableData, pagination, counts, | keyword, dateRange, tagFilter, regionFilter, activeTab, loading, tableData, pagination, counts, | ||||
| uploadHeaders, addVisible, addSaving, addForm, exporting, editingId, perms, selectedIds, | uploadHeaders, addVisible, addSaving, addForm, exporting, editingId, perms, selectedIds, | ||||
| regionTree, tagOptions, isMinorComputed, Plus, Download, Delete, | regionTree, tagOptions, isMinorComputed, Plus, Download, Delete, | ||||
| sampleReceiverVisible, sampleReceiverSaving, sampleReceiverForm, | |||||
| exportFilesVisible, exportFilesLoading, exportFileTypes, exportSuccessVisible, | exportFilesVisible, exportFilesLoading, exportFileTypes, exportSuccessVisible, | ||||
| loadList, resetFilter, onTabChange, onSizeChange, onSelectionChange, viewDetail, showAddDialog, showEditDialog, handleExport, | loadList, resetFilter, onTabChange, onSizeChange, onSelectionChange, viewDetail, showAddDialog, showEditDialog, handleExport, | ||||
| onIdCardInput, onDocUpload, onSignUpload, submitAdd, handleDelete, handleBatchDelete, | onIdCardInput, onDocUpload, onSignUpload, submitAdd, handleDelete, handleBatchDelete, | ||||
| showExportFilesDialog, submitExportFiles, goExportTask | |||||
| showSampleReceiverDialog, saveSampleReceiverConfig, showExportFilesDialog, submitExportFiles, goExportTask | |||||
| }; | }; | ||||
| } | } | ||||
| }); | }); | ||||