From ab4ea6e3670cbf3f5c5015a6a36bfa06cf725704 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 24 Oct 2025 22:20:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=A1=E6=89=B9=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CategoryManageDialog.tsx | 559 ++++++++++++++++++ frontend/src/pages/Form/Definition/index.tsx | 27 +- .../components/CategoryManageDialog.tsx | 31 +- 3 files changed, 609 insertions(+), 8 deletions(-) create mode 100644 frontend/src/pages/Form/Definition/components/CategoryManageDialog.tsx diff --git a/frontend/src/pages/Form/Definition/components/CategoryManageDialog.tsx b/frontend/src/pages/Form/Definition/components/CategoryManageDialog.tsx new file mode 100644 index 00000000..2c3ad32f --- /dev/null +++ b/frontend/src/pages/Form/Definition/components/CategoryManageDialog.tsx @@ -0,0 +1,559 @@ +import React, { useState, useEffect } from 'react'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Textarea } from "@/components/ui/textarea"; +import { Switch } from "@/components/ui/switch"; +import { useToast } from "@/components/ui/use-toast"; +import { useForm } from "react-hook-form"; +import { DataTablePagination } from '@/components/ui/pagination'; +import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page'; +import { + Plus, + Edit, + Trash2, + Search, + Loader2, + FolderKanban, + CheckCircle2, + XCircle, +} from "lucide-react"; +import DynamicIcon from '@/components/DynamicIcon'; +import LucideIconSelect from '@/components/LucideIconSelect'; +import { + getCategories, + createCategory, + updateCategory, + deleteCategory +} from '../../Category/service'; +import type { + FormCategoryResponse, + FormCategoryRequest, + FormCategoryQuery +} from '../../Category/types'; +import type { Page } from '@/types/base'; + +interface CategoryManageDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSuccess?: () => void; +} + +const CategoryManageDialog: React.FC = ({ + open, + onOpenChange, + onSuccess +}) => { + const { toast } = useToast(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState | null>(null); + const [searchText, setSearchText] = useState(''); + const [editMode, setEditMode] = useState(false); + const [editRecord, setEditRecord] = useState(null); + const [iconSelectOpen, setIconSelectOpen] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [deleteRecord, setDeleteRecord] = useState(null); + + // 分页状态 + const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + + const form = useForm({ + defaultValues: { + name: '', + code: '', + icon: '', + sort: 0, + description: '', + enabled: true, + } + }); + + // 加载分类列表 + const loadCategories = async () => { + setLoading(true); + try { + const query: FormCategoryQuery = { + name: searchText || undefined, + pageNum, + pageSize, + }; + const result = await getCategories(query); + setData(result || null); + } catch (error) { + console.error('加载分类失败:', error); + toast({ + variant: "destructive", + title: "加载失败", + description: error instanceof Error ? error.message : '未知错误', + }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (open) { + loadCategories(); + } + }, [open, searchText, pageNum, pageSize]); + + // 搜索 + const handleSearch = () => { + setPageNum(0); // 搜索时重置到第一页 + }; + + // 新建 + const handleCreate = () => { + form.reset({ + name: '', + code: '', + icon: '', + sort: 0, + description: '', + enabled: true, + }); + setEditRecord(null); + setEditMode(true); + }; + + // 编辑 + const handleEdit = (record: FormCategoryResponse) => { + setEditRecord(record); + form.reset({ + name: record.name, + code: record.code, + icon: record.icon || '', + sort: record.sort, + description: record.description || '', + enabled: record.enabled, + }); + setEditMode(true); + }; + + // 打开删除确认对话框 + const handleDeleteClick = (record: FormCategoryResponse) => { + setDeleteRecord(record); + setDeleteDialogOpen(true); + }; + + // 确认删除 + const confirmDelete = async () => { + if (!deleteRecord) return; + + try { + await deleteCategory(deleteRecord.id); + toast({ + title: "删除成功", + description: `分类 "${deleteRecord.name}" 已删除`, + }); + loadCategories(); + onSuccess?.(); + setDeleteDialogOpen(false); + setDeleteRecord(null); + } catch (error) { + console.error('删除失败:', error); + toast({ + variant: "destructive", + title: "删除失败", + description: error instanceof Error ? error.message : '未知错误', + }); + } + }; + + // 保存 + const handleSave = async (values: FormCategoryRequest) => { + try { + if (editRecord) { + await updateCategory(editRecord.id, values); + toast({ + title: "更新成功", + description: `分类 "${values.name}" 已更新`, + }); + } else { + await createCategory(values); + toast({ + title: "创建成功", + description: `分类 "${values.name}" 已创建`, + }); + } + setEditMode(false); + loadCategories(); + onSuccess?.(); + } catch (error) { + console.error('保存失败:', error); + toast({ + variant: "destructive", + title: "保存失败", + description: error instanceof Error ? error.message : '未知错误', + }); + } + }; + + // 取消编辑 + const handleCancel = () => { + setEditMode(false); + setEditRecord(null); + form.reset(); + }; + + return ( + <> + + + + + + 分类管理 + + + + {!editMode ? ( +
+ {/* 搜索栏 */} +
+
+ + setSearchText(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + className="pl-10" + /> +
+ +
+ + {/* 表格 */} +
+ + + + 分类名称 + 分类代码 + 图标 + 排序 + 状态 + 描述 + 操作 + + + + {loading ? ( + + +
+ + 加载中... +
+
+
+ ) : data?.content && data.content.length > 0 ? ( + data.content.map((record) => ( + + + {record.name} + + + + {record.code} + + + + {record.icon ? ( +
+ +
+ ) : ( + - + )} +
+ {record.sort} + + {record.enabled ? ( + + ) : ( + + )} + + + {record.description || '-'} + + +
+ + +
+
+
+ )) + ) : ( + + +
+ +

暂无分类

+
+
+
+ )} +
+
+
+ + {/* 分页 */} + {data && data.totalElements > 0 && ( +
+ setPageNum(page - 1)} + /> +
+ )} +
+ ) : ( +
+ +
+ ( + + 分类名称 + + + + + + )} + /> + + ( + + 分类代码 + + + + + + )} + /> + + ( + + 图标 + +
+ setIconSelectOpen(true)} + className="cursor-pointer" + /> + {field.value && ( +
+ +
+ )} +
+
+ + +
+ )} + /> + + ( + + 排序 + + field.onChange(Number(e.target.value))} + /> + + + + )} + /> +
+ + ( + + 描述 + +