chore:monitorview more concise code and add pagination

This commit is contained in:
Tianpao
2025-11-23 17:59:50 +08:00
parent 90b47765a7
commit 249a28ef3e

View File

@@ -1,4 +1,4 @@
<template> <template>
<div class="p-6 min-h-screen bg-base-200"> <div class="p-6 min-h-screen bg-base-200">
<div class="w-full"> <div class="w-full">
<div class="mb-6"> <div class="mb-6">
@@ -9,60 +9,32 @@
<!-- 搜索和筛选栏 --> <!-- 搜索和筛选栏 -->
<div class="bg-base-100 rounded-lg p-4 mb-6 mx-4 border border-base-300"> <div class="bg-base-100 rounded-lg p-4 mb-6 mx-4 border border-base-300">
<div class="flex flex-col lg:flex-row gap-4"> <div class="flex flex-col lg:flex-row gap-4">
<!-- 搜索框 --> <input
<div class="flex-1"> v-model="searchQuery"
<input type="text"
v-model="searchQuery" placeholder="搜索节点名称..."
type="text" class="input input-bordered w-full text-sm"
placeholder="搜索节点名称..." />
class="input input-bordered w-full text-sm"
/>
</div>
<!-- 状态筛选 --> <select v-model="statusFilter" class="select select-bordered text-sm">
<div class="flex gap-2"> <option value="all">全部状态</option>
<select <option value="online">在线</option>
v-model="statusFilter" <option value="offline">离线</option>
class="select select-bordered text-sm" </select>
>
<option value="all">全部状态</option>
<option value="online">在线</option>
<option value="offline">离线</option>
</select>
</div>
<!-- 标签筛选 --> <select v-model="tagFilter" class="select select-bordered text-sm min-w-[120px]">
<div class="flex gap-2"> <option value="">全部标签</option>
<select <option v-for="tag in availableTags" :key="tag" :value="tag">{{ tag }}</option>
v-model="tagFilter" </select>
class="select select-bordered text-sm min-w-[120px]"
>
<option value="">全部标签</option>
<option
v-for="tag in availableTags"
:key="tag"
:value="tag"
>
{{ tag }}
</option>
</select>
</div>
<!-- 排序选择 -->
<div class="flex gap-2"> <div class="flex gap-2">
<select <select v-model="sortBy" class="select select-bordered text-sm min-w-[120px]">
v-model="sortBy"
class="select select-bordered text-sm"
>
<option value="name">按名称排序</option> <option value="name">按名称排序</option>
<option value="id">按ID排序</option> <option value="id">按ID排序</option>
<option value="load">按负载排序</option> <option value="load">按负载排序</option>
<option value="connections">按连接数排序</option> <option value="connections">按连接数排序</option>
</select> </select>
<button <button @click="toggleSortOrder" class="btn btn-outline btn-sm">
@click="toggleSortOrder"
class="btn btn-outline btn-sm"
>
{{ sortOrder === 'asc' ? '↑' : '↓' }} {{ sortOrder === 'asc' ? '↑' : '↓' }}
</button> </button>
</div> </div>
@@ -105,7 +77,7 @@
<div class="space-y-3 px-4"> <div class="space-y-3 px-4">
<div <div
v-for="node in filteredAndSortedNodes" v-for="node in paginatedNodes"
:key="node.id" :key="node.id"
class="w-full border border-base-300 bg-base-100 rounded-lg px-4 py-3 hover:shadow-lg transition-all duration-300" class="w-full border border-base-300 bg-base-100 rounded-lg px-4 py-3 hover:shadow-lg transition-all duration-300"
> >
@@ -170,6 +142,82 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 分页控件 -->
<div class="bg-base-100 rounded-lg px-4 py-4 mx-4 mt-6 border border-base-300">
<div class="flex flex-col sm:flex-row justify-between items-center gap-4">
<!-- 分页信息 -->
<div class="text-sm text-base-content/70">
显示 {{ ((currentPage - 1) * pageSize) + 1 }} - {{ Math.min(currentPage * pageSize, totalNodes) }}
{{ totalNodes }} 条记录
</div>
<!-- 每页显示数量选择 -->
<div class="flex items-center gap-2 whitespace-nowrap">
<span class="text-sm text-base-content/70">每页</span>
<select v-model="pageSize" @change="changePageSize" class="select select-bordered select-sm">
<option v-for="size in pageSizeOptions" :key="size" :value="size">{{ size }}</option>
</select>
<span class="text-sm text-base-content/70"></span>
</div>
<!-- 分页按钮 -->
<div class="flex items-center gap-2">
<!-- 首页 -->
<button
@click="goToPage(1)"
:disabled="currentPage === 1"
class="btn btn-outline btn-sm"
:class="{ 'btn-disabled': currentPage === 1 }"
>
首页
</button>
<!-- 上一页 -->
<button
@click="goToPage(currentPage - 1)"
:disabled="currentPage === 1"
class="btn btn-outline btn-sm"
:class="{ 'btn-disabled': currentPage === 1 }"
>
上一页
</button>
<!-- 页码 -->
<div class="flex items-center gap-1">
<button
v-for="page in getPageNumbers()"
:key="page"
@click="goToPage(page)"
class="btn btn-sm"
:class="page === currentPage ? 'btn-primary' : 'btn-outline'"
>
{{ page }}
</button>
</div>
<!-- 下一页 -->
<button
@click="goToPage(currentPage + 1)"
:disabled="currentPage === totalPages"
class="btn btn-outline btn-sm"
:class="{ 'btn-disabled': currentPage === totalPages }"
>
下一页
</button>
<!-- 末页 -->
<button
@click="goToPage(totalPages)"
:disabled="currentPage === totalPages"
class="btn btn-outline btn-sm"
:class="{ 'btn-disabled': currentPage === totalPages }"
>
末页
</button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -179,7 +227,7 @@ import { ref, computed } from 'vue'
// 节点接口定义 // 节点接口定义
interface Node { interface Node {
id: string id: number
name: string name: string
isOnline: boolean isOnline: boolean
maxConnections: number maxConnections: number
@@ -194,13 +242,18 @@ interface Node {
const searchQuery = ref('') const searchQuery = ref('')
const statusFilter = ref('all') const statusFilter = ref('all')
const tagFilter = ref('') const tagFilter = ref('')
const sortBy = ref('name') const sortBy = ref('id')
const sortOrder = ref<'asc' | 'desc'>('asc') const sortOrder = ref<'asc' | 'desc'>('asc')
// 分页状态
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizeOptions = [10, 30, 50, 100, 200]
// 示例节点数据 // 示例节点数据
const nodes = ref<Node[]>([ const nodes = ref<Node[]>([
{ {
id: 'NODE-001', id: 1,
name: '主控节点', name: '主控节点',
isOnline: true, isOnline: true,
maxConnections: 100, maxConnections: 100,
@@ -211,7 +264,7 @@ const nodes = ref<Node[]>([
tags: ['核心', '主控', '高可用'] tags: ['核心', '主控', '高可用']
}, },
{ {
id: 'NODE-002', id: 2,
name: '数据处理节点', name: '数据处理节点',
isOnline: true, isOnline: true,
maxConnections: 80, maxConnections: 80,
@@ -222,7 +275,7 @@ const nodes = ref<Node[]>([
tags: ['计算', '数据分析', '高性能'] tags: ['计算', '数据分析', '高性能']
}, },
{ {
id: 'NODE-003', id: 3,
name: '边缘服务节点', name: '边缘服务节点',
isOnline: false, isOnline: false,
maxConnections: 50, maxConnections: 50,
@@ -231,102 +284,234 @@ const nodes = ref<Node[]>([
createdAt: new Date('2024-03-10T09:45:00'), createdAt: new Date('2024-03-10T09:45:00'),
description: '部署在网络边缘的服务节点,提供低延迟的本地化服务。', description: '部署在网络边缘的服务节点,提供低延迟的本地化服务。',
tags: ['边缘', '低延迟', '本地服务'] tags: ['边缘', '低延迟', '本地服务']
},
{
id: 4,
name: '缓存服务节点',
isOnline: true,
maxConnections: 200,
currentConnections: 120,
loadScore: 0,
createdAt: new Date('2024-01-20T08:00:00'),
description: '提供高速缓存服务,支持分布式缓存和内存数据库功能。',
tags: ['缓存', '高性能', '分布式']
},
{
id: 5,
name: 'API网关节点',
isOnline: true,
maxConnections: 150,
currentConnections: 85,
loadScore: 0,
createdAt: new Date('2024-02-01T12:00:00'),
description: '统一API入口负责请求路由、负载均衡和安全认证。',
tags: ['API', '网关', '安全']
},
{
id: 6,
name: '文件存储节点',
isOnline: true,
maxConnections: 75,
currentConnections: 30,
loadScore: 0,
createdAt: new Date('2024-01-25T16:30:00'),
description: '提供分布式文件存储服务,支持大文件上传和下载。',
tags: ['存储', '文件', '分布式']
},
{
id: 7,
name: '消息队列节点',
isOnline: false,
maxConnections: 90,
currentConnections: 0,
loadScore: 0,
createdAt: new Date('2024-03-05T11:20:00'),
description: '处理异步消息传递,支持发布订阅模式和消息持久化。',
tags: ['消息', '队列', '异步']
},
{
id: 8,
name: '监控收集节点',
isOnline: true,
maxConnections: 60,
currentConnections: 45,
loadScore: 0,
createdAt: new Date('2024-02-10T09:15:00'),
description: '收集和聚合系统监控数据,提供实时监控和告警功能。',
tags: ['监控', '数据收集', '告警']
},
{
id: 9,
name: '日志处理节点',
isOnline: true,
maxConnections: 120,
currentConnections: 95,
loadScore: 0,
createdAt: new Date('2024-01-18T14:45:00'),
description: '处理和存储系统日志,支持日志搜索和分析功能。',
tags: ['日志', '处理', '存储']
},
{
id: 10,
name: '认证服务节点',
isOnline: true,
maxConnections: 80,
currentConnections: 35,
loadScore: 0,
createdAt: new Date('2024-02-15T10:00:00'),
description: '提供用户认证和授权服务,支持多种认证方式。',
tags: ['认证', '安全', '授权']
},
{
id: 11,
name: '配置管理节点',
isOnline: true,
maxConnections: 50,
currentConnections: 20,
loadScore: 0,
createdAt: new Date('2024-01-22T13:30:00'),
description: '集中管理应用配置,支持动态配置更新和版本控制。',
tags: ['配置', '管理', '动态']
},
{
id: 12,
name: '任务调度节点',
isOnline: false,
maxConnections: 70,
currentConnections: 0,
loadScore: 0,
createdAt: new Date('2024-03-08T15:00:00'),
description: '负责任务调度和执行,支持定时任务和分布式任务处理。',
tags: ['任务', '调度', '分布式']
},
{
id: 13,
name: '搜索服务节点',
isOnline: true,
maxConnections: 100,
currentConnections: 78,
loadScore: 0,
createdAt: new Date('2024-02-05T08:45:00'),
description: '提供全文搜索和索引服务,支持复杂查询和实时搜索。',
tags: ['搜索', '索引', '查询']
},
{
id: 14,
name: '图像处理节点',
isOnline: true,
maxConnections: 60,
currentConnections: 42,
loadScore: 0,
createdAt: new Date('2024-01-28T11:15:00'),
description: '专门处理图像相关任务,包括压缩、裁剪和格式转换。',
tags: ['图像', '处理', '媒体']
},
{
id: 15,
name: '机器学习节点',
isOnline: true,
maxConnections: 40,
currentConnections: 28,
loadScore: 0,
createdAt: new Date('2024-02-18T16:00:00'),
description: '提供机器学习模型训练和推理服务,支持多种算法框架。',
tags: ['AI', '机器学习', '推理']
} }
]) ])
// 获取所有可用标签 // 获取所有可用标签
const availableTags = computed(() => { const availableTags = computed(() =>
const tags = new Set<string>() [...new Set(nodes.value.flatMap(node => node.tags))].sort()
nodes.value.forEach(node => { )
node.tags.forEach(tag => tags.add(tag))
})
return Array.from(tags).sort()
})
// 过滤和排序后的节点 // 过滤和排序后的节点
const filteredAndSortedNodes = computed(() => { const filteredAndSortedNodes = computed(() => {
let filtered = nodes.value.filter(node => { const sortMethods = {
// 搜索过滤 name: (a: Node, b: Node) => a.name.localeCompare(b.name, 'zh-CN'),
const matchesSearch = node.name.toLowerCase().includes(searchQuery.value.toLowerCase()) id: (a: Node, b: Node) => a.id - b.id,
load: (a: Node, b: Node) => a.loadScore - b.loadScore,
// 状态过滤 connections: (a: Node, b: Node) => a.currentConnections - b.currentConnections
const matchesStatus = statusFilter.value === 'all' || }
(statusFilter.value === 'online' && node.isOnline) ||
(statusFilter.value === 'offline' && !node.isOnline)
// 标签过滤
const matchesTag = !tagFilter.value || node.tags.includes(tagFilter.value)
return matchesSearch && matchesStatus && matchesTag
})
// 排序 return nodes.value
filtered.sort((a, b) => { .filter(node =>
let comparison = 0 node.name.toLowerCase().includes(searchQuery.value.toLowerCase()) &&
(statusFilter.value === 'all' ||
switch (sortBy.value) { (statusFilter.value === 'online' && node.isOnline) ||
case 'name': (statusFilter.value === 'offline' && !node.isOnline)) &&
comparison = a.name.localeCompare(b.name, 'zh-CN') (!tagFilter.value || node.tags.includes(tagFilter.value))
break )
case 'id': .sort((a, b) => {
comparison = a.id.localeCompare(b.id) const comparison = sortMethods[sortBy.value as keyof typeof sortMethods](a, b)
break return sortOrder.value === 'asc' ? comparison : -comparison
case 'load': })
comparison = a.loadScore - b.loadScore
break
case 'connections':
comparison = a.currentConnections - b.currentConnections
break
}
return sortOrder.value === 'asc' ? comparison : -comparison
})
return filtered
}) })
// 分页相关计算属性
const totalNodes = computed(() => filteredAndSortedNodes.value.length)
const totalPages = computed(() => Math.ceil(totalNodes.value / pageSize.value))
const paginatedNodes = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return filteredAndSortedNodes.value.slice(start, end)
})
// 分页方法
const goToPage = (page: number) => {
if (page >= 1 && page <= totalPages.value) {
currentPage.value = page
}
}
const changePageSize = () => {
currentPage.value = 1 // 重置到第一页
}
// 获取要显示的页码数组
const getPageNumbers = () => {
const current = currentPage.value
const total = totalPages.value
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1)
if (current <= 4) return [...Array.from({ length: 5 }, (_, i) => i + 1), total]
if (current >= total - 3) return [1, ...Array.from({ length: 5 }, (_, i) => total - 4 + i)]
return [1, current - 1, current, current + 1, total]
}
// 切换排序顺序 // 切换排序顺序
const toggleSortOrder = () => { const toggleSortOrder = () => {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc' sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
} }
// 计算负载分数 // 计算负载分数
const calculateLoadScore = (maxConnections: number, currentConnections: number): number => { const calculateLoadScore = (max: number, current: number) =>
if (maxConnections === 0) return 0 max === 0 ? 0 : Math.min((current / max) * 100, 100)
return Math.min((currentConnections / maxConnections) * 100, 100)
}
// 更新所有节点的负载分数 // 更新所有节点的负载分数
const updateLoadScores = () => { const updateLoadScores = () =>
nodes.value.forEach(node => { nodes.value.forEach(node => {
node.loadScore = calculateLoadScore(node.maxConnections, node.currentConnections) node.loadScore = calculateLoadScore(node.maxConnections, node.currentConnections)
}) })
}
// 获取负载分数颜色 // 获取负载分数颜色
const getLoadScoreColor = (score: number): string => { const getLoadScoreColor = (score: number) =>
if (score < 30) return 'text-success' score < 30 ? 'text-success' : score < 70 ? 'text-warning' : 'text-error'
if (score < 70) return 'text-warning'
return 'text-error'
}
// 获取负载分数条颜色 // 获取负载分数条颜色
const getLoadScoreBarColor = (score: number): string => { const getLoadScoreBarColor = (score: number) =>
if (score < 30) return 'bg-success' score < 30 ? 'bg-success' : score < 70 ? 'bg-warning' : 'bg-error'
if (score < 70) return 'bg-warning'
return 'bg-error'
}
// 格式化日期 // 格式化日期
const formatDate = (date: Date): string => { const formatDate = (date: Date) =>
return date.toLocaleString('zh-CN', { date.toLocaleString('zh-CN', {
year: 'numeric', year: 'numeric',
month: '2-digit', month: '2-digit',
day: '2-digit', day: '2-digit',
hour: '2-digit', hour: '2-digit',
minute: '2-digit' minute: '2-digit'
}) })
}
// 初始化负载分数 // 初始化负载分数
updateLoadScores() updateLoadScores()