CzRger 3 mesi fa
parent
commit
658596cff5

+ 4 - 0
web/app/components/header/account-setting/dept-page/detail-modal/index.module.css

@@ -0,0 +1,4 @@
+.modal {
+  padding: 24px 32px !important;
+  width: 400px !important;
+}

+ 87 - 0
web/app/components/header/account-setting/dept-page/detail-modal/index.tsx

@@ -0,0 +1,87 @@
+'use client'
+import { useCallback, useState } from 'react'
+import { RiCloseLine } from '@remixicon/react'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import Modal from '@/app/components/base/modal'
+import Button from '@/app/components/base/button'
+import { addKnowledge, editKnowledge } from '@/service/common'
+import 'react-multi-email/dist/style.css'
+import Input from '@/app/components/base/input'
+
+const InviteModal = ({
+  transfer,
+  onCancel,
+  onSend,
+}: any) => {
+  const [name, setName] = useState<string>(transfer.row?.name || '')
+  const handleSave = useCallback(async () => {
+    try {
+      let res: any = () => {}
+      if (transfer.mode === 'add') {
+        res = await addKnowledge({
+          url: '/123',
+          body: { name, status: false },
+        })
+      }
+      else {
+        res = await editKnowledge({
+          url: `/123/${transfer.row.id}`,
+          body: { name },
+        })
+      }
+      const { id }: any = res
+      if (id) {
+        onCancel()
+        onSend()
+      }
+    }
+    catch (e) { }
+  }, [name, onCancel, onSend, transfer])
+
+  return (
+    <div className={cn(s.wrap)}>
+      <Modal overflowVisible isShow onClose={() => { }} className={cn(s.modal)}>
+        <div className='mb-2 flex justify-between'>
+          <div className='text-xl font-semibold text-text-primary'>{transfer.mode === 'add' ? '新增' : '编辑'}部门</div>
+          <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={onCancel} />
+        </div>
+        <div>
+          <div className={cn('flex flex-wrap items-center justify-between')}>
+            <div className='shrink-0 py-2 text-sm font-medium leading-[20px] text-text-primary'>
+              上级部门
+            </div>
+            <Input
+              value={name}
+              onChange={e => setName(e.target.value)}
+              className='h-9'
+              placeholder='请输入部门名称'
+            />
+          </div>
+          <div className={cn('flex flex-wrap items-center justify-between')}>
+            <div className='shrink-0 py-2 text-sm font-medium leading-[20px] text-text-primary'>
+              部门名称
+            </div>
+            <Input
+              value={name}
+              onChange={e => setName(e.target.value)}
+              className='h-9'
+              placeholder='请输入部门名称'
+            />
+          </div>
+          <Button
+            tabIndex={0}
+            className='mt-4 w-full'
+            onClick={handleSave}
+            disabled={!name.length}
+            variant='primary'
+          >
+            保存
+          </Button>
+        </div>
+      </Modal>
+    </div>
+  )
+}
+
+export default InviteModal

+ 230 - 0
web/app/components/header/account-setting/dept-page/index.tsx

