SocialNetworks_duan/src/views/KeyNodeDiscern/bridgeCommunication/components/GraphPanel.vue
2025-08-19 11:23:46 +08:00

302 lines
7.9 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="right-panel">
<img src="@/assets/images/head/bayi.png" alt="" class="head" />
<div class="key-node-recognition">
<div class="content-wrapper">
<div class="chart-container">
<BridgeCommunityGraph
ref="bridgeGraphRef"
v-if="currentComponent == 'BridgeCommunityNode'"
:timestamp="store.activeTimePoint"
@click:navigateToCommunityDetail="handleClickCommunity"
/>
<DetailCommunityGraph
v-else
ref="detailCommunityGraphRef"
:communityId="currentSelectedCommunityId"
@click:goback="handleClickGoBack"
/>
</div>
<div class="timeline-container">
<span class="time-label">2023.10.07 00:00:00</span>
<div class="timeline-track" :style="trackStyle">
<div
v-for="point in store.timePoints"
:key="point.id"
class="timeline-point-wrapper"
:style="{ left: `${pointPositions[point.id]}%` }"
@click="handleTimePointClick(point.id)"
>
<el-tooltip
class="timePoint-box-item"
effect="light"
:content="point.timestamp"
placement="bottom"
>
<div class="timeline-point" :class="{ active: store.activeTimePoint === point.id }">
<el-popover
v-if="store.activeTimePoint === point.id"
effect="dark"
placement="top"
:title="point.leaderId"
:width="50"
trigger="click"
content="发布贴文"
>
<template #reference>
<div class="active-pin"></div>
</template>
</el-popover>
</div>
</el-tooltip>
</div>
</div>
<span class="time-label">2023.10.15 00:00:00</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, defineExpose, watch, computed, defineProps } from "vue"
import { useKeyNodeStore2 } from "@/store/keyNodeStore2"
// 接收 stopAutomaticPlay 方法
const props = defineProps({
stopAutomaticPlay: { type: Function, required: true }
})
import BridgeCommunityGraph from "./graph/bridgeCommunityGraph.vue"
import DetailCommunityGraph from "./graph/detailCommunityGraph.vue"
const store = useKeyNodeStore2()
const leaderGraphRef = ref(null)
// 添加对BridgeCommunityGraph的引用
const bridgeGraphRef = ref(null)
// 设置当前激活的组件--默认是BridgeCommunityGraph
const currentComponent = ref("BridgeCommunityNode")
const currentSelectedCommunityId = ref(null)
const handleGraphNodeClick = (leaderData) => {
store.openLeaderDetail(leaderData)
}
const handleClickCommunity = (communityId) => {
if (communityId) {
currentComponent.value = "DetailCommunityNode"
currentSelectedCommunityId.value = communityId
}
}
const handleClickGoBack = (currentComponentName) => {
currentComponent.value = currentComponentName
}
const highlightNode = (leaderId) => {
if (leaderGraphRef.value) {
leaderGraphRef.value.highlightNode(leaderId)
}
// 调用BridgeCommunityGraph的highlightNode方法
if (bridgeGraphRef.value) {
bridgeGraphRef.value.highlightNode(leaderId)
}
}
// 计算时间点在时间轴上的位置百分比
const calculatePositions = (timestamp) => {
// 时间范围: 2023-10-07T00:00:00 到 2023-10-15T00:00:00
const startTime = new Date("2023-10-07T00:00:00").getTime()
const endTime = new Date("2023-10-15T00:00:00").getTime()
const timeRange = endTime - startTime
// 复制并排序时间点数据
const sortedPoints = [...store.timePoints].sort((a, b) => {
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
})
// 计算每个点的原始位置
const pointPositions = {}
sortedPoints.forEach((point) => {
const pointTime = new Date(point.timestamp).getTime()
const position = ((pointTime - startTime) / timeRange) * 100
pointPositions[point.id] = Math.max(0, Math.min(100, position))
})
// 处理重叠 (假设每个点宽度约为20px时间轴总宽度为可用宽度)
const pointWidthPercentage = 5 // 估算的点宽度百分比
const minSpacing = pointWidthPercentage // 最小间距百分比
// 调整重叠的点
for (let i = 1; i < sortedPoints.length; i++) {
const prevPoint = sortedPoints[i - 1]
const currPoint = sortedPoints[i]
const prevPosition = pointPositions[prevPoint.id]
const currPosition = pointPositions[currPoint.id]
// 检查是否重叠
if (currPosition - prevPosition < minSpacing) {
// 调整当前点位置
pointPositions[currPoint.id] = prevPosition + minSpacing
// 确保不超出范围
if (pointPositions[currPoint.id] > 100) {
pointPositions[currPoint.id] = 100
}
}
}
return pointPositions
}
// 响应式引用存储位置信息
const pointPositions = ref({})
// 监听时间点变化,重新计算位置
watch(
() => store.timePoints,
() => {
pointPositions.value = calculatePositions()
},
{ immediate: true, deep: true }
)
// 计算时间轴轨道样式
const trackStyle = computed(() => {
if (!store.activeTimePoint) return {}
const activePosition = pointPositions.value[store.activeTimePoint] || 0
return {
background: `linear-gradient(90deg, #3B7699 0%, #00F3FF ${activePosition}%, #3B7699 ${activePosition}%, #3B7699 100%)`
}
})
defineExpose({ highlightNode })
// 添加时间点点击事件处理函数
const handleTimePointClick = (pointId) => {
// 停止自动播放
props.stopAutomaticPlay()
// 设置当前激活的时间点
store.setActiveTimePoint(pointId)
}
</script>
<style scoped lang="scss">
.right-panel {
width: 100%;
height: 100%;
position: relative;
}
.head {
margin: 0 auto;
}
.key-node-recognition {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.background-svg-wrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.content-wrapper {
width: 100%;
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
height: 100%;
box-sizing: border-box;
.graph-img-title {
display: absolute;
top: 0;
margin: 0 auto;
}
}
.chart-container {
width: 100%;
height: 100%;
margin-top: 35px;
}
.timeline-container {
width: 95%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 10px;
box-sizing: border-box;
position: absolute;
z-index: 1;
bottom: 15px;
left: 20px;
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);
}
.time-label {
font-size: 12px;
color: #a9c2e0;
}
.timeline-track {
flex-grow: 1;
height: 4px;
margin: 0 15px;
position: relative;
display: flex;
align-items: center;
}
.timeline-point-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 20px;
cursor: pointer;
position: absolute;
transform: translateX(-50%);
}
.timeline-point {
width: 18px;
height: 18px;
background-color: transparent;
border-radius: 50%;
border: 1.6px solid #ffe5a4;
transition: transform 0.3s ease;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.timeline-point::after {
content: "";
width: 10px;
height: 10px;
background-color: #f9bd25;
border-radius: 50%;
position: absolute;
}
.timeline-point-wrapper:hover .timeline-point {
transform: scale(1.5);
}
.timeline-point.active {
background-color: transparent;
border-color: #ffe5a4;
transform: scale(1.3);
}
.active-pin {
width: 30px;
height: 34px;
background-image: url("@/assets/images/point.png");
background-size: cover;
bottom: 6px;
position: absolute;
}
</style>