mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-27 01:01:51 +08:00
refactor: 重构部分图表为插件形式
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 互动分析视图(群聊专属)
|
||||
* 展示成员间的 @ 互动关系图
|
||||
*/
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { EChartGraph } from '@/components/charts'
|
||||
import type { EChartGraphData } from '@/components/charts'
|
||||
import { loadMentionGraph } from './queries'
|
||||
|
||||
interface TimeFilter {
|
||||
startTs?: number
|
||||
endTs?: number
|
||||
memberId?: number | null
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
sessionId: string
|
||||
timeFilter?: TimeFilter
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// 数据状态
|
||||
const isLoading = ref(true)
|
||||
const graphData = ref<EChartGraphData>({ nodes: [], links: [], maxLinkValue: 0 })
|
||||
|
||||
// 布局切换
|
||||
const layoutType = ref<'circular' | 'force'>('circular')
|
||||
|
||||
// 方向切换(有向图 vs 无向图)
|
||||
const showDirection = ref(false)
|
||||
|
||||
// 图表引用
|
||||
const graphRef = ref<InstanceType<typeof EChartGraph> | null>(null)
|
||||
|
||||
// 重置视图
|
||||
function handleResetView() {
|
||||
graphRef.value?.resetView()
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
async function loadData() {
|
||||
if (!props.sessionId) return
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
const data = await loadMentionGraph(props.sessionId, props.timeFilter)
|
||||
graphData.value = {
|
||||
nodes: data.nodes,
|
||||
links: data.links,
|
||||
maxLinkValue: data.maxLinkValue,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[chart-interaction] 加载互动关系图数据失败:', error)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 props 变化
|
||||
watch(
|
||||
() => [props.sessionId, props.timeFilter],
|
||||
() => {
|
||||
loadData()
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full flex-col">
|
||||
<!-- 顶部工具栏(仅右上角控制) -->
|
||||
<div class="flex items-center justify-end px-4 py-2">
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- 布局切换 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-gray-400">{{ t('views.interaction.layout') }}:</span>
|
||||
<UButtonGroup size="xs">
|
||||
<UButton
|
||||
:color="layoutType === 'circular' ? 'primary' : 'neutral'"
|
||||
:variant="layoutType === 'circular' ? 'solid' : 'ghost'"
|
||||
@click="layoutType = 'circular'"
|
||||
>
|
||||
{{ t('views.interaction.circular') }}
|
||||
</UButton>
|
||||
<UButton
|
||||
:color="layoutType === 'force' ? 'primary' : 'neutral'"
|
||||
:variant="layoutType === 'force' ? 'solid' : 'ghost'"
|
||||
@click="layoutType = 'force'"
|
||||
>
|
||||
{{ t('views.interaction.force') }}
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
</div>
|
||||
<!-- 方向切换 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-gray-400">{{ t('views.interaction.directed') }}:</span>
|
||||
<USwitch v-model="showDirection" size="xs" />
|
||||
</div>
|
||||
<!-- 重置视图 -->
|
||||
<UButton size="xs" color="neutral" variant="ghost" icon="i-heroicons-arrow-path" @click="handleResetView">
|
||||
{{ t('views.interaction.reset') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域(全屏) -->
|
||||
<div class="flex-1 min-h-0 relative">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="isLoading" class="absolute inset-0 flex items-center justify-center">
|
||||
<UIcon name="i-heroicons-arrow-path" class="h-8 w-8 animate-spin text-pink-500" />
|
||||
</div>
|
||||
|
||||
<!-- 图表 -->
|
||||
<template v-else>
|
||||
<div v-if="graphData.nodes.length > 0" class="h-full">
|
||||
<EChartGraph
|
||||
ref="graphRef"
|
||||
:data="graphData"
|
||||
:layout="layoutType"
|
||||
:directed="showDirection"
|
||||
:height="'100%'"
|
||||
/>
|
||||
<!-- 底部统计信息 -->
|
||||
<div
|
||||
class="absolute bottom-2 left-1/2 -translate-x-1/2 rounded-full bg-gray-100/80 px-3 py-1 text-xs text-gray-500 backdrop-blur-sm dark:bg-gray-800/80 dark:text-gray-400"
|
||||
>
|
||||
{{ t('views.interaction.graphHint', { nodes: graphData.nodes.length, links: graphData.links.length }) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex h-full items-center justify-center text-gray-400">
|
||||
<div class="text-center">
|
||||
<UIcon name="i-heroicons-user-group" class="mx-auto h-10 w-10 text-gray-300 dark:text-gray-600" />
|
||||
<p class="mt-2 text-sm">{{ t('views.interaction.noInteraction') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user