SocialNetworks_duan/src/views/GroupEvolution/component/groupChart.vue
2025-08-05 11:21:45 +08:00

326 lines
8.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="groupChart-component">
<img :src="title" alt="" class="title" v-if="title" />
<div class="chart-name" v-if="chartName">
<img class="name-icon" src="@/assets/images/groupEvolution/chart-name-icon.png" alt="" />
<span>{{ chartName }}</span>
</div>
<div class="container" :id="chartId"></div>
<div class="slider-container">
<div
class="sliding-block"
:style="{ left: `${sliderLeft}px` }"
@mousedown="onMouseDown"
></div>
</div>
</div>
</template>
<script setup>
import { defineProps, onMounted, onBeforeUnmount, ref, computed } from "vue"
import * as echarts from "echarts"
let chartInstance = null
const props = defineProps({
title: {
type: String,
default: ""
},
chartData: {
type: Object,
default: ""
},
chartName: {
type: String,
default: ""
},
moduleName: {
type: String,
default: ""
}
})
// 生成唯一ID避免多个实例冲突
const chartId = computed(() => `group-chart-${Math.random().toString(36).substring(2, 9)}`)
const sliderLeft = ref(50) // 滑块初始left
const sliderWidth = 24 // 滑块宽度
const sliderContainerWidth = ref(0) // 容器宽度
let isDragging = false
let startX = 0
let startLeft = 0
const onMouseDown = (e) => {
isDragging = true
startX = e.clientX
startLeft = sliderLeft.value
document.addEventListener("mousemove", onMouseMove)
document.addEventListener("mouseup", onMouseUp)
}
const onMouseMove = (e) => {
if (!isDragging) return
const deltaX = e.clientX - startX
let newLeft = startLeft + deltaX
newLeft = Math.max(0, Math.min(newLeft, sliderContainerWidth.value - sliderWidth))
sliderLeft.value = newLeft
// 计算当前滑块对应的x轴索引
const percent = newLeft / (sliderContainerWidth.value - sliderWidth)
const xIndex = Math.floor(percent * (props.chartData.xAxisData.length - 1))
// 调用ECharts显示tooltip
if (chartInstance) {
chartInstance.dispatchAction({
type: "showTip",
seriesIndex: 0, // 只显示第一个系列的tooltip
dataIndex: xIndex
})
}
}
const onMouseUp = () => {
isDragging = false
document.removeEventListener("mousemove", onMouseMove)
document.removeEventListener("mouseup", onMouseUp)
}
const initChart = () => {
// 使用唯一ID初始化图表
chartInstance = echarts.init(document.getElementById(chartId.value))
const legendData = props.chartData.seriesList.map((item) => item.name)
const option = {
tooltip: {
trigger: "axis"
},
legend: {
data: legendData,
itemStyle: {
// color: "#fff"
},
right: 5, // 距离右侧 10px
top: 15,
itemWidth: 10, // 图例图标的宽度
itemHeight: 10, // 图例图标的高度
textStyle: {
color: "#fff"
},
icon: "circle" // 使用圆形图标
},
tooltip: {
trigger: "axis",
backgroundColor: "rgba(0,0,0,0)", // 透明背景
borderColor: "rgba(0,0,0,0)", // 透明边框
borderWidth: 0,
extraCssText: "box-shadow:none;padding:0;",
formatter: function (params) {
let color = {
头部自媒体: "#33b6fb",
官方媒体: "#00d6da",
普通自媒体: "#fddc33"
};
// 根据传入的值来判断使用的图例颜色
switch(props.moduleName) {
case "全局群体成员演化图":
color = {
分裂总数: "#2AB8FD",
合并总数: "#02D7DA",
收缩指数: "#FFDA09",
扩展指数: "#EB57B0"
}
break;
case "群体规模演化图":
color = {
头部自媒体: "#33b6fb",
官方媒体: "#00d6da",
普通自媒体: "#fddc33"
}
break;
case "全局异常互动时刻表":
color = {
社团组一: "#2AB8FD",
社团组二: "#02D7DA",
社团组三: "#FFDA09"
}
break;
default:
color = {
头部自媒体: "#33b6fb",
官方媒体: "#00d6da",
普通自媒体: "#fddc33"
}
}
const listHtml = params
.map(
(item) => `
<div style="
display:flex;
align-items:center;
justify-content:space-between;
width:100%;
height:30px;
border-radius:4px;
background:rgba(204,250,255,0.2);
margin-bottom:6px;
padding:0 8px;
color:#fff;
font-size:12px;
">
<p style="width:10px;height:10px;border-radius:50%;background-color:${color[item.seriesName]}"></p>
<span>${item.seriesName}</span>
<span style="
color: #fff;
text-align: right;
font-family: MiSans;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: normal;
">${item.value}</span>
</div>
`
)
.join("")
return `
<div style="
width:150px;
border-radius:6px;
background:linear-gradient(304deg, rgba(28,103,175,0.3) -6.04%, rgba(2,95,137,0.3) 85.2%);
backdrop-filter:blur(4px);
padding:10px;
">
${listHtml}
</div>
`
}
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
top: "30%",
containLabel: true
},
xAxis: {
type: "category",
boundaryGap: true,
data: props.chartData.xAxisData,
axisLabel: {
color: "#94C1EC",
interval: 0
},
axisTick: {
show: false
}
},
yAxis: {
name: "数量",
max: props.chartData.yAxisRange.max,
min: props.chartData.yAxisRange.min,
interval: props.chartData.yAxisRange.interval,
nameTextStyle: {
color: "#B6D6F7",
fontSize: 13,
align: "left",
padding: [0, 0, 5, -28] // 负值让文字更靠左
},
axisLabel: {
textStyle: {
color: "#94C1EC"
}
},
type: "value",
splitLine: {
show: true, // 是否显示分割线
lineStyle: {
color: ["rgba(57, 69, 106, 0.86)"], // 分割线颜色,可以使用数组实现交替颜色
width: 1, // 分割线宽度
type: "dotted" // 分割线类型
}
}
},
series: props.chartData.seriesList.map((series) => ({
...series,
type: "line",
itemStyle: {
color: "#061a2f",
borderColor: series.themeColor, // 使用线条颜色作为边框色
borderWidth: 2
},
symbol: "circle",
symbolSize: 10,
// 确保lineStyle存在
lineStyle: {
color: series.themeColor,
width: 1
}
}))
}
chartInstance.setOption(option)
}
onMounted(() => {
initChart()
// 获取容器宽度
sliderContainerWidth.value = document.querySelector(".slider-container").offsetWidth
})
onBeforeUnmount(() => {
// 销毁图表实例,避免内存泄漏
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
document.removeEventListener("mousemove", onMouseMove)
document.removeEventListener("mouseup", onMouseUp)
})
</script>
<style scoped lang="less">
.groupChart-component {
width: 100%;
height: 100%;
padding: 10px 10px;
.title {
margin-top: -18px;
margin-left: -12px;
}
.chart-name {
margin-left: -8px;
display: flex;
align-items: center;
justify-content: flex-start;
color: #fff;
font-size: 16px;
font-family: PingFang SC;
font-weight: 400;
}
.container {
top: -19px;
width: 100%;
height: 180px;
}
.slider-container {
width: 100%;
height: 4px;
border-radius: 20px;
background: linear-gradient(270deg, rgba(0, 82, 125, 0.48) 0%, rgba(0, 200, 255, 0.23) 100%);
backdrop-filter: blur(3px);
margin-top: -10px;
position: relative;
.sliding-block {
width: 24px;
height: 8px;
border-radius: 2px;
background: linear-gradient(270deg, #00527d 0%, #00c8ff 100%);
backdrop-filter: blur(3px);
position: absolute;
top: -2px;
left: 25px;
cursor: pointer;
}
}
}
</style>