|
@@ -1,7 +1,23 @@
|
|
|
<template>
|
|
|
<div class="consultationChart">
|
|
|
- <div ref="chart" class="chart-container" style="height:400px;"></div>
|
|
|
- <div v-if="errorMessage" class="error">{{ errorMessage }}</div>
|
|
|
+ <div class="legend">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in realData"
|
|
|
+ :key="item.name"
|
|
|
+ class="legend-item"
|
|
|
+ :style="{ color: colors[index] }"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ class="color-block"
|
|
|
+ :style="{ backgroundColor: colors[index] }"
|
|
|
+ ></span>
|
|
|
+ <div>
|
|
|
+ <div class="name">{{ item.name }}</div>
|
|
|
+ <div class="value">{{ item.value }}次</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart" ref="chartDom"></div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
@@ -9,144 +25,136 @@
|
|
|
import * as echarts from "echarts";
|
|
|
|
|
|
export default {
|
|
|
- name: "ConsultationChart",
|
|
|
data() {
|
|
|
return {
|
|
|
+ colors: ["#5470C6", "#91CC75", "#FAC858", "#EE6666", "#73C0DE"],
|
|
|
chart: null,
|
|
|
- errorMessage: null
|
|
|
- };
|
|
|
- },
|
|
|
- mounted() {
|
|
|
- try {
|
|
|
- if (!echarts) throw new Error("ECharts 未正确加载");
|
|
|
-
|
|
|
- this.initChart();
|
|
|
- window.addEventListener("resize", this.resizeChart);
|
|
|
- this.observeParentSize();
|
|
|
- } catch (error) {
|
|
|
- this.errorMessage = "图表初始化失败: " + error.message;
|
|
|
- console.error(error);
|
|
|
- }
|
|
|
- },
|
|
|
- beforeDestroy() {
|
|
|
- window.removeEventListener("resize", this.resizeChart);
|
|
|
- if (this.resizeObserver) this.resizeObserver.disconnect();
|
|
|
- if (this.chart) this.chart.dispose();
|
|
|
- },
|
|
|
- methods: {
|
|
|
- initChart() {
|
|
|
- const chartDom = this.$refs.chart;
|
|
|
- if (!chartDom) throw new Error("图表容器未找到");
|
|
|
-
|
|
|
- this.chart = echarts.init(chartDom);
|
|
|
-
|
|
|
- // 堆叠柱状图数据(实际人数)
|
|
|
- const data = {
|
|
|
- stages: ["提醒会诊", "发起会诊", "执行会诊", "建议转科", "直接转科"],
|
|
|
- values: [200, 180, 140, 100, 50],
|
|
|
- lost: [] // 存储各阶段流失人数
|
|
|
- };
|
|
|
-
|
|
|
- // 计算各阶段流失人数
|
|
|
- data.lost = data.values.map((value, index) => {
|
|
|
- return index === 0 ? 0 : data.values[index-1] - value;
|
|
|
- });
|
|
|
-
|
|
|
- const option = {
|
|
|
+ realData: [], // 存储真实数据
|
|
|
+ chartOption: {
|
|
|
title: {
|
|
|
- text: '会诊转化数据',
|
|
|
- left: 'center',
|
|
|
- top: '3%',
|
|
|
+ text: "会诊转化数据",
|
|
|
+ left: "center",
|
|
|
},
|
|
|
// tooltip: {
|
|
|
- // trigger: 'axis',
|
|
|
- // axisPointer: { type: 'shadow' },
|
|
|
+ // trigger: "item",
|
|
|
// formatter: (params) => {
|
|
|
- // let result = `${params[0].name}<br/>`;
|
|
|
- // params.forEach(p => {
|
|
|
- // result += `${p.marker} ${p.seriesName}: ${p.value}人<br/>`;
|
|
|
- // });
|
|
|
- // return result;
|
|
|
+ // return `${params.name}<br/>占比:${params.value}%`;
|
|
|
// }
|
|
|
// },
|
|
|
- grid: {
|
|
|
- left: '10%',
|
|
|
- right: '10%',
|
|
|
- bottom: '10%',
|
|
|
- top: '20%',
|
|
|
- containLabel: true
|
|
|
- },
|
|
|
- xAxis: {
|
|
|
- type: 'category',
|
|
|
- data: data.stages,
|
|
|
- axisLabel: {
|
|
|
- interval: 0,
|
|
|
- rotate: 45
|
|
|
- }
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- type: 'value',
|
|
|
- name: '人数',
|
|
|
- min: 0
|
|
|
- },
|
|
|
series: [
|
|
|
{
|
|
|
- name: '当前阶段人数',
|
|
|
- type: 'bar',
|
|
|
- stack: 'total',
|
|
|
- barWidth: '50%',
|
|
|
+ type: "funnel",
|
|
|
+ sort: "none",
|
|
|
+ top: 40,
|
|
|
+ bottom: 40,
|
|
|
+ width: "70%",
|
|
|
+ left: "15%",
|
|
|
+ gap: 0,
|
|
|
label: {
|
|
|
show: true,
|
|
|
- position: 'inside',
|
|
|
- formatter: (params) => `${params.value}人`
|
|
|
+ position: "inside",
|
|
|
+ // formatter: "{c}%",
|
|
|
+ color: "#fff",
|
|
|
+ fontSize: 12
|
|
|
},
|
|
|
- data: data.values,
|
|
|
itemStyle: {
|
|
|
- color: '#3c67dc'
|
|
|
- }
|
|
|
- },
|
|
|
- {
|
|
|
- name: '流失人数',
|
|
|
- type: 'bar',
|
|
|
- stack: 'total',
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- position: 'inside',
|
|
|
- formatter: (params) => params.value > 0 ? `${params.value}人` : ''
|
|
|
+ borderColor: "#fff",
|
|
|
+ borderWidth: 2,
|
|
|
},
|
|
|
- data: data.lost,
|
|
|
- itemStyle: {
|
|
|
- color: '#c0c4cc'
|
|
|
- }
|
|
|
- }
|
|
|
- ]
|
|
|
- };
|
|
|
+ data: [
|
|
|
+ { value: 100, name: "提醒会诊" },
|
|
|
+ { value: 80, name: "发起会诊" },
|
|
|
+ { value: 60, name: "执行会诊" },
|
|
|
+ { value: 40, name: "建议转科" },
|
|
|
+ { value: 20, name: "转科" }
|
|
|
+ ],
|
|
|
+ color: ["#5470C6", "#91CC75", "#FAC858", "#EE6666", "#73C0DE"],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.initChart();
|
|
|
+ this.fetchData(); // 模拟接口请求
|
|
|
+ window.addEventListener("resize", this.handleResize);
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ window.removeEventListener("resize", this.handleResize);
|
|
|
+ if (this.chart) this.chart.dispose();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 模拟接口获取真实数据
|
|
|
+ fetchData() {
|
|
|
+ setTimeout(() => {
|
|
|
+ // 示例数据(实际替换为接口返回数据)
|
|
|
+ this.realData = [
|
|
|
+ { name: "提醒会诊", value: 2345 },
|
|
|
+ { name: "发起会诊", value: 1876 },
|
|
|
+ { name: "执行会诊", value: 1423 },
|
|
|
+ { name: "建议转科", value: 876 },
|
|
|
+ { name: "直接转科", value: 345 }
|
|
|
+ ];
|
|
|
+ }, 500);
|
|
|
+ },
|
|
|
|
|
|
- try {
|
|
|
- this.chart.setOption(option);
|
|
|
- } catch (error) {
|
|
|
- throw new Error("图表配置错误: " + error.message);
|
|
|
- }
|
|
|
+ initChart() {
|
|
|
+ this.chart = echarts.init(this.$refs.chartDom);
|
|
|
+ this.chart.setOption(this.chartOption);
|
|
|
+
|
|
|
+ new ResizeObserver(() => this.chart?.resize()).observe(
|
|
|
+ this.$refs.chartDom
|
|
|
+ );
|
|
|
},
|
|
|
- resizeChart() {
|
|
|
- if (this.chart) this.chart.resize();
|
|
|
+ handleResize() {
|
|
|
+ this.chart?.resize();
|
|
|
},
|
|
|
- observeParentSize() {
|
|
|
- const parent = this.$el.parentElement;
|
|
|
- if (parent && 'ResizeObserver' in window) {
|
|
|
- this.resizeObserver = new ResizeObserver(() => this.resizeChart());
|
|
|
- this.resizeObserver.observe(parent);
|
|
|
- } else {
|
|
|
- console.warn("当前环境不支持 ResizeObserver");
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.consultationChart {
|
|
|
+ width: 100%;
|
|
|
+ height: 360px;
|
|
|
+ display: flex;
|
|
|
+ padding: 20px;
|
|
|
+
|
|
|
+ .legend {
|
|
|
+ width: 180px;
|
|
|
+ padding: 40px 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20px;
|
|
|
+ border-right: 1px solid #eee;
|
|
|
+
|
|
|
+ &-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ .color-block {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 4px;
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .name {
|
|
|
+ font-weight: 500;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .value {
|
|
|
+ color: #666;
|
|
|
+ font-size: 13px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-};
|
|
|
-</script>
|
|
|
|
|
|
-<style scoped>
|
|
|
-.error {
|
|
|
- color: red;
|
|
|
- padding: 10px;
|
|
|
+ .chart {
|
|
|
+ flex: 1;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|