Vite+Vue3 + Echarts 封装
摘要:Echarts封装 <template> <div ref="chartRef" class="echarts-container"></div> </template> <script> import * as echarts from "echarts/core"; import { onMounted, onUnmounted, ref, watch } from "vue"; // 引入柱状图图表,图表后缀都为 Chart // import { BarChart, PieChart } from "echarts/charts"; // 标签自动布局、全局过渡动画等特性 import { DatasetComponent, GridComponent, LegendComponent, TitleComponent, TooltipComponent, } from "echarts/components"; import { LabelLayout, UniversalTransition } from "echarts/features"; import { SVGRenderer } from "echarts/renderers"; // 必须显式注册组件 echarts.use([ TitleComponent, TooltipComponent, GridComponent, LegendComponent, DatasetComponent, LabelLayout, UniversalTransition, SVGRenderer, ] ; // 图表类型映射表 const CHART_MODULES = { bar: async ( =</script><!--autointro-->...
Echarts封装
<template>
<div ref="chartRef" class="echarts-container"></div>
</template>
<script>
import * as echarts from "echarts/core";
import { onMounted, onUnmounted, ref, watch } from "vue";
// 引入柱状图图表,图表后缀都为 Chart
// import { BarChart, PieChart } from "echarts/charts";
// 标签自动布局、全局过渡动画等特性
import {
DatasetComponent,
GridComponent,
LegendComponent,
TitleComponent,
TooltipComponent,
} from "echarts/components";
import { LabelLayout, UniversalTransition } from "echarts/features";
import { SVGRenderer } from "echarts/renderers";
// 必须显式注册组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
DatasetComponent,
LabelLayout,
UniversalTransition,
SVGRenderer,
]);
// 图表类型映射表
const CHART_MODULES = {
bar: async () => (await import(/* @vite-ignore */ "echarts/charts")).BarChart,
line: async () =>
(await import(/* @vite-ignore */ "echarts/charts")).LineChart,
pie: async () => (await import(/* @vite-ignore */ "echarts/charts")).PieChart,
scatter: async () =>
(await import(/* @vite-ignore */ "echarts/charts")).ScatterChart,
// gauge: async () =>
// (await import(/* @vite-ignore */ "echarts/charts")).GaugeChart,
// radar: async () =>
// (await import(/* @vite-ignore */ "echarts/charts")).RadarChart,
// treemap: async () =>
// (await import(/* @vite-ignore */ "echarts/charts")).TreemapChart,
custom: () => Promise.resolve(null), // 自定义类型不需要图表
};
// 已加载图表缓存
const loadedCharts = new Set();
export default {
props: {
type: {
type: String,
default: "line",
validator: (v) => Object.keys(CHART_MODULES).includes(v),
},
data: {
type: [Array, Object],
required: true,
},
options: {
type: Object,
default: () => ({}),
},
theme: {
type: [String, Object],
default: "light",
},
colors: {
type: Array,
default: () => ["#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de"],
},
loading: Boolean,
autoResize: {
type: Boolean,
default: true,
},
initOptions: {
type: Object,
default: () => ({}),
},
},
emits: ["ready", "click"],
setup(props, { emit }) {
const chartRef = ref(null);
let chartInstance = null;
let observer = null;
// 动态加载图表模块
const loadChartModule = async (type) => {
try {
if (loadedCharts.has(type)) return;
const moduleLoader = CHART_MODULES[type];
if (!moduleLoader) return;
const chartModule = await moduleLoader();
if (chartModule) {
echarts.use(chartModule);
loadedCharts.add(type);
}
} catch (error) {
console.error(`[ECharts] Failed to load ${type} chart:`, error);
emit("error", error);
if (type != "line") {
await loadChartModule("line");
}
}
};
// 基础配置模板
const baseOptions = {
color: props.colors,
tooltip: { trigger: "axis" },
grid: {
containLabel: true,
left: 16,
right: 16,
top: 40,
bottom: 16,
},
};
// 图表类型模板
const chartTemplates = {
line: {
xAxis: { type: "category" },
yAxis: { type: "value" },
series: [{ type: "line" }],
},
bar: {
xAxis: { type: "category" },
yAxis: { type: "value" },
series: [{ type: "bar" }],
},
pie: {
tooltip: { trigger: "item" },
series: [
{
type: "pie",
radius: "50%",
emphasis: { focus: "scale" },
},
],
},
scatter: {
xAxis: { type: "value" },
yAxis: { type: "value" },
series: [{ type: "scatter" }],
},
};
// 数据标准化处理
const normalizeData = (rawData) => {
if (props.type === "pie") {
return rawData.map((item) => ({
name: item.name || item.label,
value: item.value || item.data,
}));
}
return rawData;
};
// 生成最终配置
const generateOptions = () => {
const normalizedData = normalizeData(props.data);
const template =
props.type === "custom" ? {} : chartTemplates[props.type];
return {
...baseOptions,
...template,
dataset: { source: normalizedData },
...props.options,
};
};
// 初始化图表
const initChart = async () => {
await loadChartModule(props.type);
if (!chartRef.value) return;
// 确保销毁旧实例
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
chartInstance = echarts.init(chartRef.value, props.theme, {
renderer: "svg",
...props.initOptions,
});
updateChart();
bindChartEvents();
setupResizeObserver();
emit("ready", chartInstance);
};
// 更新图表
const updateChart = () => {
if (!chartInstance) return;
const options = generateOptions();
chartInstance.setOption(options, true);
props.loading
? chartInstance.showLoading("default")
: chartInstance.hideLoading();
};
// 响应式容器尺寸变化
const setupResizeObserver = () => {
if (props.autoResize) {
if (typeof ResizeObserver !== "undefined") {
observer = new ResizeObserver(() => {
chartInstance && chartInstance.resize();
});
observer.observe(chartRef.value);
} else {
window.addEventListener("resize", handleResize);
}
}
};
// 窗口 resize 处理
const handleResize = () => {
chartInstance && chartInstance.resize();
};
// 事件绑定
const bindChartEvents = () => {
if (!chartInstance) return;
chartInstance.on("click", (params) => {
emit("click", params);
});
};
// 生命周期
onMounted(async () => {
await initChart();
});
onUnmounted(() => {
observer?.disconnect();
chartInstance?.dispose();
window.removeEventListener("resize", handleResize);
});
// 监听变化
watch(() => props.data, updateChart, { deep: true });
watch(
() => props.type,
(newType, oldType) => {
if (oldType != newType) {
initChart();
}
}
);
watch(() => props.loading, updateChart);
watch(() => props.options, updateChart, { deep: true });
return { chartRef };
},
};
</script>
<style scoped>
.echarts-container {
min-width: 500px;
min-height: 300px; /*必须明确高度*/
border: 1px dashed #999;
}
</style>
简单使用
<template>
<h1>Echarts 使用</h1>
<div>
<a-space>
<a-button type="primary" @click="currentType = 'line'"
>显示折线图</a-button
>
<a-button danger @click="currentType = 'bar'">显示柱状图</a-button>
</a-space>
<hr />
<EChartsWrapper
:type="currentType"
theme="light"
:colors="['#1890ff', '#52c41a']"
:options="{
title: {
text: '柱状图&折线图切换Demo',
subtext: '示例',
},
legend:
currentType == 'bar'
? {
bottom: '2%',
data: [
'Email',
'Union Ads',
'Video Ads',
'Direct',
'Search Engine',
],
}
: {
top: '10%',
},
series: [
{
name: 'Email',
type: currentType,
stack: 'Total',
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: 'Union Ads',
type: currentType,
stack: 'Total',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: 'Video Ads',
type: currentType,
stack: 'Total',
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: 'Direct',
type: currentType,
stack: 'Total',
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: 'Search Engine',
type: currentType,
stack: 'Total',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
grid:
currentType == 'bar'
? {
top: '20%',
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true,
}
: {
top: '25%',
left: '3%',
right: '4%',
bottom: '5%',
containLabel: true,
},
}"
:loading="false"
@click="handleChartClick"
/>
</div>
<div class="demo">
<div>
<EChartsWrapper
type="bar"
theme="light"
:data="barData"
:colors="['#1890ff', '#52c41a']"
autoResize
:options="{
title: {
text: '苹果销量柱状图',
subtext: 'Demo',
},
grid: {
top: '25%',
left: '3%',
right: '4%',
bottom: '5%',
containLabel: true,
},
}"
:loading="false"
:initOptions="{
locale: 'EN',
}"
@click="handleChartClick"
/>
</div>
<div>
<EChartsWrapper
type="pie"
:data="pieData"
:options="{
title: {
text: `饼状图Demo`,
left: 'center',
},
legend: {
left: 'center',
top: '10%',
pageTextStyle: {
overflow: 'truncate',
},
},
}"
@click="handleChartClick"
/>
</div>
</div>
<div>
<EChartsWrapper
type="pie"
:data="pieData"
:options="{
title: {
text: `饼状图Demo`,
left: 'center',
},
legend: {
left: 'center',
top: '10%',
pageTextStyle: {
overflow: 'truncate',
},
},
}"
@click="handleChartClick"
/>
</div>
</template>
<script>
import { ref } from "vue";
import EChartsWrapper from "./echarts.vue";
export default {
components: { EChartsWrapper },
setup() {
let currentType = ref("line");
const barData = ref([
["Product", "Sales", "aaa"],
["iPhone", 1230, 300],
["iPad", 880, 150],
["Mac", 660, 200],
["Apple Watch", 200, 200],
]);
const pieData = ref([
{ name: "Matcha Latte", value: 93.7 },
{ name: "Milk Tea", value: 55.1 },
{ name: "Cheese Cocoa", value: 82.5 },
{ name: "Walnut Brownie", value: 39.1 },
]);
const handleChartClick = (params) => {
console.log("Chart clicked:", params);
};
return { barData, handleChartClick, pieData, currentType };
},
};
/**
*
* // 二维数组格式(带表头)
[
['Product', 'Sales'],
['iPhone', 1000],
['iPad', 800]
]
// 对象数组格式(饼图专用)
[
{ name: 'iPhone', value: 1000 },
{ name: 'iPad', value: 800 }
]
*/
</script>
<style>
.demo {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 10px;
}
</style>
本文链接:https://blog.smallhao.fun/?id=24 转载需授权!
Chen’Blog版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!