| | |
| | | icon="el-icon-check" |
| | | @click="handleBatchSubmit" |
| | | :loading="batchProcessing" |
| | | :disabled="selectedExceptionIds.length === 0" |
| | | > |
| | | 批量提交处理 |
| | | 批量提交处理 ({{ selectedExceptionIds.length }}) |
| | | </el-button> |
| | | <el-button type="warning" icon="el-icon-back" @click="handleGoBack"> |
| | | 返回异常列表 |
| | |
| | | > |
| | | <el-form-item label="负责科室"> |
| | | <el-select |
| | | v-model="filterParams.deptId" |
| | | v-model="filterParams.todeptcode" |
| | | placeholder="请选择科室" |
| | | clearable |
| | | filterable |
| | | style="width: 200px" |
| | | > |
| | | <el-option |
| | | v-for="dept in deptList" |
| | | :key="dept.id" |
| | | :label="dept.name" |
| | | :value="dept.id" |
| | | :key="dept.deptCode" |
| | | :label="dept.label" |
| | | :value="dept.deptCode" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="处理状态"> |
| | | <el-select |
| | | v-model="filterParams.status" |
| | | v-model="filterParams.handleFlag" |
| | | placeholder="请选择状态" |
| | | clearable |
| | | style="width: 200px" |
| | | > |
| | | <el-option label="待处理" :value="0" /> |
| | | <el-option label="处理中" :value="1" /> |
| | | <el-option label="已处理" :value="2" /> |
| | | <el-option label="未处理" :value="'0'" /> |
| | | <el-option label="已处理" :value="'1'" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="满意度类型"> |
| | | <el-select |
| | | v-model="filterParams.templateType" |
| | | placeholder="请选择模板类型" |
| | | clearable |
| | | style="width: 200px" |
| | | > |
| | | <el-option label="语音模板" :value="1" /> |
| | | <el-option label="问卷模板" :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | |
| | | :border="true" |
| | | style="width: 100%" |
| | | @selection-change="handleSelectionChange" |
| | | row-key="id" |
| | | class="exception-table" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | |
| | | |
| | | <el-table-column |
| | | label="负责科室" |
| | | prop="responsibilityDept" |
| | | width="120" |
| | | prop="todeptname" |
| | | width="200" |
| | | align="center" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <el-tag type="primary">{{ row.responsibilityDept }}</el-tag> |
| | | <el-tag type="primary" v-if="row.todeptname">{{ |
| | | row.todeptname |
| | | }}</el-tag> |
| | | <span v-else class="no-data">未分配</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="不满意详情" |
| | | prop="unsatisfactoryDetail" |
| | | min-width="200" |
| | | align="center" |
| | | > |
| | | <el-table-column label="不满意详情" min-width="250" align="center"> |
| | | <template slot-scope="{ row }"> |
| | | <div class="detail-content"> |
| | | {{ row.unsatisfactoryDetail }} |
| | | <div class="question-text"> |
| | | <strong>问题:</strong>{{ row.questiontext }} |
| | | </div> |
| | | <div class="answer-text"> |
| | | <strong>回答:</strong>{{ row.asrtext || "无回答" }} |
| | | </div> |
| | | <div class="matched-text" v-if="row.matchedtext"> |
| | | <strong>解析值:</strong>{{ row.matchedtext }} |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <el-table-column label="患者信息" width="300" align="center"> |
| | | <template slot-scope="{ row }"> |
| | | <div class="patient-info"> |
| | | <div class="patient-item"> |
| | | <span class="label">姓名:</span> |
| | | <span class="value">{{ row.patientName }}</span> |
| | | <div class="patient-row"> |
| | | <div class="patient-item"> |
| | | <span class="label">姓名:</span> |
| | | <span class="value">{{ row.patdescJson.sendname }}</span> |
| | | </div> |
| | | <div class="patient-item"> |
| | | <span class="label">性别:</span> |
| | | <span class="value">{{ row.patdescJson.sex }}</span> |
| | | </div> |
| | | <div class="patient-item"> |
| | | <span class="label">年龄:</span> |
| | | <span class="value">{{ row.patdescJson.age }}岁</span> |
| | | </div> |
| | | </div> |
| | | <div class="patient-item"> |
| | | <span class="label">性别:</span> |
| | | <span class="value">{{ |
| | | row.gender === 1 ? "男" : "女" |
| | | }}</span> |
| | | </div> |
| | | <div class="patient-item"> |
| | | <span class="label">年龄:</span> |
| | | <span class="value">{{ row.age }}岁</span> |
| | | </div> |
| | | <div class="patient-item"> |
| | | <span class="label">电话:</span> |
| | | <span class="value">{{ row.phone }}</span> |
| | | <div class="patient-row"> |
| | | <div class="patient-item full-width"> |
| | | <span class="label">电话:</span> |
| | | <span class="value">{{ row.patdescJson.phone }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="出院信息" width="250" align="center"> |
| | | <el-table-column label="填写信息" width="180" align="center"> |
| | | <template slot-scope="{ row }"> |
| | | <div class="discharge-info"> |
| | | <div class="fill-info"> |
| | | <div class="info-item"> |
| | | <span class="label">科室:</span> |
| | | <span class="value">{{ row.dischargeDept }}</span> |
| | | <span class="label">填报时间:</span> |
| | | <span class="value time">{{ |
| | | formatDateTime(row.createTime) |
| | | }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">病区:</span> |
| | | <span class="value">{{ row.dischargeWard }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">填写时间:</span> |
| | | <span class="value time">{{ row.fillTime }}</span> |
| | | <div v-if="row.recordurl" class="info-item"> |
| | | <el-button |
| | | type="text" |
| | | size="small" |
| | | @click="handlePlayAudio(row.recordurl)" |
| | | icon="el-icon-headset" |
| | | > |
| | | 播放录音 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | |
| | | <el-table-column |
| | | label="处理状态" |
| | | prop="processStatus" |
| | | prop="handleFlag" |
| | | width="100" |
| | | align="center" |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <el-tag :type="getStatusTagType(row.processStatus)" effect="dark"> |
| | | {{ getStatusText(row.processStatus) }} |
| | | <el-tag :type="getStatusTagType(row.handleFlag)" effect="dark"> |
| | | {{ getStatusText(row.handleFlag) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="最新处理信息" width="180" align="center"> |
| | | <template slot-scope="{ row }"> |
| | | <div v-if="row.handleTime" class="handle-info"> |
| | | <div class="info-item"> |
| | | <span class="label">处理人:</span> |
| | | <span class="value">{{ row.handleBy || "系统" }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">处理时间:</span> |
| | | <span class="value time">{{ |
| | | formatDateTime(row.handleTime) |
| | | }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="label">处理说明:</span> |
| | | <span class="value">{{ formatHandledesc(row.handledesc) }}</span> |
| | | </div> |
| | | </div> |
| | | <span v-else class="no-data">未处理</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | |
| | | size="small" |
| | | icon="el-icon-edit" |
| | | @click="handleProcess(row)" |
| | | :disabled="row.processStatus === 2" |
| | | :disabled="row.handleresult === 'resolved'" |
| | | > |
| | | 处理 |
| | | </el-button> |
| | |
| | | <el-dialog |
| | | title="处理异常反馈" |
| | | :visible.sync="processDialogVisible" |
| | | width="600px" |
| | | width="60%" |
| | | top="6vh" |
| | | center |
| | | > |
| | | <el-form |
| | | :model="processForm" |
| | | :rules="processRules" |
| | | ref="processForm" |
| | | label-width="100px" |
| | | size="medium" |
| | | > |
| | | <el-form-item label="处理状态" prop="status"> |
| | | <el-select |
| | | v-model="processForm.status" |
| | | placeholder="请选择处理状态" |
| | | style="width: 100%" |
| | | <div class="flow-dialog-body"> |
| | | <!-- 左半部分:处理信息 --> |
| | | <div class="flow-left"> |
| | | <el-form |
| | | :model="processForm" |
| | | :rules="processRules" |
| | | ref="processForm" |
| | | label-width="100px" |
| | | size="medium" |
| | | > |
| | | <el-option label="处理中" :value="1" /> |
| | | <el-option label="已处理" :value="2" /> |
| | | <el-option label="已驳回" :value="3" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="是否流转" prop="isFlow"> |
| | | <el-switch |
| | | v-model="processForm.isFlow" |
| | | active-value="1" |
| | | inactive-value="0" |
| | | active-text="流转" |
| | | inactive-text="不流转" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="报备科室" prop="reportDepts"> |
| | | <el-select |
| | | v-model="processForm.reportDepts" |
| | | placeholder="请选择报备科室" |
| | | multiple |
| | | filterable |
| | | collapse-tags |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="dept in deptList" |
| | | :key="dept.id" |
| | | :label="dept.name" |
| | | :value="dept.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="处理状态" prop="handleFlag"> |
| | | <el-select |
| | | v-model="processForm.handleFlag" |
| | | placeholder="请选择处理状态" |
| | | style="width: 100%" |
| | | > |
| | | <el-option label="已处理" :value="'1'" /> |
| | | <el-option label="取消处理" :value="'0'" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="处理备注" prop="remark"> |
| | | <el-input |
| | | v-model="processForm.remark" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入处理备注(最多500字)" |
| | | maxlength="500" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="报备科室" prop="ccdepts"> |
| | | <el-select |
| | | v-model="processForm.ccdepts" |
| | | placeholder="请选择报备科室" |
| | | multiple |
| | | filterable |
| | | collapse-tags |
| | | style="width: 100%" |
| | | :disabled="processForm.handleFlag !== '1'" |
| | | > |
| | | <el-option |
| | | v-for="dept in deptList" |
| | | :key="dept.deptCode" |
| | | :label="dept.label" |
| | | :value="dept.deptCode" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="附件上传"> |
| | | <el-upload |
| | | class="upload-demo" |
| | | action="#" |
| | | :on-preview="handlePreview" |
| | | :on-remove="handleRemove" |
| | | :before-remove="beforeRemove" |
| | | :limit="3" |
| | | :on-exceed="handleExceed" |
| | | :file-list="fileList" |
| | | > |
| | | <el-button size="small" type="primary">点击上传</el-button> |
| | | <div slot="tip" class="el-upload__tip"> |
| | | 支持上传图片、文档等附件,单个文件不超过10MB |
| | | </div> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-form> |
| | | <el-form-item label="处理结果" prop="handleresult"> |
| | | <el-select |
| | | v-model="processForm.handleresult" |
| | | placeholder="请选择处理结果" |
| | | style="width: 100%" |
| | | :disabled="processForm.handleFlag !== '1'" |
| | | > |
| | | <el-option label="已解决" value="resolved" /> |
| | | <el-option label="已解释" value="explained" /> |
| | | <el-option label="已转交" value="transferred" /> |
| | | <el-option label="需改进" value="improvement" /> |
| | | <el-option label="已驳回" value="rejected" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="处理说明" prop="handledesc"> |
| | | <el-input |
| | | v-model="processForm.handledesc" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入处理说明(最多500字)" |
| | | maxlength="500" |
| | | show-word-limit |
| | | :disabled="processForm.handleFlag !== '1'" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="最终意见" |
| | | prop="finaloption" |
| | | v-if="hasQualityPermission" |
| | | > |
| | | <el-input |
| | | v-model="processForm.finaloption" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请输入最终处理意见(最多300字)" |
| | | maxlength="300" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- 右半部分:流转时间线(选中流转后展开) --> |
| | | <div class="flow-right" v-if="processForm.isFlow === '1'"> |
| | | <el-timeline> |
| | | <el-timeline-item type="primary" placement="top" color="#5788fe"> |
| | | <div class="flow-step-title">第一步 · 流转发起</div> |
| | | <el-form label-width="90px" size="small" class="flow-step-form"> |
| | | <el-form-item label="流转人"> |
| | | <el-input |
| | | v-model="processForm.flowPerson" |
| | | placeholder="流转人(默认当前用户)" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="流转人编号"> |
| | | <el-input |
| | | v-model="processForm.flowPersonCode" |
| | | placeholder="流转人编号(默认当前用户工号)" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="流转时间"> |
| | | <el-date-picker |
| | | v-model="processForm.flowTime" |
| | | type="datetime" |
| | | value-format="yyyy-MM-dd HH:mm:ss" |
| | | placeholder="流转时间" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="流转说明"> |
| | | <el-input |
| | | v-model="processForm.flowDesc" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="请输入流转说明" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="流转科室"> |
| | | <el-select |
| | | v-model="processForm.flowDepts" |
| | | multiple |
| | | filterable |
| | | collapse-tags |
| | | placeholder="请选择流转科室" |
| | | style="width: 100%" |
| | | @change="onFlowDeptChange" |
| | | > |
| | | <el-option |
| | | v-for="dept in deptList" |
| | | :key="dept.deptCode" |
| | | :label="dept.label" |
| | | :value="dept.deptCode" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="流转科室code"> |
| | | <el-select |
| | | v-model="processForm.flowDeptCodes" |
| | | multiple |
| | | disabled |
| | | placeholder="选择科室后自动生成" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="code in processForm.flowDeptCodes" |
| | | :key="code" |
| | | :label="code" |
| | | :value="code" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="推送负责人"> |
| | | <el-select |
| | | v-model="processForm.pushUsers" |
| | | multiple |
| | | filterable |
| | | collapse-tags |
| | | placeholder="请选择推送负责人" |
| | | style="width: 100%" |
| | | @change="onPushUserChange" |
| | | > |
| | | <el-option |
| | | v-for="u in userList" |
| | | :key="u.userId" |
| | | :label="u.nickName || u.userName" |
| | | :value="u.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-timeline-item> |
| | | |
| | | <el-timeline-item type="primary" placement="top" color="#e6a23c"> |
| | | <div class="flow-step-title">第二步 · 科室反馈</div> |
| | | <el-table |
| | | :data="processForm.flowFeedbacks" |
| | | border |
| | | size="mini" |
| | | v-if="processForm.flowFeedbacks.length > 0" |
| | | > |
| | | <el-table-column |
| | | label="科室" |
| | | prop="deptName" |
| | | width="120" |
| | | align="center" |
| | | /> |
| | | <el-table-column label="是否同意" width="130" align="center"> |
| | | <template slot-scope="scope"> |
| | | <el-select |
| | | v-model="scope.row.agree" |
| | | placeholder="请选择" |
| | | size="mini" |
| | | > |
| | | <el-option label="同意" value="1" /> |
| | | <el-option label="不同意" value="0" /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="具体说明" min-width="200" align="center"> |
| | | <template slot-scope="scope"> |
| | | <el-input |
| | | v-model="scope.row.desc" |
| | | placeholder="请输入具体说明" |
| | | size="mini" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div v-else class="flow-empty-tip"> |
| | | 请先在第一步选择流转科室 |
| | | </div> |
| | | </el-timeline-item> |
| | | |
| | | <el-timeline-item type="primary" placement="top" color="#67c23a"> |
| | | <div class="flow-step-title">第三步 · 处理结果</div> |
| | | <el-form label-width="90px" size="small" class="flow-step-form"> |
| | | <el-form-item label="处理人"> |
| | | <el-input |
| | | v-model="processForm.handleBy" |
| | | placeholder="处理人" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="处理时间"> |
| | | <el-date-picker |
| | | v-model="processForm.handleTime" |
| | | type="datetime" |
| | | value-format="yyyy-MM-dd HH:mm:ss" |
| | | placeholder="处理时间" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | </div> |
| | | </div> |
| | | <span slot="footer" class="dialog-footer"> |
| | | <el-button @click="processDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="submitProcess" :loading="processing"> |
| | |
| | | label-width="100px" |
| | | size="medium" |
| | | > |
| | | <el-form-item label="处理状态" prop="status"> |
| | | <el-form-item label="处理状态" prop="handleFlag"> |
| | | <el-select |
| | | v-model="batchProcessForm.status" |
| | | v-model="batchProcessForm.handleFlag" |
| | | placeholder="请选择处理状态" |
| | | style="width: 100%" |
| | | > |
| | | <el-option label="处理中" :value="1" /> |
| | | <el-option label="已处理" :value="2" /> |
| | | <el-option label="已驳回" :value="3" /> |
| | | <el-option label="已处理" :value="'1'" /> |
| | | <el-option label="取消处理" :value="'0'" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="报备科室" prop="reportDepts"> |
| | | <el-form-item label="报备科室" prop="ccdepts"> |
| | | <el-select |
| | | v-model="batchProcessForm.reportDepts" |
| | | v-model="batchProcessForm.ccdepts" |
| | | placeholder="请选择报备科室" |
| | | multiple |
| | | filterable |
| | | collapse-tags |
| | | style="width: 100%" |
| | | :disabled="batchProcessForm.handleFlag !== '1'" |
| | | > |
| | | <el-option |
| | | v-for="dept in deptList" |
| | | :key="dept.id" |
| | | :label="dept.name" |
| | | :value="dept.id" |
| | | :key="dept.deptCode" |
| | | :label="dept.label" |
| | | :value="dept.deptCode" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="处理备注" prop="remark"> |
| | | <el-form-item label="处理结果" prop="handleresult"> |
| | | <el-select |
| | | v-model="batchProcessForm.handleresult" |
| | | placeholder="请选择处理结果" |
| | | style="width: 100%" |
| | | :disabled="batchProcessForm.handleFlag !== '1'" |
| | | > |
| | | <el-option label="已解决" value="resolved" /> |
| | | <el-option label="已解释" value="explained" /> |
| | | <el-option label="已转交" value="transferred" /> |
| | | <el-option label="需改进" value="improvement" /> |
| | | <el-option label="已驳回" value="rejected" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="处理说明" prop="handledesc"> |
| | | <el-input |
| | | v-model="batchProcessForm.remark" |
| | | v-model="batchProcessForm.handledesc" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入处理备注(最多500字)" |
| | | placeholder="请输入处理说明(最多500字)" |
| | | maxlength="500" |
| | | show-word-limit |
| | | :disabled="batchProcessForm.handleFlag !== '1'" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | @click="submitBatchProcess" |
| | | :loading="batchProcessing" |
| | | > |
| | | 批量提交 |
| | | 批量提交 ({{ selectedExceptionIds.length }}) |
| | | </el-button> |
| | | </span> |
| | | </el-dialog> |
| | | <!-- 进度对话框 --> |
| | | <el-dialog |
| | | title="批量处理进度" |
| | | :visible.sync="batchProgress.visible" |
| | | width="400px" |
| | | :close-on-click-modal="false" |
| | | :show-close="false" |
| | | :close-on-press-escape="false" |
| | | > |
| | | <div class="progress-content"> |
| | | <el-progress |
| | | :percentage="batchProgress.percentage" |
| | | :status="batchProgress.percentage === 100 ? 'success' : ''" |
| | | /> |
| | | <div class="progress-info"> |
| | | 已处理 {{ batchProgress.processed }}/{{ batchProgress.total }} 条记录 |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | <!-- 异常详情弹框 --> |
| | | <Details-anomaly |
| | | :visible="detailDialogVisible" |
| | | :record-id="selectedRecordId" |
| | | :title="detailDialogTitle" |
| | | :record-data="selectedRecordData" |
| | | @update:visible="handleDetailDialogClose" |
| | | @processed="handleProcessed" |
| | | @close="handleDetailDialogClose" |
| | | /> |
| | | |
| | | <!-- 录音播放器 --> |
| | | <audio |
| | | v-if="audioUrl" |
| | | :src="audioUrl" |
| | | ref="audioPlayer" |
| | | controls |
| | | style="display: none" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import DetailsAnomaly from "./components/DetailsAnomaly.vue"; |
| | | import { tracelist, traceedit } from "@/api/AiCentre/index"; |
| | | import dayjs from "dayjs"; |
| | | import { deptTreeSelect, listUser } from "@/api/system/user"; |
| | | |
| | | export default { |
| | | name: "BatchProcess", |
| | | components: { |
| | |
| | | }, |
| | | data() { |
| | | return { |
| | | // 添加以下数据 |
| | | // 详情弹框相关 |
| | | detailDialogVisible: false, |
| | | selectedRecordId: null, |
| | | selectedRecordData: null, |
| | | detailDialogTitle: "异常反馈详情", |
| | | |
| | | // 音频播放 |
| | | audioUrl: "", |
| | | |
| | | // 当前处理的异常ID |
| | | currentExceptionId: null, |
| | | |
| | |
| | | |
| | | // 过滤参数 |
| | | filterParams: { |
| | | deptId: "", |
| | | status: "", |
| | | todeptcode: "", |
| | | handleFlag: "", |
| | | templateType: null, |
| | | scriptids: null, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }, |
| | |
| | | processing: false, |
| | | batchProcessing: false, |
| | | |
| | | // 权限控制 |
| | | hasQualityPermission: false, // 是否具有质管权限 |
| | | |
| | | // 科室列表 |
| | | deptList: [ |
| | | { id: 1, name: "心血管内科" }, |
| | | { id: 2, name: "神经内科" }, |
| | | { id: 3, name: "普外科" }, |
| | | { id: 4, name: "骨科" }, |
| | | { id: 5, name: "妇产科" }, |
| | | { id: 6, name: "儿科" }, |
| | | { id: 7, name: "急诊科" }, |
| | | { id: 8, name: "呼吸内科" }, |
| | | { id: 9, name: "消化内科" }, |
| | | { id: 10, name: "内分泌科" }, |
| | | ], |
| | | deptList: [], |
| | | // 用户列表(流转负责人候选) |
| | | userList: [], |
| | | |
| | | // 异常列表数据 |
| | | exceptionList: [], |
| | |
| | | // 处理对话框 |
| | | processDialogVisible: false, |
| | | processForm: { |
| | | status: "", |
| | | reportDepts: [], |
| | | remark: "", |
| | | handleFlag: "", |
| | | ccdepts: [], |
| | | handleresult: "", |
| | | handledesc: "", |
| | | finaloption: "", |
| | | // 流转相关 |
| | | isFlow: "0", |
| | | flowPerson: "", |
| | | flowPersonCode: "", |
| | | flowTime: "", |
| | | flowDesc: "", |
| | | flowDepts: [], |
| | | flowDeptNames: [], |
| | | flowDeptCodes: [], |
| | | pushUsers: [], |
| | | pushUserNames: [], |
| | | pushUserNos: [], |
| | | flowFeedbacks: [], |
| | | handleBy: "", |
| | | handleTime: "", |
| | | }, |
| | | batchProgress: { |
| | | visible: false, |
| | | percentage: 0, |
| | | processed: 0, |
| | | total: 0, |
| | | }, |
| | | processRules: { |
| | | status: [ |
| | | handleFlag: [ |
| | | { required: true, message: "请选择处理状态", trigger: "change" }, |
| | | ], |
| | | remark: [ |
| | | { required: true, message: "请输入处理备注", trigger: "blur" }, |
| | | handleresult: [ |
| | | { |
| | | min: 5, |
| | | max: 500, |
| | | message: "备注长度在 5 到 500 个字符", |
| | | required: true, |
| | | message: "请选择处理结果", |
| | | trigger: "change", |
| | | validator: (rule, value, callback) => { |
| | | if (this.processForm.handleFlag === "1" && !value) { |
| | | callback(new Error("请选择处理结果")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | | handledesc: [ |
| | | { |
| | | required: true, |
| | | message: "请输入处理说明", |
| | | trigger: "blur", |
| | | validator: (rule, value, callback) => { |
| | | if ( |
| | | this.processForm.handleFlag === "1" && |
| | | (!value || value.trim().length < 3) |
| | | ) { |
| | | callback(new Error("处理说明至少3个字符")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | fileList: [], |
| | | |
| | | // 批量处理对话框 |
| | | batchDialogVisible: false, |
| | | batchProcessForm: { |
| | | status: "", |
| | | reportDepts: [], |
| | | remark: "", |
| | | handleFlag: "", |
| | | ccdepts: [], |
| | | handleresult: "", |
| | | handledesc: "", |
| | | }, |
| | | }; |
| | | }, |
| | | |
| | | created() { |
| | | // 从路由参数获取问题ID |
| | | this.filterParams.scriptids = |
| | | this.$route.query.questionId || this.$route.query.questionIds || null; |
| | | // if (this.$route.query.questionId) { |
| | | // } else if (this.$route.query.questionIds) { |
| | | // console.log( |
| | | // this.$route.query.questionIds, |
| | | // "this.$route.query.questionIds" |
| | | // ); |
| | | |
| | | this.filterParams.templateType = Number(this.$route.query.type) || null; |
| | | |
| | | // this.filterParams.scriptid = null; |
| | | // } |
| | | this.hasQualityPermission = this.checkQualityPermission(); |
| | | }, |
| | | |
| | | mounted() { |
| | | this.loadExceptionList(); |
| | | this.getDeptOptions(); |
| | | this.getUserOptions(); |
| | | }, |
| | | |
| | | methods: { |
| | | // 加载异常列表 |
| | | async loadExceptionList() { |
| | | this.loading = true; |
| | | // 格式化日期时间 |
| | | formatDateTime(dateTime) { |
| | | if (!dateTime) return ""; |
| | | try { |
| | | // Mock 数据 |
| | | await new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | this.exceptionList = [ |
| | | { |
| | | id: 1, |
| | | responsibilityDept: "心血管内科", |
| | | unsatisfactoryDetail: |
| | | "医生查房时间太短,沟通不够充分,对病情解释不够详细", |
| | | patientName: "张先生", |
| | | gender: 1, |
| | | age: 45, |
| | | phone: "138****1234", |
| | | dischargeDept: "心血管内科", |
| | | dischargeWard: "内科一病区", |
| | | fillTime: "2024-01-15 10:30:25", |
| | | processStatus: 0, |
| | | questionnaireId: 1001, |
| | | }, |
| | | { |
| | | id: 2, |
| | | responsibilityDept: "神经内科", |
| | | unsatisfactoryDetail: |
| | | "护士打针技术不佳,扎了三次才成功,且态度不够耐心", |
| | | patientName: "李女士", |
| | | gender: 0, |
| | | age: 38, |
| | | phone: "139****5678", |
| | | dischargeDept: "神经内科", |
| | | dischargeWard: "内科二病区", |
| | | fillTime: "2024-01-14 16:20:10", |
| | | processStatus: 0, |
| | | questionnaireId: 1002, |
| | | }, |
| | | { |
| | | id: 3, |
| | | responsibilityDept: "普外科", |
| | | unsatisfactoryDetail: "术后换药不及时,伤口疼痛时没有及时处理", |
| | | patientName: "王先生", |
| | | gender: 1, |
| | | age: 52, |
| | | phone: "137****9012", |
| | | dischargeDept: "普外科", |
| | | dischargeWard: "外科一病区", |
| | | fillTime: "2024-01-13 09:15:45", |
| | | processStatus: 1, |
| | | questionnaireId: 1003, |
| | | }, |
| | | { |
| | | id: 4, |
| | | responsibilityDept: "骨科", |
| | | unsatisfactoryDetail: "康复指导不够专业,对恢复过程描述不清楚", |
| | | patientName: "刘女士", |
| | | gender: 0, |
| | | age: 65, |
| | | phone: "136****3456", |
| | | dischargeDept: "骨科", |
| | | dischargeWard: "外科二病区", |
| | | fillTime: "2024-01-12 14:40:30", |
| | | processStatus: 0, |
| | | questionnaireId: 1004, |
| | | }, |
| | | { |
| | | id: 5, |
| | | responsibilityDept: "妇产科", |
| | | unsatisfactoryDetail: |
| | | "产前检查排队时间过长,等待期间没有休息座位", |
| | | patientName: "陈女士", |
| | | gender: 0, |
| | | age: 28, |
| | | phone: "135****7890", |
| | | dischargeDept: "妇产科", |
| | | dischargeWard: "妇产科病区", |
| | | fillTime: "2024-01-11 11:25:15", |
| | | processStatus: 2, |
| | | questionnaireId: 1005, |
| | | }, |
| | | { |
| | | id: 6, |
| | | responsibilityDept: "儿科", |
| | | unsatisfactoryDetail: |
| | | "儿童用药剂量交代不清晰,用药注意事项没有说明", |
| | | patientName: "赵宝宝", |
| | | gender: 1, |
| | | age: 5, |
| | | phone: "134****1234", |
| | | dischargeDept: "儿科", |
| | | dischargeWard: "儿科病区", |
| | | fillTime: "2024-01-10 15:50:20", |
| | | processStatus: 0, |
| | | questionnaireId: 1006, |
| | | }, |
| | | { |
| | | id: 7, |
| | | responsibilityDept: "急诊科", |
| | | unsatisfactoryDetail: "急诊等待时间过长,病情没有得到及时评估", |
| | | patientName: "孙先生", |
| | | gender: 1, |
| | | age: 40, |
| | | phone: "133****5678", |
| | | dischargeDept: "急诊科", |
| | | dischargeWard: "急诊病区", |
| | | fillTime: "2024-01-09 10:15:40", |
| | | processStatus: 0, |
| | | questionnaireId: 1007, |
| | | }, |
| | | { |
| | | id: 8, |
| | | responsibilityDept: "呼吸内科", |
| | | unsatisfactoryDetail: "医生开药较多,费用较高,没有说明必要性", |
| | | patientName: "周女士", |
| | | gender: 0, |
| | | age: 55, |
| | | phone: "132****9012", |
| | | dischargeDept: "呼吸内科", |
| | | dischargeWard: "内科一病区", |
| | | fillTime: "2024-01-08 13:30:55", |
| | | processStatus: 1, |
| | | questionnaireId: 1008, |
| | | }, |
| | | ]; |
| | | this.total = this.exceptionList.length; |
| | | resolve(); |
| | | }, 500); |
| | | const date = new Date(dateTime); |
| | | if (isNaN(date.getTime())) { |
| | | return dateTime; |
| | | } |
| | | return ( |
| | | date.toLocaleDateString().replace(/\//g, "-") + |
| | | " " + |
| | | date.toTimeString().split(" ")[0] |
| | | ); |
| | | } catch (error) { |
| | | console.error("日期格式化错误:", error); |
| | | return dateTime; |
| | | } |
| | | }, |
| | | /** 查询科室列表 */ |
| | | getDeptOptions() { |
| | | deptTreeSelect() |
| | | .then((res) => { |
| | | if (res.code == 200) { |
| | | this.deptList = this.flattenArray(res.data) || []; |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | console.error("获取科室列表失败:", error); |
| | | this.$message.error("获取科室列表失败"); |
| | | }); |
| | | } finally { |
| | | this.loading = false; |
| | | }, |
| | | /** 查询用户列表(流转负责人候选) */ |
| | | getUserOptions() { |
| | | listUser({ pageNum: 1, pageSize: 999 }) |
| | | .then((res) => { |
| | | if (res.code == 200) { |
| | | this.userList = res.rows || []; |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | console.error("获取用户列表失败:", error); |
| | | this.$message.error("获取用户列表失败"); |
| | | }); |
| | | }, |
| | | flattenArray(multiArray) { |
| | | let result = []; |
| | | |
| | | function flatten(element) { |
| | | if (element.children && element.children.length > 0) { |
| | | element.children.forEach((child) => flatten(child)); |
| | | } else { |
| | | let item = JSON.parse(JSON.stringify(element)); |
| | | result.push(item); |
| | | } |
| | | } |
| | | |
| | | multiArray.forEach((element) => flatten(element)); |
| | | return result; |
| | | }, |
| | | // 解析患者描述信息 |
| | | parsePatDesc(patdesc) { |
| | | if (!patdesc) return []; |
| | | |
| | | try { |
| | | const parts = patdesc.split("|"); |
| | | const items = []; |
| | | |
| | | if (parts[0]) items.push({ label: "姓名", value: parts[0] }); |
| | | if (parts[1]) items.push({ label: "电话", value: parts[1] }); |
| | | if (parts[2]) items.push({ label: "科室", value: parts[2] }); |
| | | |
| | | return items; |
| | | } catch (error) { |
| | | console.error("解析患者信息失败:", error); |
| | | return []; |
| | | } |
| | | }, |
| | | |
| | | // 检查质管权限 |
| | | checkQualityPermission() { |
| | | // 这里可以根据实际权限系统实现 |
| | | const userRoles = this.$store.getters.roles || []; |
| | | return ( |
| | | userRoles.includes("quality_manager") || userRoles.includes("admin") |
| | | ); |
| | | }, |
| | | |
| | | // 获取状态标签类型 |
| | | getStatusTagType(status) { |
| | | switch (status) { |
| | | case 0: |
| | | return "warning"; // 待处理 |
| | | case 1: |
| | | return "primary"; // 处理中 |
| | | case 2: |
| | | getStatusTagType(handleFlag) { |
| | | switch (handleFlag) { |
| | | case "0": |
| | | return "warning"; // 未处理 |
| | | case "1": |
| | | return "success"; // 已处理 |
| | | default: |
| | | return "info"; |
| | |
| | | }, |
| | | |
| | | // 获取状态文本 |
| | | getStatusText(status) { |
| | | switch (status) { |
| | | case 0: |
| | | return "待处理"; |
| | | case 1: |
| | | return "处理中"; |
| | | case 2: |
| | | getStatusText(handleFlag) { |
| | | switch (handleFlag) { |
| | | case "0": |
| | | return "未处理"; |
| | | case "1": |
| | | return "已处理"; |
| | | default: |
| | | return "未知"; |
| | | } |
| | | }, |
| | | |
| | | // 播放录音 |
| | | handlePlayAudio(url) { |
| | | this.audioUrl = url; |
| | | this.$nextTick(() => { |
| | | const audioPlayer = this.$refs.audioPlayer; |
| | | if (audioPlayer) { |
| | | audioPlayer.play().catch((error) => { |
| | | console.error("播放失败:", error); |
| | | this.$message.error("音频播放失败"); |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | // 构建查询参数 |
| | | buildQueryParams() { |
| | | const params = { |
| | | pageNum: this.filterParams.pageNum, |
| | | pageSize: this.filterParams.pageSize, |
| | | }; |
| | | |
| | | if (this.filterParams.todeptcode) { |
| | | params.todeptcode = this.filterParams.todeptcode; |
| | | } |
| | | |
| | | if (this.filterParams.handleFlag !== "") { |
| | | params.handleFlag = this.filterParams.handleFlag; |
| | | } |
| | | |
| | | if (this.filterParams.templateType) { |
| | | params.templateType = this.filterParams.templateType; |
| | | } |
| | | |
| | | // if (this.filterParams.scriptid) { |
| | | // params.scriptid = this.filterParams.scriptid; |
| | | // } |
| | | if (this.filterParams.scriptids) { |
| | | params.scriptids = this.filterParams.scriptids.split(","); |
| | | } |
| | | |
| | | return params; |
| | | }, |
| | | |
| | | // 加载异常列表 |
| | | async loadExceptionList() { |
| | | this.loading = true; |
| | | try { |
| | | const params = this.buildQueryParams(); |
| | | const response = await tracelist(params); |
| | | |
| | | if (response && response.code === 200) { |
| | | this.exceptionList = response.rows || []; |
| | | this.total = response.total || 0; |
| | | } else { |
| | | this.exceptionList = []; |
| | | this.total = 0; |
| | | this.$message.error(response?.msg || "加载异常列表失败"); |
| | | } |
| | | } catch (error) { |
| | | console.error("加载异常列表失败:", error); |
| | | this.$message.error("加载异常列表失败,请稍后重试"); |
| | | this.exceptionList = []; |
| | | this.total = 0; |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | |
| | |
| | | // 重置筛选 |
| | | handleResetFilter() { |
| | | this.filterParams = { |
| | | deptId: "", |
| | | status: "", |
| | | todeptcode: "", |
| | | handleFlag: "", |
| | | scriptids: |
| | | this.$route.query.questionId || this.$route.query.questionIds || null, |
| | | templateType: Number(this.$route.query.type) || null, |
| | | |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }; |
| | | this.selectedExceptionIds = []; |
| | | this.loadExceptionList(); |
| | | }, |
| | | |
| | |
| | | this.$message.warning("请先选择要处理的异常反馈"); |
| | | return; |
| | | } |
| | | |
| | | // 重置批量处理表单 |
| | | this.batchProcessForm = { |
| | | handleFlag: "", |
| | | ccdepts: [], |
| | | handleresult: "", |
| | | handledesc: "", |
| | | }; |
| | | |
| | | this.batchDialogVisible = true; |
| | | }, |
| | | |
| | | // 返回异常列表 |
| | | handleGoBack() { |
| | | this.$router.push("/satisfaction/exception/list"); |
| | | // this.$router.push("/satisfaction/exception/list"); |
| | | this.$router.push("/Intelligentcenter/dispose"); |
| | | }, |
| | | |
| | | // 查看详情 |
| | | handleViewDetail(row) { |
| | | this.selectedRecordId = row.id; |
| | | this.detailDialogTitle = `${row.patientName} - 异常反馈详情`; |
| | | this.selectedRecordData = row; |
| | | |
| | | // 生成弹框标题 |
| | | let title = "异常反馈详情"; |
| | | if (row.patdesc) { |
| | | const patientName = row.patdescJson.sendname; |
| | | if (patientName) { |
| | | title = `${patientName} - ${title}`; |
| | | } |
| | | } |
| | | this.detailDialogTitle = title; |
| | | |
| | | this.detailDialogVisible = true; |
| | | }, |
| | | |
| | | // 处理详情弹框关闭 |
| | | handleDetailDialogClose() { |
| | | this.detailDialogVisible = false; |
| | | this.selectedRecordId = null; |
| | | }, // 处理完成后的回调 |
| | | this.selectedRecordData = null; |
| | | }, |
| | | |
| | | // 处理完成后的回调 |
| | | handleProcessed() { |
| | | // 重新加载数据 |
| | | this.loadExceptionList(); |
| | | }, |
| | | |
| | | // 处理单个异常 |
| | | handleProcess(row) { |
| | | this.currentExceptionId = row.id; |
| | | |
| | | const nickName = this.$store.state.user.nickName || ""; |
| | | const now = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | |
| | | // 是否流转(后端 circulationStatus:0 不流转 / 1 流转) |
| | | const isFlow = String(row.circulationStatus) === "1" ? "1" : "0"; |
| | | |
| | | // 报备科室(兼容数组或逗号分隔字符串) |
| | | const ccdepts = Array.isArray(row.ccdepts) |
| | | ? row.ccdepts |
| | | : row.ccdepts |
| | | ? row.ccdepts.split(",") |
| | | : []; |
| | | |
| | | // 流转科室 code / 名称 |
| | | const flowDeptCodes = row.circulationDeptCode |
| | | ? row.circulationDeptCode.split(",") |
| | | : []; |
| | | const flowDeptNames = row.circulationDeptName |
| | | ? row.circulationDeptName.split(",") |
| | | : []; |
| | | |
| | | // 推送负责人:后端存工号(userName),下拉 value 用 userId,需回映 |
| | | const pushUserNos = row.circulationDeptPersonCode |
| | | ? row.circulationDeptPersonCode.split(",") |
| | | : []; |
| | | const pushUserNames = row.circulationDeptPersonName |
| | | ? row.circulationDeptPersonName.split(",") |
| | | : []; |
| | | const pushUsers = pushUserNos.map((no) => { |
| | | const u = this.userList.find((item) => item.userName === no); |
| | | return u ? u.userId : no; |
| | | }); |
| | | |
| | | // 科室反馈:从 handledesc JSON 中还原 |
| | | let flowFeedbacks = []; |
| | | try { |
| | | const parsed = |
| | | typeof row.handledesc === "string" && |
| | | row.handledesc.trim().startsWith("{") |
| | | ? JSON.parse(row.handledesc) |
| | | : null; |
| | | if (parsed && parsed.flow && Array.isArray(parsed.flow.feedbacks)) { |
| | | flowFeedbacks = parsed.flow.feedbacks; |
| | | } |
| | | } catch (e) { |
| | | // handledesc 非 JSON,忽略 |
| | | } |
| | | |
| | | // 流转时间:后端 Date 字段,统一转成 yyyy-MM-dd HH:mm:ss 供日期选择器回显 |
| | | let flowTime = now; |
| | | if (row.circulationTime) { |
| | | const d = new Date( |
| | | typeof row.circulationTime === "number" |
| | | ? row.circulationTime |
| | | : String(row.circulationTime).replace(" ", "T") |
| | | ); |
| | | if (!isNaN(d.getTime())) { |
| | | const pad = (n) => String(n).padStart(2, "0"); |
| | | flowTime = |
| | | d.getFullYear() + |
| | | "-" + |
| | | pad(d.getMonth() + 1) + |
| | | "-" + |
| | | pad(d.getDate()) + |
| | | " " + |
| | | pad(d.getHours()) + |
| | | ":" + |
| | | pad(d.getMinutes()) + |
| | | ":" + |
| | | pad(d.getSeconds()); |
| | | } |
| | | } |
| | | |
| | | // 初始化表单数据 |
| | | this.processForm = { |
| | | status: row.processStatus === 0 ? 1 : row.processStatus, |
| | | reportDepts: [], |
| | | remark: "", |
| | | handleFlag: "1", |
| | | ccdepts, |
| | | handleresult: row.handleresult || "", |
| | | handledesc: this.formatHandledesc(row.handledesc), |
| | | finaloption: row.finaloption || "", |
| | | // 流转相关 |
| | | isFlow, |
| | | flowPerson: row.circulationPersonName || nickName, |
| | | flowPersonCode: |
| | | row.circulationPersonCode || this.$store.state.user.userName || "", |
| | | flowTime, |
| | | flowDesc: row.circulationExplain || "", |
| | | flowDepts: [...flowDeptCodes], |
| | | flowDeptNames: [...flowDeptNames], |
| | | flowDeptCodes: [...flowDeptCodes], |
| | | pushUsers, |
| | | pushUserNames: [...pushUserNames], |
| | | pushUserNos: [...pushUserNos], |
| | | flowFeedbacks, |
| | | handleBy: nickName, |
| | | handleTime: now, |
| | | }; |
| | | |
| | | this.processDialogVisible = true; |
| | | }, |
| | | |
| | | // 流转科室变化:同步科室名称、科室code,并生成反馈表格 |
| | | onFlowDeptChange(codes) { |
| | | this.processForm.flowDeptCodes = [...codes]; |
| | | this.processForm.flowDeptNames = codes.map((code) => { |
| | | const dept = this.deptList.find((item) => item.deptCode === code); |
| | | return dept ? dept.label : code; |
| | | }); |
| | | // 生成/维护第二步反馈表格(保留已填写的反馈) |
| | | this.processForm.flowFeedbacks = codes.map((code) => { |
| | | const existing = this.processForm.flowFeedbacks.find( |
| | | (item) => item.deptCode === code |
| | | ); |
| | | if (existing) return existing; |
| | | const dept = this.deptList.find((item) => item.deptCode === code); |
| | | return { |
| | | deptCode: code, |
| | | deptName: dept ? dept.label : code, |
| | | agree: "", |
| | | desc: "", |
| | | }; |
| | | }); |
| | | }, |
| | | |
| | | // 推送负责人变化:同步负责人昵称、工号 |
| | | onPushUserChange(ids) { |
| | | this.processForm.pushUserNames = ids.map((id) => { |
| | | const user = this.userList.find((u) => String(u.userId) === String(id)); |
| | | return user ? user.nickName || user.userName : ""; |
| | | }); |
| | | this.processForm.pushUserNos = ids.map((id) => { |
| | | const user = this.userList.find((u) => String(u.userId) === String(id)); |
| | | return user ? user.userName || "" : ""; |
| | | }); |
| | | }, |
| | | |
| | | // 组装 handledesc:流转时以 JSON 形式保存原处理说明与流转说明、科室反馈 |
| | | buildHandledesc() { |
| | | if (this.processForm.isFlow !== "1") { |
| | | return this.processForm.handledesc; |
| | | } |
| | | const flow = { |
| | | feedbacks: this.processForm.flowFeedbacks.map((f) => ({ |
| | | deptCode: f.deptCode, |
| | | deptName: f.deptName, |
| | | agree: f.agree, |
| | | desc: f.desc, |
| | | })), |
| | | }; |
| | | return JSON.stringify({ |
| | | desc: this.processForm.handledesc, |
| | | flow, |
| | | }); |
| | | }, |
| | | |
| | | // 解析 handledesc:若是流转 JSON 则取其中的处理说明,否则原样返回 |
| | | formatHandledesc(val) { |
| | | if (!val) return ""; |
| | | if (typeof val !== "string") return val; |
| | | const trimmed = val.trim(); |
| | | if (trimmed.startsWith("{")) { |
| | | try { |
| | | const obj = JSON.parse(trimmed); |
| | | if (obj && obj.desc != null) return obj.desc; |
| | | } catch (e) { |
| | | // 不是合法 JSON,按原文本返回 |
| | | } |
| | | } |
| | | return val; |
| | | }, |
| | | |
| | | // 流转时间转为带时区的 ISO 格式,供后端 Date 类型字段反序列化 |
| | | toISODateTime(str) { |
| | | if (!str) return null; |
| | | const d = new Date(String(str).replace(" ", "T")); |
| | | if (isNaN(d.getTime())) return null; |
| | | const pad = (n) => String(n).padStart(2, "0"); |
| | | const offset = -d.getTimezoneOffset(); |
| | | const sign = offset >= 0 ? "+" : "-"; |
| | | const abs = Math.abs(offset); |
| | | const tz = sign + pad(Math.floor(abs / 60)) + ":" + pad(abs % 60); |
| | | return ( |
| | | d.getFullYear() + |
| | | "-" + pad(d.getMonth() + 1) + |
| | | "-" + pad(d.getDate()) + |
| | | "T" + pad(d.getHours()) + |
| | | ":" + pad(d.getMinutes()) + |
| | | ":" + pad(d.getSeconds()) + |
| | | "." + String(d.getMilliseconds()).padStart(3, "0") + |
| | | tz |
| | | ); |
| | | }, |
| | | |
| | | // 提交处理 |
| | | async submitProcess() { |
| | | this.$refs.processForm.validate(async (valid) => { |
| | | if (valid) { |
| | | this.processing = true; |
| | | try { |
| | | // Mock API调用 |
| | | await new Promise((resolve) => setTimeout(resolve, 1000)); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | |
| | | this.$message.success("处理提交成功"); |
| | | this.processDialogVisible = false; |
| | | this.loadExceptionList(); |
| | | } finally { |
| | | this.processing = false; |
| | | } |
| | | // 流转时校验必须选择流转科室 |
| | | if ( |
| | | this.processForm.isFlow === "1" && |
| | | (!this.processForm.flowDepts || this.processForm.flowDepts.length === 0) |
| | | ) { |
| | | this.$message.warning("请先选择流转科室"); |
| | | return; |
| | | } |
| | | |
| | | this.processing = true; |
| | | |
| | | try { |
| | | // 准备提交数据 |
| | | const submitData = { |
| | | id: this.currentExceptionId, |
| | | handleFlag: this.processForm.handleFlag, |
| | | handleresult: this.processForm.handleresult, |
| | | handledesc: this.buildHandledesc(), |
| | | finaloption: this.processForm.finaloption, |
| | | handleBy: |
| | | this.processForm.handleBy || this.$store.state.user.nickName, |
| | | handleTime: |
| | | this.processForm.handleTime || |
| | | dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | // 将数组转换为逗号分隔的字符串 |
| | | ccdepts: Array.isArray(this.processForm.ccdepts) |
| | | ? this.processForm.ccdepts.join(",") |
| | | : this.processForm.ccdepts, |
| | | // 流转相关主表字段(后端已定稿) |
| | | circulationPersonName: this.processForm.flowPerson, |
| | | circulationPersonCode: |
| | | this.processForm.flowPersonCode || |
| | | this.$store.state.user.userName || |
| | | "", |
| | | circulationStatus: this.processForm.isFlow, |
| | | circulationDeptName: this.processForm.flowDeptNames.join(","), |
| | | circulationDeptCode: this.processForm.flowDeptCodes.join(","), |
| | | circulationDeptPersonName: this.processForm.pushUserNames.join(","), |
| | | circulationDeptPersonCode: this.processForm.pushUserNos.join(","), |
| | | circulationExplain: this.processForm.flowDesc, |
| | | circulationTime: this.toISODateTime(this.processForm.flowTime), |
| | | }; |
| | | // TODO: 这里需要调用实际的处理接口 |
| | | await traceedit(submitData); |
| | | |
| | | // await new Promise((resolve) => setTimeout(resolve, 1000)); |
| | | |
| | | this.$message.success("处理提交成功"); |
| | | this.processDialogVisible = false; |
| | | this.loadExceptionList(); |
| | | } catch (error) { |
| | | console.error("处理提交失败:", error); |
| | | this.$message.error("处理提交失败,请稍后重试"); |
| | | } finally { |
| | | this.processing = false; |
| | | } |
| | | }); |
| | | }, |
| | |
| | | // 提交批量处理 |
| | | async submitBatchProcess() { |
| | | this.$refs.batchProcessForm.validate(async (valid) => { |
| | | if (valid) { |
| | | this.batchProcessing = true; |
| | | try { |
| | | // Mock API调用 |
| | | await new Promise((resolve) => setTimeout(resolve, 1500)); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | |
| | | this.$message.success( |
| | | `已批量处理 ${this.selectedExceptionIds.length} 条异常反馈` |
| | | this.batchProcessing = true; |
| | | // 显示进度条 |
| | | this.batchProgress = { |
| | | visible: true, |
| | | percentage: 0, |
| | | processed: 0, |
| | | total: this.selectedExceptionIds.length, |
| | | }; |
| | | try { |
| | | // 准备批量提交数据 |
| | | const processData = { |
| | | handleFlag: this.batchProcessForm.handleFlag, |
| | | handleresult: this.batchProcessForm.handleresult, |
| | | handledesc: this.batchProcessForm.handledesc, |
| | | ccdepts: Array.isArray(this.batchProcessForm.ccdepts) |
| | | ? this.batchProcessForm.ccdepts.join(",") |
| | | : this.batchProcessForm.ccdepts, |
| | | }; |
| | | |
| | | // 控制并发数 |
| | | const CONCURRENT_LIMIT = 10; // 同时最多3个请求 |
| | | const totalCount = this.selectedExceptionIds.length; |
| | | const results = []; |
| | | let successCount = 0; |
| | | let failCount = 0; |
| | | |
| | | this.$message.info(`开始批量处理 ${totalCount} 条记录...`); |
| | | |
| | | // 分组处理 |
| | | for ( |
| | | let i = 0; |
| | | i < this.selectedExceptionIds.length; |
| | | i += CONCURRENT_LIMIT |
| | | ) { |
| | | const batchIds = this.selectedExceptionIds.slice( |
| | | i, |
| | | i + CONCURRENT_LIMIT |
| | | ); |
| | | this.batchDialogVisible = false; |
| | | this.selectedExceptionIds = []; |
| | | this.loadExceptionList(); |
| | | } finally { |
| | | this.batchProcessing = false; |
| | | |
| | | // 并发处理当前批次 |
| | | const batchPromises = batchIds.map((id) => |
| | | traceedit({ |
| | | id: id, |
| | | ...processData, |
| | | }) |
| | | .then((result) => ({ |
| | | id, |
| | | success: result && result.code === 200, |
| | | error: result?.msg, |
| | | })) |
| | | .catch((error) => ({ |
| | | id, |
| | | success: false, |
| | | error: error.message, |
| | | })) |
| | | ); |
| | | |
| | | const batchResults = await Promise.all(batchPromises); |
| | | results.push(...batchResults); |
| | | |
| | | // 更新统计 |
| | | batchResults.forEach((result) => { |
| | | if (result.success) { |
| | | successCount++; |
| | | } else { |
| | | failCount++; |
| | | console.error(`处理记录 ${result.id} 失败:`, result.error); |
| | | } |
| | | }); |
| | | // 更新进度 |
| | | this.batchProgress.processed = i + 1; |
| | | this.batchProgress.percentage = Math.round( |
| | | ((i + 1) / totalCount) * 100 |
| | | ); |
| | | // 显示进度 |
| | | console.log( |
| | | `进度: ${Math.min( |
| | | i + CONCURRENT_LIMIT, |
| | | totalCount |
| | | )}/${totalCount}` |
| | | ); |
| | | } |
| | | |
| | | // 处理结果提示 |
| | | if (successCount === totalCount) { |
| | | this.$message.success(`已成功处理全部 ${totalCount} 条异常反馈`); |
| | | } else { |
| | | this.$message.warning( |
| | | `已处理 ${successCount} 条,失败 ${failCount} 条异常反馈` |
| | | ); |
| | | } |
| | | |
| | | this.batchDialogVisible = false; |
| | | this.selectedExceptionIds = []; |
| | | this.loadExceptionList(); |
| | | } catch (error) { |
| | | console.error("批量处理失败:", error); |
| | | this.$message.error("批量处理失败,请稍后重试"); |
| | | } finally { |
| | | this.batchProcessing = false; |
| | | this.batchProgress.visible = false; |
| | | } |
| | | }); |
| | | }, |
| | |
| | | handlePageChange(page) { |
| | | this.filterParams.pageNum = page; |
| | | this.loadExceptionList(); |
| | | }, |
| | | |
| | | // 文件上传相关方法 |
| | | handlePreview(file) { |
| | | console.log("预览文件:", file); |
| | | }, |
| | | |
| | | handleRemove(file, fileList) { |
| | | console.log("移除文件:", file, fileList); |
| | | }, |
| | | |
| | | beforeRemove(file) { |
| | | return this.$confirm(`确定移除 ${file.name}?`); |
| | | }, |
| | | |
| | | handleExceed(files, fileList) { |
| | | this.$message.warning( |
| | | `当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${ |
| | | files.length + fileList.length |
| | | } 个文件` |
| | | ); |
| | | }, |
| | | }, |
| | | }; |
| | |
| | | } |
| | | |
| | | .detail-content { |
| | | font-size: 13px; |
| | | color: #606266; |
| | | line-height: 1.5; |
| | | text-align: left; |
| | | font-size: 12px; |
| | | line-height: 1.5; |
| | | |
| | | .question-text { |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .answer-text { |
| | | color: #f56c6c; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .matched-text { |
| | | color: #e6a23c; |
| | | font-style: italic; |
| | | } |
| | | |
| | | strong { |
| | | color: #606266; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | |
| | | .patient-info { |
| | | .patient-item { |
| | | .patient-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 5px; |
| | | padding: 2px 0; |
| | | margin-bottom: 8px; |
| | | |
| | | .label { |
| | | font-size: 12px; |
| | | color: #606266; |
| | | min-width: 40px; |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 13px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | text-align: right; |
| | | .patient-item { |
| | | flex: 1; |
| | | display: flex; |
| | | justify-content: flex-start; |
| | | align-items: center; |
| | | padding: 0 5px; |
| | | |
| | | &.full-width { |
| | | flex: 1 0 100%; |
| | | margin-left: 0; |
| | | margin-right: 0; |
| | | } |
| | | |
| | | .label { |
| | | font-size: 12px; |
| | | color: #606266; |
| | | margin-right: 5px; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 12px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | text-align: right; |
| | | word-break: break-all; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .discharge-info { |
| | | .fill-info, |
| | | .handle-info { |
| | | font-size: 12px; |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | justify-content: flex-start; |
| | | align-items: center; |
| | | margin-bottom: 5px; |
| | | padding: 2px 0; |
| | | |
| | | .label { |
| | | font-size: 12px; |
| | | color: #606266; |
| | | min-width: 50px; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 13px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | text-align: right; |
| | | // text-align: right; |
| | | flex: 1; |
| | | |
| | | &.time { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | font-size: 11px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | font-style: italic; |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // 处理弹框左右布局 |
| | | .flow-dialog-body { |
| | | display: flex; |
| | | align-items: stretch; |
| | | |
| | | .flow-left { |
| | | flex: 1; |
| | | min-width: 320px; |
| | | padding-right: 24px; |
| | | border-right: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .flow-right { |
| | | flex: 1; |
| | | min-width: 320px; |
| | | padding-left: 24px; |
| | | max-height: 60vh; |
| | | overflow-y: auto; |
| | | |
| | | .flow-step-title { |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 12px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .flow-step-form { |
| | | ::v-deep .el-form-item { |
| | | margin-bottom: 12px; |
| | | } |
| | | } |
| | | |
| | | .flow-empty-tip { |
| | | padding: 20px; |
| | | text-align: center; |
| | | color: #909399; |
| | | font-size: 13px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .batch-process { |
| | | padding: 10px; |