## 1. 需求背景
在 Dify 智能体开发平台中,不同角色的用户应看到不同的导航栏菜单。例如:
- 普通成员(normal)不能看到"工作室(Studio)"、"知识库(Knowledge)"、"工具(Tools)"等高级功能。
- 管理员(admin)、所有者(owner)、编辑(editor)、知识库管理员(dataset_operator)可以看到更多菜单。
## 2. 角色与权限说明
- **owner**:工作区所有者,最高权限
- **admin**:管理员,高权限
- **editor**:编辑,可管理应用
- **dataset_operator**:知识库管理员,仅可管理知识库
- **normal**:普通成员,仅可使用应用
## 3. 主要实现过程与具体文件修改
### 3.1 角色判断逻辑
**文件:**
- `web/context/app-context.tsx`
**关键代码:**
```ts
const isCurrentWorkspaceEditor = useMemo(() => ['owner', 'admin', 'editor'].includes(currentWorkspace.role), [currentWorkspace.role])
const isCurrentWorkspaceDatasetOperator = useMemo(() => currentWorkspace.role === 'dataset_operator', [currentWorkspace.role])
```
**说明:**
- 通过 `currentWorkspace.role` 获取当前用户角色。
- 统一用变量 `isCurrentWorkspaceEditor`、`isCurrentWorkspaceDatasetOperator` 进行角色判断,便于后续条件渲染和权限控制。
### 3.2 导航栏菜单的条件渲染
**文件:**
- `web/app/components/header/index.tsx`
**关键改动:**
将 Knowledge(DatasetNav)、Studio(AppNav)、Tools(ToolsNav)等菜单的渲染条件统一为:
```tsx
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <AppNav />}
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <ToolsNav className={navClassName} />}
```
**目的与效果:**
- 只有 owner、admin、editor、dataset_operator 能看到这些菜单。
- normal 角色用户不会看到这些高级菜单。
- 该逻辑在移动端和桌面端导航栏都做了同步处理。
### 3.3 路由页面的权限控制(防止手动输入绕过)
**文件:**
- `web/app/(commonLayout)/tools/page.tsx`
**关键改动:**
在页面组件内增加 useEffect 跳转逻辑:
```tsx
useEffect(() => {
if (isCurrentWorkspaceDatasetOperator)
return router.replace('/datasets')
if (currentWorkspace?.role === 'normal')
return router.replace('/apps')
}, [isCurrentWorkspaceDatasetOperator, router, currentWorkspace])
```
**目的与效果:**
- 即使 normal 用户手动输入 `/tools` 也会被自动跳转到 `/apps`,无法访问 Tools 页面。
- dataset_operator 角色会被跳转到 `/datasets`。
- 保障了前端页面的越权访问安全。
### 3.4 国际化与菜单名称
**文件:**
- `web/i18n/zh-Hans/common.ts` 等多语言文件
**关键内容:**
```ts
menus: {
apps: '工作室',
datasets: '知识库',
tools: '工具',
// ...
}
```
**说明:**
- 菜单名称通过 i18n 文件配置,保证多语言一致性。
## 4. 技术要点与注意事项
- **前端渲染控制**:所有菜单的显示都应基于角色变量做条件渲染,避免硬编码。
- **路由守卫**:重要页面需在页面组件内用 useEffect 做二次跳转,防止用户手动输入 URL 越权访问。
- **国际化**:菜单名称通过 i18n 文件配置,保证多语言一致性。
- **角色变量复用**:统一用 `isCurrentWorkspaceEditor`、`isCurrentWorkspaceDatasetOperator` 等变量,便于维护和扩展。
- **代码复用性**:所有角色判断和菜单渲染逻辑集中在 context 和 header 组件,便于后续维护。
## 5. 代码片段示例
#### 导航栏菜单条件渲染(web/app/components/header/index.tsx)
```tsx
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <ToolsNav className={navClassName} />}
```
#### 路由页面权限跳转(web/app/(commonLayout)/tools/page.tsx)
```tsx
useEffect(() => {
if (isCurrentWorkspaceDatasetOperator)
return router.replace('/datasets')
if (currentWorkspace?.role === 'normal')
return router.replace('/apps')
}, [isCurrentWorkspaceDatasetOperator, router, currentWorkspace])
```
## 6. 结论
通过在 `web/context/app-context.tsx` 统一角色变量、在 `web/app/components/header/index.tsx` 统一菜单条件渲染、在 `web/app/(commonLayout)/tools/page.tsx` 做路由守卫,配合 i18n 国际化,Dify 平台实现了导航栏菜单的精细化角色权限控制,既保证了安全性,也提升了代码的可维护性和扩展性。