| | |
| | | <el-radio-button label="month">按月</el-radio-button> |
| | | <el-radio-button label="year">按年</el-radio-button> |
| | | </el-radio-group> |
| | | <span class="filter-label">满意度类型:</span> |
| | | <el-select |
| | | v-model="satisfactionScope" |
| | | size="small" |
| | | clearable |
| | | placeholder="默认离院" |
| | | class="filter-scope" |
| | | @change="refreshAll" |
| | | > |
| | | <el-option label="离院满意度" value="leave" /> |
| | | <el-option label="在院满意度" value="hospital" /> |
| | | </el-select> |
| | | <span class="filter-label">统计维度:</span> |
| | | <el-radio-group v-model="queryParams.dimension" size="small" @change="onDimensionChange"> |
| | | <el-radio-button label="ward">按病区统计</el-radio-button> |
| | |
| | | dicts: ['dimensionality_type'], |
| | | data() { |
| | | return { |
| | | // 满意度类型:leave=离院满意度(离院科室/病区),hospital=在院满意度(在院科室/病区) |
| | | satisfactionScope: 'leave', |
| | | queryParams: { |
| | | dateRange: [], |
| | | interval: 'month', |
| | |
| | | }, |
| | | defaultWardCodes() { |
| | | return Array.from(new Set(this.wardCodeList.map((w) => w.districtCode))); |
| | | }, |
| | | // 是否按「在院满意度」口径查询(清空下拉时按默认的离院口径) |
| | | isInHospitalSatisfaction() { |
| | | return this.satisfactionScope === 'hospital'; |
| | | }, |
| | | intervalLabel() { |
| | | return { day: '按天', week: '按周', month: '按月', year: '按年' }[this.queryParams.interval] || '按月'; |
| | |
| | | if (!den) return '0.00'; |
| | | return (num / den).toFixed(2); |
| | | }, |
| | | // 综合得分展示:直接使用后端 avgScore,格式化为两位小数 |
| | | formatScore(val) { |
| | | const n = Number(val); |
| | | return isNaN(n) ? '0.00' : n.toFixed(2); |
| | | }, |
| | | // 解析比例字符串:"14.29%"、"0"、"0.5" → 数值(百分比);无效返回 null |
| | | parsePercent(value) { |
| | | if (value == null || value === '') return null; |
| | |
| | | const arr = Array.isArray(list) ? list : (list && (list.list || list.rows)) || []; |
| | | const options = []; |
| | | let total = 0; |
| | | let avgScore = null; |
| | | arr.forEach((item) => { |
| | | // 综合得分:后端返回的题目级 avgScore(与题目同级) |
| | | if (avgScore == null) { |
| | | const rawAvg = Number(item && item.avgScore); |
| | | if (!isNaN(rawAvg)) avgScore = rawAvg; |
| | | } |
| | | const details = Array.isArray(item && item.subtaskDetailRatioExportList) |
| | | ? item.subtaskDetailRatioExportList |
| | | : []; |
| | |
| | | total += count; |
| | | }); |
| | | }); |
| | | return { options, total }; |
| | | return { options, total, avgScore }; |
| | | }, |
| | | // 单题选项统计(真实接口 /smartor/serviceSubtaskDetail/sltdMydTotalByScore) |
| | | async fetchSingleQuestion() { |
| | |
| | | questionContent: this.singleQuestionContent, |
| | | }; |
| | | const sel = this.singleDeptWards || []; |
| | | // 科室/病区条件随「满意度类型」切换:在院 => deptcodes/hospitaldistrictcodes,离院 => leaveldeptcodes/leavehospitaldistrictcodes |
| | | if (this.queryParams.dimension === 'dept') { |
| | | const names = sel.map((s) => String(s).replace(/^dept:/, '')); |
| | | payload.leaveldeptcodes = names.length ? this.mapDeptNames(names) : this.defaultDeptCodes; |
| | | const codes = names.length ? this.mapDeptNames(names) : this.defaultDeptCodes; |
| | | if (this.isInHospitalSatisfaction) payload.deptcodes = codes; |
| | | else payload.leaveldeptcodes = codes; |
| | | } else { |
| | | const names = sel.map((s) => String(s).replace(/^ward:/, '')); |
| | | payload.leavehospitaldistrictcodes = names.length ? this.mapWardNames(names) : this.defaultWardCodes; |
| | | const codes = names.length ? this.mapWardNames(names) : this.defaultWardCodes; |
| | | if (this.isInHospitalSatisfaction) payload.hospitaldistrictcodes = codes; |
| | | else payload.leavehospitaldistrictcodes = codes; |
| | | } |
| | | const res = await sltdMydTotalByScore(payload); |
| | | console.log('[sltdMydTotalByScore 单题] code=', res && res.code, 'data=', JSON.stringify(res && res.data).slice(0, 1500)); |
| | | const list = res && res.code === 200 ? res.data : []; |
| | | const { options, total } = this.normalizeSingleQuestion(list); |
| | | const { options, total, avgScore } = this.normalizeSingleQuestion(list); |
| | | this.singleQuestionData = options.map((o) => ({ |
| | | option: o.option, |
| | | count: o.count, |
| | |
| | | })); |
| | | this.singleQuestionMeta = { |
| | | total: total.toLocaleString(), |
| | | score: this.scoreOfOptions(options), |
| | | score: this.formatScore(avgScore), |
| | | }; |
| | | if (!options.length) this.notify('当前条件下暂无该题的选项统计数据', 'warning'); |
| | | } catch (e) { |
| | |
| | | |
| | | <style lang="scss" scoped> |
| | | .export-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } |
| | | .filter-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; .filter-label { font-size: 13px; color: #606266; } .filter-date { width: 260px; } } |
| | | .filter-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; .filter-label { font-size: 13px; color: #606266; } .filter-date { width: 260px; } .filter-scope { width: 140px; } } |
| | | .chart-card { margin-bottom: 0; } |
| | | .chart-box { width: 100%; height: 360px; } |
| | | .chart-wrap { position: relative; width: 100%; } |