import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { format } from 'date-fns'; import { zhCN } from 'date-fns/locale'; import { toast } from 'sonner'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import type { InferRequestType, InferResponseType } from 'hono/client'; import { Button } from '@/client/components/ui/button'; import { Input } from '@/client/components/ui/input'; import { Label } from '@/client/components/ui/label'; import { Badge } from '@/client/components/ui/badge'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table'; 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 { Textarea } from '@/client/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select'; import { goodsClient } from '@/client/api'; import { CreateGoodsDto, UpdateGoodsDto } from '@/server/modules/goods/goods.schema'; import { DataTablePagination } from '@/client/admin-shadcn/components/DataTablePagination'; import ImageSelector from '@/client/admin-shadcn/components/ImageSelector'; import GoodsCategorySelector from '@/client/admin-shadcn/components/GoodsCategorySelector'; import GoodsCategoryCascadeSelector from '@/client/admin-shadcn/components/GoodsCategoryCascadeSelector'; import SupplierSelector from '@/client/admin-shadcn/components/SupplierSelector'; import { Search, Plus, Edit, Trash2, Package } from 'lucide-react'; type CreateRequest = InferRequestType['json']; type UpdateRequest = InferRequestType['json']; type GoodsResponse = InferResponseType['data'][0]; const createFormSchema = CreateGoodsDto; const updateFormSchema = UpdateGoodsDto; export const GoodsPage = () => { const queryClient = useQueryClient(); const [searchParams, setSearchParams] = useState({ page: 1, limit: 10, search: '' }); const [isModalOpen, setIsModalOpen] = useState(false); const [editingGoods, setEditingGoods] = useState(null); const [isCreateForm, setIsCreateForm] = useState(true); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [goodsToDelete, setGoodsToDelete] = useState(null); // 创建表单 const createForm = useForm({ resolver: zodResolver(createFormSchema), defaultValues: { name: '', price: 0, costPrice: 0, categoryId1: 0, categoryId2: 0, categoryId3: 0, goodsType: 1, supplierId: null, imageFileId: null, slideImageIds: [], detail: '', instructions: '', sort: 0, state: 1, stock: 0, lowestBuy: 1, }, }); // 更新表单 const updateForm = useForm({ resolver: zodResolver(updateFormSchema), }); // 获取商品列表 const { data, isLoading, refetch } = useQuery({ queryKey: ['goods', searchParams], queryFn: async () => { const res = await goodsClient.$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 goodsClient.$post({ json: data }); if (res.status !== 201) throw new Error('创建商品失败'); return await res.json(); }, onSuccess: () => { toast.success('商品创建成功'); setIsModalOpen(false); createForm.reset(); refetch(); }, onError: (error) => { toast.error(error.message || '创建商品失败'); } }); // 更新商品 const updateMutation = useMutation({ mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => { const res = await goodsClient[':id']['$put']({ param: { id: id.toString() }, json: data }); if (res.status !== 200) throw new Error('更新商品失败'); return await res.json(); }, onSuccess: () => { toast.success('商品更新成功'); setIsModalOpen(false); setEditingGoods(null); refetch(); }, onError: (error) => { toast.error(error.message || '更新商品失败'); } }); // 删除商品 const deleteMutation = useMutation({ mutationFn: async (id: number) => { const res = await goodsClient[':id']['$delete']({ param: { id: id.toString() } }); if (res.status !== 204) throw new Error('删除商品失败'); return id; }, onSuccess: () => { toast.success('商品删除成功'); setDeleteDialogOpen(false); setGoodsToDelete(null); refetch(); }, onError: (error) => { toast.error(error.message || '删除商品失败'); } }); // 处理搜索 const handleSearch = (e: React.FormEvent) => { e.preventDefault(); setSearchParams(prev => ({ ...prev, page: 1 })); }; // 处理创建 const handleCreateGoods = () => { setIsCreateForm(true); setEditingGoods(null); createForm.reset(); setIsModalOpen(true); }; // 处理编辑 const handleEditGoods = (goods: GoodsResponse) => { setIsCreateForm(false); setEditingGoods(goods); updateForm.reset({ name: goods.name, price: goods.price, costPrice: goods.costPrice, categoryId1: goods.categoryId1, categoryId2: goods.categoryId2, categoryId3: goods.categoryId3, goodsType: goods.goodsType, supplierId: goods.supplierId, imageFileId: goods.imageFileId, slideImageIds: goods.slideImages?.map(img => img.id) || [], detail: goods.detail || '', instructions: goods.instructions || '', sort: goods.sort, state: goods.state, stock: goods.stock, lowestBuy: goods.lowestBuy, }); setIsModalOpen(true); }; // 处理删除 const handleDeleteGoods = (id: number) => { setGoodsToDelete(id); setDeleteDialogOpen(true); }; // 确认删除 const confirmDelete = () => { if (goodsToDelete) { deleteMutation.mutate(goodsToDelete); } }; // 提交表单 const handleSubmit = (data: CreateRequest | UpdateRequest) => { if (isCreateForm) { createMutation.mutate(data as CreateRequest); } else if (editingGoods) { updateMutation.mutate({ id: editingGoods.id, data: data as UpdateRequest }); } }; return (

商品管理

商品列表 管理您的商品信息
setSearchParams(prev => ({ ...prev, search: e.target.value }))} className="pl-8" />
商品图片 商品名称 价格 库存 销量 供应商 状态 创建时间 操作 {data?.data.map((goods) => ( {goods.imageFile?.fullUrl ? ( {goods.name} ) : (
)}
{goods.name} ¥{goods.price.toFixed(2)} {goods.stock} {goods.salesNum} {goods.supplier?.name || '-'} {goods.state === 1 ? '可用' : '不可用'} {format(new Date(goods.createdAt), 'yyyy-MM-dd', { locale: zhCN })}
))}
{data?.data.length === 0 && !isLoading && (

暂无商品数据

)}
setSearchParams(prev => ({ ...prev, page, limit }))} />
{/* 创建/编辑对话框 */} {isCreateForm ? '创建商品' : '编辑商品'} {isCreateForm ? '创建一个新的商品' : '编辑商品信息'} {isCreateForm ? (
( 商品名称 * )} />
( 售卖价 * )} /> ( 成本价 * )} />
( 供应商 )} /> ( 商品类型 )} />
( 库存 * )} /> ( 商品主图 推荐尺寸:800x800px )} /> ( 商品轮播图 最多上传5张轮播图,推荐尺寸:800x800px )} /> ( 商品简介