引入 husky,并全局重新格式化
This commit is contained in:
@@ -36,26 +36,26 @@ async function UserCenter() {
|
||||
>
|
||||
{profile.id_token
|
||||
? (
|
||||
<>
|
||||
<div className="flex gap-2 items-center">
|
||||
<CheckCircleIcon size={20} className="text-done"/>
|
||||
<span>已实名</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<span className="text-sm">{profile.name}</span>
|
||||
<span className="text-xs text-weak">{profile.id_no}</span>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
<>
|
||||
<div className="flex gap-2 items-center">
|
||||
<CheckCircleIcon size={20} className="text-done"/>
|
||||
<span>已实名</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<span className="text-sm">{profile.name}</span>
|
||||
<span className="text-xs text-weak">{profile.id_no}</span>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<span className="flex gap-2 items-center">
|
||||
<CircleAlertIcon className="text-warn"/>
|
||||
<span>未实名</span>
|
||||
</span>
|
||||
<Button className="h-9">去实名</Button>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<span className="flex gap-2 items-center">
|
||||
<CircleAlertIcon className="text-warn"/>
|
||||
<span>未实名</span>
|
||||
</span>
|
||||
<Button className="h-9">去实名</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm text-weak">账户余额</h4>
|
||||
|
||||
@@ -168,24 +168,24 @@ function Announcements(props: Props) {
|
||||
<CardContent className="flex-auto p-0">
|
||||
{!props.list.length
|
||||
? (
|
||||
<div className="flex flex-col items-center justify-center gap-2 h-full">
|
||||
<Image alt="coming soon" src={soon}/>
|
||||
<p>暂无公告</p>
|
||||
</div>
|
||||
)
|
||||
<div className="flex flex-col items-center justify-center gap-2 h-full">
|
||||
<Image alt="coming soon" src={soon}/>
|
||||
<p>暂无公告</p>
|
||||
</div>
|
||||
)
|
||||
: props.list.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={merge(
|
||||
`transition-colors duration-150 ease-in-out`,
|
||||
`flex flex-col gap-1 px-4 py-2`,
|
||||
`hover:bg-muted cursor-pointer`,
|
||||
)}
|
||||
>
|
||||
<h4>{item.title}</h4>
|
||||
<p className="text-sm text-weak">{format(item.created_at, 'yyyy-MM-dd HH:mm')}</p>
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
key={item.id}
|
||||
className={merge(
|
||||
`transition-colors duration-150 ease-in-out`,
|
||||
`flex flex-col gap-1 px-4 py-2`,
|
||||
`hover:bg-muted cursor-pointer`,
|
||||
)}
|
||||
>
|
||||
<h4>{item.title}</h4>
|
||||
<p className="text-sm text-weak">{format(item.created_at, 'yyyy-MM-dd HH:mm')}</p>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@@ -7,46 +7,50 @@ import Image from 'next/image'
|
||||
import logoAvatar from '../_assets/logo-avatar.svg'
|
||||
import logoText from '../_assets/logo-text.svg'
|
||||
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from '@/components/ui/tooltip'
|
||||
import { UserRound } from 'lucide-react'
|
||||
import { UserRoundPen } from 'lucide-react'
|
||||
import { IdCard } from 'lucide-react'
|
||||
import { LockKeyhole } from 'lucide-react'
|
||||
import { Wallet } from 'lucide-react'
|
||||
import { ShoppingCart } from 'lucide-react'
|
||||
import { Package } from 'lucide-react'
|
||||
import { HardDriveUpload } from 'lucide-react'
|
||||
import { Eye } from 'lucide-react'
|
||||
import { Archive } from 'lucide-react'
|
||||
import { ArchiveRestore } from 'lucide-react'
|
||||
|
||||
import {UserRound} from 'lucide-react'
|
||||
import {UserRoundPen} from 'lucide-react'
|
||||
import {IdCard} from 'lucide-react'
|
||||
import {LockKeyhole} from 'lucide-react'
|
||||
import {Wallet} from 'lucide-react'
|
||||
import {ShoppingCart} from 'lucide-react'
|
||||
import {Package} from 'lucide-react'
|
||||
import {HardDriveUpload} from 'lucide-react'
|
||||
import {Eye} from 'lucide-react'
|
||||
import {Archive} from 'lucide-react'
|
||||
import {ArchiveRestore} from 'lucide-react'
|
||||
|
||||
export type NavbarProps = {}
|
||||
|
||||
|
||||
export default function Navbar(props: NavbarProps) {
|
||||
|
||||
const navbar = useLayoutStore(store => store.navbar)
|
||||
|
||||
return (
|
||||
<nav data-expand={navbar} className={merge(
|
||||
`transition-[flex-basis] duration-300 ease-in-out`,
|
||||
`flex-none`,
|
||||
`flex flex-col overflow-hidden group`,
|
||||
`data-[expand=true]:basis-52 data-[expand=false]:basis-16`,
|
||||
` `,
|
||||
)}>
|
||||
{/* logo */}
|
||||
<Link href={'/'} className={merge(
|
||||
`flex-none h-[64px] flex items-center justify-center`,
|
||||
<nav
|
||||
data-expand={navbar}
|
||||
className={merge(
|
||||
`transition-[flex-basis] duration-300 ease-in-out`,
|
||||
`flex-none`,
|
||||
`flex flex-col overflow-hidden group`,
|
||||
`data-[expand=true]:basis-52 data-[expand=false]:basis-16`,
|
||||
` `,
|
||||
)}>
|
||||
<Image src={logoAvatar} alt={`logo`} className={`w-10 h-10 object-contain`}/>
|
||||
<Image src={logoText} alt={`logo`} className={merge(
|
||||
`h-10 translate-1 object-cover object-left`,
|
||||
`transition-[opacity,width] duration-[200ms,300ms] ease-in-out`,
|
||||
`group-data-[expand=true]:delay-[100ms,0ms]`,
|
||||
`group-data-[expand=true]:opacity-100 group-data-[expand=false]:opacity-0`,
|
||||
`group-data-[expand=true]:w-[85px] group-data-[expand=false]:w-0`,
|
||||
)}/>
|
||||
{/* logo */}
|
||||
<Link
|
||||
href="/"
|
||||
className={merge(
|
||||
`flex-none h-[64px] flex items-center justify-center`,
|
||||
)}>
|
||||
<Image src={logoAvatar} alt="logo" className="w-10 h-10 object-contain"/>
|
||||
<Image
|
||||
src={logoText}
|
||||
alt="logo"
|
||||
className={merge(
|
||||
`h-10 translate-1 object-cover object-left`,
|
||||
`transition-[opacity,width] duration-[200ms,300ms] ease-in-out`,
|
||||
`group-data-[expand=true]:delay-[100ms,0ms]`,
|
||||
`group-data-[expand=true]:opacity-100 group-data-[expand=false]:opacity-0`,
|
||||
`group-data-[expand=true]:w-[85px] group-data-[expand=false]:w-0`,
|
||||
)}/>
|
||||
</Link>
|
||||
|
||||
{/* routes */}
|
||||
@@ -56,20 +60,20 @@ export default function Navbar(props: NavbarProps) {
|
||||
`group-data-[expand=true]:px-4 group-data-[expand=false]:px-3`,
|
||||
)}>
|
||||
<TooltipProvider>
|
||||
<NavItem href={'/admin'} icon={<UserRound size={20}/>} label={`账户总览`} expand={navbar}/>
|
||||
<NavTitle label={`个人信息`}/>
|
||||
<NavItem href={`/admin/profile`} icon={<UserRoundPen size={20}/>} label={`个人中心`} expand={navbar}/>
|
||||
<NavItem href={`/admin/identify`} icon={<IdCard size={20}/>} label={`实名认证`} expand={navbar}/>
|
||||
<NavItem href={`/admin/whitelist`} icon={<LockKeyhole size={20}/>} label={`白名单`} expand={navbar}/>
|
||||
<NavItem href={`/admin/bills`} icon={<Wallet size={20}/>} label={`我的账单`} expand={navbar}/>
|
||||
<NavTitle label={`套餐管理`}/>
|
||||
<NavItem href={`/admin/purchase`} icon={<ShoppingCart size={20}/>} label={`购买套餐`} expand={navbar}/>
|
||||
<NavItem href={`/admin/resources`} icon={<Package size={20}/>} label={`套餐管理`} expand={navbar}/>
|
||||
<NavTitle label={`IP 管理`}/>
|
||||
<NavItem href={`/admin/extract`} icon={<HardDriveUpload size={20}/>} label={`提取 IP`} expand={navbar}/>
|
||||
<NavItem href={`/admin/channels`} icon={<Eye size={20}/>} label={`IP 管理`} expand={navbar}/>
|
||||
<NavItem href={`/admin`} icon={<Archive size={20}/>} label={`提取记录`} expand={navbar}/>
|
||||
<NavItem href={`/admin`} icon={<ArchiveRestore size={20}/>} label={`使用记录`} expand={navbar}/>
|
||||
<NavItem href="/admin" icon={<UserRound size={20}/>} label="账户总览" expand={navbar}/>
|
||||
<NavTitle label="个人信息"/>
|
||||
<NavItem href="/admin/profile" icon={<UserRoundPen size={20}/>} label="个人中心" expand={navbar}/>
|
||||
<NavItem href="/admin/identify" icon={<IdCard size={20}/>} label="实名认证" expand={navbar}/>
|
||||
<NavItem href="/admin/whitelist" icon={<LockKeyhole size={20}/>} label="白名单" expand={navbar}/>
|
||||
<NavItem href="/admin/bills" icon={<Wallet size={20}/>} label="我的账单" expand={navbar}/>
|
||||
<NavTitle label="套餐管理"/>
|
||||
<NavItem href="/admin/purchase" icon={<ShoppingCart size={20}/>} label="购买套餐" expand={navbar}/>
|
||||
<NavItem href="/admin/resources" icon={<Package size={20}/>} label="套餐管理" expand={navbar}/>
|
||||
<NavTitle label="IP 管理"/>
|
||||
<NavItem href="/admin/extract" icon={<HardDriveUpload size={20}/>} label="提取 IP" expand={navbar}/>
|
||||
<NavItem href="/admin/channels" icon={<Eye size={20}/>} label="IP 管理" expand={navbar}/>
|
||||
<NavItem href="/admin" icon={<Archive size={20}/>} label="提取记录" expand={navbar}/>
|
||||
<NavItem href="/admin" icon={<ArchiveRestore size={20}/>} label="使用记录" expand={navbar}/>
|
||||
</TooltipProvider>
|
||||
</section>
|
||||
</nav>
|
||||
@@ -89,11 +93,14 @@ function NavTitle(props: {
|
||||
<span className={merge(
|
||||
`transition-[opacity] duration-200 ease-in-out absolute mx-4`,
|
||||
`group-data-[expand=true]:delay-100 group-data-[expand=true]:opacity-100 group-data-[expand=false]:opacity-0`,
|
||||
)}>{props.label}</span>
|
||||
)}>
|
||||
{props.label}
|
||||
</span>
|
||||
<span className={merge(
|
||||
`transition-[opacity] duration-200 ease-in-out absolute w-full border-b block`,
|
||||
`group-data-[expand=false]:delay-100 group-data-[expand=false]:opacity-100 group-data-[expand=true]:opacity-0`,
|
||||
)}></span>
|
||||
)}>
|
||||
</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
@@ -104,7 +111,6 @@ function NavItem(props: {
|
||||
label: string
|
||||
expand?: boolean
|
||||
}) {
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
@@ -116,22 +122,26 @@ function NavItem(props: {
|
||||
return (
|
||||
<Tooltip open={open} onOpenChange={handleOpenChange}>
|
||||
<TooltipTrigger asChild>
|
||||
<Link className={merge(
|
||||
`transition-[padding] duration-300 ease-in-out`,
|
||||
`flex items-center rounded-md gap-2 whitespace-nowrap`,
|
||||
`hover:bg-gray-100`,
|
||||
`group-data-[expand=true]:px-4`,
|
||||
)} href={props.href}>
|
||||
<span className={`flex-none w-10 h-10 flex items-center justify-center`}>{props.icon}</span>
|
||||
<Link
|
||||
className={merge(
|
||||
`transition-[padding] duration-300 ease-in-out`,
|
||||
`flex items-center rounded-md gap-2 whitespace-nowrap`,
|
||||
`hover:bg-gray-100`,
|
||||
`group-data-[expand=true]:px-4`,
|
||||
)}
|
||||
href={props.href}>
|
||||
<span className="flex-none w-10 h-10 flex items-center justify-center">{props.icon}</span>
|
||||
<span className={merge(
|
||||
`flex-auto`,
|
||||
`transition-[width,opacity] duration-300 ease-in-out`,
|
||||
`group-data-[expand=true]:w-auto group-data-[expand=true]:opacity-100`,
|
||||
`group-data-[expand=false]:w-0 group-data-[expand=false]:opacity-0`,
|
||||
)}>{props.label}</span>
|
||||
)}>
|
||||
{props.label}
|
||||
</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side={`right`} sideOffset={16}>
|
||||
<TooltipContent side="right" sideOffset={16}>
|
||||
<p>{props.label}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
import { Metadata } from 'next'
|
||||
import {ReactNode} from 'react'
|
||||
import {Metadata} from 'next'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
}
|
||||
|
||||
export type BillsLayoutProps = {
|
||||
children: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default async function BillsLayout(props: BillsLayoutProps) {
|
||||
return props.children
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ type FilterSchema = zod.infer<typeof filterSchema>
|
||||
export type BillsPageProps = {}
|
||||
|
||||
export default function BillsPage(props: BillsPageProps) {
|
||||
|
||||
// ======================
|
||||
// 查询
|
||||
// ======================
|
||||
@@ -95,58 +94,58 @@ export default function BillsPage(props: BillsPageProps) {
|
||||
<Page>
|
||||
|
||||
{/* 操作区 */}
|
||||
<section className={`flex justify-between flex-wrap`}>
|
||||
<section className="flex justify-between flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold">账单管理</h1>
|
||||
</div>
|
||||
|
||||
<Form form={form} onSubmit={onSubmit} className={`flex items-end gap-4`}>
|
||||
<FormField name={`type`} label={<span className={`text-sm`}>账单类型</span>}>
|
||||
<Form form={form} onSubmit={onSubmit} className="flex items-end gap-4">
|
||||
<FormField name="type" label={<span className="text-sm">账单类型</span>}>
|
||||
{({id, field}) => (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger className={`w-24 h-9`}>
|
||||
<SelectValue placeholder={`选择类型`}/>
|
||||
<SelectTrigger className="w-24 h-9">
|
||||
<SelectValue placeholder="选择类型"/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={`all`}>全部</SelectItem>
|
||||
<SelectItem value={`3`}>充值</SelectItem>
|
||||
<SelectItem value={`1`}>消费</SelectItem>
|
||||
<SelectItem value={`2`}>退款</SelectItem>
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
<SelectItem value="3">充值</SelectItem>
|
||||
<SelectItem value="1">消费</SelectItem>
|
||||
<SelectItem value="2">退款</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<Label className={`text-sm`}>创建时间</Label>
|
||||
<div className={`flex items-center`}>
|
||||
<FormField name={`create_after`}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-sm">创建时间</Label>
|
||||
<div className="flex items-center">
|
||||
<FormField name="create_after">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`开始时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
className="w-36"
|
||||
placeholder="开始时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<span className={`px-1`}>-</span>
|
||||
<FormField name={`create_before`}>
|
||||
<span className="px-1">-</span>
|
||||
<FormField name="create_before">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`结束时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
className="w-36"
|
||||
placeholder="结束时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
<Button className={`h-9`} type="submit">
|
||||
<Button className="h-9" type="submit">
|
||||
<Search/>
|
||||
<span>筛选</span>
|
||||
</Button>
|
||||
<Button theme={`outline`} className={`h-9`} type="button" onClick={() => form.reset()}>
|
||||
<Button theme="outline" className="h-9" type="button" onClick={() => form.reset()}>
|
||||
<Eraser/>
|
||||
<span>重置</span>
|
||||
</Button>
|
||||
@@ -173,21 +172,21 @@ export default function BillsPage(props: BillsPageProps) {
|
||||
accessorKey: 'bill_no', header: `账单编号`,
|
||||
}, {
|
||||
accessorKey: 'type', header: `类型`, cell: ({row}) => (
|
||||
<div className={`flex gap-2 items-center`}>
|
||||
<div className="flex gap-2 items-center">
|
||||
{row.original.type === 1 && (
|
||||
<div className={`flex gap-2 items-center bg-orange-50 w-fit px-2 py-1 rounded-md`}>
|
||||
<div className="flex gap-2 items-center bg-orange-50 w-fit px-2 py-1 rounded-md">
|
||||
<CreditCard size={16}/>
|
||||
<span>消费</span>
|
||||
</div>
|
||||
)}
|
||||
{row.original.type === 2 && (
|
||||
<div className={`flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md`}>
|
||||
<div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md">
|
||||
<CreditCard size={16}/>
|
||||
<span>退款</span>
|
||||
</div>
|
||||
)}
|
||||
{row.original.type === 3 && (
|
||||
<div className={`flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md`}>
|
||||
<div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md">
|
||||
<CreditCard size={16}/>
|
||||
<span>充值</span>
|
||||
</div>
|
||||
@@ -203,28 +202,28 @@ export default function BillsPage(props: BillsPageProps) {
|
||||
<>
|
||||
{row.original.trade && (
|
||||
row.original.trade.status === 0 ? (
|
||||
<div className={`flex flex-col gap-1`}>
|
||||
<div className={`flex gap-1 items-center text-warn`}>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1 items-center text-warn">
|
||||
<ClockIcon size={16}/>
|
||||
<span>订单待支付</span>
|
||||
<Link href={`/admin/bills`} className={`text-sm underline text-blue-500`}>
|
||||
<Link href="/admin/bills" className="text-sm underline text-blue-500">
|
||||
{row.original.trade.inner_no}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
) : row.original.trade.status === 1 ? (
|
||||
<div className={`flex gap-1 items-center text-done`}>
|
||||
<div className="flex gap-1 items-center text-done">
|
||||
<CheckCircle size={16}/>
|
||||
<span>已完成</span>
|
||||
</div>
|
||||
) : row.original.trade.status === 2 ? (
|
||||
<div className={`flex gap-1 items-center text-weak`}>
|
||||
<div className="flex gap-1 items-center text-weak">
|
||||
<AlertCircle size={16}/>
|
||||
<span>已取消</span>
|
||||
</div>
|
||||
) : row.original.trade.status === 3 ? (
|
||||
<div className={`flex gap-1 items-center text-fail`}>
|
||||
<div className="flex gap-1 items-center text-fail">
|
||||
<AlertCircle size={16}/>
|
||||
<span>已退款</span>
|
||||
</div>
|
||||
@@ -237,15 +236,18 @@ export default function BillsPage(props: BillsPageProps) {
|
||||
},
|
||||
{
|
||||
accessorKey: 'amount', header: `支付信息`, cell: ({row}) => (
|
||||
<div className={`flex gap-1`}>
|
||||
<span className={`text-sm`}>
|
||||
<div className="flex gap-1">
|
||||
<span className="text-sm">
|
||||
{!row.original.trade && '余额'}
|
||||
{row.original.trade && row.original.trade.method === 1 && '支付宝'}
|
||||
{row.original.trade && row.original.trade.method === 2 && '微信'}
|
||||
</span>
|
||||
<span className={
|
||||
row.original.amount > 0 ? `text-green-400` : `text-orange-400`
|
||||
}>¥{row.original.amount}</span>
|
||||
}>
|
||||
¥
|
||||
{row.original.amount}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -255,8 +257,8 @@ export default function BillsPage(props: BillsPageProps) {
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'action', header: `操作`, cell: (item) => (
|
||||
<div className={`flex gap-2`}>
|
||||
accessorKey: 'action', header: `操作`, cell: item => (
|
||||
<div className="flex gap-2">
|
||||
-
|
||||
</div>
|
||||
),
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
import { Metadata } from 'next'
|
||||
import {ReactNode} from 'react'
|
||||
import {Metadata} from 'next'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
title: 'IP管理 - 蓝狐代理',
|
||||
}
|
||||
return {
|
||||
title: 'IP管理 - 蓝狐代理',
|
||||
}
|
||||
}
|
||||
|
||||
export type ChannelsLayoutProps = {
|
||||
children: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default async function ChannelsLayout(props: ChannelsLayoutProps) {
|
||||
return props.children
|
||||
}
|
||||
return props.children
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/c
|
||||
export type ChannelsPageProps = {}
|
||||
|
||||
export default function ChannelsPage(props: ChannelsPageProps) {
|
||||
|
||||
// ======================
|
||||
// data
|
||||
// ======================
|
||||
@@ -88,7 +87,7 @@ export default function ChannelsPage(props: ChannelsPageProps) {
|
||||
expire_before: undefined,
|
||||
},
|
||||
})
|
||||
const filterHandler = filterForm.handleSubmit(async value => {
|
||||
const filterHandler = filterForm.handleSubmit(async (value) => {
|
||||
await refresh(data.page, data.size)
|
||||
})
|
||||
|
||||
@@ -98,46 +97,48 @@ export default function ChannelsPage(props: ChannelsPageProps) {
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<section className={`flex justify-between`}>
|
||||
<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>}>
|
||||
<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 className="h-9 w-36">
|
||||
<SelectValue placeholder="选择认证方式"/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={'0'}>全部</SelectItem>
|
||||
<SelectItem value={'1'}>IP 白名单</SelectItem>
|
||||
<SelectItem value={'2'}>账号密码</SelectItem>
|
||||
<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`}>
|
||||
<fieldset className="flex flex-col gap-2 items-start">
|
||||
<div>
|
||||
<legend className={`block text-sm`}>过期时间</legend>
|
||||
<legend className="block text-sm">过期时间</legend>
|
||||
</div>
|
||||
<div className={`flex gap-1 items-center`}>
|
||||
<FormField<FilterSchema, 'expire_after'> name={`expire_after`}>
|
||||
<div className="flex gap-1 items-center">
|
||||
<FormField<FilterSchema, 'expire_after'> name="expire_after">
|
||||
{({field}) => (
|
||||
<DatePicker placeholder={`选择开始时间`} {...field} format={`yyyy-MM-dd`}/>
|
||||
<DatePicker placeholder="选择开始时间" {...field} format="yyyy-MM-dd"/>
|
||||
)}
|
||||
</FormField>
|
||||
<span>-</span>
|
||||
<FormField<FilterSchema, 'expire_before'> name={`expire_before`}>
|
||||
<FormField<FilterSchema, 'expire_before'> name="expire_before">
|
||||
{({field}) => (
|
||||
<DatePicker placeholder={`选择结束时间`} {...field} format={`yyyy-MM-dd`}/>
|
||||
<DatePicker placeholder="选择结束时间" {...field} format="yyyy-MM-dd"/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
</fieldset>
|
||||
<Button className={`h-9`}>
|
||||
<SearchIcon/>筛选
|
||||
<Button className="h-9">
|
||||
<SearchIcon/>
|
||||
筛选
|
||||
</Button>
|
||||
<Button theme={`outline`} className={`h-9`} onClick={() => filterForm.reset()}>
|
||||
<EraserIcon/>重置
|
||||
<Button theme="outline" className="h-9" onClick={() => filterForm.reset()}>
|
||||
<EraserIcon/>
|
||||
重置
|
||||
</Button>
|
||||
</Form>
|
||||
</section>
|
||||
@@ -149,8 +150,8 @@ export default function ChannelsPage(props: ChannelsPageProps) {
|
||||
page: data.page,
|
||||
size: data.size,
|
||||
total: data.total,
|
||||
onPageChange: (page) => refresh(page, data.size),
|
||||
onSizeChange: (size) => refresh(1, size),
|
||||
onPageChange: page => refresh(page, data.size),
|
||||
onSizeChange: size => refresh(1, size),
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
@@ -158,16 +159,26 @@ export default function ChannelsPage(props: ChannelsPageProps) {
|
||||
},
|
||||
{
|
||||
header: '认证方式', cell: ({row}) => {
|
||||
return <div className={`flex flex-col gap-1`}>
|
||||
{row.original.auth_ip && (<>
|
||||
<span className={`text-weak`}>IP 白名单</span>
|
||||
<span>{row.original.whitelists.replaceAll(",", ", ")}</span>
|
||||
</>)}
|
||||
{row.original.auth_pass && (<>
|
||||
<span className={`text-weak`}>账号密码</span>
|
||||
<span>{row.original.username}:{row.original.password}</span>
|
||||
</>)}
|
||||
</div>
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
{row.original.auth_ip && (
|
||||
<>
|
||||
<span className="text-weak">IP 白名单</span>
|
||||
<span>{row.original.whitelists.replaceAll(',', ', ')}</span>
|
||||
</>
|
||||
)}
|
||||
{row.original.auth_pass && (
|
||||
<>
|
||||
<span className="text-weak">账号密码</span>
|
||||
<span>
|
||||
{row.original.username}
|
||||
:
|
||||
{row.original.password}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -181,4 +192,3 @@ export default function ChannelsPage(props: ChannelsPageProps) {
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
import { Metadata } from 'next'
|
||||
import {ReactNode} from 'react'
|
||||
import {Metadata} from 'next'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
}
|
||||
|
||||
export type ExtractLayoutProps = {
|
||||
children: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default async function ExtractLayout(props: ExtractLayoutProps) {
|
||||
return props.children
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export type ExtractPageProps = {}
|
||||
export default async function ExtractPage(props: ExtractPageProps) {
|
||||
return (
|
||||
<Page>
|
||||
<Extract className={`p-8`}/>
|
||||
<Extract className="p-8"/>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { Metadata } from 'next'
|
||||
import {ReactNode} from 'react'
|
||||
import {Metadata} from 'next'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
@@ -8,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
}
|
||||
|
||||
export type IdentifyLayoutProps = {
|
||||
children: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default async function IdentifyLayout(props: IdentifyLayoutProps) {
|
||||
return props.children
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import {CheckCircle, CheckCircleIcon, WorkflowIcon} from 'lucide-react'
|
||||
export type IdentifyPageProps = {}
|
||||
|
||||
export default function IdentifyPage(props: IdentifyPageProps) {
|
||||
|
||||
// ======================
|
||||
// 填写信息
|
||||
// ======================
|
||||
@@ -104,72 +103,72 @@ export default function IdentifyPage(props: IdentifyPageProps) {
|
||||
// ======================
|
||||
|
||||
return (
|
||||
<Page className={`flex-row`}>
|
||||
<div className={`flex-3/4 flex flex-col bg-white rounded-lg overflow-hidden gap-16`}>
|
||||
<Page className="flex-row">
|
||||
<div className="flex-3/4 flex flex-col bg-white rounded-lg overflow-hidden gap-16">
|
||||
|
||||
{/* banner */}
|
||||
<section className={`flex-none basis-40 relative flex flex-col gap-4 pl-8 justify-center`}>
|
||||
<Image src={banner} alt={`背景图`} aria-hidden className={`absolute inset-0 w-full h-full object-cover`}/>
|
||||
<h3 className={`text-lg font-bold z-10 relative`}>蓝狐HTTP邀请您参与【先测后买】服务</h3>
|
||||
<p className={`text-sm text-gray-600 z-10 relative`}>为了保障您的账户安全,请先完成实名认证,即可获取福利套餐测试资格</p>
|
||||
<section className="flex-none basis-40 relative flex flex-col gap-4 pl-8 justify-center">
|
||||
<Image src={banner} alt="背景图" aria-hidden className="absolute inset-0 w-full h-full object-cover"/>
|
||||
<h3 className="text-lg font-bold z-10 relative">蓝狐HTTP邀请您参与【先测后买】服务</h3>
|
||||
<p className="text-sm text-gray-600 z-10 relative">为了保障您的账户安全,请先完成实名认证,即可获取福利套餐测试资格</p>
|
||||
</section>
|
||||
|
||||
<div className={`flex-auto flex justify-center items-start`}>
|
||||
<div className="flex-auto flex justify-center items-start">
|
||||
{/* 个人认证 */}
|
||||
<section className={`w-96 bg-gray-50 p-8 rounded-md flex flex-col gap-4 items-center`}>
|
||||
<Image src={personal} alt={`个人认证`}/>
|
||||
<section className="w-96 bg-gray-50 p-8 rounded-md flex flex-col gap-4 items-center">
|
||||
<Image src={personal} alt="个人认证"/>
|
||||
<div>
|
||||
<h3 className={`text-center text-lg font-bold`}>个人认证</h3>
|
||||
<p className={`text-sm text-gray-600`}>
|
||||
<h3 className="text-center text-lg font-bold">个人认证</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
平台不会收集您的个人信息,您的信息仅用于账户安全认证
|
||||
</p>
|
||||
</div>
|
||||
{profile?.id_token ? (
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<CheckCircleIcon className={`text-done`}/>
|
||||
<p className="flex gap-2 items-center">
|
||||
<CheckCircleIcon className="text-done"/>
|
||||
<span>已完成实名认证</span>
|
||||
</p>
|
||||
) : (
|
||||
<Dialog open={openDialog} onOpenChange={setOpenDialog}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className={`w-full`}>去认证</Button>
|
||||
<Button className="w-full">去认证</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
{step === 'form' ? `实名认证` : `扫码完成认证`}
|
||||
</DialogTitle>
|
||||
{step === 'form' && (
|
||||
<Form form={form} handler={handler} className={`flex flex-col gap-4`}>
|
||||
<FormField<Schema> name={`name`} label={`姓名`}>
|
||||
<Form form={form} handler={handler} className="flex flex-col gap-4">
|
||||
<FormField<Schema> name="name" label="姓名">
|
||||
{({id, field}) => (
|
||||
<input
|
||||
{...field}
|
||||
id={id}
|
||||
placeholder={`请输入姓名`}
|
||||
className={`border rounded p-2 w-full`}
|
||||
autoComplete={`name`}
|
||||
placeholder="请输入姓名"
|
||||
className="border rounded p-2 w-full"
|
||||
autoComplete="name"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema> name={`iden_no`} label={`身份证号`}>
|
||||
<FormField<Schema> name="iden_no" label="身份证号">
|
||||
{({id, field}) => (
|
||||
<input
|
||||
{...field}
|
||||
id={id}
|
||||
placeholder={`请输入身份证号`}
|
||||
className={`border rounded p-2 w-full`}
|
||||
placeholder="请输入身份证号"
|
||||
className="border rounded p-2 w-full"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<DialogFooter>
|
||||
<Button type={`submit`} className={`w-full mt-4`}>提交</Button>
|
||||
<Button type="submit" className="w-full mt-4">提交</Button>
|
||||
</DialogFooter>
|
||||
</Form>
|
||||
)}
|
||||
{step === 'scan' && (
|
||||
<div className={`flex flex-col gap-4 items-center`}>
|
||||
<div className="flex flex-col gap-4 items-center">
|
||||
<canvas ref={canvas} width={256} height={256}/>
|
||||
<p className={`text-sm text-gray-600`}>请扫码完成认证</p>
|
||||
<p className="text-sm text-gray-600">请扫码完成认证</p>
|
||||
<Button onClick={async () => {
|
||||
await refreshProfile()
|
||||
setOpenDialog(false)
|
||||
@@ -185,48 +184,51 @@ export default function IdentifyPage(props: IdentifyPageProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className={`flex-none basis-80`}>
|
||||
<Card className="flex-none basis-80">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<WorkflowIcon size={18}/>操作引导
|
||||
<WorkflowIcon size={18}/>
|
||||
操作引导
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className={`flex flex-col px-4`}>
|
||||
<p className={`text-sm text-weak mb-4`}>
|
||||
<CardContent className="flex flex-col px-4">
|
||||
<p className="text-sm text-weak mb-4">
|
||||
为响应国家相关规定,使用HTTP代理需完成实名认证。认证服务由支付宝提供,您的个人信息将受到严格保护,仅用于账户安全认证
|
||||
</p>
|
||||
<p className={`flex gap-2 items-center justify-between w-56 self-center`}>
|
||||
<span className={`flex gap-2`}>
|
||||
<span className={`bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center`}>01</span>
|
||||
<span>注册账号</span>
|
||||
</span>
|
||||
<Image alt={`步骤配图`} src={step1} aria-hidden/>
|
||||
<p className="flex gap-2 items-center justify-between w-56 self-center">
|
||||
<span className="flex gap-2">
|
||||
<span className="bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center">01</span>
|
||||
<span>注册账号</span>
|
||||
</span>
|
||||
<Image alt="步骤配图" src={step1} aria-hidden/>
|
||||
</p>
|
||||
<div className={`h-16 w-56 px-4 flex self-center`}>
|
||||
<div className="h-16 w-56 px-4 flex self-center">
|
||||
<div className={merge(
|
||||
`w-0 h-full border-x border-primary border-dashed relative`,
|
||||
`after:absolute after:-left-[3px] after:bottom-0 after:w-[6px] after:h-[6px] after:rounded-full after:bg-primary`,
|
||||
)}></div>
|
||||
)}>
|
||||
</div>
|
||||
</div>
|
||||
<p className={`flex gap-2 items-center justify-between w-56 self-center`}>
|
||||
<span className={`flex gap-2`}>
|
||||
<span className={`bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center`}>02</span>
|
||||
<span>实名认证</span>
|
||||
</span>
|
||||
<Image alt={`步骤配图`} src={step2} aria-hidden/>
|
||||
<p className="flex gap-2 items-center justify-between w-56 self-center">
|
||||
<span className="flex gap-2">
|
||||
<span className="bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center">02</span>
|
||||
<span>实名认证</span>
|
||||
</span>
|
||||
<Image alt="步骤配图" src={step2} aria-hidden/>
|
||||
</p>
|
||||
<div className={`h-16 w-56 px-4 flex self-center`}>
|
||||
<div className="h-16 w-56 px-4 flex self-center">
|
||||
<div className={merge(
|
||||
`w-0 h-full border-x border-primary border-dashed relative`,
|
||||
`after:absolute after:-left-[3px] after:bottom-0 after:w-[6px] after:h-[6px] after:rounded-full after:bg-primary`,
|
||||
)}></div>
|
||||
)}>
|
||||
</div>
|
||||
</div>
|
||||
<p className={`flex gap-2 items-center justify-between w-56 self-center`}>
|
||||
<span className={`flex gap-2`}>
|
||||
<span className={`bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center`}>03</span>
|
||||
<span>充值、支付</span>
|
||||
</span>
|
||||
<Image alt={`步骤配图`} src={step3} aria-hidden/>
|
||||
<p className="flex gap-2 items-center justify-between w-56 self-center">
|
||||
<span className="flex gap-2">
|
||||
<span className="bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center">03</span>
|
||||
<span>充值、支付</span>
|
||||
</span>
|
||||
<Image alt="步骤配图" src={step3} aria-hidden/>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -16,7 +16,7 @@ export default async function AdminLayout(props: AdminLayoutProps) {
|
||||
|
||||
<Navbar/>
|
||||
|
||||
<div className={`flex-auto flex flex-col items-stretch`}>
|
||||
<div className="flex-auto flex flex-col items-stretch">
|
||||
<Header/>
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
import { Metadata } from 'next'
|
||||
import {ReactNode} from 'react'
|
||||
import {Metadata} from 'next'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
}
|
||||
|
||||
export type ProfileLayoutProps = {
|
||||
children: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default async function ProfileLayout(props: ProfileLayoutProps) {
|
||||
return props.children
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import {useRouter} from 'next/navigation'
|
||||
export type ProfilePageProps = {}
|
||||
|
||||
export default function ProfilePage(props: ProfilePageProps) {
|
||||
|
||||
const router = useRouter()
|
||||
const profile = useProfileStore(store => store.profile)
|
||||
|
||||
@@ -42,9 +41,9 @@ export default function ProfilePage(props: ProfilePageProps) {
|
||||
if (!profile) {
|
||||
return (
|
||||
<Page>
|
||||
<div className={`flex flex-col gap-4`}>
|
||||
<h3 className={`text-lg font-bold`}>加载中...</h3>
|
||||
<p className={`text-sm text-gray-600`}>请稍等片刻</p>
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="text-lg font-bold">加载中...</h3>
|
||||
<p className="text-sm text-gray-600">请稍等片刻</p>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
@@ -52,59 +51,63 @@ export default function ProfilePage(props: ProfilePageProps) {
|
||||
|
||||
return (
|
||||
<Page className="flex-row items-start">
|
||||
<div className={`flex-3/4 flex flex-col gap-4`}>
|
||||
<div className="flex-3/4 flex flex-col gap-4">
|
||||
{/* banner */}
|
||||
<section className={`flex-none basis-40 relative rounded-lg overflow-hidden flex flex-col gap-4 pl-8 justify-center`}>
|
||||
<Image src={banner} alt={`背景图`} aria-hidden className={`absolute inset-0 w-full h-full object-cover`}/>
|
||||
<h3 className={`text-lg font-bold z-10 relative`}>蓝狐HTTP邀请您参与【先测后买】服务</h3>
|
||||
<p className={`text-sm text-gray-600 z-10 relative`}>为了保障您的账户安全,请先完成实名认证,即可获取福利套餐测试资格</p>
|
||||
<section className="flex-none basis-40 relative rounded-lg overflow-hidden flex flex-col gap-4 pl-8 justify-center">
|
||||
<Image src={banner} alt="背景图" aria-hidden className="absolute inset-0 w-full h-full object-cover"/>
|
||||
<h3 className="text-lg font-bold z-10 relative">蓝狐HTTP邀请您参与【先测后买】服务</h3>
|
||||
<p className="text-sm text-gray-600 z-10 relative">为了保障您的账户安全,请先完成实名认证,即可获取福利套餐测试资格</p>
|
||||
</section>
|
||||
|
||||
{/* 块信息 */}
|
||||
<div className={`flex-none basis-40 flex gap-4`}>
|
||||
<div className="flex-none basis-40 flex gap-4">
|
||||
|
||||
<Card className={`flex-1`}>
|
||||
<Card className="flex-1">
|
||||
<CardHeader>
|
||||
<CardTitle className={`font-normal`}>帐号余额(元)</CardTitle>
|
||||
<CardTitle className="font-normal">帐号余额(元)</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className={`flex-auto flex justify-between items-center px-8`}>
|
||||
<p className={`text-xl`}>{profile?.balance}</p>
|
||||
<CardContent className="flex-auto flex justify-between items-center px-8">
|
||||
<p className="text-xl">{profile?.balance}</p>
|
||||
<RechargeModal classNames={{
|
||||
trigger: `h-10 px-6`,
|
||||
}}/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className={`flex-1`}>
|
||||
<Card className="flex-1">
|
||||
<CardHeader>
|
||||
<CardTitle className={`font-normal`}>实名认证</CardTitle>
|
||||
<CardTitle className="font-normal">实名认证</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className={`flex-auto flex justify-between items-center px-8`}>
|
||||
<CardContent className="flex-auto flex justify-between items-center px-8">
|
||||
{!profile?.id_token
|
||||
? <>
|
||||
<p className={`text-sm`}>为了保障您的账户安全和正常使用服务,请您尽快完成实名认证</p>
|
||||
<Button theme={`outline`} className={`mx-16 w-24`} onClick={() => router.push('/admin/identify')}>去认证</Button>
|
||||
</>
|
||||
: <>
|
||||
<p className={`flex flex-col gap-1`}>
|
||||
<span>{profile.name}</span>
|
||||
<span className={`text-sm`}>{profile.id_no}</span>
|
||||
</p>
|
||||
<p className={`flex gap-1 items-center`}>
|
||||
<CheckCircle className={`text-done`} size={18}/>
|
||||
<span>已认证</span>
|
||||
</p>
|
||||
</>}
|
||||
? (
|
||||
<>
|
||||
<p className="text-sm">为了保障您的账户安全和正常使用服务,请您尽快完成实名认证</p>
|
||||
<Button theme="outline" className="mx-16 w-24" onClick={() => router.push('/admin/identify')}>去认证</Button>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<p className="flex flex-col gap-1">
|
||||
<span>{profile.name}</span>
|
||||
<span className="text-sm">{profile.id_no}</span>
|
||||
</p>
|
||||
<p className="flex gap-1 items-center">
|
||||
<CheckCircle className="text-done" size={18}/>
|
||||
<span>已认证</span>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className={`flex-auto shrink-0 basis-80 rounded-lg bg-white p-4 flex flex-col gap-8`}>
|
||||
<div className="flex-auto shrink-0 basis-80 rounded-lg bg-white p-4 flex flex-col gap-8">
|
||||
|
||||
{/* 安全信息 */}
|
||||
<div className={`flex flex-col gap-4`}>
|
||||
<h3 className={`font-normal`}>安全信息</h3>
|
||||
<div className={`flex gap-4 items-center`}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="font-normal">安全信息</h3>
|
||||
<div className="flex gap-4 items-center">
|
||||
<p>{profile.phone}</p>
|
||||
<PasswordForm profile={profile}/>
|
||||
</div>
|
||||
@@ -133,7 +136,7 @@ function Aftersale(props: {
|
||||
qrcode.toCanvas(canvasRef.current, String(admin), {
|
||||
width: 180,
|
||||
margin: 0,
|
||||
}).catch(err => {
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
@@ -143,32 +146,36 @@ function Aftersale(props: {
|
||||
<Card className="flex-none basis-80">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<QrCodeIcon size={18}/> 关于售后
|
||||
<QrCodeIcon size={18}/>
|
||||
{' '}
|
||||
关于售后
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className={`flex flex-col gap-8`}>
|
||||
<CardContent className="flex flex-col gap-8">
|
||||
|
||||
<div className={`flex flex-col gap-4`}>
|
||||
<p className={`text-weak text-sm`}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-weak text-sm">
|
||||
1.全国100万+动态IP代理资源免费测试,先测后买让您安心使用。
|
||||
</p>
|
||||
|
||||
<p className={`text-weak text-sm`}>
|
||||
<p className="text-weak text-sm">
|
||||
2.注册即享新人福利,专业的客户经理,多维度为您提供在线代理相关答疑
|
||||
</p>
|
||||
|
||||
<p className={`text-weak text-sm`}>
|
||||
<p className="text-weak text-sm">
|
||||
3.1V1专属售后答疑,技术团队7*24小时在线支持提供专属解决方案
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={`flex flex-col gap-4 items-center`}>
|
||||
<div className="flex flex-col gap-4 items-center">
|
||||
<p>您的专属客服经理</p>
|
||||
<div>
|
||||
<canvas ref={canvasRef} width="180" height="180" className={`mx-auto bg-muted`}/>
|
||||
<canvas ref={canvasRef} width="180" height="180" className="mx-auto bg-muted"/>
|
||||
</div>
|
||||
<p className="text-xs text-weak">
|
||||
扫描上方二维码添加客服经理微信<br/>获取更多帮助与支持
|
||||
扫描上方二维码添加客服经理微信
|
||||
<br/>
|
||||
获取更多帮助与支持
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -179,7 +186,6 @@ function Aftersale(props: {
|
||||
function BasicForm(props: {
|
||||
profile: User
|
||||
}) {
|
||||
|
||||
const schema = z.object({
|
||||
username: z.string(),
|
||||
email: z.string().email('请输入正确的邮箱'),
|
||||
@@ -196,7 +202,7 @@ function BasicForm(props: {
|
||||
contact_wechat: props.profile.contact_wechat || '',
|
||||
},
|
||||
})
|
||||
const handler = form.handleSubmit(async value => {
|
||||
const handler = form.handleSubmit(async (value) => {
|
||||
try {
|
||||
const resp = await update(value)
|
||||
if (!resp.success) {
|
||||
@@ -217,8 +223,8 @@ function BasicForm(props: {
|
||||
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col gap-4`}>
|
||||
<h3 className={`font-normal`}>基本信息</h3>
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="font-normal">基本信息</h3>
|
||||
<Form
|
||||
form={form}
|
||||
handler={handler}
|
||||
@@ -227,60 +233,65 @@ function BasicForm(props: {
|
||||
)}
|
||||
>
|
||||
<FormField<Schema>
|
||||
name={`username`}
|
||||
label={<span className={`w-full flex justify-end`}>用户名</span>}
|
||||
className={`grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4`}
|
||||
name="username"
|
||||
label={<span className="w-full flex justify-end">用户名</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder={`请输入用户名`} className={`w-52`}/>
|
||||
<Input {...field} placeholder="请输入用户名" className="w-52"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema>
|
||||
name={`email`}
|
||||
label={<span className={`w-full flex justify-end`}>邮箱</span>}
|
||||
className={`grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4`}
|
||||
name="email"
|
||||
label={<span className="w-full flex justify-end">邮箱</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder={`请输入邮箱`} className={`w-52`}/>
|
||||
<Input {...field} placeholder="请输入邮箱" className="w-52"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema>
|
||||
name={`contact_qq`}
|
||||
label={<span className={`w-full flex justify-end`}>QQ</span>}
|
||||
className={`grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4`}
|
||||
name="contact_qq"
|
||||
label={<span className="w-full flex justify-end">QQ</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder={`请输入QQ号`} className={`w-52`}/>
|
||||
<Input {...field} placeholder="请输入QQ号" className="w-52"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema>
|
||||
name={`contact_wechat`}
|
||||
label={<span className={`w-full flex justify-end`}>微信</span>}
|
||||
className={`grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4`}
|
||||
name="contact_wechat"
|
||||
label={<span className="w-full flex justify-end">微信</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder={`请输入微信号`} className={`w-52`}/>
|
||||
<Input {...field} placeholder="请输入微信号" className="w-52"/>
|
||||
)}
|
||||
</FormField>
|
||||
<div className={`flex justify-end gap-4 col-span-2 justify-self-stretch`}>
|
||||
<Button theme={`outline`} type={`button`} onClick={() => form.reset({
|
||||
username: props.profile.username || '',
|
||||
email: props.profile.email || '',
|
||||
contact_qq: props.profile.contact_qq || '',
|
||||
contact_wechat: props.profile.contact_wechat || '',
|
||||
})}>撤销</Button>
|
||||
<div className="flex justify-end gap-4 col-span-2 justify-self-stretch">
|
||||
<Button
|
||||
theme="outline"
|
||||
type="button"
|
||||
onClick={() => form.reset({
|
||||
username: props.profile.username || '',
|
||||
email: props.profile.email || '',
|
||||
contact_qq: props.profile.contact_qq || '',
|
||||
contact_wechat: props.profile.contact_wechat || '',
|
||||
})}>
|
||||
撤销
|
||||
</Button>
|
||||
<Button>保存</Button>
|
||||
</div>
|
||||
</Form>
|
||||
@@ -291,7 +302,6 @@ function BasicForm(props: {
|
||||
function PasswordForm(props: {
|
||||
profile: User
|
||||
}) {
|
||||
|
||||
// ======================
|
||||
// open
|
||||
// ======================
|
||||
@@ -324,7 +334,7 @@ function PasswordForm(props: {
|
||||
confirm_password: '',
|
||||
},
|
||||
})
|
||||
const handler = form.handleSubmit(async value => {
|
||||
const handler = form.handleSubmit(async (value) => {
|
||||
try {
|
||||
const resp = await updatePassword({
|
||||
phone: value.phone,
|
||||
@@ -374,7 +384,7 @@ function PasswordForm(props: {
|
||||
|
||||
setCaptchaWait(60)
|
||||
interval.current = setInterval(() => {
|
||||
setCaptchaWait(wait => {
|
||||
setCaptchaWait((wait) => {
|
||||
if (wait <= 1) {
|
||||
clearInterval(interval.current!)
|
||||
return 0
|
||||
@@ -393,38 +403,38 @@ function PasswordForm(props: {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button theme={`outline`} className={`w-24 h-9`}>修改密码</Button>
|
||||
<Button theme="outline" className="w-24 h-9">修改密码</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>修改密码</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form form={form} handler={handler} className={`flex flex-col gap-4 mt-4`}>
|
||||
<Form form={form} handler={handler} className="flex flex-col gap-4 mt-4">
|
||||
|
||||
{/* 手机号 */}
|
||||
<FormField<Schema> name={`phone`} label={`手机号`} className={`flex-auto`}>
|
||||
<FormField<Schema> name="phone" label="手机号" className="flex-auto">
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder={`请输入手机号`} autoComplete={`tel-national`}/>
|
||||
<Input {...field} placeholder="请输入手机号" autoComplete="tel-national"/>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
<FormField<Schema> name={`captcha`} label={`验证码`}>
|
||||
<FormField<Schema> name="captcha" label="验证码">
|
||||
{({field}) => (
|
||||
<div className={`flex gap-4`}>
|
||||
<Input {...field} placeholder={`请输入验证码`} autoComplete={`one-time-code`}/>
|
||||
<Button className={`p-0 bg-transparent`} onClick={refreshCaptcha} type={`button`}>
|
||||
<img src={captchaUrl} alt={`验证码`} className={`h-10`}/>
|
||||
<div className="flex gap-4">
|
||||
<Input {...field} placeholder="请输入验证码" autoComplete="one-time-code"/>
|
||||
<Button className="p-0 bg-transparent" onClick={refreshCaptcha} type="button">
|
||||
<img src={captchaUrl} alt="验证码" className="h-10"/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{/* 短信令牌 */}
|
||||
<FormField<Schema> name={`code`} label={`短信令牌`} className={`flex-auto`}>
|
||||
<FormField<Schema> name="code" label="短信令牌" className="flex-auto">
|
||||
{({field}) => (
|
||||
<div className={`flex gap-4`}>
|
||||
<Input {...field} placeholder={`请输入验证码`} autoComplete={`one-time-code`}/>
|
||||
<Button theme={`outline`} type={`button`} className={`w-36`} onClick={() => sendVerifier()}>
|
||||
<div className="flex gap-4">
|
||||
<Input {...field} placeholder="请输入验证码" autoComplete="one-time-code"/>
|
||||
<Button theme="outline" type="button" className="w-36" onClick={() => sendVerifier()}>
|
||||
{captchaWait > 0
|
||||
? `重新发送(${captchaWait})`
|
||||
: `获取短信令牌`
|
||||
@@ -435,29 +445,35 @@ function PasswordForm(props: {
|
||||
</FormField>
|
||||
|
||||
{/* 新密码 */}
|
||||
<FormField<Schema> name={`password`} label={`新密码`} className={`flex-auto`}>
|
||||
<FormField<Schema> name="password" label="新密码" className="flex-auto">
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder={`请输入新密码`} type={`password`} autoComplete={`new-password`}/>
|
||||
<Input {...field} placeholder="请输入新密码" type="password" autoComplete="new-password"/>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{/* 确认密码 */}
|
||||
<FormField<Schema> name={`confirm_password`} label={`确认密码`} className={`flex-auto`}>
|
||||
<FormField<Schema> name="confirm_password" label="确认密码" className="flex-auto">
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder={`请再次输入新密码`} type={`password`} autoComplete={`new-password`}/>
|
||||
<Input {...field} placeholder="请再次输入新密码" type="password" autoComplete="new-password"/>
|
||||
)}
|
||||
</FormField>
|
||||
</Form>
|
||||
|
||||
<DialogFooter>
|
||||
<Button theme={`outline`} type={`button`} onClick={() => {
|
||||
setOpen(false)
|
||||
form.reset()
|
||||
}}>关闭</Button>
|
||||
<Button onClick={async e => {
|
||||
<Button
|
||||
theme="outline"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
form.reset()
|
||||
}}>
|
||||
关闭
|
||||
</Button>
|
||||
<Button onClick={async (e) => {
|
||||
const result = await handler(e)
|
||||
|
||||
}}>保存</Button>
|
||||
}}>
|
||||
保存
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
import { Metadata } from 'next'
|
||||
import {ReactNode} from 'react'
|
||||
import {Metadata} from 'next'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
title: '购买套餐 - 蓝狐代理',
|
||||
}
|
||||
return {
|
||||
title: '购买套餐 - 蓝狐代理',
|
||||
}
|
||||
}
|
||||
|
||||
export type PurchaseLayoutProps = {
|
||||
children: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default async function PurchaseLayout(props: PurchaseLayoutProps) {
|
||||
return props.children
|
||||
}
|
||||
return props.children
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export type PurchasePageProps = {}
|
||||
|
||||
export default async function PurchasePage(props: PurchasePageProps) {
|
||||
return (
|
||||
<Page className={`flex-col`}>
|
||||
<Page className="flex-col">
|
||||
<Purchase/>
|
||||
</Page>
|
||||
)
|
||||
|
||||
@@ -111,198 +111,221 @@ export default function LongResource(props: LongResourceProps) {
|
||||
await refresh(1, data.size)
|
||||
}
|
||||
|
||||
return <>
|
||||
{/* 操作区 */}
|
||||
<section className={`flex justify-between flex-wrap`}>
|
||||
<div>
|
||||
return (
|
||||
<>
|
||||
{/* 操作区 */}
|
||||
<section className="flex justify-between flex-wrap">
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form form={form} onSubmit={onSubmit} className={`flex items-end gap-4 flex-wrap`}>
|
||||
<FormField name={`resource_no`} label={<span className={`text-sm`}>套餐编号</span>}>
|
||||
{({id, field}) => (
|
||||
<Input {...field} id={id} className={`h-9`}/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name={`type`} label={<span className={`text-sm`}>类型</span>}>
|
||||
{({field}) => (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger className={`w-24 h-9`}>
|
||||
<SelectValue placeholder={`选择套餐类型`}/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={`all`}>全部</SelectItem>
|
||||
<SelectItem value={`expire`}>包时</SelectItem>
|
||||
<SelectItem value={`quota`}>包量</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<Label className={`text-sm`}>开通时间</Label>
|
||||
<div className={`flex items-center`}>
|
||||
<FormField name={`create_after`}>
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`开始时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<span className={`px-1`}>-</span>
|
||||
<FormField name={`create_before`}>
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`结束时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<Label className={`text-sm`}>最后使用时间</Label>
|
||||
<div className={`flex items-center`}>
|
||||
<FormField name={`expire_after`}>
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`开始时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<span className={`px-1`}>-</span>
|
||||
<FormField name={`expire_before`}>
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`结束时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex gap-4`}>
|
||||
<Button className={`h-9`}>
|
||||
<Search/>
|
||||
<span>筛选</span>
|
||||
</Button>
|
||||
<Button theme={`outline`} className={`h-9`} onClick={() => form.reset({
|
||||
type: 'all',
|
||||
resource_no: '',
|
||||
create_after: undefined,
|
||||
create_before: undefined,
|
||||
expire_after: undefined,
|
||||
expire_before: undefined,
|
||||
})}>
|
||||
<Eraser/>
|
||||
<span>重置</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</section>
|
||||
|
||||
{/* 数据表 */}
|
||||
<DataTable
|
||||
data={data.list}
|
||||
status={status}
|
||||
pagination={{
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
size: data.size,
|
||||
onPageChange: async (page: number) => {
|
||||
await refresh(page, data.size)
|
||||
},
|
||||
onSizeChange: async (size: number) => {
|
||||
await refresh(data.page, size)
|
||||
},
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'resource_no', header: `套餐编号`,
|
||||
},
|
||||
{
|
||||
accessorKey: 'type', header: `类型`, cell: ({row}) => (
|
||||
<div className={`flex gap-2 items-center`}>
|
||||
{row.original.long.type === 1 && (
|
||||
<div className={`flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md`}>
|
||||
<Timer size={20}/>
|
||||
<span>包时</span>
|
||||
</div>
|
||||
)}
|
||||
{row.original.long.type === 2 && (
|
||||
<div className={`flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md`}>
|
||||
<Box size={20}/>
|
||||
<span>包量</span>
|
||||
</div>
|
||||
)}
|
||||
<Form form={form} onSubmit={onSubmit} className="flex items-end gap-4 flex-wrap">
|
||||
<FormField name="resource_no" label={<span className="text-sm">套餐编号</span>}>
|
||||
{({id, field}) => (
|
||||
<Input {...field} id={id} className="h-9"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name="type" label={<span className="text-sm">类型</span>}>
|
||||
{({field}) => (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="w-24 h-9">
|
||||
<SelectValue placeholder="选择套餐类型"/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
<SelectItem value="expire">包时</SelectItem>
|
||||
<SelectItem value="quota">包量</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-sm">开通时间</Label>
|
||||
<div className="flex items-center">
|
||||
<FormField name="create_after">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className="w-36"
|
||||
placeholder="开始时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<span className="px-1">-</span>
|
||||
<FormField name="create_before">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className="w-36"
|
||||
placeholder="结束时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'live', header: `IP 时效`, cell: ({row}) => (
|
||||
<span>
|
||||
{row.original.long.live} 小时
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'expire', header: `使用情况`, cell: ({row}) => (
|
||||
<div className={`flex gap-1`}>
|
||||
{row.original.long.type === 1 ? (
|
||||
<div className={`flex gap-1`}>
|
||||
{isAfter(row.original.long.expire, new Date())
|
||||
? <span className={`text-green-500`}>正常</span>
|
||||
: <span className={`text-red-500`}>过期</span>}
|
||||
<span>|</span>
|
||||
<span>今日限额:{row.original.long.daily_used} / {row.original.long.daily_limit}</span>
|
||||
<span>|</span>
|
||||
<span>{intlFormatDistance(row.original.long.expire, new Date())} 到期</span>
|
||||
</div>
|
||||
) : row.original.long.type === 2 ? (
|
||||
<div className={`flex gap-1`}>
|
||||
{row.original.long.used < row.original.long.quota
|
||||
? <span className={`text-green-500`}>正常</span>
|
||||
: <span className={`text-red-500`}>已用完</span>}
|
||||
<span>|</span>
|
||||
<span>用量统计:{row.original.long.used} / {row.original.long.quota}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-sm">最后使用时间</Label>
|
||||
<div className="flex items-center">
|
||||
<FormField name="expire_after">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className="w-36"
|
||||
placeholder="开始时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<span className="px-1">-</span>
|
||||
<FormField name="expire_before">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className="w-36"
|
||||
placeholder="结束时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'daily_last', header: '最近使用时间', cell: ({row}) => {
|
||||
return (
|
||||
format(row.original.long.daily_last, 'yyyy-MM-dd') === '0001-01-01'
|
||||
? '-'
|
||||
: format(row.original.long.daily_last, 'yyyy-MM-dd HH:mm')
|
||||
)
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Button className="h-9">
|
||||
<Search/>
|
||||
<span>筛选</span>
|
||||
</Button>
|
||||
<Button
|
||||
theme="outline"
|
||||
className="h-9"
|
||||
onClick={() => form.reset({
|
||||
type: 'all',
|
||||
resource_no: '',
|
||||
create_after: undefined,
|
||||
create_before: undefined,
|
||||
expire_after: undefined,
|
||||
expire_before: undefined,
|
||||
})}>
|
||||
<Eraser/>
|
||||
<span>重置</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</section>
|
||||
|
||||
{/* 数据表 */}
|
||||
<DataTable
|
||||
data={data.list}
|
||||
status={status}
|
||||
pagination={{
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
size: data.size,
|
||||
onPageChange: async (page: number) => {
|
||||
await refresh(page, data.size)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'created_at', header: '开通时间', cell: ({row}) => (
|
||||
format(row.getValue('created_at'), 'yyyy-MM-dd HH:mm')
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'action', header: `操作`, cell: (item) => (
|
||||
<div className={`flex gap-2`}>
|
||||
-
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
onSizeChange: async (size: number) => {
|
||||
await refresh(data.page, size)
|
||||
},
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'resource_no', header: `套餐编号`,
|
||||
},
|
||||
{
|
||||
accessorKey: 'type', header: `类型`, cell: ({row}) => (
|
||||
<div className="flex gap-2 items-center">
|
||||
{row.original.long.type === 1 && (
|
||||
<div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md">
|
||||
<Timer size={20}/>
|
||||
<span>包时</span>
|
||||
</div>
|
||||
)}
|
||||
{row.original.long.type === 2 && (
|
||||
<div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md">
|
||||
<Box size={20}/>
|
||||
<span>包量</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'live', header: `IP 时效`, cell: ({row}) => (
|
||||
<span>
|
||||
{row.original.long.live}
|
||||
{' '}
|
||||
小时
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'expire', header: `使用情况`, cell: ({row}) => (
|
||||
<div className="flex gap-1">
|
||||
{row.original.long.type === 1 ? (
|
||||
<div className="flex gap-1">
|
||||
{isAfter(row.original.long.expire, new Date())
|
||||
? <span className="text-green-500">正常</span>
|
||||
: <span className="text-red-500">过期</span>}
|
||||
<span>|</span>
|
||||
<span>
|
||||
今日限额:
|
||||
{row.original.long.daily_used}
|
||||
{' '}
|
||||
/
|
||||
{row.original.long.daily_limit}
|
||||
</span>
|
||||
<span>|</span>
|
||||
<span>
|
||||
{intlFormatDistance(row.original.long.expire, new Date())}
|
||||
{' '}
|
||||
到期
|
||||
</span>
|
||||
</div>
|
||||
) : row.original.long.type === 2 ? (
|
||||
<div className="flex gap-1">
|
||||
{row.original.long.used < row.original.long.quota
|
||||
? <span className="text-green-500">正常</span>
|
||||
: <span className="text-red-500">已用完</span>}
|
||||
<span>|</span>
|
||||
<span>
|
||||
用量统计:
|
||||
{row.original.long.used}
|
||||
{' '}
|
||||
/
|
||||
{row.original.long.quota}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'daily_last', header: '最近使用时间', cell: ({row}) => {
|
||||
return (
|
||||
format(row.original.long.daily_last, 'yyyy-MM-dd') === '0001-01-01'
|
||||
? '-'
|
||||
: format(row.original.long.daily_last, 'yyyy-MM-dd HH:mm')
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'created_at', header: '开通时间', cell: ({row}) => (
|
||||
format(row.getValue('created_at'), 'yyyy-MM-dd HH:mm')
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'action', header: `操作`, cell: item => (
|
||||
<div className="flex gap-2">
|
||||
-
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -112,198 +112,221 @@ export default function ShortResource(props: ShortResourceProps) {
|
||||
await refresh(1, data.size)
|
||||
}
|
||||
|
||||
return <>
|
||||
{/* 操作区 */}
|
||||
<section className={`flex justify-between flex-wrap`}>
|
||||
<div>
|
||||
return (
|
||||
<>
|
||||
{/* 操作区 */}
|
||||
<section className="flex justify-between flex-wrap">
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form form={form} onSubmit={onSubmit} className={`flex items-end gap-4 flex-wrap`}>
|
||||
<FormField name={`resource_no`} label={<span className={`text-sm`}>套餐编号</span>}>
|
||||
{({id, field}) => (
|
||||
<Input {...field} id={id} className={`h-9`}/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name={`type`} label={<span className={`text-sm`}>类型</span>}>
|
||||
{({field}) => (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger className={`w-24 h-9`}>
|
||||
<SelectValue placeholder={`选择套餐类型`}/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={`all`}>全部</SelectItem>
|
||||
<SelectItem value={`expire`}>包时</SelectItem>
|
||||
<SelectItem value={`quota`}>包量</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<Label className={`text-sm`}>开通时间</Label>
|
||||
<div className={`flex items-center`}>
|
||||
<FormField name={`create_after`}>
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`开始时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<span className={`px-1`}>-</span>
|
||||
<FormField name={`create_before`}>
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`结束时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<Label className={`text-sm`}>最后使用时间</Label>
|
||||
<div className={`flex items-center`}>
|
||||
<FormField name={`expire_after`}>
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`开始时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<span className={`px-1`}>-</span>
|
||||
<FormField name={`expire_before`}>
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className={`w-36`}
|
||||
placeholder={`结束时间`}
|
||||
format={`yyyy-MM-dd`}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex gap-4`}>
|
||||
<Button className={`h-9`}>
|
||||
<Search/>
|
||||
<span>筛选</span>
|
||||
</Button>
|
||||
<Button theme={`outline`} className={`h-9`} onClick={() => form.reset({
|
||||
type: 'all',
|
||||
resource_no: '',
|
||||
create_after: undefined,
|
||||
create_before: undefined,
|
||||
expire_after: undefined,
|
||||
expire_before: undefined,
|
||||
})}>
|
||||
<Eraser/>
|
||||
<span>重置</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</section>
|
||||
|
||||
{/* 数据表 */}
|
||||
<DataTable
|
||||
data={data.list}
|
||||
status={status}
|
||||
pagination={{
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
size: data.size,
|
||||
onPageChange: async (page: number) => {
|
||||
await refresh(page, data.size)
|
||||
},
|
||||
onSizeChange: async (size: number) => {
|
||||
await refresh(data.page, size)
|
||||
},
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'resource_no', header: `套餐编号`,
|
||||
},
|
||||
{
|
||||
accessorKey: 'type', header: `类型`, cell: ({row}) => (
|
||||
<div className={`flex gap-2 items-center`}>
|
||||
{row.original.short.type === 1 && (
|
||||
<div className={`flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md`}>
|
||||
<Timer size={20}/>
|
||||
<span>包时</span>
|
||||
</div>
|
||||
)}
|
||||
{row.original.short.type === 2 && (
|
||||
<div className={`flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md`}>
|
||||
<Box size={20}/>
|
||||
<span>包量</span>
|
||||
</div>
|
||||
)}
|
||||
<Form form={form} onSubmit={onSubmit} className="flex items-end gap-4 flex-wrap">
|
||||
<FormField name="resource_no" label={<span className="text-sm">套餐编号</span>}>
|
||||
{({id, field}) => (
|
||||
<Input {...field} id={id} className="h-9"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name="type" label={<span className="text-sm">类型</span>}>
|
||||
{({field}) => (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="w-24 h-9">
|
||||
<SelectValue placeholder="选择套餐类型"/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
<SelectItem value="expire">包时</SelectItem>
|
||||
<SelectItem value="quota">包量</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-sm">开通时间</Label>
|
||||
<div className="flex items-center">
|
||||
<FormField name="create_after">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className="w-36"
|
||||
placeholder="开始时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<span className="px-1">-</span>
|
||||
<FormField name="create_before">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className="w-36"
|
||||
placeholder="结束时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'live', header: `IP 时效`, cell: ({row}) => (
|
||||
<span>
|
||||
{row.original.short.live / 60} 分钟
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'expire', header: `使用情况`, cell: ({row}) => (
|
||||
<div className={`flex gap-1`}>
|
||||
{row.original.short.type === 1 ? (
|
||||
<div className={`flex gap-1`}>
|
||||
{isAfter(row.original.short.expire, new Date())
|
||||
? <span className={`text-green-500`}>正常</span>
|
||||
: <span className={`text-red-500`}>过期</span>}
|
||||
<span>|</span>
|
||||
<span>今日限额:{row.original.short.daily_used} / {row.original.short.daily_limit}</span>
|
||||
<span>|</span>
|
||||
<span>{intlFormatDistance(row.original.short.expire, new Date())} 到期</span>
|
||||
</div>
|
||||
) : row.original.short.type === 2 ? (
|
||||
<div className={`flex gap-1`}>
|
||||
{row.original.short.used < row.original.short.quota
|
||||
? <span className={`text-green-500`}>正常</span>
|
||||
: <span className={`text-red-500`}>已用完</span>}
|
||||
<span>|</span>
|
||||
<span>用量统计:{row.original.short.used} / {row.original.short.quota}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-sm">最后使用时间</Label>
|
||||
<div className="flex items-center">
|
||||
<FormField name="expire_after">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className="w-36"
|
||||
placeholder="开始时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<span className="px-1">-</span>
|
||||
<FormField name="expire_before">
|
||||
{({field}) => (
|
||||
<DatePicker
|
||||
{...field}
|
||||
className="w-36"
|
||||
placeholder="结束时间"
|
||||
format="yyyy-MM-dd"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'daily_last', header: '最近使用时间', cell: ({row}) => {
|
||||
return (
|
||||
format(row.original.short.daily_last, 'yyyy-MM-dd') === '0001-01-01'
|
||||
? '-'
|
||||
: format(row.original.short.daily_last, 'yyyy-MM-dd HH:mm')
|
||||
)
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Button className="h-9">
|
||||
<Search/>
|
||||
<span>筛选</span>
|
||||
</Button>
|
||||
<Button
|
||||
theme="outline"
|
||||
className="h-9"
|
||||
onClick={() => form.reset({
|
||||
type: 'all',
|
||||
resource_no: '',
|
||||
create_after: undefined,
|
||||
create_before: undefined,
|
||||
expire_after: undefined,
|
||||
expire_before: undefined,
|
||||
})}>
|
||||
<Eraser/>
|
||||
<span>重置</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</section>
|
||||
|
||||
{/* 数据表 */}
|
||||
<DataTable
|
||||
data={data.list}
|
||||
status={status}
|
||||
pagination={{
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
size: data.size,
|
||||
onPageChange: async (page: number) => {
|
||||
await refresh(page, data.size)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'created_at', header: '开通时间', cell: ({row}) => (
|
||||
format(row.getValue('created_at'), 'yyyy-MM-dd HH:mm')
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'action', header: `操作`, cell: (item) => (
|
||||
<div className={`flex gap-2`}>
|
||||
-
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
onSizeChange: async (size: number) => {
|
||||
await refresh(data.page, size)
|
||||
},
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'resource_no', header: `套餐编号`,
|
||||
},
|
||||
{
|
||||
accessorKey: 'type', header: `类型`, cell: ({row}) => (
|
||||
<div className="flex gap-2 items-center">
|
||||
{row.original.short.type === 1 && (
|
||||
<div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md">
|
||||
<Timer size={20}/>
|
||||
<span>包时</span>
|
||||
</div>
|
||||
)}
|
||||
{row.original.short.type === 2 && (
|
||||
<div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md">
|
||||
<Box size={20}/>
|
||||
<span>包量</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'live', header: `IP 时效`, cell: ({row}) => (
|
||||
<span>
|
||||
{row.original.short.live / 60}
|
||||
{' '}
|
||||
分钟
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'expire', header: `使用情况`, cell: ({row}) => (
|
||||
<div className="flex gap-1">
|
||||
{row.original.short.type === 1 ? (
|
||||
<div className="flex gap-1">
|
||||
{isAfter(row.original.short.expire, new Date())
|
||||
? <span className="text-green-500">正常</span>
|
||||
: <span className="text-red-500">过期</span>}
|
||||
<span>|</span>
|
||||
<span>
|
||||
今日限额:
|
||||
{row.original.short.daily_used}
|
||||
{' '}
|
||||
/
|
||||
{row.original.short.daily_limit}
|
||||
</span>
|
||||
<span>|</span>
|
||||
<span>
|
||||
{intlFormatDistance(row.original.short.expire, new Date())}
|
||||
{' '}
|
||||
到期
|
||||
</span>
|
||||
</div>
|
||||
) : row.original.short.type === 2 ? (
|
||||
<div className="flex gap-1">
|
||||
{row.original.short.used < row.original.short.quota
|
||||
? <span className="text-green-500">正常</span>
|
||||
: <span className="text-red-500">已用完</span>}
|
||||
<span>|</span>
|
||||
<span>
|
||||
用量统计:
|
||||
{row.original.short.used}
|
||||
{' '}
|
||||
/
|
||||
{row.original.short.quota}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'daily_last', header: '最近使用时间', cell: ({row}) => {
|
||||
return (
|
||||
format(row.original.short.daily_last, 'yyyy-MM-dd') === '0001-01-01'
|
||||
? '-'
|
||||
: format(row.original.short.daily_last, 'yyyy-MM-dd HH:mm')
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'created_at', header: '开通时间', cell: ({row}) => (
|
||||
format(row.getValue('created_at'), 'yyyy-MM-dd HH:mm')
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'action', header: `操作`, cell: item => (
|
||||
<div className="flex gap-2">
|
||||
-
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
import { Metadata } from 'next'
|
||||
import {ReactNode} from 'react'
|
||||
import {Metadata} from 'next'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
}
|
||||
|
||||
export type ResourcesLayoutProps = {
|
||||
children: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default async function ResourcesLayout(props: ResourcesLayoutProps) {
|
||||
return props.children
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,21 @@ import ShortResource from '@/app/admin/resources/_client/short'
|
||||
import LongResource from '@/app/admin/resources/_client/long'
|
||||
|
||||
export default async function ResourcesPage() {
|
||||
|
||||
// ======================
|
||||
// render
|
||||
// ======================
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Tabs defaultValue={`short`}>
|
||||
<TabsList className={`bg-card p-1.5 rounded-lg`}>
|
||||
<TabsTrigger value={`short`} className={`w-30 h-9 data-[state=active]:bg-primary-muted text-base rounded-md`}>短效套餐</TabsTrigger>
|
||||
<TabsTrigger value={`long`} className={`w-30 h-9 data-[state=active]:bg-primary-muted text-base rounded-md`}>长效套餐</TabsTrigger>
|
||||
<Tabs defaultValue="short">
|
||||
<TabsList className="bg-card p-1.5 rounded-lg">
|
||||
<TabsTrigger value="short" className="w-30 h-9 data-[state=active]:bg-primary-muted text-base rounded-md">短效套餐</TabsTrigger>
|
||||
<TabsTrigger value="long" className="w-30 h-9 data-[state=active]:bg-primary-muted text-base rounded-md">长效套餐</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value={`short`} className={`flex flex-col gap-4`}>
|
||||
<TabsContent value="short" className="flex flex-col gap-4">
|
||||
<ShortResource/>
|
||||
</TabsContent>
|
||||
<TabsContent value={`long`} className={`flex flex-col gap-4`}>
|
||||
<TabsContent value="long" className="flex flex-col gap-4">
|
||||
<LongResource/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
import { Metadata } from 'next'
|
||||
import {ReactNode} from 'react'
|
||||
import {Metadata} from 'next'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
}
|
||||
|
||||
export type WhitelistLayoutProps = {
|
||||
children: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default async function WhitelistLayout(props: WhitelistLayoutProps) {
|
||||
return props.children
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
||||
}
|
||||
else {
|
||||
const newSelection = new Set<number>()
|
||||
data.list.forEach(item => {
|
||||
data.list.forEach((item) => {
|
||||
newSelection.add(item.id)
|
||||
})
|
||||
setSelection(newSelection)
|
||||
@@ -231,8 +231,8 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
||||
添加白名单
|
||||
</Button>
|
||||
<Button
|
||||
theme={`fail`}
|
||||
className={`ml-2`}
|
||||
theme="fail"
|
||||
className="ml-2"
|
||||
disabled={selection.size === 0 || wait}
|
||||
onClick={() => confirmRemove()}>
|
||||
<Trash2/>
|
||||
@@ -267,7 +267,7 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
||||
cell: ({row}) => (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
className={`h-9 w-9`}
|
||||
className="h-9 w-9"
|
||||
theme="outline"
|
||||
onClick={() => openDialog('edit', row.original)}
|
||||
disabled={wait}
|
||||
@@ -275,9 +275,9 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
||||
<Edit className="w-4 h-4"/>
|
||||
</Button>
|
||||
<Button
|
||||
className={`h-9 w-9`}
|
||||
className="h-9 w-9"
|
||||
onClick={() => confirmRemove(row.original.id)}
|
||||
theme={`fail`}
|
||||
theme="fail"
|
||||
disabled={wait}
|
||||
>
|
||||
<Trash2 className="w-4 h-4"/>
|
||||
@@ -297,22 +297,22 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form<SchemaType>
|
||||
className={`flex flex-col gap-4 py-4`}
|
||||
className="flex flex-col gap-4 py-4"
|
||||
form={form}
|
||||
onSubmit={onSubmit}>
|
||||
<FormField name={`host`} label={`IP地址`}>
|
||||
<FormField name="host" label="IP地址">
|
||||
{({id, field}) => (
|
||||
<Input {...field} id={id} placeholder="输入IP地址"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name={`remark`} label={`备注`}>
|
||||
<FormField name="remark" label="备注">
|
||||
{({id, field}) => (
|
||||
<Textarea {...field} id={id} placeholder="输入备注信息(可选)" disabled={wait}/>
|
||||
)}
|
||||
</FormField>
|
||||
<DialogFooter className={`gap-4 mt-4`}>
|
||||
<Button theme={`outline`} type="button" onClick={() => toggleDialog(false)} disabled={wait}>取消</Button>
|
||||
<Button type={`submit`} disabled={wait}>
|
||||
<DialogFooter className="gap-4 mt-4">
|
||||
<Button theme="outline" type="button" onClick={() => toggleDialog(false)} disabled={wait}>取消</Button>
|
||||
<Button type="submit" disabled={wait}>
|
||||
{wait && <Loader2 className="w-4 h-4 mr-2 animate-spin"/>}
|
||||
保存
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user