配置前端工程化,模块化,组件化,调整项目目录,文件名等

This commit is contained in:
qumeng039@126.com 2025-08-12 11:50:27 +08:00
parent 4735723452
commit 7f5dac177f
35 changed files with 548 additions and 427 deletions

View File

@ -1,139 +1,11 @@
<template>
<div class="app-container">
<el-container>
<el-header>
<div class="header-logo"></div>
</el-header>
<el-container>
<el-aside>
<nav class="menu">
<!-- 手动生成第一个菜单项 -->
<div v-for="item in menuItems" :key="item.index" class="el-sub-menu">
<div class="menu-title" :class="{ 'active-parent': hasActiveChild(item) }">
<!-- 激活状态的左侧青色图片 -->
<img
v-if="hasActiveChild(item)"
src="./assets/images/titleActiveLeftRec.png"
alt=""
class="title-active-left"
/>
<div class="tltlemenu-left">
<img
src="./assets/images/titlelogo.png"
style="width: 20px; height: 20px; margin-right: 12px"
/>
{{ item.title }}
</div>
<img
src="./assets/images/titleright.png"
alt=""
style="width: 66px; height: 16px"
/>
</div>
<ul class="menu-items">
<div
v-for="child in item.subItems"
:key="child.index"
style="text-decoration: none"
>
<li
:key="child.index"
class="el-menu-item"
@click="jumpPage(child.index)"
:class="{ active: isActive(child.index) }"
>
<div>
{{ child.title }}
</div>
</li>
</div>
</ul>
</div>
</nav>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
<el-dialog v-model="openDialog" width="500" align-center class="custom-dialog-prepare">
<div class="center-tips">
<img src="./assets/images/icon/pre-icon.png" alt="" class="pre-icon" />
<div class="tips-font">该案例正在开发中</div>
</div>
</el-dialog>
<router-view />
</div>
</template>
<script setup>
import { onMounted, ref, onUnmounted } from "vue"
import { useRoute, useRouter } from "vue-router"
const router = useRouter()
const route = useRoute()
const menuItems = [
{
index: "1",
title: "关键节点识别",
subItems: [
{ index: "/key-node-1", title: "重大舆情事件锚点推荐" },
{ index: "/key-node-2", title: "传播意见领袖识别" },
{ index: "/key-node-3", title: "传播桥梁节点识别" }
]
},
{
index: "2",
title: "链路预测",
subItems: [
{ index: "/link-prediction-1", title: "人物互动隐关系预测" },
{ index: "/link-prediction-2", title: "社交紧密团体识别" },
{ index: "/link-prediction-3", title: "人物社交隐关系预测" }
]
},
{
index: "3",
title: "群体演化分析",
subItems: [
{ index: "/group-evolution-1", title: "群体识别发现" },
{ index: "/group-evolution-2", title: "群体结构演化分析" },
{ index: "/group-evolution-3", title: "群体成员演化分析" },
{ index: "/group-evolution-4", title: "异常群体捕捉" }
]
}
]
const openDialog = ref(false)
//
const isActive = (routePath) => {
return route.path === routePath
}
//
const hasActiveChild = (item) => {
return item.subItems.some((child) => isActive(child.index))
}
//
const jumpPage = (routePath) => {
const activedPaths = [
"/key-node-1",
"/key-node-2",
"/key-node-3",
"/link-prediction-1",
"/link-prediction-2",
"/link-prediction-3",
"/group-evolution-1",
"/group-evolution-2",
"/group-evolution-3",
"/group-evolution-4"
]
if (activedPaths.includes(routePath)) {
router.push({ path: routePath })
return
}
openDialog.value = true
}
import { onMounted, onUnmounted } from "vue"
const disableZoom = () => {
// 100%
const resetZoom = () => {
@ -182,139 +54,10 @@ onMounted(() => {
.app-container {
width: 100vw;
height: 111vh;
background-image: url("./assets/images/bci.png");
background-image: url("@/assets/images/bci.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-color: #02131f;
}
:deep(.custom-dialog-prepare) {
width: 480px;
height: 250px;
background: url("./assets/images/preparation.png") no-repeat center;
display: flex;
align-items: center;
justify-content: center;
}
:deep(.custom-dialog-prepare) .pre-icon {
width: 30px;
height: 30px;
margin-right: 10px;
}
:deep(.custom-dialog-prepare) .center-tips {
display: flex;
align-items: center;
}
:deep(.custom-dialog-prepare) .center-tips .tips-font {
font-size: 24px;
color: #fff;
opacity: 0.7;
}
.el-header {
width: 100%;
height: 115px;
padding: 0;
.header-logo {
width: 100%;
height: 100%;
background-image: url("./assets/images/head.png");
background-size: cover;
background-repeat: no-repeat;
}
}
.menu {
width: 320px;
height: 100%;
border-width: 2px;
border-style: solid;
border-image: linear-gradient(to bottom, #3aa1f8, #3aa1f833) 1;
background-image: linear-gradient(to right, #063d7133, #081e38cc);
.menu-title {
cursor: pointer;
padding: 10px;
width: 288px;
border-radius: 2px;
margin-top: 16px;
margin-left: 16px;
background-image: linear-gradient(270deg, rgba(6, 61, 113, 0.2) 0%, rgba(8, 30, 56, 0.8) 100%);
/* border-image: linear-gradient(to right, #225f9200, #3aa1f8) 1; */
border: 2px solid;
border-image-source: linear-gradient(90deg, #3aa1f8 0%, rgba(58, 161, 248, 0.2) 100%);
border-image-slice: 1;
display: flex;
align-items: center;
justify-content: space-between;
&:hover {
border-radius: 2px;
background: linear-gradient(270deg, rgba(14, 167, 213, 0) 0%, rgba(8, 118, 190, 0.24) 100%);
}
}
.menu-items {
padding: 0 15px;
}
}
.menu-title.active-parent {
/* 设置背景图片 */
background-image:
url("./assets/images/titleActiveMaskGroup.png"), Linear-gradient(to right, #0876be, #0ea7d500);
background-repeat: no-repeat;
background-size: contain;
position: relative;
border: none;
padding-left: 30px; /* 为左侧图片腾出空间 */
}
.title-active-left {
position: absolute;
left: 0;
top: 8%;
width: 3px; /* 根据实际图片大小调整 */
height: 88%;
z-index: 1;
}
.el-menu-item {
width: 100%;
height: 36px;
margin-top: 12px;
line-height: 36px;
cursor: pointer;
color: #fff;
padding-left: 40px;
border-radius: 2px;
}
.el-menu-item.active {
background-image: linear-gradient(270deg, rgba(14, 167, 213, 0) 0%, rgba(8, 118, 190, 0.24) 100%);
border-radius: 2px;
}
.tltlemenu-left {
display: flex;
align-items: center;
}
.sub-menu-items {
list-style-type: none;
padding: 0;
margin: 0;
margin-top: 5px;
}
.el-menu-item:hover {
background-image: linear-gradient(to right, #0876be, #0ea7d500);
}
.el-main {
padding: 0 0;
margin-left: 30px;
}
.el-aside {
overflow: visible;
color: aliceblue;
font-family: Arial, sans-serif;
margin-left: 20px;
}
</style>

View File

@ -0,0 +1,208 @@
<template>
<div class="aside-component">
<nav class="menu">
<!-- 手动生成第一个菜单项 -->
<div v-for="item in menuItems" :key="item.index" class="el-sub-menu">
<div class="menu-title" :class="{ 'active-parent': hasActiveChild(item) }">
<!-- 激活状态的左侧青色图片 -->
<img
v-if="hasActiveChild(item)"
src="@/assets/images/titleActiveLeftRec.png"
alt=""
class="title-active-left"
/>
<div class="tltlemenu-left">
<img
src="@/assets/images/titlelogo.png"
style="width: 20px; height: 20px; margin-right: 12px"
/>
{{ item.title }}
</div>
<img src="@/assets/images/titleright.png" alt="" style="width: 66px; height: 16px" />
</div>
<ul class="menu-items">
<div v-for="child in item.subItems" :key="child.index" style="text-decoration: none">
<li
:key="child.index"
class="el-menu-item"
@click="jumpPage(child.index)"
:class="{ active: isActive(child.index) }"
>
<div>
{{ child.title }}
</div>
</li>
</div>
</ul>
</div>
</nav>
</div>
</template>
<script setup>
import { onMounted, defineEmits } from "vue"
import { useRoute, useRouter } from "vue-router"
const router = useRouter()
const route = useRoute()
const emit = defineEmits(["update:openDialog"])
const menuItems = [
{
index: "1",
title: "关键节点识别",
subItems: [
{ index: "/key-node-1", title: "重大舆情事件锚点推荐" },
{ index: "/key-node-2", title: "传播意见领袖识别" },
{ index: "/key-node-3", title: "传播桥梁节点识别" }
]
},
{
index: "2",
title: "链路预测",
subItems: [
{ index: "/link-prediction-1", title: "人物互动隐关系预测" },
{ index: "/link-prediction-2", title: "社交紧密团体识别" },
{ index: "/link-prediction-3", title: "人物社交隐关系预测" }
]
},
{
index: "3",
title: "群体演化分析",
subItems: [
{ index: "/group-evolution-1", title: "群体识别发现" },
{ index: "/group-evolution-2", title: "群体结构演化分析" },
{ index: "/group-evolution-3", title: "群体成员演化分析" },
{ index: "/group-evolution-4", title: "异常群体捕捉" }
]
}
]
//
const isActive = (routePath) => {
return route.path === routePath
}
//
const hasActiveChild = (item) => {
return item.subItems.some((child) => isActive(child.index))
}
//
const jumpPage = (routePath) => {
const activedPaths = [
"/key-node-1",
"/key-node-2",
"/key-node-3",
"/link-prediction-1",
"/link-prediction-2",
"/link-prediction-3",
"/group-evolution-1",
"/group-evolution-2",
"/group-evolution-3",
"/group-evolution-4"
]
if (activedPaths.includes(routePath)) {
router.push({ path: routePath })
return
}
emit("update:openDialog", true)
}
onMounted(() => {
jumpPage("/key-node-1")
})
</script>
<style lang="less">
.aside-component {
width: 100%;
height: 100%;
.menu {
width: 320px;
height: 100%;
border-width: 2px;
border-style: solid;
border-image: linear-gradient(to bottom, #3aa1f8, #3aa1f833) 1;
background-image: linear-gradient(to right, #063d7133, #081e38cc);
.menu-title {
cursor: pointer;
padding: 10px;
width: 288px;
border-radius: 2px;
margin-top: 16px;
margin-left: 16px;
background-image: linear-gradient(
270deg,
rgba(6, 61, 113, 0.2) 0%,
rgba(8, 30, 56, 0.8) 100%
);
/* border-image: linear-gradient(to right, #225f9200, #3aa1f8) 1; */
border: 2px solid;
border-image-source: linear-gradient(90deg, #3aa1f8 0%, rgba(58, 161, 248, 0.2) 100%);
border-image-slice: 1;
display: flex;
align-items: center;
justify-content: space-between;
&:hover {
border-radius: 2px;
background: linear-gradient(270deg, rgba(14, 167, 213, 0) 0%, rgba(8, 118, 190, 0.24) 100%);
}
}
.menu-items {
padding: 0 15px;
}
}
}
.menu-title.active-parent {
/* 设置背景图片 */
background-image:
url("@/assets/images/titleActiveMaskGroup.png"), Linear-gradient(to right, #0876be, #0ea7d500);
background-repeat: no-repeat;
background-size: contain;
position: relative;
border: none;
padding-left: 30px; /* 为左侧图片腾出空间 */
}
.title-active-left {
position: absolute;
left: 0;
top: 8%;
width: 3px; /* 根据实际图片大小调整 */
height: 88%;
z-index: 1;
}
.el-menu-item {
width: 100%;
height: 36px;
margin-top: 12px;
line-height: 36px;
cursor: pointer;
color: #fff;
padding-left: 40px;
border-radius: 2px;
}
.el-menu-item.active {
background-image: linear-gradient(270deg, rgba(14, 167, 213, 0) 0%, rgba(8, 118, 190, 0.24) 100%);
border-radius: 2px;
}
.tltlemenu-left {
display: flex;
align-items: center;
}
.sub-menu-items {
list-style-type: none;
padding: 0;
margin: 0;
margin-top: 5px;
}
.el-menu-item:hover {
background-image: linear-gradient(to right, #0876be, #0ea7d500);
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div class="dialog-component">
<el-dialog v-model="openDialog" width="500" align-center class="custom-dialog-prepare">
<div class="center-tips">
<img src="@/assets/images/icon/pre-icon.png" class="pre-icon" />
<div class="tips-font">该案例正在开发中</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, defineProps } from "vue"
const props = defineProps({
openDialog: {
type: Boolean,
required: true
}
})
const openDialog = ref(props.openDialog)
</script>
<style lang="less">
:deep(.custom-dialog-prepare) {
width: 480px;
height: 250px;
background: url("@/assets/images/preparation.png") no-repeat center;
display: flex;
align-items: center;
justify-content: center;
}
:deep(.custom-dialog-prepare) .pre-icon {
width: 30px;
height: 30px;
margin-right: 10px;
}
:deep(.custom-dialog-prepare) .center-tips {
display: flex;
align-items: center;
}
:deep(.custom-dialog-prepare) .center-tips .tips-font {
font-size: 24px;
color: #fff;
opacity: 0.7;
}
</style>

View File

@ -0,0 +1,15 @@
<template>
<div class="header-logo"></div>
</template>
<script setup></script>
<style lang="less">
.header-logo {
width: 100%;
height: 100%;
background-image: url("@/assets/images/head.png");
background-size: cover;
background-repeat: no-repeat;
}
</style>

50
src/layout/index.vue Normal file
View File

@ -0,0 +1,50 @@
<template>
<div class="home-container">
<el-container>
<el-header>
<QHeader></QHeader>
</el-header>
<el-container>
<el-aside>
<QAside @update:open-dialog="handleOpenDialog"></QAside>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
<QDialog :openDialog="openDialog"></QDialog>
</div>
</template>
<script setup>
import { ref } from "vue"
import QAside from "./components/aside/index.vue"
import QHeader from "./components/header/index.vue"
import QDialog from "./components/dialog/index.vue"
const openDialog = ref(false)
const handleOpenDialog = (isOpen) => {
openDialog.value = isOpen
}
</script>
<style lang="less">
.el-header {
width: 100%;
height: 115px;
padding: 0;
}
.el-main {
padding: 0 0;
margin-left: 30px;
}
.el-aside {
height: 96.6vh;
overflow: visible;
color: aliceblue;
font-family: Arial, sans-serif;
margin-left: 20px;
}
</style>

View File

@ -1,35 +1,59 @@
import { createRouter, createWebHistory } from "vue-router";
// 导入组件,这里先占位
const KeyNodeRecognition2 = () => import("@/views/KeyNodeRecognition1/index.vue");
const KeyNodeRecognition3 = () => import("@/views/KeyNodeRecognition2/index.vue");
const KeyNodeRecognition1 = () => import("@/views/KeyNodeRecognition3/index.vue");
const LinkPrediction1 = () => import("@/views/LinkPrediction/characterInteraction/index.vue");
const LinkPrediction2 = () => import("@/views/LinkPrediction/socialGroups/index.vue");
const LinkPrediction3 = () =>
import("@/views/LinkPrediction/charactersHiddenInteraction/index.vue");
const GroupEvolution1 = () => import("@/views/GroupEvolution/groupIdentifyDiscovery/index.vue");
const GroupEvolution2 = () => import("@/views/GroupEvolution/groupStructure/index.vue");
const GroupEvolution3 = () => import("@/views/GroupEvolution/groupMember/index.vue");
const GroupEvolution4 = () => import("@/views/GroupEvolution/abnormalGroup/index.vue");
import { createRouter, createWebHistory } from "vue-router"
const routes = [
{ path: "/", redirect: "/key-node-1" },
{ path: "/key-node-3", component: KeyNodeRecognition3 },
{ path: "/key-node-2", component: KeyNodeRecognition2 },
{ path: "/key-node-1", component: KeyNodeRecognition1 },
{ path: "/link-prediction-1", component: LinkPrediction1 },
{ path: "/link-prediction-2", component: LinkPrediction2 },
{ path: "/link-prediction-3", component: LinkPrediction3 },
{ path: "/group-evolution-1", component: GroupEvolution1 },
{ path: "/group-evolution-2", component: GroupEvolution2 },
{ path: "/group-evolution-3", component: GroupEvolution3 },
{ path: "/group-evolution-4", component: GroupEvolution4 }
];
{ path: "/", redirect: "/home" },
{
path: "/home",
component: () => import("@/layout/index.vue"),
children: [
{ path: "/", redirect: "/key-node-1" },
{
path: "/key-node-1",
component: () => import("@/views/KeyNodeDiscern/anchorRecommendation/index.vue")
},
{
path: "/key-node-2",
component: () => import("@/views/KeyNodeDiscern/opinionLeader/index.vue")
},
{
path: "/key-node-3",
component: () => import("@/views/KeyNodeDiscern/bridgeCommunication/index.vue")
},
{
path: "/link-prediction-1",
component: () => import("@/views/LinkPrediction/characterInteraction/index.vue")
},
{
path: "/link-prediction-2",
component: () => import("@/views/LinkPrediction/socialGroups/index.vue")
},
{
path: "/link-prediction-3",
component: () => import("@/views/LinkPrediction/charactersHiddenInteraction/index.vue")
},
{
path: "/group-evolution-1",
component: () => import("@/views/GroupEvolution/groupIdentifyDiscovery/index.vue")
},
{
path: "/group-evolution-2",
component: () => import("@/views/GroupEvolution/groupStructure/index.vue")
},
{
path: "/group-evolution-3",
component: () => import("@/views/GroupEvolution/groupMember/index.vue")
},
{
path: "/group-evolution-4",
component: () => import("@/views/GroupEvolution/abnormalGroup/index.vue")
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes
});
})
export default router;
export default router

View File

@ -53,7 +53,7 @@ const defaultConfig = {
borderWidth: 5,
borderColor: "100,250,100",
showShadow: true, //
shadowBlur: 5, //
shadowBlur: 10, //
shadowColor: "50,250,30" //
}
},
@ -137,6 +137,8 @@ const highLightAboutNodesOrLinks = (type) => {
const { newNodes, newLinks } = graph.value
if (type == "nodes") {
//
console.log(graphVis.nodes)
graphVis.nodes.forEach((node) =>
newNodes.forEach((newNode) => {
if (node.id === newNode.name) {

View File

@ -48,7 +48,7 @@
<div class="content">
<!-- Title Image: using a placeholder as the local asset is not available -->
<img
src="../../../assets/images/chuanbo-show-title.png"
src="@/assets/images/chuanbo-show-title.png"
class="title-img"
style="margin-top: -22px; margin-left: -24px"
/>
@ -58,7 +58,12 @@
<div v-for="chart in chartData" :key="chart.id" class="chart-section">
<!-- Section Title -->
<div class="section-title">
<img src="../../../assets/images/icon/qiaolianganalysisicon.png" alt="" style="" class="icon"/>
<img
src="@/assets/images/icon/qiaolianganalysisicon.png"
alt=""
style=""
class="icon"
/>
<span>{{ chart.title }}</span>
</div>
@ -83,7 +88,15 @@
:class="row.type"
:style="{ width: (row.value / chart.max) * 100 + '%' }"
></div>
<span class="value" :class="{ highlight: row.highlight, 'value-leader': row.type === 'leader', 'value-user': row.type === 'user' }">{{ row.value }}</span>
<span
class="value"
:class="{
highlight: row.highlight,
'value-leader': row.type === 'leader',
'value-user': row.type === 'user'
}"
>{{ row.value }}</span
>
</div>
</div>
<!-- X-Axis Labels -->
@ -100,7 +113,7 @@
</template>
<script setup>
import { ref } from "vue";
import { ref } from "vue"
const chartData = ref([
{
@ -133,7 +146,7 @@ const chartData = ref([
{ label: "其他节点", value: 120, type: "user" }
]
}
]);
])
</script>
<style scoped>
@ -208,7 +221,7 @@ const chartData = ref([
justify-content: space-around;
padding-right: 15px;
font-size: 14px;
color: #94C1EC;
color: #94c1ec;
text-align: right;
height: 70px;
flex-shrink: 0;
@ -281,11 +294,11 @@ const chartData = ref([
}
.value-leader {
color: #2AB8FD;
color: #2ab8fd;
}
.value-user {
color: #34DFF2;
color: #34dff2;
}
.x-axis-labels {

View File

@ -1,7 +1,7 @@
<template>
<div class="left-panel">
<img
src="../../../assets/images/chuanbo_account_list.png"
src="@/assets/images/chuanbo_account_list.png"
alt=""
class="headerImage"
style="margin-top: -22px; margin-left: -15px"
@ -50,32 +50,36 @@
</template>
<script setup>
import { ref, computed, watch, nextTick, defineEmits } from 'vue';
import { useKeyNodeStore2 } from '@/store/keyNodeStore2';
import { ref, computed, watch, nextTick, defineEmits } from "vue"
import { useKeyNodeStore2 } from "@/store/keyNodeStore2"
const emit = defineEmits(['selectLeader']);
const store = useKeyNodeStore2();
const emit = defineEmits(["selectLeader"])
const store = useKeyNodeStore2()
const leaderListRef = ref(null);
const tabs = ref(["全部", "新闻媒体", "自媒体", "政府官号"]);
const activeTab = ref("全部");
const leaderListRef = ref(null)
const tabs = ref(["全部", "新闻媒体", "自媒体", "政府官号"])
const activeTab = ref("全部")
const filteredVisibleLeaders = computed(() => {
if (activeTab.value === "全部") {
return store.visibleLeaders;
return store.visibleLeaders
}
return store.visibleLeaders.filter((leader) => leader.category === activeTab.value);
});
return store.visibleLeaders.filter((leader) => leader.category === activeTab.value)
})
watch(filteredVisibleLeaders, async () => {
await nextTick();
if (leaderListRef.value) {
leaderListRef.value.scrollTo({
top: leaderListRef.value.scrollHeight,
behavior: "smooth"
});
}
}, { deep: true });
watch(
filteredVisibleLeaders,
async () => {
await nextTick()
if (leaderListRef.value) {
leaderListRef.value.scrollTo({
top: leaderListRef.value.scrollHeight,
behavior: "smooth"
})
}
},
{ deep: true }
)
</script>
<style scoped lang="less">
@ -200,4 +204,4 @@ watch(filteredVisibleLeaders, async () => {
width: 100px; /* 固定宽度确保对齐 */
justify-content: flex-start; /* 内容靠左对齐 */
}
</style>
</style>

View File

@ -39,68 +39,67 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { useKeyNodeStore2 } from "@/store/keyNodeStore2";
import { ref, onMounted, onUnmounted } from "vue"
import { useKeyNodeStore2 } from "@/store/keyNodeStore2"
import LeaderList from "./components/LeaderList.vue";
import GraphPanel from "./components/GraphPanel.vue";
import DetailsModal from "./components/DetailsModal.vue";
import LeaderDetailDialog from "./components/LeaderDetailDialog.vue";
import PostDetailDialog from "./components/PostDetailDialog.vue";
import LeaderList from "./components/LeaderList.vue"
import GraphPanel from "./components/GraphPanel.vue"
import DetailsModal from "./components/DetailsModal.vue"
import LeaderDetailDialog from "./components/LeaderDetailDialog.vue"
import PostDetailDialog from "./components/PostDetailDialog.vue"
import LeaderAnalysis from "./components/LeaderAnalysis.vue";
import EventHeatmap from "../../components/weight/EventHeatmap.vue";
import PostDynamics from "../../components/weight/PostDynamics.vue";
import WordCloud from "../../components/weight/WordCloud.vue";
import LeaderAnalysis from "./components/LeaderAnalysis.vue"
import EventHeatmap from "../../../components/weight/EventHeatmap.vue"
import PostDynamics from "../../../components/weight/PostDynamics.vue"
import WordCloud from "../../../components/weight/WordCloud.vue"
const store = useKeyNodeStore2();
const graphPanelRef = ref(null);
// activeChartTab ---
const activeChartTab = ref('trend'); // 'trend'
const store = useKeyNodeStore2()
const graphPanelRef = ref(null)
// activeChartTab ---
const activeChartTab = ref("trend") // 'trend'
const handleLeaderSelect = (leader) => {
console.log("选中的领袖ID:", leader);
console.log("选中的领袖ID:", leader)
if (graphPanelRef.value && leader.id) {
graphPanelRef.value.highlightNode(leader.id);
graphPanelRef.value.highlightNode(leader.id)
}
};
}
const handleShowDetails = (chartConfig, tab) => {
store.openDetailsModal(chartConfig);
activeChartTab.value = tab;
console.log("图表数据类型:",activeChartTab.value);
};
store.openDetailsModal(chartConfig)
activeChartTab.value = tab
console.log("图表数据类型:", activeChartTab.value)
}
const handleOpenPostDialog = (post) => {
store.openPostDetail(post);
};
store.openPostDetail(post)
}
let timer = null;
let timer = null
const automaticPlay = () => {
let index = 1;
if (timer) clearInterval(timer);
let index = 1
if (timer) clearInterval(timer)
timer = setInterval(() => {
store.setActiveTimePoint(index);
store.setActiveTimePoint(index)
if (index >= store.allLeaderData.length) {
clearInterval(timer);
timer = null;
return;
clearInterval(timer)
timer = null
return
}
index++;
}, 1500);
};
index++
}, 1500)
}
// store.initializeTimePoints()
onMounted(() => {
store.initializeTimePoints();
automaticPlay();
});
store.initializeTimePoints()
automaticPlay()
})
onUnmounted(() => {
if (timer) {
clearInterval(timer);
clearInterval(timer)
}
});
})
const analysisChartData = ref([
{
@ -130,7 +129,7 @@ const analysisChartData = ref([
{ name: "所有用户", value: 0.46 }
]
}
]);
])
const wordCloudData = ref([
{
@ -265,7 +264,7 @@ const wordCloudData = ref([
fontSize: 16,
opacity: 1
}
]);
])
</script>
<style scoped>

View File

@ -59,7 +59,12 @@
<div v-for="chart in chartData" :key="chart.id" class="chart-section">
<!-- Section Title -->
<div class="section-title">
<img src="../../../assets/images/icon/qiaolianganalysisicon.png" alt="" style="" class="icon"/>
<img
src="@/assets/images/icon/qiaolianganalysisicon.png"
alt=""
style=""
class="icon"
/>
<span>{{ chart.title }}</span>
</div>
@ -84,7 +89,15 @@
:class="row.type"
:style="{ width: (row.value / chart.max) * 100 + '%' }"
></div>
<span class="value" :class="{ highlight: row.highlight, 'value-leader': row.type === 'leader', 'value-user': row.type === 'user' }">{{ row.value }}</span>
<span
class="value"
:class="{
highlight: row.highlight,
'value-leader': row.type === 'leader',
'value-user': row.type === 'user'
}"
>{{ row.value }}</span
>
</div>
</div>
<!-- X-Axis Labels -->
@ -101,7 +114,7 @@
</template>
<script setup>
import { ref } from "vue";
import { ref } from "vue"
const chartData = ref([
{
@ -134,7 +147,7 @@ const chartData = ref([
{ label: "所有用户", value: 1.31, type: "user" }
]
}
]);
])
</script>
<style scoped>
@ -281,11 +294,11 @@ const chartData = ref([
}
.value-leader {
color: #2AB8FD;
color: #2ab8fd;
}
.value-user {
color: #34DFF2;
color: #34dff2;
}
.x-axis-labels {

View File

@ -1,7 +1,7 @@
<template>
<div class="left-panel">
<img
src="../../../assets/images/leaderTitle.png"
src="@/assets/images/leaderTitle.png"
alt=""
class="headerImage"
style="margin-top: -22px; margin-left: -15px"
@ -50,32 +50,36 @@
</template>
<script setup>
import { ref, computed, watch, nextTick, defineEmits } from 'vue';
import { useKeyNodeStore1 } from '@/store/keyNodeStore1';
import { ref, computed, watch, nextTick, defineEmits } from "vue"
import { useKeyNodeStore1 } from "@/store/keyNodeStore1"
const emit = defineEmits(['selectLeader']);
const store = useKeyNodeStore1();
const emit = defineEmits(["selectLeader"])
const store = useKeyNodeStore1()
const leaderListRef = ref(null);
const tabs = ref(["全部", "新闻媒体", "自媒体", "政府官号"]);
const activeTab = ref("全部");
const leaderListRef = ref(null)
const tabs = ref(["全部", "新闻媒体", "自媒体", "政府官号"])
const activeTab = ref("全部")
const filteredVisibleLeaders = computed(() => {
if (activeTab.value === "全部") {
return store.visibleLeaders;
return store.visibleLeaders
}
return store.visibleLeaders.filter((leader) => leader.category === activeTab.value);
});
return store.visibleLeaders.filter((leader) => leader.category === activeTab.value)
})
watch(filteredVisibleLeaders, async () => {
await nextTick();
if (leaderListRef.value) {
leaderListRef.value.scrollTo({
top: leaderListRef.value.scrollHeight,
behavior: "smooth"
});
}
}, { deep: true });
watch(
filteredVisibleLeaders,
async () => {
await nextTick()
if (leaderListRef.value) {
leaderListRef.value.scrollTo({
top: leaderListRef.value.scrollHeight,
behavior: "smooth"
})
}
},
{ deep: true }
)
</script>
<style scoped lang="less">
@ -200,4 +204,4 @@ watch(filteredVisibleLeaders, async () => {
width: 100px; /* 固定宽度确保对齐 */
justify-content: flex-start; /* 内容靠左对齐 */
}
</style>
</style>

View File

@ -40,73 +40,73 @@
<WordCloud :wordsCloudList="wordCloudData" />
</div>
<DetailsModal :activeChartTab="activeChartTab"/>
<DetailsModal :activeChartTab="activeChartTab" />
<LeaderDetailDialog />
<PostDetailDialog />
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { useKeyNodeStore1 } from "@/store/keyNodeStore1";
import { ref, onMounted, onUnmounted } from "vue"
import { useKeyNodeStore1 } from "@/store/keyNodeStore1"
import LeaderList from "./components/LeaderList.vue";
import GraphPanel from "./components/GraphPanel.vue";
import DetailsModal from "./components/DetailsModal.vue";
import LeaderDetailDialog from "./components/LeaderDetailDialog.vue";
import PostDetailDialog from "./components/PostDetailDialog.vue";
import LeaderList from "./components/LeaderList.vue"
import GraphPanel from "./components/GraphPanel.vue"
import DetailsModal from "./components/DetailsModal.vue"
import LeaderDetailDialog from "./components/LeaderDetailDialog.vue"
import PostDetailDialog from "./components/PostDetailDialog.vue"
import LeaderAnalysis from "./components/LeaderAnalysis.vue";
import EventHeatmap from "../../components/weight/EventHeatmap.vue";
import PostDynamics from "../../components/weight/PostDynamics.vue";
import WordCloud from "../../components/weight/WordCloud.vue";
import LeaderAnalysis from "./components/LeaderAnalysis.vue"
import EventHeatmap from "../../../components/weight/EventHeatmap.vue"
import PostDynamics from "../../../components/weight/PostDynamics.vue"
import WordCloud from "../../../components/weight/WordCloud.vue"
const store = useKeyNodeStore1();
const graphPanelRef = ref(null);
const store = useKeyNodeStore1()
const graphPanelRef = ref(null)
// activeChartTab ---
const activeChartTab = ref('trend'); // 'trend'
const activeChartTab = ref("trend") // 'trend'
const handleLeaderSelect = (leader) => {
if (graphPanelRef.value && leader.id) {
graphPanelRef.value.highlightNode(leader.id);
graphPanelRef.value.highlightNode(leader.id)
}
};
}
const handleShowDetails = (chartConfig, tab) => {
store.openDetailsModal(chartConfig);
activeChartTab.value = tab;
console.log("图表数据类型:",activeChartTab.value);
};
store.openDetailsModal(chartConfig)
activeChartTab.value = tab
console.log("图表数据类型:", activeChartTab.value)
}
const handleOpenPostDialog = (post) => {
store.openPostDetail(post);
};
store.openPostDetail(post)
}
let timer = null;
let timer = null
const automaticPlay = () => {
let index = 1;
if (timer) clearInterval(timer);
let index = 1
if (timer) clearInterval(timer)
timer = setInterval(() => {
store.setActiveTimePoint(index);
store.setActiveTimePoint(index)
if (index >= store.allLeaderData.length) {
clearInterval(timer);
timer = null;
return;
clearInterval(timer)
timer = null
return
}
index++;
}, 1500);
};
index++
}, 1500)
}
onMounted(() => {
store.initializeTimePoints();
automaticPlay();
});
store.initializeTimePoints()
automaticPlay()
})
onUnmounted(() => {
if (timer) {
clearInterval(timer);
clearInterval(timer)
}
});
})
const analysisChartData = ref([
{
@ -136,7 +136,7 @@ const analysisChartData = ref([
{ name: "所有用户", value: 0.46 }
]
}
]);
])
const wordCloudData = ref([
{
@ -210,7 +210,7 @@ const wordCloudData = ref([
{ text: "领土", top: 57.5, left: 72.5, width: 49, height: 19, fontSize: 12, opacity: 0.6 },
{ text: "原则", top: 77.5, left: 264.5, width: 49, height: 19, fontSize: 12, opacity: 0.7 },
{ text: "台湾", top: 195.5, left: 287.5, width: 49, height: 19, fontSize: 12, opacity: 0.8 }
]);
])
</script>
<style scoped>