| | |
| | | > |
| | | <el-form-item label="满意度类型" prop="templateid"> |
| | | <el-select |
| | | v-model="queryParams.templateid" |
| | | v-model="queryParams.templateType" |
| | | placeholder="请选择模板" |
| | | clearable |
| | | style="width: 200px" |
| | |
| | | <el-option |
| | | v-for="dept in deptList" |
| | | :key="dept.deptCode" |
| | | :label="dept.deptName" |
| | | :label="dept.label" |
| | | :value="dept.deptCode" |
| | | /> |
| | | </el-select> |
| | |
| | | > |
| | | 批量处理 ({{ selectedIds.length }}) |
| | | </el-button> |
| | | <el-button |
| | | type="info" |
| | | icon="el-icon-download" |
| | | @click="handleExport" |
| | | > |
| | | <el-button type="info" icon="el-icon-download" @click="handleExport"> |
| | | 导出异常数据 |
| | | </el-button> |
| | | <el-button |
| | |
| | | <el-col :span="8"> |
| | | <el-card shadow="never" class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon" style="background: #f0f9ff;"> |
| | | <i class="el-icon-s-claim" style="color: #5788FE;"></i> |
| | | <div class="stat-icon" style="background: #f0f9ff"> |
| | | <i class="el-icon-s-claim" style="color: #5788fe"></i> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-title">总异常数量</div> |
| | | <div class="stat-value">{{ overviewData.totalExceptionCount }}</div> |
| | | <div class="stat-value"> |
| | | {{ overviewData.totalExceptionCount }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | |
| | | <el-col :span="8"> |
| | | <el-card shadow="never" class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon" style="background: #f0f9ff;"> |
| | | <i class="el-icon-s-flag" style="color: #E6A23C;"></i> |
| | | <div class="stat-icon" style="background: #f0f9ff"> |
| | | <i class="el-icon-s-flag" style="color: #e6a23c"></i> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-title">待处理异常</div> |
| | |
| | | <el-col :span="8"> |
| | | <el-card shadow="never" class="stat-card"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon" style="background: #f0f9ff;"> |
| | | <i class="el-icon-check" style="color: #67C23A;"></i> |
| | | <div class="stat-icon" style="background: #f0f9ff"> |
| | | <i class="el-icon-check" style="color: #67c23a"></i> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-title">已处理异常</div> |
| | |
| | | @selection-change="handleSelectionChange" |
| | | class="exception-table" |
| | | > |
| | | <el-table-column |
| | | type="selection" |
| | | width="55" |
| | | align="center" |
| | | /> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | |
| | | <el-table-column |
| | | label="序号" |
| | |
| | | size="mini" |
| | | :type="getTemplateTypeTag(row.templateType)" |
| | | > |
| | | {{ row.templateType === 1 ? '语音模板' : '问卷模板' }} |
| | | {{ row.templateType === 1 ? "语音模板" : "问卷模板" }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="填写情况" |
| | | width="200" |
| | | align="center" |
| | | > |
| | | <el-table-column label="填写情况" width="200" align="center"> |
| | | <template slot-scope="{ row }"> |
| | | <div class="fill-statistics"> |
| | | <div class="stat-item"> |
| | | <span class="stat-label">有效填写:</span> |
| | | <span class="stat-value">{{ row.fillSituation.effectiveFillNum }}</span> |
| | | <span class="stat-value">{{ |
| | | row.fillSituation.effectiveFillNum |
| | | }}</span> |
| | | </div> |
| | | <div class="stat-item"> |
| | | <span class="stat-label">异常填写:</span> |
| | | <span class="stat-value exception-count">{{ row.fillSituation.exceptionFillNum }}</span> |
| | | <span class="stat-value exception-count">{{ |
| | | row.fillSituation.exceptionFillNum |
| | | }}</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column |
| | | label="异常任务" |
| | | width="280" |
| | | align="center" |
| | | > |
| | | <el-table-column label="异常任务" width="280" align="center"> |
| | | <template slot-scope="{ row }"> |
| | | <div class="exception-tasks"> |
| | | <div class="task-category"> |
| | | <div class="task-title">已处理</div> |
| | | <div class="task-count processed">{{ row.exceptionQuesNum.yesDeal }}</div> |
| | | <div class="task-count processed"> |
| | | {{ row.exceptionQuesNum.yesDeal }} |
| | | </div> |
| | | </div> |
| | | <div class="task-category"> |
| | | <div class="task-title">待处理</div> |
| | | <div class="task-count pending">{{ row.exceptionQuesNum.noDeal }}</div> |
| | | <div class="task-count pending"> |
| | | {{ row.exceptionQuesNum.noDeal }} |
| | | </div> |
| | | </div> |
| | | <div class="task-category"> |
| | | <div class="task-title">异常总数</div> |
| | | <div class="task-count total">{{ row.exceptionQuesNum.all }}</div> |
| | | <div class="task-count total"> |
| | | {{ row.exceptionQuesNum.all }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | > |
| | | <template slot-scope="{ row }"> |
| | | <div v-if="row.handleTime" class="last-process"> |
| | | <div class="process-time">{{ formatDateTime(row.handleTime) }}</div> |
| | | <div class="process-user">{{ row.handleBy || '系统处理' }}</div> |
| | | <div class="process-time"> |
| | | {{ formatDateTime(row.handleTime) }} |
| | | </div> |
| | | <div class="process-user">{{ row.handleBy || "系统处理" }}</div> |
| | | </div> |
| | | <span v-else class="no-process">暂无处理记录</span> |
| | | </template> |
| | |
| | | |
| | | <script> |
| | | import { tracedeallist } from "@/api/AiCentre/index"; |
| | | import { deptTreeSelect } from "@/api/system/user"; |
| | | |
| | | export default { |
| | | name: 'ExceptionList', |
| | | name: "ExceptionList", |
| | | data() { |
| | | return { |
| | | // 查询参数 |
| | | queryParams: { |
| | | todeptcode: [], // 处理科室编号数组 |
| | | todeptname: '', // 处理科室名称 |
| | | templateid: '', // 任务模板ID |
| | | handleStartTime: '', // 处理开始时间 |
| | | handleEndTime: '', // 处理结束时间 |
| | | todeptname: "", // 处理科室名称 |
| | | templateType: 2, // 任务模板ID |
| | | handleStartTime: "", // 处理开始时间 |
| | | handleEndTime: "", // 处理结束时间 |
| | | handleTimeRange: [], // 时间范围,用于界面展示 |
| | | pageNum: 1, |
| | | pageSize: 10 |
| | | pageSize: 10, |
| | | }, |
| | | |
| | | // 加载状态 |
| | |
| | | |
| | | // 模板列表 |
| | | templateList: [ |
| | | { id: 1, name: '语音模板' }, |
| | | { id: 2, name: '问卷模板' } |
| | | { id: 1, name: "语音满意度" }, |
| | | { id: 2, name: "问卷满意度" }, |
| | | // 你可以根据实际情况从接口获取模板列表 |
| | | ], |
| | | |
| | | // 科室列表 |
| | | deptList: [ |
| | | // 你可以从接口获取科室列表,这里先用静态数据 |
| | | { deptCode: '001', deptName: '心血管内科' }, |
| | | { deptCode: '002', deptName: '神经内科' }, |
| | | { deptCode: '003', deptName: '普外科' }, |
| | | { deptCode: '004', deptName: '骨科' }, |
| | | { deptCode: '005', deptName: '妇产科' }, |
| | | { deptCode: '006', deptName: '儿科' }, |
| | | { deptCode: '007', deptName: '急诊科' }, |
| | | { deptCode: '008', deptName: '呼吸内科' }, |
| | | { deptCode: '009', deptName: '消化内科' }, |
| | | { deptCode: '010', deptName: '内分泌科' }, |
| | | { deptCode: '011', deptName: '肾内科' }, |
| | | { deptCode: '012', deptName: '肿瘤科' } |
| | | ], |
| | | deptList: [], |
| | | |
| | | // 异常列表数据 |
| | | exceptionList: [], |
| | |
| | | totalExceptionCount: 0, |
| | | pendingCount: 0, |
| | | processedCount: 0, |
| | | todayProcessedCount: 0 |
| | | todayProcessedCount: 0, |
| | | }, |
| | | |
| | | // 日期选择器选项 |
| | | pickerOptions: { |
| | | shortcuts: [ |
| | | { |
| | | text: '最近一周', |
| | | text: "最近一周", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); |
| | | picker.$emit('pick', [start, end]); |
| | | } |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | { |
| | | text: '最近一个月', |
| | | text: "最近一个月", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); |
| | | picker.$emit('pick', [start, end]); |
| | | } |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | { |
| | | text: '最近三个月', |
| | | text: "最近三个月", |
| | | onClick(picker) { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | | start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); |
| | | picker.$emit('pick', [start, end]); |
| | | } |
| | | } |
| | | picker.$emit("pick", [start, end]); |
| | | }, |
| | | }, |
| | | ], |
| | | disabledDate(time) { |
| | | return time.getTime() > Date.now(); |
| | | } |
| | | } |
| | | }, |
| | | }, |
| | | }; |
| | | }, |
| | | |
| | | mounted() { |
| | | this.loadData(); |
| | | this.getDeptOptions(); |
| | | }, |
| | | |
| | | methods: { |
| | | // 格式化日期时间 |
| | | formatDateTime(dateTime) { |
| | | if (!dateTime) return ''; |
| | | if (!dateTime) return ""; |
| | | const date = new Date(dateTime); |
| | | return date.toLocaleDateString().replace(/\//g, '-') + ' ' + |
| | | date.toTimeString().split(' ')[0]; |
| | | return ( |
| | | date.toLocaleDateString().replace(/\//g, "-") + |
| | | " " + |
| | | date.toTimeString().split(" ")[0] |
| | | ); |
| | | }, |
| | | |
| | | // 获取模板类型标签样式 |
| | | getTemplateTypeTag(type) { |
| | | return type === 1 ? 'primary' : 'success'; |
| | | return type === 1 ? "primary" : "success"; |
| | | }, |
| | | |
| | | // 构建查询参数 |
| | | buildQueryParams() { |
| | | const params = { |
| | | pageNum: this.queryParams.pageNum, |
| | | pageSize: this.queryParams.pageSize |
| | | pageSize: this.queryParams.pageSize, |
| | | }; |
| | | |
| | | // 处理科室编号 |
| | | if (this.queryParams.todeptcode && this.queryParams.todeptcode.length > 0) { |
| | | if ( |
| | | this.queryParams.todeptcode && |
| | | this.queryParams.todeptcode.length > 0 |
| | | ) { |
| | | // 接口可能需要字符串格式的科室编号,根据实际情况调整 |
| | | params.todeptcode = this.queryParams.todeptcode.join(','); |
| | | params.todeptcode = this.queryParams.todeptcode.join(","); |
| | | } |
| | | |
| | | // 模板ID |
| | | if (this.queryParams.templateid) { |
| | | params.templateid = this.queryParams.templateid; |
| | | if (this.queryParams.templateType) { |
| | | params.templateType = this.queryParams.templateType; |
| | | } |
| | | |
| | | // 处理时间范围 |
| | | if (this.queryParams.handleTimeRange && this.queryParams.handleTimeRange.length === 2) { |
| | | if ( |
| | | this.queryParams.handleTimeRange && |
| | | this.queryParams.handleTimeRange.length === 2 |
| | | ) { |
| | | params.handleStartTime = this.queryParams.handleTimeRange[0]; |
| | | params.handleEndTime = this.queryParams.handleTimeRange[1]; |
| | | } |
| | | |
| | | return params; |
| | | }, |
| | | /** 查询科室列表 */ |
| | | getDeptOptions() { |
| | | deptTreeSelect() |
| | | .then((res) => { |
| | | if (res.code == 200) { |
| | | this.deptList = this.flattenArray(res.data) || []; |
| | | } |
| | | }) |
| | | .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; |
| | | }, |
| | | // 加载数据 |
| | | async loadData() { |
| | | this.loading = true; |
| | | try { |
| | | await Promise.all([ |
| | | this.loadExceptionList(), |
| | | this.loadOverviewData() |
| | | ]); |
| | | await Promise.all([this.loadExceptionList(), this.loadOverviewData()]); |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | |
| | | |
| | | if (response.code==200) { |
| | | this.exceptionList = response.rows.detailTraceDealDTOList || []; |
| | | this.overviewData.totalExceptionCount=response.rows.totalException |
| | | this.overviewData.pendingCount=response.rows.noDealException |
| | | this.overviewData.processedCount=response.rows.yesDealException |
| | | this.overviewData.totalExceptionCount = response.rows.totalException; |
| | | this.overviewData.pendingCount = response.rows.noDealException; |
| | | this.overviewData.processedCount = response.rows.yesDealException; |
| | | this.total = response.total || 0; |
| | | } else { |
| | | this.exceptionList = []; |
| | | this.total = 0; |
| | | } |
| | | } catch (error) { |
| | | console.error('加载异常列表失败:', error); |
| | | this.$message.error('加载异常列表失败,请稍后重试'); |
| | | console.error("加载异常列表失败:", error); |
| | | this.$message.error("加载异常列表失败,请稍后重试"); |
| | | this.exceptionList = []; |
| | | this.total = 0; |
| | | } |
| | |
| | | async loadOverviewData() { |
| | | try { |
| | | // 从接口数据计算统计数据 |
| | | const totalExceptionCount = this.exceptionList.reduce((sum, item) => |
| | | sum + (item.exceptionQuesNum?.all || 0), 0); |
| | | const pendingCount = this.exceptionList.reduce((sum, item) => |
| | | sum + (item.exceptionQuesNum?.noDeal || 0), 0); |
| | | const processedCount = this.exceptionList.reduce((sum, item) => |
| | | sum + (item.exceptionQuesNum?.yesDeal || 0), 0); |
| | | const totalExceptionCount = this.exceptionList.reduce( |
| | | (sum, item) => sum + (item.exceptionQuesNum?.all || 0), |
| | | 0 |
| | | ); |
| | | const pendingCount = this.exceptionList.reduce( |
| | | (sum, item) => sum + (item.exceptionQuesNum?.noDeal || 0), |
| | | 0 |
| | | ); |
| | | const processedCount = this.exceptionList.reduce( |
| | | (sum, item) => sum + (item.exceptionQuesNum?.yesDeal || 0), |
| | | 0 |
| | | ); |
| | | |
| | | // 计算今日处理数(这里可以根据实际需求调整逻辑) |
| | | const today = new Date().toISOString().split('T')[0]; |
| | | const todayProcessedCount = this.exceptionList.filter(item => { |
| | | const today = new Date().toISOString().split("T")[0]; |
| | | const todayProcessedCount = this.exceptionList.filter((item) => { |
| | | if (!item.handleTime) return false; |
| | | const handleDate = new Date(item.handleTime).toISOString().split('T')[0]; |
| | | const handleDate = new Date(item.handleTime) |
| | | .toISOString() |
| | | .split("T")[0]; |
| | | return handleDate === today; |
| | | }).length; |
| | | |
| | |
| | | totalExceptionCount, |
| | | pendingCount, |
| | | processedCount, |
| | | todayProcessedCount |
| | | todayProcessedCount, |
| | | }; |
| | | } catch (error) { |
| | | console.error('加载概览数据失败:', error); |
| | | console.error("加载概览数据失败:", error); |
| | | this.overviewData = { |
| | | totalExceptionCount: 0, |
| | | pendingCount: 0, |
| | | processedCount: 0, |
| | | todayProcessedCount: 0 |
| | | todayProcessedCount: 0, |
| | | }; |
| | | } |
| | | }, |
| | |
| | | // 处理批量处理 |
| | | handleBatchProcess() { |
| | | if (this.selectedIds.length === 0) { |
| | | this.$message.warning('请先选择要处理的异常题目'); |
| | | this.$message.warning("请先选择要处理的异常题目"); |
| | | return; |
| | | } |
| | | |
| | | // 跳转到批量处理页面 |
| | | this.$router.push({ |
| | | path: '/Intelligentcenter/batch', |
| | | path: "/Intelligentcenter/batch", |
| | | query: { |
| | | questionIds: this.selectedIds.join(',') |
| | | } |
| | | questionIds: this.selectedIds.join(","), |
| | | type: this.queryParams.templateType, |
| | | }, |
| | | }); |
| | | }, |
| | | |
| | | // 处理导出 |
| | | handleExport() { |
| | | this.$message.success('导出功能开发中...'); |
| | | this.$message.success("导出功能开发中..."); |
| | | }, |
| | | |
| | | // 刷新数据 |
| | | refreshData() { |
| | | this.loadData(); |
| | | this.$message.success('数据已刷新'); |
| | | this.$message.success("数据已刷新"); |
| | | }, |
| | | |
| | | // 处理选择变化 |
| | | handleSelectionChange(selection) { |
| | | this.selectedIds = selection.map(item => item.scriptid); |
| | | this.selectedIds = selection.map((item) => item.scriptid); |
| | | }, |
| | | |
| | | // 处理单个题目批量处理 |
| | | handleBatchQuestion(row) { |
| | | this.$router.push({ |
| | | path: '/Intelligentcenter/batch', |
| | | path: "/Intelligentcenter/batch", |
| | | query: { |
| | | questionId: row.scriptid, |
| | | questionText: encodeURIComponent(row.questiontext) |
| | | } |
| | | type: this.queryParams.templateType, |
| | | }, |
| | | }); |
| | | }, |
| | | |
| | |
| | | handlePageChange(page) { |
| | | this.queryParams.pageNum = page; |
| | | this.loadExceptionList(); |
| | | } |
| | | } |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background: linear-gradient(135deg, #5788FE 0%, #66b1ff 100%); |
| | | background: linear-gradient(135deg, #5788fe 0%, #66b1ff 100%); |
| | | border-radius: 8px; |
| | | color: white; |
| | | |
| | |
| | | } |
| | | |
| | | &.total { |
| | | color: #5788FE; |
| | | color: #5788fe; |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | .process-user { |
| | | font-size: 13px; |
| | | color: #5788FE; |
| | | color: #5788fe; |
| | | font-weight: 500; |
| | | } |
| | | } |