2025-07-22 16:40:46 +08:00
|
|
|
|
<template>
|
2025-08-05 14:23:32 +08:00
|
|
|
|
<div class="groupGraph-component">
|
|
|
|
|
|
<img src="@/assets/images/groupEvolution/graph-title.png" class="titleImage" />
|
|
|
|
|
|
<div class="container" id="container"></div>
|
|
|
|
|
|
<div class="timeList">
|
|
|
|
|
|
<TimeAxis
|
|
|
|
|
|
v-if="timeList.length"
|
|
|
|
|
|
:time-list="timeList"
|
2025-08-05 15:25:40 +08:00
|
|
|
|
:is-auto-play="true"
|
2025-08-05 14:23:32 +08:00
|
|
|
|
:start-time="new Date(timeList[0])"
|
|
|
|
|
|
:end-time="new Date(timeList[timeList.length - 1])"
|
|
|
|
|
|
@click:pointerDown="handlePointerDown"
|
|
|
|
|
|
@slide:pointerUp="handlePointerDown"
|
|
|
|
|
|
></TimeAxis>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-07-22 16:40:46 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-08-05 14:23:32 +08:00
|
|
|
|
<script setup>
|
2025-08-06 15:01:12 +08:00
|
|
|
|
import { defineProps, defineEmits, onMounted, watch, nextTick } from "vue"
|
2025-08-05 14:23:32 +08:00
|
|
|
|
import { storeToRefs } from "pinia"
|
2025-08-06 15:01:12 +08:00
|
|
|
|
import * as echarts from "echarts"
|
2025-08-05 14:23:32 +08:00
|
|
|
|
import TimeAxis from "@/components/timeAxis.vue"
|
|
|
|
|
|
import { convertToUtcIsoString } from "@/utils/transform"
|
2025-08-06 15:01:12 +08:00
|
|
|
|
import nodeHoverImg from "@/assets/images/nodeHover.png"
|
2025-08-05 14:23:32 +08:00
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
store: {
|
|
|
|
|
|
required: true
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-08-06 15:01:12 +08:00
|
|
|
|
let chart = null
|
2025-08-05 14:23:32 +08:00
|
|
|
|
const emit = defineEmits(["click:pointerDownAndSlide"])
|
|
|
|
|
|
// 解构 store 中的 state
|
2025-08-06 15:01:12 +08:00
|
|
|
|
const { timeList, graph } = storeToRefs(props.store)
|
2025-08-05 14:23:32 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理时间轴点击事件和拉动
|
|
|
|
|
|
const handlePointerDown = (time) => {
|
|
|
|
|
|
const utcTime = convertToUtcIsoString(time)
|
|
|
|
|
|
emit("click:pointerDownAndSlide", utcTime)
|
|
|
|
|
|
}
|
2025-08-06 15:01:12 +08:00
|
|
|
|
|
|
|
|
|
|
const initChart = () => {
|
|
|
|
|
|
if (chart == null) {
|
|
|
|
|
|
chart = echarts.init(document.getElementById("container"))
|
|
|
|
|
|
}
|
|
|
|
|
|
if (Object.keys(graph.value).length === 0) {
|
|
|
|
|
|
chart.clear()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function variableColorAndPos(groupId) {
|
|
|
|
|
|
const resultMap = {
|
|
|
|
|
|
0: { color: "#1f8473", x: 100, y: 100 },
|
|
|
|
|
|
1: { color: "#807d2c", x: 300, y: 300 },
|
|
|
|
|
|
6: { color: "#0c7090", x: 600, y: 600 }
|
|
|
|
|
|
}
|
|
|
|
|
|
return resultMap[parseInt(groupId)]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
//hover上去的窗口
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: "item",
|
|
|
|
|
|
backgroundColor: "rgba(0,0,0,0)", // 透明背景
|
|
|
|
|
|
borderColor: "rgba(0,0,0,0)", // 透明边框
|
|
|
|
|
|
borderWidth: 0,
|
|
|
|
|
|
extraCssText: "box-shadow:none;padding:0;",
|
|
|
|
|
|
formatter: function (params) {
|
|
|
|
|
|
if (params.dataType === "node") {
|
|
|
|
|
|
return `<div
|
|
|
|
|
|
style="
|
|
|
|
|
|
padding:10px 15px;
|
|
|
|
|
|
height: 68px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
background: url('${nodeHoverImg}');
|
|
|
|
|
|
background-size: cover;
|
|
|
|
|
|
background-position: center;
|
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
">
|
|
|
|
|
|
<div style="color:#fff;letter-spacing: 0.14px;">
|
|
|
|
|
|
<div >用户名:${params.data.name}</div>
|
|
|
|
|
|
<div >组ID:${params.data.groupId}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>`
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
edgeLabel: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
color: "#fff",
|
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
|
textShadowColor: "#fff",
|
|
|
|
|
|
textShadowBlur: 0,
|
|
|
|
|
|
textShadowOffsetX: 0,
|
|
|
|
|
|
textShadowOffsetY: 0
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: "graph",
|
|
|
|
|
|
layout: "force",
|
|
|
|
|
|
animation: false,
|
|
|
|
|
|
draggable: true,
|
|
|
|
|
|
roam: true,
|
|
|
|
|
|
zoom: 0.1,
|
|
|
|
|
|
force: {
|
|
|
|
|
|
initLayout: "circular", // 初始布局使用圆形
|
|
|
|
|
|
edgeLength: 6000,
|
|
|
|
|
|
repulsion: 5000,
|
|
|
|
|
|
gravity: 0.1,
|
|
|
|
|
|
friction: 0.02,
|
|
|
|
|
|
coolingFactor: 0.1
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
animationDurationUpdate: 3500, // 节点移动更平滑
|
|
|
|
|
|
data: graph.value?.nodes.map((node) => ({
|
|
|
|
|
|
...node,
|
|
|
|
|
|
symbol: "circle",
|
|
|
|
|
|
x: variableColorAndPos(node.groupId).x,
|
|
|
|
|
|
y: variableColorAndPos(node.groupId).y,
|
|
|
|
|
|
symbolSize: 40,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: variableColorAndPos(node.groupId).color,
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
borderColor: "#46C6AD",
|
|
|
|
|
|
borderWidth: 1,
|
|
|
|
|
|
shadowBlur: 4,
|
|
|
|
|
|
borderType: "solid",
|
|
|
|
|
|
shadowColor: "rgba(19, 27, 114, 0.25)"
|
|
|
|
|
|
}
|
|
|
|
|
|
})),
|
|
|
|
|
|
links: graph.value?.links,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: "#37ACD7",
|
|
|
|
|
|
width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
chart.setOption(option)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
initChart()
|
|
|
|
|
|
})
|
|
|
|
|
|
let lastPostsLength = 0 //当列表更新时,记录上一次的长度
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => graph.value,
|
|
|
|
|
|
(newValue) => {
|
|
|
|
|
|
if (newValue.nodes.length > lastPostsLength) {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
initChart()
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
lastPostsLength = newValue.nodes.length //实现按需更新
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
|
|
|
|
|
)
|
2025-08-05 14:23:32 +08:00
|
|
|
|
</script>
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
|
.groupGraph-component {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
2025-08-05 14:23:32 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
.titleImage {
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 503px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.timeList {
|
|
|
|
|
|
width: 95%;
|
|
|
|
|
|
height: 42px;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 20px;
|
|
|
|
|
|
bottom: 20px;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
2025-07-22 16:40:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|