2025-07-22 16:40:46 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="groupChart-component">
|
2025-07-23 11:22:19 +08:00
|
|
|
|
<img :src="title" alt="" class="title" v-if="title" />
|
2025-07-28 16:02:19 +08:00
|
|
|
|
<div class="chart-name" v-if="chartName">
|
2025-08-04 16:18:13 +08:00
|
|
|
|
<img class="name-icon" src="@/assets/images/groupEvolution/chart-name-icon.png" alt="" />
|
2025-07-28 16:02:19 +08:00
|
|
|
|
<span>{{ chartName }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="container" :id="chartId"></div>
|
2025-07-22 16:40:46 +08:00
|
|
|
|
<div class="slider-container">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="sliding-block"
|
|
|
|
|
|
:style="{ left: `${sliderLeft}px` }"
|
|
|
|
|
|
@mousedown="onMouseDown"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-08-04 16:18:13 +08:00
|
|
|
|
import { defineProps, onMounted, onBeforeUnmount, ref, computed } from "vue"
|
|
|
|
|
|
import * as echarts from "echarts"
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
2025-08-04 16:18:13 +08:00
|
|
|
|
let chartInstance = null
|
2025-07-22 16:40:46 +08:00
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
title: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: ""
|
|
|
|
|
|
},
|
|
|
|
|
|
chartData: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
default: ""
|
2025-07-28 16:02:19 +08:00
|
|
|
|
},
|
|
|
|
|
|
chartName: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: ""
|
2025-08-04 17:40:17 +08:00
|
|
|
|
},
|
|
|
|
|
|
moduleName: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: ""
|
2025-07-22 16:40:46 +08:00
|
|
|
|
}
|
2025-08-04 16:18:13 +08:00
|
|
|
|
})
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
2025-07-28 16:02:19 +08:00
|
|
|
|
// 生成唯一ID,避免多个实例冲突
|
2025-08-04 16:18:13 +08:00
|
|
|
|
const chartId = computed(() => `group-chart-${Math.random().toString(36).substring(2, 9)}`)
|
2025-07-28 16:02:19 +08:00
|
|
|
|
|
2025-08-04 16:18:13 +08:00
|
|
|
|
const sliderLeft = ref(50) // 滑块初始left
|
|
|
|
|
|
const sliderWidth = 24 // 滑块宽度
|
|
|
|
|
|
const sliderContainerWidth = ref(0) // 容器宽度
|
|
|
|
|
|
let isDragging = false
|
|
|
|
|
|
let startX = 0
|
|
|
|
|
|
let startLeft = 0
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
const onMouseDown = (e) => {
|
2025-08-04 16:18:13 +08:00
|
|
|
|
isDragging = true
|
|
|
|
|
|
startX = e.clientX
|
|
|
|
|
|
startLeft = sliderLeft.value
|
|
|
|
|
|
document.addEventListener("mousemove", onMouseMove)
|
|
|
|
|
|
document.addEventListener("mouseup", onMouseUp)
|
|
|
|
|
|
}
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
const onMouseMove = (e) => {
|
2025-08-04 16:18:13 +08:00
|
|
|
|
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
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
// 计算当前滑块对应的x轴索引
|
2025-08-04 16:18:13 +08:00
|
|
|
|
const percent = newLeft / (sliderContainerWidth.value - sliderWidth)
|
|
|
|
|
|
const xIndex = Math.floor(percent * (props.chartData.xAxisData.length - 1))
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
// 调用ECharts显示tooltip
|
|
|
|
|
|
if (chartInstance) {
|
|
|
|
|
|
chartInstance.dispatchAction({
|
|
|
|
|
|
type: "showTip",
|
|
|
|
|
|
seriesIndex: 0, // 只显示第一个系列的tooltip
|
|
|
|
|
|
dataIndex: xIndex
|
2025-08-04 16:18:13 +08:00
|
|
|
|
})
|
2025-07-22 16:40:46 +08:00
|
|
|
|
}
|
2025-08-04 16:18:13 +08:00
|
|
|
|
}
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
const onMouseUp = () => {
|
2025-08-04 16:18:13 +08:00
|
|
|
|
isDragging = false
|
|
|
|
|
|
document.removeEventListener("mousemove", onMouseMove)
|
|
|
|
|
|
document.removeEventListener("mouseup", onMouseUp)
|
|
|
|
|
|
}
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
const initChart = () => {
|
2025-07-28 16:02:19 +08:00
|
|
|
|
// 使用唯一ID初始化图表
|
2025-08-04 16:18:13 +08:00
|
|
|
|
chartInstance = echarts.init(document.getElementById(chartId.value))
|
|
|
|
|
|
const legendData = props.chartData.seriesList.map((item) => item.name)
|
2025-07-22 16:40:46 +08:00
|
|
|
|
const option = {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: "axis"
|
|
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
data: legendData,
|
2025-08-04 17:40:17 +08:00
|
|
|
|
itemStyle: {
|
|
|
|
|
|
// color: "#fff"
|
|
|
|
|
|
},
|
|
|
|
|
|
right: 5, // 距离右侧 10px
|
|
|
|
|
|
top: 15,
|
|
|
|
|
|
itemWidth: 10, // 图例图标的宽度
|
|
|
|
|
|
itemHeight: 10, // 图例图标的高度
|
2025-07-22 16:40:46 +08:00
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: "#fff"
|
2025-08-04 16:18:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
icon: "circle" // 使用圆形图标
|
2025-07-22 16:40:46 +08:00
|
|
|
|
},
|
|
|
|
|
|
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) {
|
2025-08-04 17:40:17 +08:00
|
|
|
|
let color = {
|
2025-07-22 16:40:46 +08:00
|
|
|
|
头部自媒体: "#33b6fb",
|
|
|
|
|
|
官方媒体: "#00d6da",
|
|
|
|
|
|
普通自媒体: "#fddc33"
|
|
|
|
|
|
};
|
2025-08-04 17:40:17 +08:00
|
|
|
|
// 根据传入的值来判断使用的图例颜色
|
|
|
|
|
|
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"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-22 16:40:46 +08:00
|
|
|
|
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>
|
|
|
|
|
|
`
|
|
|
|
|
|
)
|
2025-08-04 16:18:13 +08:00
|
|
|
|
.join("")
|
2025-07-22 16:40:46 +08:00
|
|
|
|
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>
|
2025-08-04 16:18:13 +08:00
|
|
|
|
`
|
2025-07-22 16:40:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
left: "3%",
|
|
|
|
|
|
right: "4%",
|
|
|
|
|
|
bottom: "3%",
|
|
|
|
|
|
top: "30%",
|
|
|
|
|
|
containLabel: true
|
|
|
|
|
|
},
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: "category",
|
2025-08-04 16:18:13 +08:00
|
|
|
|
boundaryGap: true,
|
2025-07-22 16:40:46 +08:00
|
|
|
|
data: props.chartData.xAxisData,
|
|
|
|
|
|
axisLabel: {
|
2025-08-04 16:18:13 +08:00
|
|
|
|
color: "#94C1EC",
|
|
|
|
|
|
interval: 0
|
|
|
|
|
|
},
|
|
|
|
|
|
axisTick: {
|
|
|
|
|
|
show: false
|
2025-07-22 16:40:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
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" // 分割线类型
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-08-04 16:18:13 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
2025-08-04 16:18:13 +08:00
|
|
|
|
initChart()
|
2025-07-22 16:40:46 +08:00
|
|
|
|
// 获取容器宽度
|
2025-08-04 16:18:13 +08:00
|
|
|
|
sliderContainerWidth.value = document.querySelector(".slider-container").offsetWidth
|
|
|
|
|
|
})
|
2025-07-28 16:02:19 +08:00
|
|
|
|
|
2025-07-22 16:40:46 +08:00
|
|
|
|
onBeforeUnmount(() => {
|
2025-07-28 16:02:19 +08:00
|
|
|
|
// 销毁图表实例,避免内存泄漏
|
|
|
|
|
|
if (chartInstance) {
|
2025-08-04 16:18:13 +08:00
|
|
|
|
chartInstance.dispose()
|
|
|
|
|
|
chartInstance = null
|
2025-07-28 16:02:19 +08:00
|
|
|
|
}
|
2025-08-04 16:18:13 +08:00
|
|
|
|
document.removeEventListener("mousemove", onMouseMove)
|
|
|
|
|
|
document.removeEventListener("mouseup", onMouseUp)
|
|
|
|
|
|
})
|
2025-07-22 16:40:46 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
|
.groupChart-component {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
padding: 10px 10px;
|
|
|
|
|
|
.title {
|
|
|
|
|
|
margin-top: -18px;
|
|
|
|
|
|
margin-left: -12px;
|
|
|
|
|
|
}
|
2025-07-28 16:02:19 +08:00
|
|
|
|
.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;
|
|
|
|
|
|
}
|
2025-07-22 16:40:46 +08:00
|
|
|
|
.container {
|
2025-08-04 17:40:17 +08:00
|
|
|
|
top: -19px;
|
2025-07-22 16:40:46 +08:00
|
|
|
|
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);
|
2025-07-28 16:02:19 +08:00
|
|
|
|
margin-top: -10px;
|
2025-07-22 16:40:46 +08:00
|
|
|
|
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>
|