From 0a3c1a27359a7c510ed2f9984d7e4f40756fcd50 Mon Sep 17 00:00:00 2001 From: Wenyan Date: Mon, 8 Dec 2025 21:36:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9D=83=E9=99=90=E6=93=8D?= =?UTF-8?q?=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/role-permissions/role-permissions.ts | 31 +++++---- app/components/dify-chat/index.tsx | 37 ++++++++++- app/components/dify-chat/sidebar.tsx | 28 +++++++- app/hooks/dify-chat-apps/useChatApps.ts | 70 ++++++++++++++++++-- app/routes/role-permissions._index.tsx | 48 ++++++++++---- 5 files changed, 175 insertions(+), 39 deletions(-) diff --git a/app/api/role-permissions/role-permissions.ts b/app/api/role-permissions/role-permissions.ts index 403657c..c0e5124 100644 --- a/app/api/role-permissions/role-permissions.ts +++ b/app/api/role-permissions/role-permissions.ts @@ -399,22 +399,25 @@ export async function getRoleRoutesWithPermissions(roleId: number): Promise<{ return ids; }; - // 收集所有已选中的权限ID(从当前角色的权限中) - const collectPermissionIds = (routes: RouteInfo[]): number[] => { - let ids: number[] = []; - routes.forEach(route => { - if (route.permissions) { - ids = ids.concat(route.permissions.map(p => p.id)); - } - if (route.children) { - ids = ids.concat(collectPermissionIds(route.children)); - } - }); - return ids; - }; + // v3.5: 修复BUG - 删除了 collectPermissionIds 函数 + // BUG说明:从路由元数据收集权限ID是错误的,因为路由的permissions数组包含的仅仅是权限定义, + // 而不是实际授权状态。实际授权状态应该从 `/api/v3/rbac/role-permissions` 接口获取。 + // + // 之前的错误实现: + // const collectPermissionIds = (routes: RouteInfo[]): number[] => { + // let ids: number[] = []; + // routes.forEach(route => { + // if (route.permissions) { + // ids = ids.concat(route.permissions.map(p => p.id)); + // } + // }); + // return ids; + // }; + // + // 修复方案:返回空数组,由调用方使用 getRolePermissions() 获取实际授权数据 const selectedRouteIds = collectRouteIds(mappedRoutes); - const selectedPermissionIds = collectPermissionIds(mappedRoutes); + const selectedPermissionIds: number[] = []; // v3.5: 修复BUG - 返回空数组 return { routes: mappedRoutes, diff --git a/app/components/dify-chat/index.tsx b/app/components/dify-chat/index.tsx index ad46076..032b2d7 100644 --- a/app/components/dify-chat/index.tsx +++ b/app/components/dify-chat/index.tsx @@ -60,6 +60,26 @@ export default function Chat() { handleChatAppChange: originalHandleChatAppChange, } = 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 { conversationList, @@ -405,14 +425,27 @@ export default function Chat() { * 切换应用后刷新加载对应的会话列表 */ 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) => { - console.log('🔄 [Chat] 应用已切换到:', app.app_name, '开始刷新会话列表...'); + console.log('✅ [Chat] originalHandleChatAppChange回调触发,传入应用:', app.app_name); try { // 重新获取会话列表,传入新的应用ID获取该应用的会话 + console.log('📋 [Chat] 开始获取新应用的会话列表...'); const conversationData = await fetchConversations(app.app_id); const conversations = (conversationData as any).data || []; diff --git a/app/components/dify-chat/sidebar.tsx b/app/components/dify-chat/sidebar.tsx index 5ba0205..b15d53f 100644 --- a/app/components/dify-chat/sidebar.tsx +++ b/app/components/dify-chat/sidebar.tsx @@ -10,7 +10,7 @@ import { SearchOutlined, } from '@ant-design/icons'; 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 { ConversationItem } from '~/api/dify-chat'; import { deleteConversation, renameConversation } from '~/api/dify-chat'; @@ -68,6 +68,30 @@ const ChatSidebar = forwardRef(({ token: { colorBgContainer, borderRadiusLG }, } = theme.useToken(); + // 去重:防止后端返回重复的应用数据 + const uniqueChatApps = useMemo(() => { + const appMap = new Map(); + 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 => conv.name.toLowerCase().includes(searchValue.toLowerCase()) @@ -302,7 +326,7 @@ const ChatSidebar = forwardRef(({ placeholder="选择对话应用" size="small" > - {(chatApps || []).map(app => ( + {uniqueChatApps.map(app => ( {app.app_name} diff --git a/app/hooks/dify-chat-apps/useChatApps.ts b/app/hooks/dify-chat-apps/useChatApps.ts index cbce67d..589f8bf 100644 --- a/app/hooks/dify-chat-apps/useChatApps.ts +++ b/app/hooks/dify-chat-apps/useChatApps.ts @@ -28,9 +28,17 @@ export function useChatApps() { const loadChatApps = useCallback(async () => { setLoadingChatApps(true); setError(null); - + try { + console.log('[useChatApps] 开始加载对话应用列表...'); 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); return response.data; } catch (err: any) { @@ -48,13 +56,40 @@ export function useChatApps() { const loadDefaultChatApp = useCallback(async () => { setLoadingDefault(true); setError(null); - + try { + console.log('[useChatApps] 开始加载默认对话应用...'); const response = await getDefaultChatApp(); - // 如果加载所有应用失败,但成功加载了默认应用,将默认应用添加到chatApps数组中 - setChatApps(prev => [...prev, response.data]); - setCurrentChatApp(response.data); - return response.data; + const defaultApp = response.data; + + console.log('[useChatApps] 默认应用加载完成:', { + 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) { console.error('[useChatApps] 加载默认对话应用失败:', err); setError(err.message || '加载默认对话应用失败'); @@ -70,14 +105,26 @@ export function useChatApps() { * @param onAppChanged 切换完成后的回调函数 */ const handleChatAppChange = useCallback((appId: string, onAppChanged?: (app: ChatApp) => void) => { + console.log('[useChatApps] 开始切换对话应用,目标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) { - console.log('[useChatApps] 切换对话应用:', app.app_name, app.app_id); + console.log('[useChatApps] 找到应用,准备切换:', app.app_name, app.app_id); setCurrentChatApp(app); + console.log('[useChatApps] 设置currentChatApp完成'); + // 切换应用后,调用回调函数 if (onAppChanged) { + console.log('[useChatApps] 调用onAppChanged回调'); onAppChanged(app); } + } else { + console.warn('[useChatApps] 未找到应用ID:', appId, '可用应用:', chatApps.map(a => ({ id: a.app_id, name: a.app_name }))); } }, [chatApps]); @@ -91,21 +138,30 @@ export function useChatApps() { try { try { + console.log('[useChatApps] ==================== 开始初始化对话应用 ===================='); // 尝试加载可用应用列表 + console.log('[useChatApps] 步骤1: 调用loadChatApps()加载我的应用列表...'); const apps = await loadChatApps(); + console.log('[useChatApps] 步骤1完成: 加载到', apps.length, '个应用'); if (apps.length > 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); + console.log('[useChatApps] ==================== 初始化完成(路径1) ===================='); } else { // 如果没有配置应用,尝试获取默认应用 + console.log('[useChatApps] 应用列表为空,调用loadDefaultChatApp()...'); await loadDefaultChatApp(); + console.log('[useChatApps] ==================== 初始化完成(路径2) ===================='); } } catch (err) { // 加载应用列表失败,尝试获取默认应用 console.warn('[useChatApps] 加载应用列表失败,尝试获取默认应用:', err); await loadDefaultChatApp(); + console.log('[useChatApps] ==================== 初始化完成(路径3) ===================='); } } catch (err: any) { console.error('[useChatApps] 初始化失败:', err); diff --git a/app/routes/role-permissions._index.tsx b/app/routes/role-permissions._index.tsx index add48cc..5fc7340 100644 --- a/app/routes/role-permissions._index.tsx +++ b/app/routes/role-permissions._index.tsx @@ -1002,8 +1002,12 @@ export default function RolePermissions() { }; extractDisplayPermissions(routesWithPerms); - // 从 getRolePermissions 结果中提取已分配的权限ID(原始ID) - const assignedPermissionIds = rolePermissions.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); // 用于显示 @@ -1282,6 +1286,7 @@ export default function RolePermissions() { }; // 保存权限 - v3.3: 同时保存路由权限和API权限,仅省级管理员可操作 + // v3.5: 增加事务性操作和回滚机制 const handleSavePermissions = async () => { if (!selectedRole) return; @@ -1292,6 +1297,11 @@ export default function RolePermissions() { } setSavingPermissions(true); + + // v3.5: 开始事务性操作,保存原始状态以便回滚 + const originalRouteIds = [...selectedRouteIds]; + const originalPermissionIds = [...selectedPermissionIds]; + try { // 1. 保存路由权限 const routeResult = await updateRoleRoutePermissions(selectedRole.id, selectedRouteIds); @@ -1306,25 +1316,35 @@ export default function RolePermissions() { return; } + // v3.5: 只有在路由权限保存成功后才保存API权限 // 2. 保存API权限(如果有选中的权限) + let permResult; if (selectedPermissionIds.length > 0) { - const permResult = await saveRoleApiPermissions(selectedRole.id, selectedPermissionIds); - - if (!permResult.success) { - toastService.error(permResult.message); - return; - } - - toastService.success(`路由权限保存成功,${permResult.message}`); + permResult = await saveRoleApiPermissions(selectedRole.id, selectedPermissionIds); } else { // 没有选中API权限时,清空该角色的所有API权限 - const permResult = await saveRoleApiPermissions(selectedRole.id, []); - - toastService.success(routeResult.message); + permResult = await saveRoleApiPermissions(selectedRole.id, []); } + + // 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) { console.error("保存权限失败:", error); - toastService.error("保存权限失败"); + toastService.error("保存权限失败,已自动回滚到原始状态"); + // 发生异常时回滚到原始状态 + setSelectedRouteIds(originalRouteIds); + setSelectedPermissionIds(originalPermissionIds); } finally { setSavingPermissions(false); }