更新侧边栏改造使用路由结构,更新中间件重定向

This commit is contained in:
wmp
2025-10-14 16:54:55 +08:00
parent 10395a49c1
commit 010990ea3c
14 changed files with 117 additions and 278 deletions

View File

@@ -0,0 +1,395 @@
'use client'
import { useEffect, useState, Suspense, useCallback } from 'react'
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table'
import { getGatewayInfo, getGatewayConfig, type GatewayConfig, type GatewayInfo } from '@/actions/stats'
import { Pagination } from '@/components/ui/pagination'
import { toast } from 'sonner'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { Button } from '@/components/ui/button'
import { Form, FormField, FormItem, FormLabel } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
import { Search } from 'lucide-react'
function GatewayConfigContent() {
const [data, setData] = useState<GatewayConfig[]>([])
const [loading, setLoading] = useState(false)
const [infoData, setInfoData] = useState<GatewayInfo[]>([])
// 分页状态
const [page, setPage] = useState(1)
const [total, setTotal] = useState(0)
// 定义表单验证规则
const filterSchema = z.object({
macaddr: z.string().optional(),
public: z.string().optional(),
city: z.string().optional(),
inner_ip: z.string().optional(),
user: z.string().optional(),
})
type FilterFormValues = z.infer<typeof filterSchema>
// 初始化表单
const form = useForm<FilterFormValues>({
resolver: zodResolver(filterSchema),
defaultValues: {
macaddr: '',
public: '',
city: '',
inner_ip: '',
user: '',
},
})
// 初始化调用
useEffect(() => {
const initData = async () => {
setLoading(true)
try {
// 获取网关基本信息
const infoResult = await getGatewayInfo()
if (!infoResult.success) {
throw new Error(infoResult.error || '查询网关信息失败')
}
setInfoData(infoResult.data)
}
catch (error) {
toast.error((error as Error).message || '获取数据失败')
}
finally {
setLoading(false)
}
}
initData()
fetchData({}, 1)
}, [])
const { handleSubmit: formHandleSubmit } = form
// 网关配置数据查询函数(用于表单查询)
const fetchData = async (filters: {
mac?: string
public?: string
city?: string
user?: string
inner_ip?: string
}, page: number = 1) => {
setLoading(true)
try {
const result = await getGatewayConfig(page, filters)
if (!result.success) {
throw new Error(result.error || '查询网关配置失败')
}
const shrink = ['黔东南', '延边']
result.data.items.forEach((item) => {
shrink.forEach((s) => {
if (item.city?.startsWith(s)) {
item.city = s
}
})
})
setData(result.data.items)
setTotal(result.data.total)
}
catch (error) {
toast.error((error as Error).message || '获取网关配置失败')
}
finally {
setLoading(false)
}
}
const onSubmit = (data: FilterFormValues) => {
setPage(1)
const filters = {
mac: data.macaddr || '',
public: data.public || '',
city: data.city || '',
user: data.user || '',
inner_ip: data.inner_ip || '',
}
fetchData(filters, 1)
}
// 处理页码变化
const handlePageChange = (page: number) => {
setPage(page)
const formValues = form.getValues()
const filters = {
mac: formValues.macaddr || undefined,
public: formValues.public || undefined,
city: formValues.city || undefined,
user: formValues.user || undefined,
inner_ip: formValues.inner_ip || undefined,
}
fetchData(filters, page)
}
// 处理每页显示数量变化
const handleSizeChange = (size: number) => {
setPage(1)
const formValues = form.getValues()
const filters = {
mac: formValues.macaddr || undefined,
public: formValues.public || undefined,
city: formValues.city || undefined,
user: formValues.user || undefined,
inner_ip: formValues.inner_ip || undefined,
}
fetchData(filters, 1)
}
const getStatusBadge = (value: number, trueText: string = '是', falseText: string = '否') => {
// 0是正常1是更新正常绿+ 更新(红)
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${value === 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
{value === 0 ? trueText : falseText}
</span>
)
}
const getOnlineStatus = (isonline: number) => {
// 0是空闲1是在用在用+ 空闲(绿)
return (
<div className="flex items-center">
<div className={`${isonline === 0 ? 'bg-green-500' : 'bg-red-500'}`} />
{getStatusBadge(isonline, '空闲', '在用')}
</div>
)
}
// 当前选中的mac
const [selectedMac, setSelectedMac] = useState<string>('')
const handleMacClick = useCallback(async (macaddr: string) => {
setSelectedMac(macaddr)
await fetchData({ mac: macaddr }, 1)
}, [])
return (
<div className="flex flex-col bg-white shadow p-6 overflow-hidden">
{/* 查询表单 */}
<div className="flex gap-6">
<div className="flex flex-1">
<div className="flex flex-col w-full">
<Form {...form}>
<form onSubmit={formHandleSubmit(onSubmit)}>
<div className="flex gap-4 mb-2">
<FormField
name="macaddr"
render={({ field }) => (
<FormItem className="flex-1 relative">
<div className="relative">
<Input
placeholder="搜索MAC地址..."
{...field}
className="w-full pr-10"
/>
<Search
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 cursor-pointer"
onClick={(e) => {
e.preventDefault()
onSubmit(form.getValues())
}}
/>
</div>
</FormItem>
)}
/>
</div>
</form>
</Form>
<div className="flex gap-4">
<div className="flex items-center">
<div className="w-3 h-3 bg-green-500 rounded-full mr-2"></div>
<span className="text-sm font-medium">线 {infoData.filter(item => item.enable === 1).length}</span>
</div>
<div className="flex items-center">
<div className="w-3 h-3 bg-red-500 rounded-full mr-2"></div>
<span className="text-sm font-medium">线 {infoData.filter(item => item.enable === 0).length}</span>
</div>
</div>
</div>
</div>
<div className="flex flex-3 rounded-lg bg-white">
<Form {...form}>
<form onSubmit={formHandleSubmit(onSubmit)}>
<div className="flex gap-4">
<FormField
name="public"
render={({ field }) => (
<FormItem>
<FormLabel>IP地址</FormLabel>
<Input placeholder="输入IP地址" {...field} />
</FormItem>
)}
/>
<FormField
name="user"
render={({ field }) => (
<FormItem>
<FormLabel>线</FormLabel>
<Input placeholder="输入线路" {...field} />
</FormItem>
)}
/>
<FormField
name="inner_ip"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<Input placeholder="输入端口" {...field} />
</FormItem>
)}
/>
<FormField
name="city"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<Input placeholder="输入城市" {...field} />
</FormItem>
)}
/>
<Button type="submit" className="ml-2 mt-6 px-4 py-2 bg-blue-600 hover:bg-blue-700">
</Button>
</div>
</form>
</Form>
</div>
</div>
{loading ? (
<div className="text-center py-12">
<div className="text-gray-400 text-4xl mb-4"></div>
<p className="text-gray-600">...</p>
</div>
) : infoData.length > 0 ? (
<>
{/* 网关基本信息 */}
<div className="flex gap-6 p-2 overflow-hidden">
<div className="flex flex-1 flex-col overflow-auto mb-6 border-t bg-white">
{infoData.map((item, index) => (
<div
key={index}
className={cn('p-4 flex-col ', index !== infoData.length - 1 ? 'border-b' : '')}>
<div className="flex flex-col">
<div className="flex items-center justify-between mb-2">
<div
className={cn('font-medium cursor-pointer',
selectedMac === item.macaddr ? 'text-blue-700' : 'text-gray-900')}
onClick={() => handleMacClick(item.macaddr)}
>
{item.macaddr}
</div>
<div className="flex items-center">
<div
className={cn(
'w-2 h-2 rounded-full mr-2',
item.enable === 1 ? 'bg-green-500' : 'bg-red-500',
)}
/>
<span className={cn(
'text-xs font-medium',
item.enable === 1 ? 'text-green-700' : 'text-red-700',
)}>
{item.enable === 1 ? '在线' : '离线'}
</span>
</div>
</div>
</div>
<div className="text-sm text-gray-600 mb-3">
{item.inner_ip || '未配置IP'}
</div>
<div className="flex gap-2 space-y-1 text-xs text-gray-500">
<div>: {item.setid || 'N/A'}</div>
</div>
</div>
))}
</div>
<div className="flex-3 flex flex-col overflow-hidden">
{ data.length > 0
? (
<>
<div className="flex overflow-auto">
<Table>
<TableHeader>
<TableRow className="bg-gray-50">
<TableHead className="text-xs text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="text-xs text-gray-500 uppercase tracking-wider">线</TableHead>
<TableHead className="text-xs text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="text-xs text-gray-500 uppercase tracking-wider">MAC</TableHead>
<TableHead className="text-xs text-gray-500 uppercase tracking-wider">IP</TableHead>
<TableHead className="text-xs text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="text-xs text-gray-500 uppercase tracking-wider"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item, index) => (
<TableRow key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
<TableCell className="px-4 py-2">{item.inner_ip}</TableCell>
<TableCell className="px-4 py-2">{item.user}</TableCell>
<TableCell className="px-4 py-2">{item.city}</TableCell>
<TableCell className="px-4 py-2">{item.edge}</TableCell>
<TableCell className="px-4 py-2">{item.public}</TableCell>
<TableCell className="px-4 py-2">
{getStatusBadge(item.ischange, '正常', '更新')}
</TableCell>
<TableCell className="px-4 py-2">
{getOnlineStatus(item.isonline)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{/* 分页组件 */}
<div className="flex gap-6">
<Pagination
total={total}
page={page}
size={250}
sizeOptions={[250]}
onPageChange={handlePageChange}
onSizeChange={handleSizeChange}
className="mt-4"
/>
</div>
</>
) : (
<div className="text-center py-12">
<div className="text-gray-400 text-4xl mb-4">📋</div>
<p className="text-gray-600"></p>
</div>
)}
</div>
</div>
</>
) : (
<div className="text-center py-12">
<div className="text-gray-400 text-4xl mb-4">📋</div>
<p className="text-gray-600"></p>
</div>
)}
</div>
)
}
export default function GatewayConfig() {
return (
<Suspense fallback={(
<div className="bg-white shadow p-6">
<div className="text-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto"></div>
<p className="mt-4 text-gray-600">...</p>
</div>
</div>
)}>
<GatewayConfigContent />
</Suspense>
)
}