<template>
|
<div class="charts-panel" ref="pdfTarget">
|
<!-- PDF导出按钮 -->
|
<div class="export-bar">
|
<el-button type="danger" size="small" icon="el-icon-download" @click="exportPDF">导出PDF</el-button>
|
</div>
|
|
<el-row :gutter="20">
|
<!-- 1. 时间维度 - 折线图 -->
|
<el-col :span="12">
|
<el-card shadow="never" class="chart-card">
|
<div slot="header" class="card-header">
|
<span>时间维度 · 门诊患者满意度趋势</span>
|
<el-tag type="success" size="small">环比 +0.97</el-tag>
|
</div>
|
<div ref="chart1" class="chart-box"></div>
|
</el-card>
|
</el-col>
|
|
<!-- 2. 分类维度 - 分组柱状图 -->
|
<el-col :span="12">
|
<el-card shadow="never" class="chart-card">
|
<div slot="header" class="card-header">
|
<span>分类维度 · 各指标年度对比</span>
|
</div>
|
<div ref="chart2" class="chart-box"></div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20" style="margin-top: 20px">
|
<!-- 3. 单题选项 - 数据表格 -->
|
<el-col :span="12">
|
<el-card shadow="never" class="chart-card">
|
<div slot="header" class="card-header">
|
<span>单题 · 第22题:抱怨和不满能否及时得到回应?</span>
|
</div>
|
<el-table :data="singleQuestionData" border size="small">
|
<el-table-column prop="option" label="选项" />
|
<el-table-column prop="count" label="合计数量" width="120" align="center" />
|
<el-table-column prop="rate" label="占比比例" width="120" align="center" />
|
</el-table>
|
<div class="table-footer">本题有效填写人次:<b>26,233</b> 综合满意度:<b>83.20%</b></div>
|
</el-card>
|
</el-col>
|
|
<!-- 4. 院区维度 - 分组柱状图 -->
|
<el-col :span="12">
|
<el-card shadow="never" class="chart-card">
|
<div slot="header" class="card-header">
|
<span>院区维度 · 各院区年度对比</span>
|
</div>
|
<div ref="chart4" class="chart-box"></div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20" style="margin-top: 20px">
|
<!-- 5. 科室维度 - 排名表格 -->
|
<el-col :span="12">
|
<el-card shadow="never" class="chart-card">
|
<div slot="header" class="card-header">
|
<span>科室维度 · 门诊患者满意度得分排名</span>
|
</div>
|
<el-table :data="deptRankData" border size="small">
|
<el-table-column type="index" label="排名" width="60" align="center" />
|
<el-table-column prop="dept" label="科室" />
|
<el-table-column prop="score" label="综合得分" width="120" align="center" />
|
</el-table>
|
</el-card>
|
</el-col>
|
|
<!-- 6. 单题科室 - 交叉分析表格 -->
|
<el-col :span="12">
|
<el-card shadow="never" class="chart-card">
|
<div slot="header" class="card-header">
|
<span>单题×科室 · 第22题各科室得分</span>
|
</div>
|
<el-table :data="crossDeptData" border size="small" max-height="400">
|
<el-table-column type="index" label="排名" width="60" align="center" />
|
<el-table-column prop="dept" label="科室" />
|
<el-table-column prop="score" label="综合得分" width="120" align="center" />
|
</el-table>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
</template>
|
|
<script>
|
import * as echarts from 'echarts';
|
|
export default {
|
name: "ChartsPanel",
|
data() {
|
return {
|
singleQuestionData: [
|
{ option: "非常满意", count: 16513, rate: "62.95%" },
|
{ option: "满意", count: 7088, rate: "27.02%" },
|
{ option: "一般", count: 1760, rate: "6.71%" },
|
{ option: "不满意", count: 872, rate: "3.32%" },
|
],
|
deptRankData: [
|
{ dept: "保健科", score: "81.54" }, { dept: "介入治疗科", score: "80.72" },
|
{ dept: "专科护士门诊", score: "79.27" }, { dept: "康复医学科", score: "78.65" },
|
{ dept: "皮肤科", score: "78.12" }, { dept: "内分泌科", score: "77.89" },
|
{ dept: "消化内科", score: "77.34" }, { dept: "肾内科", score: "76.50" },
|
{ dept: "呼吸内科", score: "75.88" }, { dept: "心血管内科", score: "75.21" },
|
],
|
crossDeptData: [
|
{ dept: "保健科", score: "81.24" }, { dept: "介入治疗科", score: "81.22" },
|
{ dept: "专科护士门诊", score: "80.15" }, { dept: "康复医学科", score: "79.88" },
|
{ dept: "皮肤科", score: "79.01" }, { dept: "内分泌科", score: "78.45" },
|
{ dept: "消化内科", score: "77.92" }, { dept: "肾内科", score: "77.13" },
|
{ dept: "呼吸内科", score: "76.80" }, { dept: "心血管内科", score: "76.05" },
|
{ dept: "神经内科", score: "75.62" }, { dept: "血液科", score: "75.01" },
|
{ dept: "风湿免疫科", score: "74.38" }, { dept: "肿瘤内科", score: "73.85" },
|
{ dept: "感染科", score: "73.12" }, { dept: "老年科", score: "72.56" },
|
{ dept: "普外科", score: "71.90" }, { dept: "骨科", score: "71.23" },
|
{ dept: "泌尿外科", score: "70.55" }, { dept: "妇产科", score: "69.88" },
|
],
|
};
|
},
|
mounted() { this.$nextTick(() => { this.initCharts(); }); },
|
methods: {
|
initCharts() {
|
const quarters = ['第一季度', '第二季度', '第三季度', '第四季度'];
|
const categories = ['隐私保护', '环境与标识', '医务人员回应', '医生沟通', '挂号体验', '护士沟通'];
|
const hospitals = ['院区一', '院区二', '院区三'];
|
const baseGrid = { top: 20, right: 30, bottom: 30, left: 50 };
|
const baseTooltip = { backgroundColor: 'rgba(255,255,255,0.95)', borderColor: '#e8e8e8', textStyle: { color: '#333', fontSize: 13 }, extraCssText: 'box-shadow: 0 2px 12px rgba(0,0,0,0.1); border-radius: 6px; padding: 10px 14px;' };
|
const baseXAxis = { axisLine: { lineStyle: { color: '#e0e0e0' } }, axisTick: { show: false }, axisLabel: { color: '#666', fontSize: 12 } };
|
const baseYAxis = { splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } }, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: '#999', fontSize: 11 } };
|
|
// 1. 折线图 - 时间维度
|
const c1 = echarts.init(this.$refs.chart1);
|
c1.setOption({
|
color: ['#5B8FF9'],
|
grid: { ...baseGrid, top: 25 },
|
tooltip: { ...baseTooltip, trigger: 'axis' },
|
xAxis: [{ ...baseXAxis, data: quarters, boundaryGap: false }],
|
yAxis: [{ ...baseYAxis, min: 86, max: 91, name: '满意度得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
|
series: [{
|
name: '门诊患者满意度', type: 'line', smooth: true, symbol: 'circle', symbolSize: 8,
|
data: [88.20, 87.32, 88.50, 89.17],
|
lineStyle: { width: 3, shadowBlur: 8, shadowColor: 'rgba(91,143,249,0.3)' },
|
itemStyle: { color: '#5B8FF9' },
|
areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ offset: 0, color: 'rgba(91,143,249,0.25)' }, { offset: 1, color: 'rgba(91,143,249,0.02)' }
|
])},
|
}],
|
});
|
|
// 2. 分组柱状图 - 分类维度
|
const c2 = echarts.init(this.$refs.chart2);
|
c2.setOption({
|
color: ['#6DC8EC', '#5B8FF9'],
|
grid: baseGrid,
|
tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow', shadowStyle: { color: 'rgba(0,0,0,0.03)' } } },
|
legend: { data: ['2023年', '2024年'], bottom: 0, textStyle: { color: '#666', fontSize: 12 } },
|
xAxis: [{ ...baseXAxis, data: categories, axisLabel: { ...baseXAxis.axisLabel, interval: 0 } }],
|
yAxis: [{ ...baseYAxis, name: '得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
|
series: [
|
{ name: '2023年', type: 'bar', barWidth: 22, barGap: '30%',
|
itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#6DC8EC' }, { offset: 1, color: '#48B0D5' }]) },
|
data: [86.34, 85.12, 83.45, 86.22, 88.55, 87.33] },
|
{ name: '2024年', type: 'bar', barWidth: 22,
|
itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#5B8FF9' }, { offset: 1, color: '#3D6FDB' }]) },
|
data: [88.06, 84.57, 82.88, 87.77, 88.12, 87.00] },
|
],
|
});
|
|
// 4. 院区维度 - 分组柱状图
|
const c4 = echarts.init(this.$refs.chart4);
|
c4.setOption({
|
color: ['#6DC8EC', '#5B8FF9'],
|
grid: baseGrid,
|
tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow' } },
|
legend: { data: ['2023年', '2024年'], bottom: 0, textStyle: { color: '#666', fontSize: 12 } },
|
xAxis: [{ ...baseXAxis, data: hospitals }],
|
yAxis: [{ ...baseYAxis, name: '满意度得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
|
series: [
|
{ name: '2023年', type: 'bar', barWidth: 24, barGap: '30%',
|
itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#6DC8EC' }, { offset: 1, color: '#48B0D5' }]) },
|
data: [86.20, 87.69, 85.55],
|
label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: p => p.value.toFixed(2) } },
|
{ name: '2024年', type: 'bar', barWidth: 24,
|
itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#5B8FF9' }, { offset: 1, color: '#3D6FDB' }]) },
|
data: [88.15, 88.14, 85.88],
|
label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: p => p.value.toFixed(2) } },
|
],
|
});
|
|
// 响应式
|
window.addEventListener('resize', () => { c1.resize(); c2.resize(); c4.resize(); });
|
},
|
|
async exportPDF() {
|
// 先将 ECharts 实例转为图片 dataURL
|
const chartRefs = [
|
{ ref: this.$refs.chart1, id: 'chart1' },
|
{ ref: this.$refs.chart2, id: 'chart2' },
|
{ ref: this.$refs.chart4, id: 'chart4' },
|
];
|
const chartImages = {};
|
for (const { ref, id } of chartRefs) {
|
if (ref) {
|
const instance = echarts.getInstanceByDom(ref);
|
if (instance) {
|
chartImages[id] = instance.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' });
|
}
|
}
|
}
|
|
// 克隆内容并替换图表为图片
|
const printContent = this.$refs.pdfTarget.cloneNode(true);
|
for (const { ref, id } of chartRefs) {
|
if (chartImages[id] && ref) {
|
const boxes = printContent.querySelectorAll('.chart-box');
|
for (const box of boxes) {
|
if (box.innerHTML.includes(id) || boxes.length === chartRefs.length) {
|
box.innerHTML = `<img src="${chartImages[id]}" style="width:100%;height:auto;" />`;
|
}
|
}
|
}
|
}
|
// 更可靠的方式:按位置匹配
|
const chartBoxes = printContent.querySelectorAll('.chart-box');
|
const imgKeys = Object.keys(chartImages);
|
chartBoxes.forEach((box, i) => {
|
if (imgKeys[i] && chartImages[imgKeys[i]]) {
|
box.innerHTML = `<img src="${chartImages[imgKeys[i]]}" style="width:100%;height:auto;" />`;
|
}
|
});
|
|
const win = window.open('', '_blank', 'width=900,height=800');
|
if (!win) { this.$message.warning('请允许弹出窗口以导出PDF'); return; }
|
win.document.write(`
|
<html><head><meta charset="utf-8"><title>各项统计表</title>
|
<style>
|
body { font-family: 'Microsoft YaHei',sans-serif; padding: 20px; }
|
.chart-card { break-inside: avoid; margin-bottom: 20px; border: 1px solid #ebeef5; border-radius: 6px; }
|
.card-header { padding: 12px 16px; border-bottom: 1px solid #ebeef5; font-size: 15px; font-weight: 600; color: #303133; background: #f5f7fa; }
|
.chart-box img { max-width: 100%; display: block; }
|
table { width: 100%; border-collapse: collapse; margin: 12px 0; }
|
th, td { border: 1px solid #d9d9d9; padding: 8px 12px; text-align: center; font-size: 12px; }
|
th { background: #f0f2f5; font-weight: 600; }
|
.table-footer { text-align: right; padding: 8px 0; font-size: 13px; color: #606266; }
|
.export-bar, .el-tag { display: none; }
|
@media print { @page { size: A4; margin: 10mm; } }
|
</style></head><body>
|
${printContent.innerHTML}
|
<script>window.onload=function(){window.print();}<\/script>
|
</body></html>
|
`);
|
win.document.close();
|
},
|
},
|
};
|
</script>
|
|
<style lang="scss" scoped>
|
.export-bar { text-align: right; margin-bottom: 16px; }
|
.chart-card { margin-bottom: 0; }
|
.chart-box { width: 100%; height: 360px; }
|
.card-header { display: flex; justify-content: space-between; align-items: center; span { font-size: 15px; font-weight: 600; color: #303133; } }
|
.table-footer { text-align: right; padding: 10px 0 0; font-size: 13px; color: #606266; }
|
</style>
|