<template>
|
<div class="charts-panel" ref="pdfTarget">
|
<!-- 顶部筛选:时间区间 + 区间类型 + PDF导出 -->
|
<div class="export-bar">
|
<div class="filter-bar">
|
<span class="filter-label">时间范围:</span>
|
<el-date-picker
|
v-model="queryParams.dateRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
value-format="yyyy-MM-dd"
|
size="small"
|
class="filter-date"
|
/>
|
<span class="filter-label">区间类型:</span>
|
<el-radio-group v-model="queryParams.interval" size="small" @change="refreshAll">
|
<el-radio-button label="day">按天</el-radio-button>
|
<el-radio-button label="month">按月</el-radio-button>
|
<el-radio-button label="year">按年</el-radio-button>
|
</el-radio-group>
|
<el-button type="primary" size="small" icon="el-icon-search" @click="refreshAll">查询</el-button>
|
<el-button size="small" icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
</div>
|
<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">环比 {{ trendMoM }}</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>分类维度 · 各指标{{ intervalLabel }}对比</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>单题 · 选项统计</span>
|
<div class="header-right">
|
<el-select v-model="singleQuestionId" size="small" class="sel sel-lg" @change="refreshAll">
|
<el-option v-for="q in questionOptions" :key="q.id" :label="q.title" :value="q.id" />
|
</el-select>
|
<el-select v-model="singleDeptWards" multiple collapse-tags size="small" class="sel" clearable filterable placeholder="全部科室/病区" @change="refreshAll">
|
<el-option-group label="科室">
|
<el-option v-for="d in deptOptions" :key="'dept:' + d" :label="d" :value="'dept:' + d" />
|
</el-option-group>
|
<el-option-group label="病区">
|
<el-option v-for="w in wardOptions" :key="'ward:' + w" :label="w" :value="'ward:' + w" />
|
</el-option-group>
|
</el-select>
|
</div>
|
</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>{{ singleQuestionMeta.total }}</b> 综合满意度:<b>{{ singleQuestionMeta.score }}</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>院区维度 · 各院区{{ intervalLabel }}对比</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>{{ rankDimLabel }}维度 · 门诊患者满意度得分排名</span>
|
<div class="header-right">
|
<el-radio-group v-model="rankDimension" size="mini" @change="onRankDimensionChange">
|
<el-radio-button label="dept">按科室</el-radio-button>
|
<el-radio-button label="ward">按病区</el-radio-button>
|
</el-radio-group>
|
<el-select v-model="rankDeptWards" multiple collapse-tags size="small" class="sel" clearable filterable :placeholder="'全部' + rankDimLabel" @change="refreshAll">
|
<el-option v-for="name in rankOptions" :key="name" :label="name" :value="name" />
|
</el-select>
|
</div>
|
</div>
|
<el-table :data="rankRows" border size="small" max-height="400">
|
<el-table-column type="index" label="排名" width="60" align="center" />
|
<el-table-column prop="name" :label="rankDimLabel" />
|
<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>单题×{{ crossDimLabel }} · 得分排名</span>
|
<div class="header-right">
|
<el-radio-group v-model="crossDimension" size="mini" @change="onCrossDimensionChange">
|
<el-radio-button label="dept">按科室</el-radio-button>
|
<el-radio-button label="ward">按病区</el-radio-button>
|
</el-radio-group>
|
<el-select v-model="crossDeptWards" multiple collapse-tags size="small" class="sel" clearable filterable :placeholder="'全部' + crossDimLabel" @change="refreshAll">
|
<el-option v-for="name in crossOptions" :key="name" :label="name" :value="name" />
|
</el-select>
|
</div>
|
</div>
|
<el-table :data="crossRows" border size="small" max-height="400">
|
<el-table-column type="index" label="排名" width="60" align="center" />
|
<el-table-column prop="name" :label="crossDimLabel" />
|
<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';
|
|
// 单题选项
|
const QUESTION_OPTIONS = ['非常满意', '满意', '一般', '不满意'];
|
// 可选题目
|
const QUESTIONS = [
|
{ id: 22, title: '第22题:抱怨和不满能否及时得到回应?' },
|
{ id: 1, title: '第1题:您对我院工作人员的服务质量满意不满意?' },
|
{ id: 5, title: '第5题:您对医院服务设施的总体印象如何?' },
|
{ id: 8, title: '第8题:医生对您是否尊重?' },
|
{ id: 12, title: '第12题:护士对您是否尊重?' },
|
{ id: 15, title: '第15题:您对预约挂号是否满意?' },
|
];
|
const DEPTS = ['保健科', '介入治疗科', '专科护士门诊', '康复医学科', '皮肤科', '内分泌科', '消化内科', '肾内科', '呼吸内科', '心血管内科', '神经内科', '血液科', '风湿免疫科', '肿瘤内科', '感染科', '老年科', '普外科', '骨科', '泌尿外科', '妇产科'];
|
const WARDS = ['一病区', '二病区', '三病区', '四病区', '五病区', '六病区', '七病区', '八病区'];
|
|
// 确定性伪随机
|
function hashString(str) {
|
let h = 2166136261;
|
for (let i = 0; i < str.length; i++) {
|
h ^= str.charCodeAt(i);
|
h = Math.imul(h, 16777619);
|
}
|
return h >>> 0;
|
}
|
function mulberry32(a) {
|
return function () {
|
a |= 0;
|
a = (a + 0x6d2b79f5) | 0;
|
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
};
|
}
|
|
export default {
|
name: 'ChartsPanel',
|
data() {
|
return {
|
queryParams: {
|
dateRange: [],
|
interval: 'month',
|
},
|
deptOptions: DEPTS,
|
wardOptions: WARDS,
|
questionOptions: QUESTIONS,
|
|
// 单题筛选
|
singleQuestionId: 22,
|
singleDeptWards: [],
|
|
// 排名维度筛选
|
rankDimension: 'dept',
|
rankDeptWards: [],
|
crossDimension: 'dept',
|
crossDeptWards: [],
|
|
// 图表数据
|
trend: { buckets: [], data: [], seriesName: '门诊患者满意度' },
|
categoryCompare: { categories: [], seriesNames: [], series1: [], series2: [] },
|
hospitalCompare: { hospitals: [], seriesNames: [], series1: [], series2: [] },
|
|
// 表格数据
|
singleQuestionData: [],
|
singleQuestionMeta: { total: '0', score: '0.00%' },
|
deptRankData: [],
|
wardRankData: [],
|
crossDeptData: [],
|
crossWardData: [],
|
};
|
},
|
computed: {
|
intervalLabel() {
|
return { day: '按天', month: '按月', year: '按年' }[this.queryParams.interval] || '按月';
|
},
|
trendMoM() {
|
const d = this.trend.data;
|
if (d.length < 2) return '0.00';
|
const diff = d[d.length - 1] - d[d.length - 2];
|
return (diff >= 0 ? '+' : '') + diff.toFixed(2);
|
},
|
currentSingleQuestion() {
|
return this.questionOptions.find((q) => q.id === this.singleQuestionId) || {};
|
},
|
rankDimLabel() {
|
return this.rankDimension === 'dept' ? '科室' : '病区';
|
},
|
crossDimLabel() {
|
return this.crossDimension === 'dept' ? '科室' : '病区';
|
},
|
rankOptions() {
|
return this.rankDimension === 'dept' ? this.deptOptions : this.wardOptions;
|
},
|
crossOptions() {
|
return this.crossDimension === 'dept' ? this.deptOptions : this.wardOptions;
|
},
|
rankRows() {
|
const list = this.rankDimension === 'dept' ? this.deptRankData : this.wardRankData;
|
if (!this.rankDeptWards.length) return list;
|
return list.filter((r) => this.rankDeptWards.includes(r.name));
|
},
|
crossRows() {
|
const list = this.crossDimension === 'dept' ? this.crossDeptData : this.crossWardData;
|
if (!this.crossDeptWards.length) return list;
|
return list.filter((r) => this.crossDeptWards.includes(r.name));
|
},
|
},
|
mounted() {
|
this.$nextTick(() => {
|
if (!this.queryParams.dateRange || !this.queryParams.dateRange.length) {
|
this.queryParams.dateRange = this.defaultRange();
|
}
|
this.initCharts();
|
this.refreshAll();
|
});
|
},
|
methods: {
|
initCharts() {
|
echarts.init(this.$refs.chart1);
|
echarts.init(this.$refs.chart2);
|
echarts.init(this.$refs.chart4);
|
window.addEventListener('resize', this.handleResize);
|
},
|
handleResize() {
|
['chart1', 'chart2', 'chart4'].forEach((ref) => {
|
const c = echarts.getInstanceByDom(this.$refs[ref]);
|
if (c) c.resize();
|
});
|
},
|
handleReset() {
|
this.queryParams.dateRange = this.defaultRange();
|
this.queryParams.interval = 'month';
|
this.refreshAll();
|
},
|
onRankDimensionChange() {
|
this.rankDeptWards = [];
|
this.refreshAll();
|
},
|
onCrossDimensionChange() {
|
this.crossDeptWards = [];
|
this.refreshAll();
|
},
|
defaultRange() {
|
const end = new Date();
|
const start = new Date();
|
start.setDate(start.getDate() - 29);
|
const f = (d) => {
|
const y = d.getFullYear();
|
const m = String(d.getMonth() + 1).padStart(2, '0');
|
const day = String(d.getDate()).padStart(2, '0');
|
return `${y}-${m}-${day}`;
|
};
|
return [f(start), f(end)];
|
},
|
// 区间类型 => 时间桶 + 柱状图对比序列名
|
intervalConfig() {
|
const buckets = this.genBuckets(this.queryParams.interval);
|
if (this.queryParams.interval === 'day') return { buckets, seriesNames: ['昨日', '今日'] };
|
if (this.queryParams.interval === 'year') return { buckets, seriesNames: ['去年', '今年'] };
|
return { buckets, seriesNames: ['上月', '本月'] };
|
},
|
genBuckets(type) {
|
const end = this.queryParams.dateRange && this.queryParams.dateRange[1]
|
? new Date(this.queryParams.dateRange[1])
|
: new Date();
|
const buckets = [];
|
if (type === 'day') {
|
for (let i = 14; i >= 0; i--) {
|
const d = new Date(end);
|
d.setDate(end.getDate() - i);
|
buckets.push(`${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`);
|
}
|
} else if (type === 'year') {
|
for (let i = 3; i >= 0; i--) buckets.push(String(end.getFullYear() - i));
|
} else {
|
for (let i = 5; i >= 0; i--) {
|
const d = new Date(end.getFullYear(), end.getMonth() - i, 1);
|
buckets.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`);
|
}
|
}
|
return buckets;
|
},
|
// 刷新全部数据(模拟,接口就绪后替换为真实请求)
|
refreshAll() {
|
const baseChart = JSON.stringify(this.queryParams);
|
const baseTime = JSON.stringify({ dateRange: this.queryParams.dateRange });
|
|
// 图表:区间类型 + 时间范围
|
this.genChartData(mulberry32(hashString(baseChart + 'charts')));
|
// 单题表:时间范围 + 题目 + 科室/病区
|
this.genSingleData(mulberry32(hashString(baseTime + JSON.stringify({ sq: this.singleQuestionId, sdw: this.singleDeptWards }))));
|
// 科室/病区排名:时间范围 + 维度 + 选择
|
this.genRankData(mulberry32(hashString(baseTime + JSON.stringify({ rd: this.rankDimension, rdw: this.rankDeptWards }))));
|
// 单题×科室/病区排名:时间范围 + 维度 + 选择 + 题目
|
this.genCrossData(mulberry32(hashString(baseTime + JSON.stringify({ cd: this.crossDimension, cdw: this.crossDeptWards, sq: this.singleQuestionId }))));
|
|
this.renderCharts();
|
},
|
genChartData(rnd) {
|
const jitter = (lo, hi) => lo + rnd() * (hi - lo);
|
const cfg = this.intervalConfig();
|
const buckets = cfg.buckets;
|
|
this.trend = {
|
buckets,
|
seriesName: '门诊患者满意度',
|
data: buckets.map(() => Math.round(jitter(86, 90) * 100) / 100),
|
};
|
|
const categories = ['隐私保护', '环境与标识', '医务人员回应', '医生沟通', '挂号体验', '护士沟通'];
|
this.categoryCompare = {
|
categories,
|
seriesNames: cfg.seriesNames,
|
series1: categories.map(() => Math.round(jitter(82, 88) * 100) / 100),
|
series2: categories.map(() => Math.round(jitter(83, 89) * 100) / 100),
|
};
|
|
const hospitals = ['院区一', '院区二', '院区三'];
|
this.hospitalCompare = {
|
hospitals,
|
seriesNames: cfg.seriesNames,
|
series1: hospitals.map(() => Math.round(jitter(85, 89) * 100) / 100),
|
series2: hospitals.map(() => Math.round(jitter(86, 90) * 100) / 100),
|
};
|
},
|
genSingleData(rnd) {
|
const jitter = (lo, hi) => lo + rnd() * (hi - lo);
|
const scopeFactor = this.singleDeptWards.length ? 0.18 : 1;
|
const total = Math.round(jitter(20000, 30000) * scopeFactor);
|
const weights = [0.62, 0.27, 0.07, 0.04];
|
const counts = weights.map((w) => Math.max(0, Math.round(total * w * jitter(0.92, 1.08))));
|
const good = counts[0] + counts[1];
|
this.singleQuestionData = QUESTION_OPTIONS.map((opt, i) => ({
|
option: opt,
|
count: counts[i],
|
rate: ((counts[i] / total) * 100).toFixed(2) + '%',
|
}));
|
this.singleQuestionMeta = {
|
total: total.toLocaleString(),
|
score: ((good / total) * 100).toFixed(2) + '%',
|
};
|
},
|
genRankData(rnd) {
|
const jitter = (lo, hi) => lo + rnd() * (hi - lo);
|
this.deptRankData = DEPTS
|
.map((d) => ({ name: d, score: jitter(70, 85).toFixed(2) }))
|
.sort((a, b) => Number(b.score) - Number(a.score));
|
this.wardRankData = WARDS
|
.map((w) => ({ name: w, score: jitter(70, 85).toFixed(2) }))
|
.sort((a, b) => Number(b.score) - Number(a.score));
|
},
|
genCrossData(rnd) {
|
const jitter = (lo, hi) => lo + rnd() * (hi - lo);
|
this.crossDeptData = DEPTS
|
.map((d) => ({ name: d, score: jitter(70, 85).toFixed(2) }))
|
.sort((a, b) => Number(b.score) - Number(a.score));
|
this.crossWardData = WARDS
|
.map((w) => ({ name: w, score: jitter(70, 85).toFixed(2) }))
|
.sort((a, b) => Number(b.score) - Number(a.score));
|
},
|
renderCharts() {
|
const c1 = echarts.getInstanceByDom(this.$refs.chart1);
|
const c2 = echarts.getInstanceByDom(this.$refs.chart2);
|
const c4 = echarts.getInstanceByDom(this.$refs.chart4);
|
if (!c1 || !c2 || !c4) return;
|
|
const lineGrid = { top: 32, right: 30, bottom: 32, left: 85 };
|
const barGrid = { top: 48, right: 30, bottom: 60, left: 85 };
|
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 } };
|
const areaGrad = 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)' },
|
]);
|
|
// 1. 折线图 - 时间维度
|
c1.setOption({
|
color: ['#5B8FF9'],
|
grid: lineGrid,
|
tooltip: { ...baseTooltip, trigger: 'axis' },
|
xAxis: [{ ...baseXAxis, data: this.trend.buckets, boundaryGap: false }],
|
yAxis: [{ ...baseYAxis, name: '满意度得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
|
series: [{
|
name: this.trend.seriesName, type: 'line', smooth: true, symbol: 'circle', symbolSize: 8,
|
data: this.trend.data,
|
lineStyle: { width: 3, shadowBlur: 8, shadowColor: 'rgba(91,143,249,0.3)' },
|
itemStyle: { color: '#5B8FF9' },
|
areaStyle: { color: areaGrad },
|
}],
|
});
|
|
// 2. 分组柱状图 - 分类维度
|
c2.setOption({
|
color: ['#6DC8EC', '#5B8FF9'],
|
grid: barGrid,
|
tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow', shadowStyle: { color: 'rgba(0,0,0,0.03)' } } },
|
legend: { data: this.categoryCompare.seriesNames, bottom: 8, textStyle: { color: '#666', fontSize: 12 } },
|
xAxis: [{ ...baseXAxis, data: this.categoryCompare.categories, axisLabel: { ...baseXAxis.axisLabel, interval: 0 } }],
|
yAxis: [{ ...baseYAxis, name: '得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
|
series: [
|
{ name: this.categoryCompare.seriesNames[0], 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: this.categoryCompare.series1 },
|
{ name: this.categoryCompare.seriesNames[1], 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: this.categoryCompare.series2 },
|
],
|
});
|
|
// 4. 院区维度 - 分组柱状图
|
c4.setOption({
|
color: ['#6DC8EC', '#5B8FF9'],
|
grid: barGrid,
|
tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow' } },
|
legend: { data: this.hospitalCompare.seriesNames, bottom: 8, textStyle: { color: '#666', fontSize: 12 } },
|
xAxis: [{ ...baseXAxis, data: this.hospitalCompare.hospitals }],
|
yAxis: [{ ...baseYAxis, name: '满意度得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
|
series: [
|
{ name: this.hospitalCompare.seriesNames[0], 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: this.hospitalCompare.series1,
|
label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: (p) => p.value.toFixed(2) } },
|
{ name: this.hospitalCompare.seriesNames[1], 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: this.hospitalCompare.series2,
|
label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: (p) => p.value.toFixed(2) } },
|
],
|
});
|
},
|
|
async exportPDF() {
|
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);
|
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, .filter-bar, .el-tag, .el-select, .el-radio-group { 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 { 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; } }
|
.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; } }
|
.header-right { display: flex; align-items: center; gap: 10px; .sel { width: 140px; } .sel-lg { width: 240px; } }
|
.table-footer { text-align: right; padding: 10px 0 0; font-size: 13px; color: #606266; }
|
</style>
|