diff --git a/src/actions/bill.ts b/src/actions/bill.ts new file mode 100644 index 0000000..53dcc1e --- /dev/null +++ b/src/actions/bill.ts @@ -0,0 +1,15 @@ +import {Bill} from '@/lib/models' +import {callByUser} from '@/actions/base' +import {PageRecord} from '@/lib/api' + +export async function listBills(params: { + page?: number + size?: number + bill_no?: string + type?: number + status?: number + create_after?: Date + create_before?: Date +}) { + return await callByUser>('/api/bill/list', params) +} diff --git a/src/actions/resource.ts b/src/actions/resource.ts index 9d209d3..51452a8 100644 --- a/src/actions/resource.ts +++ b/src/actions/resource.ts @@ -1,19 +1,20 @@ 'use server' import {callByUser} from '@/actions/base' -import {ResourcePss} from '@/lib/models' +import {Resource} from '@/lib/models' import {PageRecord} from '@/lib/api' async function listResourcePss(props: { page: number size: number + resource_no?: string type?: number create_after?: Date create_before?: Date expire_after?: Date expire_before?: Date -}){ - return await callByUser>('/api/resource/list/pss', props) +}) { + return await callByUser>('/api/resource/list/pss', props) } async function createResourceByBalance(props: { diff --git a/src/app/admin/(dashboard)/page.tsx b/src/app/admin/(dashboard)/page.tsx index 537a1c1..9ba6a9d 100644 --- a/src/app/admin/(dashboard)/page.tsx +++ b/src/app/admin/(dashboard)/page.tsx @@ -1,9 +1,11 @@ +import Page from '@/components/page' + export type DashboardPageProps = {} export default async function DashboardPage(props: DashboardPageProps) { return ( -
+ {/* banner */}
@@ -36,6 +38,6 @@ export default async function DashboardPage(props: DashboardPageProps) {
-
+ ) } diff --git a/src/app/admin/bills/page.tsx b/src/app/admin/bills/page.tsx index 0ac7dab..1380909 100644 --- a/src/app/admin/bills/page.tsx +++ b/src/app/admin/bills/page.tsx @@ -1,10 +1,321 @@ -import {ReactNode} from 'react' +'use client' +import {useEffect, useState} from 'react' +import {PageRecord} from '@/lib/api' +import {Bill} from '@/lib/models' +import {useStatus} from '@/lib/states' +import {listBills} from '@/actions/bill' +import {Search, Eraser, CreditCard, AlertCircle, CheckCircle} from 'lucide-react' +import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select' +import {Button} from '@/components/ui/button' +import DataTable from '@/components/data-table' +import {format} from 'date-fns' +import DatePicker from '@/components/date-picker' +import {Form, FormField} from '@/components/ui/form' +import {useForm} from 'react-hook-form' +import zod from 'zod' +import {zodResolver} from '@hookform/resolvers/zod' +import {Label} from '@/components/ui/label' +import Page from '@/components/page' +import Link from 'next/link' -export type BillsPageProps = { -} +const filterSchema = zod.object({ + type: zod.enum(['all', '0', '1', '2']).default('all'), + status: zod.enum(['all', '0', '1', '2']).default('all'), + create_after: zod.date().optional(), + create_before: zod.date().optional(), +}) + +type FilterSchema = zod.infer + +export type BillsPageProps = {} + +export default function BillsPage(props: BillsPageProps) { + + // ====================== + // 查询 + // ====================== + + const [status, setStatus] = useStatus() + const [data, setData] = useState>({ + page: 1, + size: 10, + total: 0, + list: [], + }) + + const refresh = async (page: number, size: number) => { + setStatus('load') + try { + const typeValue = form.getValues('type') + const statusValue = form.getValues('status') + const type = typeValue === 'all' ? undefined : parseInt(typeValue) + const statusParam = statusValue === 'all' ? undefined : parseInt(statusValue) + const create_after = form.getValues('create_after') + const create_before = form.getValues('create_before') + + const res = await listBills({ + page, size, type, status: statusParam, create_after, create_before, + }) + + if (res.success) { + setData(res.data) + setStatus('done') + } + else { + throw new Error('Failed to load bills') + } + } + catch (e) { + setStatus('fail') + } + } + + useEffect(() => { + refresh(1, 10).then() + }, []) + + // ====================== + // 筛选 + // ====================== + + const form = useForm({ + resolver: zodResolver(filterSchema), + defaultValues: { + type: 'all', + status: 'all', + }, + }) + + const onSubmit = async (value: FilterSchema) => { + console.log(value) + await refresh(1, data.size) + } + + // 获取类型显示内容 + const getBillTypeText = (type: number) => { + switch (type) { + case 1: + return '充值' + case 2: + return '消费' + case 3: + return '退款' + default: + return '未知' + } + } + + // 获取状态显示内容 + const getBillStatusText = (status: number) => { + switch (status) { + case 1: + return '待支付' + case 2: + return '已完成' + case 3: + return '已取消' + default: + return '未知' + } + } + + // ====================== + // render + // ====================== -export default async function BillsPage(props: BillsPageProps) { return ( -
+ + + {/* 操作区 */} +
+
+

账单管理

+
+ +
+ 账单类型}> + {({id, field}) => ( + + )} + + 状态}> + {({id, field}) => ( + + )} + +
+ +
+ + {({field}) => ( + + )} + + - + + {({field}) => ( + + )} + +
+
+ + +
+
+ + {/* 数据表 */} + { + await refresh(page, data.size) + }, + onSizeChange: async (size: number) => { + await refresh(data.page, size) + }, + }} + columns={[ + { + accessorKey: 'bill_no', header: `账单编号`, + }, { + accessorKey: 'type', header: `类型`, cell: ({row}) => ( +
+ {row.original.type === 0 && ( +
+ + 充值 +
+ )} + {row.original.type === 1 && ( +
+ + 消费 +
+ )} + {row.original.type === 2 && ( +
+ + 退款 +
+ )} +
+ ), + }, + { + accessorKey: 'info', header: `账单详情`, cell: ({row}) => ( +
+ {row.original.info} + + {row.original.type === 1 && ( + + + {row.original.resource.pss.type === 1 && `包时`} + {row.original.resource.pss.type === 2 && `包量`} + + - + + {row.original.resource.pss.live / 60 + `分钟`} + + + )} +
+ ), + }, + { + accessorKey: 'status', header: `状态`, cell: ({row}) => ( + <> + {row.original.status === 0 && ( +
+ + 未完成 +
+ )} + {row.original.status === 1 && ( +
+ + 已完成 +
+ )} + {row.original.status === 2 && ( +
+ + 已作废 +
+ )} + + ), + }, + { + accessorKey: 'amount', header: `支付信息`, cell: ({row}) => ( +
+ + {!row.original.trade && '余额'} + {row.original.trade && row.original.trade.method === 1 && '支付宝'} + {row.original.trade && row.original.trade.method === 2 && '微信'} + + 0 ? `text-green-400` : `text-orange-400` + }>¥{row.original.amount} +
+ ), + }, + { + accessorKey: 'created_at', header: '创建时间', cell: ({row}) => ( + format(new Date(row.original.created_at), 'yyyy-MM-dd HH:mm') + ), + }, + { + accessorKey: 'action', header: `操作`, cell: (item) => ( +
+ - +
+ ), + }, + ]} + /> +
) } diff --git a/src/app/admin/extract/page.tsx b/src/app/admin/extract/page.tsx index 64972a5..4ccad08 100644 --- a/src/app/admin/extract/page.tsx +++ b/src/app/admin/extract/page.tsx @@ -1,10 +1,13 @@ import {ReactNode} from 'react' +import Page from '@/components/page' export type ExtractPageProps = { } export default async function ExtractPage(props: ExtractPageProps) { return ( -
+ + + ) } diff --git a/src/app/admin/identify/page.tsx b/src/app/admin/identify/page.tsx index 6f69184..b6a9907 100644 --- a/src/app/admin/identify/page.tsx +++ b/src/app/admin/identify/page.tsx @@ -2,12 +2,13 @@ import {Button} from '@/components/ui/button' import banner from './_assets/banner.webp' import personal from './_assets/personal.webp' import Image from 'next/image' +import Page from '@/components/page' export type IdentifyPageProps = {} export default async function IdentifyPage(props: IdentifyPageProps) { return ( -
+
{/* banner */} @@ -34,6 +35,6 @@ export default async function IdentifyPage(props: IdentifyPageProps) {

操作引导

为响应国家相关规定,使用HTTP代理需完成实名认证。认证服务由支付宝提供,您的个人信息将受到严格保护,仅用于账户安全认证

-
+ ) } diff --git a/src/app/admin/purchase/page.tsx b/src/app/admin/purchase/page.tsx index 5089fab..bcad2db 100644 --- a/src/app/admin/purchase/page.tsx +++ b/src/app/admin/purchase/page.tsx @@ -1,11 +1,12 @@ -import Purchase from '@/components/composites/purchase' +import Purchase from '@/components/composites/purchase/purchase' +import Page from '@/components/page' export type PurchasePageProps = {} export default async function PurchasePage(props: PurchasePageProps) { return ( -
+ -
+ ) } diff --git a/src/app/admin/resources/page.tsx b/src/app/admin/resources/page.tsx index c1e3a1a..2ba3bef 100644 --- a/src/app/admin/resources/page.tsx +++ b/src/app/admin/resources/page.tsx @@ -1,31 +1,23 @@ 'use client' import {useEffect, useState} from 'react' import {PageRecord} from '@/lib/api' -import {ResourcePss} from '@/lib/models' +import {Resource} from '@/lib/models' import {useStatus} from '@/lib/states' import {listResourcePss} from '@/actions/resource' -import {Box, Eraser, Search, Timer, Trash2} from 'lucide-react' -import {Pagination} from '@/components/ui/pagination' +import {Box, Eraser, Search, Timer} from 'lucide-react' import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select' import {Button} from '@/components/ui/button' -import DataTable from '@/components/DataTable' +import DataTable from '@/components/data-table' import {format, intlFormatDistance, isAfter, isEqual, parse} from 'date-fns' -import DatePicker from '@/components/DatePicker' +import DatePicker from '@/components/date-picker' import {Form, FormField} from '@/components/ui/form' import {useForm} from 'react-hook-form' import zod from 'zod' import {zodResolver} from '@hookform/resolvers/zod' import {Label} from '@/components/ui/label' - -const filterSchema = zod.object({ - type: zod.enum(['expire', 'quota', 'all']), - create_after: zod.date().optional(), - create_before: zod.date().optional(), - expire_after: zod.date().optional(), - expire_before: zod.date().optional(), -}) - -type FilterSchema = zod.infer +import {Input} from '@/components/ui/input' +import Page from '@/components/page' +import {useSearchParams} from 'next/navigation' export type ResourcesPageProps = {} @@ -36,7 +28,7 @@ export default function ResourcesPage(props: ResourcesPageProps) { // ====================== const [status, setStatus] = useStatus() - const [data, setData] = useState>({ + const [data, setData] = useState>({ page: 1, size: 10, total: 0, @@ -55,12 +47,20 @@ export default function ResourcesPage(props: ResourcesPageProps) { const create_before = form.getValues('create_before') const expire_after = form.getValues('expire_after') const expire_before = form.getValues('expire_before') + const resource_no = form.getValues('resource_no') const res = await listResourcePss({ - page, size, type, create_after, create_before, expire_after, expire_before, + page, size, + type, + create_after, + create_before, + expire_after, + expire_before, + resource_no, }) if (res.success) { + console.log(res.data) setData(res.data) setStatus('done') } @@ -81,16 +81,37 @@ export default function ResourcesPage(props: ResourcesPageProps) { // 筛选 // ====================== + const filterSchema = zod.object({ + resource_no: zod.string().optional().default(''), + type: zod.enum(['expire', 'quota', 'all']).default('all'), + create_after: zod.date().optional(), + create_before: zod.date().optional(), + expire_after: zod.date().optional(), + expire_before: zod.date().optional(), + }) + + type FilterSchema = zod.infer + + const params = useSearchParams() + let paramType = params.get('type') + if (paramType != 'all' && paramType != 'expire' && paramType != 'quota') { + paramType = 'all' + } + const form = useForm({ resolver: zodResolver(filterSchema), defaultValues: { - type: 'all', + resource_no: params.get('resource_no') || '', + type: paramType as 'expire' | 'quota' | 'all', + 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 onSubmit = async (value: FilterSchema) => { - console.log(value) - await refresh(data.page, data.size) + await refresh(1, data.size) } // ====================== @@ -98,7 +119,7 @@ export default function ResourcesPage(props: ResourcesPageProps) { // ====================== return ( -
+ {/* 操作区 */}
@@ -106,7 +127,12 @@ export default function ResourcesPage(props: ResourcesPageProps) { -
+ + 套餐编号}> + {({id, field}) => ( + + )} + 类型}> {({id, field}) => (