From 2a15f5a5f9d3b8b8fb881a9f5cbd08f15375d00b Mon Sep 17 00:00:00 2001 From: duanhao Date: Thu, 31 Jul 2025 17:06:11 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A4=BE=E5=9B=A2=E7=B4=A7=E5=AF=86=E5=85=B3?= =?UTF-8?q?=E7=B3=BB-=E7=82=B9=E8=BE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../socialGroups/components/communityNode.vue | 38 ++- .../socialGroups/components/detailNode.vue | 223 ++++++++++++------ .../socialGroups/components/graph.vue | 8 +- .../socialGroups/components/userPanel.vue | 2 +- .../LinkPrediction/socialGroups/index.vue | 4 +- 5 files changed, 194 insertions(+), 81 deletions(-) diff --git a/src/views/LinkPrediction/socialGroups/components/communityNode.vue b/src/views/LinkPrediction/socialGroups/components/communityNode.vue index 490ba26..0a1d8c4 100644 --- a/src/views/LinkPrediction/socialGroups/components/communityNode.vue +++ b/src/views/LinkPrediction/socialGroups/components/communityNode.vue @@ -17,6 +17,7 @@ import * as echarts from "echarts" import nodeHoverImg from "@/assets/images/nodeHover.png" let chart = null +let linkList = null const emit = defineEmits(["click:node", "click:edge"]) const statisticsList = inject("statisticsList"); const communityNodeList = inject("communityNodeList"); @@ -49,16 +50,16 @@ const initChart = async () => { target: communityNei.id, edge: communityNei.isHidden ? 1 : 0, //该边存在互动隐关系则权值为1,否则为0 lineStyle: { - width: communityNei.isHidden ? 4 : 1, // 无互动=细线,有互动=加粗 + width: communityNei.isHidden ? 7 : 1, // 无互动=细线,有互动=加粗 color: communityNei.isHidden ? "#FF5E00" : "#37ACD7", // 无互动=灰色,有互动=黄色 type: communityNei.isHidden ? "dashed" : "solid", // 无互动=实线,有互动=虚线 - dashArray: [2, 1] // 2像素实线,1像素空白 + opacity: communityNei.isHidden ? 1 : 0.5, } }) edgeSet.add(key) }) }) - + linkList = links const data = { nodes, links } const categories = [ @@ -241,7 +242,36 @@ const handleClickNode = () => { } else if (params.dataType == "edge") { const { data } = params if (data.edge) { - emit("click:edge", data) + const clickEdgeTarget = data.target + const clickEdgeSource = data.source + console.log("linkList", linkList) + // 找所有的虚线边 + const dashedEdgeList = linkList.filter((item) => { + return item.lineStyle.type === "dashed" + }) + console.log("dashedEdgeList", dashedEdgeList) + // 从所有的虚线边中找到连接了clickEdgeTarget或者clickEdgeSource的边 + const connectEdgeList = dashedEdgeList.filter((item) => { + return item.source === clickEdgeSource + || item.target === clickEdgeTarget + || item.source === clickEdgeTarget + || item.target === clickEdgeSource + }) + console.log("connectEdgeList", connectEdgeList) + let groupIdList = [] + // 遍历边的source和target,找到所有的groupId + connectEdgeList.forEach((item) => { + if(!groupIdList.includes(item.source)) { + groupIdList.push(item.source) + } + if(!groupIdList.includes(item.target)) { + groupIdList.push(item.target) + } + }) + // 只取前三个值 + groupIdList = groupIdList.slice(0, 3) + console.log("打印点击边时,与目标节点和源节点相连的其他节点id",groupIdList); + emit("click:edge", data, groupIdList) } } }) diff --git a/src/views/LinkPrediction/socialGroups/components/detailNode.vue b/src/views/LinkPrediction/socialGroups/components/detailNode.vue index 46720b2..3368326 100644 --- a/src/views/LinkPrediction/socialGroups/components/detailNode.vue +++ b/src/views/LinkPrediction/socialGroups/components/detailNode.vue @@ -31,16 +31,20 @@ > -
-
- -
-
-
+ +
+
+ +
+
{{ TansTimestamp(endTime, "YYYY.MM.DD HH:mm:ss") }}
@@ -61,6 +65,21 @@ const socialGroupsStore = useSocialGroupsStore() const { communityDetailNodeList } = storeToRefs(socialGroupsStore) const { timeList } = storeToRefs(socialGroupsStore) const { curHighlightUserIdList } = storeToRefs(socialGroupsStore) +// 添加对curSelecedGroupIds的监听,用于检测列表项切换 +const { curSelecedGroupIds } = storeToRefs(socialGroupsStore) + + +// 添加对curSelecedGroupIds的watch,确保切换列表项时重置时间轴 +watch(curSelecedGroupIds, (newIds) => { + if (newIds && newIds.length > 0) { + // 重置时间轴位置到起点 + currentPosition.value = 0; + currentTime.value = new Date("2024-05-16 16:56:04"); + // 重新开始自动播放 + pause(); // 先停止可能正在运行的计时器 + play(); // 重新开始播放 + } +}, { deep: true }) // 用于监听是从 列表 点进来的还是从 边 点进来的 const { clickEvent } = storeToRefs(socialGroupsStore) @@ -68,6 +87,7 @@ const chartsData = ref({}) const emit = defineEmits(["click:goback"]) const handleGoback = () => { + pause() emit("click:goback", "CommunityNode") } @@ -100,6 +120,48 @@ 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 lastPosition = ref(0) // 时间列表最后的时间点的位置 +const isPlaying = ref(false) // 是否自动播放 +let playTimer = null + +// 自动播放控制 +const play = () => { + if (isPlaying.value) return + isPlaying.value = true + playTimer = setInterval(() => { + // 步进像素 + const step = 4 // 每次移动4px,可根据需要调整速度 + if (currentPosition.value >= axisWidth) { + pause() + return + } + currentPosition.value = Math.min(axisWidth, currentPosition.value + step) + currentTime.value = getTimeFromPosition(currentPosition.value) + sendTimeChangeRequest() + }, 300) // 每300ms移动一次 +} + +// 发送请求逻辑封装 +const sendTimeChangeRequest = () => { + const currentTimes = TansTimestamp(currentTime.value, "YYYY-MM-DD HH:mm:ss") + if (socialGroupsStore.curRelationId == "") { + socialGroupsStore.initGraphCommunityDetailNode(socialGroupsStore.curSelecedGroupIds, currentTimes) + } else { + socialGroupsStore.initGraphCommunityDetailNode( + socialGroupsStore.curSelecedGroupIds, + currentTimes, + socialGroupsStore.curRelationId + ) + } +} + +const pause = () => { + isPlaying.value = false + if (playTimer) { + clearInterval(playTimer) + playTimer = null + } +} const axisRef = ref(null) const isDragging = ref(false) @@ -132,18 +194,16 @@ watch(timeList, (newList) => { // watch来监听timeList变化并设置初始值 watch(timePointsWithPositions, (newTimePoints) => { if (newTimePoints && newTimePoints.length > 0) { - const lastTimePoint = newTimePoints[newTimePoints.length - 1] - currentTime.value = lastTimePoint.time - currentPosition.value = lastTimePoint.position + // 始终将currentPosition设置为0(时间轴起点) + currentPosition.value = 0; + currentTime.value = "2024-05-16 16:56:04" + lastPosition.value = newTimePoints[newTimePoints.length - 1].position // 触发图更新 const currentTimes = TansTimestamp(currentTime.value, "YYYY-MM-DD HH:mm:ss") + socialGroupsStore.initGraphCommunityDetailNode(socialGroupsStore.curSelecedGroupIds, currentTimes) } }, { immediate: true }) -// 判断当前时间点是否激活 -/* const isTimeSignActive = (timeStr) => { - return new Date(timeStr).getTime() === currentTime.value.getTime() -} */ // 添加时间点点击事件处理函数 const handleTimePointClick = (timeStr) => { @@ -167,6 +227,7 @@ const getTimeFromPosition = (position) => { // 指针按下事件 const handlePointerDown = (e) => { if (e.target.classList.contains("timeLine-point")) return + pause() // 拖动或点击时暂停自动播放 const rect = axisRef.value.getBoundingClientRect() const position = Math.max(0, Math.min(axisWidth, e.clientX - rect.left)) @@ -247,6 +308,10 @@ const initChart = async () => { else if (interactionTime > 30) return 10 else return 1 } + // 添加边唯一标识集合,用于检测重复边 + const edgeSet = new Set() + // 使用Map存储边,方便后续查找和更新 + const edgeMap = new Map() if (!Object.keys(socialGroupsStore.communityDetailNodeList).length) return // 先获取到所有节点 socialGroupsStore.communityAllNodeList.forEach((item) => { @@ -260,17 +325,40 @@ const initChart = async () => { }) Object.entries(socialGroupsStore.communityDetailNodeList).forEach(([parentId, children]) => { children.forEach((child) => { - links.push({ - source: parentId, - target: child.id, - edge: child.isHidden ? 1 : 0, - interactionTimes: child.interactionTime, - lineStyle: { - width: child.isHidden ? 4 : edgeWidth(child.interactionTime), - color: child.isHidden ? "#FF5E00" : "#37ACD7", // 无互动=灰色,有互动=黄色 - type: child.isHidden ? "dashed" : "solid" // 无互动=实线,有互动=虚线 + // 生成边的唯一标识符,与方向无关 + const edgeKey = [parentId, child.id].sort().join('-') + // 检查边是否已存在 + if (!edgeSet.has(edgeKey)) { + edgeSet.add(edgeKey) + const newEdge = { + source: parentId, + target: child.id, + edge: child.isHidden ? 1 : 0, + interactionTimes: child.interactionTime, + lineStyle: { + width: child.isHidden ? 7 : edgeWidth(child.interactionTime), + color: child.isHidden ? "#FF5E00" : "#37ACD7", // 无互动=橙色,有互动=蓝色 + type: child.isHidden ? "dashed" : "solid", // 无互动=虚线,有互动=实线 + opacity: child.isHidden ? 1 : 0.5, + } } - }) + links.push(newEdge) + edgeMap.set(edgeKey, newEdge) + } else { + // 边已存在,检查isHidden状态是否变化 + const existingEdge = edgeMap.get(edgeKey) + // 只在isHidden从false变为true时更新样式 + if (!existingEdge.edge && child.isHidden) { + existingEdge.edge = 1 + existingEdge.lineStyle = { + width: 7, + color: "#FF5E00", + type: "dashed", + opacity: 1 + } + } + } + }) @@ -443,7 +531,7 @@ const initChart = async () => { categories: categories, force: { edgeLength: 2500, - repulsion: 4000, + repulsion: 20000, gravity: 0.1, friction: 0.02, coolingFactor: 0.1 @@ -499,8 +587,6 @@ const highLightUserNodes = (newHiglightUserIdList) => { //等待所有节点添加完毕后再查找 newHiglightUserIdList.forEach((id) => { const index = chartsData.value.nodes.findIndex((node) => node.id === id) - console.log(index); - if (index != -1) { chart.dispatchAction({ type: "highlight", @@ -520,6 +606,7 @@ onMounted(() => { }, 0) }) highLightUserNodes() + play() }) @@ -642,52 +729,48 @@ onMounted(() => { height: 30px; background: transparent; } - } - .active-sign { - position: relative; + + .active-needle { + width: 30px; + height: 34px; + background-image: url("@/assets/images/point.png"); + background-size: cover; + bottom: 1px; + left: -11px; + position: absolute; + } + .timeLine-point { z-index: 2; - .active-needle { - width: 30px; - height: 34px; - background-image: url("@/assets/images/point.png"); - background-size: cover; - bottom: 1px; - left: -11px; - position: absolute; + 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; + left: -5px; + cursor: pointer; + user-select: none; + will-change: left; + transform: translate3d(0, 0, 0); // 强制启用硬件加速 + &:hover { + transform: translate3d(0, 0, 0) scale(1.1); } - .timeLine-point { - width: 18px; - height: 18px; - background-color: transparent; + &:active { + transform: translate3d(0, 0, 0) scale(0.95); + } + &::after { + content: ""; + width: 10px; + height: 10px; + background-color: #f9bd25; border-radius: 50%; - border: 1.6px solid #ffe5a4; position: absolute; - display: flex; - align-items: center; - justify-content: center; - top: -6px; - left: -5px; - 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; - } } - } } .current-time-display { diff --git a/src/views/LinkPrediction/socialGroups/components/graph.vue b/src/views/LinkPrediction/socialGroups/components/graph.vue index 8cfa4e6..4f85ccd 100644 --- a/src/views/LinkPrediction/socialGroups/components/graph.vue +++ b/src/views/LinkPrediction/socialGroups/components/graph.vue @@ -34,15 +34,17 @@ const handleClickNode = async (nodeInfo) => { socialGroupsStore.curRelationId = "" } -const handleClickEdge = async (edgeInfo) => { +const handleClickEdge = async (edgeInfo, groupIdList) => { console.log("点击边"); socialGroupsStore.curComponent = "detailNode" socialGroupsStore.clickEvent = "edge" socialGroupsStore.curRelationId = "" // 先设置socialGroupsStore.clickEdgeTimeList - await socialGroupsStore.getClickEdgeTimeList([edgeInfo.source, edgeInfo.target]) + // await socialGroupsStore.getClickEdgeTimeList([edgeInfo.source, edgeInfo.target]) + await socialGroupsStore.getClickEdgeTimeList(groupIdList) const lastTime = socialGroupsStore.timeList[socialGroupsStore.timeList.length - 1] - await socialGroupsStore.initGraphCommunityDetailNode([edgeInfo.source, edgeInfo.target], lastTime) + // await socialGroupsStore.initGraphCommunityDetailNode([edgeInfo.source, edgeInfo.target], lastTime) + await socialGroupsStore.initGraphCommunityDetailNode(groupIdList, lastTime) } diff --git a/src/views/LinkPrediction/socialGroups/components/userPanel.vue b/src/views/LinkPrediction/socialGroups/components/userPanel.vue index 2438222..b60f49b 100644 --- a/src/views/LinkPrediction/socialGroups/components/userPanel.vue +++ b/src/views/LinkPrediction/socialGroups/components/userPanel.vue @@ -10,7 +10,7 @@ >
TOP{{ index+1 }}
diff --git a/src/views/LinkPrediction/socialGroups/index.vue b/src/views/LinkPrediction/socialGroups/index.vue index 7725097..a281c6c 100644 --- a/src/views/LinkPrediction/socialGroups/index.vue +++ b/src/views/LinkPrediction/socialGroups/index.vue @@ -88,9 +88,7 @@ const handleSelectedUserGroup = (group) => { console.log("点击列表group:",group) const groupIds = group.list.map((item)=>item.groupId) socialGroupsStore.curRelationId = group.relationId //保存当前点击的relationid,为了区分到底是从哪点进二级界面的 - const length = group.timeList.length - const lastTime = group.timeList[length - 1] - socialGroupsStore.initGraphCommunityDetailNode(groupIds, lastTime, group.relationId) + socialGroupsStore.initGraphCommunityDetailNode(groupIds, "2024-05-16 16:56:04", group.relationId) socialGroupsStore.setTimeList(group.timeList) socialGroupsStore.getSocialGroupPostListByRelationId(group.relationId) };