@@ -0,0 +1,230 @@
+'use client'
+import { useState } from 'react'
+import useSWR from 'swr'
+import dayjs from 'dayjs'
+import 'dayjs/locale/zh-cn'
+import relativeTime from 'dayjs/plugin/relativeTime'
+import DetailModal from './detail-modal'
+import UserModal from './user-modal'
+import { useContext } from 'use-context-selector'
+import { RiAddLine } from '@remixicon/react'
+import { useTranslation } from 'react-i18next'
+import { delKnowledge, fetchDepts } from '@/service/common'
+import I18n from '@/context/i18n'
+import { useAppContext } from '@/context/app-context'
+import LogoEmbeddedChatHeader from '@/app/components/base/logo/logo-embedded-chat-header'
+import { useProviderContext } from '@/context/provider-context'
+import { Plan } from '@/app/components/billing/type'
+import Button from '@/app/components/base/button'
+import UpgradeBtn from '@/app/components/billing/upgrade-btn'
+import { NUM_INFINITE } from '@/app/components/billing/config'
+import { LanguagesSupported } from '@/i18n/language'
+import cn from '@/utils/classnames'
+import Confirm from '@/app/components/base/confirm'
+import useTimestamp from '@/hooks/use-timestamp'
+import { Column as AntdColumn, Space as AntdSpace, Table as AntdTable } from 'antd'
+dayjs.extend(relativeTime)
+
+const DeptsPage = () => {
+  const { t } = useTranslation()
+  const { locale } = useContext(I18n)
+  const { formatTime } = useTimestamp()
+  const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager, systemFeatures } = useAppContext()
+  const { data, mutate }: any = useSWR(
+    {
+      url: '/external_applications',
+      params: {
+        page: 1,
+        limit: 1000,
+      },
+    },
+    fetchDepts,
+  )
+  const [detailModalVisible, setDetailModalVisible] = useState(false)
+  const [userModalVisible, setUserModalVisible] = useState(false)
+  const [transfer, setTransfer] = useState<any>({
+    mode: 'add',
+    row: null,
+  })
+  const deptList = data?.data || []
+  const { plan, enableBilling } = useProviderContext()
+  const isNotUnlimitedMemberPlan = enableBilling && plan.type !== Plan.team && plan.type !== Plan.enterprise
+  const isMemberFull = enableBilling && isNotUnlimitedMemberPlan && deptList.length >= plan.total.teamMembers
+
+  const [showConfirmDelete, setShowConfirmDelete] = useState(false)
+  const [row, setRow] = useState<any>({})
+  const handleDel = async () => {
+    try {
+      await delKnowledge({
+        url: `/external_applications/${row.id}`,
+        body: {},
+      })
+      setShowConfirmDelete(false)
+      mutate()
+    }
+    catch (e) { }
+  }
+  return (
+    <>
+      <div className='flex flex-col'>
+        <div className='mb-4 flex items-center gap-3 rounded-xl border-l-[0.5px] border-t-[0.5px] border-divider-subtle bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 p-3 pr-5'>
+          <LogoEmbeddedChatHeader className='!h-12 !w-12' />
+          <div className='grow'>
+            <div className='system-md-semibold text-text-secondary'>{currentWorkspace?.name}</div>
+            {enableBilling && (
+              <div className='system-xs-medium mt-1 text-text-tertiary'>
+                {isNotUnlimitedMemberPlan
+                  ? (
+                    <div className='flex space-x-1'>
+                      <div>{t('billing.plansCommon.member')}{locale !== LanguagesSupported[1] && deptList.length > 1 && 's'}</div>
+                      <div className=''>{deptList.length}</div>
+                      <div>/</div>
+                      <div>{plan.total.teamMembers === NUM_INFINITE ? t('billing.plansCommon.unlimited') : plan.total.teamMembers}</div>
+                    </div>
+                  )
+                  : (
+                    <div className='flex space-x-1'>
+                      <div>{deptList.length}</div>
+                      <div>{t('billing.plansCommon.memberAfter')}{locale !== LanguagesSupported[1] && deptList.length > 1 && 's'}</div>
+                    </div>
+                  )}
+              </div>
+            )}
+          </div>
+          {isMemberFull && (
+            <UpgradeBtn className='mr-2' loc='member-invite' />
+          )}
+          <Button variant='primary' className={cn('shrink-0')} disabled={!isCurrentWorkspaceManager || isMemberFull}
+            onClick={() => {
+              setTransfer({ mode: 'add', row: null })
+              setDetailModalVisible(true)
+            }}>
+            <RiAddLine className='mr-1 h-4 w-4' />
+            新增
+          </Button>
+        </div>
+        <div className='overflow-visible lg:overflow-visible'>
+          <AntdTable
+            rowKey="id"
+            dataSource={deptList}
+            pagination={false}
+          >
+            <AntdColumn title="部门名称" dataIndex="name" key="name"/>
+            <AntdColumn title="关联用户数量" dataIndex="relation" key="relation" align="center" width={120}/>
+            <AntdColumn title="操作" key="action" align="center" width={100} render={(_: any, record: any) => (
+              <AntdSpace size="middle">
+                {!(record.children?.length > 0) && (
+                  <Button variant='ghost-accent' size='small' className={cn('shrink-0')}
+                    onClick={() => {
+                      setTransfer({
+                        mode: 'user',
+                        row: JSON.parse(JSON.stringify(record)),
+                      })
+                      setUserModalVisible(true)
+                    }}>
+                    关联用户
+                  </Button>
+                )}
+                <Button variant='ghost-accent' size='small' className={cn('shrink-0')} disabled={!isCurrentWorkspaceManager || isMemberFull}
+                  onClick={() => {
+                    setTransfer({
+                      mode: 'edit',
+                      row: JSON.parse(JSON.stringify(record)),
+                    })
+                    setDetailModalVisible(true)
+                  }}>
+                  编辑
+                </Button>
+                {!(record.children?.length > 0) && !(record.relation > 0) && (
+                  <Button variant='ghost' size='small' className={cn('shrink-0 text-red-600')} disabled={!isCurrentWorkspaceManager || isMemberFull} onClick={() => {
+                    setRow(record)
+                    setShowConfirmDelete(true)
+                  }}>
+                    刪除
+                  </Button>
+                )}
+              </AntdSpace>
+            )}/>
+          </AntdTable>
+          {/* <div className='flex min-w-[480px] items-center border-b border-divider-regular py-[7px]'> */}
+          {/*  <div className='system-xs-medium-uppercase shrink-0 grow text-text-tertiary'>部门名称</div> */}
+          {/*  <div className='system-xs-medium-uppercase shrink-0 w-[200px] text-center text-text-tertiary'>关联用户数量</div> */}
+          {/*  <div className='system-xs-medium-uppercase w-[200px] shrink-0 px-3 text-center text-text-tertiary'>操作</div> */}
+          {/* </div> */}
+          {/* <div className='relative min-w-[480px]'> */}
+          {/*  { */}
+          {/*    deptList.map((dept: any) => ( */}
+          {/*      <div key={dept.id} className='flex justify-between border-b border-divider-subtle'> */}
+          {/*        <div className='system-sm-regular shrink-0 grow py-2 text-text-secondary'>{dept.name}</div> */}
+          {/*        <div className='system-sm-regular shrink-0 w-[200px] py-2 text-center text-text-secondary'>0</div> */}
+          {/*        <div className='flex w-[200px] shrink-0 items-center justify-center'> */}
+          {/*          <Button variant='ghost-accent' size='small' className={cn('shrink-0')} disabled={!isCurrentWorkspaceManager || isMemberFull} */}
+          {/*            onClick={() => { */}
+          {/*              setTransfer({ */}
+          {/*                mode: 'user', */}
+          {/*                row: JSON.parse(JSON.stringify(dept)), */}
+          {/*              }) */}
+          {/*              setUserModalVisible(true) */}
+          {/*            }}> */}
+          {/*            关联用户 */}
+          {/*          </Button> */}
+          {/*          <Button variant='ghost-accent' size='small' className={cn('shrink-0')} disabled={!isCurrentWorkspaceManager || isMemberFull} */}
+          {/*            onClick={() => { */}
+          {/*              setTransfer({ */}
+          {/*                mode: 'edit', */}
+          {/*                row: JSON.parse(JSON.stringify(dept)), */}
+          {/*              }) */}
+          {/*              setDetailModalVisible(true) */}
+          {/*            }}> */}
+          {/*            编辑 */}
+          {/*          </Button> */}
+          {/*          <Button variant='ghost' size='small' className={cn('shrink-0 text-red-600')} disabled={!isCurrentWorkspaceManager || isMemberFull} onClick={() => { */}
+          {/*            setRow(dept) */}
+          {/*            setShowConfirmDelete(true) */}
+          {/*          }}> */}
+          {/*            刪除 */}
+          {/*          </Button> */}
+          {/*        </div> */}
+          {/*      </div> */}
+          {/*    )) */}
+          {/*  } */}
+          {/* </div> */}
+        </div>
+      </div>
+      {
+        detailModalVisible && (
+          <DetailModal
+            transfer={transfer}
+            onCancel={() => setDetailModalVisible(false)}
+            onSend={() => {
+              mutate()
+            }}
+          />
+        )
+      }
+      {
+        userModalVisible && (
+          <UserModal
+            transfer={transfer}
+            onCancel={() => {
+              setUserModalVisible(false)
+              mutate()
+            }}
+            onSend={() => mutate()}
+          />
+        )
+      }
+      {showConfirmDelete && (
+        <Confirm
+          title="删除确认"
+          content={`请确认是否删除${row.name}?`}
+          isShow={showConfirmDelete}
+          onConfirm={handleDel}
+          onCancel={() => setShowConfirmDelete(false)}
+        />
+      )}
+    </>
+  )
+}
+
+export default DeptsPage

