生产管理系统 - 角色管理联调

This commit is contained in:
2025-11-06 10:17:44 +08:00
parent 279bbe8536
commit 9f1cf21042
8 changed files with 600 additions and 189 deletions

View File

@@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"; import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -16,7 +16,7 @@ import {
PaginationPrevious, PaginationPrevious,
PaginationEllipsis PaginationEllipsis
} from '@/components/ui/pagination'; } from '@/components/ui/pagination';
import { Eye, Edit, Lock, Trash2, UserX, UserCheck, CheckCircle, XCircle, Loader2 } from 'lucide-react'; import { Eye, Edit, Trash2, UserX, UserCheck, CheckCircle, XCircle, Loader2 } from 'lucide-react';
import { Employee, UserStatus } from '../types'; import { Employee, UserStatus } from '../types';
import { PaginationState } from './employeeApi'; import { PaginationState } from './employeeApi';
@@ -28,7 +28,6 @@ interface EmployeeListProps {
onPageSizeChange?: (size: number) => void; onPageSizeChange?: (size: number) => void;
onViewDetail: (employee: Employee) => void; onViewDetail: (employee: Employee) => void;
onEdit: (employee: Employee) => void; onEdit: (employee: Employee) => void;
onResetPassword: (employee: Employee) => void;
onToggleStatus: (employee: Employee) => void; onToggleStatus: (employee: Employee) => void;
onDelete: (id: string) => void; onDelete: (id: string) => void;
onAudit?: (employee: Employee, action: 'approve' | 'reject') => void; onAudit?: (employee: Employee, action: 'approve' | 'reject') => void;
@@ -43,7 +42,6 @@ export function EmployeeList({
onPageSizeChange, onPageSizeChange,
onViewDetail, onViewDetail,
onEdit, onEdit,
onResetPassword,
onToggleStatus, onToggleStatus,
onDelete, onDelete,
onAudit, onAudit,
@@ -174,20 +172,6 @@ export function EmployeeList({
<p></p> <p></p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
onClick={() => onResetPassword(employee)}
>
<Lock className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p></p>
</TooltipContent>
</Tooltip>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button

View File

@@ -34,6 +34,10 @@ import {
PaginationState, PaginationState,
EmployeesQueryParams EmployeesQueryParams
} from './components/employeeApi'; } from './components/employeeApi';
import {
fetchRoles,
transformRolesList
} from '../role/components/roleApi';
export default function EmployeeManagementPage() { export default function EmployeeManagementPage() {
const [employees, setEmployees] = useState<Employee[]>([]); const [employees, setEmployees] = useState<Employee[]>([]);
@@ -80,10 +84,22 @@ export default function EmployeeManagementPage() {
loadRoles(); loadRoles();
}, [pagination.page, pagination.size,filters.searchKeyword, filters.statusFilter]); }, [pagination.page, pagination.size,filters.searchKeyword, filters.statusFilter]);
const loadRoles = () => { const loadRoles = async () => {
const data = localStorage.getItem('smart_agriculture_roles'); try {
if (data) { // 调用角色API获取角色数据
setRoles(JSON.parse(data)); const response = await fetchRoles({
page: 1,
size: 100, // 获取所有角色
sort_order: 'desc'
});
// 转换数据格式
const transformedRoles = transformRolesList(response.data);
setRoles(transformedRoles);
} catch (error) {
console.error('Failed to load roles:', error);
// API失败时设置为空数组
setRoles([]);
} }
}; };
@@ -407,10 +423,6 @@ export default function EmployeeManagementPage() {
} }
}; };
const handleResetPassword = (employee: Employee) => {
if (!confirm(`确定要重置 ${employee.name} 的密码吗?`)) return;
toast.success('密码已重置为123456');
};
const handleViewDetail = (employee: Employee) => { const handleViewDetail = (employee: Employee) => {
setSelectedEmployee(employee); setSelectedEmployee(employee);
@@ -431,7 +443,6 @@ export default function EmployeeManagementPage() {
: emp : emp
); );
setEmployees(updated); setEmployees(updated);
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
toast.success('审核通过'); toast.success('审核通过');
} else { } else {
const reason = prompt('请输入驳回原因:'); const reason = prompt('请输入驳回原因:');
@@ -449,7 +460,6 @@ export default function EmployeeManagementPage() {
: emp : emp
); );
setEmployees(updated); setEmployees(updated);
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
toast.success('已驳回'); toast.success('已驳回');
} }
} }
@@ -480,7 +490,6 @@ export default function EmployeeManagementPage() {
onPageSizeChange={handlePageSizeChange} onPageSizeChange={handlePageSizeChange}
onViewDetail={handleViewDetail} onViewDetail={handleViewDetail}
onEdit={handleEdit} onEdit={handleEdit}
onResetPassword={handleResetPassword}
onToggleStatus={handleToggleStatus} onToggleStatus={handleToggleStatus}
onDelete={handleDelete} onDelete={handleDelete}
onAudit={handleAudit} onAudit={handleAudit}

View File

@@ -5,18 +5,21 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Di
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Loader2 } from 'lucide-react';
import { Role, RoleType } from '../types'; import { Role, RoleType } from '../types';
interface RoleDetailDialogProps { interface RoleDetailDialogProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
selectedRole: Role | null; selectedRole: Role | null;
detailLoading?: boolean;
} }
export function RoleDetailDialog({ export function RoleDetailDialog({
open, open,
onOpenChange, onOpenChange,
selectedRole selectedRole,
detailLoading = false
}: RoleDetailDialogProps) { }: RoleDetailDialogProps) {
const getRoleTypeBadge = (type: RoleType) => { const getRoleTypeBadge = (type: RoleType) => {
return type === 'system' ? ( return type === 'system' ? (
@@ -34,8 +37,6 @@ export function RoleDetailDialog({
); );
}; };
if (!selectedRole) return null;
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-2xl">
@@ -46,6 +47,12 @@ export function RoleDetailDialog({
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
{detailLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin mr-2" />
<span>...</span>
</div>
) : selectedRole ? (
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<Label></Label> <Label></Label>
@@ -76,13 +83,13 @@ export function RoleDetailDialog({
<div> <div>
<Label></Label> <Label></Label>
<div className="mt-1"> <div className="mt-1">
{selectedRole.menuIds.includes('*') ? '全部' : selectedRole.menuIds.length} {selectedRole.menuIds?.includes('*') ? '全部' : (selectedRole.menuIds?.length || 0)}
</div> </div>
</div> </div>
<div> <div>
<Label></Label> <Label></Label>
<div className="mt-1"> <div className="mt-1">
{selectedRole.permissionIds.includes('*') ? '全部' : selectedRole.permissionIds.length} {selectedRole.permissionIds?.includes('*') ? '全部' : (selectedRole.permissionIds?.length || 0)}
</div> </div>
</div> </div>
<div> <div>
@@ -98,6 +105,11 @@ export function RoleDetailDialog({
</div> </div>
</div> </div>
</div> </div>
) : (
<div className="flex items-center justify-center py-8">
<span></span>
</div>
)}
</div> </div>
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}> <Button variant="outline" onClick={() => onOpenChange(false)}>

View File

@@ -9,7 +9,7 @@ import { Textarea } from '@/components/ui/textarea';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { Card } from '@/components/ui/card'; import { Card } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
import { ChevronRight } from 'lucide-react'; import { ChevronRight, Loader2 } from 'lucide-react';
import { Role, RoleFormData, allSystemMenus } from '../types'; import { Role, RoleFormData, allSystemMenus } from '../types';
interface RoleFormDialogProps { interface RoleFormDialogProps {
@@ -19,6 +19,7 @@ interface RoleFormDialogProps {
formData: RoleFormData; formData: RoleFormData;
onFormDataChange: (data: RoleFormData) => void; onFormDataChange: (data: RoleFormData) => void;
onSave: () => void; onSave: () => void;
editFormLoading?: boolean;
} }
export function RoleFormDialog({ export function RoleFormDialog({
@@ -27,7 +28,8 @@ export function RoleFormDialog({
editingRole, editingRole,
formData, formData,
onFormDataChange, onFormDataChange,
onSave onSave,
editFormLoading = false
}: RoleFormDialogProps) { }: RoleFormDialogProps) {
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
@@ -38,6 +40,14 @@ export function RoleFormDialog({
{editingRole ? '编辑角色信息' : '添加新角色'} {editingRole ? '编辑角色信息' : '添加新角色'}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{/* Loading state for edit form */}
{editFormLoading && editingRole ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin mr-2" />
<span>...</span>
</div>
) : (
<ScrollArea className="max-h-[calc(90vh-180px)]"> <ScrollArea className="max-h-[calc(90vh-180px)]">
<div className="space-y-6 pr-4"> <div className="space-y-6 pr-4">
{/* 基本信息 */} {/* 基本信息 */}
@@ -223,6 +233,7 @@ export function RoleFormDialog({
</div> </div>
</div> </div>
</ScrollArea> </ScrollArea>
)}
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}> <Button variant="outline" onClick={() => onOpenChange(false)}>

View File

@@ -5,6 +5,7 @@ import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { DataPagination } from '@/components/ui/data-pagination';
import { Eye, Edit, Trash2, Shield } from 'lucide-react'; import { Eye, Edit, Trash2, Shield } from 'lucide-react';
import { Role, RoleType } from '../types'; import { Role, RoleType } from '../types';
import { PaginationState } from './roleApi'; import { PaginationState } from './roleApi';
@@ -125,60 +126,20 @@ export function RoleList({
</Table> </Table>
{/* 分页控制 */} {/* 分页控制 */}
{!loading && pagination && pagination.totalPages > 1 && ( {!loading && pagination && pagination.total > 0 && (
<div className="flex items-center justify-between px-2 py-4"> <DataPagination
<div className="flex items-center space-x-2 text-sm text-muted-foreground"> currentPage={pagination.page}
<span> {(pagination.page - 1) * pagination.size + 1} {Math.min(pagination.page * pagination.size, pagination.total)} {pagination.total} </span> totalPages={pagination.totalPages}
</div> pageSize={pagination.size}
<div className="flex items-center space-x-2"> totalItems={pagination.total}
<Button startIndex={(pagination.page - 1) * pagination.size + 1}
variant="outline" endIndex={Math.min(pagination.page * pagination.size, pagination.total)}
size="sm" onPageChange={(page) => onPageChange?.(page)}
onClick={() => onPageChange?.(pagination.page - 1)} onPageSizeChange={(size) => onPageSizeChange?.(size)}
disabled={!pagination.hasPrev} canPreviousPage={pagination.hasPrev}
> canNextPage={pagination.hasNext}
pageSizeOptions={[10, 20, 50, 100]}
</Button> />
<div className="flex items-center space-x-1">
{Array.from({ length: pagination.totalPages }, (_, i) => i + 1).map((pageNum) => (
<Button
key={pageNum}
variant={pageNum === pagination.page ? "default" : "outline"}
size="sm"
className="w-8 h-8 p-0"
onClick={() => onPageChange?.(pageNum)}
>
{pageNum}
</Button>
))}
</div>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange?.(pagination.page + 1)}
disabled={!pagination.hasNext}
>
</Button>
{onPageSizeChange && (
<>
<select
value={pagination.size}
onChange={(e) => onPageSizeChange(Number(e.target.value))}
className="ml-2 h-8 w-16 rounded-md border border-input bg-background text-sm"
>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
<span className="text-sm text-muted-foreground">/</span>
</>
)}
</div>
</div>
)} )}
</Card> </Card>
); );

View File

@@ -8,10 +8,16 @@
import { getAuthToken } from "@/utils/token"; import { getAuthToken } from "@/utils/token";
import { import {
getRolesApiV1UsersPermissionsRolesGet, getRolesApiV1UsersPermissionsRolesGet,
createRoleApiV1UsersPermissionsRolesPost,
getRoleApiV1UsersPermissionsRolesRoleIdGet,
deleteRoleApiV1UsersPermissionsRolesRoleIdDelete,
updateRoleApiV1UsersPermissionsRolesRoleIdPut,
} from "@/lib/api/sdk.gen"; } from "@/lib/api/sdk.gen";
import { import {
RoleApiData, RoleApiData,
Role, Role,
RolesApiResponse,
RolesQueryParams,
} from '../types'; } from '../types';
// 本地定义PaginationState以避免导入问题 // 本地定义PaginationState以避免导入问题
@@ -102,9 +108,9 @@ export async function fetchRoles(params: RolesQueryParams = {}): Promise<RolesAp
*/ */
export function transformRoleData(apiRole: RoleApiData): Role { export function transformRoleData(apiRole: RoleApiData): Role {
return { return {
id: apiRole.id, id: apiRole.id, // 使用API返回的真实ID
name: apiRole.name, name: apiRole.name,
code: apiRole.name.toLowerCase().replace(/\s+/g, '_'), // 生成code code: apiRole.id, // 使用API返回的ID作为code确保唯一性
description: apiRole.description, description: apiRole.description,
type: 'custom', // 默认为自定义角色 type: 'custom', // 默认为自定义角色
menuIds: [], menuIds: [],
@@ -123,6 +129,140 @@ export function transformRolesList(apiRoles: RoleApiData[]): Role[] {
return apiRoles.map(transformRoleData); return apiRoles.map(transformRoleData);
} }
/**
* 创建新角色
*/
export async function createRole(roleData: {
name: string;
description: string;
permission_ids: string[];
}) {
try {
// 获取认证token
const token = getAuthToken();
console.log('创建角色API调用参数:', roleData);
// 使用SDK API调用创建角色
const response = await createRoleApiV1UsersPermissionsRolesPost({
body: roleData,
headers: token ? {
'Authorization': `Bearer ${token}`,
} : undefined,
});
if (response.error) {
throw new Error(response.error.message || '创建角色失败');
}
console.log('创建角色API响应:', response.data);
return response.data;
} catch (error) {
console.error('Failed to create role:', error);
throw error;
}
}
/**
* 获取角色详情
*/
export async function getRoleDetail(roleId: string) {
try {
// 获取认证token
const token = getAuthToken();
console.log('获取角色详情API调用参数:', roleId);
console.log('即将调用的URL: /api/v1/users/permissions/roles/' + roleId);
// 使用SDK API调用获取角色详情
const response = await getRoleApiV1UsersPermissionsRolesRoleIdGet({
path: {
role_id: roleId,
},
headers: token ? {
'Authorization': `Bearer ${token}`,
} : undefined,
});
if (response.error) {
throw new Error(response.error.message || '获取角色详情失败');
}
console.log('获取角色详情API响应:', response.data);
return response.data;
} catch (error) {
console.error('Failed to get role detail:', error);
throw error;
}
}
/**
* 更新角色
*/
export async function updateRole(roleId: string, roleData: {
name: string;
description: string;
permission_ids: string[];
}) {
try {
// 获取认证token
const token = getAuthToken();
console.log('更新角色API调用参数:', roleId, roleData);
console.log('即将调用的URL: /api/v1/users/permissions/roles/' + roleId);
// 使用SDK API调用更新角色
const response = await updateRoleApiV1UsersPermissionsRolesRoleIdPut({
path: {
role_id: roleId,
},
body: roleData,
headers: token ? {
'Authorization': `Bearer ${token}`,
} : undefined,
});
if (response.error) {
throw new Error(response.error.message || '更新角色失败');
}
console.log('更新角色API响应:', response.data);
return response.data;
} catch (error) {
console.error('Failed to update role:', error);
throw error;
}
}
/**
* 删除角色
*/
export async function deleteRole(roleId: string) {
try {
// 获取认证token
const token = getAuthToken();
console.log('删除角色API调用参数:', roleId);
console.log('即将调用的URL: /api/v1/users/permissions/roles/' + roleId);
// 使用SDK API调用删除角色
const response = await deleteRoleApiV1UsersPermissionsRolesRoleIdDelete({
path: {
role_id: roleId,
},
headers: token ? {
'Authorization': `Bearer ${token}`,
} : undefined,
});
if (response.error) {
throw new Error(response.error.message || '删除角色失败');
}
console.log('删除角色API响应:', response.data);
return response.data;
} catch (error) {
console.error('Failed to delete role:', error);
throw error;
}
}
/** /**
* 格式化日期 * 格式化日期
*/ */

View File

@@ -9,6 +9,16 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { RoleManagementHeader } from './components/RoleManagementHeader'; import { RoleManagementHeader } from './components/RoleManagementHeader';
import { RoleManagementStatsCards } from './components/RoleManagementStatsCards'; import { RoleManagementStatsCards } from './components/RoleManagementStatsCards';
@@ -20,6 +30,10 @@ import { RoleManagementInstructions } from './components/RoleManagementInstructi
import { Role, RoleFormData, RoleFilters } from './types'; import { Role, RoleFormData, RoleFilters } from './types';
import { import {
fetchRoles, fetchRoles,
createRole,
getRoleDetail,
deleteRole,
updateRole,
transformRolesList, transformRolesList,
RolesApiResponse, RolesApiResponse,
RolesQueryParams, RolesQueryParams,
@@ -42,8 +56,12 @@ export default function RoleManagementPage() {
}); });
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [showDetailDialog, setShowDetailDialog] = useState(false); const [showDetailDialog, setShowDetailDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [editingRole, setEditingRole] = useState<Role | null>(null); const [editingRole, setEditingRole] = useState<Role | null>(null);
const [selectedRole, setSelectedRole] = useState<Role | null>(null); const [selectedRole, setSelectedRole] = useState<Role | null>(null);
const [deletingRoleId, setDeletingRoleId] = useState<string | null>(null);
const [detailLoading, setDetailLoading] = useState(false);
const [editFormLoading, setEditFormLoading] = useState(false);
const [formData, setFormData] = useState<RoleFormData>({ const [formData, setFormData] = useState<RoleFormData>({
type: 'custom', type: 'custom',
status: 'active', status: 'active',
@@ -82,11 +100,8 @@ export default function RoleManagementPage() {
console.error('Failed to load roles:', error); console.error('Failed to load roles:', error);
toast.error('加载角色数据失败'); toast.error('加载角色数据失败');
// 如果API失败使用localStorage中的数据作为fallback // API失败时设置为空数组
const data = localStorage.getItem('smart_agriculture_roles'); setRoles([]);
if (data) {
setRoles(JSON.parse(data));
}
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -101,10 +116,23 @@ export default function RoleManagementPage() {
// 分页处理函数 // 分页处理函数
const handlePageChange = (page: number) => { const handlePageChange = (page: number) => {
setPagination(prev => ({ ...prev, page })); // 确保页码不超出范围
const filteredCount = roles.filter(role => {
const matchKeyword = !filters.searchKeyword ||
role.name.includes(filters.searchKeyword) ||
role.code.includes(filters.searchKeyword) ||
(role.description && role.description.includes(filters.searchKeyword));
return matchKeyword;
}).length;
const maxPage = Math.ceil(filteredCount / pagination.size);
const validPage = Math.min(Math.max(1, page), maxPage || 1);
setPagination(prev => ({ ...prev, page: validPage }));
}; };
const handlePageSizeChange = (size: number) => { const handlePageSizeChange = (size: number) => {
// 当分页大小改变时,重置到第一页
setPagination(prev => ({ ...prev, size, page: 1 })); setPagination(prev => ({ ...prev, size, page: 1 }));
}; };
@@ -117,6 +145,22 @@ export default function RoleManagementPage() {
return matchKeyword; return matchKeyword;
}); });
// 客户端分页逻辑
const totalFilteredItems = filteredRoles.length;
const totalPages = Math.ceil(totalFilteredItems / pagination.size);
const startIndex = (pagination.page - 1) * pagination.size;
const endIndex = Math.min(startIndex + pagination.size, totalFilteredItems);
const paginatedRoles = totalFilteredItems > 0 ? filteredRoles.slice(startIndex, endIndex) : [];
// 计算分页状态
const clientPagination = {
...pagination,
total: totalFilteredItems,
totalPages,
hasNext: pagination.page < totalPages,
hasPrev: pagination.page > 1,
};
const handleAddRole = () => { const handleAddRole = () => {
setEditingRole(null); setEditingRole(null);
setFormData({ setFormData({
@@ -128,45 +172,185 @@ export default function RoleManagementPage() {
setShowForm(true); setShowForm(true);
}; };
const handleEdit = (role: Role) => { // 处理表单弹窗关闭
setEditingRole(role); const handleFormDialogClose = (open: boolean) => {
setFormData(role); if (!open) {
setShowForm(true); // 弹窗关闭时清空所有数据和loading状态
setEditingRole(null);
setEditFormLoading(false);
setFormData({
type: 'custom',
status: 'active',
menuIds: [],
permissionIds: [],
});
}
setShowForm(open);
}; };
const handleSave = () => { // 处理详情弹窗关闭
if (!formData.name || !formData.code) { const handleDetailDialogClose = (open: boolean) => {
toast.error('请填写必填项'); if (!open) {
// 弹窗关闭时清空选中的角色和loading状态
setSelectedRole(null);
setDetailLoading(false);
}
setShowDetailDialog(open);
};
const handleEdit = (role: Role) => {
// 先打开弹框,设置初始状态
setEditingRole(role);
setFormData({
type: 'custom',
status: 'active',
menuIds: [],
permissionIds: [],
});
setShowForm(true);
setEditFormLoading(true);
// 在弹框内部异步加载数据
loadEditFormData(role.id);
};
// 加载编辑表单数据的独立函数
const loadEditFormData = async (roleId: string) => {
try {
console.log('编辑角色使用角色ID:', roleId);
// 调用API获取角色详情
const roleDetail = await getRoleDetail(roleId);
// 将API返回的数据转换为表单格式
const formData: RoleFormData = {
name: roleDetail.name,
code: editingRole?.code || roleDetail.id, // 使用转换后的code即API返回的ID
description: roleDetail.description,
type: 'custom',
status: 'active',
menuIds: [],
permissionIds: [],
defaultHomePage: editingRole?.defaultHomePage,
};
setFormData(formData);
} catch (error: any) {
console.error('获取角色详情失败:', error);
// 处理API返回的错误信息
if (error?.data?.message) {
toast.error(error.data.message);
} else if (error?.data?.detail?.original_detail) {
toast.error(error.data.detail.original_detail);
} else if (error?.message) {
toast.error(error.message);
} else {
toast.error('获取角色详情失败');
}
// 加载失败时关闭弹框
setShowForm(false);
} finally {
setEditFormLoading(false);
}
};
const handleSave = async () => {
if (!formData.name) {
toast.error('请填写角色名称');
return; return;
} }
if (editingRole) { if (editingRole) {
const updated = roles.map(role => // 编辑角色 - 检查数据是否有变化
role.id === editingRole.id const hasChanges =
? { editingRole.name !== formData.name ||
...role, editingRole.description !== formData.description ||
...formData, JSON.stringify(editingRole.permissionIds) !== JSON.stringify(formData.permissionIds);
updatedAt: new Date().toISOString(),
}
: role
);
setRoles(updated);
localStorage.setItem('smart_agriculture_roles', JSON.stringify(updated));
toast.success('角色更新成功');
} else {
const newRole: Role = {
id: `role-${Date.now()}`,
...formData as Role,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const updated = [...roles, newRole];
setRoles(updated);
localStorage.setItem('smart_agriculture_roles', JSON.stringify(updated));
toast.success('角色添加成功');
}
if (hasChanges) {
// 有数据变化调用API更新角色
try {
setLoading(true);
// 准备API参数
const roleData = {
name: formData.name,
description: formData.description || '',
permission_ids: formData.permissionIds || [],
};
console.log('开始更新角色:', editingRole.id, roleData);
// 调用API更新角色
await updateRole(editingRole.id, roleData);
// 成功后刷新列表
await loadRoles();
toast.success('角色更新成功');
setShowForm(false); setShowForm(false);
} catch (error: any) {
console.error('更新角色失败:', error);
// 处理API返回的错误信息
if (error?.data?.message) {
toast.error(error.data.message);
} else if (error?.message) {
toast.error(error.message);
} else {
toast.error('更新操作失败');
}
// 更新失败时不关闭表单,不刷新列表
} finally {
setLoading(false);
}
} else {
// 数据没有变化,直接关闭表单,不刷新列表
toast.success('角色信息无变化');
setShowForm(false);
}
} else {
// 新增角色调用API
try {
setLoading(true);
// 准备API参数
const roleData = {
name: formData.name,
description: formData.description || '',
permission_ids: formData.permissionIds || [],
};
console.log('开始创建角色:', roleData);
// 调用API创建角色
await createRole(roleData);
// 成功后刷新列表
await loadRoles();
toast.success('角色添加成功');
setShowForm(false);
} catch (error: any) {
console.error('创建角色失败:', error);
// 处理API返回的错误信息
if (error?.data?.message) {
toast.error(error.data.message);
} else if (error?.message) {
toast.error(error.message);
} else {
toast.error('新增操作失败');
}
// 新增失败时不关闭表单,不刷新列表
} finally {
setLoading(false);
}
}
}; };
const handleDelete = (id: string) => { const handleDelete = (id: string) => {
@@ -176,17 +360,107 @@ export default function RoleManagementPage() {
return; return;
} }
if (!confirm('确定要删除该角色吗?')) return; setDeletingRoleId(id);
setShowDeleteDialog(true);
};
// 确认删除角色
const handleConfirmDelete = async () => {
if (!deletingRoleId) return;
try {
setLoading(true);
console.log('删除角色使用角色ID:', deletingRoleId);
// 调用API删除角色
await deleteRole(deletingRoleId);
// 成功后刷新列表
await loadRoles();
const updated = roles.filter(role => role.id !== id);
setRoles(updated);
localStorage.setItem('smart_agriculture_roles', JSON.stringify(updated));
toast.success('角色删除成功'); toast.success('角色删除成功');
setShowDeleteDialog(false);
setDeletingRoleId(null);
} catch (error: any) {
console.error('删除角色失败:', error);
// 处理API返回的错误信息
if (error?.data?.message) {
toast.error(error.data.message);
} else if (error?.message) {
toast.error(error.message);
} else {
toast.error('删除操作失败');
}
// 删除失败时不关闭对话框,不刷新列表
} finally {
setLoading(false);
}
};
// 取消删除
const handleCancelDelete = () => {
setShowDeleteDialog(false);
setDeletingRoleId(null);
}; };
const handleViewDetail = (role: Role) => { const handleViewDetail = (role: Role) => {
setSelectedRole(role); // 先打开弹框,设置初始状态,确保所有必需属性都有默认值
const roleWithDefaults: Role = {
...role,
menuIds: role.menuIds || [],
permissionIds: role.permissionIds || [],
};
setSelectedRole(roleWithDefaults);
setShowDetailDialog(true); setShowDetailDialog(true);
setDetailLoading(true);
// 在弹框内部异步加载数据
loadRoleDetail(role.id);
};
// 加载角色详情的独立函数
const loadRoleDetail = async (roleId: string) => {
try {
console.log('查看角色详情使用角色ID:', roleId);
// 调用API获取角色详情
const roleDetail = await getRoleDetail(roleId);
// 将API返回的数据转换为页面格式
const detailedRole: Role = {
...selectedRole,
name: roleDetail.name,
description: roleDetail.description,
id: roleDetail.id, // 使用API返回的真实ID
code: selectedRole?.code || roleDetail.id, // 保持原有的code
tenant_id: roleDetail.tenant_id,
createdAt: roleDetail.created_at,
updatedAt: roleDetail.updated_at,
menuIds: selectedRole?.menuIds || [],
permissionIds: selectedRole?.permissionIds || [],
};
setSelectedRole(detailedRole);
} catch (error: any) {
console.error('获取角色详情失败:', error);
// 处理API返回的错误信息
if (error?.data?.message) {
toast.error(error.data.message);
} else if (error?.data?.detail?.original_detail) {
toast.error(error.data.detail.original_detail);
} else if (error?.message) {
toast.error(error.message);
} else {
toast.error('获取角色详情失败');
}
} finally {
setDetailLoading(false);
}
}; };
return ( return (
@@ -206,9 +480,9 @@ export default function RoleManagementPage() {
{/* 角色列表 */} {/* 角色列表 */}
<RoleList <RoleList
roles={filteredRoles} roles={paginatedRoles}
loading={loading} loading={loading}
pagination={pagination} pagination={clientPagination}
onPageChange={handlePageChange} onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange} onPageSizeChange={handlePageSizeChange}
onViewDetail={handleViewDetail} onViewDetail={handleViewDetail}
@@ -219,20 +493,40 @@ export default function RoleManagementPage() {
{/* 添加/编辑表单 */} {/* 添加/编辑表单 */}
<RoleFormDialog <RoleFormDialog
open={showForm} open={showForm}
onOpenChange={setShowForm} onOpenChange={handleFormDialogClose}
editingRole={editingRole} editingRole={editingRole}
formData={formData} formData={formData}
onFormDataChange={setFormData} onFormDataChange={setFormData}
onSave={handleSave} onSave={handleSave}
editFormLoading={editFormLoading}
/> />
{/* 详情对话框 */} {/* 详情对话框 */}
<RoleDetailDialog <RoleDetailDialog
open={showDetailDialog} open={showDetailDialog}
onOpenChange={setShowDetailDialog} onOpenChange={handleDetailDialogClose}
selectedRole={selectedRole} selectedRole={selectedRole}
detailLoading={detailLoading}
/> />
{/* 删除确认对话框 */}
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancelDelete}></AlertDialogCancel>
<AlertDialogAction onClick={handleConfirmDelete} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* 使用说明 */} {/* 使用说明 */}
<RoleManagementInstructions /> <RoleManagementInstructions />
</div> </div>