import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { Button } from '@/client/components/ui/button'; 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 { Input } from '@/client/components/ui/input'; import { Textarea } from '@/client/components/ui/textarea'; import { Switch } from '@/client/components/ui/switch'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/client/components/ui/select'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Plus, Edit, Trash2, Search } from 'lucide-react'; import { membershipPlanClient } from '@/client/api'; import type { InferResponseType, InferRequestType } from 'hono/client'; import { toast } from 'sonner'; import { z } from 'zod'; import { CreateMembershipPlanDto, UpdateMembershipPlanDto, MembershipType } from '@/server/modules/membership/membership-plan.schema'; import { format } from 'date-fns'; import { Badge } from '@/client/components/ui/badge'; import { DataTablePagination } from '@/client/admin/components/DataTablePagination'; import { Skeleton } from '@/client/components/ui/skeleton'; // 类型定义 type MembershipPlanResponse = InferResponseType['data'][0]; type MembershipPlanListResponse = InferResponseType; type CreateMembershipPlanRequest = InferRequestType['json']; type UpdateMembershipPlanRequest = InferRequestType['json']; // 直接使用后端的schema const createFormSchema = CreateMembershipPlanDto; const updateFormSchema = UpdateMembershipPlanDto; // 类型映射 const typeLabels: Record = { [MembershipType.SINGLE]: '单次', [MembershipType.MONTHLY]: '单月', [MembershipType.YEARLY]: '年', [MembershipType.LIFETIME]: '永久', }; export const MembershipPlans = () => { // 状态管理 const [searchParams, setSearchParams] = useState({ page: 1, limit: 10, search: '', }); const [isModalOpen, setIsModalOpen] = useState(false); const [isCreateForm, setIsCreateForm] = useState(true); const [editingPlan, setEditingPlan] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [planToDelete, setPlanToDelete] = useState(null); // 表单实例 const createForm = useForm({ resolver: zodResolver(createFormSchema), defaultValues: { name: '', type: MembershipType.MONTHLY, price: 0, durationDays: 30, description: null, features: [], isActive: 1, sortOrder: 0, }, }); const updateForm = useForm({ resolver: zodResolver(updateFormSchema), }); // 数据查询 const { data, isLoading, refetch } = useQuery({ queryKey: ['membership-plans', searchParams], queryFn: async () => { const res = await membershipPlanClient.$get({ query: { page: searchParams.page, pageSize: searchParams.limit, keyword: searchParams.search, }, }); if (res.status !== 200) throw new Error('获取套餐列表失败'); return await res.json(); }, }); // 业务逻辑函数 const handleSearch = () => { setSearchParams(prev => ({ ...prev, page: 1 })); refetch(); }; const handleCreatePlan = () => { setIsCreateForm(true); setEditingPlan(null); createForm.reset({ name: '', type: MembershipType.MONTHLY, price: 29.99, durationDays: 30, description: null, features: [], isActive: 1, sortOrder: 0, }); setIsModalOpen(true); }; const handleEditPlan = (plan: MembershipPlanResponse) => { setIsCreateForm(false); setEditingPlan(plan); updateForm.reset({ name: plan.name, type: plan.type, price: plan.price, durationDays: plan.durationDays, description: plan.description, features: plan.features, isActive: plan.isActive, sortOrder: plan.sortOrder, }); setIsModalOpen(true); }; const handleDeletePlan = (id: number) => { setPlanToDelete(id); setDeleteDialogOpen(true); }; const confirmDelete = async () => { if (!planToDelete) return; try { const res = await membershipPlanClient[':id']['$delete']({ param: { id: planToDelete.toString() }, }); if (res.status === 204) { toast.success('删除成功'); setDeleteDialogOpen(false); setPlanToDelete(null); refetch(); } else { const error = await res.json(); toast.error(error.message || '删除失败'); } } catch (error) { toast.error('删除失败,请重试'); } }; const handleCreateSubmit = async (data: CreateMembershipPlanRequest) => { try { const res = await membershipPlanClient.$post({ json: data }); if (res.status === 201) { toast.success('创建成功'); setIsModalOpen(false); refetch(); } else { const error = await res.json(); toast.error(error.message || '创建失败'); } } catch (error) { toast.error('创建失败,请重试'); } }; const handleUpdateSubmit = async (data: UpdateMembershipPlanRequest) => { if (!editingPlan) return; try { const res = await membershipPlanClient[':id']['$put']({ param: { id: editingPlan.id.toString() }, json: data, }); if (res.status === 200) { toast.success('更新成功'); setIsModalOpen(false); refetch(); } else { const error = await res.json(); toast.error(error.message || '更新失败'); } } catch (error) { toast.error('更新失败,请重试'); } }; // 渲染骨架屏 if (isLoading) { return (
{[...Array(5)].map((_, i) => (
))}
); } return (
{/* 页面标题区域 */}

会员套餐管理

{/* 搜索区域 */} 套餐列表 管理所有会员套餐
{ e.preventDefault(); handleSearch(); }} className="flex gap-2">
setSearchParams(prev => ({ ...prev, search: e.target.value }))} className="pl-8" />
{/* 数据表格 */}
套餐名称 类型 价格 有效期 状态 排序 创建时间 操作 {data?.data.map((plan) => ( {plan.name} {typeLabels[plan.type]} ¥{plan.price.toFixed(2)} {plan.durationDays === 0 ? ( 永久 ) : ( `${plan.durationDays}天` )} {plan.isActive === 1 ? '启用' : '禁用'} {plan.sortOrder} {format(new Date(plan.createdAt), 'yyyy-MM-dd HH:mm')}
))}
{data?.data.length === 0 && !isLoading && (

暂无数据

)}
{/* 分页 */} setSearchParams(prev => ({ ...prev, page, limit }))} />
{/* 创建/编辑模态框 */} {isCreateForm ? '创建套餐' : '编辑套餐'} {isCreateForm ? '创建一个新的会员套餐' : '编辑现有套餐信息'} {isCreateForm ? (
( 套餐名称 * )} /> ( 套餐类型 * )} /> ( 价格(元) * field.onChange(parseFloat(e.target.value))} /> )} /> ( 有效期(天) * field.onChange(parseInt(e.target.value))} /> 0表示永久有效 )} /> ( 套餐描述