"use client" import { zodResolver } from "@hookform/resolvers/zod" import { format } from "date-fns" import { CreditCard, Wallet } from "lucide-react" import Link from "next/link" import { useRouter, useSearchParams } from "next/navigation" import { Suspense, useEffect, useState } from "react" import { Controller, useForm } from "react-hook-form" import { toast } from "sonner" import { z } from "zod" import { getPageBill, getSkuList } from "@/actions/bill" import { DataTable, useDataTable } from "@/components/data-table" import { Page } from "@/components/page" import { Button } from "@/components/ui/button" import { Field, FieldError, FieldGroup, FieldLabel, } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { ProductCode } from "@/lib/base" import type { Billing } from "@/models/billing" type FilterValues = { bill_no?: string user_phone?: string trade_inner_no?: string resource_no?: string sku_code?: string product_code?: string created_at_start?: Date created_at_end?: Date } type SkuOption = { resource_code: string resource_name: string } const filterSchema = z .object({ phone: z .string() .optional() .transform(val => val?.trim()), bill_no: z .string() .optional() .transform(val => val?.trim()), resource_no: z.string().optional(), inner_no: z.string().optional(), created_at_start: z.string().optional(), created_at_end: z.string().optional(), product_code: z.string().optional(), sku_code: z.string().optional(), }) .superRefine((data, ctx) => { if (data.created_at_start && data.created_at_end) { const start = new Date(data.created_at_start) const end = new Date(data.created_at_end) if (end < start) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "结束时间不能早于开始时间", path: ["created_at_end"], }) } } }) type FilterSchema = z.infer export default function BillingPage() { const searchParams = useSearchParams() const innerNo = searchParams.get("inner_no") const billNo = searchParams.get("bill_no") const resourceNo = searchParams.get("resource_no") const [skuOptions, setSkuOptions] = useState([]) const [loading, setLoading] = useState(true) const [skuProductCode, setSkuProductCode] = useState( ProductCode.All, ) const router = useRouter() const { control, handleSubmit, reset, getValues } = useForm({ resolver: zodResolver(filterSchema), defaultValues: { bill_no: billNo || "", inner_no: innerNo || "", created_at_start: "", created_at_end: "", phone: "", resource_no: resourceNo || "", sku_code: "all", product_code: "", }, }) useEffect(() => { setLoading(true) getSkuList({ product_code: skuProductCode, }) .then(resp => { if (!resp.success) { throw new Error(resp.message) } setSkuOptions( resp.data.map(sku => ({ resource_code: sku.code, resource_name: sku.name, })), ) }) .catch(e => { console.error("获取套餐类型失败:", e) toast.error( `获取套餐类型失败:${e instanceof Error ? e.message : String(e)}`, ) setSkuOptions([]) }) .finally(() => { setLoading(false) }) }, [skuProductCode]) const loadData = (page: number, size: number) => { const result: FilterValues = {} const filters = getValues() if (filters.phone?.trim()) result.user_phone = filters.phone.trim() if (filters.inner_no?.trim()) result.trade_inner_no = filters.inner_no.trim() if (filters.bill_no?.trim()) result.bill_no = filters.bill_no.trim() if (filters.resource_no?.trim()) result.resource_no = filters.resource_no.trim() if (filters.product_code && filters.product_code !== ProductCode.All) { result.product_code = filters.product_code } if (filters.sku_code && filters.sku_code !== "all") { result.sku_code = filters.sku_code } if (filters.created_at_start) result.created_at_start = new Date(filters.created_at_start) if (filters.created_at_end) result.created_at_end = new Date(filters.created_at_end) return getPageBill({ page, size, ...result, }) } const clearFilter = () => { router.replace("/billing") reset({ bill_no: "", inner_no: "", created_at_start: "", created_at_end: "", phone: "", resource_no: "", sku_code: "all", product_code: "", }) setSkuProductCode(ProductCode.All) table.pagination.onPageChange(1) } const table = useDataTable(loadData) const onFilter = handleSubmit(() => { table.pagination.onPageChange(1) }) return (
( 会员号 {fieldState.error?.message} )} /> ( 套餐号 {fieldState.error?.message} )} /> ( 账单号 {fieldState.error?.message} )} /> ( 订单号 {fieldState.error?.message} )} /> ( 产品类型 )} /> ( 套餐类型 {fieldState.error?.message} )} /> ( 开始时间 {fieldState.error?.message} )} /> ( 结束时间 {fieldState.error?.message} )} />
{...table} columns={[ { header: "创建时间", accessorKey: "created_at", cell: ({ row }) => format( new Date(row.original.created_at), "yyyy-MM-dd HH:mm:ss", ), }, { header: "套餐号", accessorKey: "resource.resource_no", cell: ({ row }) => { const resource_no = row.original.resource?.resource_no return resource_no ? ( {resource_no} ) : ( ) }, }, { header: "会员号", accessorFn: row => row.user?.phone || "" }, { header: "账单详情", accessorKey: "info", cell: ({ row }) => { const bill = row.original return (
{bill.type === 1 && (
消费
)} {bill.type === 2 && (
退款
)} {bill.type === 3 && (
充值
)}
{bill.info}
) }, }, { header: "应付金额", accessorKey: "amount", cell: ({ row }) => { const amount = typeof row.original.amount === "string" ? parseFloat(row.original.amount) : row.original.amount || 0 return (
0 ? "text-green-500" : "text-orange-500" } > ¥{amount.toFixed(2)}
) }, }, { header: "实付金额", accessorKey: "actual", cell: ({ row }) => { const actual = typeof row.original.actual === "string" ? parseFloat(row.original.actual) : row.original.actual || 0 return (
0 ? "text-green-500" : "text-orange-500" } > ¥{actual.toFixed(2)}
) }, }, { header: "账单号", accessorKey: "bill_no", cell: ({ row }) => { const billNo = row.original.bill_no return ( {billNo} ) }, }, { header: "订单号", accessorKey: "trade.inner_no", cell: ({ row }) => { const bill = row.original return (
{bill.trade?.acquirer === 1 && (
支付宝
)} {bill.trade?.acquirer === 2 && (
微信
)} {bill.trade?.acquirer === 3 && (
银联
)} {!bill.trade?.acquirer && (
余额
)}
{bill.trade?.inner_no}
) }, }, ]} />
) }