dify修改setting根据用户角色显示菜单内容

设置页面在 web/app/components/header/account-setting/index.tsx 中。这个文件,是设置中工作空间相关的设置项。在设置页面中,workplaceGroupItems 数组包含了所有工作空间相关的设置项,包括 provider、members、billing、data-source、api-based-extension、custom 等。

修改此文件:

'use client'

import { useTranslation } from 'react-i18next'

import { useEffect, useRef, useState } from 'react'

import {

  RiBrain2Fill,

  RiBrain2Line,

  RiCloseLine,

  RiColorFilterFill,

  RiColorFilterLine,

  RiDatabase2Fill,

  RiDatabase2Line,

  RiGroup2Fill,

  RiGroup2Line,

  RiMoneyDollarCircleFill,

  RiMoneyDollarCircleLine,

  RiPuzzle2Fill,

  RiPuzzle2Line,

  RiTranslate2,

} from '@remixicon/react'

import Button from '../../base/button'

import MembersPage from './members-page'

import LanguagePage from './language-page'

import ApiBasedExtensionPage from './api-based-extension-page'

import DataSourcePage from './data-source-page'

import ModelProviderPage from './model-provider-page'

import cn from '@/utils/classnames'

import BillingPage from '@/app/components/billing/billing-page'

import CustomPage from '@/app/components/custom/custom-page'

import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'

import { useProviderContext } from '@/context/provider-context'

import { useAppContext } from '@/context/app-context'

import MenuDialog from '@/app/components/header/account-setting/menu-dialog'

import Input from '@/app/components/base/input'

const iconClassName = `

  w-5 h-5 mr-2

`

type IAccountSettingProps = {

  onCancel: () => void

  activeTab?: string

}

type GroupItem = {

  key: string

  name: string

  description?: string

  icon: React.JSX.Element

  activeIcon: React.JSX.Element

}

