From 206325bc5fc177fc0af8da26df7eaec64c183da0 Mon Sep 17 00:00:00 2001 From: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Date: Wed, 19 Feb 2025 17:27:19 +0800 Subject: [PATCH] chore: team, orgs, search and so on (#3807) * feat: clb search support username, memberName, contacts * feat: popup org names * feat: update team member table * feat: restore the member * feat: search user in team member table * feat: bind contact * feat: export members * feat: org tab could delete member * feat: org table search * feat: team notification account bind * feat: permission tab search * fix: wecom sso * chore(init): copy notificationAccount to user.contact * chore: adjust * fix: ts error * fix: useConfirm iconColor customization * pref: fe * fix: style * fix: fix team member manage * pref: enlarge team member pagesize * pref: initv4822 * fix: pageSize * pref: initscritpt --- packages/global/support/user/api.d.ts | 10 + .../global/support/user/team/controller.d.ts | 7 + packages/global/support/user/team/type.d.ts | 5 + packages/global/support/user/type.d.ts | 2 + packages/service/common/file/csv.ts | 4 + packages/service/support/user/controller.ts | 3 +- packages/service/support/user/schema.ts | 1 + .../support/user/team/teamMemberSchema.ts | 3 + .../web/components/common/Icon/constants.ts | 3 +- .../Icon/icons/common/confirm/deleteTip.svg | 4 +- .../Icon/icons/common/confirm/restoreTip.svg | 3 + .../common/Input/SearchInput/index.tsx | 2 +- .../web/components/common/MyModal/index.tsx | 2 +- packages/web/hooks/useConfirm.tsx | 12 +- packages/web/i18n/zh-CN/account_info.json | 4 +- packages/web/i18n/zh-CN/account_team.json | 19 +- projects/app/package.json | 2 +- projects/app/src/components/Layout/index.tsx | 6 +- .../MemberManager/MemberItemCard.tsx | 67 ++++ .../permission/MemberManager/MemberModal.tsx | 314 +++++++++--------- ...cationModal.tsx => UpdateContactModal.tsx} | 36 +- .../support/user/team/OrgTags/index.tsx | 39 +++ .../account/team/EditInfoModal.tsx | 62 +++- .../team/GroupManage/GroupManageMember.tsx | 4 +- .../account/team/MemberTable.tsx | 203 ++++++++--- .../team/OrgManage/OrgMemberManageModal.tsx | 4 +- .../account/team/OrgManage/index.tsx | 226 +++++++++---- .../account/team/PermissionManage/index.tsx | 47 ++- .../pageComponents/account/team/context.tsx | 30 +- .../login/LoginForm/FormLayout.tsx | 2 +- projects/app/src/pages/account/info/index.tsx | 35 +- projects/app/src/pages/account/team/index.tsx | 3 +- projects/app/src/pages/api/admin/initv4822.ts | 29 ++ projects/app/src/web/support/user/api.ts | 18 +- projects/app/src/web/support/user/team/api.ts | 5 +- 35 files changed, 867 insertions(+), 349 deletions(-) create mode 100644 packages/service/common/file/csv.ts create mode 100644 packages/web/components/common/Icon/icons/common/confirm/restoreTip.svg create mode 100644 projects/app/src/components/support/permission/MemberManager/MemberItemCard.tsx rename projects/app/src/components/support/user/inform/{UpdateNotificationModal.tsx => UpdateContactModal.tsx} (80%) create mode 100644 projects/app/src/components/support/user/team/OrgTags/index.tsx create mode 100644 projects/app/src/pages/api/admin/initv4822.ts diff --git a/packages/global/support/user/api.d.ts b/packages/global/support/user/api.d.ts index 31da306b0..86ce1f0df 100644 --- a/packages/global/support/user/api.d.ts +++ b/packages/global/support/user/api.d.ts @@ -1,5 +1,9 @@ +import { MemberGroupSchemaType, MemberGroupType } from 'support/permission/memberGroup/type'; import { OAuthEnum } from './constant'; import { TrackRegisterParams } from './login/api'; +import { TeamMemberStatusEnum } from './team/constant'; +import { OrgType } from './team/org/type'; +import { TeamMemberItemType } from './team/type'; export type PostLoginProps = { username: string; @@ -21,3 +25,9 @@ export type FastLoginProps = { token: string; code: string; }; + +export type SearchResult = { + members: Omit[]; + orgs: Omit[]; + groups: MemberGroupSchemaType[]; +}; diff --git a/packages/global/support/user/team/controller.d.ts b/packages/global/support/user/team/controller.d.ts index fb331d0de..f8c0ea2ff 100644 --- a/packages/global/support/user/team/controller.d.ts +++ b/packages/global/support/user/team/controller.d.ts @@ -13,6 +13,7 @@ export type CreateTeamProps = { defaultTeam?: boolean; memberName?: string; memberAvatar?: string; + notificationAccount?: string; }; export type UpdateTeamProps = Omit & { name?: string; @@ -39,6 +40,12 @@ export type UpdateInviteProps = { tmbId: string; status: TeamMemberSchema['status']; }; + +export type UpdateStatusProps = { + tmbId: string; + status: TeamMemberSchema['status']; +}; + export type InviteMemberResponse = Record< 'invite' | 'inValid' | 'inTeam', { username: string; userId: string }[] diff --git a/packages/global/support/user/team/type.d.ts b/packages/global/support/user/team/type.d.ts index 6f063576f..c087046ea 100644 --- a/packages/global/support/user/team/type.d.ts +++ b/packages/global/support/user/team/type.d.ts @@ -34,6 +34,7 @@ export type TeamTagSchema = TeamTagItemType & { _id: string; teamId: string; createTime: Date; + updateTime?: Date; }; export type TeamMemberSchema = { @@ -41,6 +42,7 @@ export type TeamMemberSchema = { teamId: string; userId: string; createTime: Date; + updateTime?: Date; name: string; role: `${TeamMemberRoleEnum}`; status: `${TeamMemberStatusEnum}`; @@ -79,6 +81,9 @@ export type TeamMemberItemType = { role: `${TeamMemberRoleEnum}`; status: `${TeamMemberStatusEnum}`; permission: TeamPermission; + contact?: string; + createTime: Date; + updateTime?: Date; }; export type TeamTagItemType = { diff --git a/packages/global/support/user/type.d.ts b/packages/global/support/user/type.d.ts index 13f6dd522..43ea840e4 100644 --- a/packages/global/support/user/type.d.ts +++ b/packages/global/support/user/type.d.ts @@ -17,6 +17,7 @@ export type UserModelSchema = { fastgpt_sem?: { keyword: string; }; + contact?: string; }; export type UserType = { @@ -29,6 +30,7 @@ export type UserType = { standardInfo?: standardInfoType; notificationAccount?: string; permission: TeamPermission; + contact?: string; }; export type SourceMemberType = { diff --git a/packages/service/common/file/csv.ts b/packages/service/common/file/csv.ts new file mode 100644 index 000000000..eaf53cc4f --- /dev/null +++ b/packages/service/common/file/csv.ts @@ -0,0 +1,4 @@ +export const generateCsv = (headers: string[], data: string[][]) => { + const csv = [headers.join(','), ...data.map((row) => row.join(','))].join('\n'); + return csv; +}; diff --git a/packages/service/support/user/controller.ts b/packages/service/support/user/controller.ts index ab28aac72..aef12b2d2 100644 --- a/packages/service/support/user/controller.ts +++ b/packages/service/support/user/controller.ts @@ -46,6 +46,7 @@ export async function getUserDetail({ promotionRate: user.promotionRate, team: tmb, notificationAccount: tmb.notificationAccount, - permission: tmb.permission + permission: tmb.permission, + contact: user.contact }; } diff --git a/packages/service/support/user/schema.ts b/packages/service/support/user/schema.ts index 78234fd34..cf97cda04 100644 --- a/packages/service/support/user/schema.ts +++ b/packages/service/support/user/schema.ts @@ -57,6 +57,7 @@ const UserSchema = new Schema({ }, fastgpt_sem: Object, sourceDomain: String, + contact: String, /** @deprecated */ avatar: String diff --git a/packages/service/support/user/team/teamMemberSchema.ts b/packages/service/support/user/team/teamMemberSchema.ts index f7893c0d0..26df6297c 100644 --- a/packages/service/support/user/team/teamMemberSchema.ts +++ b/packages/service/support/user/team/teamMemberSchema.ts @@ -36,6 +36,9 @@ const TeamMemberSchema = new Schema({ type: Date, default: () => new Date() }, + updateTime: { + type: Date + }, defaultTeam: { type: Boolean, default: false diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 4783aa2da..bdb91dc63 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -26,6 +26,7 @@ export const iconPaths = { 'common/closeLight': () => import('./icons/common/closeLight.svg'), 'common/confirm/commonTip': () => import('./icons/common/confirm/commonTip.svg'), 'common/confirm/deleteTip': () => import('./icons/common/confirm/deleteTip.svg'), + 'common/confirm/restoreTip': () => import('./icons/common/confirm/restoreTip.svg'), 'common/confirm/rightTip': () => import('./icons/common/confirm/rightTip.svg'), 'common/courseLight': () => import('./icons/common/courseLight.svg'), 'common/customTitleLight': () => import('./icons/common/customTitleLight.svg'), @@ -387,9 +388,9 @@ export const iconPaths = { 'model/moonshot': () => import('./icons/model/moonshot.svg'), 'model/ollama': () => import('./icons/model/ollama.svg'), 'model/openai': () => import('./icons/model/openai.svg'), + 'model/ppio': () => import('./icons/model/ppio.svg'), 'model/qwen': () => import('./icons/model/qwen.svg'), 'model/siliconflow': () => import('./icons/model/siliconflow.svg'), - 'model/ppio': () => import('./icons/model/ppio.svg'), 'model/sparkDesk': () => import('./icons/model/sparkDesk.svg'), 'model/stepfun': () => import('./icons/model/stepfun.svg'), 'model/yi': () => import('./icons/model/yi.svg'), diff --git a/packages/web/components/common/Icon/icons/common/confirm/deleteTip.svg b/packages/web/components/common/Icon/icons/common/confirm/deleteTip.svg index f11aca957..7d90a6c59 100644 --- a/packages/web/components/common/Icon/icons/common/confirm/deleteTip.svg +++ b/packages/web/components/common/Icon/icons/common/confirm/deleteTip.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/packages/web/components/common/Icon/icons/common/confirm/restoreTip.svg b/packages/web/components/common/Icon/icons/common/confirm/restoreTip.svg new file mode 100644 index 000000000..9fb3b9538 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/confirm/restoreTip.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Input/SearchInput/index.tsx b/packages/web/components/common/Input/SearchInput/index.tsx index 99affe0ca..10603f091 100644 --- a/packages/web/components/common/Input/SearchInput/index.tsx +++ b/packages/web/components/common/Input/SearchInput/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Input, InputProps, InputGroup, InputLeftElement } from '@chakra-ui/react'; import MyIcon from '../../Icon'; diff --git a/packages/web/components/common/MyModal/index.tsx b/packages/web/components/common/MyModal/index.tsx index d49337a5c..e8ee75c26 100644 --- a/packages/web/components/common/MyModal/index.tsx +++ b/packages/web/components/common/MyModal/index.tsx @@ -84,7 +84,7 @@ const MyModal = ({ objectFit={'contain'} alt="" src={iconSrc} - w={'1.5rem'} + w={'20px'} borderRadius={'sm'} /> diff --git a/packages/web/hooks/useConfirm.tsx b/packages/web/hooks/useConfirm.tsx index b62e7b77b..eb924d086 100644 --- a/packages/web/hooks/useConfirm.tsx +++ b/packages/web/hooks/useConfirm.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { useDisclosure, Button, ModalBody, ModalFooter } from '@chakra-ui/react'; +import { useDisclosure, Button, ModalBody, ModalFooter, type ImageProps } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import MyModal from '../components/common/MyModal'; import { useMemoizedFn } from 'ahooks'; @@ -11,6 +11,7 @@ export const useConfirm = (props?: { showCancel?: boolean; type?: 'common' | 'delete'; hideFooter?: boolean; + iconColor?: ImageProps['color']; }) => { const { t } = useTranslation(); @@ -34,6 +35,7 @@ export const useConfirm = (props?: { const { title = map?.title || t('common:Warning'), iconSrc = map?.iconSrc, + iconColor, content, showCancel = true, hideFooter = false @@ -93,7 +95,13 @@ export const useConfirm = (props?: { }, [isOpen]); return ( - + {customContent} diff --git a/packages/web/i18n/zh-CN/account_info.json b/packages/web/i18n/zh-CN/account_info.json index efd52c90a..189e11425 100644 --- a/packages/web/i18n/zh-CN/account_info.json +++ b/packages/web/i18n/zh-CN/account_info.json @@ -71,5 +71,7 @@ "user_team_team_name": "团队名", "verification_code": "验证码", "you_can_convert": "您可以兑换", - "yuan": "元" + "yuan": "元", + "contact": "联系方式", + "please_bind_contact": "请绑定联系方式" } diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json index 91587efc2..d0fa3eb6d 100644 --- a/packages/web/i18n/zh-CN/account_team.json +++ b/packages/web/i18n/zh-CN/account_team.json @@ -1,7 +1,6 @@ { "action": "操作", "confirm_delete_group": "确认删除群组?", - "confirm_delete_member": "确认删除成员?", "confirm_delete_org": "确认删除该部门?", "confirm_leave_team": "确认离开该团队? \n退出后,您在该团队所有的资源均转让给团队所有者。", "create_group": "创建群组", @@ -26,7 +25,9 @@ "owner": "所有者", "permission": "权限", "remark": "备注", - "remove_tip": "确认将 {{username}} 移出团队?", + "remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“已离职”,不删除操作数据,账号下资源自动转让给团队所有者。", + "restore_tip": "确认将 {{username}} 加入团队吗?仅恢复该成员账号可用性及相关权限,无法恢复账号下资源。", + "restore_tip_title": "恢复确认", "retain_admin_permissions": "保留管理员权限", "search_member_group_name": "搜索成员/群组名称", "total_team_members": "共 {{amount}} 名成员", @@ -38,5 +39,17 @@ "waiting": "待接受", "sync_immediately": "立即同步", "sync_member_failed": "同步成员失败", - "sync_member_success": "同步成员成功" + "sync_member_success": "同步成员成功", + "contact": "联系方式", + "join_update_time": "加入/更新时间", + "leave": "已离职", + "search_member": "搜索成员", + "export_members": "导出成员", + "delete_from_team": "移出团队", + "delete_from_org": "移出部门", + "confirm_delete_from_team": "确认将 {{username}} 移出团队?", + "confirm_delete_from_org": "确认将 {{username}} 移出部门?", + "search_org": "搜索部门", + "notification_recieve": "团队通知接收", + "set_name_avatar": "团队头像 & 团队名" } diff --git a/projects/app/package.json b/projects/app/package.json index a04eb75a0..81c963767 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.8.21", + "version": "4.8.22", "private": false, "scripts": { "dev": "next dev", diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index 3e8fa907c..0a9c284f5 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -21,9 +21,7 @@ const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/U const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal')); const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal')); const ImportantInform = dynamic(() => import('@/components/support/user/inform/ImportantInform')); -const UpdateNotification = dynamic( - () => import('@/components/support/user/inform/UpdateNotificationModal') -); +const UpdateContact = dynamic(() => import('@/components/support/user/inform/UpdateContactModal')); const pcUnShowLayoutRoute: Record = { '/': true, @@ -156,7 +154,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { {notSufficientModalType && } {!!userInfo && } {showUpdateNotification && ( - setIsUpdateNotification(false)} /> + setIsUpdateNotification(false)} mode="contact" /> )} {!!userInfo && importantInforms.length > 0 && ( diff --git a/projects/app/src/components/support/permission/MemberManager/MemberItemCard.tsx b/projects/app/src/components/support/permission/MemberManager/MemberItemCard.tsx new file mode 100644 index 000000000..d928953e5 --- /dev/null +++ b/projects/app/src/components/support/permission/MemberManager/MemberItemCard.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Box, Checkbox, HStack, VStack } from '@chakra-ui/react'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import PermissionTags from './PermissionTags'; +import { PermissionValueType } from '@fastgpt/global/support/permission/type'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import OrgTags from '../../user/team/OrgTags'; + +function MemberItemCard({ + avatar, + key, + onChange, + isChecked, + onDelete, + name, + permission, + orgs +}: { + avatar: string; + key: string; + onChange: () => void; + isChecked?: boolean; + onDelete?: () => void; + name: string; + permission?: PermissionValueType; + orgs?: string[]; +}) { + return ( + <> + + {isChecked !== undefined && } + + + + {name} + {orgs && orgs.length > 0 && } + + {permission && } + {onDelete !== undefined && ( + + )} + + + ); +} + +export default MemberItemCard; diff --git a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx index d1eeaf408..46e04af52 100644 --- a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx @@ -37,6 +37,8 @@ import { getTeamMembers } from '@/web/support/user/team/api'; import { getGroupList } from '@/web/support/user/team/group/api'; import { getOrgList } 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'; const HoverBoxStyle = { bgColor: 'myGray.50', @@ -72,6 +74,12 @@ function MemberModal({ const [parentPath, setParentPath] = useState(''); + const { data: searchedData } = useRequest2(() => GetSearchUserGroupOrg(searchText), { + manual: false, + throttleWait: 500, + refreshDeps: [searchText] + }); + const paths = useMemo(() => { const splitPath = parentPath.split('/').filter(Boolean); return splitPath @@ -97,7 +105,10 @@ function MemberModal({ return orgs.find((org) => org.pathId === currentOrgId); }, [orgs, parentPath]); const filterOrgs: (OrgType & { count?: number })[] = useMemo(() => { - if (searchText) return orgs.filter((item) => item.name.includes(searchText)); + if (searchText && searchedData) { + const orgids = searchedData.orgs.map((item) => item._id); + return orgs.filter((org) => orgids.includes(String(org._id))); + } if (!searchText && filterClass !== 'org') return []; if (parentPath === '') { setParentPath(`/${orgs[0].pathId}`); @@ -110,27 +121,34 @@ function MemberModal({ count: item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length })); - }, [orgs, searchText, filterClass, parentPath]); + }, [searchText, filterClass, parentPath, orgs, searchedData]); const [selectedMemberIdList, setSelectedMembers] = useState([]); const filterMembers = useMemo(() => { - if (searchText) return members.filter((item) => item.memberName.includes(searchText)); + if (searchText) { + return searchedData?.members; + } if (!searchText && filterClass !== 'member' && filterClass !== 'org') return []; - if (currentOrg && filterClass === 'org') { return members.filter((item) => currentOrg.members.find((v) => v.tmbId === item.tmbId)); } return members; - }, [members, searchText, filterClass, currentOrg]); + }, [members, searchedData, searchText, filterClass, currentOrg]); const [selectedGroupIdList, setSelectedGroupIdList] = useState([]); const filterGroups = useMemo(() => { - if (searchText) return groups.filter((item) => item.name.includes(searchText)); + if (searchText) { + return searchedData?.groups.map((item) => ({ + groupName: item.name, + _id: item.id, + ...item + })); + } if (!searchText && filterClass !== 'group') return []; return groups; - }, [groups, searchText, filterClass]); + }, [searchText, filterClass, groups, searchedData]); const permissionList = useContextSelector(CollaboratorContext, (v) => v.permissionList); const getPerLabelList = useContextSelector(CollaboratorContext, (v) => v.getPerLabelList); @@ -146,6 +164,7 @@ function MemberModal({ CollaboratorContext, (v) => v.onUpdateCollaborators ); + const { runAsync: onConfirm, loading: isUpdating } = useRequest2( () => onUpdateCollaborators({ @@ -210,6 +229,7 @@ function MemberModal({ iconSrc={addOnly ? 'keyPrimary' : 'modal/AddClb'} title={addOnly ? t('user:team.add_permission') : t('user:team.add_collaborator')} minW="800px" + maxW={'60vw'} h={'100%'} maxH={'90vh'} isCentered @@ -300,136 +320,122 @@ function MemberModal({ )} - {filterClass && ( - - {filterOrgs.map((org) => { - const onChange = () => { - setSelectedOrgIdList((state) => { - if (state.includes(org._id)) { - return state.filter((v) => v !== org._id); - } - return [...state, org._id]; - }); - }; - const collaborator = collaboratorList?.find((v) => v.orgId === org._id); - return ( - - - - - {org.name} - {org.count && ( - <> - - {org.count} - - - )} - - + + {filterOrgs?.map((org) => { + const onChange = () => { + setSelectedOrgIdList((state) => { + if (state.includes(org._id)) { + return state.filter((v) => v !== org._id); + } + return [...state, org._id]; + }); + }; + const collaborator = collaboratorList?.find((v) => v.orgId === org._id); + return ( + + + + + {org.name} {org.count && ( - { - setParentPath(getOrgChildrenPath(org)); - e.stopPropagation(); - }} - /> + <> + + {org.count} + + )} - ); - })} - {filterMembers.map((member) => { - const onChange = () => { - setSelectedMembers((state) => { - if (state.includes(member.tmbId)) { - return state.filter((v) => v !== member.tmbId); - } - return [...state, member.tmbId]; - }); - }; - const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId); - return ( - - + {org.count && ( + { + setParentPath(getOrgChildrenPath(org)); + e.stopPropagation(); + }} /> - - - {member.memberName} - - - - ); - })} - {filterGroups.map((group) => { - const onChange = () => { - setSelectedGroupIdList((state) => { - if (state.includes(group._id)) { - return state.filter((v) => v !== group._id); - } - return [...state, group._id]; - }); - }; - const collaborator = collaboratorList?.find((v) => v.groupId === group._id); - return ( - - - - - {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} - - - - ); - })} - - )} + )} + + ); + })} + {filterMembers?.map((member) => { + const onChange = () => { + setSelectedMembers((state) => { + if (state.includes(member.tmbId)) { + return state.filter((v) => v !== member.tmbId); + } + return [...state, member.tmbId]; + }); + }; + const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId); + const memberOrgs = orgs.filter((org) => + org.members.find((v) => String(v.tmbId) === String(member.tmbId)) + ); + const memberPathIds = memberOrgs.map((org) => + (org.path + '/' + org.pathId).split('/').slice(0) + ); + const memberOrgNames = memberPathIds.map((pathIds) => + pathIds.map((id) => orgs.find((v) => v.pathId === id)?.name).join('/') + ); + return ( + + ); + })} + {filterGroups?.map((group) => { + const onChange = () => { + setSelectedGroupIdList((state) => { + if (state.includes(group._id)) { + return state.filter((v) => v !== group._id); + } + return [...state, group._id]; + }); + }; + const collaborator = collaboratorList?.find((v) => v.groupId === group._id); + return ( + + ); + })} + @@ -441,29 +447,27 @@ function MemberModal({ {selectedList.map((item) => { return ( - - - - {item.name} - - - + avatar={item.avatar} + name={item.name ?? ''} + onChange={item.onDelete} + onDelete={item.onDelete} + orgs={(() => { + if (!item.id.startsWith('member-')) return []; + const id = item.id.replace('member-', ''); + const memberOrgs = orgs.filter((org) => + org.members.find((v) => v.tmbId === id) + ); + const memberPathIds = memberOrgs.map((org) => + (org.path + '/' + org.pathId).split('/').slice(0) + ); + const memberOrgNames = memberPathIds.map((pathIds) => + pathIds.map((id) => orgs.find((v) => v.pathId === id)?.name).join('/') + ); + return memberOrgNames; + })()} + /> ); })} diff --git a/projects/app/src/components/support/user/inform/UpdateNotificationModal.tsx b/projects/app/src/components/support/user/inform/UpdateContactModal.tsx similarity index 80% rename from projects/app/src/components/support/user/inform/UpdateNotificationModal.tsx rename to projects/app/src/components/support/user/inform/UpdateContactModal.tsx index e6e220196..8d17f15f8 100644 --- a/projects/app/src/components/support/user/inform/UpdateNotificationModal.tsx +++ b/projects/app/src/components/support/user/inform/UpdateContactModal.tsx @@ -4,34 +4,48 @@ import MyModal from '@fastgpt/web/components/common/MyModal'; import { useTranslation } from 'next-i18next'; import { useForm } from 'react-hook-form'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { updateNotificationAccount } from '@/web/support/user/api'; +import { updateContact, updateNotificationAccount } from '@/web/support/user/api'; import Icon from '@fastgpt/web/components/common/Icon'; import { useSendCode } from '@/web/support/user/hooks/useSendCode'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useSystemStore } from '@/web/common/system/useSystemStore'; type FormType = { - account: string; + contact: string; verifyCode: string; }; -const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => { +const UpdateContactModal = ({ + onClose, + mode +}: { + onClose: () => void; + mode: 'contact' | 'notification_account'; +}) => { const { t } = useTranslation(); const { initUserInfo } = useUserStore(); const { feConfigs } = useSystemStore(); const { register, handleSubmit, watch } = useForm({ defaultValues: { - account: '', + contact: '', verifyCode: '' } }); - const account = watch('account'); + + const account = watch('contact'); const verifyCode = watch('verifyCode'); const { runAsync: onSubmit, loading: isLoading } = useRequest2( (data: FormType) => { - return updateNotificationAccount(data); + if (mode === 'contact') { + return updateContact(data); + } else { + return updateNotificationAccount({ + account: data.contact, + verifyCode: data.verifyCode + }); + } }, { onSuccess() { @@ -62,7 +76,11 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => { isOpen iconSrc="common/settingLight" w={'32rem'} - title={t('common:support.user.info.notification_receiving_hint')} + title={ + mode === 'notification_account' + ? t('common:support.user.info.notification_receiving_hint') + : t('account_info:contact') + } > @@ -75,7 +93,7 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => { @@ -108,4 +126,4 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => { ); }; -export default UpdateNotificationModal; +export default UpdateContactModal; diff --git a/projects/app/src/components/support/user/team/OrgTags/index.tsx b/projects/app/src/components/support/user/team/OrgTags/index.tsx new file mode 100644 index 000000000..4347f22e9 --- /dev/null +++ b/projects/app/src/components/support/user/team/OrgTags/index.tsx @@ -0,0 +1,39 @@ +import { Box, Flex, VStack } from '@chakra-ui/react'; +import MyPopover from '@fastgpt/web/components/common/MyPopover'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import Tag from '@fastgpt/web/components/common/Tag'; +import React from 'react'; + +function OrgTags({ orgs, type = 'simple' }: { orgs: string[]; type?: 'simple' | 'tag' }) { + return ( + + {orgs.map((org, index) => ( + + {org.slice(1)} + + ))} + + } + > + {type === 'simple' ? ( + + {orgs + .map((org) => org.split('/').pop()) + .join(', ') + .slice(0, 30)} + {orgs.length > 1 && '...'} + + ) : ( + + {orgs.map((org, index) => ( + {org.split('/').pop()} + ))} + + )} + + ); +} + +export default OrgTags; diff --git a/projects/app/src/pageComponents/account/team/EditInfoModal.tsx b/projects/app/src/pageComponents/account/team/EditInfoModal.tsx index d171089ea..e5c5bd4c5 100644 --- a/projects/app/src/pageComponents/account/team/EditInfoModal.tsx +++ b/projects/app/src/pageComponents/account/team/EditInfoModal.tsx @@ -2,18 +2,30 @@ import React from 'react'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'next-i18next'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; -import { useToast } from '@fastgpt/web/hooks/useToast'; import { useRequest } from '@fastgpt/web/hooks/useRequest'; import MyModal from '@fastgpt/web/components/common/MyModal'; -import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react'; +import { + Box, + Button, + Flex, + HStack, + Input, + ModalBody, + ModalFooter, + useDisclosure +} from '@chakra-ui/react'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import Avatar from '@fastgpt/web/components/common/Avatar'; import { postCreateTeam, putUpdateTeam } from '@/web/support/user/team/api'; import { CreateTeamProps } from '@fastgpt/global/support/user/team/controller.d'; import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants'; +import Icon from '@fastgpt/web/components/common/Icon'; +import dynamic from 'next/dynamic'; +const UpdateContact = dynamic(() => import('@/components/support/user/inform/UpdateContactModal')); export type EditTeamFormDataType = CreateTeamProps & { id?: string; + notificationAccount?: string; }; export const defaultForm = { @@ -31,12 +43,12 @@ function EditModal({ onSuccess: () => void; }) { const { t } = useTranslation(); - const { toast } = useToast(); const { register, setValue, handleSubmit, watch } = useForm({ defaultValues: defaultData }); const avatar = watch('avatar'); + const notificationAccount = watch('notificationAccount'); const { File, @@ -74,6 +86,8 @@ function EditModal({ errorToast: t('common:common.Update Failed') }); + const { isOpen: isOpenContact, onClose: onCloseContact, onOpen: onOpenContact } = useDisclosure(); + return ( - {t('user:team.Set Name')} + {t('account_team:set_name_avatar')} @@ -110,6 +124,38 @@ function EditModal({ })} /> + + {t('account_team:notification_recieve')} + + + {(() => { + return notificationAccount ? ( + {notificationAccount} + ) : ( + + + {t('account_info:please_bind_contact')} + + ); + })()} + + @@ -142,6 +188,14 @@ function EditModal({ }) } /> + {isOpenContact && ( + { + onCloseContact(); + }} + mode="notification_account" + /> + )} ); } diff --git a/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx index 847e28d5e..fbd8df13b 100644 --- a/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx +++ b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx @@ -148,7 +148,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro gridTemplateColumns="1fr 1fr" h={'100%'} > - + void; editGro setSearchKey(e.target.value); }} /> - + {filtered.map((member) => { return ( import('./InviteModal')); const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal')); @@ -43,14 +51,14 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { feConfigs, setNotSufficientModalType } = useSystemStore(); const { - groups, refetchGroups, myTeams, refetchTeams, members, refetchMembers, onSwitchTeam, - MemberScrollData + MemberScrollData, + orgs } = useContextSelector(TeamContext, (v) => v); const { @@ -64,8 +72,25 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { type: 'delete' }); + const { ConfirmModal: ConfirmRestoreMemberModal, openConfirm: openRestoreMember } = useConfirm({ + type: 'common', + title: t('account_team:restore_tip_title'), + iconSrc: 'common/confirm/restoreTip', + iconColor: 'primary.500' + }); + + const [searchText, setSearchText] = useState(''); const isSyncMember = feConfigs.register_method?.includes('sync'); + const { data: searchMembersData } = useRequest2( + () => GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false }), + { + manual: false, + throttleWait: 500, + refreshDeps: [searchText] + } + ); + const { runAsync: onLeaveTeam } = useRequest2( async () => { const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0]; @@ -92,12 +117,28 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { errorToast: t('account_team:sync_member_failed') }); + const { runAsync: onRestore, loading: isUpdateInvite } = useRequest2(updateStatus, { + onSuccess() { + refetchMembers(); + }, + successToast: t('common:user.team.invite.Accepted'), + errorToast: t('common:user.team.invite.Reject') + }); + + const isLoading = isUpdateInvite || isSyncing; + return ( <> - {isSyncing && } + {isLoading && } {Tabs} - + + + setSearchText(e.target.value)} + /> + {userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && ( + )} @@ -177,45 +235,66 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { {t('account_team:user_name')} - {t('account_team:member_group')} - {!isSyncMember && ( - - {t('common:common.Action')} - - )} + {t('account_team:contact')} + {t('account_team:org')} + {t('account_team:join_update_time')} + + {t('common:common.Action')} + - {members?.map((item) => ( - - - - - - {item.memberName} - {item.status === 'waiting' && ( - - {t('account_team:waiting')} - - )} - - - - - - group.members.map((m) => m.tmbId).includes(item.tmbId) - ) - .map((g) => g.name)} - max={3} - /> - - {!isSyncMember && ( + {(searchText && searchMembersData ? searchMembersData.members : members).map( + (member) => ( + + + + + + {member.memberName} + {member.status === 'waiting' && ( + + {t('account_team:waiting')} + + )} + {member.status === 'leave' && ( + + {t('account_team:leave')} + + )} + + + + {member.contact || '-'} + + {(() => { + const memberOrgs = orgs.filter((org) => + org.members.find((v) => String(v.tmbId) === String(member.tmbId)) + ); + const memberPathIds = memberOrgs.map((org) => + (org.path + '/' + org.pathId).split('/').slice(0) + ); + const memberOrgNames = memberPathIds.map((pathIds) => + pathIds.map((id) => orgs.find((v) => v.pathId === id)?.name).join('/') + ); + return ; + })()} + + + + {format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')} + + {member.updateTime + ? format(new Date(member.updateTime), 'yyyy-MM-dd HH:mm:ss') + : '-'} + + + {userInfo?.team.permission.hasManagePer && - item.role !== TeamMemberRoleEnum.owner && - item.tmbId !== userInfo?.team.tmbId && ( + member.role !== TeamMemberRoleEnum.owner && + member.tmbId !== userInfo?.team.tmbId && + (member.status !== TeamMemberStatusEnum.leave ? ( { openRemoveMember( () => - delRemoveMember(item.tmbId).then(() => + delRemoveMember(member.tmbId).then(() => Promise.all([refetchGroups(), refetchMembers()]) ), undefined, t('account_team:remove_tip', { - username: item.memberName + username: member.memberName }) )(); }} /> - )} + ) : ( + { + openRestoreMember( + () => + onRestore({ + tmbId: member.tmbId, + status: TeamMemberStatusEnum.active + }), + undefined, + t('account_team:restore_tip', { + username: member.memberName + }) + )(); + }} + /> + ))} - )} - - ))} + + ) + )} + diff --git a/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx index 8ecd21739..ca726383a 100644 --- a/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx +++ b/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx @@ -112,7 +112,7 @@ function OrgMemberManageModal({ gridTemplateColumns="1fr 1fr" h={'100%'} > - + - + {filterMembers.map((member) => { return ( import('./OrgInfoModal')); const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal')); @@ -74,8 +76,9 @@ function ActionButton({ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); const { userInfo, isTeamAdmin } = useUserStore(); + const [searchOrg, setSearchOrg] = useState(''); - const { members, MemberScrollData } = useContextSelector(TeamContext, (v) => v); + const { members, MemberScrollData, refetchMembers } = useContextSelector(TeamContext, (v) => v); const { feConfigs } = useSystemStore(); const isSyncMember = feConfigs.register_method?.includes('sync'); @@ -88,6 +91,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { manual: false, refreshDeps: [userInfo?.team?.teamId] }); + const currentOrgs = useMemo(() => { if (orgs.length === 0) return []; // Auto select the first org(root org is team) @@ -107,6 +111,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { }; }); }, [orgs, parentPath]); + const currentOrg = useMemo(() => { const splitPath = parentPath.split('/'); const currentOrgId = splitPath[splitPath.length - 1]; @@ -121,7 +126,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { .map((id) => { const org = orgs.find((org) => org.pathId === id)!; - if (org.path === '') return; + if (org?.path === '') return; return { parentId: getOrgChildrenPath(org), @@ -148,20 +153,52 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { }); // Delete member - const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({ - type: 'delete', - content: t('account_team:confirm_delete_member') - }); + const { ConfirmModal: ConfirmDeleteMemberFromOrg, openConfirm: openDeleteMemberFromOrgModal } = + useConfirm({ + type: 'delete' + }); + + const { ConfirmModal: ConfirmDeleteMemberFromTeam, openConfirm: openDeleteMemberFromTeamModal } = + useConfirm({ + type: 'delete' + }); + const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, { onSuccess: () => { refetchOrgs(); } }); + const { runAsync: deleteMemberFromTeamReq } = useRequest2(delRemoveMember, { + onSuccess: () => { + refetchOrgs(); + refetchMembers(); + } + }); + + const searchedOrgs = useMemo(() => { + if (!searchOrg) return []; + + return orgs + .filter((org) => org.name.includes(searchOrg)) + .map((org) => ({ + ...org, + count: + org.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(org)).length + })); + }, [orgs, searchOrg]); + return ( <> {Tabs} + + setSearchOrg(e.target.value)} + /> + {t('common:Name')} - {!isSyncMember && ( - - {t('common:common.Action')} - - )} + + {t('common:common.Action')} + - {currentOrgs.map((org) => ( - + {searchedOrgs.map((org) => ( + setParentPath(getOrgChildrenPath(org))} + > setParentPath(getOrgChildrenPath(org))} + onClick={() => { + setParentPath(getOrgChildrenPath(org)); + setSearchOrg(''); + }} > {org.count} @@ -208,73 +250,130 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { /> - {isTeamAdmin && !isSyncMember && ( - - } - menuList={[ - { - children: [ - { - icon: 'edit', - label: t('account_team:edit_info'), - onClick: () => setEditOrg(org) - }, - { - icon: 'common/file/move', - label: t('common:Move'), - onClick: () => setMovingOrg(org) - }, - { - icon: 'delete', - label: t('account_team:delete'), - type: 'danger', - onClick: () => deleteOrgHandler(org._id) - } - ] - } - ]} - /> - - )} ))} - {currentOrg?.members.map((member) => { - const memberInfo = members.find((m) => m.tmbId === member.tmbId); - if (!memberInfo) return null; - - return ( - + {!searchOrg && + currentOrgs.map((org) => ( + - + { + setParentPath(getOrgChildrenPath(org)); + setSearchOrg(''); + }} + > + + {org.count} + + - - {isTeamAdmin && !isSyncMember && ( + {isTeamAdmin && !isSyncMember && ( + } menuList={[ { children: [ + { + icon: 'edit', + label: t('account_team:edit_info'), + onClick: () => setEditOrg(org) + }, + { + icon: 'common/file/move', + label: t('common:Move'), + onClick: () => setMovingOrg(org) + }, { icon: 'delete', label: t('account_team:delete'), type: 'danger', - onClick: () => - openDeleteMemberModal(() => - deleteMemberReq(currentOrg._id, member.tmbId) - )() + onClick: () => deleteOrgHandler(org._id) } ] } ]} /> - )} - + + )} - ); - })} + ))} + {!searchOrg && + currentOrg?.members.map((member) => { + const memberInfo = members.find((m) => m.tmbId === member.tmbId); + if (!memberInfo) return null; + + return ( + + + + + + {isTeamAdmin && ( + } + menuList={[ + { + children: [ + { + menuItemStyles: { + _hover: { + color: 'red.600', + backgroundColor: 'red.50' + } + }, + label: t('account_team:delete_from_team', { + username: memberInfo.memberName + }), + onClick: () => { + openDeleteMemberFromTeamModal( + () => deleteMemberFromTeamReq(member.tmbId), + undefined, + t('account_team:confirm_delete_from_team', { + username: memberInfo.memberName + }) + )(); + } + }, + ...(isSyncMember + ? [] + : [ + { + menuItemStyles: { + _hover: { + color: 'red.600', + bgColor: 'red.50' + } + }, + label: t('account_team:delete_from_org'), + onClick: () => + openDeleteMemberFromOrgModal( + () => + deleteMemberReq(currentOrg._id, member.tmbId), + undefined, + t('account_team:confirm_delete_from_org', { + username: memberInfo.memberName + }) + )() + } + ]) + ] + } + ]} + /> + )} + + + ); + })} @@ -361,7 +460,8 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { )} - + + ); } diff --git a/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx b/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx index b2228a3c3..e480ff3e2 100644 --- a/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx +++ b/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { Box, Checkbox, @@ -40,7 +40,8 @@ import CollaboratorContextProvider, { } from '@/components/support/permission/MemberManager/context'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useContextSelector } from 'use-context-selector'; -import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator'; +import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; +import { GetSearchUserGroupOrg } from '@/web/support/user/api'; function PermissionManage({ Tabs, @@ -69,27 +70,37 @@ function PermissionManage({ const [isExpandGroup, setExpandGroup] = useToggle(true); const [isExpandOrg, setExpandOrg] = useToggle(true); - const { tmbList, groupList, orgList } = useMemo(() => { - const tmbList: CollaboratorItemType[] = []; - const groupList: CollaboratorItemType[] = []; - const orgList: CollaboratorItemType[] = []; + const [searchKey, setSearchKey] = useState(''); - collaboratorList.forEach((item) => { - if (item.tmbId) { - tmbList.push(item); - } else if (item.groupId) { - groupList.push(item); - } else if (item.orgId) { - orgList.push(item); - } - }); + const { data: searchResult } = useRequest2(() => GetSearchUserGroupOrg(searchKey), { + manual: false, + throttleWait: 500, + refreshDeps: [searchKey] + }); + + const { tmbList, groupList, orgList } = useMemo(() => { + const tmbList = collaboratorList.filter( + (item) => + Object.keys(item).includes('tmbId') && + (!searchKey || searchResult?.members.find((member) => member.tmbId === item.tmbId)) + ); + const groupList = collaboratorList.filter( + (item) => + Object.keys(item).includes('groupId') && + (!searchKey || searchResult?.groups.find((group) => group.groupId === item.groupId)) + ); + const orgList = collaboratorList.filter( + (item) => + Object.keys(item).includes('orgId') && + (!searchKey || searchResult?.orgs.find((org) => org._id === item.orgId)) + ); return { tmbList, groupList, orgList }; - }, [collaboratorList]); + }, [collaboratorList, searchResult, searchKey]); const { runAsync: onUpdatePermission, loading: addLoading } = useRequest2( async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: 'write' | 'manage' }) => { @@ -131,12 +142,12 @@ function PermissionManage({ {Tabs} - {/* setSearchKey(e.target.value)} - /> */} + /> {userInfo?.team.permission.hasManagePer && ( - )} + )} {feConfigs.isPlus && ( @@ -310,7 +297,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => { )} {isOpenUpdatePsw && } - {isOpenUpdateNotification && } + {isOpenUpdateContact && } onSelectImage(e, { diff --git a/projects/app/src/pages/account/team/index.tsx b/projects/app/src/pages/account/team/index.tsx index 57088d435..37b3b4ce6 100644 --- a/projects/app/src/pages/account/team/index.tsx +++ b/projects/app/src/pages/account/team/index.tsx @@ -104,7 +104,8 @@ const Team = () => { setEditTeamData({ id: userInfo.team.teamId, name: userInfo.team.teamName, - avatar: userInfo.team.avatar + avatar: userInfo.team.avatar, + notificationAccount: userInfo.team.notificationAccount }); }} /> diff --git a/projects/app/src/pages/api/admin/initv4822.ts b/projects/app/src/pages/api/admin/initv4822.ts new file mode 100644 index 000000000..8da6e2998 --- /dev/null +++ b/projects/app/src/pages/api/admin/initv4822.ts @@ -0,0 +1,29 @@ +import { NextAPI } from '@/service/middleware/entry'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { MongoUser } from '@fastgpt/service/support/user/schema'; +import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema'; +import { NextApiRequest, NextApiResponse } from 'next'; + +/* + * 复制 Team 表中的 notificationAccount 到 User 表的 contact 中 + */ +async function handler(req: NextApiRequest, _res: NextApiResponse) { + await authCert({ req, authRoot: true }); + const users = await MongoUser.find(); + const teams = await MongoTeam.find(); + for await (const user of users) { + try { + const team = teams.find((team) => String(team.ownerId) === String(user._id)); + if (team && !user.contact) { + user.contact = team.notificationAccount; + } + await user.save(); + } catch (error) { + console.error(error); + } + } + + return { success: true }; +} + +export default NextAPI(handler); diff --git a/projects/app/src/web/support/user/api.ts b/projects/app/src/web/support/user/api.ts index b1d076d37..910df96d4 100644 --- a/projects/app/src/web/support/user/api.ts +++ b/projects/app/src/web/support/user/api.ts @@ -7,7 +7,8 @@ import { UserType } from '@fastgpt/global/support/user/type.d'; import type { FastLoginProps, OauthLoginProps, - PostLoginProps + PostLoginProps, + SearchResult } from '@fastgpt/global/support/user/api.d'; import { AccountRegisterBody, @@ -70,6 +71,10 @@ export const updatePasswordByOld = ({ oldPsw, newPsw }: { oldPsw: string; newPsw export const updateNotificationAccount = (data: { account: string; verifyCode: string }) => PUT('/proApi/support/user/team/updateNotificationAccount', data); +export const updateContact = (data: { contact: string; verifyCode: string }) => { + return PUT('/proApi/support/user/account/updateContact', data); +}; + export const postLogin = ({ password, ...props }: PostLoginProps) => POST('/support/user/account/loginByPassword', { ...props, @@ -92,3 +97,14 @@ export const getCaptchaPic = (username: string) => }>('/proApi/support/user/account/captcha/getImgCaptcha', { username }); export const postSyncMembers = () => POST('/proApi/support/user/team/org/sync'); + +export const GetSearchUserGroupOrg = ( + searchKey: string, + options?: { + members?: boolean; + orgs?: boolean; + groups?: boolean; + } +) => GET('/proApi/support/user/search', { searchKey, ...options }); + +export const ExportMembers = () => GET<{ csv: string }>('/proApi/support/user/team/member/export'); diff --git a/projects/app/src/web/support/user/team/api.ts b/projects/app/src/web/support/user/team/api.ts index af49e4022..aa857a356 100644 --- a/projects/app/src/web/support/user/team/api.ts +++ b/projects/app/src/web/support/user/team/api.ts @@ -9,6 +9,7 @@ import { InviteMemberProps, InviteMemberResponse, UpdateInviteProps, + UpdateStatusProps, UpdateTeamProps } from '@fastgpt/global/support/user/team/controller.d'; import type { TeamTagItemType, TeamTagSchema } from '@fastgpt/global/support/user/team/type'; @@ -31,7 +32,7 @@ export const putSwitchTeam = (teamId: string) => PUT(`/proApi/support/user/team/switch`, { teamId }); /* --------------- team member ---------------- */ -export const getTeamMembers = (props: PaginationProps) => +export const getTeamMembers = (props: PaginationProps<{ withLeaved?: boolean }>) => GET>(`/proApi/support/user/team/member/list`, props); export const postInviteTeamMember = (data: InviteMemberProps) => POST(`/proApi/support/user/team/member/invite`, data); @@ -41,6 +42,8 @@ export const delRemoveMember = (tmbId: string) => DELETE(`/proApi/support/user/team/member/delete`, { tmbId }); export const updateInviteResult = (data: UpdateInviteProps) => PUT('/proApi/support/user/team/member/updateInvite', data); +export const updateStatus = (data: UpdateStatusProps) => + PUT('/proApi/support/user/team/member/updateStatus', data); export const delLeaveTeam = () => DELETE('/proApi/support/user/team/member/leave'); /* -------------- team collaborator -------------------- */