'use client' import {createWhitelist, removeWhitelist, listWhitelist, Whitelist, updateWhitelist} from '@/actions/whitelist' import {Button} from '@/components/ui/button' import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog' import {useEffect, useRef, useState} from 'react' import {PageRecord} from '@/lib/api' import {useStatus} from '@/lib/states' import {toast} from 'sonner' import {Form, FormField} from '@/components/ui/form' import {useForm} from 'react-hook-form' import {Input} from '@/components/ui/input' import {Textarea} from '@/components/ui/textarea' import {zodResolver} from '@hookform/resolvers/zod' import {z} from 'zod' import {Edit, Plus, Trash2, Loader2} from 'lucide-react' import { AlertDialog, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog' import Page from '@/components/page' import DataTable from '@/components/data-table' import {format, parseISO} from 'date-fns' import {getClientIp} from '@/actions/ip' const schema = z.object({ host: z.string().min(1, {message: 'IP地址不能为空'}), remark: z.string().optional(), }) type SchemaType = z.infer export type WhitelistPageProps = {} const MAX_WHITELIST_COUNT = 5 export default function WhitelistPage(props: WhitelistPageProps) { const [wait, setWait] = useState(false) // ====================== // 数据 // ====================== const [status, setStatus] = useStatus() const [data, setData] = useState>({ page: 1, size: 10, total: 0, list: [], }) const refresh = async (page: number, size: number) => { setStatus('load') setWait(true) try { const resp = await listWhitelist({page, size}) console.log(resp, '白名单resp') if (!resp.success) { throw new Error(resp.message) } setStatus('done') setData(resp.data) } catch (e) { setStatus('fail') toast.error('加载数据失败', { description: e instanceof Error ? e.message : String(e), }) } finally { setWait(false) } } // ====================== // 弹窗 // ====================== const [dialogVisible, setDialogVisible] = useState(false) const [dialogType, setDialogType] = useState<'add' | 'edit'>('add') const [dialogData, setDialogData] = useState() const openDialog = (type: 'add' | 'edit', data?: Whitelist) => { form.reset({ host: data?.host || '', remark: data?.remark || '', }) setDialogVisible(true) setDialogType(type) setDialogData(data) } const toggleDialog = (open: boolean) => { setDialogVisible(open) if (!open) { form.reset() } } const [alertVisible, setAlertVisible] = useState(false) const removeId = useRef(undefined) const confirmRemove = (id?: number) => { setAlertVisible(true) if (id) { removeId.current = id } else { removeId.current = undefined } } const remove = async () => { setWait(true) try { const ids = removeId.current === undefined ? Array.from(selection).map(id => ({id})) : [{id: removeId.current}] const resp = await removeWhitelist(ids) if (resp && resp.success) { setAlertVisible(false) toast.success('删除成功') await refresh(data.page, data.size) } else { toast.error('删除失败') } } catch (error) { toast.error('删除失败') } finally { setWait(false) } } const [selection, setSelection] = useState(new Set()) const toggleSelect = (id: number) => { if (selection.has(id)) { selection.delete(id) } else { selection.add(id) } setSelection(new Set(selection)) } const toggleSelectAll = () => { if (selection.size === data.list.length) { setSelection(new Set()) } else { const newSelection = new Set() data.list.forEach((item) => { newSelection.add(item.id) }) setSelection(newSelection) } } // ====================== // 表单 // ====================== const form = useForm({ resolver: zodResolver(schema), defaultValues: { host: '', remark: '', }, }) const onSubmit = async (value: SchemaType) => { setWait(true) try { // 添加白名单 if (dialogType === 'add') { const resp = await createWhitelist(value) if (!resp.success) { throw new Error(resp.message) } await refresh(1, data.size) toggleDialog(false) toast.success('添加成功') } // 编辑白名单 else { if (!dialogData) { throw new Error('编辑数据出错') } const resp = await updateWhitelist({...value, id: dialogData.id}) if (!resp.success) { throw new Error(resp.message) } await refresh(1, data.size) toggleDialog(false) toast.success('编辑成功') } } catch (e) { toast.error(dialogType === 'add' ? '添加失败' : '编辑失败', { description: e instanceof Error ? e.message : String(e), }) } finally { setWait(false) } } // ====================== // region render // ====================== useEffect(() => { refresh(1, 10).then() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) if (status === 'fail') { return
加载失败,请重试
} const getCurrentIP = async () => { setWait(true) try { const result = await getClientIp() if (result.ip) { form.setValue('host', result.ip) toast.success('已获取当前IP地址') } else { toast.error('获取失败', { description: result.error || '请手动输入IP地址', }) } } finally { setWait(false) } } return ( {/* 全局操作 */}
{/* 数据表 */} refresh(page, data.size), onSizeChange: (size: number) => refresh(1, size), }} columns={[ { header: `IP 地址`, accessorKey: 'ip', }, { header: `备注`, accessorKey: 'remark', }, { header: `添加时间`, cell: ({row}) => format(parseISO(row.original.created_at), 'yyyy-MM-dd HH:mm'), }, { id: 'actions', header: `操作`, cell: ({row}) => (
), }, ]} /> {/* 编辑表单 */} {dialogType === 'add' ? '添加白名单' : '编辑白名单'} className="flex flex-col gap-4 py-4" form={form} onSubmit={onSubmit}> {({id, field}) => (
)}
{({id, field}) => (