Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
4871a698
authored
Mar 26, 2025
by
Archer
Committed by
archer
Mar 27, 2025
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
perf: member group (#4324)
* sync collection * remove lock * perf: member group
parent
64fb0914
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
202 additions
and
244 deletions
+202
-244
packages/global/support/permission/memberGroup/api.d.ts
+4
-0
packages/global/support/permission/memberGroup/type.d.ts
+18
-15
packages/global/support/user/api.d.ts
+4
-1
packages/service/support/user/team/controller.ts
+8
-0
projects/app/src/components/support/permission/MemberManager/MemberModal.tsx
+5
-6
projects/app/src/components/support/user/team/Info/MemberTag.tsx
+4
-4
projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx
+17
-25
projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx
+6
-11
projects/app/src/pageComponents/account/team/GroupManage/GroupTransferOwnerModal.tsx
+21
-26
projects/app/src/pageComponents/account/team/GroupManage/index.tsx
+44
-76
projects/app/src/pageComponents/account/team/MemberTable.tsx
+57
-70
projects/app/src/pageComponents/account/team/OrgManage/index.tsx
+1
-4
projects/app/src/pageComponents/account/team/context.tsx
+6
-4
projects/app/src/web/support/user/team/group/api.ts
+7
-2
No files found.
packages/global/support/permission/memberGroup/api.d.ts
0 → 100644
View file @
4871a698
export
type
GetGroupListBody
=
{
searchKey
?:
string
;
withMembers
?:
boolean
;
};
packages/global/support/permission/memberGroup/type.d.ts
View file @
4871a698
import
{
TeamMemberItemType
}
from
'support/user/team/type'
;
import
{
TeamPermission
}
from
'../user/controller'
;
import
{
GroupMemberRole
}
from
'./constant'
;
import
{
Permission
}
from
'../controller'
;
type
MemberGroupSchemaType
=
{
_id
:
string
;
...
...
@@ -16,23 +17,25 @@ type GroupMemberSchemaType = {
role
:
`
${
GroupMemberRole
}
`
;
};
type
MemberGroupType
=
MemberGroupSchemaType
&
{
members
:
{
tmbId
:
string
;
name
:
string
;
avatar
:
string
;
}[];
count
:
number
;
owner
:
{
tmbId
:
string
;
name
:
string
;
avatar
:
string
;
};
canEdit
:
boolean
;
type
MemberGroupListItemType
<
T
extends
boolean
|
undefined
>
=
MemberGroupSchemaType
&
{
members
:
T
extends
true
?
{
tmbId
:
string
;
name
:
string
;
avatar
:
string
;
}[]
:
undefined
;
count
:
T
extends
true
?
number
:
undefined
;
owner
?:
T
extends
true
?
{
tmbId
:
string
;
name
:
string
;
avatar
:
string
;
}
:
undefined
;
permission
:
T
extends
true
?
Permission
:
undefined
;
};
type
MemberGroupListType
=
MemberGroupType
[];
type
GroupMemberItemType
=
{
tmbId
:
string
;
name
:
string
;
...
...
packages/global/support/user/api.d.ts
View file @
4871a698
import
{
MemberGroupSchemaType
,
MemberGroupType
}
from
'support/permission/memberGroup/type'
;
import
{
MemberGroupSchemaType
,
MemberGroupListItemType
}
from
'support/permission/memberGroup/type'
;
import
{
OAuthEnum
}
from
'./constant'
;
import
{
TrackRegisterParams
}
from
'./login/api'
;
import
{
TeamMemberStatusEnum
}
from
'./team/constant'
;
...
...
packages/service/support/user/team/controller.ts
View file @
4871a698
...
...
@@ -55,6 +55,14 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
};
}
export
const
getTeamOwner
=
async
(
teamId
:
string
)
=>
{
const
tmb
=
await
MongoTeamMember
.
findOne
({
teamId
,
role
:
TeamMemberRoleEnum
.
owner
}).
lean
();
return
tmb
;
};
export
async
function
getTmbInfoByTmbId
({
tmbId
}:
{
tmbId
:
string
})
{
if
(
!
tmbId
)
{
return
Promise
.
reject
(
'tmbId or userId is required'
);
...
...
projects/app/src/components/support/permission/MemberManager/MemberModal.tsx
View file @
4871a698
...
...
@@ -28,20 +28,17 @@ import {
DEFAULT_USER_AVATAR
}
from
'@fastgpt/global/common/system/constants'
;
import
Path
from
'@/components/common/folder/Path'
;
import
{
getOrgChildrenPath
}
from
'@fastgpt/global/support/user/team/org/constant'
;
import
{
ParentTreePathItemType
}
from
'@fastgpt/global/common/parentFolder/type'
;
import
{
OrgListItemType
,
OrgType
}
from
'@fastgpt/global/support/user/team/org/type'
;
import
{
useContextSelector
}
from
'use-context-selector'
;
import
{
CollaboratorContext
}
from
'./context'
;
import
{
getTeamMembers
}
from
'@/web/support/user/team/api'
;
import
{
getGroupList
}
from
'@/web/support/user/team/group/api'
;
import
{
getOrgList
,
getOrgMembers
}
from
'@/web/support/user/team/org/api'
;
import
{
useScrollPagination
}
from
'@fastgpt/web/hooks/useScrollPagination'
;
import
MemberItemCard
from
'./MemberItemCard'
;
import
{
GetSearchUserGroupOrg
}
from
'@/web/support/user/api'
;
import
useOrg
from
'@/web/support/user/team/org/hooks/useOrg'
;
import
{
MemberGroupListType
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
import
{
TeamMemberItemType
}
from
'@fastgpt/global/support/user/team/type'
;
import
{
MemberGroupListItemType
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
const
HoverBoxStyle
=
{
bgColor
:
'myGray.50'
,
...
...
@@ -76,7 +73,9 @@ function MemberModal({
const
{
data
:
groups
=
[],
loading
:
loadingGroupsAndOrgs
}
=
useRequest2
(
async
()
=>
{
if
(
!
userInfo
?.
team
?.
teamId
)
return
[];
return
getGroupList
();
return
getGroupList
<
false
>
({
withMembers
:
false
});
},
{
manual
:
false
,
...
...
@@ -117,7 +116,7 @@ function MemberModal({
return
members
;
},
[
searchText
,
members
,
searchedData
?.
members
]);
const
[
selectedGroupList
,
setSelectedGroupList
]
=
useState
<
MemberGroupList
Type
>
([]);
const
[
selectedGroupList
,
setSelectedGroupList
]
=
useState
<
MemberGroupList
ItemType
<
false
>
[]
>
([]);
const
filterGroups
=
useMemo
(()
=>
{
if
(
searchText
)
{
return
searchedData
?.
groups
.
map
((
item
)
=>
({
...
...
projects/app/src/components/support/user/team/Info/MemberTag.tsx
View file @
4871a698
...
...
@@ -3,16 +3,16 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
import
React
from
'react'
;
type
Props
=
{
name
:
string
;
avatar
:
string
;
name
?
:
string
;
avatar
?
:
string
;
};
function
MemberTag
({
name
,
avatar
}:
Props
)
{
return
(
<
HStack
>
<
Avatar
src=
{
avatar
}
w=
{
[
'18px'
,
'22px'
]
}
rounded=
"50%"
/>
{
avatar
&&
<
Avatar
src=
{
avatar
}
w=
{
[
'18px'
,
'22px'
]
}
rounded=
"50%"
/>
}
<
Box
maxW=
{
'150px'
}
className=
{
'textEllipsis'
}
>
{
name
}
{
name
||
'-'
}
</
Box
>
</
HStack
>
);
...
...
projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx
View file @
4871a698
...
...
@@ -4,15 +4,13 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
import
FormLabel
from
'@fastgpt/web/components/common/MyBox/FormLabel'
;
import
{
useTranslation
}
from
'next-i18next'
;
import
React
,
{
useMemo
}
from
'react'
;
import
React
from
'react'
;
import
{
useSelectFile
}
from
'@/web/common/file/hooks/useSelectFile'
;
import
{
useRequest2
}
from
'@fastgpt/web/hooks/useRequest'
;
import
{
useForm
}
from
'react-hook-form'
;
import
{
useContextSelector
}
from
'use-context-selector'
;
import
{
TeamContext
}
from
'../context'
;
import
{
postCreateGroup
,
putUpdateGroup
}
from
'@/web/support/user/team/group/api'
;
import
{
DEFAULT_TEAM_AVATAR
}
from
'@fastgpt/global/common/system/constants'
;
import
{
MemberGroupListType
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
import
{
MemberGroupList
Item
Type
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
export
type
GroupFormType
=
{
avatar
:
string
;
...
...
@@ -21,17 +19,15 @@ export type GroupFormType = {
function
GroupInfoModal
({
onClose
,
editGroupId
,
groups
,
refetchGroups
editGroup
,
onSuccess
}:
{
onClose
:
()
=>
void
;
editGroupId
?:
string
;
groups
:
MemberGroupListType
;
refetchGroups
:
()
=>
void
;
editGroup
?:
MemberGroupListItemType
<
true
>
;
onSuccess
:
()
=>
void
;
})
{
const
{
refetchMembers
}
=
useContextSelector
(
TeamContext
,
(
v
)
=>
v
);
const
{
t
}
=
useTranslation
();
const
{
File
:
AvatarSelect
,
onOpen
:
onOpenSelectAvatar
,
...
...
@@ -41,14 +37,10 @@ function GroupInfoModal({
multiple
:
false
});
const
group
=
useMemo
(()
=>
{
return
groups
.
find
((
item
)
=>
item
.
_id
===
editGroupId
);
},
[
editGroupId
,
groups
]);
const
{
register
,
handleSubmit
,
getValues
,
setValue
}
=
useForm
<
GroupFormType
>
({
defaultValues
:
{
name
:
g
roup
?.
name
||
''
,
avatar
:
g
roup
?.
avatar
||
DEFAULT_TEAM_AVATAR
name
:
editG
roup
?.
name
||
''
,
avatar
:
editG
roup
?.
avatar
||
DEFAULT_TEAM_AVATAR
}
});
...
...
@@ -74,21 +66,21 @@ function GroupInfoModal({
});
},
{
onSuccess
:
()
=>
Promise
.
all
([
onClose
(),
refetchGroups
(),
refetchMember
s
()])
onSuccess
:
()
=>
Promise
.
all
([
onClose
(),
onSucces
s
()])
}
);
const
{
runAsync
:
onUpdate
,
loading
:
isLoadingUpdate
}
=
useRequest2
(
async
(
data
:
GroupFormType
)
=>
{
if
(
!
editGroup
Id
)
return
;
if
(
!
editGroup
)
return
;
return
putUpdateGroup
({
groupId
:
editGroup
I
d
,
groupId
:
editGroup
.
_i
d
,
name
:
data
.
name
,
avatar
:
data
.
avatar
});
},
{
onSuccess
:
()
=>
Promise
.
all
([
onClose
(),
refetchGroups
(),
refetchMember
s
()])
onSuccess
:
()
=>
Promise
.
all
([
onClose
(),
onSucces
s
()])
}
);
...
...
@@ -97,8 +89,8 @@ function GroupInfoModal({
return
(
<
MyModal
onClose=
{
onClose
}
title=
{
editGroup
Id
?
t
(
'user:team.group.edit'
)
:
t
(
'user:team.group.create'
)
}
iconSrc=
{
g
roup
?.
avatar
??
DEFAULT_TEAM_AVATAR
}
title=
{
editGroup
?
t
(
'user:team.group.edit'
)
:
t
(
'user:team.group.create'
)
}
iconSrc=
{
editG
roup
?.
avatar
??
DEFAULT_TEAM_AVATAR
}
>
<
ModalBody
flex=
{
1
}
overflow=
{
'auto'
}
display=
{
'flex'
}
flexDirection=
{
'column'
}
gap=
{
4
}
>
<
FormLabel
w=
"80px"
>
{
t
(
'user:team.avatar_and_name'
)
}
</
FormLabel
>
...
...
@@ -120,14 +112,14 @@ function GroupInfoModal({
<
Button
isLoading=
{
isLoading
}
onClick=
{
handleSubmit
((
data
)
=>
{
if
(
editGroup
Id
)
{
if
(
editGroup
)
{
onUpdate
(
data
);
}
else
{
onCreate
(
data
);
}
})
}
>
{
editGroup
Id
?
t
(
'common:common.Save'
)
:
t
(
'common:new_create'
)
}
{
editGroup
?
t
(
'common:common.Save'
)
:
t
(
'common:new_create'
)
}
</
Button
>
</
ModalFooter
>
<
AvatarSelect
onSelect=
{
onSelectAvatar
}
/>
...
...
projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx
View file @
4871a698
...
...
@@ -26,7 +26,7 @@ import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
import
SearchInput
from
'@fastgpt/web/components/common/Input/SearchInput'
;
import
{
GroupMemberItemType
,
MemberGroupListType
MemberGroupList
Item
Type
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
import
{
useMount
}
from
'ahooks'
;
...
...
@@ -43,24 +43,19 @@ export type GroupFormType = {
function
GroupEditModal
({
onClose
,
editGroupId
,
group
s
,
refetchGroup
s
group
,
onSucces
s
}:
{
onClose
:
()
=>
void
;
editGroupId
?:
string
;
group
s
:
MemberGroupListType
;
refetchGroup
s
:
()
=>
void
;
group
:
MemberGroupListItemType
<
true
>
;
onSucces
s
:
()
=>
void
;
})
{
const
{
t
}
=
useTranslation
();
const
{
userInfo
}
=
useUserStore
();
const
{
toast
}
=
useToast
();
const
group
=
useMemo
(()
=>
{
return
groups
.
find
((
item
)
=>
item
.
_id
===
editGroupId
);
},
[
editGroupId
,
groups
]);
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
<
string
>
();
...
...
@@ -94,7 +89,7 @@ function GroupEditModal({
});
},
{
onSuccess
:
()
=>
Promise
.
all
([
onClose
(),
refetchGroups
(),
refetchMember
s
()])
onSuccess
:
()
=>
Promise
.
all
([
onClose
(),
onSucces
s
()])
}
);
...
...
projects/app/src/pageComponents/account/team/GroupManage/GroupTransferOwnerModal.tsx
View file @
4871a698
import
{
putGroupChangeOwner
,
putUpdateGroup
}
from
'@/web/support/user/team/group/api'
;
import
{
putGroupChangeOwner
}
from
'@/web/support/user/team/group/api'
;
import
{
Box
,
Flex
,
...
...
@@ -15,26 +15,24 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
import
MyModal
from
'@fastgpt/web/components/common/MyModal'
;
import
{
useRequest2
}
from
'@fastgpt/web/hooks/useRequest'
;
import
{
useTranslation
}
from
'next-i18next'
;
import
React
,
{
use
Memo
,
use
State
}
from
'react'
;
import
React
,
{
useState
}
from
'react'
;
import
{
TeamContext
}
from
'../context'
;
import
{
useContextSelector
}
from
'use-context-selector'
;
import
{
MemberGroupListType
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
import
{
MemberGroupList
Item
Type
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
import
{
GetSearchUserGroupOrg
}
from
'@/web/support/user/api'
;
import
{
Omit
}
from
'@fastgpt/web/components/common/DndDrag'
;
export
type
ChangeOwnerModalProps
=
{
groupId
:
string
;
groups
:
MemberGroupListType
;
refetchGroups
:
()
=>
void
;
};
export
function
ChangeOwnerModal
({
onClose
,
groupId
,
groups
,
refetchGroups
}:
ChangeOwnerModalProps
&
{
onClose
:
()
=>
void
})
{
group
,
onSuccess
,
onClose
}:
{
group
:
MemberGroupListItemType
<
true
>
;
onSuccess
:
()
=>
void
;
onClose
:
()
=>
void
;
})
{
const
{
t
}
=
useTranslation
();
const
[
inputValue
,
setInputValue
]
=
React
.
useState
(
''
);
const
{
data
:
searchedData
}
=
useRequest2
(
async
()
=>
{
...
...
@@ -50,14 +48,8 @@ export function ChangeOwnerModal({
);
const
{
members
:
allMembers
}
=
useContextSelector
(
TeamContext
,
(
v
)
=>
v
);
const
group
=
useMemo
(()
=>
{
return
groups
.
find
((
item
)
=>
item
.
_id
===
groupId
);
},
[
groupId
,
groups
]);
const
memberList
=
searchedData
?
searchedData
.
members
:
allMembers
;
const
[
keepAdmin
,
setKeepAdmin
]
=
useState
(
true
);
const
{
isOpen
:
isOpenMemberListMenu
,
onClose
:
onCloseMemberListMenu
,
...
...
@@ -69,10 +61,12 @@ export function ChangeOwnerModal({
'permission'
|
'teamId'
>
|
null
>
(
null
);
const
{
runAsync
,
loading
}
=
useRequest2
(
(
tmbId
:
string
)
=>
putGroupChangeOwner
(
groupId
,
tmbId
),
const
[
keepAdmin
,
setKeepAdmin
]
=
useState
(
true
);
const
{
runAsync
:
onTransfer
,
loading
}
=
useRequest2
(
(
tmbId
:
string
)
=>
putGroupChangeOwner
(
group
.
_id
,
tmbId
),
{
onSuccess
:
()
=>
Promise
.
all
([
onClose
(),
refetchGroup
s
()]),
onSuccess
:
()
=>
Promise
.
all
([
onClose
(),
onSucces
s
()]),
successToast
:
t
(
'common:permission.change_owner_success'
),
errorToast
:
t
(
'common:permission.change_owner_failed'
)
}
...
...
@@ -82,7 +76,7 @@ export function ChangeOwnerModal({
if
(
!
selectedMember
)
{
return
;
}
await
runAsync
(
selectedMember
.
tmbId
);
await
onTransfer
(
selectedMember
.
tmbId
);
};
return
(
...
...
@@ -92,7 +86,6 @@ export function ChangeOwnerModal({
iconColor=
"primary.600"
onClose=
{
onClose
}
title=
{
t
(
'common:permission.change_owner'
)
}
isLoading=
{
loading
}
>
<
ModalBody
>
<
HStack
>
...
...
@@ -181,7 +174,9 @@ export function ChangeOwnerModal({
<
Button
onClick=
{
onClose
}
variant=
{
'whiteBase'
}
>
{
t
(
'common:common.Cancel'
)
}
</
Button
>
<
Button
onClick=
{
onConfirm
}
>
{
t
(
'common:common.Confirm'
)
}
</
Button
>
<
Button
isLoading=
{
loading
}
isDisabled=
{
!
selectedMember
}
onClick=
{
onConfirm
}
>
{
t
(
'common:common.Confirm'
)
}
</
Button
>
</
HStack
>
</
ModalFooter
>
</
MyModal
>
...
...
projects/app/src/pageComponents/account/team/GroupManage/index.tsx
View file @
4871a698
...
...
@@ -3,7 +3,6 @@ import {
Box
,
Button
,
Flex
,
HStack
,
Table
,
TableContainer
,
Tbody
,
...
...
@@ -16,20 +15,18 @@ import {
import
{
useTranslation
}
from
'next-i18next'
;
import
{
useConfirm
}
from
'@fastgpt/web/hooks/useConfirm'
;
import
MyBox
from
'@fastgpt/web/components/common/MyBox'
;
import
{
useContextSelector
}
from
'use-context-selector'
;
import
{
TeamContext
}
from
'../context'
;
import
MyMenu
,
{
MenuItemType
}
from
'@fastgpt/web/components/common/MyMenu'
;
import
MyIcon
from
'@fastgpt/web/components/common/Icon'
;
import
{
useUserStore
}
from
'@/web/support/user/useUserStore'
;
import
{
useRequest2
}
from
'@fastgpt/web/hooks/useRequest'
;
import
{
deleteGroup
,
getGroupList
,
getGroupMembers
}
from
'@/web/support/user/team/group/api'
;
import
{
deleteGroup
,
getGroupList
}
from
'@/web/support/user/team/group/api'
;
import
{
DefaultGroupName
}
from
'@fastgpt/global/support/user/team/group/constant'
;
import
MemberTag
from
'../../../../components/support/user/team/Info/MemberTag'
;
import
MyTooltip
from
'@fastgpt/web/components/common/MyTooltip'
;
import
dynamic
from
'next/dynamic'
;
import
{
useState
}
from
'react'
;
import
IconButton
from
'../OrgManage/IconButton'
;
import
{
MemberGroupType
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
import
{
MemberGroup
ListItem
Type
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
const
ChangeOwnerModal
=
dynamic
(()
=>
import
(
'./GroupTransferOwnerModal'
));
const
GroupInfoModal
=
dynamic
(()
=>
import
(
'./GroupInfoModal'
));
...
...
@@ -39,26 +36,23 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const
{
t
}
=
useTranslation
();
const
{
userInfo
}
=
useUserStore
();
const
{
members
,
teamSize
}
=
useContextSelector
(
TeamContext
,
(
v
)
=>
v
);
const
{
data
:
groups
=
[],
loading
:
isLoadingGroups
,
refresh
:
refetchGroups
}
=
useRequest2
(
getGroupList
,
{
}
=
useRequest2
(
()
=>
getGroupList
<
true
>
({
withMembers
:
true
})
,
{
manual
:
false
,
refreshDeps
:
[
userInfo
?.
team
?.
teamId
]
});
const
[
editGroup
,
setEditGroup
]
=
useState
<
MemberGroup
Type
>
();
const
[
editGroup
,
setEditGroup
]
=
useState
<
MemberGroup
ListItemType
<
true
>
>
();
const
{
isOpen
:
isOpenGroupInfo
,
onOpen
:
onOpenGroupInfo
,
onClose
:
onCloseGroupInfo
}
=
useDisclosure
();
const
onEditGroupInfo
=
(
e
:
MemberGroupType
)
=>
{
const
onEditGroupInfo
=
(
e
:
MemberGroupListItemType
<
true
>
)
=>
{
setEditGroup
(
e
);
onOpenGroupInfo
();
};
...
...
@@ -67,7 +61,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
type
:
'delete'
,
content
:
t
(
'account_team:confirm_delete_group'
)
});
const
{
runAsync
:
delDeleteGroup
}
=
useRequest2
(
deleteGroup
,
{
onSuccess
:
()
=>
{
refetchGroups
();
...
...
@@ -79,21 +72,17 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
onOpen
:
onOpenManageGroupMember
,
onClose
:
onCloseManageGroupMember
}
=
useDisclosure
();
const
onManageMember
=
(
e
:
MemberGroup
Type
)
=>
{
const
onManageMember
=
(
e
:
MemberGroup
ListItemType
<
true
>
)
=>
{
setEditGroup
(
e
);
onOpenManageGroupMember
();
};
const
hasGroupManagePer
=
(
group
:
(
typeof
groups
)[
0
])
=>
userInfo
?.
team
.
permission
.
hasManagePer
;
const
isGroupOwner
=
(
group
:
(
typeof
groups
)[
0
])
=>
userInfo
?.
team
.
permission
.
hasManagePer
;
const
{
isOpen
:
isOpenChangeOwner
,
onOpen
:
onOpenChangeOwner
,
onClose
:
onCloseChangeOwner
}
=
useDisclosure
();
const
onChangeOwner
=
(
e
:
MemberGroup
Type
)
=>
{
const
onChangeOwner
=
(
e
:
MemberGroup
ListItemType
<
true
>
)
=>
{
setEditGroup
(
e
);
onOpenChangeOwner
();
};
...
...
@@ -135,57 +124,37 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
{
groups
?.
map
((
group
)
=>
(
<
Tr
key=
{
group
.
_id
}
overflow=
{
'unset'
}
>
<
Td
>
<
HStack
>
<
MemberTag
name=
{
group
.
name
===
DefaultGroupName
?
userInfo
?.
team
.
teamName
??
''
:
group
.
name
}
avatar=
{
group
.
avatar
}
/>
<
Box
>
(
{
group
.
name
===
DefaultGroupName
?
teamSize
:
group
.
count
}
)
</
Box
>
</
HStack
>
</
Td
>
<
Td
>
<
MemberTag
name=
{
group
.
name
===
DefaultGroupName
?
members
.
find
((
item
)
=>
item
.
role
===
'owner'
)?.
memberName
??
''
:
group
.
owner
.
name
}
avatar=
{
group
.
name
===
DefaultGroupName
?
members
.
find
((
item
)
=>
item
.
role
===
'owner'
)?.
avatar
??
''
:
group
.
owner
.
avatar
group
.
name
===
DefaultGroupName
?
userInfo
?.
team
.
teamName
??
''
:
group
.
name
}
avatar=
{
group
.
avatar
}
/>
</
Td
>
<
Td
>
{
group
.
name
===
DefaultGroupName
?
(
<
AvatarGroup
avatars=
{
members
.
map
((
v
)
=>
v
.
avatar
)
}
total=
{
teamSize
}
/>
)
:
hasGroupManagePer
(
group
)
?
(
<
MyTooltip
label=
{
t
(
'account_team:manage_member'
)
}
>
<
Box
cursor=
"pointer"
onClick=
{
()
=>
onManageMember
(
group
)
}
>
<
AvatarGroup
avatars=
{
group
.
members
.
map
(
(
v
)
=>
members
.
find
((
m
)
=>
m
.
tmbId
===
v
.
tmbId
)?.
avatar
??
''
)
}
total=
{
group
.
count
}
/>
</
Box
>
</
MyTooltip
>
)
:
(
<
AvatarGroup
avatars=
{
group
.
members
.
map
(
(
v
)
=>
members
.
find
((
m
)
=>
m
.
tmbId
===
v
.
tmbId
)?.
avatar
??
''
)
}
total=
{
group
.
count
}
/>
)
}
<
MemberTag
name=
{
group
.
owner
?.
name
}
avatar=
{
group
.
owner
?.
avatar
}
/>
</
Td
>
<
Td
>
<
MyTooltip
label=
{
group
.
permission
?.
hasManagePer
?
t
(
'account_team:manage_member'
)
:
''
}
>
<
Box
{
...
(
group
.
permission
?
.
hasManagePer
?
{
cursor
:
'
pointer
',
onClick
:
()
=
>
onManageMember(group)
}
:
{}
)}
>
<
AvatarGroup
avatars=
{
group
?.
members
.
map
((
v
)
=>
v
.
avatar
)
}
total=
{
group
.
count
}
/>
</
Box
>
</
MyTooltip
>
</
Td
>
<
Td
>
{
hasGroupManagePer
(
group
)
&&
group
.
name
!==
DefaultGroupName
&&
(
{
group
.
permission
?.
hasManagePer
&&
(
<
MyMenu
Button=
{
<
IconButton
name=
{
'more'
}
/>
}
menuList=
{
[
...
...
@@ -205,7 +174,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
onManageMember
(
group
);
}
},
...(
isGroupOwner
(
group
)
...(
group
.
permission
?.
isOwner
?
[
{
label
:
t
(
'account_team:transfer_ownership'
),
...
...
@@ -239,34 +208,33 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
</
MyBox
>
<
ConfirmDeleteGroupModal
/>
{
isOpenChangeOwner
&&
editGroup
&&
(
<
ChangeOwnerModal
groupId=
{
editGroup
.
_id
}
onClose=
{
onCloseChangeOwner
}
groups=
{
groups
}
refetchGroups=
{
refetchGroups
}
/>
)
}
{
isOpenGroupInfo
&&
(
<
GroupInfoModal
groups=
{
groups
}
refetchGroup
s=
{
refetchGroups
}
editGroup=
{
editGroup
}
onSucces
s=
{
refetchGroups
}
onClose=
{
()
=>
{
onCloseGroupInfo
();
setEditGroup
(
undefined
);
}
}
editGroupId=
{
editGroup
?.
_id
}
/>
)
}
{
isOpenChangeOwner
&&
editGroup
&&
(
<
ChangeOwnerModal
group=
{
editGroup
}
onClose=
{
onCloseChangeOwner
}
onSuccess=
{
refetchGroups
}
/>
)
}
{
isOpenManageGroupMember
&&
editGroup
&&
(
<
GroupManageMember
groups=
{
groups
}
refetchGroups=
{
refetchGroups
}
group=
{
editGroup
}
onClose=
{
()
=>
{
onCloseManageGroupMember
();
setEditGroup
(
undefined
);
}
}
editGroupId=
{
editGroup
.
_id
}
onSuccess=
{
refetchGroups
}
/>
)
}
</>
...
...
projects/app/src/pageComponents/account/team/MemberTable.tsx
View file @
4871a698
...
...
@@ -20,8 +20,9 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import
{
useEditTextarea
}
from
'@fastgpt/web/hooks/useEditTextarea'
;
import
{
delRemoveMember
,
postRestoreMember
,
putUpdateMemberNameByManager
getTeamMembers
,
putUpdateMemberNameByManager
,
postRestoreMember
}
from
'@/web/support/user/team/api'
;
import
Tag
from
'@fastgpt/web/components/common/Tag'
;
import
Icon
from
'@fastgpt/web/components/common/Icon'
;
...
...
@@ -33,7 +34,6 @@ import dynamic from 'next/dynamic';
import
{
useRequest2
}
from
'@fastgpt/web/hooks/useRequest'
;
import
{
delLeaveTeam
}
from
'@/web/support/user/team/api'
;
import
{
GetSearchUserGroupOrg
,
postSyncMembers
}
from
'@/web/support/user/api'
;
import
MyLoading
from
'@fastgpt/web/components/common/MyLoading'
;
import
{
TeamMemberRoleEnum
,
TeamMemberStatusEnum
...
...
@@ -41,10 +41,12 @@ import {
import
format
from
'date-fns/format'
;
import
OrgTags
from
'@/components/support/user/team/OrgTags'
;
import
SearchInput
from
'@fastgpt/web/components/common/Input/SearchInput'
;
import
{
useState
}
from
'react'
;
import
{
use
Callback
,
use
State
}
from
'react'
;
import
{
downloadFetch
}
from
'@/web/common/system/utils'
;
import
{
TeamMemberItemType
}
from
'@fastgpt/global/support/user/team/type'
;
import
{
useToast
}
from
'@fastgpt/web/hooks/useToast'
;
import
MyBox
from
'@fastgpt/web/components/common/MyBox'
;
import
{
useScrollPagination
}
from
'@fastgpt/web/hooks/useScrollPagination'
;
const
InviteModal
=
dynamic
(()
=>
import
(
'./Invite/InviteModal'
));
const
TeamTagModal
=
dynamic
(()
=>
import
(
'@/components/support/user/team/TeamTagModal'
));
...
...
@@ -55,32 +57,31 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const
{
userInfo
}
=
useUserStore
();
const
{
feConfigs
}
=
useSystemStore
();
const
isSyncMember
=
feConfigs
?.
register_method
?.
includes
(
'sync'
);
const
{
myTeams
,
refetchTeams
,
members
,
refetchMembers
,
onSwitchTeam
,
MemberScrollData
}
=
useContextSelector
(
TeamContext
,
(
v
)
=>
v
);
const
{
myTeams
,
onSwitchTeam
}
=
useContextSelector
(
TeamContext
,
(
v
)
=>
v
);
const
{
isOpen
:
isOpenTeamTagsAsync
,
onOpen
:
onOpenTeamTagsAsync
,
onClose
:
onCloseTeamTagsAsync
}
=
useDisclosure
();
const
{
isOpen
:
isOpenInvite
,
onOpen
:
onOpenInvite
,
onClose
:
onCloseInvite
}
=
useDisclosure
();
const
{
ConfirmModal
:
ConfirmRemoveMemberModal
,
openConfirm
:
openRemoveMember
}
=
useConfirm
({
type
:
'delete'
});
const
{
ConfirmModal
:
ConfirmRestoreMemberModal
,
openConfirm
:
openRestoreMember
}
=
useConfirm
({
type
:
'common'
,
title
:
t
(
'account_team:restore_tip_title'
),
iconSrc
:
'common/confirm/restoreTip'
,
iconColor
:
'primary.500'
// member action
const
{
data
:
members
=
[],
isLoading
:
loadingMembers
,
refreshList
:
refetchMemberList
,
ScrollData
:
MemberScrollData
}
=
useScrollPagination
(
getTeamMembers
,
{
pageSize
:
20
,
params
:
{
withLeaved
:
true
}
});
const
[
searchText
,
setSearchText
]
=
useState
<
string
>
(
''
);
const
isSyncMember
=
feConfigs
.
register_method
?.
includes
(
'sync'
);
const
{
data
:
searchMembersData
}
=
useRequest2
(
const
{
data
:
searchMembersData
,
run
:
refreshSearchMembers
}
=
useRequest2
(
async
()
=>
{
if
(
!
searchText
)
return
Promise
.
resolve
();
return
GetSearchUserGroupOrg
(
searchText
,
{
members
:
true
,
orgs
:
false
,
groups
:
false
});
...
...
@@ -88,47 +89,54 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
{
manual
:
false
,
throttleWait
:
500
,
debounceWait
:
200
,
refreshDeps
:
[
searchText
]
}
);
const
{
runAsync
:
onLeaveTeam
}
=
useRequest2
(
async
()
=>
{
const
defaultTeam
=
myTeams
[
0
];
// change to personal team
onSwitchTeam
(
defaultTeam
.
teamId
);
return
delLeaveTeam
();
},
{
onSuccess
()
{
refetchTeams
();
refetchMembers
();
},
errorToast
:
t
(
'account_team:user_team_leave_team_failed'
)
}
);
const
{
ConfirmModal
:
ConfirmLeaveTeamModal
,
openConfirm
:
openLeaveConfirm
}
=
useConfirm
({
content
:
t
(
'account_team:confirm_leave_team'
)
});
const
onRefreshMembers
=
useCallback
(()
=>
{
refetchMemberList
();
refreshSearchMembers
();
},
[
refetchMemberList
,
refreshSearchMembers
]);
const
{
isOpen
:
isOpenInvite
,
onOpen
:
onOpenInvite
,
onClose
:
onCloseInvite
}
=
useDisclosure
();
const
{
runAsync
:
onSyncMember
,
loading
:
isSyncing
}
=
useRequest2
(
postSyncMembers
,
{
onSuccess
()
{
refetchMembers
();
},
onSuccess
:
onRefreshMembers
,
successToast
:
t
(
'account_team:sync_member_success'
),
errorToast
:
t
(
'account_team:sync_member_failed'
)
});
const
{
runAsync
:
onRestore
,
loading
:
isUpdateInvite
}
=
useRequest2
(
postRestoreMember
,
{
const
{
ConfirmModal
:
ConfirmLeaveTeamModal
,
openConfirm
:
openLeaveConfirm
}
=
useConfirm
({
content
:
t
(
'account_team:confirm_leave_team'
)
});
const
{
runAsync
:
onLeaveTeam
}
=
useRequest2
(
delLeaveTeam
,
{
onSuccess
()
{
refetchMembers
();
const
defaultTeam
=
myTeams
[
0
];
onSwitchTeam
(
defaultTeam
.
teamId
);
},
successToast
:
t
(
'common:user.team.invite.Accepted'
),
errorToast
:
t
(
'account_team:user_team_leave_team_failed'
)
});
const
{
ConfirmModal
:
ConfirmRemoveMemberModal
,
openConfirm
:
openRemoveMember
}
=
useConfirm
({
type
:
'delete'
});
const
{
runAsync
:
onRemoveMember
}
=
useRequest2
(
delRemoveMember
,
{
onSuccess
:
onRefreshMembers
});
const
{
ConfirmModal
:
ConfirmRestoreMemberModal
,
openConfirm
:
openRestoreMember
}
=
useConfirm
({
type
:
'common'
,
title
:
t
(
'account_team:restore_tip_title'
),
iconSrc
:
'common/confirm/restoreTip'
,
iconColor
:
'primary.500'
});
const
{
runAsync
:
onRestore
}
=
useRequest2
(
postRestoreMember
,
{
onSuccess
:
onRefreshMembers
,
successToast
:
t
(
'common:common.Success'
),
errorToast
:
t
(
'common:user.team.invite.Reject'
)
});
const
isLoading
=
isUpdateInvite
||
isSyncing
;
const
isLoading
=
loadingMembers
||
isSyncing
;
const
{
EditModal
:
EditMemberNameModal
,
onOpenModal
:
openEditMemberName
}
=
useEditTextarea
({
title
:
t
(
'account_team:edit_member'
),
...
...
@@ -141,7 +149,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
defaultVal
:
memberName
,
onSuccess
:
(
newName
:
string
)
=>
{
return
putUpdateMemberNameByManager
(
tmbId
,
newName
).
then
(()
=>
{
refetc
hMembers
();
onRefres
hMembers
();
});
},
onError
:
(
err
)
=>
{
...
...
@@ -155,7 +163,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
return
(
<>
{
isLoading
&&
<
MyLoading
/>
}
<
Flex
justify=
{
'space-between'
}
align=
{
'center'
}
pb=
{
'1rem'
}
>
{
Tabs
}
<
HStack
alignItems=
{
'center'
}
>
...
...
@@ -237,7 +244,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
</
HStack
>
</
Flex
>
<
Box
flex=
{
'1 0 0'
}
overflow=
{
'auto'
}
>
<
MyBox
isLoading=
{
isLoading
}
flex=
{
'1 0 0'
}
overflow=
{
'auto'
}
>
<
MemberScrollData
>
<
TableContainer
overflow=
{
'unset'
}
fontSize=
{
'sm'
}
>
<
Table
overflow=
{
'unset'
}
>
...
...
@@ -321,27 +328,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
}
}
onClick=
{
()
=>
{
openRemoveMember
(
()
=>
delRemoveMember
(
member
.
tmbId
).
then
(
refetchMembers
),
undefined
,
t
(
'account_team:remove_tip'
,
{
username
:
member
.
memberName
})
)();
}
}
/>
<
Icon
name=
{
'common/trash'
}
cursor=
{
'pointer'
}
w=
"1rem"
p=
"1"
borderRadius=
"sm"
_hover=
{
{
color
:
'red.600'
,
bgColor
:
'myGray.100'
}
}
onClick=
{
()
=>
{
openRemoveMember
(
()
=>
delRemoveMember
(
member
.
tmbId
).
then
(
refetchMembers
),
()
=>
onRemoveMember
(
member
.
tmbId
),
undefined
,
t
(
'account_team:remove_tip'
,
{
username
:
member
.
memberName
...
...
@@ -385,7 +372,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
<
EditMemberNameModal
/>
</
TableContainer
>
</
MemberScrollData
>
</
Box
>
</
My
Box
>
<
ConfirmLeaveTeamModal
/>
{
isOpenInvite
&&
userInfo
?.
team
?.
teamId
&&
<
InviteModal
onClose=
{
onCloseInvite
}
/>
}
...
...
projects/app/src/pageComponents/account/team/OrgManage/index.tsx
View file @
4871a698
...
...
@@ -14,7 +14,7 @@ import {
Tr
,
VStack
}
from
'@chakra-ui/react'
;
import
type
{
OrgListItemType
,
OrgType
}
from
'@fastgpt/global/support/user/team/org/type'
;
import
type
{
OrgListItemType
}
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'
;
...
...
@@ -32,12 +32,9 @@ 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
{
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
}
from
'@/web/support/user/team/api'
;
import
SearchInput
from
'@fastgpt/web/components/common/Input/SearchInput'
;
import
{
useScrollPagination
}
from
'@fastgpt/web/hooks/useScrollPagination'
;
import
useOrg
from
'@/web/support/user/team/org/hooks/useOrg'
;
const
OrgInfoModal
=
dynamic
(()
=>
import
(
'./OrgInfoModal'
));
...
...
projects/app/src/pageComponents/account/team/context.tsx
View file @
4871a698
...
...
@@ -13,10 +13,8 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import
type
{
TeamTmbItemType
,
TeamMemberItemType
}
from
'@fastgpt/global/support/user/team/type'
;
import
{
useRequest2
}
from
'@fastgpt/web/hooks/useRequest'
;
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
{
OrgType
}
from
'@fastgpt/global/support/user/team/org/type
'
;
import
{
useRouter
}
from
'next/router
'
;
const
EditInfoModal
=
dynamic
(()
=>
import
(
'./EditInfoModal'
));
...
...
@@ -55,6 +53,8 @@ export const TeamContext = createContext<TeamModalContextType>({
export
const
TeamModalContextProvider
=
({
children
}:
{
children
:
ReactNode
})
=>
{
const
{
t
}
=
useTranslation
();
const
router
=
useRouter
();
const
[
editTeamData
,
setEditTeamData
]
=
useState
<
EditTeamFormDataType
>
();
const
{
userInfo
,
initUserInfo
}
=
useUserStore
();
...
...
@@ -96,10 +96,12 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
const
{
runAsync
:
onSwitchTeam
,
loading
:
isSwitchingTeam
}
=
useRequest2
(
async
(
teamId
:
string
)
=>
{
await
putSwitchTeam
(
teamId
);
refetchMembers
();
return
initUserInfo
();
},
{
onSuccess
:
()
=>
{
router
.
reload
();
},
errorToast
:
t
(
'common:user.team.Switch Team Failed'
)
}
);
...
...
projects/app/src/web/support/user/team/group/api.ts
View file @
4871a698
import
{
DELETE
,
GET
,
POST
,
PUT
}
from
'@/web/common/api/request'
;
import
{
GetGroupListBody
}
from
'@fastgpt/global/support/permission/memberGroup/api'
;
import
type
{
GroupMemberItemType
,
MemberGroupListType
MemberGroupList
Item
Type
}
from
'@fastgpt/global/support/permission/memberGroup/type'
;
import
type
{
postCreateGroupData
,
putUpdateGroupData
}
from
'@fastgpt/global/support/user/team/group/api'
;
export
const
getGroupList
=
()
=>
GET
<
MemberGroupListType
>
(
'/proApi/support/user/team/group/list'
);
export
const
getGroupList
=
<
T
extends
boolean
>
(
data
:
GetGroupListBody
)
=>
POST
<
MemberGroupListItemType
<
T
>
[]
>
(
'/proApi/support/user/team/group/list'
,
data
).
then
((
res
)
=>
{
console
.
log
(
res
);
return
res
;
});
export
const
postCreateGroup
=
(
data
:
postCreateGroupData
)
=>
POST
(
'/proApi/support/user/team/group/create'
,
data
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment