From a9d2b856e0f6be6475319c2dc36bf405c2f908fc Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期二, 22 九月 2026 14:04:52 +0800
Subject: [PATCH] 测试完成
---
src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue | 807 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 707 insertions(+), 100 deletions(-)
diff --git a/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue b/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
index c8a13c6..cc1be67 100644
--- a/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
+++ b/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
@@ -1,7 +1,34 @@
<template>
<div class="charts-panel" ref="pdfTarget">
- <!-- PDF瀵煎嚭鎸夐挳 -->
+ <!-- 椤堕儴绛涢�夛細鏃堕棿鍖洪棿 + 鍖洪棿绫诲瀷 + 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="week">鎸夊懆</el-radio-button>
+ <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-radio-group v-model="queryParams.dimension" size="small" @change="onDimensionChange">
+ <el-radio-button label="ward">鎸夌梾鍖虹粺璁�</el-radio-button>
+ <el-radio-button label="dept">鎸夌瀹ょ粺璁�</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>
@@ -10,10 +37,13 @@
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<div slot="header" class="card-header">
- <span>鏃堕棿缁村害 路 闂ㄨ瘖鎮h�呮弧鎰忓害瓒嬪娍</span>
- <el-tag type="success" size="small">鐜瘮 +0.97</el-tag>
+ <span>鎮h�呮弧鎰忓害缁煎悎寰楀垎瓒嬪娍</span>
+ <el-tag type="success" size="small">鐜瘮 {{ trendMoM }}</el-tag>
</div>
- <div ref="chart1" class="chart-box"></div>
+ <div class="chart-wrap" v-loading="trendLoading" element-loading-text="瓒嬪娍鍔犺浇涓�...">
+ <div ref="chart1" class="chart-box"></div>
+ <div v-if="!trendLoading && !trend.buckets.length" class="chart-empty">鏆傛棤瓒嬪娍鏁版嵁</div>
+ </div>
</el-card>
</el-col>
@@ -21,26 +51,43 @@
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<div slot="header" class="card-header">
- <span>鍒嗙被缁村害 路 鍚勬寚鏍囧勾搴﹀姣�</span>
+ <span>鍒嗙被缁村害 路 鍚勬寚鏍噞{ intervalLabel }}瀵规瘮</span>
</div>
- <div ref="chart2" class="chart-box"></div>
+ <div class="chart-wrap" v-loading="categoryLoading" element-loading-text="鍒嗙被缁村害鍔犺浇涓�...">
+ <div ref="chart2" class="chart-box"></div>
+ <div v-if="!categoryLoading && !categoryCompare.categories.length" class="chart-empty">鏆傛棤鍒嗙被缁村害鏁版嵁</div>
+ </div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
- <!-- 3. 鍗曢閫夐」 - 鏁版嵁琛ㄦ牸 -->
+ <!-- 3. 鍗曢閫夐」 - 鏁版嵁琛ㄦ牸锛堝彲閫夋嫨棰樼洰銆佺瀹�/鐥呭尯锛� -->
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<div slot="header" class="card-header">
- <span>鍗曢 路 绗�22棰橈細鎶辨�ㄥ拰涓嶆弧鑳藉惁鍙婃椂寰楀埌鍥炲簲锛�</span>
+ <span>鍗曢 路 閫夐」缁熻</span>
+ <div class="header-right">
+ <el-select v-model="singleQuestionId" size="small" class="sel sel-lg" filterable remote clearable
+ :remote-method="searchQuestions" :loading="questionLoading" placeholder="璇疯緭鍏ラ棶棰樺唴瀹规悳绱�" @change="onSingleQuestionChange">
+ <el-option v-for="q in questionOptions" :key="q.id" :label="q.scriptContent || q.title" :value="q.id" />
+ </el-select>
+ <el-select v-model="singleDeptWards" multiple collapse-tags size="small" class="sel" clearable filterable :placeholder="queryParams.dimension === 'dept' ? '鍏ㄩ儴绉戝' : '鍏ㄩ儴鐥呭尯'" @change="refreshAll">
+ <template v-if="queryParams.dimension === 'dept'">
+ <el-option v-for="d in deptOptions" :key="'dept:' + d" :label="d" :value="'dept:' + d" />
+ </template>
+ <template v-else>
+ <el-option v-for="w in wardOptions" :key="'ward:' + w" :label="w" :value="'ward:' + w" />
+ </template>
+ </el-select>
+ </div>
</div>
- <el-table :data="singleQuestionData" border size="small">
+ <el-table :data="singleQuestionData" border size="small" v-loading="singleLoading" element-loading-text="閫夐」缁熻鍔犺浇涓�...">
<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>
+ <div class="table-footer">鏈鏈夋晥濉啓浜烘锛�<b>{{ singleQuestionMeta.total }}</b>銆�缁煎悎寰楀垎锛�<b>{{ singleQuestionMeta.score }}</b></div>
</el-card>
</el-col>
@@ -48,38 +95,61 @@
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<div slot="header" class="card-header">
- <span>闄㈠尯缁村害 路 鍚勯櫌鍖哄勾搴﹀姣�</span>
+ <span>闄㈠尯缁村害 路 鍚勯櫌鍖簕{ intervalLabel }}瀵规瘮</span>
</div>
- <div ref="chart4" class="chart-box"></div>
+ <div class="chart-wrap" v-loading="hospitalLoading" element-loading-text="闄㈠尯缁村害鍔犺浇涓�...">
+ <div ref="chart4" class="chart-box"></div>
+ <div v-if="!hospitalLoading && !hospitalCompare.hospitals.length" class="chart-empty">鏆傛棤闄㈠尯缁村害鏁版嵁</div>
+ </div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
- <!-- 5. 绉戝缁村害 - 鎺掑悕琛ㄦ牸 -->
+ <!-- 5. 绉戝/鐥呭尯缁村害 - 鎺掑悕琛ㄦ牸 -->
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<div slot="header" class="card-header">
- <span>绉戝缁村害 路 闂ㄨ瘖鎮h�呮弧鎰忓害寰楀垎鎺掑悕</span>
+ <span>{{ rankDimLabel }}缁村害 路 闂ㄨ瘖鎮h�呮弧鎰忓害寰楀垎鎺掑悕</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="deptRankData" border size="small">
- <el-table-column type="index" label="鎺掑悕" width="60" align="center" />
- <el-table-column prop="dept" label="绉戝" />
+ <el-table :data="rankRows" border size="small" max-height="400" v-loading="rankLoading" element-loading-text="鎺掑悕鍔犺浇涓�...">
+ <el-table-column prop="rank" label="鎺掑悕" width="60" align="center" />
+ <el-table-column prop="name" :label="rankDimLabel" />
<el-table-column prop="score" label="缁煎悎寰楀垎" width="120" align="center" />
+ <el-table-column prop="itemCount" label="椤规暟" width="100" align="center" />
</el-table>
</el-card>
</el-col>
- <!-- 6. 鍗曢绉戝 - 浜ゅ弶鍒嗘瀽琛ㄦ牸 -->
+ <!-- 6. 鍗曢脳绉戝/鐥呭尯 - 浜ゅ弶鎺掑悕琛ㄦ牸 -->
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<div slot="header" class="card-header">
- <span>鍗曢脳绉戝 路 绗�22棰樺悇绉戝寰楀垎</span>
+ <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="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 :data="crossRows" border size="small" max-height="400" v-loading="crossLoading" element-loading-text="鎺掑悕鍔犺浇涓�...">
+ <el-table-column prop="rank" label="鎺掑悕" width="60" align="center" />
+ <el-table-column prop="name" :label="crossDimLabel" />
<el-table-column prop="score" label="缁煎悎寰楀垎" width="120" align="center" />
+ <el-table-column prop="itemCount" label="椤规暟" width="100" align="center" />
</el-table>
</el-card>
</el-col>
@@ -89,114 +159,659 @@
<script>
import * as echarts from 'echarts';
+import { mydTrend, sltdMydTotalByDimension, sltdMydTotalByScore ,mzMydScoreRank} from "@/api/AiCentre/satisfaction";
+import { getissuelist } from "@/api/AiCentre/questionnaire";
+
+const DEPTS = ['淇濆仴绉�', '浠嬪叆娌荤枟绉�', '涓撶鎶ゅ+闂ㄨ瘖', '搴峰鍖诲绉�', '鐨偆绉�', '鍐呭垎娉岀', '娑堝寲鍐呯', '鑲惧唴绉�', '鍛煎惛鍐呯', '蹇冭绠″唴绉�', '绁炵粡鍐呯', '琛�娑茬', '椋庢箍鍏嶇柅绉�', '鑲跨槫鍐呯', '鎰熸煋绉�', '鑰佸勾绉�', '鏅绉�', '楠ㄧ', '娉屽翱澶栫', '濡囦骇绉�'];
+const WARDS = ['涓�鐥呭尯', '浜岀梾鍖�', '涓夌梾鍖�', '鍥涚梾鍖�', '浜旂梾鍖�', '鍏梾鍖�', '涓冪梾鍖�', '鍏梾鍖�'];
export default {
- name: "ChartsPanel",
+ name: 'ChartsPanel',
+ dicts: ['dimensionality_type'],
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" },
- ],
+ queryParams: {
+ dateRange: [],
+ interval: 'month',
+ dimension: 'ward', // 缁熻缁村害锛歸ard=鎸夌梾鍖虹粺璁� dept=鎸夌瀹ょ粺璁�
+ },
+ deptOptions: DEPTS,
+ wardOptions: WARDS,
+ questionOptions: [],
+
+ // 鍗曢绛涢��
+ singleQuestionId: null,
+ singleQuestionContent: '',
+ singleDeptWards: [],
+
+ // 鎺掑悕缁村害绛涢��
+ rankDimension: 'dept',
+ rankDeptWards: [],
+ crossDimension: 'dept',
+ crossDeptWards: [],
+
+ // 鍥捐〃鏁版嵁
+ trendLoading: false,
+ categoryLoading: false,
+ hospitalLoading: false,
+ trend: { buckets: [], data: [], seriesName: '闂ㄨ瘖鎮h�呮弧鎰忓害' },
+ categoryCompare: { categories: [], seriesNames: [], series1: [], series2: [] },
+ hospitalCompare: { hospitals: [], seriesNames: [], series1: [], series2: [] },
+
+ // 鍗曢 / 鎺掑悕 / 浜ゅ弶琛ㄥ姞杞界姸鎬�
+ singleLoading: false,
+ questionLoading: false,
+ rankLoading: false,
+ crossLoading: false,
+
+ // 琛ㄦ牸鏁版嵁
+ singleQuestionData: [],
+ singleQuestionMeta: { total: '0', score: '0.00' },
+ rankData: [],
+ crossData: [],
};
},
- mounted() { this.$nextTick(() => { this.initCharts(); }); },
+ computed: {
+ // 绉戝/鐥呭尯缂栫爜锛氫紭鍏堝彇褰撳墠鐢ㄦ埛鎵�灞烇紝鏃犲垯鍏滃簳鍚嶇О鏈韩浣滀负缂栫爜
+ deptCodeList() {
+ const list = this.$store.getters.belongDepts || [];
+ return list.length ? list : DEPTS.map((d) => ({ deptName: d, deptCode: d }));
+ },
+ wardCodeList() {
+ const list = this.$store.getters.belongWards || [];
+ return list.length ? list : WARDS.map((w) => ({ districtName: w, districtCode: w }));
+ },
+ defaultDeptCodes() {
+ return Array.from(new Set(this.deptCodeList.map((d) => d.deptCode)));
+ },
+ defaultWardCodes() {
+ return Array.from(new Set(this.wardCodeList.map((w) => w.districtCode)));
+ },
+ intervalLabel() {
+ return { day: '鎸夊ぉ', week: '鎸夊懆', 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() {
+ return this.rankData;
+ },
+ crossRows() {
+ return this.crossData;
+ },
+ },
+ mounted() {
+ this.$nextTick(() => {
+ if (!this.queryParams.dateRange || !this.queryParams.dateRange.length) {
+ this.queryParams.dateRange = this.defaultRange();
+ }
+ this.initCharts();
+ // 鍒濆鍖栧嵆鏄剧ず鍚勬暟鎹潡鐨勫姞杞界姸鎬侊紝閬垮厤鍏堥棯鐜扳�滄殏鏃犳暟鎹��
+ this.trendLoading = true;
+ this.categoryLoading = true;
+ this.hospitalLoading = true;
+ this.singleLoading = true;
+ this.rankLoading = true;
+ this.crossLoading = true;
+ // 鏁版嵁鍧椾笌棰樼洰鍒楄〃骞惰鍔犺浇锛氭暟鎹潡涓嶄緷璧栭鐩垪琛紝鏃犻渶涓茶绛夊緟
+ this.refreshAll();
+ this.loadQuestions('').then(() => {
+ if (this.singleQuestionContent) {
+ // 鍗曢閫夐」缁熻 / 鍗曢浜ゅ弶鎺掑悕渚濊禆闂鏂囨湰锛岄鐩姞杞藉畬鎴愬悗琛ユ媺涓�娆�
+ this.fetchSingleQuestion();
+ this.fetchCross();
+ } else {
+ // 棰樼洰鍒楄〃涓虹┖鎴栧姞杞藉け璐ワ紝缁撴潫鍗曢鐩稿叧鍔犺浇鐘舵��
+ this.singleLoading = false;
+ this.crossLoading = false;
+ }
+ });
+ });
+ },
methods: {
initCharts() {
- const quarters = ['绗竴瀛e害', '绗簩瀛e害', '绗笁瀛e害', '绗洓瀛e害'];
- const categories = ['闅愮淇濇姢', '鐜涓庢爣璇�', '鍖诲姟浜哄憳鍥炲簲', '鍖荤敓娌熼��', '鎸傚彿浣撻獙', '鎶ゅ+娌熼��'];
- const hospitals = ['闄㈠尯涓�', '闄㈠尯浜�', '闄㈠尯涓�'];
- const baseGrid = { top: 20, right: 30, bottom: 30, left: 50 };
+ 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();
+ });
+ },
+ // 缁村害缂栫爜 鈫� 瀛楀吀鍚嶇О锛坉imensionality_type锛�
+ getDimensionLabel(code) {
+ const dict = this.dict && this.dict.type && this.dict.type.dimensionality_type;
+ if (dict) {
+ const hit = dict.find((item) => String(item.value) === String(code));
+ if (hit) return hit.label;
+ }
+ return code == null ? '' : String(code);
+ },
+ // 鍙�夐鐩細杩滅▼妯$硦鎼滅储锛堟寜闂鍐呭 scriptContent锛�
+ searchQuestions(keyword) {
+ this.loadQuestions(keyword || '');
+ },
+ // 鍔犺浇鍙�夐鐩紙鍙傝�冮棶棰樺垪琛ㄩ〉 getissuelist锛�
+ async loadQuestions(keyword) {
+ this.questionLoading = true;
+ try {
+ const res = await getissuelist({
+ pageNum: 1,
+ pageSize: 50,
+ isavailable: '',
+ scriptContent: keyword || '',
+ });
+ const rows = (res && res.rows) || [];
+ this.questionOptions = rows.map((q) => ({
+ id: q.id,
+ scriptContent: q.scriptContent || q.title || '',
+ }));
+ // 鏃犲叧閿瓧鏃堕粯璁ら�変腑绗竴棰�
+ if (!keyword && this.questionOptions.length && !this.singleQuestionContent) {
+ const first = this.questionOptions[0];
+ this.singleQuestionId = first.id;
+ this.singleQuestionContent = first.scriptContent;
+ }
+ } catch (e) {
+ this.questionOptions = [];
+ } finally {
+ this.questionLoading = false;
+ }
+ },
+ onSingleQuestionChange(val) {
+ const q = this.questionOptions.find((o) => String(o.id) === String(val));
+ this.singleQuestionContent = q ? q.scriptContent : '';
+ this.refreshAll();
+ },
+ // 绉戝/鐥呭尯鍚嶇О 鈫� 缂栫爜
+ mapDeptNames(names) {
+ return names.map((n) => {
+ const hit = this.deptCodeList.find((d) => d.deptName === n);
+ return hit ? hit.deptCode : n;
+ });
+ },
+ mapWardNames(names) {
+ return names.map((n) => {
+ const hit = this.wardCodeList.find((w) => w.districtName === n);
+ return hit ? hit.districtCode : n;
+ });
+ },
+ // 缁煎悎寰楀垎锛堢湡瀹炲彛寰勶級锛毼�(閫夐」鍒嗗�� 脳 濉姤閲�) / 危(濉姤閲�)
+ scoreOfOptions(options) {
+ let num = 0;
+ let den = 0;
+ (options || []).forEach((o) => {
+ const c = Number(o && o.count) || 0;
+ const s = Number(o && o.score);
+ if (isNaN(s)) return;
+ num += s * c;
+ den += c;
+ });
+ if (!den) return '0.00';
+ return (num / den).toFixed(2);
+ },
+ // 瑙f瀽姣斾緥瀛楃涓诧細"14.29%"銆�"0"銆�"0.5" 鈫� 鏁板�硷紙鐧惧垎姣旓級锛涙棤鏁堣繑鍥� null
+ parsePercent(value) {
+ if (value == null || value === '') return null;
+ const n = Number(String(value).replace(/%/g, '').trim());
+ return isNaN(n) ? null : n;
+ },
+ // 鍗曢杩斿洖 List<QuestionResultDTO> 鈫� { options, total }
+ normalizeSingleQuestion(list) {
+ const arr = Array.isArray(list) ? list : (list && (list.list || list.rows)) || [];
+ const options = [];
+ let total = 0;
+ arr.forEach((item) => {
+ const details = Array.isArray(item && item.subtaskDetailRatioExportList)
+ ? item.subtaskDetailRatioExportList
+ : [];
+ details.forEach((d) => {
+ const option = (d && (d.optionresult || d.targetvalue || d.optioncontent || d.questiontext)) || '';
+ const count = Number(d && d.count) || 0;
+ const rawScore = Number(d && d.score);
+ const ratio = this.parsePercent(d && d.ratio);
+ options.push({ option, count, score: isNaN(rawScore) ? null : rawScore, ratio });
+ total += count;
+ });
+ });
+ return { options, total };
+ },
+ // 鍗曢閫夐」缁熻锛堢湡瀹炴帴鍙� /smartor/serviceSubtaskDetail/sltdMydTotalByScore锛�
+ async fetchSingleQuestion() {
+ const range = this.queryParams.dateRange || [];
+ if (range.length !== 2 || !range[0] || !range[1] || !this.singleQuestionContent) {
+ this.singleQuestionData = [];
+ this.singleQuestionMeta = { total: '0', score: '0.00' };
+ return;
+ }
+ this.singleLoading = true;
+ try {
+ const payload = {
+ deptOrDistrict: '1',
+ serviceTypeList: ['6', '14'],
+ startFinishTime: range[0],
+ endFinishTime: range[1],
+ questionContent: this.singleQuestionContent,
+ };
+ const sel = this.singleDeptWards || [];
+ if (this.queryParams.dimension === 'dept') {
+ const names = sel.map((s) => String(s).replace(/^dept:/, ''));
+ payload.leaveldeptcodes = names.length ? this.mapDeptNames(names) : this.defaultDeptCodes;
+ } else {
+ const names = sel.map((s) => String(s).replace(/^ward:/, ''));
+ payload.leavehospitaldistrictcodes = names.length ? this.mapWardNames(names) : this.defaultWardCodes;
+ }
+ 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);
+ this.singleQuestionData = options.map((o) => ({
+ option: o.option,
+ count: o.count,
+ rate: o.ratio != null
+ ? o.ratio.toFixed(2) + '%'
+ : (total ? ((o.count / total) * 100).toFixed(2) + '%' : '0.00%'),
+ }));
+ this.singleQuestionMeta = {
+ total: total.toLocaleString(),
+ score: this.scoreOfOptions(options),
+ };
+ if (!options.length) this.notify('褰撳墠鏉′欢涓嬫殏鏃犺棰樼殑閫夐」缁熻鏁版嵁', 'warning');
+ } catch (e) {
+ this.singleQuestionData = [];
+ this.singleQuestionMeta = { total: '0', score: '0.00' };
+ this.notify('鍗曢閫夐」缁熻鏁版嵁璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯', 'error');
+ } finally {
+ this.singleLoading = false;
+ }
+ },
+ handleReset() {
+ this.queryParams.dateRange = this.defaultRange();
+ this.queryParams.interval = 'month';
+ this.queryParams.dimension = 'ward';
+ this.singleDeptWards = [];
+ this.refreshAll();
+ },
+ // 鍒囨崲鍏ㄥ眬缁熻缁村害鏃舵竻绌哄崟棰樼瓫閫夛紝淇濊瘉绉戝/鐥呭尯浜掓枼
+ onDimensionChange() {
+ this.singleDeptWards = [];
+ 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() - 91);
+ 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 === 'week') 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 === 'week') {
+ for (let i = 5; i >= 0; i--) {
+ const d = new Date(end);
+ d.setDate(end.getDate() - i * 7);
+ 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;
+ },
+ // 鍒锋柊鍏ㄩ儴鏁版嵁锛堣秼鍔裤�佸垎绫荤淮搴︺�侀櫌鍖虹淮搴︺�佸崟棰樸�佹帓鍚嶃�佷氦鍙夊潎涓虹湡瀹炴帴鍙o級
+ async refreshAll() {
+ // 鍏釜鏁版嵁鍧楀苟琛屾媺鍙栵紝鍚勫尯鍧楀悓鏃舵樉绀哄悇鑷殑鍔犺浇鐘舵��
+ await Promise.all([
+ this.fetchTrend(),
+ this.fetchCategoryCompare(),
+ this.fetchHospitalCompare(),
+ this.fetchSingleQuestion(),
+ this.fetchRank(),
+ this.fetchCross(),
+ ]);
+
+ this.renderCharts();
+ },
+ // 婊℃剰搴﹁秼鍔匡紙鐪熷疄鎺ュ彛锛夛細data = [{ period, totalScore }]
+ async fetchTrend() {
+ const range = this.queryParams.dateRange || [];
+ if (range.length !== 2 || !range[0] || !range[1]) {
+ this.trend = { buckets: [], data: [], seriesName: '闂ㄨ瘖鎮h�呮弧鎰忓害' };
+ return;
+ }
+
+ this.trendLoading = true;
+ try {
+ const res = await mydTrend({
+ startFinishTime: range[0],
+ endFinishTime: range[1],
+ type: this.queryParams.interval, // day / week / month / year
+ });
+ const list = res && res.code === 200 && Array.isArray(res.data) ? res.data : [];
+ this.trend = {
+ seriesName: '闂ㄨ瘖鎮h�呮弧鎰忓害',
+ buckets: list.map((i) => String((i && i.period) || '')),
+ data: list.map((i) => {
+ const n = Number(i && i.totalScore);
+ return isNaN(n) ? 0 : Math.round(n * 100) / 100;
+ }),
+ };
+ if (!list.length) this.notify('褰撳墠鏉′欢涓嬫殏鏃犺秼鍔挎暟鎹�', 'warning');
+ } catch (e) {
+ this.trend = { buckets: [], data: [], seriesName: '闂ㄨ瘖鎮h�呮弧鎰忓害' };
+ this.notify('瓒嬪娍鏁版嵁璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯', 'error');
+ } finally {
+ this.trendLoading = false;
+ }
+ },
+ notify(msg, type) {
+ const fn = { warning: 'warning', error: 'error', success: 'success' }[type] || 'info';
+ if (this.$message && this.$message[fn]) this.$message[fn](msg);
+ },
+ // 鍒嗙被缁村害瀵规瘮锛堢湡瀹炴帴鍙o級锛歞imensionType=1锛宒ata = [{ period, dimension, totalScore }]
+ async fetchCategoryCompare() {
+ const range = this.queryParams.dateRange || [];
+ if (range.length !== 2 || !range[0] || !range[1]) {
+ this.categoryCompare = { categories: [], seriesNames: [], series1: [], series2: [] };
+ return;
+ }
+ this.categoryLoading = true;
+ try {
+ const res = await sltdMydTotalByDimension({
+ startFinishTime: range[0],
+ endFinishTime: range[1],
+ type: this.queryParams.interval, // day / week / month / year
+ dimensionType: "1", // 1=鎸夋弧鎰忓害缁村害缁熻
+ });
+ // 鎺掓煡鐢細鎵撳嵃鎺ュ彛鍘熷杩斿洖锛屼究浜庢牳瀵瑰瓧娈靛悕锛堢‘璁ゆ棤璇悗鍙垹闄わ級
+ console.log("[sltdMydTotalByDimension dimensionType=1] code=", res && res.code, "data=", JSON.stringify(res && res.data).slice(0, 2000));
+ const list = res && res.code === 200 && Array.isArray(res.data) ? res.data : [];
+ // 缁村害缂栫爜 鈫� 瀛楀吀鍚嶇О锛屽苟杩囨护涓ゆ湡鍧囨棤鏁版嵁鐨勭淮搴�
+ const d = this.buildCompareData(list, (code) => this.getDimensionLabel(code));
+ this.categoryCompare = { categories: d.labels, seriesNames: d.seriesNames, series1: d.series1, series2: d.series2 };
+ if (!list.length) this.notify('褰撳墠鏉′欢涓嬫殏鏃犲垎绫荤淮搴︽暟鎹�', 'warning');
+ } catch (e) {
+ this.categoryCompare = { categories: [], seriesNames: [], series1: [], series2: [] };
+ this.notify('鍒嗙被缁村害鏁版嵁璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯', 'error');
+ } finally {
+ this.categoryLoading = false;
+ }
+ },
+ // 闄㈠尯缁村害瀵规瘮锛堢湡瀹炴帴鍙o級锛歞imensionType=2锛宒ata = [{ period, dimension(闄㈠尯鍚�), totalScore }]
+ async fetchHospitalCompare() {
+ const range = this.queryParams.dateRange || [];
+ if (range.length !== 2 || !range[0] || !range[1]) {
+ this.hospitalCompare = { hospitals: [], seriesNames: [], series1: [], series2: [] };
+ return;
+ }
+ this.hospitalLoading = true;
+ try {
+ const res = await sltdMydTotalByDimension({
+ startFinishTime: range[0],
+ endFinishTime: range[1],
+ type: this.queryParams.interval, // day / week / month / year
+ dimensionType: "2", // 2=鎸夐櫌鍖虹粺璁�
+ });
+ // 鎺掓煡鐢細鎵撳嵃鎺ュ彛鍘熷杩斿洖锛屼究浜庢牳瀵瑰瓧娈靛悕锛堢‘璁ゆ棤璇悗鍙垹闄わ級
+ console.log("[sltdMydTotalByDimension dimensionType=2] code=", res && res.code, "data=", JSON.stringify(res && res.data).slice(0, 2000));
+ const list = res && res.code === 200 && Array.isArray(res.data) ? res.data : [];
+ const d = this.buildCompareData(list);
+ this.hospitalCompare = { hospitals: d.labels, seriesNames: d.seriesNames, series1: d.series1, series2: d.series2 };
+ if (!list.length) this.notify('褰撳墠鏉′欢涓嬫殏鏃犻櫌鍖虹淮搴︽暟鎹�', 'warning');
+ } catch (e) {
+ this.hospitalCompare = { hospitals: [], seriesNames: [], series1: [], series2: [] };
+ this.notify('闄㈠尯缁村害鏁版嵁璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯', 'error');
+ } finally {
+ this.hospitalLoading = false;
+ }
+ },
+ // 灏� [{ period, dimension, totalScore }] 缁勮涓哄垎缁勬煴鐘跺浘鏁版嵁锛堜笂鏈�/鏈湡涓ょ粍瀵规瘮锛�
+ // labelFn锛氬彲閫夛紝鐢ㄤ簬鎶婄淮搴︾紪鐮佹槧灏勪负瀛楀吀鍚嶇О锛堝垎绫荤淮搴﹀浘浼犲叆锛�
+ buildCompareData(list, labelFn) {
+ const labels = [];
+ const byCat = {};
+ list.forEach((i) => {
+ // 缁村害缂栫爜锛坉imensionType=1锛夋垨闄㈠尯鍚嶇О锛坉imensionType=2锛夛紱鑻ュ悗绔瓧娈靛懡鍚嶄笉鍚岋紝鍏滃簳鍙� period
+ const key = (i && (i.dimension || i.campusName || i.campusid)) || (i && i.period) || '';
+ if (!key) return;
+ if (!byCat[key]) { byCat[key] = {}; labels.push(key); }
+ const n = Number(i.totalScore);
+ byCat[key][i.period] = isNaN(n) ? 0 : Math.round(n * 100) / 100;
+ });
+ // 鍚庣鎸夋椂闂村崌搴忚繑鍥烇紝鍙栨渶鍚庝袱涓懆鏈熶綔涓衡�滀笂鏈� / 鏈湡鈥�
+ const periods = [...new Set(list.map((i) => i && i.period).filter(Boolean))];
+ const cur = periods[periods.length - 1];
+ const prev = periods[periods.length - 2];
+ const val = (cat, p) => (p != null && byCat[cat][p] != null ? byCat[cat][p] : 0);
+ // 杩囨护涓ゆ湡鍧囨棤鏁版嵁鐨勭淮搴�
+ const cats = labels.filter((c) => val(c, prev) !== 0 || val(c, cur) !== 0);
+ const display = labelFn ? cats.map((c) => labelFn(c)) : cats;
+ return {
+ labels: display,
+ seriesNames: this.intervalConfig().seriesNames,
+ series1: cats.map((c) => val(c, prev)),
+ series2: cats.map((c) => val(c, cur)),
+ };
+ },
+ // 绉戝/鐥呭尯缁村害 路 闂ㄨ瘖鎮h�呮弧鎰忓害寰楀垎鎺掑悕锛堢湡瀹炴帴鍙� /mzMydScoreRank锛屼笉甯﹂棶棰樻枃鏈級
+ async fetchRank() {
+ const range = this.queryParams.dateRange || [];
+ if (range.length !== 2 || !range[0] || !range[1]) {
+ this.rankData = [];
+ return;
+ }
+ this.rankLoading = true;
+ try {
+ const res = await mzMydScoreRank(this.buildRankPayload(this.rankDimension, this.rankDeptWards, ''));
+ this.rankData = this.normalizeRank(res);
+ if (!this.rankData.length) this.notify('褰撳墠鏉′欢涓嬫殏鏃犳帓鍚嶆暟鎹�', 'warning');
+ } catch (e) {
+ this.rankData = [];
+ this.notify('鎺掑悕鏁版嵁璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯', 'error');
+ } finally {
+ this.rankLoading = false;
+ }
+ },
+ // 鍗曢脳绉戝/鐥呭尯 路 寰楀垎鎺掑悕锛堢湡瀹炴帴鍙� /mzMydScoreRank锛屽甫闂鏂囨湰锛�
+ async fetchCross() {
+ const range = this.queryParams.dateRange || [];
+ if (range.length !== 2 || !range[0] || !range[1] || !this.singleQuestionContent) {
+ this.crossData = [];
+ return;
+ }
+ this.crossLoading = true;
+ try {
+ const res = await mzMydScoreRank(this.buildRankPayload(this.crossDimension, this.crossDeptWards, this.singleQuestionContent));
+ this.crossData = this.normalizeRank(res);
+ if (!this.crossData.length) this.notify('褰撳墠鏉′欢涓嬫殏鏃犳帓鍚嶆暟鎹�', 'warning');
+ } catch (e) {
+ this.crossData = [];
+ this.notify('鎺掑悕鏁版嵁璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯', 'error');
+ } finally {
+ this.crossLoading = false;
+ }
+ },
+ // 缁勮鎺掑悕鎺ュ彛璇锋眰浣擄紙dimensionType/serviceType/deptCodes/districtCodes/鏃堕棿/questionText锛�
+ buildRankPayload(dimension, selectedNames, questionText) {
+ const range = this.queryParams.dateRange || [];
+ const payload = {
+ dimensionType: dimension === 'dept' ? '1' : '2',
+ serviceTypes: ['6', '14'],
+ startFinishTime: range[0],
+ endFinishTime: range[1],
+ };
+ if (questionText) payload.questionText = questionText;
+ if (selectedNames && selectedNames.length) {
+ if (dimension === 'dept') {
+ payload.deptCodes = this.mapDeptNames(selectedNames);
+ } else {
+ payload.districtCodes = this.mapWardNames(selectedNames);
+ }
+ }
+ return payload;
+ },
+ // 褰掍竴鍖栨帓鍚嶈繑鍥� List<MzMydScoreRankDTO> 鈫� [{ rank, code, name, score, itemCount }]
+ normalizeRank(res) {
+ const list = res && res.code === 200 && Array.isArray(res.data) ? res.data : [];
+ return list.map((item) => ({
+ rank: item.rank,
+ code: item.code,
+ name: item.name,
+ score: item.score,
+ itemCount: item.itemCount,
+ }));
+ },
+ 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. 鎶樼嚎鍥� - 鏃堕棿缁村害
- const c1 = echarts.init(this.$refs.chart1);
c1.setOption({
color: ['#5B8FF9'],
- grid: { ...baseGrid, top: 25 },
+ grid: lineGrid,
tooltip: { ...baseTooltip, trigger: 'axis' },
- xAxis: [{ ...baseXAxis, data: quarters, boundaryGap: false }],
- yAxis: [{ ...baseYAxis, min: 86, max: 91, name: '婊℃剰搴﹀緱鍒�', nameTextStyle: { color: '#999', fontSize: 11 } }],
+ xAxis: [{ ...baseXAxis, data: this.trend.buckets, boundaryGap: false }],
+ yAxis: [{ ...baseYAxis, name: '婊℃剰搴﹀緱鍒�', nameTextStyle: { color: '#999', fontSize: 11 } }],
series: [{
- name: '闂ㄨ瘖鎮h�呮弧鎰忓害', type: 'line', smooth: true, symbol: 'circle', symbolSize: 8,
- data: [88.20, 87.32, 88.50, 89.17],
+ 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: 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)' }
- ])},
+ areaStyle: { color: areaGrad },
}],
});
// 2. 鍒嗙粍鏌辩姸鍥� - 鍒嗙被缁村害
- const c2 = echarts.init(this.$refs.chart2);
- c2.setOption({
+ // 鍒嗙被杈冨锛堝鍗佸嚑涓弧鎰忓害缁村害锛夋椂搴曢儴鍚嶇О浼氶噸鍙狅紝杩欓噷鎸夋暟閲忓姩鎬侀�傞厤锛�
+ // 鈮�6 涓細姝e父姘村钩鏍囩锛�
+ // 7~10 涓細x 杞存爣绛惧�炬枩 35掳 + 搴曢儴鐣欑櫧鍔犲ぇ + 鏌卞瓙鍙樼粏锛�
+ // >10 涓細鍐嶅惎鐢ㄦí鍚戠缉鏀撅紙鍒濆鏄剧ず绾� 8 涓垎绫伙紝鍙嫋鍔�/婊氬姩鏌ョ湅鍏朵綑锛夈��
+ const catCategories = this.categoryCompare.categories || [];
+ const catN = catCategories.length;
+ const catRotate = catN > 6 ? 35 : 0;
+ const catEnableZoom = catN > 10;
+ const catBarWidth = catN > 10 ? 18 : catN > 6 ? 20 : 22;
+ const catLegend = catEnableZoom
+ ? { data: this.categoryCompare.seriesNames, top: 4, textStyle: { color: '#666', fontSize: 12 } }
+ : { data: this.categoryCompare.seriesNames, bottom: 8, textStyle: { color: '#666', fontSize: 12 } };
+ const catGrid = catEnableZoom
+ ? { top: 36, right: 30, bottom: 80, left: 85 }
+ : { top: 48, right: 30, bottom: catRotate ? 100 : 60, left: 85 };
+ const catOption = {
color: ['#6DC8EC', '#5B8FF9'],
- grid: baseGrid,
+ grid: catGrid,
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 } }],
+ legend: catLegend,
+ xAxis: [{ ...baseXAxis, data: catCategories, axisLabel: { ...baseXAxis.axisLabel, interval: 0, rotate: catRotate } }],
yAxis: [{ ...baseYAxis, name: '寰楀垎', nameTextStyle: { color: '#999', fontSize: 11 } }],
series: [
- { name: '2023骞�', type: 'bar', barWidth: 22, barGap: '30%',
+ { name: this.categoryCompare.seriesNames[0], type: 'bar', barWidth: catBarWidth, 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,
+ data: this.categoryCompare.series1 },
+ { name: this.categoryCompare.seriesNames[1], type: 'bar', barWidth: catBarWidth,
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] },
+ data: this.categoryCompare.series2 },
],
- });
+ };
+ if (catEnableZoom) {
+ // 鍒濆鍙睍绀哄墠 8 涓垎绫诲搴︼紝閬垮厤鏌卞瓙鎸ゆ垚涓�鍥紝鐢ㄦ埛鍙í鍚戞粴鍔�/鎷栧姩鏌ョ湅鍏朵綑
+ const zoomEnd = catN > 0 ? Math.min(100, Math.round((8 / catN) * 100)) : 100;
+ catOption.dataZoom = [
+ { type: 'inside', start: 0, end: zoomEnd },
+ { type: 'slider', height: 16, bottom: 4, start: 0, end: zoomEnd },
+ ];
+ }
+ c2.setOption(catOption);
// 4. 闄㈠尯缁村害 - 鍒嗙粍鏌辩姸鍥�
- const c4 = echarts.init(this.$refs.chart4);
c4.setOption({
color: ['#6DC8EC', '#5B8FF9'],
- grid: baseGrid,
+ grid: barGrid,
tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow' } },
- legend: { data: ['2023骞�', '2024骞�'], bottom: 0, textStyle: { color: '#666', fontSize: 12 } },
- xAxis: [{ ...baseXAxis, data: hospitals }],
+ 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: '2023骞�', type: 'bar', barWidth: 24, barGap: '30%',
+ { 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: [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,
+ 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: [88.15, 88.14, 85.88],
- label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: p => p.value.toFixed(2) } },
+ data: this.hospitalCompare.series2,
+ 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' },
@@ -212,19 +827,7 @@
}
}
- // 鍏嬮殕鍐呭骞舵浛鎹㈠浘琛ㄤ负鍥剧墖
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) => {
@@ -246,7 +849,7 @@
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; }
+ .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}
@@ -260,9 +863,13 @@
</script>
<style lang="scss" scoped>
-.export-bar { text-align: right; margin-bottom: 16px; }
+.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; }
+.chart-wrap { position: relative; width: 100%; }
+.chart-empty { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; background: #fff; color: #909399; font-size: 14px; }
.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>
--
Gitblit v1.9.3