713 lines
25 KiB
Vue
713 lines
25 KiB
Vue
<template>
|
||
<div ref="chartDom" class="w-full h-full"></div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { onMounted, ref, watch, computed } from "vue";
|
||
import * as echarts from "echarts";
|
||
|
||
const chartDom = ref(null);
|
||
let myChart: echarts.ECharts | null = null;
|
||
|
||
const props = defineProps({
|
||
chartDataArray: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
});
|
||
|
||
const emits = defineEmits(["dateChange"]);
|
||
|
||
// 定义数据类型接口
|
||
interface ChartDataItem {
|
||
date: string;
|
||
value: number;
|
||
}
|
||
|
||
interface ChartTypeData {
|
||
type: number;
|
||
items: ChartDataItem[];
|
||
}
|
||
|
||
interface SeriesDataItem {
|
||
name: string;
|
||
data: number[];
|
||
color: string;
|
||
}
|
||
|
||
interface ChartDataFormat {
|
||
dates: string[];
|
||
seriesData: SeriesDataItem[];
|
||
}
|
||
|
||
// 处理图表数据
|
||
const processChartData = (dataArray: ChartTypeData[], mode: string): ChartDataFormat => {
|
||
if (!dataArray || dataArray.length === 0) return {
|
||
dates: [],
|
||
seriesData: [
|
||
{ name: "获客", data: [], color: "#20E6A4" },
|
||
{ name: "缴费", data: [], color: "#0783FA" },
|
||
{ name: "流失", data: [], color: "#FFD15C" },
|
||
],
|
||
};
|
||
|
||
// 收集所有日期并排序
|
||
let allDates = new Set<string>();
|
||
dataArray.forEach((item: ChartTypeData) => {
|
||
if (item.items && item.items.length > 0) {
|
||
item.items.forEach((dateItem: ChartDataItem) => {
|
||
if (dateItem.date) {
|
||
allDates.add(dateItem.date);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// 将日期转换为数组并排序
|
||
let sortedDates = Array.from(allDates).sort((a: string, b: string) => {
|
||
// 假设日期格式为 MM-DD
|
||
const [monthA, dayA] = a.split('-').map(Number);
|
||
const [monthB, dayB] = b.split('-').map(Number);
|
||
if (monthA !== monthB) return monthA - monthB;
|
||
return dayA - dayB;
|
||
});
|
||
|
||
// 根据模式选择显示的数据量
|
||
if (mode === "week") {
|
||
// 周模式显示最近7天
|
||
if (sortedDates.length > 7) {
|
||
sortedDates = sortedDates.slice(-7);
|
||
}
|
||
}
|
||
// 月模式显示最近30天或全部
|
||
else if (mode === "month") {
|
||
// 如果数据超过30天,显示最近30天
|
||
if (sortedDates.length > 30) {
|
||
sortedDates = sortedDates.slice(-30);
|
||
}
|
||
// 否则显示全部数据
|
||
}
|
||
|
||
// 格式化日期显示
|
||
const formattedDates = sortedDates.map((date: string) => {
|
||
const [month, day] = date.split('-');
|
||
return `${month}.${day}`;
|
||
});
|
||
|
||
// 创建每个系列的数据
|
||
const seriesData: SeriesDataItem[] = [
|
||
{ name: "获客", data: [], color: "#20E6A4" },
|
||
{ name: "缴费", data: [], color: "#0783FA" },
|
||
{ name: "流失", data: [], color: "#FFD15C" },
|
||
];
|
||
|
||
// 填充数据值
|
||
sortedDates.forEach((date: string) => {
|
||
// 对每个系列填充当前日期的值
|
||
dataArray.forEach((typeData: ChartTypeData, typeIndex: number) => {
|
||
if (typeIndex < seriesData.length) {
|
||
const dateItem = typeData.items.find(item => item.date === date);
|
||
seriesData[typeIndex].data.push(dateItem ? dateItem.value : 0);
|
||
}
|
||
});
|
||
});
|
||
|
||
return {
|
||
dates: formattedDates,
|
||
seriesData,
|
||
};
|
||
};
|
||
|
||
// 使用computed属性基于props生成图表数据
|
||
const chartData = computed((): ChartDataFormat => {
|
||
if (props.chartDataArray && props.chartDataArray.length > 0) {
|
||
return processChartData(props.chartDataArray as ChartTypeData[], currentMode.value);
|
||
}
|
||
|
||
// 如果没有提供数据,则使用默认数据
|
||
return {
|
||
dates: ["5.24", "5.25", "5.26", "5.27", "5.28", "5.29", "5.30"],
|
||
seriesData: [
|
||
{ name: "获客", data: [180, 230, 200, 280, 250, 300, 350], color: "#20E6A4" },
|
||
{ name: "缴费", data: [150, 210, 180, 240, 200, 260, 320], color: "#0783FA" },
|
||
{ name: "流失", data: [120, 380, 220, 350, 300, 280, 400], color: "#FFD15C" },
|
||
],
|
||
};
|
||
});
|
||
|
||
const currentMode = ref("week");
|
||
|
||
const legendIcons = ref<{ [key: string]: string }>({});
|
||
|
||
// 生成SVG图标
|
||
const generateSvgIcon = (color: string) => {
|
||
return encodeURIComponent(`
|
||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" viewBox="0 0 16 16">
|
||
<g><g><path d="M0,2L0,14C0,15.1046,0.89543,16,2,16L14,16C15.1046,16,16,15.1046,16,14L16,2C16,0.89543,15.1046,0,14,0L2,0C0.89543,0,0,0.89543,0,2ZM13.949,6.32574L7.64171,11.99Q7.35295,12.25,6.94914,12.25Q6.54551,12.25,6.25625,11.9902L2.05119,8.2139Q1.75,7.94411,1.75,7.55595Q1.75,7.16798,2.05072,6.89792Q2.33947,6.63794,2.74329,6.63794Q3.14692,6.63794,3.43617,6.89771L6.94886,10.0523L12.5636,5.00998Q12.8523,4.75,13.2562,4.75Q13.6598,4.75,13.9488,5.00955Q14.25,5.27935,14.25,5.66751Q14.25,6.05547,13.949,6.32574Z" fill-rule="evenodd" fill="${color}" fill-opacity="1"/></g></g></svg>`);
|
||
};
|
||
|
||
// 初始化图例图标
|
||
const initLegendIcons = () => {
|
||
chartData.value.seriesData.forEach((item) => {
|
||
legendIcons.value[item.name] = generateSvgIcon(item.color);
|
||
});
|
||
};
|
||
|
||
// 获取图例图标
|
||
const getLegendIcon = (name: string, isSelected = true) => {
|
||
const seriesItem = chartData.value.seriesData.find((item) => item.name === name);
|
||
const color = isSelected ? seriesItem?.color || "#555" : "#555";
|
||
return `image://data:image/svg+xml;charset=utf-8,${generateSvgIcon(color)}`;
|
||
};
|
||
|
||
// 更新图例图标颜色
|
||
const updateLegendIcons = (selected: Record<string, boolean>) => {
|
||
if (!myChart) return;
|
||
|
||
const option = myChart.getOption() as any;
|
||
const legendData = option.legend[0].data as any[];
|
||
|
||
// 更新每个图例项的图标
|
||
const updatedLegendData = legendData.map((item: any) => {
|
||
const isSelected = selected[item.name] !== false;
|
||
return {
|
||
...item,
|
||
icon: getLegendIcon(item.name, isSelected),
|
||
};
|
||
});
|
||
|
||
// 更新图表配置
|
||
myChart.setOption({
|
||
legend: {
|
||
data: updatedLegendData,
|
||
},
|
||
});
|
||
};
|
||
|
||
// 生成图表系列的函数
|
||
const generateSeries = (seriesData: any) => {
|
||
return seriesData.map((item: any) => {
|
||
const series = {
|
||
name: item.name,
|
||
type: "line",
|
||
data: item.data,
|
||
symbol: "circle",
|
||
symbolSize: 8,
|
||
itemStyle: {
|
||
color: "#111", // 中心填充颜色
|
||
borderColor: item.color, // 边框颜色
|
||
borderWidth: 2,
|
||
},
|
||
lineStyle: {
|
||
width: 3,
|
||
color: item.color,
|
||
},
|
||
};
|
||
return series;
|
||
});
|
||
};
|
||
|
||
// 切换周/月显示
|
||
const switchDisplayMode = (mode: string) => {
|
||
currentMode.value = mode;
|
||
emits("dateChange", mode);
|
||
// 重新渲染图表
|
||
initChart();
|
||
};
|
||
|
||
const initChart = () => {
|
||
if (!myChart) {
|
||
myChart = echarts.init(chartDom.value);
|
||
}
|
||
|
||
// 初始化图例图标
|
||
initLegendIcons();
|
||
|
||
// 按钮的SVG背景 - 使用Base64编码
|
||
const buttonSvgBase64 = encodeURIComponent(`
|
||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="72" height="28" viewBox="0 0 72 28"><defs><filter id="master_svg0_104_04023" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB" x="0" y="0" width="1" height="1"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" type="matrix" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="8" dx="0"/><feGaussianBlur stdDeviation="6"/><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.08237092196941376 0 0 0 0 0.17847032845020294 0 0 0 1 0"/><feBlend mode="normal" in2="shape" result="effect1_innerShadow"/><feColorMatrix in="SourceAlpha" type="matrix" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="-8" dx="0"/><feGaussianBlur stdDeviation="6"/><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.08235294371843338 0 0 0 0 0.18039216101169586 0 0 0 1 0"/><feBlend mode="normal" in2="effect1_innerShadow" result="effect2_innerShadow"/></filter></defs><g><g filter="url(#master_svg0_104_04023)"><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="#061E3A" fill-opacity="1"/></g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113ZM14.5113,1L71,1L71,13.4887Q71,18.6574,70.8935,20.1702Q70.753,22.1653,70.237,23.1779Q69.2045,25.2045,67.1779,26.237Q66.1653,26.753,64.1702,26.8935Q62.6574,27,57.4887,27L1,27L1,14.5113Q1,9.34262,1.10652,7.82977Q1.247,5.83468,1.76295,4.82207Q2.79553,2.79553,4.82207,1.76295Q5.83468,1.247,7.82977,1.10652Q9.34262,1,14.5113,1Z" fill-rule="evenodd" fill="#2A8EFE" fill-opacity="1"/></g></g></svg>
|
||
`);
|
||
|
||
// 定义选中状态的SVG背景
|
||
const activeButtonSvgBase64 = encodeURIComponent(`
|
||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="72" height="28" viewBox="0 0 72 28"><defs><radialGradient cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" id="master_svg0_85_00187" gradientTransform="translate(36 28) rotate(90) scale(16.125258922576904 182.11454980862436)"><stop offset="0%" stop-color="#FFCE4F" stop-opacity="0.5749016404151917"/><stop offset="99.90439414978027%" stop-color="#E0BF00" stop-opacity="0"/></radialGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg1_85_00188"><stop offset="0%" stop-color="#E0BF00" stop-opacity="0.4000000059604645"/><stop offset="100%" stop-color="#E0BF00" stop-opacity="0"/></linearGradient><linearGradient x1="0.5" y1="1" x2="0.5" y2="1.9133386611938477" id="master_svg2_85_00190"><stop offset="0%" stop-color="#FFF7A6" stop-opacity="1"/><stop offset="100%" stop-color="#046172" stop-opacity="0"/></linearGradient></defs><g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="url(#master_svg0_85_00187)" fill-opacity="1"/><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="url(#master_svg1_85_00188)" fill-opacity="1"/></g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113ZM14.5113,1L71,1L71,13.4887Q71,18.6574,70.8935,20.1702Q70.753,22.1653,70.237,23.1779Q69.2045,25.2045,67.1779,26.237Q66.1653,26.753,64.1702,26.8935Q62.6574,27,57.4887,27L1,27L1,14.5113Q1,9.34262,1.10652,7.82977Q1.247,5.83468,1.76295,4.82207Q2.79553,2.79553,4.82207,1.76295Q5.83468,1.247,7.82977,1.10652Q9.34262,1,14.5113,1Z" fill-rule="evenodd" fill="url(#master_svg2_85_00190)" fill-opacity="1"/></g></g></svg>
|
||
`);
|
||
|
||
const option = {
|
||
grid: {
|
||
top: "14%",
|
||
left: "5%",
|
||
right: "3%",
|
||
bottom: "15%",
|
||
},
|
||
legend: {
|
||
type: "plain",
|
||
itemWidth: 16,
|
||
itemHeight: 16,
|
||
top: 3,
|
||
left: "center",
|
||
itemGap: 40,
|
||
textStyle: {
|
||
color: "#C0EEFF",
|
||
fontSize: 14,
|
||
lineHeight: 16,
|
||
},
|
||
data: chartData.value.seriesData.map((item) => ({
|
||
name: item.name,
|
||
icon:
|
||
"image://data:image/svg+xml;charset=utf-8," +
|
||
encodeURIComponent(`
|
||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" viewBox="0 0 16 16">
|
||
<g><g><path d="M0,2L0,14C0,15.1046,0.89543,16,2,16L14,16C15.1046,16,16,15.1046,16,14L16,2C16,0.89543,15.1046,0,14,0L2,0C0.89543,0,0,0.89543,0,2ZM13.949,6.32574L7.64171,11.99Q7.35295,12.25,6.94914,12.25Q6.54551,12.25,6.25625,11.9902L2.05119,8.2139Q1.75,7.94411,1.75,7.55595Q1.75,7.16798,2.05072,6.89792Q2.33947,6.63794,2.74329,6.63794Q3.14692,6.63794,3.43617,6.89771L6.94886,10.0523L12.5636,5.00998Q12.8523,4.75,13.2562,4.75Q13.6598,4.75,13.9488,5.00955Q14.25,5.27935,14.25,5.66751Q14.25,6.05547,13.949,6.32574Z" fill-rule="evenodd" fill="${item.color}" fill-opacity="1"/></g></g></svg>`),
|
||
textStyle: {
|
||
color: "#C0EEFF",
|
||
verticalAlign: "middle",
|
||
},
|
||
})),
|
||
selectedMode: true,
|
||
inactiveColor: "#555",
|
||
itemStyle: {
|
||
borderWidth: 0,
|
||
},
|
||
},
|
||
// 使用graphic组件添加自定义按钮
|
||
graphic: [
|
||
// 按周按钮
|
||
{
|
||
type: "group",
|
||
right: 97,
|
||
top: 0,
|
||
// z: 100,
|
||
children: [
|
||
{
|
||
type: "image",
|
||
// z: 100,
|
||
style: {
|
||
image: `data:image/svg+xml;charset=utf-8,${currentMode.value === "week" ? activeButtonSvgBase64 : buttonSvgBase64}`,
|
||
width: 72,
|
||
height: 28,
|
||
},
|
||
cursor: "pointer",
|
||
onclick: () => switchDisplayMode("week"),
|
||
},
|
||
{
|
||
type: "text",
|
||
// z: 100,
|
||
style: {
|
||
text: "按周",
|
||
x: 36,
|
||
y: 14,
|
||
textAlign: "center",
|
||
textVerticalAlign: "middle",
|
||
fill: `${currentMode.value === "week" ? "#F8EA21" : "#DBF7FF"}`,
|
||
fontSize: 14,
|
||
},
|
||
cursor: "pointer",
|
||
onclick: () => switchDisplayMode("week"),
|
||
},
|
||
],
|
||
},
|
||
// 按月按钮
|
||
{
|
||
type: "group",
|
||
right: 15,
|
||
top: 0,
|
||
// z: 100,
|
||
children: [
|
||
{
|
||
type: "image",
|
||
// z: 100,
|
||
style: {
|
||
image: `data:image/svg+xml;charset=utf-8,${currentMode.value === "month" ? activeButtonSvgBase64 : buttonSvgBase64}`,
|
||
width: 72,
|
||
height: 28,
|
||
},
|
||
cursor: "pointer",
|
||
onclick: () => switchDisplayMode("month"),
|
||
},
|
||
{
|
||
type: "text",
|
||
// z: 100,
|
||
style: {
|
||
text: "按月",
|
||
x: 36,
|
||
y: 14,
|
||
textAlign: "center",
|
||
textVerticalAlign: "middle",
|
||
fill: `${currentMode.value === "month" ? "#F8EA21" : "#DBF7FF"}`,
|
||
fontSize: 14,
|
||
},
|
||
cursor: "pointer",
|
||
onclick: () => switchDisplayMode("month"),
|
||
},
|
||
],
|
||
},
|
||
],
|
||
tooltip: {
|
||
trigger: "axis",
|
||
renderMode: "html",
|
||
padding: 0,
|
||
borderWidth: 0,
|
||
extraCssText: "background-color: transparent;",
|
||
formatter: (params: any) => {
|
||
const date = params[0].name;
|
||
const seriesData = params
|
||
.map(
|
||
(item: any) => `<div class="tip-content">
|
||
<div class="tip-content-left">
|
||
<div class="tip-icon" style="background-color: ${item.borderColor};"></div>
|
||
<div class="tip-text">${item.seriesName}</div>
|
||
</div>
|
||
<div class="tip-content-right">
|
||
<div class="tip-text">${item.value}人</div>
|
||
</div>
|
||
</div>`,
|
||
)
|
||
.join("");
|
||
return `<div style="" class='tool-tip-wrapper'>
|
||
<div class="tool-tip-header">
|
||
<span class="tip-date">${date}</span>
|
||
<div class="tip-graphic">
|
||
<div class="qua-group">
|
||
<div class="qua-item"></div>
|
||
<div class="qua-item"></div>
|
||
<div class="qua-item"></div>
|
||
<div class="qua-item"></div>
|
||
<div class="qua-item"></div>
|
||
</div>
|
||
<div class="rect-group">
|
||
<div class="rect-item"></div>
|
||
<div class="rect-item"></div>
|
||
<div class="rect-item"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="tip-content-wrapper">
|
||
${seriesData}
|
||
</div>
|
||
</div>`;
|
||
},
|
||
axisPointer: {
|
||
id: "xPointer",
|
||
type: "line", // 直线指示器
|
||
lineStyle: {
|
||
color: "#4760FF",
|
||
width: 2,
|
||
type: "dashed",
|
||
},
|
||
},
|
||
},
|
||
xAxis: {
|
||
type: "category",
|
||
data: chartData.value.dates,
|
||
axisLabel: {
|
||
formatter: (value: any) => value,
|
||
color: "#C0EEFF",
|
||
fontSize: 14,
|
||
},
|
||
nameTextStyle: {
|
||
color: "#243174",
|
||
},
|
||
axisLine: {
|
||
lineStyle: {
|
||
color: "#243174",
|
||
},
|
||
},
|
||
},
|
||
yAxis: {
|
||
name: "人数",
|
||
nameLocation: "end",
|
||
nameTextStyle: {
|
||
color: "#C0EEFF",
|
||
fontSize: 14,
|
||
padding: [0, 5, 0, 0],
|
||
align: "right",
|
||
},
|
||
type: "value",
|
||
min: 0,
|
||
max: 500,
|
||
interval: 100,
|
||
axisLabel: {
|
||
color: "#C0EEFF",
|
||
fontSize: 14,
|
||
},
|
||
axisLine: {
|
||
show: true,
|
||
lineStyle: {
|
||
color: "#243174",
|
||
},
|
||
},
|
||
splitLine: {
|
||
lineStyle: {
|
||
type: "dashed",
|
||
color: "#308EFF",
|
||
width: 1,
|
||
opacity: 0.2,
|
||
},
|
||
},
|
||
},
|
||
series: [
|
||
...generateSeries(chartData.value.seriesData),
|
||
{
|
||
type: "bar",
|
||
id: "axisOverlayBar",
|
||
// zlevel: 1,
|
||
silent: false,
|
||
barWidth: "100%",
|
||
data: chartData.value.dates.map(() => 500), // 使用y轴的最大值
|
||
itemStyle: {
|
||
color: "transparent",
|
||
},
|
||
tooltip: {
|
||
show: false,
|
||
},
|
||
},
|
||
],
|
||
};
|
||
|
||
myChart.setOption(option, true);
|
||
|
||
// 跟踪鼠标指示器状态
|
||
const hoverBarId = "mouseHoverBar";
|
||
|
||
myChart.on("mouseover", { seriesId: "axisOverlayBar" }, (params) => {
|
||
if (!myChart) return;
|
||
|
||
// 获取当前数据索引
|
||
const dataIndex = params.dataIndex;
|
||
|
||
// 获取当前option配置
|
||
const _option = myChart.getOption() as any;
|
||
|
||
// 检查是否已存在高亮条
|
||
let seriesIndex = -1;
|
||
if (_option.series && Array.isArray(_option.series)) {
|
||
_option.series.forEach((series: any, index: number) => {
|
||
if (series.id === hoverBarId) {
|
||
seriesIndex = index;
|
||
}
|
||
});
|
||
}
|
||
|
||
// 构建高亮条配置
|
||
const hoverBarOption = {
|
||
id: hoverBarId,
|
||
type: "bar",
|
||
barWidth: 40,
|
||
barGap: "-100%",
|
||
// zlevel: 10,
|
||
silent: true,
|
||
animation: true,
|
||
data: chartData.value.dates.map((_: any, index: number) => {
|
||
return index === dataIndex ? 500 : "-";
|
||
}),
|
||
itemStyle: {
|
||
color: {
|
||
type: "linear",
|
||
x: 0,
|
||
y: 0,
|
||
x2: 0,
|
||
y2: 1,
|
||
colorStops: [
|
||
{
|
||
offset: 0,
|
||
color: "#fff", // 开始颜色
|
||
},
|
||
{
|
||
offset: 1,
|
||
color: "#0783FA", // 结束颜色
|
||
},
|
||
],
|
||
global: false,
|
||
},
|
||
opacity: 0.3,
|
||
},
|
||
tooltip: {
|
||
show: false,
|
||
},
|
||
};
|
||
|
||
if (seriesIndex === -1) {
|
||
// 如果不存在则添加新的高亮条
|
||
if (_option.series && Array.isArray(_option.series)) {
|
||
_option.series.push(hoverBarOption);
|
||
}
|
||
} else {
|
||
// 如果已存在则更新位置
|
||
if (_option.series && Array.isArray(_option.series)) {
|
||
_option.series[seriesIndex].data = hoverBarOption.data;
|
||
}
|
||
}
|
||
|
||
// 更新图表
|
||
myChart.setOption(_option, false);
|
||
});
|
||
|
||
// 鼠标移出图表时移除高亮条
|
||
myChart.on("mouseout", { seriesId: "axisOverlayBar" }, () => {
|
||
if (!myChart) return;
|
||
const _option = myChart.getOption() as any;
|
||
const hoverBarExists = _option.series.find((series: any) => series.id && series.id === hoverBarId);
|
||
|
||
if (hoverBarExists) {
|
||
try {
|
||
// 检查series是否存在
|
||
if (_option.series && Array.isArray(_option.series)) {
|
||
// 查找并删除高亮条
|
||
const newSeries = _option.series.filter((series: any) => {
|
||
return series.id !== hoverBarId;
|
||
});
|
||
|
||
// 直接设置新的series数组
|
||
_option.series = newSeries;
|
||
// 更新图表
|
||
myChart.setOption(_option, true);
|
||
}
|
||
} catch (err) {
|
||
console.error("删除高亮条出错:", err);
|
||
}
|
||
}
|
||
});
|
||
|
||
myChart.on("legendselectchanged", (params: any) => {
|
||
// 更新图例图标
|
||
updateLegendIcons(params.selected);
|
||
});
|
||
};
|
||
|
||
const handleResize = () => {
|
||
myChart?.resize();
|
||
};
|
||
onMounted(() => {
|
||
window.addEventListener("resize", handleResize);
|
||
});
|
||
|
||
// 监听图表数据变化,更新图表
|
||
watch(() => props.chartDataArray, () => {
|
||
initChart();
|
||
}, { deep: true });
|
||
|
||
onUnmounted(() => {
|
||
window.removeEventListener("resize", handleResize);
|
||
myChart?.dispose();
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
:deep(.tool-tip-wrapper) {
|
||
background-color: transparent;
|
||
position: relative;
|
||
width: 184px;
|
||
border-radius: 4px;
|
||
box-shadow: 0px 4px 10px 0px #0d3472;
|
||
overflow: hidden;
|
||
padding: 10px;
|
||
background-color: rgb(3, 33, 64);
|
||
|
||
&::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
border: 2px solid transparent;
|
||
border-image: linear-gradient(rgb(3, 33, 64), rgba(0, 170, 255, 0.3)) 2;
|
||
}
|
||
|
||
.tool-tip-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
.tip-date {
|
||
font-size: 14px;
|
||
color: #c0eeff;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.tip-graphic {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.qua-group {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-right: 4px;
|
||
.qua-item {
|
||
width: 2px;
|
||
height: 8px;
|
||
color: rgba(255, 255, 255, 1);
|
||
text-align: center;
|
||
transform: skewX(-40deg);
|
||
background: linear-gradient(360deg, #0085ff 0%, rgba(0, 133, 255, 0) 93%, rgba(0, 133, 255, 0) 100%);
|
||
margin-left: 3px;
|
||
}
|
||
}
|
||
|
||
.rect-group {
|
||
display: flex;
|
||
align-items: center;
|
||
.rect-item {
|
||
width: 5px;
|
||
height: 5px;
|
||
background-color: #00c2ff;
|
||
transform: rotate(45deg);
|
||
margin-left: 5px;
|
||
animation: colorChange 1.5s infinite;
|
||
|
||
&:nth-child(1) {
|
||
animation-delay: 0s;
|
||
}
|
||
|
||
&:nth-child(2) {
|
||
animation-delay: 0.5s;
|
||
}
|
||
|
||
&:nth-child(3) {
|
||
animation-delay: 1s;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.tip-content {
|
||
padding: 6px 7px 6px 10px;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
background-color: rgb(4, 50, 89);
|
||
font-size: 14px;
|
||
color: #b9ddfd;
|
||
.tip-content-left {
|
||
display: flex;
|
||
align-items: center;
|
||
.tip-icon {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
margin-right: 5px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.tip-content-wrapper {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 4px;
|
||
}
|
||
}
|
||
|
||
@keyframes colorChange {
|
||
0%,
|
||
100% {
|
||
background-color: #00c2ff;
|
||
}
|
||
50% {
|
||
background-color: #0085ff;
|
||
}
|
||
}
|
||
</style>
|