diff --git a/docSite/content/zh-cn/docs/development/upgrading/492.md b/docSite/content/zh-cn/docs/development/upgrading/492.md index 9146efbb9..06999a3eb 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/492.md +++ b/docSite/content/zh-cn/docs/development/upgrading/492.md @@ -18,6 +18,7 @@ weight: 799 2. 知识库分块增加自定义分隔符预设值,同时支持自定义换行符分割。 3. 外部变量改名:自定义变量。 并且支持在测试时调试,在分享链接中,该变量直接隐藏。 4. 集合同步时,支持同步修改标题。 +5. 团队成员管理重构,抽离主流 IM SSO(企微、飞书、钉钉),并支持通过自定义 SSO 接入 FastGPT。同时完善与外部系统的成员同步。 ## ⚙️ 优化 diff --git a/packages/global/support/user/team/org/type.d.ts b/packages/global/support/user/team/org/type.d.ts index b25384a6e..f2afac2d5 100644 --- a/packages/global/support/user/team/org/type.d.ts +++ b/packages/global/support/user/team/org/type.d.ts @@ -1,6 +1,6 @@ -import type { TeamPermission } from 'support/permission/user/controller'; +import type { TeamPermission } from '../../../permission/user/controller'; import { ResourcePermissionType } from '../type'; -import { SourceMemberType } from 'support/user/type'; +import { SourceMemberType } from '../../type'; type OrgSchemaType = { _id: string; @@ -8,7 +8,7 @@ type OrgSchemaType = { pathId: string; path: string; name: string; - avatar?: string; + avatar: string; description?: string; updateTime: Date; }; @@ -20,7 +20,12 @@ type OrgMemberSchemaType = { tmbId: string; }; -type OrgType = Omit & { +export type OrgListItemType = OrgSchemaType & { + permission: TeamPermission; + total: number; // members + children orgs +}; + +export type OrgType = Omit & { avatar: string; permission: TeamPermission; members: OrgMemberSchemaType[]; diff --git a/packages/service/support/permission/memberGroup/memberGroupSchema.ts b/packages/service/support/permission/memberGroup/memberGroupSchema.ts index cc4ee05bc..6964785bc 100644 --- a/packages/service/support/permission/memberGroup/memberGroupSchema.ts +++ b/packages/service/support/permission/memberGroup/memberGroupSchema.ts @@ -1,7 +1,6 @@ import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; import { connectionMongo, getMongoModel } from '../../../common/mongo'; import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; -import { GroupMemberCollectionName } from './groupMemberSchema'; const { Schema } = connectionMongo; export const MemberGroupCollectionName = 'team_member_groups'; diff --git a/projects/app/package.json b/projects/app/package.json index b37c1b5ad..2185f6f96 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.9.1", + "version": "4.9.2", "private": false, "scripts": { "dev": "next dev", diff --git a/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx index 7cd017e2d..e0d1b05f4 100644 --- a/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx +++ b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx @@ -24,6 +24,8 @@ import { useUserStore } from '@/web/support/user/useUserStore'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; +import { GroupMemberItemType } from '@fastgpt/global/support/permission/memberGroup/type'; +import { useMount } from 'ahooks'; export type GroupFormType = { members: { @@ -46,26 +48,20 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro return groups.find((item) => item._id === editGroupId); }, [editGroupId, groups]); - const { data: groupMembers } = useRequest2( - () => { - if (editGroupId) return getGroupMembers(editGroupId); - return Promise.resolve(undefined); - }, - { - manual: false, - onSuccess: (data) => { - setMembers(data ?? []); - } - } - ); - const allMembers = useContextSelector(TeamContext, (v) => v.members); const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers); const MemberScrollData = useContextSelector(TeamContext, (v) => v.MemberScrollData); const [hoveredMemberId, setHoveredMemberId] = useState(); const selectedMembersRef = useRef(null); - const [members, setMembers] = useState(groupMembers || []); + const [members, setMembers] = useState([]); + + useMount(async () => { + if (editGroupId) { + const data = await getGroupMembers(editGroupId); + setMembers(data); + } + }); const [searchKey, setSearchKey] = useState(''); const filtered = useMemo(() => { @@ -80,7 +76,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2( async () => { if (!editGroupId || !members.length) return; - console.log(members); + return putUpdateGroup({ groupId: editGroupId, memberList: members @@ -209,7 +205,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro {t('common:chosen') + ': ' + members.length} - {members?.map((member) => { + {members.map((member) => { return ( setHoveredMemberId(member.tmbId)} diff --git a/projects/app/src/pageComponents/account/team/MemberTable.tsx b/projects/app/src/pageComponents/account/team/MemberTable.tsx index d6232c549..994008bc6 100644 --- a/projects/app/src/pageComponents/account/team/MemberTable.tsx +++ b/projects/app/src/pageComponents/account/team/MemberTable.tsx @@ -268,7 +268,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { {member.memberName} {member.status !== 'active' && ( - + {t('account_team:leave')} )} diff --git a/projects/app/src/pageComponents/account/team/OrgManage/OrgInfoModal.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgInfoModal.tsx index c2e49b368..c992fbead 100644 --- a/projects/app/src/pageComponents/account/team/OrgManage/OrgInfoModal.tsx +++ b/projects/app/src/pageComponents/account/team/OrgManage/OrgInfoModal.tsx @@ -7,7 +7,6 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useTranslation } from 'next-i18next'; -import dynamic from 'next/dynamic'; import { useForm } from 'react-hook-form'; export type OrgFormType = { diff --git a/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx index 74e63bd8f..13a3bb204 100644 --- a/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx +++ b/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx @@ -21,7 +21,7 @@ import type React from 'react'; import { useEffect, useMemo, useState } from 'react'; import { useContextSelector } from 'use-context-selector'; import { TeamContext } from '../context'; -import { OrgType } from '@fastgpt/global/support/user/team/org/type'; +import { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type'; import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; export type GroupFormType = { @@ -46,7 +46,7 @@ function OrgMemberManageModal({ refetchOrgs, onClose }: { - currentOrg: OrgType; + currentOrg: OrgListItemType; refetchOrgs: () => void; onClose: () => void; }) { diff --git a/projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx index 080c4eedb..96e2e705a 100644 --- a/projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx +++ b/projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx @@ -1,6 +1,6 @@ import { putMoveOrg } from '@/web/support/user/team/org/api'; import { Button, ModalBody, ModalFooter } from '@chakra-ui/react'; -import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; +import type { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useTranslation } from 'next-i18next'; @@ -15,13 +15,13 @@ function OrgMoveModal({ onClose, onSuccess }: { - movingOrg: OrgType; - orgs: OrgType[]; + movingOrg: OrgListItemType; + orgs: OrgListItemType[]; onClose: () => void; onSuccess: () => void; }) { const { t } = useTranslation(); - const [selectedOrg, setSelectedOrg] = useState(); + const [selectedOrg, setSelectedOrg] = useState(); const { userInfo } = useUserStore(); const team = userInfo?.team!; diff --git a/projects/app/src/pageComponents/account/team/OrgManage/index.tsx b/projects/app/src/pageComponents/account/team/OrgManage/index.tsx index 5b5fc4f81..1c7fe5e53 100644 --- a/projects/app/src/pageComponents/account/team/OrgManage/index.tsx +++ b/projects/app/src/pageComponents/account/team/OrgManage/index.tsx @@ -14,7 +14,7 @@ import { Tr, VStack } from '@chakra-ui/react'; -import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; +import type { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type'; import Avatar from '@fastgpt/web/components/common/Avatar'; import MyIcon from '@fastgpt/web/components/common/Icon'; import type { IconNameType } from '@fastgpt/web/components/common/Icon/type'; @@ -23,9 +23,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useTranslation } from 'next-i18next'; import { useMemo, useState } from 'react'; -import { useContextSelector } from 'use-context-selector'; import MemberTag from '@/components/support/user/team/Info/MemberTag'; -import { TeamContext } from '../context'; import { deleteOrg, deleteOrgMember, @@ -39,10 +37,10 @@ import { defaultOrgForm, type OrgFormType } from './OrgInfoModal'; import dynamic from 'next/dynamic'; import MyBox from '@fastgpt/web/components/common/MyBox'; import Path from '@/components/common/folder/Path'; -import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; +import { ParentIdType, ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant'; import { useSystemStore } from '@/web/common/system/useSystemStore'; -import { delRemoveMember, getTeamMembers } from '@/web/support/user/team/api'; +import { delRemoveMember } from '@/web/support/user/team/api'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; @@ -82,24 +80,16 @@ function ActionButton({ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); const { userInfo, isTeamAdmin } = useUserStore(); - const [searchOrg, setSearchOrg] = useState(''); - const [orgStack, setOrgStack] = useState([]); - const currentOrg = useMemo(() => orgStack[orgStack.length - 1], [orgStack]); - - const [rootOrg, setRootOrg] = useState(); - - const { data: members = [], ScrollData: MemberScrollData } = useScrollPagination(getOrgMembers, { - pageSize: 20, - params: { - orgId: currentOrg?._id ?? rootOrg?._id - }, - refreshDeps: [currentOrg?._id, rootOrg?._id] - }); - const { feConfigs } = useSystemStore(); - const isSyncMember = feConfigs.register_method?.includes('sync'); - const [path, setPath] = useState(''); + + const [searchOrg, setSearchOrg] = useState(''); + + const [parentId, setParentId] = useState(); + const [currentOrg, setCurrentOrg] = useState(); + + // 用于 org 层级 + const [orgStack, setOrgStack] = useState([]); const { data: orgs = [], @@ -107,43 +97,35 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { refresh: refetchOrgs } = useRequest2( () => { - // sync path to orgStack - const splitPath = path.split('/').filter(Boolean); - const orgs = orgStack.filter((o) => splitPath.includes(o.pathId)); - setOrgStack(orgs); - return getOrgList(path); + return getOrgList(parentId); }, { manual: false, - refreshDeps: [userInfo?.team?.teamId, path], - onSuccess: (data) => { - if (!rootOrg) { - setRootOrg(data[0]); - } - } + refreshDeps: [userInfo?.team?.teamId, parentId] } ); const paths = useMemo(() => { + if (!currentOrg) return []; return orgStack .map((org) => { - if (org?.path === '') return; return { parentId: getOrgChildrenPath(org), parentName: org.name }; }) .filter(Boolean) as ParentTreePathItemType[]; - }, [orgStack]); + }, [currentOrg, orgStack]); - const onClickOrg = (org: OrgType) => { + const onClickOrg = (org: OrgListItemType) => { + setParentId(currentOrg?._id); setOrgStack([...orgStack, org]); - setPath(getOrgChildrenPath(org)); + setCurrentOrg(org); }; const [editOrg, setEditOrg] = useState(); - const [manageMemberOrg, setManageMemberOrg] = useState(); - const [movingOrg, setMovingOrg] = useState(); + const [manageMemberOrg, setManageMemberOrg] = useState(); + const [movingOrg, setMovingOrg] = useState(); // Delete org const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({ @@ -157,6 +139,14 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { } }); + const { data: members = [], ScrollData: MemberScrollData } = useScrollPagination(getOrgMembers, { + pageSize: 20, + params: { + orgId: currentOrg?._id + }, + refreshDeps: [currentOrg?._id] + }); + // Delete member const { ConfirmModal: ConfirmDeleteMemberFromOrg, openConfirm: openDeleteMemberFromOrgModal } = useConfirm({ @@ -183,11 +173,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { const searchedOrgs = useMemo(() => { if (!searchOrg) return []; - return orgs - .filter((org) => org.name.includes(searchOrg)) - .map((org) => ({ - ...org - })); + return orgs.filter((org) => org.name.includes(searchOrg)); }, [orgs, searchOrg]); return ( @@ -231,7 +217,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { onClickOrg(org)}> onClickOrg(org)}> - + {org.total} { setEditOrg({ ...defaultOrgForm, - parentId: currentOrg?._id ?? rootOrg?._id + parentId: currentOrg?._id }); }} /> diff --git a/projects/app/src/pageComponents/account/team/context.tsx b/projects/app/src/pageComponents/account/team/context.tsx index b8cf4c88c..b1aa6730b 100644 --- a/projects/app/src/pageComponents/account/team/context.tsx +++ b/projects/app/src/pageComponents/account/team/context.tsx @@ -16,7 +16,6 @@ import { useTranslation } from 'next-i18next'; import { getGroupList } from '@/web/support/user/team/group/api'; import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type'; import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; -import { getOrgList } from '@/web/support/user/team/org/api'; import { OrgType } from '@fastgpt/global/support/user/team/org/type'; const EditInfoModal = dynamic(() => import('./EditInfoModal')); @@ -25,7 +24,6 @@ type TeamModalContextType = { myTeams: TeamTmbItemType[]; members: TeamMemberItemType[]; groups: MemberGroupListType; - orgs: OrgType[]; isLoading: boolean; onSwitchTeam: (teamId: string) => void; setEditTeamData: React.Dispatch>; @@ -33,7 +31,6 @@ type TeamModalContextType = { refetchMembers: () => void; refetchTeams: () => void; refetchGroups: () => void; - refetchOrgs: () => void; teamSize: number; MemberScrollData: ReturnType['ScrollData']; }; @@ -42,7 +39,6 @@ export const TeamContext = createContext({ myTeams: [], groups: [], members: [], - orgs: [], isLoading: false, onSwitchTeam: function (_teamId: string): void { throw new Error('Function not implemented.'); @@ -59,9 +55,6 @@ export const TeamContext = createContext({ refetchGroups: function (): void { throw new Error('Function not implemented.'); }, - refetchOrgs: function (): void { - throw new Error('Function not implemented.'); - }, teamSize: 0, MemberScrollData: () => <> }); @@ -80,15 +73,6 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode }) refreshDeps: [userInfo?._id] }); - const { - data: orgs = [], - loading: isLoadingOrgs, - refresh: refetchOrgs - } = useRequest2(getOrgList, { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - }); - const { data: teamMemberCountData, refresh: refetchTeamMemberCount } = useRequest2( getTeamMemberCount, { @@ -135,16 +119,13 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode }) refreshDeps: [userInfo?.team?.teamId] }); - const isLoading = - isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups || isLoadingOrgs; + const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups; const contextValue = { myTeams, refetchTeams, isLoading, onSwitchTeam, - orgs, - refetchOrgs, // create | update team setEditTeamData, diff --git a/projects/app/src/web/support/user/team/org/api.ts b/projects/app/src/web/support/user/team/org/api.ts index c33a5ced9..825ec5446 100644 --- a/projects/app/src/web/support/user/team/org/api.ts +++ b/projects/app/src/web/support/user/team/org/api.ts @@ -4,13 +4,14 @@ import type { putUpdateOrgData, putUpdateOrgMembersData } from '@fastgpt/global/support/user/team/org/api'; -import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; +import type { OrgListItemType } from '@fastgpt/global/support/user/team/org/type'; import type { putMoveOrgType } from '@fastgpt/global/support/user/team/org/api'; import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type'; +import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; -export const getOrgList = (path: string) => - GET(`/proApi/support/user/team/org/list`, { orgPath: path }); +export const getOrgList = (parentId: ParentIdType) => + GET(`/proApi/support/user/team/org/list`, { parentId }); export const postCreateOrg = (data: postCreateOrgData) => POST('/proApi/support/user/team/org/create', data); diff --git a/projects/app/src/web/support/user/useUserStore.ts b/projects/app/src/web/support/user/useUserStore.ts index a37715fac..d0247f425 100644 --- a/projects/app/src/web/support/user/useUserStore.ts +++ b/projects/app/src/web/support/user/useUserStore.ts @@ -9,7 +9,6 @@ import type { UserType } from '@fastgpt/global/support/user/type.d'; import type { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type'; import { getTeamPlanStatus } from './team/api'; import { getGroupList } from './team/group/api'; -import { getOrgList } from './team/org/api'; type State = { systemMsgReadId: string;