2025-07-25 15:07:08 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="detailNode-component">
|
2025-07-25 17:24:14 +08:00
|
|
|
|
<img src="@/assets/images/icon/goback.png" alt="" class="goback" @click="handleGoback" />
|
2025-07-25 15:07:08 +08:00
|
|
|
|
<div class="graph-container" id="container"></div>
|
2025-07-28 15:59:40 +08:00
|
|
|
|
<div class="statistic-container">
|
2025-07-29 12:13:19 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="statistics-item"
|
|
|
|
|
|
v-for="item in interactionStore.statisticsDetailList"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
>
|
2025-07-28 15:59:40 +08:00
|
|
|
|
<img :src="item.icon" class="icon" />
|
|
|
|
|
|
<div class="name">{{ item.name }}: </div>
|
|
|
|
|
|
<div class="count">{{ item.count }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-07-29 12:13:19 +08:00
|
|
|
|
<div class="time-axis">
|
|
|
|
|
|
<div class="time">{{ TansTimestamp(startTime, "YYYY.MM.DD HH:mm:ss") }}</div>
|
|
|
|
|
|
<div class="axis" ref="axisRef" @pointerdown="handlePointerDown">
|
2025-07-29 20:08:13 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="time-section"
|
2025-07-30 16:33:29 +08:00
|
|
|
|
v-for="time in timeList"
|
2025-07-29 20:08:13 +08:00
|
|
|
|
:key="time"
|
2025-07-30 16:33:29 +08:00
|
|
|
|
:style="{ left: getTimeSectionLeft(time) + 5 + 'px' }"
|
2025-07-29 20:08:13 +08:00
|
|
|
|
></div>
|
2025-07-29 12:13:19 +08:00
|
|
|
|
<div class="progress-bar" :style="trackStyle"></div>
|
|
|
|
|
|
<div class="active-sign" :style="{ left: `${currentPosition}px` }">
|
|
|
|
|
|
<div class="active-needle"></div>
|
2025-07-29 16:23:19 +08:00
|
|
|
|
<el-tooltip
|
|
|
|
|
|
:content="TansTimestamp(currentTime, 'YYYY.MM.DD HH:mm:ss')"
|
|
|
|
|
|
placement="bottom"
|
|
|
|
|
|
effect="light"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="timeLine-point" @pointerdown.stop="handlePointPointerDown"></div>
|
|
|
|
|
|
</el-tooltip>
|
2025-07-29 12:13:19 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="time">{{ TansTimestamp(endTime, "YYYY.MM.DD HH:mm:ss") }}</div>
|
|
|
|
|
|
</div>
|
2025-07-25 15:07:08 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-07-30 16:33:29 +08:00
|
|
|
|
import { defineEmits, onMounted, ref, onUnmounted, computed, watch, nextTick } from "vue"
|
|
|
|
|
|
import { TansTimestamp, getAvatarUrl } from "@/utils/transform"
|
2025-07-28 15:59:40 +08:00
|
|
|
|
import nodeHoverImg from "@/assets/images/nodeHover.png"
|
|
|
|
|
|
import * as echarts from "echarts"
|
2025-07-29 12:13:19 +08:00
|
|
|
|
import { storeToRefs } from "pinia"
|
|
|
|
|
|
import { useCharacterInteractionStore } from "@/store/llinkPrediction/index"
|
2025-07-30 16:33:29 +08:00
|
|
|
|
|
2025-07-29 12:13:19 +08:00
|
|
|
|
const interactionStore = useCharacterInteractionStore()
|
2025-07-30 16:33:29 +08:00
|
|
|
|
const { communityDetailNodeRelation, timeList, predictionUserIds } = storeToRefs(interactionStore)
|
2025-07-25 17:24:14 +08:00
|
|
|
|
const emit = defineEmits(["click:goback"])
|
2025-07-30 11:23:19 +08:00
|
|
|
|
const chartsData = ref({})
|
2025-07-25 17:24:14 +08:00
|
|
|
|
const handleGoback = () => {
|
|
|
|
|
|
emit("click:goback", "CommunityNode")
|
|
|
|
|
|
}
|
2025-07-28 15:59:40 +08:00
|
|
|
|
|
2025-07-29 12:13:19 +08:00
|
|
|
|
//当点击时间轴的时候,communityDetailNodeList改变,重新更新关系图
|
|
|
|
|
|
watch(
|
2025-07-30 16:33:29 +08:00
|
|
|
|
communityDetailNodeRelation,
|
2025-07-29 20:08:13 +08:00
|
|
|
|
() => {
|
2025-07-29 12:13:19 +08:00
|
|
|
|
initChart()
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-07-30 16:33:29 +08:00
|
|
|
|
//监听predictionUserIds的变化从而筛选需要高亮的预测节点
|
2025-07-30 11:23:19 +08:00
|
|
|
|
watch(
|
2025-07-30 16:33:29 +08:00
|
|
|
|
predictionUserIds,
|
2025-07-30 11:23:19 +08:00
|
|
|
|
(newIds) => {
|
|
|
|
|
|
if (newIds.length != 0) {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
highLightUserNodes(newIds)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-07-30 16:33:29 +08:00
|
|
|
|
{ deep: true, immediate: true }
|
2025-07-30 11:23:19 +08:00
|
|
|
|
)
|
2025-07-30 16:33:29 +08:00
|
|
|
|
|
2025-07-29 12:13:19 +08:00
|
|
|
|
// 时间轴相关数据
|
|
|
|
|
|
const startTime = ref(new Date("2024-05-16 16:56:04"))
|
|
|
|
|
|
const endTime = ref(new Date("2024-05-23 10:16:56"))
|
|
|
|
|
|
const currentTime = ref(new Date("2024-05-16 16:56:04")) // 当前选中的时间
|
|
|
|
|
|
const currentPosition = ref(0) // 初始位置(轴长度的一半)
|
|
|
|
|
|
const axisRef = ref(null)
|
|
|
|
|
|
const isDragging = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存时间计算相关的常量
|
|
|
|
|
|
const axisWidth = 426
|
|
|
|
|
|
const startTimeMs = startTime.value.getTime()
|
|
|
|
|
|
const endTimeMs = endTime.value.getTime()
|
|
|
|
|
|
const totalDuration = endTimeMs - startTimeMs
|
|
|
|
|
|
|
2025-07-29 20:08:13 +08:00
|
|
|
|
//点击用户组列表后,显示这两个用户的交互时间切片,获得每一个时间距离时间轴初始位置的距离
|
|
|
|
|
|
const getTimeSectionLeft = computed(() => {
|
|
|
|
|
|
return (time) => {
|
|
|
|
|
|
const total = endTime.value.getTime() - startTime.value.getTime()
|
|
|
|
|
|
const offset = new Date(time).getTime() - startTime.value.getTime()
|
|
|
|
|
|
return Math.max(0, Math.min(426, (offset / total) * 426))
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-07-30 16:33:29 +08:00
|
|
|
|
|
2025-07-29 12:13:19 +08:00
|
|
|
|
// 根据位置计算时间
|
|
|
|
|
|
const getTimeFromPosition = (position) => {
|
|
|
|
|
|
const ratio = Math.max(0, Math.min(1, position / axisWidth))
|
|
|
|
|
|
const timeOffset = totalDuration * ratio
|
|
|
|
|
|
return new Date(startTimeMs + timeOffset)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 指针按下事件
|
|
|
|
|
|
const handlePointerDown = (e) => {
|
|
|
|
|
|
if (e.target.classList.contains("timeLine-point")) return
|
|
|
|
|
|
const rect = axisRef.value.getBoundingClientRect()
|
|
|
|
|
|
const position = Math.max(0, Math.min(axisWidth, e.clientX - rect.left))
|
|
|
|
|
|
|
|
|
|
|
|
// 直接更新位置,不使用节流函数
|
|
|
|
|
|
currentPosition.value = position
|
|
|
|
|
|
currentTime.value = getTimeFromPosition(position)
|
|
|
|
|
|
|
|
|
|
|
|
// 点击后输出当前时间
|
|
|
|
|
|
const currentTimes = TansTimestamp(currentTime.value, "YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
interactionStore.initGraphCommunityDetailNode(interactionStore.curSelecedGroupIds, currentTimes)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 时间点指针按下事件
|
|
|
|
|
|
const handlePointPointerDown = (e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
isDragging.value = true
|
|
|
|
|
|
// 缓存轴的边界矩形,避免重复计算
|
|
|
|
|
|
const rect = axisRef.value.getBoundingClientRect()
|
|
|
|
|
|
const axisLeft = rect.left
|
|
|
|
|
|
|
|
|
|
|
|
const handlePointerMove = (e) => {
|
|
|
|
|
|
if (!isDragging.value) return
|
|
|
|
|
|
const position = Math.max(0, Math.min(axisWidth, e.clientX - axisLeft))
|
|
|
|
|
|
|
|
|
|
|
|
// 直接更新位置,不检查阈值,确保实时响应
|
|
|
|
|
|
currentPosition.value = position
|
|
|
|
|
|
currentTime.value = getTimeFromPosition(position)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handlePointerUp = () => {
|
|
|
|
|
|
isDragging.value = false
|
|
|
|
|
|
// 拖动结束时输出当前时间
|
2025-07-29 16:23:19 +08:00
|
|
|
|
const currentTimes = TansTimestamp(currentTime.value, "YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
interactionStore.initGraphCommunityDetailNode(interactionStore.curSelecedGroupIds, currentTimes)
|
2025-07-29 12:13:19 +08:00
|
|
|
|
|
|
|
|
|
|
document.removeEventListener("pointermove", handlePointerMove)
|
|
|
|
|
|
document.removeEventListener("pointerup", handlePointerUp)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener("pointermove", handlePointerMove, { passive: true })
|
|
|
|
|
|
document.addEventListener("pointerup", handlePointerUp)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const trackStyle = computed(() => {
|
|
|
|
|
|
const progressPercent = Math.min(100, (currentPosition.value / 426) * 100)
|
|
|
|
|
|
return {
|
|
|
|
|
|
background: `linear-gradient(90deg, #00F3FF 0%, #00F3FF ${progressPercent}%, #3B7699 ${progressPercent}%, #3B7699 100%)`,
|
|
|
|
|
|
width: "100%"
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-07-28 15:59:40 +08:00
|
|
|
|
|
2025-07-29 12:13:19 +08:00
|
|
|
|
// 组件卸载时清理事件监听器
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
document.removeEventListener("pointermove", () => {})
|
|
|
|
|
|
document.removeEventListener("pointerup", () => {})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
let chart = null
|
2025-07-29 16:23:19 +08:00
|
|
|
|
|
2025-07-28 15:59:40 +08:00
|
|
|
|
const initChart = async () => {
|
|
|
|
|
|
chart = echarts.init(document.getElementById("container"))
|
|
|
|
|
|
|
|
|
|
|
|
const links = []
|
2025-07-30 16:33:29 +08:00
|
|
|
|
let nodes = []
|
2025-07-29 16:23:19 +08:00
|
|
|
|
const edgeWidth = (interactionTime) => {
|
|
|
|
|
|
if (interactionTime > 3) return 4
|
|
|
|
|
|
else if (interactionTime > 10) return 6
|
|
|
|
|
|
else if (interactionTime > 20) return 8
|
|
|
|
|
|
else if (interactionTime > 30) return 10
|
|
|
|
|
|
else return 1
|
|
|
|
|
|
}
|
2025-07-30 16:33:29 +08:00
|
|
|
|
if (!Object.keys(interactionStore.communityDetailNodeRelation).length) return
|
|
|
|
|
|
|
|
|
|
|
|
//先处理节点
|
|
|
|
|
|
nodes = interactionStore.communityDetailNodeList.map((item) => ({
|
|
|
|
|
|
id: item.userId,
|
|
|
|
|
|
name: item.userName,
|
|
|
|
|
|
symbolSize: 40,
|
|
|
|
|
|
postNum: item.postNum,
|
|
|
|
|
|
fancy: item.fans
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
Object.entries(interactionStore.communityDetailNodeRelation).forEach(([parentId, children]) => {
|
2025-07-28 15:59:40 +08:00
|
|
|
|
children.forEach((child) => {
|
|
|
|
|
|
links.push({
|
2025-07-30 11:23:19 +08:00
|
|
|
|
source: parentId,
|
2025-07-28 15:59:40 +08:00
|
|
|
|
target: child.id,
|
|
|
|
|
|
edge: child.isHidden ? 1 : 0,
|
2025-07-29 16:23:19 +08:00
|
|
|
|
interactionTimes: child.interactionTime,
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
width: child.isHidden ? 4 : edgeWidth(child.interactionTime),
|
|
|
|
|
|
color: child.isHidden ? "#f8bf38" : "#37ACD7", // 无互动=灰色,有互动=黄色
|
|
|
|
|
|
type: child.isHidden ? "dashed" : "solid" // 无互动=实线,有互动=虚线
|
|
|
|
|
|
}
|
2025-07-28 15:59:40 +08:00
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2025-07-30 11:23:19 +08:00
|
|
|
|
chartsData.value = { links, nodes }
|
2025-07-28 15:59:40 +08:00
|
|
|
|
|
|
|
|
|
|
const categories = [
|
2025-07-29 16:23:19 +08:00
|
|
|
|
{ name: "事件活跃者", category: 0, icon: "circle" },
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "互动关系",
|
|
|
|
|
|
category: 1,
|
|
|
|
|
|
icon: `image://${new URL("@/assets/images/linkPrediction/icon/interaction-icon2.png", import.meta.url)}`
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "互动隐关系",
|
|
|
|
|
|
category: 2,
|
|
|
|
|
|
icon: `image://${new URL("@/assets/images/linkPrediction/icon/hidden-icon.png", import.meta.url)}`
|
|
|
|
|
|
}
|
2025-07-28 15:59:40 +08:00
|
|
|
|
]
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
//图例配置
|
|
|
|
|
|
legend: [
|
|
|
|
|
|
{
|
|
|
|
|
|
data: categories.map((c) => ({
|
|
|
|
|
|
name: c.name,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color:
|
|
|
|
|
|
c.category === 0
|
|
|
|
|
|
? new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
|
|
|
|
|
{ offset: 1, color: "#1a3860" }, // 深蓝渐变
|
|
|
|
|
|
{ offset: 0.5, color: "#38546b" },
|
|
|
|
|
|
{ offset: 0, color: "#5fb3b3" }
|
|
|
|
|
|
])
|
|
|
|
|
|
: c.category === 1
|
|
|
|
|
|
? new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
|
|
|
|
|
{ offset: 0, color: "#9eec9c" }, // 绿色渐变
|
|
|
|
|
|
{ offset: 0.37, color: "#aef295" },
|
|
|
|
|
|
{ offset: 1, color: "#c2f989" }
|
|
|
|
|
|
])
|
|
|
|
|
|
: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
|
|
|
|
|
{ offset: 0, color: "#ff9a9e" }, // 红色渐变(用于 category === 2)
|
|
|
|
|
|
{ offset: 0.5, color: "#fad0c4" },
|
|
|
|
|
|
{ offset: 1, color: "#fbc2eb" }
|
|
|
|
|
|
])
|
2025-07-29 16:23:19 +08:00
|
|
|
|
},
|
|
|
|
|
|
icon: c.icon
|
2025-07-28 15:59:40 +08:00
|
|
|
|
})),
|
|
|
|
|
|
right: 21,
|
2025-07-29 16:23:19 +08:00
|
|
|
|
symbolKeepAspect: false,
|
2025-07-28 15:59:40 +08:00
|
|
|
|
bottom: 70,
|
|
|
|
|
|
orient: "vertical",
|
|
|
|
|
|
itemWidth: 16,
|
|
|
|
|
|
itemHeight: 16,
|
|
|
|
|
|
itemGap: 12,
|
|
|
|
|
|
backgroundColor: "rgba(0,67,125,0.56)", // 半透明深蓝
|
|
|
|
|
|
borderRadius: 8, // 圆角
|
|
|
|
|
|
borderColor: "#c2f2ff", // 淡蓝色边框
|
|
|
|
|
|
borderWidth: 0.3,
|
|
|
|
|
|
padding: [12, 20, 12, 20], // 上右下左
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: "#fff",
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: "normal"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
//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;">
|
2025-07-30 16:33:29 +08:00
|
|
|
|
<div >用户名:${params.data.name}</div>
|
2025-07-28 15:59:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>`
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
edgeLabel: {
|
|
|
|
|
|
show: false,
|
|
|
|
|
|
position: "middle",
|
|
|
|
|
|
formatter: function (params) {
|
2025-07-29 20:08:13 +08:00
|
|
|
|
return `${params.data.interactionTimes}次互动`
|
2025-07-28 15:59:40 +08:00
|
|
|
|
},
|
|
|
|
|
|
fontSize: 14
|
|
|
|
|
|
},
|
|
|
|
|
|
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,
|
2025-07-29 16:23:19 +08:00
|
|
|
|
zoom: 0.15,
|
2025-07-28 15:59:40 +08:00
|
|
|
|
categories: categories,
|
|
|
|
|
|
force: {
|
|
|
|
|
|
edgeLength: 2500,
|
|
|
|
|
|
repulsion: 4000,
|
2025-07-29 12:13:19 +08:00
|
|
|
|
gravity: 0.1,
|
2025-07-28 15:59:40 +08:00
|
|
|
|
friction: 0.02,
|
|
|
|
|
|
coolingFactor: 0.1
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
animationDurationUpdate: 3500, // 节点移动更平滑
|
2025-07-30 11:23:19 +08:00
|
|
|
|
data: chartsData.value.nodes.map((node) => ({
|
2025-07-28 15:59:40 +08:00
|
|
|
|
...node,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: new echarts.graphic.RadialGradient(0.98, 0.38, 0.9, [
|
|
|
|
|
|
{ offset: 1, color: "#1a3860" }, // 最左侧
|
|
|
|
|
|
{ offset: 0.5, color: "#38546b" }, // 中间
|
|
|
|
|
|
{ offset: 0, color: "#5fb3b3" } // 最右侧
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
borderColor: "#46C6AD",
|
|
|
|
|
|
borderWidth: 1,
|
|
|
|
|
|
shadowBlur: 4,
|
|
|
|
|
|
borderType: "dashed",
|
|
|
|
|
|
shadowColor: "rgba(19, 27, 114, 0.25)"
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
shadowBlur: 20,
|
|
|
|
|
|
shadowColor: "#c4a651",
|
|
|
|
|
|
borderColor: "#fcd267",
|
2025-07-30 11:23:19 +08:00
|
|
|
|
borderWidth: 5,
|
2025-07-28 15:59:40 +08:00
|
|
|
|
borderType: "solid"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})),
|
2025-07-30 11:23:19 +08:00
|
|
|
|
links: chartsData.value.links,
|
2025-07-28 15:59:40 +08:00
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: "#37ACD7",
|
|
|
|
|
|
width: 1
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
chart.setOption(option)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-30 11:23:19 +08:00
|
|
|
|
const highLightUserNodes = (userIds) => {
|
|
|
|
|
|
if (!userIds) return
|
|
|
|
|
|
chart.dispatchAction({
|
|
|
|
|
|
type: "downplay",
|
|
|
|
|
|
seriesIndex: 0
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
//等待所有节点添加完毕后再查找
|
|
|
|
|
|
userIds.forEach((id) => {
|
|
|
|
|
|
const index = chartsData.value.nodes.findIndex((node) => node.id === id)
|
|
|
|
|
|
if (index != -1) {
|
|
|
|
|
|
chart.dispatchAction({
|
|
|
|
|
|
type: "highlight",
|
|
|
|
|
|
dataIndex: index
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 15:59:40 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
initChart()
|
2025-07-30 11:23:19 +08:00
|
|
|
|
highLightUserNodes()
|
2025-07-28 15:59:40 +08:00
|
|
|
|
})
|
2025-07-25 15:07:08 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
|
.detailNode-component {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
position: relative;
|
2025-07-25 17:24:14 +08:00
|
|
|
|
.goback {
|
|
|
|
|
|
position: absolute;
|
2025-07-28 15:59:40 +08:00
|
|
|
|
top: -25px;
|
2025-07-25 17:24:14 +08:00
|
|
|
|
left: 20px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
2025-07-25 15:07:08 +08:00
|
|
|
|
.graph-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 93%;
|
|
|
|
|
|
}
|
2025-07-28 15:59:40 +08:00
|
|
|
|
.statistic-container {
|
|
|
|
|
|
width: 378px;
|
|
|
|
|
|
height: 42px;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
border: 1px solid #3aa1f8;
|
|
|
|
|
|
background: linear-gradient(270deg, rgba(0, 82, 125, 0.48) 0%, rgba(0, 200, 255, 0.23) 100%);
|
|
|
|
|
|
backdrop-filter: blur(3px);
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
bottom: 105px;
|
|
|
|
|
|
left: 21px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
.statistics-item {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
.icon {
|
|
|
|
|
|
width: 14px;
|
|
|
|
|
|
height: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.name {
|
|
|
|
|
|
color: rgba(255, 255, 255, 0.76);
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-family: OPPOSans;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
line-height: normal;
|
|
|
|
|
|
margin-left: 3px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.count {
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-family: D-DIN;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
line-height: normal;
|
|
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.time-axis {
|
|
|
|
|
|
width: 95%;
|
|
|
|
|
|
height: 42px;
|
|
|
|
|
|
border: 1px solid #3aa1f8;
|
|
|
|
|
|
background: linear-gradient(270deg, rgba(0, 82, 125, 0.48) 0%, rgba(0, 200, 255, 0.23) 100%);
|
|
|
|
|
|
backdrop-filter: blur(3px);
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 20px;
|
|
|
|
|
|
bottom: 50px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
z-index: 1;
|
2025-07-29 12:13:19 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 0px 10px;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
touch-action: none; // 防止触摸设备上的默认行为
|
|
|
|
|
|
.time {
|
|
|
|
|
|
font-family: "PingFang SC";
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
line-height: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
.axis {
|
|
|
|
|
|
width: 426px;
|
|
|
|
|
|
height: 6px;
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
background-color: #3b7699;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transform: translateZ(0); // 启用硬件加速
|
|
|
|
|
|
position: relative;
|
2025-07-29 20:08:13 +08:00
|
|
|
|
.time-section {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: -8px;
|
|
|
|
|
|
width: 4px;
|
|
|
|
|
|
height: 22px;
|
|
|
|
|
|
background: #ffe066;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
z-index: 3;
|
|
|
|
|
|
}
|
2025-07-29 12:13:19 +08:00
|
|
|
|
.progress-bar {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
height: 6px;
|
|
|
|
|
|
background-color: #00ecf9;
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
.active-sign {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
.active-needle {
|
|
|
|
|
|
width: 30px;
|
|
|
|
|
|
height: 34px;
|
|
|
|
|
|
background-image: url("@/assets/images/point.png");
|
|
|
|
|
|
background-size: cover;
|
|
|
|
|
|
bottom: 1px;
|
2025-07-29 16:23:19 +08:00
|
|
|
|
left: -11px;
|
2025-07-29 12:13:19 +08:00
|
|
|
|
position: absolute;
|
|
|
|
|
|
}
|
|
|
|
|
|
.timeLine-point {
|
|
|
|
|
|
width: 18px;
|
|
|
|
|
|
height: 18px;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
border: 1.6px solid #ffe5a4;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
top: -6px;
|
2025-07-29 16:23:19 +08:00
|
|
|
|
left: -5px;
|
2025-07-29 12:13:19 +08:00
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
will-change: left;
|
|
|
|
|
|
transform: translate3d(0, 0, 0); // 强制启用硬件加速
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
transform: translate3d(0, 0, 0) scale(1.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
transform: translate3d(0, 0, 0) scale(0.95);
|
|
|
|
|
|
}
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
width: 10px;
|
|
|
|
|
|
height: 10px;
|
|
|
|
|
|
background-color: #f9bd25;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-28 15:59:40 +08:00
|
|
|
|
}
|
2025-07-25 15:07:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|