新增通道列表查询页面
This commit is contained in:
184
src/app/admin/channels/page.tsx
Normal file
184
src/app/admin/channels/page.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
'use client'
|
||||
import {useEffect, useState} from 'react'
|
||||
import {useStatus} from '@/lib/states'
|
||||
import {PageRecord} from '@/lib/api'
|
||||
import {Channel} from '@/lib/models'
|
||||
import Page from '@/components/page'
|
||||
import DataTable from '@/components/data-table'
|
||||
import {toast} from 'sonner'
|
||||
import {listChannels} from '@/actions/channel'
|
||||
import {format} from 'date-fns'
|
||||
import {Form, FormField} from '@/components/ui/form'
|
||||
import {z} from 'zod'
|
||||
import {useForm} from 'react-hook-form'
|
||||
import {zodResolver} from '@hookform/resolvers/zod'
|
||||
import DatePicker from '@/components/date-picker'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {EraserIcon, SearchIcon} from 'lucide-react'
|
||||
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select'
|
||||
|
||||
export type ChannelsPageProps = {}
|
||||
|
||||
export default function ChannelsPage(props: ChannelsPageProps) {
|
||||
|
||||
// ======================
|
||||
// data
|
||||
// ======================
|
||||
|
||||
const [status, setStatus] = useStatus()
|
||||
const [data, setData] = useState<PageRecord<Channel>>({
|
||||
page: 1,
|
||||
size: 10,
|
||||
total: 0,
|
||||
list: [],
|
||||
})
|
||||
|
||||
const refresh = async (page: number, size: number) => {
|
||||
try {
|
||||
setStatus('load')
|
||||
|
||||
// 筛选条件
|
||||
const filter = filterForm.getValues()
|
||||
const auth_type = filter.auth_type ? parseInt(filter.auth_type) : undefined
|
||||
|
||||
// 请求数据
|
||||
console.log({
|
||||
page, size, ...filter, auth_type,
|
||||
})
|
||||
const resp = await listChannels({
|
||||
page, size, ...filter, auth_type,
|
||||
})
|
||||
if (!resp.success) {
|
||||
throw new Error(resp.message)
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
setData(resp.data)
|
||||
setStatus('done')
|
||||
}
|
||||
catch (e) {
|
||||
setStatus('fail')
|
||||
console.error(e)
|
||||
toast.error('获取提取结果失败', {
|
||||
description: (e as Error).message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
refresh(data.page, data.size).then()
|
||||
}, [])
|
||||
|
||||
// ======================
|
||||
// filter
|
||||
// ======================
|
||||
|
||||
const filterSchema = z.object({
|
||||
auth_type: z.enum(['0', '1', '2']),
|
||||
expire_after: z.date().optional(),
|
||||
expire_before: z.date().optional(),
|
||||
})
|
||||
type FilterSchema = z.infer<typeof filterSchema>
|
||||
|
||||
const filterForm = useForm<FilterSchema>({
|
||||
resolver: zodResolver(filterSchema),
|
||||
defaultValues: {
|
||||
auth_type: '0',
|
||||
expire_after: undefined,
|
||||
expire_before: undefined,
|
||||
},
|
||||
})
|
||||
const filterHandler = filterForm.handleSubmit(async value => {
|
||||
await refresh(data.page, data.size)
|
||||
})
|
||||
|
||||
// ======================
|
||||
// render
|
||||
// ======================
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<section className={`flex justify-between`}>
|
||||
<div></div>
|
||||
<Form form={filterForm} handler={filterHandler} className={`flex-none flex gap-4 items-end`}>
|
||||
<FormField<FilterSchema, 'auth_type'> name={`auth_type`} label={<span className={`text-sm`}>认证方式</span>}>
|
||||
{({field}) => (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger className={`h-9 w-36`}>
|
||||
<SelectValue placeholder={`选择认证方式`}/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={'0'}>全部</SelectItem>
|
||||
<SelectItem value={'1'}>IP 白名单</SelectItem>
|
||||
<SelectItem value={'2'}>账号密码</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
<fieldset className={`flex flex-col gap-2 items-start`}>
|
||||
<div>
|
||||
<legend className={`block text-sm`}>过期时间</legend>
|
||||
</div>
|
||||
<div className={`flex gap-1 items-center`}>
|
||||
<FormField<FilterSchema, 'expire_after'> name={`expire_after`}>
|
||||
{({field}) => (
|
||||
<DatePicker placeholder={`选择开始时间`} {...field} format={`yyyy-MM-dd`}/>
|
||||
)}
|
||||
</FormField>
|
||||
<span>-</span>
|
||||
<FormField<FilterSchema, 'expire_before'> name={`expire_before`}>
|
||||
{({field}) => (
|
||||
<DatePicker placeholder={`选择结束时间`} {...field} format={`yyyy-MM-dd`}/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
</fieldset>
|
||||
<Button className={`h-9`}>
|
||||
<SearchIcon/>筛选
|
||||
</Button>
|
||||
<Button theme={`outline`} className={`h-9`} onClick={() => filterForm.reset()}>
|
||||
<EraserIcon/>重置
|
||||
</Button>
|
||||
</Form>
|
||||
</section>
|
||||
|
||||
<DataTable
|
||||
status={status}
|
||||
data={data.list}
|
||||
pagination={{
|
||||
page: data.page,
|
||||
size: data.size,
|
||||
total: data.total,
|
||||
onPageChange: (page) => refresh(page, data.size),
|
||||
onSizeChange: (size) => refresh(1, size),
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
header: '代理地址', cell: ({row}) => `${row.original.proxy_host}:${row.original.proxy_port}`,
|
||||
},
|
||||
{
|
||||
header: '认证方式', cell: ({row}) => {
|
||||
return <div className={`flex flex-col gap-1`}>
|
||||
{row.original.auth_ip && (<>
|
||||
<span className={`text-weak`}>IP 白名单</span>
|
||||
<span>{row.original.user_host}</span>
|
||||
</>)}
|
||||
{row.original.auth_pass && (<>
|
||||
<span className={`text-weak`}>账号密码</span>
|
||||
<span>{row.original.username}:{row.original.password}</span>
|
||||
</>)}
|
||||
</div>
|
||||
},
|
||||
},
|
||||
{
|
||||
header: '过期时间', cell: ({row}) => format(row.original.expiration, 'yyyy-MM-dd HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
header: '操作', cell: ({row}) => <span>-</span>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user