A:调整页面整体布局

This commit is contained in:
jimmy committed 2025-08-11 10:18:52 +08:00
1 parent 80d13f7d93
commit 5288010a63
3 files changed
+339 -127

No files matched your search

-4
View File
@@ -15,13 +15,9 @@ declare module 'vue' {
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
NDataTable: typeof import('naive-ui')['NDataTable']
NetworkConnectionPanel2: typeof import('./src/components/NetworkConnectionPanel2.vue')['default']
NHighlight: typeof import('naive-ui')['NHighlight']
NInput: typeof import('naive-ui')['NInput']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NSelect: typeof import('naive-ui')['NSelect']
NTooltip: typeof import('naive-ui')['NTooltip']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
RuleItem: typeof import('./src/components/RuleItem.vue')['default']
@@ -166,8 +166,14 @@ const chartOption = computed<EChartsOption>(() => {
show: true,
position: 'bottom',
formatter: '{b}',
fontSize: 10,
color: 'var(--text-secondary)'
fontSize: 11,
fontWeight: 600,
color: '#2c3e50',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
padding: [2, 6],
borderRadius: 4,
borderColor: '#e1e5e9',
borderWidth: 1
}
})
nodeSet.add(remoteIpNodeId)
@@ -189,8 +195,12 @@ const chartOption = computed<EChartsOption>(() => {
show: true,
position: 'right',
formatter: `{b}`,
fontSize: 9,
color: 'var(--text-muted)'
fontSize: 10,
fontWeight: 500,
color: '#34495e',
backgroundColor: 'rgba(255, 255, 255, 0.8)',
padding: [1, 4],
borderRadius: 3
}
})
nodeSet.add(portNodeId)
@@ -66,79 +66,161 @@
</div>
<div class="modal-body">
<!-- 主要内容区域 -->
<div class="content-grid">
<!-- 网络关系图 - 主图表 -->
<div class="chart-section main-chart">
<div class="section-header">
<h4 class="section-title">
<n-icon :component="GitNetworkOutline" class="section-icon" />
网络连接拓扑
</h4>
<div class="section-extra">
<span class="connection-count">{{ analysisData?.topConnections.length || 0 }} 个连接</span>
<!-- 主要Tab内容区域 -->
<n-tabs v-model:value="activeMainTab" type="card" size="large" class="detail-tabs" animated>
<!-- 网络拓扑Tab -->
<n-tab-pane name="topology" tab="网络拓扑">
<div class="tab-content">
<div class="chart-section full-height">
<div class="section-header">
<h4 class="section-title">
<n-icon :component="GitNetworkOutline" class="section-icon" />
网络连接拓扑
</h4>
<div class="section-controls">
<span class="connection-count">{{ analysisData?.topConnections.length || 0 }} 个连接</span>
<n-button size="small" @click="refreshData">
<template #icon>
<n-icon :component="RefreshOutline" />
</template>
刷新
</n-button>
</div>
</div>
<div class="chart-container extra-large">
<NetworkAnalysisChart
v-if="analysisData"
:data="analysisData"
:loading="loading"
/>
<div v-else class="empty-chart">
<n-icon :component="GitNetworkOutline" size="48" />
<span>暂无网络拓扑数据</span>
</div>
</div>
</div>
</div>
<div class="chart-container">
<NetworkAnalysisChart
v-if="analysisData"
:data="analysisData"
:loading="loading"
/>
</div>
</div>
</n-tab-pane>
<!-- 协议分布图 -->
<div class="chart-section protocol-chart">
<div class="section-header">
<h4 class="section-title">
<n-icon :component="StatsChartOutline" class="section-icon" />
协议分布
</h4>
</div>
<div class="chart-container">
<ProtocolChart v-if="protocolChartData.length > 0" :data="protocolChartData" />
<div v-else class="empty-chart">
<n-icon :component="PieChartOutline" size="32" />
<span>暂无协议数据</span>
<!-- 流量分析Tab -->
<n-tab-pane name="traffic" tab="流量分析">
<div class="tab-content">
<div class="analysis-grid">
<!-- 协议分布 -->
<div class="chart-section">
<div class="section-header">
<h4 class="section-title">
<n-icon :component="StatsChartOutline" class="section-icon" />
协议分布
</h4>
</div>
<div class="chart-container large">
<ProtocolChart v-if="protocolChartData.length > 0" :data="protocolChartData" />
<div v-else class="empty-chart">
<n-icon :component="PieChartOutline" size="32" />
<span>暂无协议数据</span>
</div>
</div>
</div>
<!-- 流量趋势 -->
<div class="chart-section">
<div class="section-header">
<h4 class="section-title">
<n-icon :component="TrendingUpOutline" class="section-icon" />
流量趋势
</h4>
</div>
<div class="chart-container large">
<TimeTrendChart v-if="timeTrendData.length > 0" :data="timeTrendData" />
<div v-else class="empty-chart">
<n-icon :component="BarChartOutline" size="32" />
<span>暂无趋势数据</span>
</div>
</div>
</div>
</div>
</div>
</div>
</n-tab-pane>
<!-- 时间趋势图 -->
<div class="chart-section trend-chart">
<div class="section-header">
<h4 class="section-title">
<n-icon :component="TrendingUpOutline" class="section-icon" />
流量趋势
</h4>
</div>
<div class="chart-container">
<TimeTrendChart v-if="timeTrendData.length > 0" :data="timeTrendData" />
<div v-else class="empty-chart">
<n-icon :component="BarChartOutline" size="32" />
<span>暂无趋势数据</span>
<!-- 连接详情Tab -->
<n-tab-pane name="connections" tab="连接详情">
<div class="tab-content table-content">
<div class="table-section">
<div class="section-header">
<h4 class="section-title">
<n-icon :component="GitNetworkOutline" class="section-icon" />
连接详情
</h4>
<div class="section-controls">
<span class="data-count">共 {{ analysisData?.topConnections.length || 0 }} 条记录</span>
<n-button size="small" @click="refreshData">
<template #icon>
<n-icon :component="RefreshOutline" />
</template>
刷新
</n-button>
</div>
</div>
<div class="table-container">
<ConnectionsTable :data="analysisData?.topConnections || []" />
</div>
</div>
</div>
</div>
</div>
</n-tab-pane>
<!-- 详细数据表格 -->
<div class="data-tables">
<div class="table-tabs">
<n-tabs v-model:value="activeTab" type="line" animated>
<n-tab-pane name="connections" tab="连接详情">
<ConnectionsTable :data="analysisData?.topConnections || []" />
</n-tab-pane>
<n-tab-pane name="ports" tab="端口统计">
<PortStatsTable :data="portStatsData" />
</n-tab-pane>
<n-tab-pane name="timeline" tab="时间线">
<TimelineView :data="analysisData?.timeTrends || []" />
</n-tab-pane>
</n-tabs>
</div>
</div>
<!-- 端口统计Tab -->
<n-tab-pane name="ports" tab="端口统计">
<div class="tab-content table-content">
<div class="table-section">
<div class="section-header">
<h4 class="section-title">
<n-icon :component="StatsChartOutline" class="section-icon" />
端口统计
</h4>
<div class="section-controls">
<span class="data-count">共 {{ portStatsData.length || 0 }} 个端口</span>
<n-button size="small" @click="refreshData">
<template #icon>
<n-icon :component="RefreshOutline" />
</template>
刷新
</n-button>
</div>
</div>
<div class="table-container">
<PortStatsTable :data="portStatsData" />
</div>
</div>
</div>
</n-tab-pane>
<!-- 时间线Tab -->
<n-tab-pane name="timeline" tab="时间线">
<div class="tab-content table-content">
<div class="table-section">
<div class="section-header">
<h4 class="section-title">
<n-icon :component="TrendingUpOutline" class="section-icon" />
活动时间线
</h4>
<div class="section-controls">
<span class="data-count">{{ timeRangeText }} 内的活动</span>
<n-button size="small" @click="refreshData">
<template #icon>
<n-icon :component="RefreshOutline" />
</template>
刷新
</n-button>
</div>
</div>
<div class="table-container">
<TimelineView :data="analysisData?.timeTrends || []" />
</div>
</div>
</div>
</n-tab-pane>
</n-tabs>
</div>
</div>
</n-modal>
@@ -248,6 +330,7 @@ const showModal = computed({
const analysisData = ref<NetworkAnalysisData | null>(null)
const loading = ref(false)
const activeTab = ref('connections')
const activeMainTab = ref('topology')
// 计算属性
const timeRangeText = computed(() => {
@@ -358,14 +441,6 @@ const formatBytes = (bytes: number): string => {
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`
}
// 监听器
watch(() => props.show, (newShow) => {
if (newShow && props.appId) {
nextTick(() => {
fetchAnalysisData()
})
}
})
watch(() => [props.appId, props.timeRange], () => {
if (props.show && props.appId) {
@@ -517,26 +592,100 @@ watch(() => [props.appId, props.timeRange], () => {
flex-direction: column;
}
/* 内容网格 */
.content-grid {
display: grid;
grid-template-columns: 2fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 1px;
height: 400px;
background: var(--border-primary);
margin-bottom: 1px;
/* 主要Tab样式 */
.detail-tabs {
height: 100%;
display: flex;
flex-direction: column;
}
:deep(.detail-tabs .n-tabs) {
height: 100%;
display: flex;
flex-direction: column;
}
:deep(.detail-tabs .n-tabs-nav) {
flex-shrink: 0;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border-secondary);
padding: 8px 20px;
}
:deep(.detail-tabs .n-tabs-tab) {
font-weight: 600;
padding: 12px 20px;
border-radius: 8px;
margin-right: 8px;
transition: all 0.3s ease;
}
:deep(.detail-tabs .n-tabs-tab--active) {
background: var(--accent-primary);
color: white;
}
:deep(.detail-tabs .n-tabs-tab:hover:not(.n-tabs-tab--active)) {
background: var(--bg-hover);
}
:deep(.detail-tabs .n-tabs-content) {
flex: 1;
overflow: hidden;
}
:deep(.detail-tabs .n-tab-pane) {
height: 100%;
padding: 0;
}
/* Tab内容 */
.tab-content {
height: 100%;
display: flex;
flex-direction: column;
background: var(--bg-secondary);
overflow: hidden;
}
.tab-content.table-content {
background: var(--bg-card);
}
/* 分析网格(流量分析Tab中的布局) */
.analysis-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
padding: 20px;
height: 100%;
overflow-y: auto;
}
/* 图表区域 */
.chart-section {
background: var(--bg-card);
border: 1px solid var(--border-primary);
border-radius: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.main-chart {
grid-row: span 2;
.chart-section.full-height {
margin: 20px;
height: calc(100% - 40px);
}
.table-section {
background: var(--bg-card);
border: 1px solid var(--border-primary);
border-radius: 12px;
margin: 20px;
height: calc(100% - 40px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.section-header {
@@ -546,6 +695,21 @@ watch(() => [props.appId, props.timeRange], () => {
padding: 16px 20px;
border-bottom: 1px solid var(--border-secondary);
flex-shrink: 0;
background: var(--bg-tertiary);
}
.section-controls {
display: flex;
align-items: center;
gap: 12px;
}
.data-count {
font-size: 12px;
color: var(--text-muted);
padding: 4px 8px;
background: var(--bg-secondary);
border-radius: 6px;
}
.section-title {
@@ -581,6 +745,20 @@ watch(() => [props.appId, props.timeRange], () => {
overflow: hidden;
}
.chart-container.large {
height: 300px;
}
.chart-container.extra-large {
height: 100%;
min-height: 400px;
}
.table-container {
flex: 1;
overflow: hidden;
}
.empty-chart {
display: flex;
flex-direction: column;
@@ -592,41 +770,32 @@ watch(() => [props.appId, props.timeRange], () => {
}
.empty-chart span {
font-size: 13px;
font-size: 14px;
font-weight: 500;
}
/* 数据表格 */
.data-tables {
flex: 1;
background: var(--bg-card);
overflow: hidden;
/* 滚动条美化 */
.tab-content::-webkit-scrollbar,
.table-container::-webkit-scrollbar {
width: 8px;
}
.table-tabs {
height: 100%;
display: flex;
flex-direction: column;
.tab-content::-webkit-scrollbar-track,
.table-container::-webkit-scrollbar-track {
background: var(--bg-tertiary);
border-radius: 4px;
}
.table-tabs :deep(.n-tabs) {
height: 100%;
display: flex;
flex-direction: column;
.tab-content::-webkit-scrollbar-thumb,
.table-container::-webkit-scrollbar-thumb {
background: var(--border-secondary);
border-radius: 4px;
transition: background 0.3s;
}
.table-tabs :deep(.n-tabs-nav) {
padding: 0 20px;
border-bottom: 1px solid var(--border-secondary);
}
.table-tabs :deep(.n-tabs-content) {
flex: 1;
overflow: hidden;
}
.table-tabs :deep(.n-tab-pane) {
height: 100%;
padding: 0;
.tab-content::-webkit-scrollbar-thumb:hover,
.table-container::-webkit-scrollbar-thumb:hover {
background: var(--border-hover);
}
/* 响应式设计 */
@@ -640,14 +809,19 @@ watch(() => [props.appId, props.timeRange], () => {
grid-template-columns: repeat(2, 1fr);
}
.content-grid {
.analysis-grid {
grid-template-columns: 1fr;
grid-template-rows: 200px 150px 150px;
height: auto;
gap: 12px;
padding: 16px;
}
.main-chart {
grid-row: span 1;
.chart-container.large {
height: 250px;
}
:deep(.detail-tabs .n-tabs-tab) {
padding: 10px 16px;
font-size: 14px;
}
}
@@ -672,16 +846,48 @@ watch(() => [props.appId, props.timeRange], () => {
}
.stats-overview {
grid-template-columns: 1fr;
grid-template-columns: repeat(2, 1fr);
}
.stat-card {
padding: 16px;
}
.content-grid {
height: auto;
grid-template-rows: 180px 120px 120px;
.analysis-grid {
grid-template-columns: 1fr;
padding: 12px;
}
.chart-section.full-height,
.table-section {
margin: 12px;
height: calc(100% - 24px);
}
.chart-container.large {
height: 200px;
}
.section-header {
padding: 12px 16px;
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.section-controls {
width: 100%;
justify-content: space-between;
}
:deep(.detail-tabs .n-tabs-nav) {
padding: 8px 12px;
}
:deep(.detail-tabs .n-tabs-tab) {
padding: 8px 12px;
font-size: 13px;
margin-right: 4px;
}
}