diff --git a/frontend/src/pages/System/Department/components/DeleteDialog.tsx b/frontend/src/pages/System/Department/components/DeleteDialog.tsx new file mode 100644 index 00000000..299847ee --- /dev/null +++ b/frontend/src/pages/System/Department/components/DeleteDialog.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogDescription, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { AlertCircle } from 'lucide-react'; +import type { DepartmentResponse } from '../types'; + +interface DeleteDialogProps { + open: boolean; + record: DepartmentResponse | null; + onOpenChange: (open: boolean) => void; + onConfirm: () => Promise; +} + +/** + * 部门删除确认对话框 + */ +const DeleteDialog: React.FC = ({ + open, + record, + onOpenChange, + onConfirm, +}) => { + if (!record) return null; + + const hasChildren = record.children && record.children.length > 0; + + return ( + + + + + 确认删除部门? + + + 您确定要删除部门 "{record.name}" 吗? + {hasChildren && ( + + 该部门下有子部门,请先删除子部门! + + )} + + +
+

+ 部门编码: {record.code} +

+ {record.description && ( +

+ 描述: {record.description} +

+ )} + {record.leaderName && ( +

+ 负责人: {record.leaderName} +

+ )} +
+ 状态: + + {record.enabled ? '启用' : '禁用'} + +
+ {hasChildren && ( +
+

+ 包含 {record.children!.length} 个子部门 +

+
+ )} +
+ + + + +
+
+ ); +}; + +export default DeleteDialog; + diff --git a/frontend/src/pages/System/Department/components/EditDialog.tsx b/frontend/src/pages/System/Department/components/EditDialog.tsx new file mode 100644 index 00000000..33c6bdef --- /dev/null +++ b/frontend/src/pages/System/Department/components/EditDialog.tsx @@ -0,0 +1,304 @@ +import React, { useEffect, useState } from 'react'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Switch } from '@/components/ui/switch'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { useToast } from '@/components/ui/use-toast'; +import request from '@/utils/request'; +import type { DepartmentResponse, DepartmentRequest } from '../types'; +import type { UserResponse } from '../../User/types'; + +interface EditDialogProps { + open: boolean; + record?: DepartmentResponse; + departmentTree: DepartmentResponse[]; + users: UserResponse[]; + onOpenChange: (open: boolean) => void; + onSuccess: () => void; +} + +/** + * 部门编辑对话框 + */ +const EditDialog: React.FC = ({ + open, + record, + departmentTree, + users, + onOpenChange, + onSuccess, +}) => { + const { toast } = useToast(); + const [formData, setFormData] = useState>({ + enabled: true, + sort: 1, + }); + + useEffect(() => { + if (open) { + if (record) { + setFormData({ + code: record.code, + name: record.name, + description: record.description, + parentId: record.parentId || undefined, + sort: record.sort, + enabled: record.enabled, + leaderId: record.leaderId, + leaderName: record.leaderName, + }); + } else { + // 新增时,计算下一个排序值 + const allDepts = flattenDepartments(departmentTree); + const maxSort = Math.max(0, ...allDepts.map(d => d.sort)); + setFormData({ enabled: true, sort: maxSort + 1 }); + } + } + }, [open, record, departmentTree]); + + // 扁平化部门列表 + const flattenDepartments = (depts: DepartmentResponse[], level: number = 0): Array => { + const result: Array = []; + depts.forEach(dept => { + // 过滤掉自己和子部门,避免循环引用 + if (!record || (dept.id !== record.id && !isChildOf(dept.id, record, depts))) { + result.push({ ...dept, level }); + if (dept.children) { + result.push(...flattenDepartments(dept.children, level + 1)); + } + } + }); + return result; + }; + + // 检查是否是子部门 + const isChildOf = (deptId: number, parent: DepartmentResponse, allDepts: DepartmentResponse[]): boolean => { + const findDept = (depts: DepartmentResponse[], id: number): DepartmentResponse | null => { + for (const dept of depts) { + if (dept.id === id) return dept; + if (dept.children) { + const found = findDept(dept.children, id); + if (found) return found; + } + } + return null; + }; + + const dept = findDept(allDepts, deptId); + if (!dept || !dept.children) return false; + + const checkChildren = (children: DepartmentResponse[]): boolean => { + return children.some(child => { + if (child.id === parent.id) return true; + if (child.children) return checkChildren(child.children); + return false; + }); + }; + + return checkChildren(dept.children); + }; + + const handleSubmit = async () => { + try { + // 验证 + if (!formData.code?.trim()) { + toast({ + title: '提示', + description: '请输入部门编码', + variant: 'destructive' + }); + return; + } + + // 验证部门编码格式 + if (!/^[A-Z_]+$/.test(formData.code)) { + toast({ + title: '提示', + description: '部门编码只能包含大写字母和下划线', + variant: 'destructive' + }); + return; + } + + if (!formData.name?.trim()) { + toast({ + title: '提示', + description: '请输入部门名称', + variant: 'destructive' + }); + return; + } + + const data = { + ...formData as DepartmentRequest, + parentId: formData.parentId || 0 + }; + + if (record) { + await request.put(`/api/v1/department/${record.id}`, { + ...data, + version: record.version + }); + toast({ + title: '更新成功', + description: `部门 "${formData.name}" 已更新`, + }); + } else { + await request.post('/api/v1/department', data); + toast({ + title: '创建成功', + description: `部门 "${formData.name}" 已创建`, + }); + } + + onSuccess(); + onOpenChange(false); + } catch (error) { + console.error('保存失败:', error); + toast({ + title: '保存失败', + description: error instanceof Error ? error.message : '未知错误', + variant: 'destructive' + }); + } + }; + + const flatDepartments = flattenDepartments(departmentTree); + const hasChildren = record?.children && record.children.length > 0; + + return ( + + + + {record ? '编辑部门' : '新增部门'} + +
+
+ + + {hasChildren && ( +

该部门有子部门,不能修改上级部门

+ )} +
+ +
+ + setFormData({ ...formData, code: e.target.value.toUpperCase() })} + placeholder="请输入部门编码(大写字母和下划线)" + /> +

只能包含大写字母和下划线

+
+ +
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="请输入部门名称" + /> +
+ +
+ +