增加审批组件
This commit is contained in:
parent
ab89ebe994
commit
ab4ea6e367
@ -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<CategoryManageDialogProps> = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
onSuccess
|
||||||
|
}) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [data, setData] = useState<Page<FormCategoryResponse> | null>(null);
|
||||||
|
const [searchText, setSearchText] = useState('');
|
||||||
|
const [editMode, setEditMode] = useState(false);
|
||||||
|
const [editRecord, setEditRecord] = useState<FormCategoryResponse | null>(null);
|
||||||
|
const [iconSelectOpen, setIconSelectOpen] = useState(false);
|
||||||
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
const [deleteRecord, setDeleteRecord] = useState<FormCategoryResponse | null>(null);
|
||||||
|
|
||||||
|
// 分页状态
|
||||||
|
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
|
||||||
|
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
|
const form = useForm<FormCategoryRequest>({
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<FolderKanban className="h-5 w-5" />
|
||||||
|
分类管理
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
{!editMode ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="搜索分类名称..."
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
|
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||||
|
className="pl-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button onClick={handleCreate}>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
新建分类
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 表格 */}
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="w-[160px]">分类名称</TableHead>
|
||||||
|
<TableHead className="w-[140px]">分类代码</TableHead>
|
||||||
|
<TableHead className="w-[60px]">图标</TableHead>
|
||||||
|
<TableHead className="w-[80px]">排序</TableHead>
|
||||||
|
<TableHead className="w-[80px]">状态</TableHead>
|
||||||
|
<TableHead className="min-w-[150px]">描述</TableHead>
|
||||||
|
<TableHead className="w-[100px] sticky right-0 bg-background shadow-[-2px_0_4px_rgba(0,0,0,0.05)]">操作</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{loading ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={7} className="text-center py-8">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||||
|
<span className="text-sm text-muted-foreground">加载中...</span>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : data?.content && data.content.length > 0 ? (
|
||||||
|
data.content.map((record) => (
|
||||||
|
<TableRow key={record.id}>
|
||||||
|
<TableCell className="font-medium">
|
||||||
|
{record.name}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<code className="text-xs bg-muted px-2 py-0.5 rounded whitespace-nowrap">
|
||||||
|
{record.code}
|
||||||
|
</code>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{record.icon ? (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<DynamicIcon name={record.icon} className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-muted-foreground text-sm">-</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center">{record.sort}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{record.enabled ? (
|
||||||
|
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||||
|
) : (
|
||||||
|
<XCircle className="h-4 w-4 text-gray-400" />
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="max-w-[200px] truncate" title={record.description}>
|
||||||
|
{record.description || '-'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="sticky right-0 bg-background shadow-[-2px_0_4px_rgba(0,0,0,0.05)]">
|
||||||
|
<div className="flex items-center justify-end gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
onClick={() => handleEdit(record)}
|
||||||
|
>
|
||||||
|
<Edit className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
onClick={() => handleDeleteClick(record)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3.5 w-3.5 text-destructive" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={7} className="text-center py-12 text-muted-foreground">
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<FolderKanban className="h-12 w-12 opacity-20" />
|
||||||
|
<p className="text-sm">暂无分类</p>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 分页 */}
|
||||||
|
{data && data.totalElements > 0 && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<DataTablePagination
|
||||||
|
pageIndex={pageNum + 1}
|
||||||
|
pageSize={pageSize}
|
||||||
|
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
||||||
|
onPageChange={(page) => setPageNum(page - 1)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleSave)} className="space-y-6">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
rules={{ required: '请输入分类名称' }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>分类名称</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="输入分类名称" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="code"
|
||||||
|
rules={{ required: '请输入分类代码' }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>分类代码</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="输入分类代码" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="icon"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>图标</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
placeholder="点击选择图标"
|
||||||
|
value={field.value}
|
||||||
|
readOnly
|
||||||
|
onClick={() => setIconSelectOpen(true)}
|
||||||
|
className="cursor-pointer"
|
||||||
|
/>
|
||||||
|
{field.value && (
|
||||||
|
<div className="flex items-center justify-center w-10 h-10 border rounded-md">
|
||||||
|
<DynamicIcon name={field.value} className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
<LucideIconSelect
|
||||||
|
open={iconSelectOpen}
|
||||||
|
onOpenChange={setIconSelectOpen}
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="sort"
|
||||||
|
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={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>描述</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="输入分类描述"
|
||||||
|
className="resize-none"
|
||||||
|
rows={3}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="enabled"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex items-center justify-between rounded-lg border p-4">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel className="text-base">启用状态</FormLabel>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
控制此分类是否可用
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end gap-2 pt-4 border-t">
|
||||||
|
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* 删除确认对话框 */}
|
||||||
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>确认删除分类</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription asChild>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p>您确定要删除以下分类吗?此操作无法撤销。</p>
|
||||||
|
{deleteRecord && (
|
||||||
|
<div className="rounded-md border p-3 space-y-2 bg-muted/50">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium text-muted-foreground">分类名称:</span>
|
||||||
|
<span className="font-medium">{deleteRecord.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium text-muted-foreground">分类代码:</span>
|
||||||
|
<code className="text-xs bg-background px-2 py-1 rounded">
|
||||||
|
{deleteRecord.code}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
{deleteRecord.icon && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium text-muted-foreground">图标:</span>
|
||||||
|
<DynamicIcon name={deleteRecord.icon} className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{deleteRecord.description && (
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<span className="text-sm font-medium text-muted-foreground">描述:</span>
|
||||||
|
<span className="text-sm">{deleteRecord.description}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={confirmDelete}
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
>
|
||||||
|
确认删除
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CategoryManageDialog;
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ import { FormRenderer } from '@/components/FormDesigner';
|
|||||||
import {
|
import {
|
||||||
Loader2, Plus, Search, Eye, Edit, FileText, Ban, Trash2,
|
Loader2, Plus, Search, Eye, Edit, FileText, Ban, Trash2,
|
||||||
Database, MoreHorizontal, CheckCircle2, XCircle, Clock, AlertCircle,
|
Database, MoreHorizontal, CheckCircle2, XCircle, Clock, AlertCircle,
|
||||||
Folder, Activity, Settings
|
Folder, Activity, Settings, FolderKanban
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -30,6 +30,7 @@ import type { Page } from '@/types/base';
|
|||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
||||||
import CreateModal from './components/CreateModal';
|
import CreateModal from './components/CreateModal';
|
||||||
import EditBasicInfoModal from './components/EditBasicInfoModal';
|
import EditBasicInfoModal from './components/EditBasicInfoModal';
|
||||||
|
import CategoryManageDialog from './components/CategoryManageDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单定义列表页
|
* 表单定义列表页
|
||||||
@ -47,6 +48,9 @@ const FormDefinitionList: React.FC = () => {
|
|||||||
status: undefined as FormDefinitionStatus | undefined,
|
status: undefined as FormDefinitionStatus | undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 分类管理弹窗
|
||||||
|
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
||||||
|
|
||||||
// 创建表单弹窗
|
// 创建表单弹窗
|
||||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||||
|
|
||||||
@ -297,10 +301,16 @@ const FormDefinitionList: React.FC = () => {
|
|||||||
创建和管理表单定义,支持版本控制和发布管理
|
创建和管理表单定义,支持版本控制和发布管理
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={handleCreate} size="default">
|
<div className="flex items-center gap-2">
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Button variant="outline" onClick={() => setCategoryDialogOpen(true)}>
|
||||||
创建表单
|
<FolderKanban className="h-4 w-4 mr-2" />
|
||||||
</Button>
|
分类管理
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCreate} size="default">
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
创建表单
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<Separator />
|
<Separator />
|
||||||
@ -606,6 +616,13 @@ const FormDefinitionList: React.FC = () => {
|
|||||||
loadData();
|
loadData();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 分类管理弹窗 */}
|
||||||
|
<CategoryManageDialog
|
||||||
|
open={categoryDialogOpen}
|
||||||
|
onOpenChange={setCategoryDialogOpen}
|
||||||
|
onSuccess={loadCategories}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -38,6 +38,8 @@ import { Textarea } from "@/components/ui/textarea";
|
|||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
|
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Edit,
|
Edit,
|
||||||
@ -87,6 +89,10 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [deleteRecord, setDeleteRecord] = useState<WorkflowCategoryResponse | null>(null);
|
const [deleteRecord, setDeleteRecord] = useState<WorkflowCategoryResponse | null>(null);
|
||||||
|
|
||||||
|
// 分页状态
|
||||||
|
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
|
||||||
|
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
const form = useForm<WorkflowCategoryRequest>({
|
const form = useForm<WorkflowCategoryRequest>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: '',
|
name: '',
|
||||||
@ -104,8 +110,8 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const query: WorkflowCategoryQuery = {
|
const query: WorkflowCategoryQuery = {
|
||||||
pageNum: 0,
|
pageNum,
|
||||||
pageSize: 100,
|
pageSize,
|
||||||
};
|
};
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
query.name = searchText;
|
query.name = searchText;
|
||||||
@ -128,7 +134,14 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
if (open) {
|
if (open) {
|
||||||
loadCategories();
|
loadCategories();
|
||||||
}
|
}
|
||||||
}, [open, searchText]);
|
}, [open, searchText, pageNum, pageSize]);
|
||||||
|
|
||||||
|
// 搜索时重置页码
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchText !== '') {
|
||||||
|
setPageNum(0);
|
||||||
|
}
|
||||||
|
}, [searchText]);
|
||||||
|
|
||||||
// 触发方式选项
|
// 触发方式选项
|
||||||
const triggerOptions = [
|
const triggerOptions = [
|
||||||
@ -382,6 +395,18 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 分页 */}
|
||||||
|
{data && data.totalElements > 0 && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<DataTablePagination
|
||||||
|
pageIndex={pageNum + 1}
|
||||||
|
pageSize={pageSize}
|
||||||
|
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
|
||||||
|
onPageChange={(page) => setPageNum(page - 1)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
/* 编辑表单 */
|
/* 编辑表单 */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user