+ 237 - 0
web/app/components/header/account-setting/dept-page/user-modal.tsx

@@ -0,0 +1,237 @@
+'use client'
+import React, { useCallback, useEffect, useState } from 'react'
+import { RiAddLine, RiCloseLine, RiRefreshLine, RiSearchLine } from '@remixicon/react'
+import Modal from '@/app/components/base/modal'
+import Button from '@/app/components/base/button'
+import { delCorpus, fetchIntentType, fetchTypes } from '@/service/common'
+import 'react-multi-email/dist/style.css'
+import Input from '@/app/components/base/input'
+import useSWR from 'swr'
+import cn from '@/utils/classnames'
+import Confirm from '@/app/components/base/confirm'
+import Pagination from '@/app/components/base/pagination'
+import { SimpleSelect } from '@/app/components/base/select'
+
+const TypeModal = ({
+  onCancel,
+  onSend,
+}: any) => {
+  const [page, setPage] = React.useState<number>(0)
+  const [limit, setLimit] = useState<number>(10)
+  const [name, setName] = useState('')
+  const [query, setQuery] = useState<any>({})
+  const { data, mutate }: any = useSWR(
+    {
+      url: '/xxx',
+      params: {
+        page: page + 1,
+        limit,
+        ...query,
+      },
+    },
+    fetchIntentType,
+  )
+  const list: any = data?.data || []
+  const total = data?.total || 0
+  const handleSearch = (reset = false) => {
+    if (reset)
+      setIntentType('')
+
+    const params: any = {}
+    if (intentType)
+      params.intentType = intentType
+    setQuery(params)
+    setPage(0)
+  }
+  useEffect(() => {
+    mutate()
+  }, [page, limit])
+  const [showConfirmDelete, setShowConfirmDelete] = useState(false)
+  const [row, setRow] = useState<any>({})
+  const handleDel = async () => {
+    try {
+      await delCorpus({
+        url: `/tags/${row.id}`,
+        body: {},
+      })
+      setShowConfirmDelete(false)
+    }
+    catch (e) { }
+  }
+  const [showDetail, setShowDetail] = useState(false)
+  const [mode, setMode] = useState<any>('add')
+  const [editIntentType, setEditIntentType] = useState('')
+  const [selectUser, setSelectUser] = useState<any>('')
+  const [userOptions, setUserOptions] = useState<any>([])
+  useEffect(() => {
+    fetchTypes({
+      url: '/tags/page',
+      params: {
+        page: 1,
+        limit: 1000,
+        tag_type: 'knowledge_category',
+      },
+    }).then((res: any) => {
+      setUserOptions(res.data.map((v: any) => ({ name: v.name, value: v.id })) || [])
+    })
+  }, [])
+  const handleSave = useCallback(async () => {
+    // try {
+    //   let res
+    //   if (transfer.mode === 'add') {
+    //     res = await addCorpus({
+    //       url: '/xxx',
+    //       body: { name, type: 'knowledge_category' },
+    //     })
+    //   }
+    //   else {
+    //     res = await editCorpus({
+    //       url: '/xxx',
+    //       body: { name },
+    //     })
+    //   }
+    //   const { id }: any = res
+    //   if (id) {
+    //     onCancel()
+    //     onSend()
+    //   }
+    // }
+    // catch (e) { }
+  }, [mode, editIntentType, onCancel, onSend])
+  return (
+    <div>
+      <Modal overflowVisible isShow onClose={() => { }} className="p-[24px 32px] w-[800px] max-w-[800px]">
+        <div className='mb-2 flex justify-between'>
+          <div className='text-xl font-semibold text-text-primary'>关联用户</div>
+          <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={onCancel} />
+        </div>
+        <div className='flex h-[600px] flex-col'>
+          <div className="flex items-center gap-2">
+            <div className="flex shrink-0 items-center text-gray-500">
+              用户名称
+              <Input
+                className="ml-2"
+                showClearIcon
+                wrapperClassName='!w-[200px]'
+                value={name}
+                onChange={e => setName(e.target.value)}
+                onClear={() => setName('')}
+              />
+            </div>
+            <Button variant='primary' className={cn('ml-auto shrink-0')} onClick={() => {
+              handleSearch(false)
+            }}>
+              <RiSearchLine className='mr-1 h-4 w-4' />
+              搜索
+            </Button>
+            <Button variant='primary' className={cn('shrink-0')} onClick={() => {
+              handleSearch(true)
+            }}>
+              <RiRefreshLine className='mr-1 h-4 w-4' />
+              重置
+            </Button>
+          </div>
+          <div className="mt-2">
+            <Button variant='primary' className={cn('shrink-0')}
+              onClick={() => {
+                setMode('add')
+                setEditIntentType('')
+                setShowDetail(true)
+              }}>
+              <RiAddLine className='mr-1 h-4 w-4' />
+              添加用户
+            </Button>
+          </div>
+          <div className="flex-1">
+            <div className='relative flex h-full w-full flex-col'>
+              <div className='relative grow overflow-x-auto'>
+                <table className={'mt-3 w-full min-w-[700px] max-w-full border-collapse border-0 text-sm'}>
+                  <thead className="h-8 border-b border-divider-subtle text-xs font-medium uppercase leading-8 text-text-tertiary">
+                    <tr>
+                      <td>用户名称</td>
+                      <td className="w-[120px] text-center">操作</td>
+                    </tr>
+                  </thead>
+                  <tbody className="text-text-secondary">
+                    {list.map((item: any) => (
+                      <tr
+                        key={item.id}
+                        className={'h-8 border-b border-divider-subtle hover:bg-background-default-hover'}
+                      >
+                        <td>{item.name}</td>
+                        <td className="flex justify-center gap-2">
+                          <Button variant='ghost' size='small' className={cn('shrink-0 text-red-600')} onClick={() => {
+                            setRow(item)
+                            setShowConfirmDelete(true)
+                          }}>
+                            解除关联
+                          </Button>
+                        </td>
+                      </tr>
+                    ))}
+                  </tbody>
+                </table>
+              </div>
+              {/* Show Pagination only if the total is more than the limit */}
+              {total && (
+                <Pagination
+                  total={total}
+                  limit={limit}
+                  onLimitChange={setLimit}
+                  current={page}
+                  onChange={setPage}
+                  className='w-full shrink-0 px-0 pb-0'
+                />
+              )}
+            </div>
+          </div>
+        </div>
+      </Modal>
+      {showConfirmDelete && (
+        <Confirm
+          title="删除确认"
+          content={`请确认是否删除${row.name}?`}
+          isShow={showConfirmDelete}
+          onConfirm={handleDel}
+          onCancel={() => setShowConfirmDelete(false)}
+        />
+      )}
+      {
+        showDetail && (
+          <Modal overflowVisible isShow onClose={() => { }} className="p-[24px 32px] w-[400px]">
+            <div className='mb-2 flex justify-between'>
+              <div className='text-xl font-semibold text-text-primary'>添加用户</div>
+              <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={() => setShowDetail(false)} />
+            </div>
+            <div>
+              <div className={cn('flex flex-wrap items-center justify-between py-4')}>
+                <div className='shrink-0 py-2 text-sm font-medium leading-[20px] text-text-primary'>
+                  选择用户
+                </div>
+                <div className='w-full'>
+                  <SimpleSelect
+                    defaultValue={selectUser}
+                    onSelect={(i) => { setSelectUser(i.value) }}
+                    items={userOptions}
+                    allowSearch={false}
+                  />
+                </div>
+              </div>
+              <Button
+                tabIndex={0}
+                className='w-full'
+                onClick={handleSave}
+                disabled={!editIntentType.length}
+                variant='primary'
+              >
+                保存
+              </Button>
+            </div>
+          </Modal>
+        )
+      }
+    </div>
+  )
+}
+
+export default TypeModal

