'use client' import {useCallback, useEffect, useMemo, useState} from 'react' import {useSearchParams} from 'next/navigation' import {useForm} from 'react-hook-form' import {zodResolver} from '@hookform/resolvers/zod' import zod from 'zod' import {toast} from 'sonner' import {useStatus} from '@/lib/states' import {ExtraResp} from '@/lib/api' import {listResourceLong, listResourceShort} from '@/actions/resource' import DataTable from '@/components/data-table' import {ColumnDef} from '@tanstack/react-table' import {Resource} from '@/lib/models/resource' import ResourceFilter, {ResourceFilterValues} from './filter' import { ExpireBadge, formatDateTime, getTodayUsage, isValidResourcestatus, isValidResourceType, ResourceTypeBadge, } from './utils' const filterSchema = zod.object({ resource_no: zod.string().optional().default(''), type: zod.enum(['expire', 'quota', 'all']).default('all'), status: zod.enum(['0', '1', '2']).default('1'), create_after: zod.date().optional(), create_before: zod.date().optional(), expire_after: zod.date().optional(), expire_before: zod.date().optional(), }) interface ResourceListProps { resourceType: 'long' | 'short' } export default function ResourceList({resourceType}: ResourceListProps) { const isLong = resourceType === 'long' const [status, setStatus] = useStatus() const [data, setData] = useState>({ page: 1, size: 10, total: 0, list: [], }) // 从 URL 参数初始化筛选条件 const params = useSearchParams() const paramType = params.get('type') const paramStatus = params.get('status') const form = useForm({ resolver: zodResolver(filterSchema), defaultValues: { resource_no: params.get('resource_no') || '', type: isValidResourceType(paramType) ? paramType : 'all', status: isValidResourcestatus(paramStatus) ? paramStatus : '1', create_after: params.get('create_after') ? new Date(params.get('create_after')!) : undefined, create_before: params.get('create_before') ? new Date(params.get('create_before')!) : undefined, expire_after: params.get('expire_after') ? new Date(params.get('expire_after')!) : undefined, expire_before: params.get('expire_before') ? new Date(params.get('expire_before')!) : undefined, }, }) const getValues = form.getValues // 查询数据 const refresh = useCallback(async (page: number, size: number) => { setStatus('load') try { const type = { all: undefined, expire: 1, quota: 2, }[getValues('type')] const status = getValues('status') const create_after = getValues('create_after') const create_before = getValues('create_before') const expire_after = getValues('expire_after') const expire_before = getValues('expire_before') const resource_no = getValues('resource_no') const listFn = isLong ? listResourceLong : listResourceShort const res = await listFn({ page, size, type, status: Number(status), create_after, create_before, expire_after, expire_before, resource_no, }) if (res.success) { setData(res.data) setStatus('done') } else { throw new Error(`Failed to load ${resourceType} resource`) } } catch (e) { setStatus('fail') toast.error(e instanceof Error ? e.message : `加载${isLong ? '长' : '短'}效资源失败`) } }, [getValues, setStatus, isLong, resourceType]) useEffect(() => { refresh(1, 10).then() }, [refresh]) // 处理筛选提交 const handleSubmit = async () => { await refresh(1, data.size) } // 处理重置 const handleReset = () => { form.reset({ type: 'all', status: '1', resource_no: '', create_after: undefined, create_before: undefined, expire_after: undefined, expire_before: undefined, }) } // 表格列定义 const columns = useMemo | Resource<2>>[]>(() => { const resourceKey = isLong ? 'long' : 'short' const baseColumns: ColumnDef | Resource<2>>[] = [ { header: '套餐编号', cell: ({row}) => { const expireAt = resourceKey === 'long' ? (row.original as Resource<2>).long.expire_at : (row.original as Resource<1>).short.expire_at return (
{row.original.resource_no}
) }, }, { header: '类型', cell: ({row}) => { const type = resourceKey === 'long' ? (row.original as Resource<2>).long.type : (row.original as Resource<1>).short.type return }, }, { header: 'IP 时效', cell: ({row}) => { const live = resourceKey === 'long' ? (row.original as Resource<2>).long.live : (row.original as Resource<1>).short.live return {isLong ? `${live}小时` : `${live}分钟`} }, }, { header: '使用情况', cell: ({row}) => { const resource = resourceKey === 'long' ? (row.original as Resource<2>).long : (row.original as Resource<1>).short if (resource.type === 1) { const todayUsage = getTodayUsage(resource.last_at, resource.daily) return (
{todayUsage}/{resource.quota}
) } else if (resource.type === 2) { if (isLong) { return (
{resource.used < resource.quota ? 正常 : 已用完} | 用量统计:{resource.used} / {resource.quota}
) } else { return (
{resource.used}/{resource.quota}
) } } return - }, }, { header: '最近使用时间', cell: ({row}) => { const lastAt = resourceKey === 'long' ? (row.original as Resource<2>).long.last_at : (row.original as Resource<1>).short.last_at return lastAt ? formatDateTime(lastAt) : '暂未使用' }, }, { header: '开通时间', cell: ({row}) => formatDateTime(row.original.created_at), }, { header: '状态', cell: ({row}) => { const isActive = row.original.active return ( {isActive ? '启用' : '禁用'} ) }, }, ] // 短效资源增加到期时间列 if (!isLong) { baseColumns.push({ header: '到期时间', cell: ({row}) => formatDateTime((row.original as Resource<1>).short.expire_at), }) } return baseColumns }, [isLong]) return ( <> {/* 操作区 */}
{/* 数据表 */} { await refresh(page, data.size) }, onSizeChange: async (size: number) => { await refresh(data.page, size) }, }} columns={columns} /> ) }