import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Plus, Edit, Trash2, Search, Eye } from 'lucide-react'; import { format } from 'date-fns'; import { Input } from '@/client/components/ui/input'; 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 { 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 { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { toast } from 'sonner'; import { DataTablePagination } from '@/client/admin/components/DataTablePagination'; import ImageSelector from '@/client/admin/components/ImageSelector'; import AdvertisementTypeSelector from '@/client/admin/components/AdvertisementTypeSelector'; import { advertisementClient } from '@/client/api'; import type { InferRequestType, InferResponseType } from 'hono/client'; import { CreateAdvertisementDto, UpdateAdvertisementDto } from '@d8d/server/modules/advertisements/advertisement.schema'; type CreateRequest = InferRequestType['json']; type UpdateRequest = InferRequestType['json']; type AdvertisementResponse = InferResponseType['data'][0]; const createFormSchema = CreateAdvertisementDto; const updateFormSchema = UpdateAdvertisementDto; export const AdvertisementsPage = () => { const queryClient = useQueryClient(); const [searchParams, setSearchParams] = useState({ page: 1, limit: 10, search: '' }); const [isModalOpen, setIsModalOpen] = useState(false); const [editingAdvertisement, setEditingAdvertisement] = useState(null); const [isCreateForm, setIsCreateForm] = useState(true); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [advertisementToDelete, setAdvertisementToDelete] = useState(null); // 表单实例 const createForm = useForm({ resolver: zodResolver(createFormSchema), defaultValues: { title: '', typeId: 1, code: '', url: '', imageFileId: undefined, sort: 0, status: 1, actionType: 1 } }); const updateForm = useForm({ resolver: zodResolver(updateFormSchema), defaultValues: {} }); // 获取广告类型列表 - 现在由 AdvertisementTypeSelector 内部处理 // 数据查询 const { data, isLoading, refetch } = useQuery({ queryKey: ['advertisements', searchParams], queryFn: async () => { const res = await advertisementClient.$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 advertisementClient.$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 instanceof Error ? error.message : '创建广告失败'); } }); // 更新广告 const updateMutation = useMutation({ mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => { const res = await advertisementClient[':id'].$put({ param: { id: id.toString() }, json: data }); if (res.status !== 200) throw new Error('更新广告失败'); return await res.json(); }, onSuccess: () => { toast.success('广告更新成功'); setIsModalOpen(false); setEditingAdvertisement(null); refetch(); }, onError: (error) => { toast.error(error instanceof Error ? error.message : '更新广告失败'); } }); // 删除广告 const deleteMutation = useMutation({ mutationFn: async (id: number) => { const res = await advertisementClient[':id'].$delete({ param: { id: id.toString() } }); if (res.status !== 204) throw new Error('删除广告失败'); return await res.json(); }, onSuccess: () => { toast.success('广告删除成功'); setDeleteDialogOpen(false); setAdvertisementToDelete(null); refetch(); }, onError: (error) => { toast.error(error instanceof Error ? error.message : '删除广告失败'); } }); // 处理搜索 const handleSearch = (e: React.FormEvent) => { e.preventDefault(); setSearchParams(prev => ({ ...prev, page: 1 })); refetch(); }; // 处理创建广告 const handleCreateAdvertisement = () => { setIsCreateForm(true); setEditingAdvertisement(null); createForm.reset(); setIsModalOpen(true); }; // 处理编辑广告 const handleEditAdvertisement = (advertisement: AdvertisementResponse) => { setIsCreateForm(false); setEditingAdvertisement(advertisement); updateForm.reset({ title: advertisement.title || undefined, typeId: advertisement.typeId || undefined, code: advertisement.code || undefined, url: advertisement.url || undefined, imageFileId: advertisement.imageFileId || undefined, sort: advertisement.sort || undefined, status: advertisement.status || undefined, actionType: advertisement.actionType || undefined }); setIsModalOpen(true); }; // 处理删除广告 const handleDeleteAdvertisement = (id: number) => { setAdvertisementToDelete(id); setDeleteDialogOpen(true); }; // 确认删除 const confirmDelete = () => { if (advertisementToDelete) { deleteMutation.mutate(advertisementToDelete); } }; // 处理创建表单提交 const handleCreateSubmit = async (data: CreateRequest) => { try { await createMutation.mutateAsync(data); } catch (error) { throw error; } }; // 处理编辑表单提交 const handleUpdateSubmit = async (data: UpdateRequest) => { if (!editingAdvertisement) return; try { await updateMutation.mutateAsync({ id: editingAdvertisement.id, data }); } catch (error) { throw error; } }; // 渲染加载骨架 if (isLoading) { return (

广告管理

); } return (

广告管理

广告列表 管理网站的所有广告内容
setSearchParams(prev => ({ ...prev, search: e.target.value }))} className="pl-8" />
ID 标题 类型 别名 图片 状态 排序 创建时间 操作 {data?.data.map((advertisement) => ( {advertisement.id} {advertisement.title || '-'} {advertisement.advertisementType?.name || '-'} {advertisement.code || '-'} {advertisement.imageFile?.fullUrl ? ( {advertisement.title { e.currentTarget.src = '/placeholder.png'; }} /> ) : ( 无图片 )} {advertisement.status === 1 ? '启用' : '禁用'} {advertisement.sort} {advertisement.createdAt ? format(new Date(advertisement.createdAt), 'yyyy-MM-dd HH:mm') : '-'}
))}
{data?.data.length === 0 && !isLoading && (

暂无广告数据

)} setSearchParams(prev => ({ ...prev, page, limit }))} />
{/* 创建/编辑对话框 */} {isCreateForm ? '创建广告' : '编辑广告'} {isCreateForm ? '创建一个新的广告' : '编辑现有广告信息'} {isCreateForm ? ( // 创建表单(独立渲染)
( 标题 * 广告显示的标题文本,最多30个字符 )} /> ( 广告类型 * )} /> ( 调用别名 * 用于程序调用的唯一标识,最多20个字符 )} /> ( 广告图片 推荐尺寸:1200x400px,支持jpg、png格式 )} /> ( 跳转链接 点击广告后跳转的URL地址 )} />
( 跳转类型 )} /> ( 排序值 field.onChange(parseInt(e.target.value))} /> 数值越大排序越靠前 )} />
( 状态 )} /> ) : ( // 编辑表单(独立渲染)
( 标题 * 广告显示的标题文本,最多30个字符 )} /> ( 广告类型 * )} /> ( 调用别名 * 用于程序调用的唯一标识,最多20个字符 )} /> ( 广告图片 推荐尺寸:1200x400px,支持jpg、png格式 )} /> ( 跳转链接 点击广告后跳转的URL地址 )} />
( 跳转类型 )} /> ( 排序值 field.onChange(parseInt(e.target.value))} /> 数值越大排序越靠前 )} />
( 状态 )} /> )}
{/* 删除确认对话框 */} 确认删除 确定要删除这个广告吗?此操作无法撤销。
); }; // 简单的骨架屏组件 const Skeleton = ({ className }: { className?: string }) => (
);