| | |
| | | <!-- 顶部筛选:时间范围 + 科室/病区,控制本页所有数据 --> |
| | | <el-card shadow="never" class="overview-card filter-card"> |
| | | <el-form inline class="filter-form"> |
| | | <el-form-item label="满意度类型"> |
| | | <el-select |
| | | v-model="satisfactionScope" |
| | | size="small" |
| | | style="width: 140px" |
| | | clearable |
| | | placeholder="默认离院" |
| | | @change="fetchData" |
| | | > |
| | | <el-option label="离院满意度" value="leave" /> |
| | | <el-option label="在院满意度" value="hospital" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="出院日期"> |
| | | <el-date-picker |
| | | v-model="queryParams.dateRange" |
| | |
| | | const details = pickDetails(item); |
| | | let node = map.get(title); |
| | | if (!node) { |
| | | node = { title, type: "", options: new Map() }; |
| | | node = { title, type: "", options: new Map(), avgScore: null }; |
| | | map.set(title, node); |
| | | } |
| | | // 综合得分:后端返回的题目级 avgScore(与题目同级) |
| | | if (node.avgScore == null) { |
| | | const rawAvg = Number(item && item.avgScore); |
| | | node.avgScore = isNaN(rawAvg) ? null : rawAvg; |
| | | } |
| | | |
| | | details.forEach((d) => { |
| | |
| | | |
| | | return Array.from(map.values()).map((n) => { |
| | | const options = Array.from(n.options.values()); |
| | | const item = { title: n.title, type: n.type, options }; |
| | | const item = { title: n.title, type: n.type, options, avgScore: n.avgScore }; |
| | | // 多选题:用各选项数量之和作为填报总量(接口未单独提供人次) |
| | | if (n.type === "multiple") |
| | | item.total = options.reduce((s, o) => s + o.count, 0); |
| | |
| | | name: "DataOverview", |
| | | data() { |
| | | return { |
| | | // 满意度类型:leave=离院满意度(离院科室/病区),hospital=在院满意度(在院科室/病区) |
| | | satisfactionScope: "leave", |
| | | // 顶部全局筛选 |
| | | queryParams: { |
| | | dateRange: [], |
| | | deptCodes: [], // 科室编码集合 => leaveldeptcodes |
| | | wardCodes: [], // 出院病区编码集合 => leavehospitaldistrictcodes(接口必填) |
| | | // 病区编码集合:住院满意度按在院病区(hospitaldistrictcodes),其余按出院病区(leavehospitaldistrictcodes) |
| | | wardCodes: [], |
| | | serviceTypeList: [], // 服务类型多选,提交时用逗号拼接 |
| | | deptOrDistrict: "1", // 科室/病区组合方式,默认 1:离院病区 and 离院科室 |
| | | statMode: "ward", // 统计维度:ward=按病区统计 dept=按科室统计 |
| | |
| | | defaultWardCodes() { |
| | | return Array.from(new Set(this.wardOptions.map((w) => w.value))); |
| | | }, |
| | | // 是否按「在院满意度」口径查询:在院满意度用 deptcodes/hospitaldistrictcodes, |
| | | // 离院满意度(以及清空下拉时的默认口径)用 leaveldeptcodes/leavehospitaldistrictcodes |
| | | isInHospitalSatisfaction() { |
| | | return this.satisfactionScope === "hospital"; |
| | | }, |
| | | // 未选科室时的默认查询范围:全部科室编码 |
| | | defaultDeptCodes() { |
| | | return Array.from(new Set(this.deptOptions.map((d) => d.value))); |
| | |
| | | veryBadRate: this.rateOf(veryBad.count, total), |
| | | naCount: na.count, |
| | | naRate: this.rateOf(na.count, total), |
| | | score: this.scoreOfItems(items), |
| | | total, |
| | | score: this.formatScore(q.avgScore), |
| | | }; |
| | | }); |
| | | |
| | |
| | | }); |
| | | }); |
| | | const total = sum.reduce((s, it) => s + it.count, 0); |
| | | // 总计综合得分:按各题填报量加权平均各题 avgScore |
| | | let totalScoreNum = 0; |
| | | let totalScoreDen = 0; |
| | | this.satisfactionQuestions.forEach((q, i) => { |
| | | const avg = Number(q.avgScore); |
| | | const qTotal = rows[i] ? rows[i].total : 0; |
| | | if (!isNaN(avg) && qTotal) { |
| | | totalScoreNum += avg * qTotal; |
| | | totalScoreDen += qTotal; |
| | | } |
| | | }); |
| | | rows.push({ |
| | | category: "总计", |
| | | veryGoodCount: sum[0].count, |
| | |
| | | veryBadRate: this.rateOf(sum[4].count, total), |
| | | naCount: sum[5].count, |
| | | naRate: this.rateOf(sum[5].count, total), |
| | | score: this.scoreOfItems(sum), |
| | | score: totalScoreDen ? (totalScoreNum / totalScoreDen).toFixed(2) : "0.00", |
| | | }); |
| | | return rows; |
| | | }, |
| | |
| | | if (!denominator) return "0.00"; |
| | | return (numerator / denominator).toFixed(2); |
| | | }, |
| | | // 综合得分展示:直接使用后端 avgScore,格式化为两位小数 |
| | | formatScore(val) { |
| | | const n = Number(val); |
| | | return isNaN(n) ? "0.00" : n.toFixed(2); |
| | | }, |
| | | defaultRange() { |
| | | const end = new Date(); |
| | | const start = new Date(); |
| | |
| | | return; |
| | | } |
| | | |
| | | // 病区 => leavehospitaldistrictcodes,科室 => leaveldeptcodes |
| | | // 科室/病区互斥:按病区只传 leavehospitaldistrictcodes,按科室只传 leaveldeptcodes |
| | | // 科室/病区条件随「满意度类型」切换: |
| | | // 在院满意度 => deptcodes / hospitaldistrictcodes(在院科室、在院病区) |
| | | // 离院满意度 => leaveldeptcodes / leavehospitaldistrictcodes(离院科室、离院病区) |
| | | // 科室/病区互斥:按病区只传病区编码,按科室只传科室编码 |
| | | const wards = this.queryParams.wardCodes || []; |
| | | const depts = this.queryParams.deptCodes || []; |
| | | |
| | |
| | | endFinishTime: range[1], |
| | | }; |
| | | if (this.queryParams.statMode === "dept") { |
| | | payload.leaveldeptcodes = depts.length |
| | | const deptCodes = depts.length |
| | | ? Array.from(new Set(depts)) |
| | | : this.defaultDeptCodes; |
| | | if (this.isInHospitalSatisfaction) { |
| | | payload.deptcodes = deptCodes; |
| | | } else { |
| | | payload.leaveldeptcodes = deptCodes; |
| | | } |
| | | } else { |
| | | payload.leavehospitaldistrictcodes = wards.length |
| | | const wardCodes = wards.length |
| | | ? Array.from(new Set(wards)) |
| | | : this.defaultWardCodes; |
| | | if (this.isInHospitalSatisfaction) { |
| | | payload.hospitaldistrictcodes = wardCodes; |
| | | } else { |
| | | payload.leavehospitaldistrictcodes = wardCodes; |
| | | } |
| | | } |
| | | // 服务类型多选:逗号分隔(接口字段为 serviceType 字符串) |
| | | const serviceTypes = this.queryParams.serviceTypeList || []; |
| | |
| | | endFinishTime: range[1], |
| | | }; |
| | | try { |
| | | // 按科室统计(leaveldeptcodes 优先)与按病区统计(leavehospitaldistrictcodes)各查一次 |
| | | // 按科室统计、按病区统计各查一次,条件随「满意度类型」切换: |
| | | // 在院满意度 => deptcodes / hospitaldistrictcodes;离院满意度 => leaveldeptcodes / leavehospitaldistrictcodes |
| | | const deptParam = this.isInHospitalSatisfaction |
| | | ? { deptcodes: this.defaultDeptCodes } |
| | | : { leaveldeptcodes: this.defaultDeptCodes }; |
| | | const wardParam = this.isInHospitalSatisfaction |
| | | ? { hospitaldistrictcodes: this.defaultWardCodes } |
| | | : { leavehospitaldistrictcodes: this.defaultWardCodes }; |
| | | const [deptRes, wardRes] = await Promise.all([ |
| | | statQuestionOptionByKsOrBq({ ...base, leaveldeptcodes: this.defaultDeptCodes }), |
| | | statQuestionOptionByKsOrBq({ ...base, leavehospitaldistrictcodes: this.defaultWardCodes }), |
| | | statQuestionOptionByKsOrBq({ ...base, ...deptParam }), |
| | | statQuestionOptionByKsOrBq({ ...base, ...wardParam }), |
| | | ]); |
| | | // 排查用:打印接口原始返回,便于核对两级嵌套 Map 结构(确认无误后可删除) |
| | | console.log( |