This commit is contained in:
duanhao 2025-07-29 16:25:40 +08:00
commit 46d5edfa63
9 changed files with 85 additions and 112 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

View File

@ -30,8 +30,12 @@ export function getGroupUserListFromSocial() {
} }
//人物互动隐关系预测的贴文列表 //人物互动隐关系预测的贴文列表
export function getInteractionPostList(outoIncrement) { // export function getInteractionPostList(outoIncrement) {
return http.get(`/linkPrediction/interaction/post_list?page=${outoIncrement}`) // return http.get(`/linkPrediction/interaction/post_list?page=${outoIncrement}`)
// }
export function getInteractionPostList(userGroupId) {
return http.get(`/linkPrediction/user_posts_list?relationId=${userGroupId}`)
} }
//社交紧密团体识别的贴文列表 //社交紧密团体识别的贴文列表

View File

@ -164,14 +164,10 @@ export const useCharacterInteractionStore = defineStore("characterInteraction",
initGroupCorrelationForChart() { initGroupCorrelationForChart() {
this.userChartList = this.userList this.userChartList = this.userList
}, },
async initInteractionPostList(autoIncrement) { async initInteractionPostList(userGroupId) {
const res = await getInteractionPostList(autoIncrement) const res = await getInteractionPostList(userGroupId)
if (res.code != 200) return if (res.code != 200) return
if (this.posts.length != 0) {
this.posts.push(...res.data)
} else {
this.posts = res.data this.posts = res.data
}
}, },
async initGraphCommunityNode() { async initGraphCommunityNode() {
const res = await getInteractionCommunityNodes() const res = await getInteractionCommunityNodes()

View File

@ -17,11 +17,7 @@
<Graph :title="graphTitleImg"></Graph> <Graph :title="graphTitleImg"></Graph>
</div> </div>
<div class="postList"> <div class="postList">
<PostList <PostList :posts="interactionStore.posts"></PostList>
:posts="interactionStore.posts"
@click:openDialog="handleOpenPostDialog"
@scroll:touchButtom="handleTouchButtom"
></PostList>
</div> </div>
</div> </div>
<div class="right-container"> <div class="right-container">
@ -36,34 +32,11 @@
</div> </div>
</div> </div>
</div> </div>
<el-dialog v-model="postDialog" width="640" align-center class="custom-dialog">
<img src="@/assets/images/head/post-dialog-title.png" alt="" class="postTitleImage" />
<div class="dialog-content">
<div class="post-content">{{ currentPostPost.content }}</div>
<div class="heat">
<div class="item-heat-detail">
<div class="item-heat-like">
<Icon icon="ei:like" width="25" height="25" /> {{ currentPostPost.like }}
</div>
<div class="item-heat-comment">
<Icon icon="la:comment-dots" width="25" height="25" /> {{ currentPostPost.comment }}
</div>
<div class="item-heat-transmit">
<Icon icon="mdi:share-outline" width="25" height="25" /> {{
currentPostPost.transmit
}}
</div>
</div>
</div>
</div>
</el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue" import { onMounted } from "vue"
import { Icon } from "@iconify/vue"
import { provide } from "vue" import { provide } from "vue"
import { useCharacterInteractionStore } from "@/store/llinkPrediction/index" import { useCharacterInteractionStore } from "@/store/llinkPrediction/index"
import UserPanel from "../components/userPanel.vue" import UserPanel from "../components/userPanel.vue"
@ -76,31 +49,16 @@ import graphTitleImg from "@/assets/images/linkPrediction/title/graph1-title.png
import analysisTitleImg from "@/assets/images/linkPrediction/title/analysis-title.png" import analysisTitleImg from "@/assets/images/linkPrediction/title/analysis-title.png"
const interactionStore = useCharacterInteractionStore() const interactionStore = useCharacterInteractionStore()
// //
const postDialog = ref(false)
//
const currentPostPost = ref(null)
const handleSelectedUserGroup = (group) => { const handleSelectedUserGroup = (group) => {
interactionStore.curComponent = "detailNode" interactionStore.initInteractionPostList(group.relationId)
console.log(group)
}
const handleOpenPostDialog = (post) => {
postDialog.value = true
currentPostPost.value = post
}
const handleTouchButtom = (outoIncrement) => {
interactionStore.initInteractionPostList(outoIncrement)
} }
onMounted(() => { onMounted(() => {
interactionStore.initGroupList() interactionStore.initGroupList() //
interactionStore.initInteractionPostList(1) interactionStore.initGraphCommunityNode() //
interactionStore.initGraphCommunityNode() interactionStore.initGraphStatistics() //
interactionStore.initGraphStatistics() interactionStore.initInteractionPostList("106888") //
}) })
provide("communityNodeList", interactionStore.communityNodeList) // provide("communityNodeList", interactionStore.communityNodeList) //
provide("statisticsList", interactionStore.statisticsList) provide("statisticsList", interactionStore.statisticsList)

View File

@ -60,9 +60,13 @@ const initChart = async () => {
const data = { nodes, links } const data = { nodes, links }
const categories = [ const categories = [
{ name: "普通社团", category: 0 }, { name: "普通社团", category: 0, icon: "circle" },
{ name: "含预测节点社团", category: 1 }, { name: "含预测节点社团", category: 1, icon: "circle" },
{ name: "互动隐关系", category: 2 } {
name: "互动隐关系",
category: 2,
icon: `image://${new URL("@/assets/images/linkPrediction/icon/hidden-icon.png", import.meta.url)}`
}
] ]
const option = { const option = {
// //
@ -78,18 +82,13 @@ const initChart = async () => {
{ offset: 0.5, color: "#38546b" }, { offset: 0.5, color: "#38546b" },
{ offset: 0, color: "#5fb3b3" } { offset: 0, color: "#5fb3b3" }
]) ])
: c.category === 1 : new echarts.graphic.LinearGradient(1, 0, 0, 0, [
? new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: "#9eec9c" }, // 绿 { offset: 0, color: "#9eec9c" }, // 绿
{ offset: 0.37, color: "#aef295" }, { offset: 0.37, color: "#aef295" },
{ offset: 1, color: "#c2f989" } { offset: 1, color: "#c2f989" }
]) ])
: new echarts.graphic.LinearGradient(1, 0, 0, 0, [ },
{ offset: 0, color: "#ff9a9e" }, // category === 2 icon: c.icon
{ offset: 0.5, color: "#fad0c4" },
{ offset: 1, color: "#fbc2eb" }
])
}
})), })),
right: 15, right: 15,
bottom: 10, bottom: 10,