+ 9 - 1
web/app/components/header/account-setting/index.tsx

@@ -5,7 +5,7 @@ import {
   RiBook3Fill,
   RiBook3Line,
   RiBrain2Fill,
-  RiBrain2Line,
+  RiBrain2Line, RiBuildingFill, RiBuildingLine,
   RiCloseLine,
   RiColorFilterFill,
   RiColorFilterLine,
@@ -37,6 +37,7 @@ 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'
+import DeptsPage from './dept-page'
 
 const iconClassName = `
   w-5 h-5 mr-2
@@ -118,6 +119,12 @@ export default function AccountSetting({
         icon: <RiBook3Line className={iconClassName} />,
         activeIcon: <RiBook3Fill className={iconClassName} />,
       },
+      {
+        key: 'dept',
+        name: '部门',
+        icon: <RiBuildingLine className={iconClassName} />,
+        activeIcon: <RiBuildingFill className={iconClassName} />,
+      },
     ].filter(item => !!item.key) as GroupItem[]
   })()
 
@@ -240,6 +247,7 @@ export default function AccountSetting({
               {activeMenu === 'language' && <LanguagePage />}
               {activeMenu === 'type' && <TypesPage />}
               {activeMenu === 'knowledge' && <KnowledgesPage />}
+              {activeMenu === 'dept' && <DeptsPage />}
             </div>
           </div>
         </div>

+ 20 - 10
web/app/components/header/index.tsx

@@ -86,11 +86,16 @@ const Header = () => {
       {
         !isMobile && (
           <div className='flex items-center'>
-            {!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />}
-            {!isCurrentWorkspaceDatasetOperator && <AppNav />}
-            {(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
-            {!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
-            {!isCurrentWorkspaceDatasetOperator && <SkillNav className={navClassName} />}
+            {/* {!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />} */}
+            {/* {!isCurrentWorkspaceDatasetOperator && <AppNav />} */}
+            {/* {(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />} */}
+            {/* {!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />} */}
+            {/* {!isCurrentWorkspaceDatasetOperator && <SkillNav className={navClassName} />} */}
+            <ExploreNav className={navClassName} />
+            <AppNav />
+            <DatasetNav />
+            <ToolsNav className={navClassName} />
+            <SkillNav className={navClassName} />
           </div>
         )
       }
@@ -104,11 +109,16 @@ const Header = () => {
       {
         (isMobile && isShowNavMenu) && (
           <div className='flex w-full flex-col gap-y-1 p-2'>
-            {!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />}
-            {!isCurrentWorkspaceDatasetOperator && <AppNav />}
-            {(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
-            {!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
-            {!isCurrentWorkspaceDatasetOperator && <SkillNav className={navClassName} />}
+            {/* {!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />} */}
+            {/* {!isCurrentWorkspaceDatasetOperator && <AppNav />} */}
+            {/* {(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />} */}
+            {/* {!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />} */}
+            {/* {!isCurrentWorkspaceDatasetOperator && <SkillNav className={navClassName} />} */}
+            <ExploreNav className={navClassName} />
+            <AppNav />
+            <DatasetNav />
+            <ToolsNav className={navClassName} />
+            <SkillNav className={navClassName} />
           </div>
         )
       }

+ 37 - 0
web/service/common.ts

@@ -612,3 +612,40 @@ export const delCorpusQuestion = ({ url, body }: any) => {
   console.log('删除训练语料-相似问题', url, body)
   return del(url, { body })
 }
+
+export const fetchDepts = ({ url, params }: any) => {
+  console.log('查询部门列表', params, url)
+  // return get(url, { params })
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      const arr: any = []
+      for (let i = 1; i < 3; i++) {
+        const dept1: any = {
+          id: `${i}`,
+          name: `部门_${i}`,
+          children: [],
+        }
+        for (let j = 1; j < 4; j++) {
+          const dept2: any = {
+            id: `${i}_${j}`,
+            name: `部门_${i}-${j}`,
+            children: [],
+          }
+          for (let k = 1; k < 5; k++) {
+            const dept3: any = {
+              id: `${i}_${j}_${k}`,
+              name: `部门_${i}-${j}-${k}`,
+              relation: k % 2,
+            }
+            dept2.children.push(dept3)
+          }
+          dept1.children.push(dept2)
+        }
+        arr.push(dept1)
+      }
+      resolve({
+        data: arr,
+      })
+    }, 1000)
+  })
+}