355 lines
9.3 KiB
Vue
355 lines
9.3 KiB
Vue
<template>
|
||
<div ref="chartDom" class="w-full h-full"></div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { convertNumber } from "@/utils/convertNumber";
|
||
import { usePlatformType } from "@/utils/device";
|
||
import * as echarts from "echarts";
|
||
|
||
interface DataItem {
|
||
tag: string;
|
||
total: number;
|
||
}
|
||
|
||
const props = defineProps({
|
||
chartData:{
|
||
type:Array<DataItem>,
|
||
default: () => []
|
||
},
|
||
shorthand:{
|
||
type:Boolean,
|
||
default: false
|
||
},
|
||
});
|
||
|
||
const renderItem = (params: any, api: { coord: (arg0: any[]) => any[]; value: (arg0: number) => any; style: () => any }) => {
|
||
// 柱子索引值
|
||
const { seriesIndex } = params;
|
||
|
||
// 基础坐标
|
||
const basicsCoord = api.coord([api.value(seriesIndex), api.value(1)]);
|
||
// 顶部基础 y 轴
|
||
const topBasicsYAxis = basicsCoord[1];
|
||
// 基础 x 轴
|
||
const basicsXAxis = basicsCoord[0];
|
||
// 底部 y 轴
|
||
const bottomYAxis = api.coord([api.value(seriesIndex), 0])[1];
|
||
return {
|
||
type: "group",
|
||
children: [
|
||
{
|
||
type: "leftShape",
|
||
shape: {
|
||
topBasicsYAxis,
|
||
basicsXAxis,
|
||
bottomYAxis,
|
||
},
|
||
style: {
|
||
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: "rgba(0, 147, 221, 1)" },
|
||
{ offset: 1, color: "rgba(0, 88, 255, 0.20)" },
|
||
]), // 覆盖基础样式
|
||
},
|
||
},
|
||
{
|
||
type: "rightShape",
|
||
shape: {
|
||
topBasicsYAxis,
|
||
basicsXAxis,
|
||
bottomYAxis,
|
||
},
|
||
style: {
|
||
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: "rgba(0, 147, 221, 1)" },
|
||
{ offset: 1, color: "rgba(0, 88, 255, 0.20)" },
|
||
]), // 覆盖基础样式
|
||
},
|
||
},
|
||
{
|
||
type: "topShape",
|
||
shape: {
|
||
topBasicsYAxis,
|
||
basicsXAxis,
|
||
bottomYAxis,
|
||
},
|
||
style: {
|
||
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: "rgba(143, 231, 255, 1)" },
|
||
{ offset: 1, color: "rgba(0, 132, 255, 1)" },
|
||
]), // 覆盖基础样式
|
||
},
|
||
},
|
||
{
|
||
type: "middleShape",
|
||
shape: {
|
||
topBasicsYAxis,
|
||
basicsXAxis,
|
||
bottomYAxis,
|
||
},
|
||
style: {
|
||
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: "rgba(143, 231, 255, 1)" },
|
||
{ offset: 1, color: "rgba(0, 132, 255, 0)" },
|
||
]), // 覆盖基础样式
|
||
},
|
||
},
|
||
],
|
||
};
|
||
};
|
||
|
||
const chartDom = ref(null);
|
||
let myChart: echarts.ECharts | null = null;
|
||
|
||
const initChart = () => {
|
||
if (!chartDom.value) return;
|
||
|
||
myChart = echarts.init(chartDom.value);
|
||
const {isMobile} = usePlatformType()
|
||
// 侧面宽度
|
||
const WIDTH = isMobile ? 6 : 10;
|
||
// 斜角高度
|
||
const OBLIQUE_ANGLE_HEIGHT = isMobile ? 2 : 3.5;
|
||
const leftShape = echarts.graphic.extendShape({
|
||
buildPath(ctx, shape) {
|
||
const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape;
|
||
|
||
const p1 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
|
||
const p2 = [basicsXAxis - WIDTH, bottomYAxis];
|
||
const p3 = [basicsXAxis, bottomYAxis];
|
||
const p4 = [basicsXAxis, topBasicsYAxis];
|
||
|
||
ctx.moveTo(p1[0], p1[1]);
|
||
ctx.lineTo(p2[0], p2[1]);
|
||
ctx.lineTo(p3[0], p3[1]);
|
||
ctx.lineTo(p4[0], p4[1]);
|
||
},
|
||
});
|
||
|
||
const rightShape = echarts.graphic.extendShape({
|
||
buildPath(ctx, shape) {
|
||
const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape;
|
||
|
||
const p1 = [basicsXAxis, topBasicsYAxis];
|
||
const p2 = [basicsXAxis, bottomYAxis];
|
||
const p3 = [basicsXAxis + WIDTH, bottomYAxis];
|
||
const p4 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
|
||
|
||
ctx.moveTo(p1[0], p1[1]);
|
||
ctx.lineTo(p2[0], p2[1]);
|
||
ctx.lineTo(p3[0], p3[1]);
|
||
ctx.lineTo(p4[0], p4[1]);
|
||
},
|
||
});
|
||
|
||
const topShape = echarts.graphic.extendShape({
|
||
buildPath(ctx, shape) {
|
||
const { topBasicsYAxis, basicsXAxis } = shape;
|
||
|
||
const p1 = [basicsXAxis, topBasicsYAxis];
|
||
const p2 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
|
||
const p3 = [basicsXAxis, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT * 2];
|
||
const p4 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
|
||
|
||
ctx.moveTo(p1[0], p1[1]);
|
||
ctx.lineTo(p2[0], p2[1]);
|
||
ctx.lineTo(p3[0], p3[1]);
|
||
ctx.lineTo(p4[0], p4[1]);
|
||
},
|
||
});
|
||
const middleShape = echarts.graphic.extendShape({
|
||
buildPath(ctx, shape) {
|
||
const { topBasicsYAxis, basicsXAxis, bottomYAxis } = shape;
|
||
|
||
const p1 = [basicsXAxis - 0.5, topBasicsYAxis];
|
||
const p2 = [basicsXAxis - 0.5, bottomYAxis];
|
||
const p3 = [basicsXAxis + 0.5, bottomYAxis];
|
||
const p4 = [basicsXAxis + 0.5, topBasicsYAxis];
|
||
|
||
ctx.moveTo(p1[0], p1[1]);
|
||
ctx.lineTo(p2[0], p2[1]);
|
||
ctx.lineTo(p3[0], p3[1]);
|
||
ctx.lineTo(p4[0], p4[1]);
|
||
},
|
||
});
|
||
|
||
echarts.graphic.registerShape("leftShape", leftShape);
|
||
echarts.graphic.registerShape("rightShape", rightShape);
|
||
echarts.graphic.registerShape("topShape", topShape);
|
||
echarts.graphic.registerShape("middleShape", middleShape);
|
||
|
||
// 初始化时设置空数据,后续通过updateChartData更新
|
||
const options = {
|
||
grid: {
|
||
top: props.shorthand ? 30:40,
|
||
left: props.shorthand ? 40 : 60,
|
||
right: props.shorthand ?20 : 30,
|
||
bottom: props.shorthand ?30:45,
|
||
},
|
||
xAxis: {
|
||
type: "category",
|
||
data: [],
|
||
axisLabel: {
|
||
formatter: (value: any) => {
|
||
if(props.shorthand){
|
||
return value.slice(0,2)
|
||
}else{
|
||
if (value.length <= 4) return value;
|
||
const arr = [];
|
||
for (let i = 0; i < value.length; i += 4) {
|
||
arr.push(value.slice(i, i + 4));
|
||
}
|
||
return arr.join('\n');
|
||
}
|
||
},
|
||
color: "#C0EEFF",
|
||
fontSize: props.shorthand ? 10 : 14,
|
||
interval: 0,
|
||
},
|
||
nameTextStyle: {
|
||
color: "#243174",
|
||
},
|
||
axisLine: {
|
||
lineStyle: {
|
||
color: "#243174",
|
||
},
|
||
},
|
||
},
|
||
yAxis: {
|
||
name: "人数",
|
||
nameLocation: "end",
|
||
nameTextStyle: {
|
||
color: "#C0EEFF",
|
||
fontSize: props.shorthand ? 10 : 14,
|
||
padding: [0, 5, 0, 0],
|
||
align: "right",
|
||
},
|
||
type: "value",
|
||
min: 0,
|
||
max: 100,
|
||
interval: 20,
|
||
axisLabel: {
|
||
color: "#C0EEFF",
|
||
fontSize: props.shorthand ? 10 : 14,
|
||
formatter: (value: any) => {
|
||
if(props.shorthand){
|
||
return convertNumber(value)
|
||
}else{
|
||
return value
|
||
}
|
||
},
|
||
},
|
||
axisLine: {
|
||
show: true,
|
||
lineStyle: {
|
||
color: "#243174",
|
||
},
|
||
},
|
||
splitLine: {
|
||
lineStyle: {
|
||
type: "dashed",
|
||
color: "#308EFF",
|
||
width: 1,
|
||
opacity: 0.2,
|
||
},
|
||
},
|
||
},
|
||
series: [
|
||
{
|
||
type: "custom",
|
||
renderItem: renderItem,
|
||
color: "blue",
|
||
data: [],
|
||
},
|
||
{
|
||
type: "bar",
|
||
label: {
|
||
show: true,
|
||
position: "top",
|
||
fontSize: props.shorthand ? 10 : 12,
|
||
color: "rgba(192, 238, 255, 1)",
|
||
},
|
||
tooltip: {
|
||
show: false,
|
||
},
|
||
itemStyle: {
|
||
color: "transparent",
|
||
},
|
||
data: [],
|
||
},
|
||
],
|
||
};
|
||
myChart.setOption(options);
|
||
|
||
// 如果有数据,立即更新
|
||
if (props.chartData && props.chartData.length > 0) {
|
||
updateChartData();
|
||
}
|
||
|
||
window.addEventListener("resize", handleResize);
|
||
};
|
||
|
||
// 仅更新图表数据,不重新创建图表
|
||
const updateChartData = () => {
|
||
if (!myChart || !props.chartData) return;
|
||
|
||
const chartData = props.chartData || [];
|
||
|
||
// 处理图表数据
|
||
const tags = chartData.map(item => item.tag);
|
||
const values = chartData.map(item => item.total);
|
||
|
||
// 计算y轴最大值和间隔
|
||
const maxValue = values.length > 0 ? Math.max(...values) : 0;
|
||
const yAxisMax = maxValue > 0 ? Math.ceil(maxValue / 100) * 100 : 100;
|
||
const yAxisInterval = Math.ceil(yAxisMax / 5 / 100) * 100 || 20;
|
||
|
||
myChart.setOption({
|
||
xAxis: {
|
||
data: tags,
|
||
},
|
||
yAxis: {
|
||
min: 0,
|
||
max: yAxisMax,
|
||
interval: yAxisInterval,
|
||
},
|
||
series: [
|
||
{
|
||
data: values,
|
||
},
|
||
{
|
||
data: values,
|
||
},
|
||
],
|
||
});
|
||
};
|
||
|
||
const handleResize = () => {
|
||
myChart?.resize();
|
||
};
|
||
|
||
// 监听数据变化,只更新数据不重新创建图表
|
||
watch(
|
||
() => props.chartData,
|
||
() => {
|
||
if (myChart) {
|
||
updateChartData();
|
||
}
|
||
},
|
||
{ deep: true,immediate:true }
|
||
);
|
||
|
||
onMounted(() => {
|
||
if(!myChart){
|
||
initChart();
|
||
}
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
window.removeEventListener("resize", handleResize);
|
||
myChart?.dispose();
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss"></style>
|