export default function AccountSetting({

  onCancel,

  activeTab = 'members',

}: IAccountSettingProps) {

  const [activeMenu, setActiveMenu] = useState(activeTab)

  const { t } = useTranslation()

  const { enableBilling, enableReplaceWebAppLogo } = useProviderContext()

  const { isCurrentWorkspaceDatasetOperator, currentWorkspace } = useAppContext()

  // 检查是否为normal角色

  const isNormalRole = currentWorkspace.role === 'normal'

  const workplaceGroupItems = (() => {

    if (isCurrentWorkspaceDatasetOperator)

      return []

   

    // 如果是normal角色,返回空数组(隐藏所有工作空间设置)

    if (isNormalRole)

      return []

   

    return [

      {

        key: 'provider',

        name: t('common.settings.provider'),

        icon: <RiBrain2Line className={iconClassName} />,

        activeIcon: <RiBrain2Fill className={iconClassName} />,

      },

      {

        key: 'members',

        name: t('common.settings.members'),

        icon: <RiGroup2Line className={iconClassName} />,

        activeIcon: <RiGroup2Fill className={iconClassName} />,

      },

      {

        // Use key false to hide this item

        key: enableBilling ? 'billing' : false,

        name: t('common.settings.billing'),

        description: t('billing.plansCommon.receiptInfo'),

        icon: <RiMoneyDollarCircleLine className={iconClassName} />,

        activeIcon: <RiMoneyDollarCircleFill className={iconClassName} />,

      },

      {

        key: 'data-source',

        name: t('common.settings.dataSource'),

        icon: <RiDatabase2Line className={iconClassName} />,

        activeIcon: <RiDatabase2Fill className={iconClassName} />,

      },

      {

        key: 'api-based-extension',

        name: t('common.settings.apiBasedExtension'),

        icon: <RiPuzzle2Line className={iconClassName} />,

        activeIcon: <RiPuzzle2Fill className={iconClassName} />,

      },

      {

        key: (enableReplaceWebAppLogo || enableBilling) ? 'custom' : false,

        name: t('custom.custom'),

        icon: <RiColorFilterLine className={iconClassName} />,

        activeIcon: <RiColorFilterFill className={iconClassName} />,

      },

    ].filter(item => !!item.key) as GroupItem[]

  })()

  const media = useBreakpoints()

  const isMobile = media === MediaType.mobile

  const menuItems = [

    {

      key: 'workspace-group',

      name: t('common.settings.workplaceGroup'),

      items: workplaceGroupItems,

    },

    {

      key: 'account-group',

      name: t('common.settings.generalGroup'),

      items: [

        {

          key: 'language',

          name: t('common.settings.language'),

          icon: <RiTranslate2 className={iconClassName} />,

          activeIcon: <RiTranslate2 className={iconClassName} />,

        },

      ],

    },

  ]

  const scrollRef = useRef<HTMLDivElement>(null)

  const [scrolled, setScrolled] = useState(false)

  useEffect(() => {

    const targetElement = scrollRef.current

    const scrollHandle = (e: Event) => {

      const userScrolled = (e.target as HTMLDivElement).scrollTop > 0

      setScrolled(userScrolled)

    }

    targetElement?.addEventListener('scroll', scrollHandle)

    return () => {

      targetElement?.removeEventListener('scroll', scrollHandle)

    }

  }, [])

  const activeItem = [...menuItems[0].items, ...menuItems[1].items].find(item => item.key === activeMenu)

  const [searchValue, setSearchValue] = useState<string>('')

  return (

    <MenuDialog

      show

      onClose={onCancel}

    >

      <div className='mx-auto flex h-[100vh] max-w-[1048px]'>

        <div className='flex w-[44px] flex-col border-r border-divider-burn pl-4 pr-6 sm:w-[224px]'>

          <div className='title-2xl-semi-bold mb-8 mt-6 px-3 py-2 text-text-primary'>{t('common.userProfile.settings')}</div>

          <div className='w-full'>

            {

              menuItems.map(menuItem => (

                <div key={menuItem.key} className='mb-2'>

                  {!isCurrentWorkspaceDatasetOperator && menuItem.items.length > 0 && (

                    <div className='system-xs-medium-uppercase mb-0.5 py-2 pb-1 pl-3 text-text-tertiary'>{menuItem.name}</div>

                  )}

                  <div>

                    {

                      menuItem.items.map(item => (

                        <div

                          key={item.key}

                          className={cn(

                            'mb-0.5 flex h-[37px] cursor-pointer items-center rounded-lg p-1 pl-3 text-sm',

                            activeMenu === item.key ? 'system-sm-semibold bg-state-base-active text-components-menu-item-text-active' : 'system-sm-medium text-components-menu-item-text')}

                          title={item.name}

                          onClick={() => setActiveMenu(item.key)}

                        >

                          {activeMenu === item.key ? item.activeIcon : item.icon}

                          {!isMobile && <div className='truncate'>{item.name}</div>}

                        </div>

                      ))

                    }

                  </div>

                </div>

              ))

            }

          </div>

        </div>

        <div className='relative flex w-[824px]'>

          <div className='absolute -right-11 top-6 z-[9999] flex flex-col items-center'>

            <Button

              variant='tertiary'

              size='large'

              className='px-2'

              onClick={onCancel}

            >

              <RiCloseLine className='h-5 w-5' />

            </Button>

            <div className='system-2xs-medium-uppercase mt-1 text-text-tertiary'>ESC</div>

          </div>

          <div ref={scrollRef} className='w-full overflow-y-auto bg-components-panel-bg pb-4'>

            <div className={cn('sticky top-0 z-20 mx-8 mb-[18px] flex items-center bg-components-panel-bg pb-2 pt-[27px]', scrolled && 'border-b border-divider-regular')}>

              <div className='title-2xl-semi-bold shrink-0 text-text-primary'>

                {activeItem?.name}

                {activeItem?.description && (

                  <div className='system-sm-regular mt-1 text-text-tertiary'>{activeItem?.description}</div>

                )}

              </div>

              {activeItem?.key === 'provider' && (

                <div className='flex grow justify-end'>

                  <Input

                    showLeftIcon

                    wrapperClassName='!w-[200px]'

                    className='!h-8 !text-[13px]'

                    onChange={e => setSearchValue(e.target.value)}

                    value={searchValue}

                  />

                </div>

              )}

            </div>

            <div className='px-4 pt-2 sm:px-8'>

              {activeMenu === 'provider' && <ModelProviderPage searchText={searchValue} />}

              {activeMenu === 'members' && <MembersPage />}

              {activeMenu === 'billing' && <BillingPage />}

              {activeMenu === 'data-source' && <DataSourcePage />}

              {activeMenu === 'api-based-extension' && <ApiBasedExtensionPage />}

              {activeMenu === 'custom' && <CustomPage />}

              {activeMenu === 'language' && <LanguagePage />}

            </div>

          </div>

        </div>

      </div>

    </MenuDialog>

  )

}

总结

已经成功实现了需求!根据角色控制工作空间模块的显示:

已完成的修改:

  1. 修改了设置页面 (web/app/components/header/account-setting/index.tsx):
  • 添加了 currentWorkspace 到 useAppContext 的解构中
  • 添加了 isNormalRole 变量来检查用户是否为 normal 角色
  • 在 workplaceGroupItems 函数中添加了条件判断:如果是 normal 角色,返回空数组
  • 修改了菜单标题的显示条件,只有当菜单项不为空时才显示标题

实现效果:

对于 normal 角色用户:

  • 设置页面中不会显示"工作空间"分组标题
  • 不会显示任何工作空间相关的设置项,包括:
  • 模型提供商 (Provider)
  • 成员管理 (Members)
  • 计费 (Billing)
  • 数据源 (Data Source)
  • API扩展 (API-based Extension)
  • 自定义设置 (Custom)
  • 只会显示"通用"分组下的语言设置

对于 editor 和 admin 角色用户:

  • 设置页面正常显示所有工作空间相关的设置项
  • 可以正常访问所有功能

代码逻辑:

// 第58-59行:添加角色检查
const { isCurrentWorkspaceDatasetOperator, currentWorkspace } = useAppContext()
const isNormalRole = currentWorkspace.role === 'normal'

// 第61-67行:添加normal角色的条件判断
const workplaceGroupItems = (() => {
  if (isCurrentWorkspaceDatasetOperator)
    return []
  
  // 如果是normal角色,返回空数组(隐藏所有工作空间设置)
  if (isNormalRole)
    return []
  
  // 其他代码保持不变...
})()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一路生花工作室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值