View File

@ -19,7 +19,13 @@
<div class="progress-bar" :style="trackStyle"></div> <div class="progress-bar" :style="trackStyle"></div>
<div class="active-sign" :style="{ left: `${currentPosition}px` }"> <div class="active-sign" :style="{ left: `${currentPosition}px` }">
<div class="active-needle"></div> <div class="active-needle"></div>
<el-tooltip
:content="TansTimestamp(currentTime, 'YYYY.MM.DD HH:mm:ss')"
placement="bottom"
effect="light"
>
<div class="timeLine-point" @pointerdown.stop="handlePointPointerDown"></div> <div class="timeLine-point" @pointerdown.stop="handlePointPointerDown"></div>
</el-tooltip>
</div> </div>
</div> </div>
<div class="time">{{ TansTimestamp(endTime, "YYYY.MM.DD HH:mm:ss") }}</div> <div class="time">{{ TansTimestamp(endTime, "YYYY.MM.DD HH:mm:ss") }}</div>
@ -110,8 +116,8 @@ const handlePointPointerDown = (e) => {
const handlePointerUp = () => { const handlePointerUp = () => {
isDragging.value = false isDragging.value = false
// //
const currentTimes = TansTimestamp(currentTime.value, "YYYY.MM.DD HH:mm:ss") const currentTimes = TansTimestamp(currentTime.value, "YYYY-MM-DD HH:mm:ss")
console.log("拖动结束,当前时间:", currentTimes) interactionStore.initGraphCommunityDetailNode(interactionStore.curSelecedGroupIds, currentTimes)
document.removeEventListener("pointermove", handlePointerMove) document.removeEventListener("pointermove", handlePointerMove)
document.removeEventListener("pointerup", handlePointerUp) document.removeEventListener("pointerup", handlePointerUp)
@ -136,11 +142,19 @@ onUnmounted(() => {
}) })
let chart = null let chart = null
const initChart = async () => { const initChart = async () => {
chart = echarts.init(document.getElementById("container")) chart = echarts.init(document.getElementById("container"))
const links = [] const links = []
const nodes = [] const nodes = []
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
}
if (!Object.keys(interactionStore.communityDetailNodeList).length) return if (!Object.keys(interactionStore.communityDetailNodeList).length) return
Object.entries(interactionStore.communityDetailNodeList).forEach(([parentId, children]) => { Object.entries(interactionStore.communityDetailNodeList).forEach(([parentId, children]) => {
nodes.push({ nodes.push({
@ -155,17 +169,29 @@ const initChart = async () => {
source: `parent_${parentId}`, source: `parent_${parentId}`,
target: child.id, target: child.id,
edge: child.isHidden ? 1 : 0, edge: child.isHidden ? 1 : 0,
interactionTimes: child.interactionTime interactionTimes: child.interactionTime,
lineStyle: {
width: child.isHidden ? 4 : edgeWidth(child.interactionTime),
color: child.isHidden ? "#f8bf38" : "#37ACD7", // ==
type: child.isHidden ? "dashed" : "solid" // =线=线
}
}) })
}) })
}) })
const data = { links, nodes } const data = { links, nodes }
const categories = [ const categories = [
{ name: "事件活跃者", category: 0 }, { name: "事件活跃者", category: 0, icon: "circle" },
{ name: "信息发布者", category: 1 }, {
{ name: "互动关系", category: 2 }, name: "互动关系",
{ name: "互动隐关系", category: 3 } 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)}`
}
] ]
const option = { const option = {
// //
@ -192,11 +218,12 @@ const initChart = async () => {
{ offset: 0.5, color: "#fad0c4" }, { offset: 0.5, color: "#fad0c4" },
{ offset: 1, color: "#fbc2eb" } { offset: 1, color: "#fbc2eb" }
]) ])
} },
icon: c.icon
})), })),
right: 21, right: 21,
symbolKeepAspect: false,
bottom: 70, bottom: 70,
icon: "circle",
orient: "vertical", orient: "vertical",
itemWidth: 16, itemWidth: 16,
itemHeight: 16, itemHeight: 16,
@ -248,7 +275,7 @@ const initChart = async () => {
show: false, show: false,
position: "middle", position: "middle",
formatter: function (params) { formatter: function (params) {
return params.data.edge return params.data.interactionTimes
}, },
fontSize: 14 fontSize: 14
}, },
@ -270,7 +297,7 @@ const initChart = async () => {
animation: false, animation: false,
draggable: true, draggable: true,
roam: true, roam: true,
zoom: 0.3, zoom: 0.15,
categories: categories, categories: categories,
force: { force: {
edgeLength: 2500, edgeLength: 2500,
@ -321,6 +348,12 @@ const initChart = async () => {
onMounted(() => { onMounted(() => {
initChart() initChart()
chart.on("legendselectchanged", function (params) {
//
setTimeout(() => {
chart.resize()
}, 0)
})
}) })
</script> </script>
@ -432,7 +465,7 @@ onMounted(() => {
background-image: url("@/assets/images/point.png"); background-image: url("@/assets/images/point.png");
background-size: cover; background-size: cover;
bottom: 1px; bottom: 1px;
left: -6px; left: -11px;
position: absolute; position: absolute;
} }
.timeLine-point { .timeLine-point {
@ -446,6 +479,7 @@ onMounted(() => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
top: -6px; top: -6px;
left: -5px;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
will-change: left; will-change: left;

View File

@ -5,7 +5,7 @@
alt="" alt=""
style="margin-top: -17px; margin-left: -11px" style="margin-top: -17px; margin-left: -11px"
/> />
<div class="post-list-wrapper" ref="listRef" @scroll="handleScroll"> <div class="post-list-wrapper" ref="listRef">
<div class="scrolling-content"> <div class="scrolling-content">
<div <div
class="post-item" class="post-item"
@ -15,7 +15,7 @@
@click="handleLeaderPost(post)" @click="handleLeaderPost(post)"
> >
<img src="@/assets/images/linkPrediction/icon/post-prefix.png" alt="" class="prefix" /> <img src="@/assets/images/linkPrediction/icon/post-prefix.png" alt="" class="prefix" />
<div class="timestamp">{{ TansTimestamp(post.time) }}</div> <div class="timestamp">{{ TansTimestamp(post.interactionTime) }}</div>
<div class="behavior"> <div class="behavior">
{{ post.userName }}{{ post.behavior }} {{ post.neighborName }}的贴文 {{ post.userName }}{{ post.behavior }} {{ post.neighborName }}的贴文
</div> </div>
@ -26,36 +26,18 @@
</template> </template>
<script setup> <script setup>
import { ref, defineProps, defineEmits, onMounted, onBeforeUnmount } from "vue"; import { ref, defineProps, defineEmits, onMounted } from "vue"
import { TansTimestamp } from "@/utils/transform"; import { TansTimestamp } from "@/utils/transform"
const props = defineProps({ const props = defineProps({
posts: { posts: {
type: Array, type: Array,
default: () => [] default: () => []
} }
}); })
const listRef = ref(null)
const emit = defineEmits(["click:openDialog", "scroll:touchButtom"]); onMounted(() => {})
const page = ref(0);
const listRef = ref(null);
const handleScroll = () => {
const el = listRef.value;
if (!el) return;
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 1) {
emit("scroll:touchButtom", page.value++);
}
};
const handleLeaderPost = (item) => {
emit("click:openDialog", item);
console.log(item);
};
onMounted(() => {
handleScroll();
});
</script> </script>
<style scoped lang="less"> <style scoped lang="less">