"use client" import { zodResolver } from "@hookform/resolvers/zod" import { format } from "date-fns" import { Suspense, useCallback, useState } from "react" import { Controller, useForm } from "react-hook-form" import { toast } from "sonner" import { z } from "zod" import { createCust, getPageCusts, updateCust } from "@/actions/cust" import { DataTable, useDataTable } from "@/components/data-table" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog" 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 type { Cust } from "@/models/cust" type FilterValues = { phone?: string name?: string identified?: boolean enabled?: boolean created_at_start?: Date created_at_end?: Date } const filterSchema = z .object({ phone: z.string().optional(), name: z.string().optional(), identified: z.string().optional(), enabled: z.string().optional(), created_at_start: z.string().optional(), created_at_end: 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"], }) } } }) const addUserSchema = z.object({ username: z.string().min(1, "账号不能为空"), password: z.string().min(6, "密码至少6位"), phone: z.string().regex(/^1[3-9]\d{9}$/, "请输入正确的手机号格式"), email: z.string().email("请输入正确的邮箱格式").optional().or(z.literal("")), name: z.string().optional(), contact_wechat: z.string().optional(), }) type AddUserFormValues = z.infer type FormValues = z.infer export default function UserPage() { const [filters, setFilters] = useState({}) const [editingRowId, setEditingRowId] = useState(null) const [editPhone, setEditPhone] = useState("") const [editEmail, setEditEmail] = useState("") const [isSaving, setIsSaving] = useState(false) const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) const [isAdding, setIsAdding] = useState(false) const { control, handleSubmit, reset } = useForm({ resolver: zodResolver(filterSchema), defaultValues: { phone: "", name: "", identified: "all", enabled: "all", created_at_start: "", created_at_end: "", }, }) // 添加用户表单 const { control: addControl, handleSubmit: handleAddSubmit, reset: resetAddForm, formState: { errors: addErrors }, } = useForm({ resolver: zodResolver(addUserSchema), defaultValues: { username: "", password: "", phone: "", email: "", name: "", contact_wechat: "", }, }) const fetchUsers = useCallback( (page: number, size: number) => getPageCusts({ page, size, ...filters }), [filters], ) const table = useDataTable(fetchUsers) const onFilter = handleSubmit(data => { const result: FilterValues = {} if (data.phone) result.phone = data.phone if (data.name) result.name = data.name if (data.identified && data.identified !== "all") result.identified = data.identified === "1" if (data.enabled && data.enabled !== "all") result.enabled = data.enabled === "1" setFilters(result) table.pagination.onPageChange(1) }) const refreshTable = useCallback(() => { table.pagination.onPageChange(table.pagination.page) }, [table.pagination]) // 开始编辑行 const startEdit = (row: Cust) => { setEditingRowId(row.id) setEditPhone(row.phone || "") setEditEmail(row.email || "") } // 取消编辑 const cancelEdit = () => { setEditingRowId(null) setEditPhone("") setEditEmail("") } // 保存编辑 const saveEdit = async (row: Cust) => { const phoneRegex = /^1[3-9]\d{9}$/ if (editPhone && !phoneRegex.test(editPhone)) { toast.error("请输入正确的手机号格式") return } const emailRegex = /^[^\s@]+@([^\s@]+\.)+[^\s@]+$/ if (editEmail && !emailRegex.test(editEmail)) { toast.error("请输入正确的邮箱格式") return } setIsSaving(true) try { const result = await updateCust({ id: row.id, phone: editPhone, email: editEmail, }) if (result.success) { toast.success("更新成功") table.pagination.onPageChange(table.pagination.page) cancelEdit() } else { toast.error(result.message || "更新失败") } } catch (error) { toast.error("更新失败,请稍后重试") console.error(error) } finally { setIsSaving(false) } } const onAddUser = handleAddSubmit(async data => { const payload = { username: data.username, password: data.password, phone: data.phone || "", email: data.email || "", name: data.name || "", contact_wechat: data.contact_wechat || "", } setIsAdding(true) try { const result = await createCust(payload) if (result?.success) { toast.success("添加用户成功") setIsAddDialogOpen(false) resetAddForm() refreshTable() } else { toast.error(result?.message || "添加失败") } } catch (error) { toast.error("添加失败,请稍后重试") console.error(error) } finally { setIsAdding(false) } }) // 打开添加对话框时重置表单 const openAddDialog = () => { resetAddForm() setIsAddDialogOpen(true) } return (
( 手机号 {fieldState.error?.message} )} /> ( 账户 {fieldState.error?.message} )} /> ( 实名状态 {fieldState.error?.message} )} /> ( 账号状态 {fieldState.error?.message} )} /> ( 开始时间 {fieldState.error?.message} )} /> ( 结束时间 {fieldState.error?.message} )} />
{...table} columns={[ { header: "ID", accessorKey: "id" }, { header: "账号", accessorKey: "username" }, { header: "密码", accessorKey: "admin.password" }, { header: "手机", accessorKey: "phone", cell: ({ row }) => { const isEditing = editingRowId === row.original.id if (isEditing) { return ( setEditPhone(e.target.value)} placeholder="手机号" className="w-32" /> ) } return row.original.phone || "-" }, }, { header: "邮箱", accessorKey: "email", cell: ({ row }) => { const isEditing = editingRowId === row.original.id if (isEditing) { return ( setEditEmail(e.target.value)} placeholder="邮箱" className="w-40" /> ) } return row.original.email || "-" }, }, { header: "姓名", accessorKey: "name" }, { header: "余额", accessorKey: "balance", cell: ({ row }) => { const balance = Number(row.original.balance) || 0 return ( 0 ? "text-green-500" : "text-orange-500" } > ¥{balance.toFixed(2)} ) }, }, { header: "实名状态", accessorKey: "id_type", cell: ({ row }) => ( {row.original.id_type === 1 ? "已认证" : "未认证"} ), }, { header: "身份证号", accessorKey: "id_no", cell: ({ row }) => { const idNo = row.original.id_no return idNo ? `${idNo.slice(0, 6)}****${idNo.slice(-4)}` : "-" }, }, { header: "账号状态", accessorKey: "status", cell: ({ row }) => (row.original.status === 1 ? "正常" : "禁用"), }, { header: "联系方式", accessorKey: "contact_wechat" }, { header: "客户来源", accessorKey: "" }, { header: "客户经理", accessorKey: "admin.name" }, { header: "最后登录时间", accessorKey: "last_login", cell: ({ row }) => row.original.last_login ? format( new Date(row.original.last_login), "yyyy-MM-dd HH:mm", ) : "-", }, { header: "最后登录IP", accessorKey: "last_login_ip", cell: ({ row }) => row.original.last_login_ip || "-", }, { header: "创建时间", accessorKey: "created_at", cell: ({ row }) => format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"), }, { header: "操作", cell: ({ row }) => { const isEditing = editingRowId === row.original.id if (isEditing) { return (
) } return ( ) }, }, ]} />
{/* 添加用户对话框 */} 添加用户
( 账号 * {fieldState.error?.message} )} /> ( 密码 * {fieldState.error?.message} )} /> ( 手机号 * {fieldState.error?.message} )} /> ( 姓名 {fieldState.error?.message} )} /> ( 邮箱 {fieldState.error?.message} )} /> ( 微信/联系方式 {fieldState.error?.message} )} />
) }