| | |
| | | <el-button size="small" icon="el-icon-refresh" @click="handleReset" |
| | | >重置</el-button |
| | | > |
| | | <el-button |
| | | type="warning" |
| | | size="small" |
| | | icon="el-icon-download" |
| | | @click="exportTable()" |
| | | >导出Excel</el-button |
| | | > |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | |
| | | <div slot="header" class="card-header"> |
| | | <span class="card-title">各服务项目满意度统计</span> |
| | | <span class="card-subtitle">(六档评分题:很满意/满意/一般/不满意/很不满意/未经历)</span> |
| | | <el-button |
| | | type="warning" |
| | | size="small" |
| | | icon="el-icon-download" |
| | | @click="exportTable('table1')" |
| | | >导出Excel</el-button |
| | | > |
| | | </div> |
| | | <el-table |
| | | id="table1" |
| | |
| | | placeholder="按题目模糊查询" |
| | | class="search-input" |
| | | /> |
| | | <el-button |
| | | type="warning" |
| | | size="small" |
| | | icon="el-icon-download" |
| | | @click="exportTable('table2')" |
| | | >导出Excel</el-button |
| | | > |
| | | </div> |
| | | </div> |
| | | <div id="table2" v-loading="loading" element-loading-text="统计中..."> |
| | |
| | | /> |
| | | </template> |
| | | </el-select> |
| | | <el-button |
| | | type="warning" |
| | | size="small" |
| | | icon="el-icon-download" |
| | | @click="exportTable('table3')" |
| | | >导出Excel</el-button |
| | | > |
| | | </div> |
| | | </div> |
| | | <el-table |
| | |
| | | <script> |
| | | import { sltdMydTotalByScore,statQuestionOptionByKsOrBq } from "@/api/AiCentre/satisfaction"; |
| | | import { deptTreeSelect } from "@/api/system/user"; |
| | | import XLSX from "xlsx"; |
| | | import * as XLSX from "xlsx"; |
| | | |
| | | // 满意度题的固定选项(顺序即表格列顺序)——这些选项的题目进表一,其余进表二 |
| | | const FIXED_OPTIONS = ["很满意", "满意", "一般", "不满意", "很不满意", "未经历"]; |
| | |
| | | const hit = opts.find((o) => String(o.label) === String(name)); |
| | | return hit ? hit.value : name; |
| | | }, |
| | | // 导出 Excel:一次导出整页三个表(各一个 sheet),和页面显示保持一致 |
| | | exportTable(tableId) { |
| | | const tableNames = { |
| | | table1: "各服务项目满意度统计", |
| | | table2: "其他题目统计", |
| | | table3: "各科室满意度评分对比", |
| | | }; |
| | | const name = tableNames[tableId] || "统计数据"; |
| | | const table = document.getElementById(tableId); |
| | | if (!table) return; |
| | | |
| | | try { |
| | | // 从表格提取数据构建带样式的 Excel |
| | | const order = ["table1", "table2", "table3"]; |
| | | if (tableId && order.indexOf(tableId) > 0) { |
| | | order.splice(order.indexOf(tableId), 1); |
| | | order.unshift(tableId); |
| | | } |
| | | const builders = { |
| | | table1: () => this.buildExportTable1(), |
| | | table2: () => this.buildExportTable2(), |
| | | table3: () => this.buildExportTable3(), |
| | | }; |
| | | const wb = XLSX.utils.book_new(); |
| | | const data = []; |
| | | table.querySelectorAll("tr").forEach((tr) => { |
| | | const row = []; |
| | | tr.querySelectorAll("th,td").forEach((td) => { |
| | | row.push((td.innerText || "").trim()); |
| | | }); |
| | | if (row.length) data.push(row); |
| | | let count = 0; |
| | | order.forEach((id) => { |
| | | const sheet = builders[id](); |
| | | if (!sheet) return; |
| | | const ws = XLSX.utils.aoa_to_sheet(sheet.data); |
| | | ws["!merges"] = sheet.merges || []; |
| | | const widthRow = sheet.data.find((r) => r && r.length) || []; |
| | | ws["!cols"] = widthRow.map((_, i) => ({ wch: i === 0 ? 32 : 12 })); |
| | | XLSX.utils.book_append_sheet(wb, ws, sheet.name); |
| | | count++; |
| | | }); |
| | | |
| | | const ws = XLSX.utils.aoa_to_sheet(data); |
| | | // 设置列宽 |
| | | const colWidths = data[0] ? data[0].map(() => ({ wch: 16 })) : []; |
| | | ws["!cols"] = colWidths; |
| | | |
| | | XLSX.utils.book_append_sheet(wb, ws, "Sheet1"); |
| | | XLSX.writeFile(wb, name + ".xlsx"); |
| | | if (!count) { |
| | | this.$message.warning("没有可导出的数据"); |
| | | return; |
| | | } |
| | | XLSX.writeFile(wb, "满意度数据总览.xlsx"); |
| | | this.$message.success("导出成功"); |
| | | } catch (e) { |
| | | // fallback: CSV |
| | | let csv = ""; |
| | | table.querySelectorAll("tr").forEach((tr) => { |
| | | const row = []; |
| | | tr.querySelectorAll("th,td").forEach((td) => |
| | | row.push('"' + (td.innerText || "").replace(/"/g, '""') + '"') |
| | | ); |
| | | csv += row.join(",") + "\n"; |
| | | }); |
| | | const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); |
| | | const link = document.createElement("a"); |
| | | link.href = URL.createObjectURL(blob); |
| | | link.download = name + ".csv"; |
| | | link.click(); |
| | | console.error("导出失败:", e); |
| | | this.$message.error("导出失败:" + (e.message || e)); |
| | | } |
| | | }, |
| | | |
| | | // 表一:各服务项目满意度统计 |
| | | buildExportTable1() { |
| | | const rows = this.serviceSatisfaction || []; |
| | | if (!rows.length) return null; |
| | | const data = [ |
| | | ["各服务项目满意度统计(六档评分题:很满意/满意/一般/不满意/很不满意/未经历)"], |
| | | [ |
| | | "分类/题目", |
| | | "很满意", "", "满意", "", "一般", "", |
| | | "不满意", "", "很不满意", "", "未经历", "", |
| | | "综合得分", |
| | | ], |
| | | [ |
| | | "", |
| | | "数量", "比例", "数量", "比例", "数量", "比例", |
| | | "数量", "比例", "数量", "比例", "数量", "比例", |
| | | "", |
| | | ], |
| | | ]; |
| | | rows.forEach((r) => { |
| | | data.push([ |
| | | r.category, |
| | | r.veryGoodCount, r.veryGoodRate, |
| | | r.goodCount, r.goodRate, |
| | | r.normalCount, r.normalRate, |
| | | r.badCount, r.badRate, |
| | | r.veryBadCount, r.veryBadRate, |
| | | r.naCount, r.naRate, |
| | | r.score, |
| | | ]); |
| | | }); |
| | | const merges = [ |
| | | { s: { r: 0, c: 0 }, e: { r: 0, c: 13 } }, |
| | | { s: { r: 1, c: 0 }, e: { r: 2, c: 0 } }, |
| | | ]; |
| | | for (let i = 0; i < 6; i++) { |
| | | merges.push({ s: { r: 1, c: 1 + i * 2 }, e: { r: 1, c: 2 + i * 2 } }); |
| | | } |
| | | merges.push({ s: { r: 1, c: 13 }, e: { r: 2, c: 13 } }); |
| | | return { name: "各服务项目满意度", data, merges }; |
| | | }, |
| | | |
| | | // 表二:其他题目统计(每题一块:题目标题 → 选项/数量/比例 → 本题填报总量) |
| | | buildExportTable2() { |
| | | const list = this.filteredQuestionStats || []; |
| | | if (!list.length) return null; |
| | | const data = [ |
| | | ["其他题目统计(六档满意度题已在上表,这里只列其余题型:推荐、抱怨回应、意见等)"], |
| | | [], |
| | | ]; |
| | | const merges = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 2 } }]; |
| | | list.forEach((q) => { |
| | | data.push([q.title, "", ""]); |
| | | merges.push({ |
| | | s: { r: data.length - 1, c: 0 }, |
| | | e: { r: data.length - 1, c: 2 }, |
| | | }); |
| | | data.push(["选项", "数量", "比例"]); |
| | | const opts = |
| | | q.options && q.options.length |
| | | ? q.options |
| | | : [{ option: "(无选项)", count: 0, rate: "-" }]; |
| | | opts.forEach((o) => { |
| | | data.push([o.option, o.count, o.rate]); |
| | | }); |
| | | data.push([`本题填报总量:${q.total}`, "", ""]); |
| | | merges.push({ |
| | | s: { r: data.length - 1, c: 0 }, |
| | | e: { r: data.length - 1, c: 2 }, |
| | | }); |
| | | data.push([]); |
| | | }); |
| | | return { name: "其他题目统计", data, merges }; |
| | | }, |
| | | |
| | | // 表三:各科室/病区满意度评分对比(两行动态表头) |
| | | buildExportTable3() { |
| | | const rows = this.table3Rows || []; |
| | | const questions = this.table3Questions || []; |
| | | if (!rows.length) return null; |
| | | const head1 = [this.statDimension === "dept" ? "科室" : "病区"]; |
| | | const head2 = [""]; |
| | | const groups = []; |
| | | questions.forEach((q) => { |
| | | const opts = q.options || []; |
| | | const startCol = head1.length; |
| | | head1.push(q.title); |
| | | for (let i = 1; i < opts.length + 1; i++) head1.push(""); |
| | | groups.push({ start: startCol, end: startCol + opts.length }); |
| | | opts.forEach((opt) => head2.push(opt)); |
| | | head2.push("得分"); |
| | | }); |
| | | head1.push("综合得分"); |
| | | head2.push(""); |
| | | const data = [["各科室/病区满意度评分对比"], head1, head2]; |
| | | const merges = [ |
| | | { s: { r: 0, c: 0 }, e: { r: 0, c: head1.length - 1 } }, |
| | | { s: { r: 1, c: 0 }, e: { r: 2, c: 0 } }, |
| | | { |
| | | s: { r: 1, c: head1.length - 1 }, |
| | | e: { r: 2, c: head1.length - 1 }, |
| | | }, |
| | | ]; |
| | | groups.forEach((g) => { |
| | | if (g.end > g.start) { |
| | | merges.push({ s: { r: 1, c: g.start }, e: { r: 1, c: g.end } }); |
| | | } |
| | | }); |
| | | rows.forEach((row) => { |
| | | const line = [row.name]; |
| | | questions.forEach((q) => { |
| | | const cell = (row.cells && row.cells[q.title]) || {}; |
| | | (q.options || []).forEach((opt) => { |
| | | line.push((cell[opt] && cell[opt].count) || 0); |
| | | }); |
| | | line.push(cell._score || "0.00"); |
| | | }); |
| | | line.push(row.totalScore); |
| | | data.push(line); |
| | | }); |
| | | return { name: "各科室病区评分对比", data, merges }; |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |