修复权限操作
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 || [];
|
||||
|
||||
|
||||
@@ -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<ChatSidebarRef, ChatSidebarProps>(({
|
||||
token: { colorBgContainer, borderRadiusLG },
|
||||
} = 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 =>
|
||||
conv.name.toLowerCase().includes(searchValue.toLowerCase())
|
||||
@@ -302,7 +326,7 @@ const ChatSidebar = forwardRef<ChatSidebarRef, ChatSidebarProps>(({
|
||||
placeholder="选择对话应用"
|
||||
size="small"
|
||||
>
|
||||
{(chatApps || []).map(app => (
|
||||
{uniqueChatApps.map(app => (
|
||||
<Select.Option key={app.app_id} value={app.app_id}>
|
||||
{app.app_name}
|
||||
</Select.Option>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user