生产管理系统 部门树查询、新增一级部门
This commit is contained in:
@@ -7,13 +7,20 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { Dialog, DialogContent, DialogDescription, 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Department } from '../types';
|
||||
import { Department, CreateDepartmentForm } from '../types';
|
||||
import {
|
||||
createDepartment,
|
||||
transformCreateDepartmentData,
|
||||
debounce,
|
||||
generateRandomOrderIndex,
|
||||
} from './departmentCreateApi';
|
||||
|
||||
interface DepartmentFormDialogProps {
|
||||
open: boolean;
|
||||
@@ -21,6 +28,7 @@ interface DepartmentFormDialogProps {
|
||||
editingDepartment: Department | null;
|
||||
parentDepartment: Department | null;
|
||||
onSave: (formData: Partial<Department>) => void;
|
||||
refreshDepartmentTree?: () => void;
|
||||
}
|
||||
|
||||
export function DepartmentFormDialog({
|
||||
@@ -28,13 +36,20 @@ export function DepartmentFormDialog({
|
||||
onOpenChange,
|
||||
editingDepartment,
|
||||
parentDepartment,
|
||||
onSave
|
||||
onSave,
|
||||
refreshDepartmentTree,
|
||||
}: DepartmentFormDialogProps) {
|
||||
const [formData, setFormData] = useState<Partial<Department>>({
|
||||
const [formData, setFormData] = useState<CreateDepartmentForm>({
|
||||
name: '',
|
||||
code: '',
|
||||
manager: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
description: '',
|
||||
status: 'active',
|
||||
sort: 0,
|
||||
sort: generateRandomOrderIndex(),
|
||||
parentId: parentDepartment?.id || '',
|
||||
level: parentDepartment ? (parentDepartment.level || 1) + 1 : 1,
|
||||
parentId: parentDepartment?.id,
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -43,43 +58,126 @@ export function DepartmentFormDialog({
|
||||
useState(() => {
|
||||
if (editingDepartment) {
|
||||
setFormData({
|
||||
...editingDepartment,
|
||||
children: undefined, // 排除children字段
|
||||
name: editingDepartment.name,
|
||||
code: editingDepartment.code,
|
||||
manager: editingDepartment.manager || '',
|
||||
phone: editingDepartment.phone || '',
|
||||
email: editingDepartment.email || '',
|
||||
description: editingDepartment.description || '',
|
||||
status: editingDepartment.status,
|
||||
sort: editingDepartment.sort,
|
||||
parentId: editingDepartment.parentId || '',
|
||||
level: editingDepartment.level,
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
parentId: parentDepartment?.id,
|
||||
level: parentDepartment ? (parentDepartment.level || 1) + 1 : 1,
|
||||
name: '',
|
||||
code: '',
|
||||
manager: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
description: '',
|
||||
status: 'active',
|
||||
sort: 0,
|
||||
sort: generateRandomOrderIndex(),
|
||||
parentId: parentDepartment?.id || '',
|
||||
level: parentDepartment ? (parentDepartment.level || 1) + 1 : 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const handleInputChange = (field: keyof Department, value: string | number) => {
|
||||
const handleInputChange = (field: keyof CreateDepartmentForm, value: string | number) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
// 表单验证
|
||||
const validateForm = (): boolean => {
|
||||
if (!formData.name?.trim()) {
|
||||
toast.error('请输入部门名称');
|
||||
return false;
|
||||
}
|
||||
if (!formData.code?.trim()) {
|
||||
toast.error('请输入部门编码');
|
||||
return false;
|
||||
}
|
||||
if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
||||
toast.error('邮箱格式不正确');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 创建部门的API调用函数(带防抖)
|
||||
const createDepartmentWithDebounce = useCallback(
|
||||
debounce(async (form: CreateDepartmentForm) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 调用API创建部门
|
||||
await createDepartment(form);
|
||||
|
||||
// 成功处理
|
||||
toast.success('创建一级部门成功');
|
||||
|
||||
// 关闭对话框
|
||||
onOpenChange(false);
|
||||
|
||||
// 刷新部门树
|
||||
if (refreshDepartmentTree) {
|
||||
refreshDepartmentTree();
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
setFormData({
|
||||
name: '',
|
||||
code: '',
|
||||
manager: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
description: '',
|
||||
status: 'active',
|
||||
sort: generateRandomOrderIndex(),
|
||||
parentId: parentDepartment?.id || '',
|
||||
level: parentDepartment ? (parentDepartment.level || 1) + 1 : 1,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// 失败处理 - 不关闭页面
|
||||
console.error('创建部门失败:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : '创建一级部门失败';
|
||||
toast.error(errorMessage);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 1000), // 1秒防抖
|
||||
[onOpenChange, refreshDepartmentTree, parentDepartment]
|
||||
);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formData.name || !formData.code) {
|
||||
// 表单验证
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是编辑模式,使用原有的onSave逻辑
|
||||
if (editingDepartment) {
|
||||
setLoading(true);
|
||||
try {
|
||||
await onSave(formData);
|
||||
// 重置表单
|
||||
setFormData({
|
||||
status: 'active',
|
||||
sort: 0,
|
||||
level: parentDepartment ? (parentDepartment.level || 1) + 1 : 1,
|
||||
parentId: parentDepartment?.id,
|
||||
});
|
||||
toast.success('部门更新成功');
|
||||
onOpenChange(false);
|
||||
if (refreshDepartmentTree) {
|
||||
refreshDepartmentTree();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save department:', error);
|
||||
console.error('Failed to update department:', error);
|
||||
toast.error('部门更新失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} else {
|
||||
// 创建模式,使用API调用(带防抖)
|
||||
createDepartmentWithDebounce(formData);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -87,10 +185,16 @@ export function DepartmentFormDialog({
|
||||
onOpenChange(false);
|
||||
// 重置表单
|
||||
setFormData({
|
||||
name: '',
|
||||
code: '',
|
||||
manager: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
description: '',
|
||||
status: 'active',
|
||||
sort: 0,
|
||||
sort: generateRandomOrderIndex(),
|
||||
parentId: parentDepartment?.id || '',
|
||||
level: parentDepartment ? (parentDepartment.level || 1) + 1 : 1,
|
||||
parentId: parentDepartment?.id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Building2, Plus } from 'lucide-react';
|
||||
import { Building2, Plus, RefreshCw } from 'lucide-react';
|
||||
|
||||
interface DepartmentHeaderProps {
|
||||
onAdd: () => void;
|
||||
onRefresh?: () => void;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export function DepartmentHeader({ onAdd }: DepartmentHeaderProps) {
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* filekorolheader: 部门管理API接口 - 部门树形数据查询接口服务
|
||||
* 功能:API请求封装、数据转换、错误处理、树形结构数据处理
|
||||
* 路径:/central-config/user/department/components/departmentApi
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用SDK API调用,TypeScript类型安全
|
||||
*/
|
||||
|
||||
import { getAuthToken } from "@/utils/token";
|
||||
import {
|
||||
getDepartmentTreeApiV1DepartmentsTreeGet,
|
||||
getUsersApiV1UsersGet
|
||||
} from "@/lib/api/sdk.gen";
|
||||
|
||||
// API返回的部门数据类型
|
||||
export interface DepartmentApiData {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
parent_id: string | null;
|
||||
manager_name: string | null;
|
||||
manager_phone: string | null;
|
||||
manager_email: string | null;
|
||||
description: string | null;
|
||||
status: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
tenant_id: string;
|
||||
order_index: number;
|
||||
created_by: string | null;
|
||||
updated_by: string | null;
|
||||
children?: DepartmentApiData[];
|
||||
}
|
||||
|
||||
// API返回的用户数据类型(用于部门成员)
|
||||
export interface UserApiData {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
full_name: string | null;
|
||||
display_name: string | null;
|
||||
phone: string | null;
|
||||
is_active: boolean;
|
||||
department_id: string | null;
|
||||
department_name: string | null;
|
||||
created_at: string;
|
||||
last_login_at: string | null;
|
||||
}
|
||||
|
||||
// API响应接口
|
||||
export interface DepartmentTreeApiResponse {
|
||||
data: DepartmentApiData[];
|
||||
total: number;
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// 页面使用的部门数据类型(转换后的)
|
||||
export interface Department {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
parentId: string | null;
|
||||
managerId: string | null;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
tenantId: string;
|
||||
sortOrder: number;
|
||||
memberCount?: number;
|
||||
children: Department[];
|
||||
expanded: boolean;
|
||||
level: number;
|
||||
// 兼容现有页面的字段
|
||||
manager?: string;
|
||||
employeeCount?: number;
|
||||
description?: string;
|
||||
status?: 'active' | 'inactive';
|
||||
}
|
||||
|
||||
// 页面使用的用户数据类型(转换后的)
|
||||
export interface DepartmentUser {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
fullName: string | null;
|
||||
displayName: string | null;
|
||||
phone: string | null;
|
||||
isActive: boolean;
|
||||
departmentId: string | null;
|
||||
departmentName: string | null;
|
||||
createdAt: string;
|
||||
lastLoginAt: string | null;
|
||||
}
|
||||
|
||||
// 查询参数接口
|
||||
export interface DepartmentQueryParams {
|
||||
include_inactive?: boolean;
|
||||
include_members?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门树形结构数据
|
||||
*/
|
||||
export async function fetchDepartmentTree(params: DepartmentQueryParams = {}): Promise<DepartmentTreeApiResponse> {
|
||||
try {
|
||||
// 构建查询参数对象
|
||||
const queryParams: any = {};
|
||||
|
||||
if (params.include_inactive !== undefined) queryParams.include_inactive = params.include_inactive;
|
||||
if (params.include_members !== undefined) queryParams.include_members = params.include_members;
|
||||
|
||||
// 获取认证token
|
||||
const token = getAuthToken();
|
||||
console.log('部门管理API调用参数:', queryParams);
|
||||
|
||||
// 使用SDK API调用部门树形结构查询接口
|
||||
const response = await getDepartmentTreeApiV1DepartmentsTreeGet({
|
||||
query: {
|
||||
...queryParams,
|
||||
// 添加时间戳防止缓存
|
||||
_t: Date.now(),
|
||||
},
|
||||
headers: token ? {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
} : undefined,
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(`API error: ${response.error.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const data = response.data as any;
|
||||
console.log('部门管理API响应:', data);
|
||||
|
||||
// 根据实际API响应格式处理数据
|
||||
if (Array.isArray(data)) {
|
||||
// 如果API直接返回数组
|
||||
return {
|
||||
data: data,
|
||||
total: data.length,
|
||||
success: true,
|
||||
};
|
||||
} else if (data && typeof data === 'object') {
|
||||
// 如果API返回对象格式
|
||||
return {
|
||||
data: data.data || data.items || [],
|
||||
total: data.total || data.count || 0,
|
||||
success: data.success !== false,
|
||||
message: data.message,
|
||||
};
|
||||
} else {
|
||||
// 其他情况,返回空结果
|
||||
return {
|
||||
data: [],
|
||||
total: 0,
|
||||
success: false,
|
||||
message: 'Invalid response format',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch department tree:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门成员列表
|
||||
*/
|
||||
export async function fetchDepartmentUsers(departmentId: string): Promise<DepartmentUser[]> {
|
||||
try {
|
||||
// 获取认证token
|
||||
const token = getAuthToken();
|
||||
|
||||
// 使用SDK API调用用户查询接口,按部门筛选
|
||||
const response = await getUsersApiV1UsersGet({
|
||||
query: {
|
||||
department_id: departmentId,
|
||||
_t: Date.now(),
|
||||
},
|
||||
headers: token ? {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
} : undefined,
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(`API error: ${response.error.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const data = response.data as any;
|
||||
|
||||
let users: any[] = [];
|
||||
if (Array.isArray(data)) {
|
||||
users = data;
|
||||
} else if (data && typeof data === 'object' && data.data) {
|
||||
users = data.data;
|
||||
}
|
||||
|
||||
return users.map(transformUserData);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch department users:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将API数据转换为页面所需的部门数据格式
|
||||
*/
|
||||
export function transformDepartmentData(department: DepartmentApiData, level: number = 0): Department {
|
||||
return {
|
||||
id: department.id,
|
||||
name: department.name,
|
||||
code: department.code,
|
||||
parentId: department.parent_id,
|
||||
managerId: null, // API中没有manager_id字段
|
||||
description: department.description,
|
||||
isActive: department.status === 'active',
|
||||
createdAt: formatDate(department.created_at),
|
||||
updatedAt: formatDate(department.updated_at),
|
||||
tenantId: department.tenant_id,
|
||||
sortOrder: department.order_index,
|
||||
memberCount: 0, // API中没有member_count字段
|
||||
children: (department.children || []).map(child => transformDepartmentData(child, level + 1)),
|
||||
expanded: level === 0, // 默认展开顶级部门
|
||||
level,
|
||||
// 兼容现有页面的字段
|
||||
manager: department.manager_name || undefined,
|
||||
phone: department.manager_phone || undefined,
|
||||
email: department.manager_email || undefined,
|
||||
employeeCount: 0,
|
||||
status: department.status as 'active' | 'inactive',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将API数据转换为页面所需的用户数据格式
|
||||
*/
|
||||
export function transformUserData(user: UserApiData): DepartmentUser {
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
fullName: user.full_name,
|
||||
displayName: user.display_name || user.full_name || user.username,
|
||||
phone: user.phone,
|
||||
isActive: user.is_active,
|
||||
departmentId: user.department_id,
|
||||
departmentName: user.department_name,
|
||||
createdAt: formatDate(user.created_at),
|
||||
lastLoginAt: user.last_login_at ? formatDate(user.last_login_at) : null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量转换部门数据
|
||||
*/
|
||||
export function transformDepartmentList(departments: DepartmentApiData[]): Department[] {
|
||||
return departments.map(dept => transformDepartmentData(dept));
|
||||
}
|
||||
|
||||
/**
|
||||
* 扁平化部门树形结构(用于搜索和筛选)
|
||||
*/
|
||||
export function flattenDepartments(departments: Department[]): Department[] {
|
||||
const result: Department[] = [];
|
||||
|
||||
function flatten(depts: Department[]) {
|
||||
depts.forEach(dept => {
|
||||
result.push(dept);
|
||||
if (dept.children && dept.children.length > 0) {
|
||||
flatten(dept.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
flatten(departments);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在部门树中查找部门
|
||||
*/
|
||||
export function findDepartmentInTree(departments: Department[], departmentId: string): Department | null {
|
||||
for (const dept of departments) {
|
||||
if (dept.id === departmentId) {
|
||||
return dept;
|
||||
}
|
||||
if (dept.children && dept.children.length > 0) {
|
||||
const found = findDepartmentInTree(dept.children, departmentId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*/
|
||||
function formatDate(dateString: string): string {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).replace(/\//g, '-');
|
||||
} catch (error) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
// Department tree state interface for page components
|
||||
export interface DepartmentTreeState {
|
||||
departments: Department[];
|
||||
flattenedDepartments: Department[];
|
||||
selectedDepartment: Department | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
searchKeyword: string;
|
||||
expandedDepartments: Set<string>;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* filekorolheader: 部门管理API接口 - 部门数据CRUD操作接口服务
|
||||
* 功能:API请求封装、数据转换、错误处理、部门树形管理
|
||||
* 路径:/central-config/user/department/components/departmentApi
|
||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用SDK API调用,TypeScript类型安全
|
||||
*/
|
||||
|
||||
import { getAuthToken } from "@/utils/token";
|
||||
import {
|
||||
createDepartmentApiV1DepartmentsPost,
|
||||
} from "@/lib/api/sdk.gen";
|
||||
import {
|
||||
Department,
|
||||
CreateDepartmentForm,
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
* API请求创建部门的数据结构(对应Python字段)
|
||||
*/
|
||||
export interface CreateDepartmentApiRequest {
|
||||
code: string; // 部门编码
|
||||
name: string; // 部门名称
|
||||
description?: string; // 部门描述
|
||||
manager_name?: string; // 管理者名称
|
||||
manager_phone?: string; // 管理者电话
|
||||
manager_email?: string; // 管理者邮箱(必须符合邮箱规则)
|
||||
parent_id: string; // 父级部门ID,一级部门为空字符串
|
||||
order_index: number; // 排序索引,0-10000的整数
|
||||
status: string; // 状态,默认"active"表示有效
|
||||
}
|
||||
|
||||
/**
|
||||
* API响应数据结构
|
||||
*/
|
||||
export interface CreateDepartmentApiResponse {
|
||||
code: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
manager_name?: string;
|
||||
manager_phone?: string;
|
||||
manager_email?: string;
|
||||
parent_id: string;
|
||||
order_index: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将React表单数据(驼峰命名法)转换为API请求数据(Python蛇形命名法)
|
||||
*/
|
||||
export function transformCreateDepartmentData(formData: CreateDepartmentForm): CreateDepartmentApiRequest {
|
||||
return {
|
||||
code: formData.code,
|
||||
name: formData.name,
|
||||
description: formData.description || null,
|
||||
manager_name: formData.manager || undefined,
|
||||
manager_phone: formData.phone || undefined,
|
||||
manager_email: formData.email || undefined,
|
||||
parent_id: formData.parentId || "",
|
||||
order_index: formData.sort || 0,
|
||||
status: formData.status || "active",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮箱格式验证
|
||||
*/
|
||||
export function isValidEmail(email: string): boolean {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建部门API调用
|
||||
*/
|
||||
export async function createDepartment(formData: CreateDepartmentForm): Promise<CreateDepartmentApiResponse> {
|
||||
try {
|
||||
// 获取认证token
|
||||
const token = getAuthToken();
|
||||
console.log('创建部门API调用参数:', formData);
|
||||
|
||||
// 转换表单数据为API请求格式
|
||||
const apiRequestData = transformCreateDepartmentData(formData);
|
||||
|
||||
// 邮箱格式验证
|
||||
if (apiRequestData.manager_email && !isValidEmail(apiRequestData.manager_email)) {
|
||||
throw new Error('邮箱格式不正确');
|
||||
}
|
||||
|
||||
// 使用真正的SDK API调用
|
||||
const response = await createDepartmentApiV1DepartmentsPost({
|
||||
body: apiRequestData,
|
||||
headers: token ? {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
} : undefined,
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(`API error: ${response.error.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const data = response.data as CreateDepartmentApiResponse;
|
||||
console.log('创建部门API响应:', data);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Failed to create department:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机排序索引(0-10000)
|
||||
*/
|
||||
export function generateRandomOrderIndex(): number {
|
||||
return Math.floor(Math.random() * 10001);
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
delay: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
return (...args: Parameters<T>) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => func(...args), delay);
|
||||
};
|
||||
}
|
||||
@@ -16,6 +16,12 @@ import { DepartmentTree } from './components/DepartmentTree';
|
||||
import { DepartmentFormDialog } from './components/DepartmentFormDialog';
|
||||
import { DepartmentDeleteDialog } from './components/DepartmentDeleteDialog';
|
||||
import { DepartmentInstructions } from './components/DepartmentInstructions';
|
||||
import {
|
||||
fetchDepartmentTree,
|
||||
transformDepartmentList,
|
||||
flattenDepartments,
|
||||
type DepartmentTreeState
|
||||
} from './components/departmentApi';
|
||||
|
||||
// 部门管理状态管理
|
||||
interface DepartmentManagementState {
|
||||
@@ -116,151 +122,63 @@ export default function DepartmentManagementPage() {
|
||||
try {
|
||||
dispatch({ type: 'SET_LOADING', payload: true });
|
||||
|
||||
// 暂时使用mock数据,后续可以替换为API调用
|
||||
const mockDepartments: Department[] = [
|
||||
{
|
||||
id: 'dept-1',
|
||||
name: '技术部',
|
||||
code: 'TECH',
|
||||
level: 1,
|
||||
manager: '王技术',
|
||||
phone: '13800138001',
|
||||
email: 'tech@example.com',
|
||||
description: '负责技术研发和系统维护',
|
||||
sort: 1,
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00',
|
||||
updatedAt: '2024-01-01T00:00:00',
|
||||
children: [
|
||||
{
|
||||
id: 'dept-1-1',
|
||||
parentId: 'dept-1',
|
||||
name: '研发组',
|
||||
code: 'TECH-RD',
|
||||
level: 2,
|
||||
manager: '李研发',
|
||||
phone: '13800138011',
|
||||
description: '负责系统研发',
|
||||
sort: 1,
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00',
|
||||
updatedAt: '2024-01-01T00:00:00',
|
||||
},
|
||||
{
|
||||
id: 'dept-1-2',
|
||||
parentId: 'dept-1',
|
||||
name: '运维组',
|
||||
code: 'TECH-OPS',
|
||||
level: 2,
|
||||
manager: '张运维',
|
||||
phone: '13800138012',
|
||||
description: '负责系统运维',
|
||||
sort: 2,
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00',
|
||||
updatedAt: '2024-01-01T00:00:00',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'dept-2',
|
||||
name: '管理部',
|
||||
code: 'ADMIN',
|
||||
level: 1,
|
||||
manager: '赵管理',
|
||||
phone: '13800138002',
|
||||
email: 'admin@example.com',
|
||||
description: '负责行政管理',
|
||||
sort: 2,
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00',
|
||||
updatedAt: '2024-01-01T00:00:00',
|
||||
children: [
|
||||
{
|
||||
id: 'dept-2-1',
|
||||
parentId: 'dept-2',
|
||||
name: '人事组',
|
||||
code: 'ADMIN-HR',
|
||||
level: 2,
|
||||
manager: '孙人事',
|
||||
phone: '13800138021',
|
||||
description: '负责人力资源管理',
|
||||
sort: 1,
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00',
|
||||
updatedAt: '2024-01-01T00:00:00',
|
||||
},
|
||||
{
|
||||
id: 'dept-2-2',
|
||||
parentId: 'dept-2',
|
||||
name: '财务组',
|
||||
code: 'ADMIN-FIN',
|
||||
level: 2,
|
||||
manager: '周财务',
|
||||
phone: '13800138022',
|
||||
description: '负责财务管理',
|
||||
sort: 2,
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00',
|
||||
updatedAt: '2024-01-01T00:00:00',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'dept-3',
|
||||
name: '作业部',
|
||||
code: 'OPS',
|
||||
level: 1,
|
||||
manager: '吴作业',
|
||||
phone: '13800138003',
|
||||
email: 'ops@example.com',
|
||||
description: '负责农机作业管理',
|
||||
sort: 3,
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00',
|
||||
updatedAt: '2024-01-01T00:00:00',
|
||||
children: [
|
||||
{
|
||||
id: 'dept-3-1',
|
||||
parentId: 'dept-3',
|
||||
name: '第一作业组',
|
||||
code: 'OPS-T1',
|
||||
level: 2,
|
||||
manager: '郑组长',
|
||||
phone: '13800138031',
|
||||
description: '负责区域A作业',
|
||||
sort: 1,
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00',
|
||||
updatedAt: '2024-01-01T00:00:00',
|
||||
},
|
||||
{
|
||||
id: 'dept-3-2',
|
||||
parentId: 'dept-3',
|
||||
name: '第二作业组',
|
||||
code: 'OPS-T2',
|
||||
level: 2,
|
||||
manager: '钱组长',
|
||||
phone: '13800138032',
|
||||
description: '负责区域B作业',
|
||||
sort: 2,
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00',
|
||||
updatedAt: '2024-01-01T00:00:00',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// 使用API调用获取部门树形数据
|
||||
const response = await fetchDepartmentTree({
|
||||
include_inactive: false,
|
||||
include_members: true,
|
||||
});
|
||||
|
||||
dispatch({ type: 'SET_DEPARTMENTS', payload: mockDepartments });
|
||||
if (!response.success) {
|
||||
throw new Error(response.message || '获取部门数据失败');
|
||||
}
|
||||
|
||||
// 转换API数据为页面所需的格式
|
||||
const departments = transformDepartmentList(response.data);
|
||||
|
||||
// 转换为与现有页面兼容的数据格式
|
||||
const compatibleDepartments: Department[] = departments.map(dept => ({
|
||||
id: dept.id,
|
||||
name: dept.name,
|
||||
code: dept.code,
|
||||
level: dept.level + 1, // API的level从0开始,页面从1开始
|
||||
manager: dept.manager, // 从API的manager_name字段获取
|
||||
phone: dept.phone, // 从API的manager_phone字段获取
|
||||
email: dept.email, // 从API的manager_email字段获取
|
||||
description: dept.description,
|
||||
sort: dept.sortOrder,
|
||||
status: dept.status as 'active' | 'inactive',
|
||||
parentId: dept.parentId || undefined,
|
||||
createdAt: dept.createdAt,
|
||||
updatedAt: dept.updatedAt,
|
||||
children: dept.children.map(child => ({
|
||||
id: child.id,
|
||||
name: child.name,
|
||||
code: child.code,
|
||||
level: child.level + 1,
|
||||
manager: child.manager, // 从API的manager_name字段获取
|
||||
phone: child.phone, // 从API的manager_phone字段获取
|
||||
email: child.email, // 从API的manager_email字段获取
|
||||
description: child.description,
|
||||
sort: child.sortOrder,
|
||||
status: child.status as 'active' | 'inactive',
|
||||
parentId: child.parentId || undefined,
|
||||
createdAt: child.createdAt,
|
||||
updatedAt: child.updatedAt,
|
||||
})),
|
||||
}));
|
||||
|
||||
dispatch({ type: 'SET_DEPARTMENTS', payload: compatibleDepartments });
|
||||
// 默认展开所有一级部门
|
||||
dispatch({ type: 'SET_EXPANDED_IDS', payload: new Set(mockDepartments.map(d => d.id)) });
|
||||
dispatch({ type: 'SET_EXPANDED_IDS', payload: new Set(compatibleDepartments.map(d => d.id)) });
|
||||
|
||||
toast.success(`成功加载 ${compatibleDepartments.length} 个部门`);
|
||||
} catch (error) {
|
||||
console.error('Failed to load departments:', error);
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
payload: error instanceof Error ? error.message : '加载部门数据失败'
|
||||
});
|
||||
toast.error('加载部门数据失败');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -303,6 +221,11 @@ export default function DepartmentManagementPage() {
|
||||
dispatch({ type: 'SET_EXPANDED_IDS', payload: allIds });
|
||||
};
|
||||
|
||||
// 刷新数据
|
||||
const refreshData = () => {
|
||||
loadDepartments();
|
||||
};
|
||||
|
||||
// 收起全部
|
||||
const collapseAll = () => {
|
||||
dispatch({ type: 'SET_EXPANDED_IDS', payload: new Set() });
|
||||
@@ -533,7 +456,7 @@ export default function DepartmentManagementPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<DepartmentHeader onAdd={() => handleAdd()} />
|
||||
<DepartmentHeader onAdd={() => handleAdd()} onRefresh={refreshData} loading={state.loading} />
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<DepartmentStatsCards stats={stats} />
|
||||
@@ -565,6 +488,7 @@ export default function DepartmentManagementPage() {
|
||||
editingDepartment={state.editingDepartment}
|
||||
parentDepartment={state.parentDepartment}
|
||||
onSave={handleSave}
|
||||
refreshDepartmentTree={refreshData}
|
||||
/>
|
||||
|
||||
{/* 删除确认对话框 */}
|
||||
|
||||
@@ -42,7 +42,7 @@ export function RoleList({
|
||||
return status === 'active' ? (
|
||||
<Badge className="bg-green-100 text-green-700">启用</Badge>
|
||||
) : (
|
||||
<Badge className="bg-gray-100 text-gray-700">禁用</Badge>
|
||||
<Badge className="bg-gray-100 text-gray-700">停用</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user