fix: 1. 继续对齐交叉评查的接口,完善创建交叉评查的逻辑 和 相关组件的渲染布局。
2. 文档的基本信息修改改用接口。 3. 重新完善角色权限管理的页面逻辑。 4.将评查点列表中的返回逻辑改用浏览器的记忆返回。
This commit is contained in:
@@ -20,6 +20,8 @@ import {
|
||||
deleteRole,
|
||||
revokeUserRole,
|
||||
getUserRoles,
|
||||
getRoutePermissions,
|
||||
isSharedPermission,
|
||||
type RoleInfo,
|
||||
type RouteInfo,
|
||||
type UserInfo,
|
||||
@@ -857,9 +859,18 @@ export default function RolePermissions() {
|
||||
// 存储每个路由的 permissions(routeId -> permissions[])
|
||||
const [routePermissionsMap, setRoutePermissionsMap] = useState<Map<number, ApiPermission[]>>(new Map());
|
||||
|
||||
// v3.9: 父子路由折叠状态(存储已展开的父路由ID)
|
||||
const [collapsedRouteIds, setCollapsedRouteIds] = useState<number[]>([]);
|
||||
|
||||
// 保存权限的 loading 状态
|
||||
const [savingPermissions, setSavingPermissions] = useState(false);
|
||||
|
||||
// v3.8: 加载角色权限的 loading 状态
|
||||
const [loadingPermissions, setLoadingPermissions] = useState(false);
|
||||
|
||||
// v3.8: 路由ID到路由信息的映射(用于显示通用权限关联的路由名称)
|
||||
const [routeIdToInfoMap, setRouteIdToInfoMap] = useState<Map<number, { title: string; path: string }>>(new Map());
|
||||
|
||||
// 加载初始数据
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
@@ -933,6 +944,22 @@ export default function RolePermissions() {
|
||||
setRoutes(routesData);
|
||||
setUsers(filteredUsers);
|
||||
|
||||
// v3.8: 构建路由ID到路由信息的映射
|
||||
const buildRouteIdMap = (routes: RouteInfo[]): Map<number, { title: string; path: string }> => {
|
||||
const map = new Map<number, { title: string; path: string }>();
|
||||
const traverse = (routeList: RouteInfo[]) => {
|
||||
routeList.forEach(route => {
|
||||
map.set(route.id, { title: route.route_title, path: route.route_path });
|
||||
if (route.children) {
|
||||
traverse(route.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
traverse(routes);
|
||||
return map;
|
||||
};
|
||||
setRouteIdToInfoMap(buildRouteIdMap(routesData));
|
||||
|
||||
// 默认选中第一个角色(使用过滤后的列表)
|
||||
if (filteredRoles.length > 0) {
|
||||
handleSelectRole(filteredRoles[0]);
|
||||
@@ -953,68 +980,96 @@ export default function RolePermissions() {
|
||||
// 选择角色
|
||||
const handleSelectRole = async (role: RoleInfo) => {
|
||||
setSelectedRole(role);
|
||||
setLoadingPermissions(true); // v3.8: 开始加载权限
|
||||
|
||||
// 动态导入权限映射工具
|
||||
const { mapPermissions } = await import('~/utils/permission-mapper');
|
||||
try {
|
||||
// 动态导入权限映射工具
|
||||
const { mapPermissions } = await import('~/utils/permission-mapper');
|
||||
|
||||
// v3.0: 并行加载数据
|
||||
const [routesResult, rolePermissions, users] = await Promise.all([
|
||||
getRoleRoutesWithPermissions(role.id),
|
||||
getRolePermissions(role.id), // 获取该角色已分配的权限
|
||||
getRoleUsers(role.id)
|
||||
]);
|
||||
// v3.0: 并行加载数据
|
||||
const [routesResult, rolePermissions, users] = await Promise.all([
|
||||
getRoleRoutesWithPermissions(role.id),
|
||||
getRolePermissions(role.id), // 获取该角色已分配的权限
|
||||
getRoleUsers(role.id)
|
||||
]);
|
||||
|
||||
const { routes: routesWithPerms, selectedRouteIds: routeIds } = routesResult;
|
||||
const { routes: routesWithPerms, selectedRouteIds: routeIds } = routesResult;
|
||||
|
||||
// 构建原始权限映射(未映射的,用于保存)
|
||||
const originalPermMap = new Map<number, ApiPermission[]>();
|
||||
// 存储所有原始权限的列表
|
||||
const allOriginalPerms: ApiPermission[] = [];
|
||||
const extractOriginalPermissions = (routes: RouteInfo[]) => {
|
||||
routes.forEach(route => {
|
||||
if (route.permissions && route.permissions.length > 0) {
|
||||
originalPermMap.set(route.id, route.permissions);
|
||||
allOriginalPerms.push(...route.permissions);
|
||||
}
|
||||
if (route.children) {
|
||||
extractOriginalPermissions(route.children);
|
||||
// v3.6: 为每个路由获取权限(包含通用权限)
|
||||
// 收集所有路由ID
|
||||
const collectAllRouteIds = (routes: RouteInfo[]): number[] => {
|
||||
let ids: number[] = [];
|
||||
routes.forEach(route => {
|
||||
ids.push(route.id);
|
||||
if (route.children) {
|
||||
ids = ids.concat(collectAllRouteIds(route.children));
|
||||
}
|
||||
});
|
||||
return ids;
|
||||
};
|
||||
|
||||
const allRouteIds = collectAllRouteIds(routesWithPerms);
|
||||
|
||||
// v3.6: 并行获取每个路由的权限(包含通用权限)
|
||||
const routePermissionsPromises = allRouteIds.map(async (routeId) => {
|
||||
const permissions = await getRoutePermissions(routeId);
|
||||
return { routeId, permissions };
|
||||
});
|
||||
|
||||
const routePermissionsResults = await Promise.all(routePermissionsPromises);
|
||||
|
||||
// 构建原始权限映射(用于保存)
|
||||
const originalPermMap = new Map<number, ApiPermission[]>();
|
||||
const allOriginalPerms: ApiPermission[] = [];
|
||||
// 用于去重通用权限(通用权限可能在多个路由下出现,但只需要保存一次)
|
||||
const seenPermissionIds = new Set<number>();
|
||||
|
||||
routePermissionsResults.forEach(({ routeId, permissions }) => {
|
||||
if (permissions.length > 0) {
|
||||
originalPermMap.set(routeId, permissions);
|
||||
permissions.forEach(p => {
|
||||
if (!seenPermissionIds.has(p.id)) {
|
||||
seenPermissionIds.add(p.id);
|
||||
allOriginalPerms.push(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
extractOriginalPermissions(routesWithPerms);
|
||||
|
||||
// 存储原始权限
|
||||
setOriginalRoutePermissionsMap(originalPermMap);
|
||||
setOriginalAllPermissions(allOriginalPerms);
|
||||
// 存储原始权限
|
||||
setOriginalRoutePermissionsMap(originalPermMap);
|
||||
setOriginalAllPermissions(allOriginalPerms);
|
||||
|
||||
// 构建映射后的权限映射(用于显示)
|
||||
const displayPermMap = new Map<number, ApiPermission[]>();
|
||||
const extractDisplayPermissions = (routes: RouteInfo[]) => {
|
||||
routes.forEach(route => {
|
||||
if (route.permissions && route.permissions.length > 0) {
|
||||
const mappedPermissions = mapPermissions(route.permissions);
|
||||
displayPermMap.set(route.id, mappedPermissions);
|
||||
}
|
||||
if (route.children) {
|
||||
extractDisplayPermissions(route.children);
|
||||
// 构建映射后的权限映射(用于显示)
|
||||
const displayPermMap = new Map<number, ApiPermission[]>();
|
||||
routePermissionsResults.forEach(({ routeId, permissions }) => {
|
||||
if (permissions.length > 0) {
|
||||
const mappedPermissions = mapPermissions(permissions) as ApiPermission[];
|
||||
displayPermMap.set(routeId, mappedPermissions);
|
||||
}
|
||||
});
|
||||
};
|
||||
extractDisplayPermissions(routesWithPerms);
|
||||
|
||||
// v3.5: 修复BUG - 只筛选 grant_type=GRANT 的权限
|
||||
// BUG说明:之前没有检查 grant_type,导致 DENY 的权限也被显示为勾选
|
||||
// 修改前:const assignedPermissionIds = rolePermissions.map(p => p.permission_id);
|
||||
const assignedPermissionIds = rolePermissions
|
||||
.filter(p => p.grant_type === 'GRANT')
|
||||
.map(p => p.permission_id);
|
||||
// v3.5: 修复BUG - 只筛选 grant_type=GRANT 的权限
|
||||
// BUG说明:之前没有检查 grant_type,导致 DENY 的权限也被显示为勾选
|
||||
// 修改前:const assignedPermissionIds = rolePermissions.map(p => p.permission_id);
|
||||
const assignedPermissionIds = rolePermissions
|
||||
.filter(p => p.grant_type === 'GRANT')
|
||||
.map(p => p.permission_id);
|
||||
|
||||
// 存储状态
|
||||
setRoutePermissionsMap(displayPermMap); // 用于显示
|
||||
setSelectedRouteIds(routeIds);
|
||||
setSelectedPermissionIds(assignedPermissionIds); // 使用原始权限ID
|
||||
setExpandedRouteIds([]); // 重置展开状态
|
||||
setRoleUsers(users);
|
||||
// console.log('🔑 [RolePermissions v3.0] 过滤前的已分配权限ID长度:', rolePermissions);
|
||||
|
||||
// 存储状态
|
||||
setRoutePermissionsMap(displayPermMap); // 用于显示
|
||||
setSelectedRouteIds(routeIds);
|
||||
setSelectedPermissionIds(assignedPermissionIds); // 使用原始权限ID
|
||||
setExpandedRouteIds([]); // 重置展开状态
|
||||
setRoleUsers(users);
|
||||
} catch (error) {
|
||||
console.error('加载角色权限失败:', error);
|
||||
toastService.error('加载角色权限失败');
|
||||
} finally {
|
||||
setLoadingPermissions(false); // v3.8: 结束加载权限
|
||||
}
|
||||
};
|
||||
|
||||
// 递归查找路由
|
||||
@@ -1144,6 +1199,35 @@ export default function RolePermissions() {
|
||||
);
|
||||
};
|
||||
|
||||
// v3.9: 切换父子路由折叠状态
|
||||
const handleToggleCollapse = (routeId: number) => {
|
||||
setCollapsedRouteIds(prev =>
|
||||
prev.includes(routeId)
|
||||
? prev.filter(id => id !== routeId)
|
||||
: [...prev, routeId]
|
||||
);
|
||||
};
|
||||
|
||||
// v3.9: 全部展开/全部折叠
|
||||
const handleExpandAll = () => {
|
||||
setCollapsedRouteIds([]);
|
||||
};
|
||||
|
||||
const handleCollapseAll = () => {
|
||||
// 收集所有有子路由的路由ID
|
||||
const collectParentRouteIds = (routeList: RouteInfo[]): number[] => {
|
||||
let ids: number[] = [];
|
||||
routeList.forEach(route => {
|
||||
if (route.children && route.children.length > 0) {
|
||||
ids.push(route.id);
|
||||
ids = ids.concat(collectParentRouteIds(route.children));
|
||||
}
|
||||
});
|
||||
return ids;
|
||||
};
|
||||
setCollapsedRouteIds(collectParentRouteIds(routes));
|
||||
};
|
||||
|
||||
// v3.0: 判断是否是"所有权限"项(用于过滤)
|
||||
const isAllPermission = (permission: ApiPermission): boolean => {
|
||||
const key = permission.permission_key?.toLowerCase() || '';
|
||||
@@ -1158,13 +1242,25 @@ export default function RolePermissions() {
|
||||
return permissions.filter(p => !isAllPermission(p));
|
||||
};
|
||||
|
||||
// v3.0: 切换单个API权限
|
||||
const handleTogglePermission = (permissionId: number, checked: boolean) => {
|
||||
// v3.7: 切换单个API权限(支持通用权限同步)
|
||||
const handleTogglePermission = (permission: ApiPermission, checked: boolean) => {
|
||||
const permissionId = permission.id;
|
||||
|
||||
if (checked) {
|
||||
setSelectedPermissionIds([...selectedPermissionIds, permissionId]);
|
||||
} else {
|
||||
setSelectedPermissionIds(selectedPermissionIds.filter(id => id !== permissionId));
|
||||
}
|
||||
|
||||
// v3.7: 如果是通用权限,同步更新其他关联路由的显示状态
|
||||
// 注意:由于通用权限在数据库中只有一条记录,这里只需要更新 UI 显示
|
||||
// 实际的 selectedPermissionIds 只需要包含一次该权限ID
|
||||
if (isSharedPermission(permission) && permission.related_routes) {
|
||||
// 通用权限的 permissionId 是唯一的,所以这里不需要额外处理
|
||||
// 但需要触发 UI 更新,让其他路由下显示的同一权限也更新勾选状态
|
||||
// 由于 React 的状态更新机制,上面的 setSelectedPermissionIds 已经会触发重渲染
|
||||
console.log(`🔗 [handleTogglePermission] 通用权限 ${permission.display_name} 已${checked ? '勾选' : '取消'},关联路由: ${permission.related_routes.join(', ')}`);
|
||||
}
|
||||
};
|
||||
|
||||
// v3.0: 获取HTTP方法对应的标签样式
|
||||
@@ -1350,11 +1446,11 @@ export default function RolePermissions() {
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染路由树 - v3.0: 支持展开显示API权限
|
||||
// v3.8: 渲染路由树 - 卡片式设计,支持展开显示API权限
|
||||
const renderRouteTree = (routeList: RouteInfo[], level = 0) => {
|
||||
return routeList.map(route => {
|
||||
const hasChildren = route.children && route.children.length > 0;
|
||||
// v3.0: 从 routePermissionsMap 获取该路由的 permissions,并过滤掉"所有权限"
|
||||
// 从 routePermissionsMap 获取该路由的 permissions,并过滤掉"所有权限"
|
||||
const rawPermissions = routePermissionsMap.get(route.id) || [];
|
||||
const permissions = filterPermissions(rawPermissions);
|
||||
const hasPermissions = permissions.length > 0;
|
||||
@@ -1373,15 +1469,189 @@ export default function RolePermissions() {
|
||||
selectedPermissionIds.includes(id)
|
||||
).length;
|
||||
|
||||
// 是否为一级路由(使用卡片样式)
|
||||
const isTopLevel = level === 0;
|
||||
|
||||
// 渲染权限展开按钮
|
||||
const renderPermissionButton = () => {
|
||||
if (!hasPermissions) return null;
|
||||
|
||||
const btnStyle: React.CSSProperties = {
|
||||
backgroundColor:
|
||||
selectedPermCount === permissions.length ? '#e6f7ed' :
|
||||
selectedPermCount > 0 ? '#fff7e6' : '#f5f5f5',
|
||||
color:
|
||||
selectedPermCount === permissions.length ? '#52c41a' :
|
||||
selectedPermCount > 0 ? '#fa8c16' : '#666',
|
||||
border:
|
||||
selectedPermCount === permissions.length ? '1px solid #b7eb8f' :
|
||||
selectedPermCount > 0 ? '1px solid #ffd591' : '1px solid #d9d9d9',
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="permission-expand-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleRouteExpand(route.id);
|
||||
}}
|
||||
style={btnStyle}
|
||||
>
|
||||
<i className={`ri-${isExpanded ? 'arrow-up-s' : 'arrow-down-s'}-line`}></i>
|
||||
<span>API权限 ({selectedPermCount}/{permissions.length})</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染API权限列表
|
||||
const renderPermissionsList = () => {
|
||||
if (!hasPermissions || !isExpanded) return null;
|
||||
|
||||
return (
|
||||
<div className="permissions-list">
|
||||
{permissions.map(permission => {
|
||||
const isShared = isSharedPermission(permission);
|
||||
|
||||
// 获取通用权限关联的路由名称(排除当前路由)
|
||||
const relatedRouteNames = (() => {
|
||||
if (!isShared || !permission.related_routes) return [];
|
||||
return permission.related_routes
|
||||
.filter(rid => rid !== route.id)
|
||||
.map(rid => {
|
||||
const routeInfo = routeIdToInfoMap.get(rid);
|
||||
return routeInfo ? routeInfo.title : `路由${rid}`;
|
||||
});
|
||||
})();
|
||||
|
||||
return (
|
||||
<label
|
||||
key={permission.id}
|
||||
className={`permission-item ${isShared ? 'shared' : ''}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedPermissionIds.includes(permission.id)}
|
||||
onChange={(e) => handleTogglePermission(permission, e.target.checked)}
|
||||
style={{ margin: '3px 0 0 0', flexShrink: 0 }}
|
||||
disabled={!isProvincialAdmin}
|
||||
/>
|
||||
{isShared && (
|
||||
<span
|
||||
className="shared-badge"
|
||||
title={`此权限同时适用于 ${permission.related_routes?.length || 0} 个页面`}
|
||||
>
|
||||
通用
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className={`method-tag ${(permission.api_method || '').toLowerCase()}`}
|
||||
>
|
||||
{permission.api_method || 'N/A'}
|
||||
</span>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<span style={{ color: isShared ? '#1890ff' : '#333', fontSize: '13px', fontWeight: 500 }}>
|
||||
{permission.display_name}
|
||||
</span>
|
||||
{isShared && relatedRouteNames.length > 0 && (
|
||||
<div className="related-routes">
|
||||
<i className="ri-link"></i>
|
||||
<span>同时关联:</span>
|
||||
{relatedRouteNames.map((name, idx) => (
|
||||
<span key={idx} className="related-route-tag">{name}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span style={{ color: '#999', fontSize: '11px', flexShrink: 0, fontFamily: 'Consolas, Monaco, monospace' }}>
|
||||
{permission.api_path}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// v3.9: 判断是否折叠
|
||||
const isCollapsed = collapsedRouteIds.includes(route.id);
|
||||
|
||||
// v3.9: 渲染折叠按钮
|
||||
const renderCollapseButton = () => {
|
||||
if (!hasChildren) return null;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`collapse-btn ${isCollapsed ? 'collapsed' : ''}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleCollapse(route.id);
|
||||
}}
|
||||
title={isCollapsed ? '展开子路由' : '折叠子路由'}
|
||||
>
|
||||
<i className={`ri-arrow-${isCollapsed ? 'right' : 'down'}-s-line`}></i>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
// 一级路由使用卡片样式
|
||||
if (isTopLevel) {
|
||||
return (
|
||||
<div key={route.id} className={`route-card ${isChecked ? 'checked' : ''}`}>
|
||||
<div className="route-item-content">
|
||||
{renderCollapseButton()}
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`route-${route.id}`}
|
||||
checked={isChecked}
|
||||
ref={el => {
|
||||
if (el) el.indeterminate = isIndeterminate ?? false;
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (hasChildren) {
|
||||
handleToggleParentRoute(route, e.target.checked);
|
||||
} else {
|
||||
handleToggleRoute(route.id, e.target.checked);
|
||||
}
|
||||
}}
|
||||
className="route-checkbox"
|
||||
disabled={!isProvincialAdmin}
|
||||
/>
|
||||
<label htmlFor={`route-${route.id}`} className="route-label">
|
||||
{route.icon && <i className={`${route.icon} route-icon`}></i>}
|
||||
<span className="route-title">{route.route_title}</span>
|
||||
<span className="route-path">{route.route_path}</span>
|
||||
{hasChildren && (
|
||||
<span className="children-count">
|
||||
{route.children!.length} 个子路由
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
{renderPermissionButton()}
|
||||
</div>
|
||||
|
||||
{renderPermissionsList()}
|
||||
|
||||
{hasChildren && (
|
||||
<div className={`route-children ${isCollapsed ? 'collapsed' : ''}`}>
|
||||
{renderRouteTree(route.children!, level + 1)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 子路由使用简洁样式
|
||||
return (
|
||||
<div key={route.id} className="route-item" style={{ paddingLeft: `${level * 20}px` }}>
|
||||
<div className="route-item-content">
|
||||
<div key={route.id} className="route-item">
|
||||
<div className={`route-item-content ${isChecked ? 'checked' : ''}`}>
|
||||
{renderCollapseButton()}
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`route-${route.id}`}
|
||||
checked={isChecked}
|
||||
ref={el => {
|
||||
if (el) el.indeterminate = isIndeterminate;
|
||||
if (el) el.indeterminate = isIndeterminate ?? false;
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (hasChildren) {
|
||||
@@ -1397,104 +1667,19 @@ export default function RolePermissions() {
|
||||
{route.icon && <i className={`${route.icon} route-icon`}></i>}
|
||||
<span className="route-title">{route.route_title}</span>
|
||||
<span className="route-path">{route.route_path}</span>
|
||||
{hasChildren && (
|
||||
<span className="children-count">
|
||||
{route.children!.length} 个子路由
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
|
||||
{/* v3.0: 显示权限展开按钮 */}
|
||||
{hasPermissions && (
|
||||
<button
|
||||
type="button"
|
||||
className="permission-expand-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleRouteExpand(route.id);
|
||||
}}
|
||||
style={{
|
||||
marginLeft: '8px',
|
||||
padding: '2px 8px',
|
||||
fontSize: '12px',
|
||||
backgroundColor:
|
||||
selectedPermCount === permissions.length ? '#e6f7ed' : // 全部选中:绿色
|
||||
selectedPermCount > 0 ? '#fff7e6' : // 部分选中:浅橙色
|
||||
'#f5f5f5', // 未选中:灰色
|
||||
color:
|
||||
selectedPermCount === permissions.length ? '#52c41a' : // 全部选中:绿色
|
||||
selectedPermCount > 0 ? '#fa8c16' : // 部分选中:橙色
|
||||
'#666', // 未选中:灰色
|
||||
border:
|
||||
selectedPermCount === permissions.length ? '1px solid #b7eb8f' : // 全部选中:绿色
|
||||
selectedPermCount > 0 ? '1px solid #ffd591' : // 部分选中:浅橙色
|
||||
'1px solid #d9d9d9', // 未选中:灰色
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px'
|
||||
}}
|
||||
>
|
||||
<i className={`ri-${isExpanded ? 'arrow-up-s' : 'arrow-down-s'}-line`}></i>
|
||||
<span>API权限 ({selectedPermCount}/{permissions.length})</span>
|
||||
</button>
|
||||
)}
|
||||
{renderPermissionButton()}
|
||||
</div>
|
||||
|
||||
{/* v3.0: 展开的API权限列表(过滤掉"所有权限"项) */}
|
||||
{hasPermissions && isExpanded && (
|
||||
<div
|
||||
className="permissions-list"
|
||||
style={{
|
||||
marginTop: '8px',
|
||||
marginLeft: '24px',
|
||||
padding: '12px',
|
||||
backgroundColor: '#fafafa',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #e8e8e8'
|
||||
}}
|
||||
>
|
||||
{permissions.map(permission => (
|
||||
<label
|
||||
key={permission.id}
|
||||
className="permission-item"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
padding: '6px 0',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedPermissionIds.includes(permission.id)}
|
||||
onChange={(e) => handleTogglePermission(permission.id, e.target.checked)}
|
||||
style={{ margin: 0 }}
|
||||
disabled={!isProvincialAdmin}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
...getMethodTagStyle(permission.api_method),
|
||||
padding: '2px 6px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
minWidth: '50px',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
{permission.api_method}
|
||||
</span>
|
||||
<span style={{ color: '#333', fontSize: '13px' }}>
|
||||
{permission.display_name}
|
||||
</span>
|
||||
<span style={{ color: '#999', fontSize: '11px', marginLeft: 'auto' }}>
|
||||
{permission.api_path}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{renderPermissionsList()}
|
||||
|
||||
{hasChildren && (
|
||||
<div className="route-children">
|
||||
<div className={`route-children ${isCollapsed ? 'collapsed' : ''}`}>
|
||||
{renderRouteTree(route.children!, level + 1)}
|
||||
</div>
|
||||
)}
|
||||
@@ -1654,40 +1839,83 @@ export default function RolePermissions() {
|
||||
{/* 路由权限Tab */}
|
||||
{activeTab === 'permissions' && (
|
||||
<div className="permissions-tab">
|
||||
{/* v3.3: 权限提示(仅省级管理员可修改) */}
|
||||
{!isProvincialAdmin && (
|
||||
<div className="form-notice warning" style={{ marginBottom: '16px' }}>
|
||||
<i className="ri-information-line"></i>
|
||||
<span>您当前为只读模式,仅省级管理员可以修改角色路由权限</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="permissions-header">
|
||||
<h3>为角色 "{selectedRole.role_name}" 分配路由权限</h3>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={savingPermissions ? "ri-loader-4-line spin" : "ri-save-line"}
|
||||
onClick={handleSavePermissions}
|
||||
disabled={!isProvincialAdmin || savingPermissions}
|
||||
>
|
||||
{savingPermissions ? '保存中...' : '保存权限'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* v3.0: 始终使用 routes 渲染所有可用路由,permissions 从 routePermissionsMap 获取 */}
|
||||
<div className="routes-tree">
|
||||
{renderRouteTree(routes)}
|
||||
</div>
|
||||
|
||||
{/* v3.0: 更新权限统计,显示路由和API权限数量 */}
|
||||
<div className="permissions-summary">
|
||||
<i className="ri-information-line"></i>
|
||||
已选择 <strong>{selectedRouteIds.length}</strong> 个路由权限
|
||||
{selectedPermissionIds.length > 0 && (
|
||||
<>
|
||||
,<strong>{selectedPermissionIds.length}</strong> 个API权限
|
||||
</>
|
||||
{/* v3.8: 固定头部区域 */}
|
||||
<div className="permissions-tab-header">
|
||||
{/* v3.3: 权限提示(仅省级管理员可修改) */}
|
||||
{!isProvincialAdmin && (
|
||||
<div className="form-notice warning" style={{ marginBottom: '12px' }}>
|
||||
<i className="ri-information-line"></i>
|
||||
<span>您当前为只读模式,仅省级管理员可以修改角色路由权限</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="permissions-header">
|
||||
<h3>为角色 "{selectedRole.role_name}" 分配路由权限</h3>
|
||||
{loadingPermissions ? (<></>) : (
|
||||
<>
|
||||
{/* v3.9: 折叠控制栏 */}
|
||||
<div className="collapse-controls">
|
||||
<button
|
||||
type="button"
|
||||
className="collapse-control-btn"
|
||||
onClick={handleExpandAll}
|
||||
title="展开全部"
|
||||
>
|
||||
<i className="ri-expand-diagonal-line"></i>
|
||||
<span>展开全部</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="collapse-control-btn"
|
||||
onClick={handleCollapseAll}
|
||||
title="折叠全部"
|
||||
>
|
||||
<i className="ri-contract-left-right-line"></i>
|
||||
<span>折叠全部</span>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) }
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
icon={savingPermissions ? "ri-loader-4-line spin" : "ri-save-line"}
|
||||
onClick={handleSavePermissions}
|
||||
disabled={!isProvincialAdmin || savingPermissions}
|
||||
>
|
||||
{savingPermissions ? '保存中...' : '保存权限'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* v3.8: 加载状态显示 */}
|
||||
{loadingPermissions ? (
|
||||
<div className="loading-container" style={{ minHeight: '300px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '12px' }}>
|
||||
<i className="ri-loader-4-line spin" style={{ fontSize: '32px', color: '#00684a' }}></i>
|
||||
<span style={{ color: '#666' }}>正在加载权限配置...</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
|
||||
{/* v3.8: 路由树容器 - 可滚动区域 */}
|
||||
<div className="routes-tree-container">
|
||||
<div className="routes-tree">
|
||||
{renderRouteTree(routes)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* v3.0: 更新权限统计,显示路由和API权限数量 */}
|
||||
{/* <div className="permissions-summary">
|
||||
<i className="ri-information-line"></i>
|
||||
已选择 <strong>{selectedRouteIds.length}</strong> 个路由权限
|
||||
{selectedPermissionIds.length > 0 && (
|
||||
<>
|
||||
,<strong>{selectedPermissionIds.length}</strong> 个API权限
|
||||
</>
|
||||
)}
|
||||
</div> */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user