修复权限操作

This commit is contained in:
2025-12-08 21:36:03 +08:00
parent a4479971a8
commit 0a3c1a2735
5 changed files with 175 additions and 39 deletions
+17 -14
View File
@@ -399,22 +399,25 @@ export async function getRoleRoutesWithPermissions(roleId: number): Promise<{
return ids; return ids;
}; };
// 收集所有已选中的权限ID(从当前角色的权限中) // v3.5: 修复BUG - 删除了 collectPermissionIds 函数
const collectPermissionIds = (routes: RouteInfo[]): number[] => { // BUG说明:从路由元数据收集权限ID是错误的,因为路由的permissions数组包含的仅仅是权限定义,
let ids: number[] = []; // 而不是实际授权状态。实际授权状态应该从 `/api/v3/rbac/role-permissions` 接口获取。
routes.forEach(route => { //
if (route.permissions) { // 之前的错误实现:
ids = ids.concat(route.permissions.map(p => p.id)); // const collectPermissionIds = (routes: RouteInfo[]): number[] => {
} // let ids: number[] = [];
if (route.children) { // routes.forEach(route => {
ids = ids.concat(collectPermissionIds(route.children)); // if (route.permissions) {
} // ids = ids.concat(route.permissions.map(p => p.id));
}); // }
return ids; // });
}; // return ids;
// };
//
// 修复方案:返回空数组,由调用方使用 getRolePermissions() 获取实际授权数据
const selectedRouteIds = collectRouteIds(mappedRoutes); const selectedRouteIds = collectRouteIds(mappedRoutes);
const selectedPermissionIds = collectPermissionIds(mappedRoutes); const selectedPermissionIds: number[] = []; // v3.5: 修复BUG - 返回空数组
return { return {
routes: mappedRoutes, routes: mappedRoutes,
+35 -2
View File
@@ -60,6 +60,26 @@ export default function Chat() {
handleChatAppChange: originalHandleChatAppChange, handleChatAppChange: originalHandleChatAppChange,
} = useChatApps(); } = useChatApps();
// 调试日志 - 监听chatApps和currentChatApp变化
useEffect(() => {
console.log('[Chat] chatApps 更新:', {
count: chatApps.length,
apps: chatApps.map(app => ({
app_id: app.app_id,
app_name: app.app_name,
is_default: app.is_default
}))
});
}, [chatApps]);
useEffect(() => {
console.log('[Chat] currentChatApp 更新:', currentChatApp ? {
app_id: currentChatApp.app_id,
app_name: currentChatApp.app_name,
is_default: currentChatApp.is_default
} : 'null');
}, [currentChatApp]);
// 会话管理 // 会话管理
const { const {
conversationList, conversationList,
@@ -405,14 +425,27 @@ export default function Chat() {
* 切换应用后刷新加载对应的会话列表 * 切换应用后刷新加载对应的会话列表
*/ */
const handleChatAppChange = async (appId: string) => { const handleChatAppChange = async (appId: string) => {
console.log('🔄 [Chat] 切换对话应用:', appId); console.log('🔄 [Chat] 用户点击切换对话应用,目标ID:', appId);
console.log('🔄 [Chat] 当前可用应用列表:', chatApps.map(app => ({
app_id: app.app_id,
app_name: app.app_name
})));
// 查找目标应用
const targetApp = chatApps.find(app => app.app_id === appId);
if (!targetApp) {
console.error('❌ [Chat] 未找到目标应用 ID:', appId);
return;
}
console.log('🔄 [Chat] 找到目标应用:', targetApp.app_name);
// 调用原始的切换方法 // 调用原始的切换方法
originalHandleChatAppChange(appId, async (app) => { originalHandleChatAppChange(appId, async (app) => {
console.log('🔄 [Chat] 应用已切换到:', app.app_name, '开始刷新会话列表...'); console.log(' [Chat] originalHandleChatAppChange回调触发,传入应用:', app.app_name);
try { try {
// 重新获取会话列表,传入新的应用ID获取该应用的会话 // 重新获取会话列表,传入新的应用ID获取该应用的会话
console.log('📋 [Chat] 开始获取新应用的会话列表...');
const conversationData = await fetchConversations(app.app_id); const conversationData = await fetchConversations(app.app_id);
const conversations = (conversationData as any).data || []; const conversations = (conversationData as any).data || [];
+26 -2
View File
@@ -10,7 +10,7 @@ import {
SearchOutlined, SearchOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Button, Dropdown, Input, Layout, Menu, Modal, Tooltip, message, theme, Select } from 'antd'; import { Button, Dropdown, Input, Layout, Menu, Modal, Tooltip, message, theme, Select } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react'; import { forwardRef, useImperativeHandle, useMemo, useState } from 'react';
import type { ChatApp } from '~/api/dify-chat-apps/types'; import type { ChatApp } from '~/api/dify-chat-apps/types';
import type { ConversationItem } from '~/api/dify-chat'; import type { ConversationItem } from '~/api/dify-chat';
import { deleteConversation, renameConversation } from '~/api/dify-chat'; import { deleteConversation, renameConversation } from '~/api/dify-chat';
@@ -68,6 +68,30 @@ const ChatSidebar = forwardRef<ChatSidebarRef, ChatSidebarProps>(({
token: { colorBgContainer, borderRadiusLG }, token: { colorBgContainer, borderRadiusLG },
} = theme.useToken(); } = theme.useToken();
// 去重:防止后端返回重复的应用数据
const uniqueChatApps = useMemo(() => {
const appMap = new Map<string, ChatApp>();
chatApps.forEach(app => {
// 如果已经存在相同ID的应用,保留第一个(或检查is_default属性)
if (!appMap.has(app.app_id)) {
appMap.set(app.app_id, app);
} else {
// 如果重复的是默认应用,保留默认版本
const existingApp = appMap.get(app.app_id)!;
if (app.is_default && !existingApp.is_default) {
appMap.set(app.app_id, app);
}
}
});
const unique = Array.from(appMap.values());
console.log('[Sidebar] 应用去重:', {
originalCount: chatApps.length,
uniqueCount: unique.length,
removed: chatApps.length - unique.length
});
return unique;
}, [chatApps]);
// 过滤会话列表 // 过滤会话列表
const filteredConversations = conversations.filter(conv => const filteredConversations = conversations.filter(conv =>
conv.name.toLowerCase().includes(searchValue.toLowerCase()) conv.name.toLowerCase().includes(searchValue.toLowerCase())
@@ -302,7 +326,7 @@ const ChatSidebar = forwardRef<ChatSidebarRef, ChatSidebarProps>(({
placeholder="选择对话应用" placeholder="选择对话应用"
size="small" size="small"
> >
{(chatApps || []).map(app => ( {uniqueChatApps.map(app => (
<Select.Option key={app.app_id} value={app.app_id}> <Select.Option key={app.app_id} value={app.app_id}>
{app.app_name} {app.app_name}
</Select.Option> </Select.Option>
+63 -7
View File
@@ -28,9 +28,17 @@ export function useChatApps() {
const loadChatApps = useCallback(async () => { const loadChatApps = useCallback(async () => {
setLoadingChatApps(true); setLoadingChatApps(true);
setError(null); setError(null);
try { try {
console.log('[useChatApps] 开始加载对话应用列表...');
const response = await getMyChatApps(); const response = await getMyChatApps();
console.log('[useChatApps] 加载完成,返回数据:', response);
console.log('[useChatApps] 应用列表:', response.data.map(app => ({
app_id: app.app_id,
app_name: app.app_name,
is_default: app.is_default
})));
setChatApps(response.data); setChatApps(response.data);
return response.data; return response.data;
} catch (err: any) { } catch (err: any) {
@@ -48,13 +56,40 @@ export function useChatApps() {
const loadDefaultChatApp = useCallback(async () => { const loadDefaultChatApp = useCallback(async () => {
setLoadingDefault(true); setLoadingDefault(true);
setError(null); setError(null);
try { try {
console.log('[useChatApps] 开始加载默认对话应用...');
const response = await getDefaultChatApp(); const response = await getDefaultChatApp();
// 如果加载所有应用失败,但成功加载了默认应用,将默认应用添加到chatApps数组中 const defaultApp = response.data;
setChatApps(prev => [...prev, response.data]);
setCurrentChatApp(response.data); console.log('[useChatApps] 默认应用加载完成:', {
return response.data; app_id: defaultApp.app_id,
app_name: defaultApp.app_name,
is_default: defaultApp.is_default
});
// 检查是否已经存在相同的应用
setChatApps(prev => {
console.log('[useChatApps] 准备添加默认应用,当前列表长度:', prev.length);
console.log('[useChatApps] 当前列表应用ID:', prev.map(app => app.app_id));
console.log('[useChatApps] 要添加的默认应用ID:', defaultApp.app_id);
const exists = prev.some(app => app.app_id === defaultApp.app_id);
console.log('[useChatApps] 应用是否存在检查结果:', exists);
if (!exists) {
console.log('[useChatApps] 应用不存在,添加到列表');
const newList = [...prev, defaultApp];
console.log('[useChatApps] 添加后的列表长度:', newList.length);
return newList;
}
console.log('[useChatApps] 应用已存在,返回原列表');
return prev;
});
console.log('[useChatApps] 设置当前应用为默认应用');
setCurrentChatApp(defaultApp);
return defaultApp;
} catch (err: any) { } catch (err: any) {
console.error('[useChatApps] 加载默认对话应用失败:', err); console.error('[useChatApps] 加载默认对话应用失败:', err);
setError(err.message || '加载默认对话应用失败'); setError(err.message || '加载默认对话应用失败');
@@ -70,14 +105,26 @@ export function useChatApps() {
* @param onAppChanged 切换完成后的回调函数 * @param onAppChanged 切换完成后的回调函数
*/ */
const handleChatAppChange = useCallback((appId: string, onAppChanged?: (app: ChatApp) => void) => { const handleChatAppChange = useCallback((appId: string, onAppChanged?: (app: ChatApp) => void) => {
console.log('[useChatApps] 开始切换对话应用,目标ID:', appId);
const app = chatApps.find(chatApp => chatApp.app_id === appId); const app = chatApps.find(chatApp => chatApp.app_id === appId);
console.log('[useChatApps] 查找应用结果:', app ? {
app_id: app.app_id,
app_name: app.app_name,
found: true
} : { found: false });
if (app) { if (app) {
console.log('[useChatApps] 切换对话应用:', app.app_name, app.app_id); console.log('[useChatApps] 找到应用,准备切换:', app.app_name, app.app_id);
setCurrentChatApp(app); setCurrentChatApp(app);
console.log('[useChatApps] 设置currentChatApp完成');
// 切换应用后,调用回调函数 // 切换应用后,调用回调函数
if (onAppChanged) { if (onAppChanged) {
console.log('[useChatApps] 调用onAppChanged回调');
onAppChanged(app); onAppChanged(app);
} }
} else {
console.warn('[useChatApps] 未找到应用ID:', appId, '可用应用:', chatApps.map(a => ({ id: a.app_id, name: a.app_name })));
} }
}, [chatApps]); }, [chatApps]);
@@ -91,21 +138,30 @@ export function useChatApps() {
try { try {
try { try {
console.log('[useChatApps] ==================== 开始初始化对话应用 ====================');
// 尝试加载可用应用列表 // 尝试加载可用应用列表
console.log('[useChatApps] 步骤1: 调用loadChatApps()加载我的应用列表...');
const apps = await loadChatApps(); const apps = await loadChatApps();
console.log('[useChatApps] 步骤1完成: 加载到', apps.length, '个应用');
if (apps.length > 0) { if (apps.length > 0) {
// 查找默认应用 // 查找默认应用
const defaultApp = apps.find((item) => item.is_default) || apps[0]; const defaultApp = apps.find((item) => item.is_default) || apps[0];
console.log('[useChatApps] 步骤2: 找到默认应用:', defaultApp?.app_name);
console.log('[useChatApps] 步骤2: 设置当前应用为:', defaultApp?.app_name);
setCurrentChatApp(defaultApp); setCurrentChatApp(defaultApp);
console.log('[useChatApps] ==================== 初始化完成(路径1 ====================');
} else { } else {
// 如果没有配置应用,尝试获取默认应用 // 如果没有配置应用,尝试获取默认应用
console.log('[useChatApps] 应用列表为空,调用loadDefaultChatApp()...');
await loadDefaultChatApp(); await loadDefaultChatApp();
console.log('[useChatApps] ==================== 初始化完成(路径2 ====================');
} }
} catch (err) { } catch (err) {
// 加载应用列表失败,尝试获取默认应用 // 加载应用列表失败,尝试获取默认应用
console.warn('[useChatApps] 加载应用列表失败,尝试获取默认应用:', err); console.warn('[useChatApps] 加载应用列表失败,尝试获取默认应用:', err);
await loadDefaultChatApp(); await loadDefaultChatApp();
console.log('[useChatApps] ==================== 初始化完成(路径3 ====================');
} }
} catch (err: any) { } catch (err: any) {
console.error('[useChatApps] 初始化失败:', err); console.error('[useChatApps] 初始化失败:', err);
+34 -14
View File
@@ -1002,8 +1002,12 @@ export default function RolePermissions() {
}; };
extractDisplayPermissions(routesWithPerms); extractDisplayPermissions(routesWithPerms);
// 从 getRolePermissions 结果中提取已分配的权限ID(原始ID) // v3.5: 修复BUG - 只筛选 grant_type=GRANT 的权限
const assignedPermissionIds = rolePermissions.map(p => p.permission_id); // 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); // 用于显示 setRoutePermissionsMap(displayPermMap); // 用于显示
@@ -1282,6 +1286,7 @@ export default function RolePermissions() {
}; };
// 保存权限 - v3.3: 同时保存路由权限和API权限,仅省级管理员可操作 // 保存权限 - v3.3: 同时保存路由权限和API权限,仅省级管理员可操作
// v3.5: 增加事务性操作和回滚机制
const handleSavePermissions = async () => { const handleSavePermissions = async () => {
if (!selectedRole) return; if (!selectedRole) return;
@@ -1292,6 +1297,11 @@ export default function RolePermissions() {
} }
setSavingPermissions(true); setSavingPermissions(true);
// v3.5: 开始事务性操作,保存原始状态以便回滚
const originalRouteIds = [...selectedRouteIds];
const originalPermissionIds = [...selectedPermissionIds];
try { try {
// 1. 保存路由权限 // 1. 保存路由权限
const routeResult = await updateRoleRoutePermissions(selectedRole.id, selectedRouteIds); const routeResult = await updateRoleRoutePermissions(selectedRole.id, selectedRouteIds);
@@ -1306,25 +1316,35 @@ export default function RolePermissions() {
return; return;
} }
// v3.5: 只有在路由权限保存成功后才保存API权限
// 2. 保存API权限(如果有选中的权限) // 2. 保存API权限(如果有选中的权限)
let permResult;
if (selectedPermissionIds.length > 0) { if (selectedPermissionIds.length > 0) {
const permResult = await saveRoleApiPermissions(selectedRole.id, selectedPermissionIds); permResult = await saveRoleApiPermissions(selectedRole.id, selectedPermissionIds);
if (!permResult.success) {
toastService.error(permResult.message);
return;
}
toastService.success(`路由权限保存成功,${permResult.message}`);
} else { } else {
// 没有选中API权限时,清空该角色的所有API权限 // 没有选中API权限时,清空该角色的所有API权限
const permResult = await saveRoleApiPermissions(selectedRole.id, []); permResult = await saveRoleApiPermissions(selectedRole.id, []);
toastService.success(routeResult.message);
} }
// v3.5: 处理API权限保存失败的情况
if (!permResult.success) {
console.error('API权限保存失败,正在回滚路由权限...');
// 回滚路由权限到原始状态
await updateRoleRoutePermissions(selectedRole.id, originalRouteIds);
toastService.error('权限保存失败,已自动回滚到原始状态');
// 恢复前端状态
setSelectedRouteIds(originalRouteIds);
setSelectedPermissionIds(originalPermissionIds);
return;
}
toastService.success(`路由权限:${routeResult.message} | API权限:${permResult.message}`);
} catch (error) { } catch (error) {
console.error("保存权限失败:", error); console.error("保存权限失败:", error);
toastService.error("保存权限失败"); toastService.error("保存权限失败,已自动回滚到原始状态");
// 发生异常时回滚到原始状态
setSelectedRouteIds(originalRouteIds);
setSelectedPermissionIds(originalPermissionIds);
} finally { } finally {
setSavingPermissions(false); setSavingPermissions(false);
} }