import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { format } from 'date-fns' import { zhCN } from 'date-fns/locale' import { toast } from 'sonner' import type { InferRequestType, InferResponseType } from 'hono/client' import { Plus, Search, Edit, Trash2, Eye } from 'lucide-react' import { Button } from '@/client/components/ui/button' import { Input } from '@/client/components/ui/input' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table' import { Badge } from '@/client/components/ui/badge' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form' import { Separator } from '@/client/components/ui/separator' import { Switch } from '@/client/components/ui/switch' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select' import { DataTablePagination } from '@/client/admin/components/DataTablePagination' import { merchantClient } from '@/client/api' import { CreateMerchantDto, UpdateMerchantDto } from '@d8d/merchant-module' import { Skeleton } from '@/client/components/ui/skeleton' type CreateRequest = InferRequestType['json'] type UpdateRequest = InferRequestType['json'] type MerchantResponse = InferResponseType['data'][0] const createFormSchema = CreateMerchantDto const updateFormSchema = UpdateMerchantDto export const MerchantsPage = () => { const queryClient = useQueryClient() const [searchParams, setSearchParams] = useState({ page: 1, limit: 10, search: '', }) const [isModalOpen, setIsModalOpen] = useState(false) const [editingMerchant, setEditingMerchant] = useState(null) const [isCreateForm, setIsCreateForm] = useState(true) const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [merchantToDelete, setMerchantToDelete] = useState(null) const [detailDialogOpen, setDetailDialogOpen] = useState(false) const [detailMerchant, setDetailMerchant] = useState(null) // 创建表单 const createForm = useForm({ resolver: zodResolver(createFormSchema), defaultValues: { name: '', username: '', password: '', phone: '', realname: '', state: 2, rsaPublicKey: '', aesKey: '', }, }) // 更新表单 const updateForm = useForm({ resolver: zodResolver(updateFormSchema), }) // 获取商户列表 const { data, isLoading, refetch } = useQuery({ queryKey: ['merchants', searchParams], queryFn: async () => { const res = await merchantClient.$get({ query: { page: searchParams.page, pageSize: searchParams.limit, keyword: searchParams.search, } }) if (res.status !== 200) throw new Error('获取商户列表失败') return await res.json() } }) // 创建商户 const createMutation = useMutation({ mutationFn: async (data: CreateRequest) => { const res = await merchantClient.$post({ json: data }) if (res.status !== 201) throw new Error('创建商户失败') return await res.json() }, onSuccess: () => { toast.success('商户创建成功') setIsModalOpen(false) createForm.reset() refetch() }, onError: (error: Error) => { toast.error(error.message || '创建失败') } }) // 更新商户 const updateMutation = useMutation({ mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => { const res = await merchantClient[':id']['$put']({ param: { id: id.toString() }, json: data }) if (res.status !== 200) throw new Error('更新商户失败') return await res.json() }, onSuccess: () => { toast.success('商户更新成功') setIsModalOpen(false) setEditingMerchant(null) refetch() }, onError: (error: Error) => { toast.error(error.message || '更新失败') } }) // 删除商户 const deleteMutation = useMutation({ mutationFn: async (id: number) => { const res = await merchantClient[':id']['$delete']({ param: { id: id.toString() } }) if (res.status !== 204) throw new Error('删除商户失败') return res }, onSuccess: () => { toast.success('商户删除成功') setDeleteDialogOpen(false) setMerchantToDelete(null) refetch() }, onError: (error: Error) => { toast.error(error.message || '删除失败') } }) // 搜索处理 const handleSearch = (e?: React.FormEvent) => { e?.preventDefault() setSearchParams(prev => ({ ...prev, page: 1 })) } // 创建商户 const handleCreateMerchant = () => { setIsCreateForm(true) setEditingMerchant(null) createForm.reset() setIsModalOpen(true) } // 编辑商户 const handleEditMerchant = (merchant: MerchantResponse) => { setIsCreateForm(false) setEditingMerchant(merchant) updateForm.reset({ name: merchant.name || '', username: merchant.username, phone: merchant.phone || '', realname: merchant.realname || '', state: merchant.state, rsaPublicKey: merchant.rsaPublicKey || '', aesKey: merchant.aesKey || '', }) setIsModalOpen(true) } // 查看详情 const handleViewDetail = (merchant: MerchantResponse) => { setDetailMerchant(merchant) setDetailDialogOpen(true) } // 删除商户 const handleDeleteMerchant = (id: number) => { setMerchantToDelete(id) setDeleteDialogOpen(true) } // 确认删除 const confirmDelete = () => { if (merchantToDelete) { deleteMutation.mutate(merchantToDelete) } } // 提交表单 const handleSubmit = (data: CreateRequest | UpdateRequest) => { if (isCreateForm) { createMutation.mutate(data as CreateRequest) } else if (editingMerchant) { updateMutation.mutate({ id: editingMerchant.id, data: data as UpdateRequest }) } } // 状态文本 const getStateText = (state: number) => { return state === 1 ? '启用' : '禁用' } const getStateBadgeVariant = (state: number) => { return state === 1 ? 'default' : 'secondary' } // 渲染加载骨架 if (isLoading) { return (
{[...Array(5)].map((_, i) => (
))}
) } return (
{/* 页面标题 */}

商户管理

{/* 搜索区域 */} 商户列表 管理所有商户账户信息
setSearchParams(prev => ({ ...prev, search: e.target.value }))} className="pl-8" />
{/* 数据表格 */}
商户名称 用户名 姓名 手机号 状态 登录次数 创建时间 操作 {data?.data.map((merchant) => ( {merchant.name || '-'} {merchant.username} {merchant.realname || '-'} {merchant.phone || '-'} {getStateText(merchant.state)} {merchant.loginNum} {format(new Date(merchant.createdAt), 'yyyy-MM-dd HH:mm', { locale: zhCN })}
))}
{data?.data.length === 0 && !isLoading && (

暂无数据

)}
setSearchParams(prev => ({ ...prev, page, limit }))} />
{/* 创建/编辑对话框 */} {isCreateForm ? '创建商户' : '编辑商户'} {isCreateForm ? '创建一个新的商户账户' : '编辑现有商户信息'} {isCreateForm ? (
( 商户名称 )} /> ( 用户名 * )} /> ( 密码 * )} /> ( 手机号 )} /> ( 姓名 )} /> ( 状态 )} /> ) : (
( 商户名称 )} /> ( 用户名 )} /> ( 手机号 )} /> ( 姓名 )} /> ( 密码(留空则不修改) 如果不修改密码,请留空 )} /> ( 状态 )} /> )}
{/* 详情对话框 */} 商户详情 查看商户详细信息 {detailMerchant && (

{detailMerchant.name || '-'}

{detailMerchant.username}

{detailMerchant.realname || '-'}

{detailMerchant.phone || '-'}

{getStateText(detailMerchant.state)}

{detailMerchant.loginNum}

{format(new Date(detailMerchant.createdAt), 'yyyy-MM-dd HH:mm', { locale: zhCN })}

{format(new Date(detailMerchant.updatedAt), 'yyyy-MM-dd HH:mm', { locale: zhCN })}

{detailMerchant.lastLoginTime > 0 && (

{format(new Date(detailMerchant.lastLoginTime * 1000), 'yyyy-MM-dd HH:mm', { locale: zhCN })}

)} {detailMerchant.lastLoginIp && (

{detailMerchant.lastLoginIp}

)}
)}
{/* 删除确认对话框 */} 确认删除 确定要删除这个商户吗?此操作无法撤销。
) }