|
|
@@ -1,30 +1,22 @@
|
|
|
-import React, { useState, useEffect } from 'react';
|
|
|
-import { useForm } from 'react-hook-form';
|
|
|
-import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
+import React, { useState } from 'react';
|
|
|
import { Plus, Edit, Trash2 } from 'lucide-react';
|
|
|
import { toast } from 'sonner';
|
|
|
|
|
|
import { Button } from '@/client/components/ui/button';
|
|
|
import { Input } from '@/client/components/ui/input';
|
|
|
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 { Switch } from '@/client/components/ui/switch';
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
|
|
|
import { Badge } from '@/client/components/ui/badge';
|
|
|
-import AvatarSelector from '@/client/admin-shadcn/components/AvatarSelector';
|
|
|
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
import { advertisementClient } from '@/client/api';
|
|
|
-import type { InferResponseType, InferRequestType } from 'hono/client';
|
|
|
-import { CreateAdvertisementDto, UpdateAdvertisementDto } from '@/server/modules/advertisements/advertisement.schema';
|
|
|
+import type { InferResponseType } from 'hono/client';
|
|
|
+import CreateAdvertisementForm from '@/client/admin-shadcn/components/CreateAdvertisementForm';
|
|
|
+import EditAdvertisementForm from '@/client/admin-shadcn/components/EditAdvertisementForm';
|
|
|
|
|
|
// 类型定义
|
|
|
type AdvertisementResponse = InferResponseType<typeof advertisementClient.$get, 200>;
|
|
|
type AdvertisementItem = AdvertisementResponse['data'][0];
|
|
|
-type CreateRequest = InferRequestType<typeof advertisementClient.$post>['json'];
|
|
|
-type UpdateRequest = InferRequestType<typeof advertisementClient[':id']['$put']>['json'];
|
|
|
|
|
|
const Advertisements: React.FC = () => {
|
|
|
const queryClient = useQueryClient();
|
|
|
@@ -49,42 +41,6 @@ const Advertisements: React.FC = () => {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- // 创建广告
|
|
|
- const createMutation = useMutation({
|
|
|
- mutationFn: async (data: CreateRequest) => {
|
|
|
- const response = await advertisementClient.$post({ json: data });
|
|
|
- if (response.status !== 200) throw new Error('创建广告失败');
|
|
|
- return response.json();
|
|
|
- },
|
|
|
- onSuccess: () => {
|
|
|
- queryClient.invalidateQueries({ queryKey: ['advertisements'] });
|
|
|
- toast.success('广告创建成功');
|
|
|
- setIsModalOpen(false);
|
|
|
- },
|
|
|
- onError: (error) => {
|
|
|
- toast.error(`创建失败:${error.message}`);
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // 更新广告
|
|
|
- const updateMutation = useMutation({
|
|
|
- mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => {
|
|
|
- const response = await advertisementClient[':id']['$put']({
|
|
|
- param: { id: id.toString() },
|
|
|
- json: data
|
|
|
- });
|
|
|
- if (response.status !== 200) throw new Error('更新广告失败');
|
|
|
- return response.json();
|
|
|
- },
|
|
|
- onSuccess: () => {
|
|
|
- queryClient.invalidateQueries({ queryKey: ['advertisements'] });
|
|
|
- toast.success('广告更新成功');
|
|
|
- setIsModalOpen(false);
|
|
|
- },
|
|
|
- onError: (error) => {
|
|
|
- toast.error(`更新失败:${error.message}`);
|
|
|
- }
|
|
|
- });
|
|
|
|
|
|
// 删除广告
|
|
|
const deleteMutation = useMutation({
|
|
|
@@ -92,7 +48,7 @@ const Advertisements: React.FC = () => {
|
|
|
const response = await advertisementClient[':id']['$delete']({
|
|
|
param: { id: id.toString() }
|
|
|
});
|
|
|
- if (response.status !== 200) throw new Error('删除广告失败');
|
|
|
+ if (response.status !== 204) throw new Error('删除广告失败');
|
|
|
return response.json();
|
|
|
},
|
|
|
onSuccess: () => {
|
|
|
@@ -104,32 +60,10 @@ const Advertisements: React.FC = () => {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- // 表单实例
|
|
|
- const createForm = useForm<CreateRequest>({
|
|
|
- resolver: zodResolver(CreateAdvertisementDto),
|
|
|
- defaultValues: {
|
|
|
- title: '',
|
|
|
- linkUrl: '',
|
|
|
- imageFileId: undefined,
|
|
|
- positionId: 1,
|
|
|
- sortOrder: 0,
|
|
|
- isEnabled: 1,
|
|
|
- description: '',
|
|
|
- startTime: undefined,
|
|
|
- endTime: undefined
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- const updateForm = useForm<UpdateRequest>({
|
|
|
- resolver: zodResolver(UpdateAdvertisementDto),
|
|
|
- defaultValues: {}
|
|
|
- });
|
|
|
-
|
|
|
// 打开创建表单
|
|
|
const handleCreate = () => {
|
|
|
setEditingData(null);
|
|
|
setIsCreateForm(true);
|
|
|
- createForm.reset();
|
|
|
setIsModalOpen(true);
|
|
|
};
|
|
|
|
|
|
@@ -137,27 +71,19 @@ const Advertisements: React.FC = () => {
|
|
|
const handleEdit = (data: AdvertisementItem) => {
|
|
|
setEditingData(data);
|
|
|
setIsCreateForm(false);
|
|
|
- updateForm.reset({
|
|
|
- title: data.title,
|
|
|
- linkUrl: data.linkUrl,
|
|
|
- imageFileId: data.imageFileId || undefined,
|
|
|
- positionId: data.positionId,
|
|
|
- sortOrder: data.sortOrder,
|
|
|
- isEnabled: data.isEnabled,
|
|
|
- description: data.description || undefined,
|
|
|
- startTime: data.startTime || undefined,
|
|
|
- endTime: data.endTime || undefined
|
|
|
- });
|
|
|
setIsModalOpen(true);
|
|
|
};
|
|
|
|
|
|
- // 处理表单提交
|
|
|
- const handleSubmit = (data: CreateRequest | UpdateRequest) => {
|
|
|
- if (isCreateForm) {
|
|
|
- createMutation.mutate(data as CreateRequest);
|
|
|
- } else if (editingData) {
|
|
|
- updateMutation.mutate({ id: editingData.id, data: data as UpdateRequest });
|
|
|
- }
|
|
|
+ // 处理创建成功
|
|
|
+ const handleCreateSuccess = () => {
|
|
|
+ setIsModalOpen(false);
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['advertisements'] });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理更新成功
|
|
|
+ const handleUpdateSuccess = () => {
|
|
|
+ setIsModalOpen(false);
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['advertisements'] });
|
|
|
};
|
|
|
|
|
|
// 处理删除
|
|
|
@@ -275,170 +201,20 @@ const Advertisements: React.FC = () => {
|
|
|
</Card>
|
|
|
|
|
|
{/* 创建/编辑对话框 */}
|
|
|
- <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
|
|
- <DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
|
|
- <DialogHeader>
|
|
|
- <DialogTitle>{isCreateForm ? '创建广告' : '编辑广告'}</DialogTitle>
|
|
|
- <DialogDescription>
|
|
|
- {isCreateForm ? '创建新的广告内容' : '编辑现有广告信息'}
|
|
|
- </DialogDescription>
|
|
|
- </DialogHeader>
|
|
|
-
|
|
|
- <Form {...(isCreateForm ? createForm : updateForm)}>
|
|
|
- <form onSubmit={(isCreateForm ? createForm : updateForm).handleSubmit(handleSubmit)} className="space-y-4">
|
|
|
- <FormField
|
|
|
- control={(isCreateForm ? createForm : updateForm).control}
|
|
|
- name="title"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel className="flex items-center">
|
|
|
- 广告标题
|
|
|
- <span className="text-red-500 ml-1">*</span>
|
|
|
- </FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Input placeholder="请输入广告标题" {...field} />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={(isCreateForm ? createForm : updateForm).control}
|
|
|
- name="linkUrl"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel className="flex items-center">
|
|
|
- 链接地址
|
|
|
- <span className="text-red-500 ml-1">*</span>
|
|
|
- </FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Input placeholder="请输入链接地址" {...field} />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={(isCreateForm ? createForm : updateForm).control}
|
|
|
- name="imageFileId"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel>
|
|
|
- 图片文件
|
|
|
- </FormLabel>
|
|
|
- <FormControl>
|
|
|
- <AvatarSelector
|
|
|
- value={field.value || undefined}
|
|
|
- onChange={(value) => field.onChange(value ? Number(value) : undefined)}
|
|
|
- maxSize={2}
|
|
|
- uploadPath="/advertisements"
|
|
|
- uploadButtonText="上传图片"
|
|
|
- previewSize="medium"
|
|
|
- placeholder="选择广告图片"
|
|
|
- />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <div className="grid grid-cols-2 gap-4">
|
|
|
- <FormField
|
|
|
- control={(isCreateForm ? createForm : updateForm).control}
|
|
|
- name="positionId"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel className="flex items-center">
|
|
|
- 广告位ID
|
|
|
- <span className="text-red-500 ml-1">*</span>
|
|
|
- </FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Input
|
|
|
- type="number"
|
|
|
- placeholder="请输入广告位ID"
|
|
|
- {...field}
|
|
|
- onChange={(e) => field.onChange(Number(e.target.value))}
|
|
|
- />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={(isCreateForm ? createForm : updateForm).control}
|
|
|
- name="sortOrder"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel>排序值</FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Input
|
|
|
- type="number"
|
|
|
- placeholder="排序值"
|
|
|
- {...field}
|
|
|
- onChange={(e) => field.onChange(Number(e.target.value))}
|
|
|
- />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={(isCreateForm ? createForm : updateForm).control}
|
|
|
- name="isEnabled"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
|
- <div className="space-y-0.5">
|
|
|
- <FormLabel className="text-base">启用状态</FormLabel>
|
|
|
- <FormDescription>
|
|
|
- 是否在前台显示该广告
|
|
|
- </FormDescription>
|
|
|
- </div>
|
|
|
- <FormControl>
|
|
|
- <Switch
|
|
|
- checked={field.value === 1}
|
|
|
- onCheckedChange={(checked) => field.onChange(checked ? 1 : 0)}
|
|
|
- />
|
|
|
- </FormControl>
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={(isCreateForm ? createForm : updateForm).control}
|
|
|
- name="description"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel>广告描述</FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Textarea
|
|
|
- placeholder="请输入广告描述"
|
|
|
- className="resize-none"
|
|
|
- {...field}
|
|
|
- value={field.value || ''}
|
|
|
- />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <DialogFooter>
|
|
|
- <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
|
|
|
- 取消
|
|
|
- </Button>
|
|
|
- <Button type="submit" disabled={createMutation.isPending || updateMutation.isPending}>
|
|
|
- {isCreateForm ? '创建' : '更新'}
|
|
|
- </Button>
|
|
|
- </DialogFooter>
|
|
|
- </form>
|
|
|
- </Form>
|
|
|
- </DialogContent>
|
|
|
- </Dialog>
|
|
|
+ {isCreateForm ? (
|
|
|
+ <CreateAdvertisementForm
|
|
|
+ open={isModalOpen}
|
|
|
+ onOpenChange={setIsModalOpen}
|
|
|
+ onSuccess={handleCreateSuccess}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <EditAdvertisementForm
|
|
|
+ open={isModalOpen}
|
|
|
+ onOpenChange={setIsModalOpen}
|
|
|
+ advertisement={editingData}
|
|
|
+ onSuccess={handleUpdateSuccess}
|
|
|
+ />
|
|
|
+ )}
|
|
|
</div>
|
|
|
);
|
|
|
};
|