Echart饼图效果:

未封装轮播效果
饼图组件chart-pie-stats-list.vue
html
<template>
<div class="chart-wrap flex">
<div class="item one">
<div class="chart" ref="chartRef"></div>
</div>
<div class="item one flex chart-text">
<div class="flex flex-wrap w-100 column">
<div
class="item one flex column pt15 pb15 chart-text-item"
v-for="(item, index) in dataObj.data"
:key="index"
>
<div class="title flex col-center pb10">
<div
class="icon-circle mr10"
:style="'background:' + color[index]"
></div>
<div class="text-cont">
<el-tooltip
:content="item.name.toString()"
effect="dark"
placement="top"
>
<span class="span-text-over">{{ item.name }}</span>
</el-tooltip>
</div>
</div>
<div class="value-percent flex-column">
<div
class="value flex flex-1 pb10"
v-if="fields.count"
>
<div class="text-cont flex row-right">
<el-tooltip
:content="item.value.toString()"
effect="dark"
placement="top"
>
<span class="span-text-over"
>{{ parseFloat(item.value || 0).toLocaleString()
}}<span v-if="fields.unit && unit">{{ unit }}</span></span
>
</el-tooltip>
</div>
</div>
<div
:style="[{ color: color[index % color.length] }]"
class="percent flex flex-1 row-left"
v-if="fields.percent"
>
<div class="text-cont flex row-right">
<el-tooltip
:content="item.percent.toString() + '%'"
effect="dark"
placement="top"
>
<span
class="span-text-over"
v-if="
Number(item.percent) === 100 || Number(item.percent) === 0
"
:style="'color:#686868'"
>
{{ item.percent }}%
</span>
<span class="span-text-over" :style="'color:#686868'" v-else
>{{ Number(item.percent) }}%</span
>
</el-tooltip>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import * as echarts from "echarts";
export default {
props: {
dataObj: {
type: Object,
default() {
return {};
},
},
title: {
type: String,
default: "",
},
color: {
type: Array,
default: () => ["#409EFF", "#67c23a", "#ebb563"],
},
// 字段显示
fields: {
type: Object,
default: () => ({
count: true,
unit: true,
percent: true,
}),
},
// 数据单位
unit: {
type: String,
default: "人次",
},
// 自动播放时间间隔,默认2s
autoTime: {
type: Number,
default: 2,
},
},
data() {
return {
option: {
tooltip: {
trigger: "item",
formatter: (item) => {
if (item.data.name) {
return (
item.marker +
item.name +
" " +
(this.fields.count && item.value >= 0
? `${item.value}${
this.fields.unit && this.unit ? this.unit : ""
}`
: "") +
" " +
(this.fields.percent && item.percent ? item.percent + "%" : "")
);
}
},
},
legend: {
orient: "vertical",
left: "left",
show: false,
},
series: [
{
name: "",
type: "pie",
radius: ["78%", "70%"],
label: {
show: false,
position: "center",
},
emphasis: {
label: {
show: true,
fontSize: 28,
fontWeight: "bold",
},
},
labelLine: {
show: false,
},
itemStyle: {
borderRadius: 10,
},
data: [
// { value: 1048, name: 'Search Engine' },
// { value: 735, name: 'Direct' },
],
},
],
color: this.color,
},
currentIndex: -1,
myChart: null,
timer: null,
};
},
watch: {
dataObj: {
handler() {
this.initData();
},
deep: true,
},
},
mounted() {
this.initData();
},
methods: {
initData() {
this.$set(this.option.series[0], "data", this.dataObj.data);
if (this.title) {
this.option.title.text = this.title;
}
let chartDom = this.$refs.chartRef;
this.myChart = echarts.init(chartDom);
this.option && this.myChart.setOption(this.option);
let chartTarget = this.$refs.chartRef;
this.autoPlay();
chartTarget.addEventListener("mouseenter", () => {
this.stopLightChart();
});
chartTarget.addEventListener("mouseleave", () => {
this.autoPlay();
});
},
autoPlay() {
this.autoLightChart();
this.timer = setInterval(() => {
this.autoLightChart();
}, this.autoTime * 1000);
},
autoLightChart() {
let dataLen = this.option.series[0].data.length;
const textItems = document.querySelectorAll(".chart-text-item");
// 取消之前高亮的图形
this.myChart.dispatchAction({
type: "downplay",
seriesIndex: 0,
dataIndex: this.currentIndex,
});
this.currentIndex = (this.currentIndex + 1) % dataLen;
// 高亮当前图形
this.myChart.dispatchAction({
type: "highlight",
seriesIndex: 0,
dataIndex: this.currentIndex,
});
// 显示 tooltip
this.myChart.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: this.currentIndex,
});
textItems.forEach((item, index) => {
if (index === this.currentIndex) {
item.classList.add("active");
} else {
item.classList.remove("active");
}
});
},
stopLightChart() {
clearInterval(this.timer);
let dataLen = this.option.series[0].data.length;
const textItems = document.querySelectorAll(".chart-text-item");
dataLen.forEach((item, index) => {
// 取消之前高亮的图形
this.myChart.dispatchAction({
type: "downplay",
seriesIndex: 0,
dataIndex: index,
});
});
textItems.forEach((item, index) => {
item.classList.contains("active") && item.classList.remove("active");
});
},
},
destroyed() {
this.stopLightChart();
},
};
</script>
<style lang="scss" scoped>
.chart-wrap {
width: 100%;
.chart {
height: 200px;
width: 100%;
}
.chart-text {
overflow: hidden;
.title {
.icon-circle {
width: 8px;
height: 8px;
}
}
}
}
.chart-text-item {
padding: 20px;
opacity: 0.4;
transition: all 0.4s;
border-radius: 10px;
transform: scale(0.99);
&.active {
opacity: 1;
transform: scale(1.01);
}
}
</style>
封装轮播效果后
将自动轮播的过程封装为一个js方法调用
tool-pie.js
js
/**
* echarts tooltip 自动轮播
* @param myChart //初始化echarts的实例
* @param num //类目数量(原因:循环时达到最大值后,使其从头开始循环)
* @param time //轮播间隔时长
*/
export function autoHover(myChart, num = 12, time = 2000, extra = { enable: true, normal: ".chart-text-item", active: 'active' }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// echarts实例
let echartsInstance = myChart.node;
// 开启自动轮播
let gInterVal = startAuto(myChart.chart, num, time, extra)
// /* 鼠标位于触发点关闭自动轮播,离开触发点开启自动轮播 */
echartsInstance.addEventListener("mouseenter", function () {
stopAuto(gInterVal, myChart.chart, num, extra)
})
echartsInstance.addEventListener("mouseleave", function () {
gInterVal = startAuto(myChart.chart, num, time, extra)
})
let dispose = () => {
return stopAuto(gInterVal, myChart.chart, num, extra)
}
resolve(dispose)
})
})
}
// 开启自动轮播
function startAuto(myChart, num, time, extra) {
let currentIndex = 0
activeChart(myChart, currentIndex, num, extra)
let timeTicket = null
timeTicket && clearInterval(timeTicket)
timeTicket = setInterval(() => {
currentIndex = currentIndex + 1
if (currentIndex >= num) {
currentIndex = 0
}
activeChart(myChart, currentIndex, num, extra)
}, time)
return timeTicket
}
function activeChart(myChart, currentIndex, num, extra) {
// 取消之前高亮的图形
for (let i = 0; i < num; i++) {
myChart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
dataIndex: i
})
}
// 高亮当前图形
myChart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: currentIndex
})
// 显示 tooltip
myChart.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: currentIndex
});
// 建议同一个项目统一风格,方便处理
if(extra?.enable){
const textItems = document.querySelectorAll(extra.normal);
textItems.forEach((item, index) => {
if (index === currentIndex) {
item.classList.add(extra.active);
} else {
item.classList.remove(extra.active);
}
});
}
}
// 销毁自动轮播
function stopAuto(timeTicket, myChart, num, extra) {
return new Promise((resolve, reject) => {
for (let i = 0; i < num; i++) {
myChart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
dataIndex: i
})
}
// 建议同一个项目统一风格,方便处理
if(extra?.enable){
const textItems = document.querySelectorAll(extra.normal);
textItems.forEach((item, index) => {
item.classList.contains(extra.active) && item.classList.remove(extra.active);
});
}
timeTicket && clearInterval(timeTicket)
resolve()
})
}
export default {
autoHover
}
使用 tool-pie.js 的 chart-pie-stats-list.vue
html
<!--
* @Descripttion: 会员饼图
* @Author: wang pingqi
* @Date: 2023-11-15 14:46:52
* @LastEditors: wang ping qi
* @LastEditTime: 2025-07-14 16:06:02
-->
<template>
<div class="chart-wrap flex">
<div class="item one">
<div class="chart" ref="chartRef"></div>
</div>
<div class="item one flex chart-text">
<div class="flex flex-wrap w-100 column">
<div
class="item one flex column pt15 pb15 chart-text-item"
v-for="(item, index) in dataObj.data"
:key="index"
>
<div class="title flex col-center pb10">
<div
class="icon-circle mr10"
:style="'background:' + color[index]"
></div>
<div class="text-cont">
<el-tooltip
:content="item.name.toString()"
effect="dark"
placement="top"
>
<span class="span-text-over">{{ item.name }}</span>
</el-tooltip>
</div>
</div>
<div class="value-percent flex-column">
<div
class="value flex flex-1 pb10"
v-if="fields.count"
>
<div class="text-cont flex row-right">
<el-tooltip
:content="item.value.toString()"
effect="dark"
placement="top"
>
<span class="span-text-over"
>{{ parseFloat(item.value || 0).toLocaleString()
}}<span v-if="fields.unit && unit">{{ unit }}</span></span
>
</el-tooltip>
</div>
</div>
<div
:style="[{ color: color[index % color.length] }]"
class="percent flex flex-1 row-left"
v-if="fields.percent"
>
<div class="text-cont flex row-right">
<el-tooltip
:content="item.percent.toString() + '%'"
effect="dark"
placement="top"
>
<span
class="span-text-over"
v-if="
Number(item.percent) === 100 || Number(item.percent) === 0
"
:style="'color:#686868'"
>
{{ item.percent }}%
</span>
<span class="span-text-over" :style="'color:#686868'" v-else
>{{ Number(item.percent) }}%</span
>
</el-tooltip>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import tools from "@/utils/tool-pie";
import * as echarts from "echarts";
export default {
props: {
dataObj: {
type: Object,
default() {
return {};
},
},
title: {
type: String,
default: "",
},
color: {
type: Array,
default: () => ["#409EFF", "#67c23a", "#ebb563"],
},
// 字段显示
fields: {
type: Object,
default: () => ({
count: true,
unit: true,
percent: true,
}),
},
// 数据单位
unit: {
type: String,
default: "人次",
},
// 自动播放时间间隔,默认2s
autoTime: {
type: Number,
default: 2,
},
},
data() {
return {
option: {
tooltip: {
trigger: "item",
formatter: (item) => {
if (item.data.name) {
return (
item.marker +
item.name +
" " +
(this.fields.count && item.value >= 0
? `${item.value}${
this.fields.unit && this.unit ? this.unit : ""
}`
: "") +
" " +
(this.fields.percent && item.percent ? item.percent + "%" : "")
);
}
},
},
legend: {
orient: "vertical",
left: "left",
show: false,
},
series: [
{
name: "",
type: "pie",
radius: ["78%", "70%"],
label: {
show: false,
position: "center",
},
emphasis: {
label: {
show: true,
fontSize: 28,
fontWeight: "bold",
},
},
labelLine: {
show: false,
},
itemStyle: {
borderRadius: 10,
},
data: [
// { value: 1048, name: 'Search Engine' },
// { value: 735, name: 'Direct' },
],
},
],
color: this.color,
},
myChart: null,
tools: null,
};
},
watch: {
dataObj: {
handler() {
this.initData();
},
deep: true,
},
},
mounted() {
this.initData();
},
methods: {
initData() {
this.$set(this.option.series[0], "data", this.dataObj.data);
if (this.title) {
this.option.title.text = this.title;
}
let chartDom = this.$refs.chartRef;
this.myChart = echarts.init(chartDom);
this.option && this.myChart.setOption(this.option);
if(this.$refs.chartRef && !this.tools ){
let dataLength = this.option.series[0].data.length || 0
tools.autoHover(
{
node : this.$refs.chartRef,
chart : this.myChart,
},
dataLength,
2000
).then((tools) => {
this.tools = tools
});
}
}
},
destroyed() {
if (this.tools) this.tools()
},
};
</script>
<style lang="scss" scoped>
.chart-wrap {
width: 100%;
.chart {
height: 200px;
width: 100%;
}
.chart-text {
overflow: hidden;
.title {
.icon-circle {
width: 8px;
height: 8px;
}
}
}
}
.chart-text-item {
padding: 20px;
opacity: 0.4;
transition: all 0.4s;
border-radius: 10px;
transform: scale(0.99);
&.active {
opacity: 1;
transform: scale(1.01);
}
}
</style>
调用 chart-pie-stats-list.vue
html
<template>
<chartPieStatsList :dataObj="chartData2" />
</template>
<script>
import chartPieStatsList from "./components/chart-pie-stats-list";
export default {
data() {
return {
chartData2: {
loading: true,
title: "性别比例",
type: "pie",
radius: ["50%", "50%"],
data: [
{
value: 0,
percent: 0,
name: "男",
},
{
value: 0,
percent: 0,
name: "女",
},
],
},
}
}
}
</script>