From 5192a1456f43ef97cd970943e9936af0d4cfdd88 Mon Sep 17 00:00:00 2001 From: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:22:20 +0800 Subject: [PATCH] feat: sync org from wecom, pref: member list pagination (#3549) * feat: sync org * chore: fe * chore: loading * chore: type * pref: team member list change to pagination. Edit a sort of list apis. * feat: member update avatar * chore: user avatar move to tmb * chore: init scripts move user avatar * chore: sourceMember * fix: list api sourceMember * fix: member sync * fix: pagination * chore: adjust code * chore: move changeOwner to pro * chore: init v4819 script * chore: adjust code * chore: UserBox --- .../global/common/system/types/index.d.ts | 3 +- packages/global/core/app/type.d.ts | 4 +- packages/global/core/app/version.d.ts | 3 + packages/global/core/dataset/type.d.ts | 2 + packages/global/core/workflow/type/node.d.ts | 1 + .../global/support/user/team/controller.d.ts | 1 + .../global/support/user/team/org/constant.ts | 8 +- packages/global/support/user/team/type.d.ts | 1 + packages/global/support/user/type.d.ts | 10 +- packages/service/common/api/pagination.ts | 21 ++ packages/service/common/middle/cors.ts | 2 +- .../service/support/permission/controller.ts | 9 +- packages/service/support/user/controller.ts | 2 +- packages/service/support/user/schema.ts | 11 +- .../service/support/user/team/controller.ts | 2 +- .../support/user/team/teamMemberSchema.ts | 6 +- packages/web/common/fetch/type.d.ts | 15 +- .../web/components/common/MySelect/index.tsx | 80 +++-- .../web/components/common/UserBox/index.tsx | 23 ++ packages/web/hooks/usePagination.tsx | 63 ++-- packages/web/i18n/zh-CN/account_team.json | 6 +- .../components/core/app/InputGuideConfig.tsx | 4 +- .../permission/ChangeOwnerModal/index.tsx | 57 ++-- .../permission/MemberManager/MemberModal.tsx | 287 +++++++++--------- projects/app/src/global/core/api/appReq.d.ts | 6 +- .../app/src/global/core/api/datasetReq.d.ts | 6 +- .../account/bill/components/BillTable.tsx | 17 +- .../account/bill/components/InvoiceTable.tsx | 5 +- projects/app/src/pages/account/info/index.tsx | 9 +- projects/app/src/pages/account/inform.tsx | 6 +- projects/app/src/pages/account/promotion.tsx | 5 +- .../account/team/components/MemberTable.tsx | 186 +++++++----- .../team/components/OrgManage/index.tsx | 124 ++++---- .../pages/account/team/components/context.tsx | 30 +- projects/app/src/pages/account/team/index.tsx | 9 +- .../app/src/pages/account/usage/index.tsx | 20 +- projects/app/src/pages/api/admin/initv4818.ts | 38 ++- projects/app/src/pages/api/admin/initv4819.ts | 37 +++ .../app/src/pages/api/core/app/getChatLogs.ts | 33 +- projects/app/src/pages/api/core/app/list.ts | 41 ++- .../src/pages/api/core/app/version/list.ts | 47 ++- .../api/core/app/version/publish.test.ts | 1 - .../src/pages/api/core/chat/getHistories.ts | 6 +- .../api/core/chat/getPaginationRecords.ts | 14 +- .../pages/api/core/chat/inputGuide/list.ts | 4 +- .../pages/api/core/dataset/collection/list.ts | 26 +- .../api/core/dataset/collection/scrollList.ts | 4 +- .../src/pages/api/core/dataset/data/list.ts | 18 +- .../pages/api/core/dataset/data/v2/list.ts | 4 +- .../app/src/pages/api/core/dataset/list.ts | 29 +- .../pages/api/support/user/account/update.ts | 41 ++- .../app/detail/components/Logs/index.tsx | 23 +- .../components/PublishHistoriesSlider.tsx | 33 +- .../Flow/NodeTemplatesModal.tsx | 21 +- .../src/pages/app/list/components/List.tsx | 19 +- .../components/CollectionCard/Context.tsx | 3 +- .../src/pages/dataset/list/component/List.tsx | 21 +- projects/app/src/types/app.d.ts | 3 + projects/app/src/types/index.d.ts | 9 - projects/app/src/web/core/app/api.ts | 4 +- projects/app/src/web/core/app/api/plugin.ts | 3 +- projects/app/src/web/core/app/api/version.ts | 2 +- .../core/chat/context/chatRecordContext.tsx | 4 +- projects/app/src/web/core/dataset/api.ts | 5 +- .../dataset/components/SelectCollections.tsx | 30 +- .../src/web/support/activity/promotion/api.ts | 11 +- projects/app/src/web/support/user/api.ts | 2 + .../app/src/web/support/user/inform/api.ts | 8 +- projects/app/src/web/support/user/team/api.ts | 5 +- .../app/src/web/support/user/useUserStore.ts | 20 +- .../app/src/web/support/wallet/bill/api.ts | 8 +- .../web/support/wallet/bill/invoice/api.ts | 6 +- .../app/src/web/support/wallet/usage/api.ts | 15 +- 73 files changed, 883 insertions(+), 759 deletions(-) create mode 100644 packages/service/common/api/pagination.ts create mode 100644 packages/web/components/common/UserBox/index.tsx create mode 100644 projects/app/src/pages/api/admin/initv4819.ts diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index 90bcb7dfd108..64b7bd0a42ff 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -40,7 +40,7 @@ export type FastGPTConfigFileType = { export type FastGPTFeConfigsType = { show_emptyChat?: boolean; - register_method?: ['email' | 'phone']; + register_method?: ['email' | 'phone' | 'sync']; login_method?: ['email' | 'phone']; // Attention: login method is diffrent with oauth find_password_method?: ['email' | 'phone']; bind_notification_method?: ['email' | 'phone']; @@ -76,7 +76,6 @@ export type FastGPTFeConfigsType = { wecom?: { corpid?: string; agentid?: string; - secret?: string; }; microsoft?: { clientId?: string; diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 86885c71e375..899628936ed9 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -12,8 +12,9 @@ import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/use import { StoreEdgeItemType } from '../workflow/type/edge'; import { AppPermission } from '../../support/permission/app/controller'; import { ParentIdType } from '../../common/parentFolder/type'; -import { FlowNodeInputTypeEnum } from 'core/workflow/node/constant'; +import { FlowNodeInputTypeEnum } from '../../core/workflow/node/constant'; import { WorkflowTemplateBasicType } from '@fastgpt/global/core/workflow/type'; +import { SourceMemberType } from '../../support/user/type'; export type AppSchema = { _id: string; @@ -63,6 +64,7 @@ export type AppListItemType = { permission: AppPermission; inheritPermission?: boolean; private?: boolean; + sourceMember: SourceMemberType; }; export type AppDetailType = AppSchema & { diff --git a/packages/global/core/app/version.d.ts b/packages/global/core/app/version.d.ts index 178834ce4181..dbf0ae53f1f0 100644 --- a/packages/global/core/app/version.d.ts +++ b/packages/global/core/app/version.d.ts @@ -1,5 +1,7 @@ +import { TeamMemberStatusEnum } from 'support/user/team/constant'; import { StoreEdgeItemType } from '../workflow/type/edge'; import { AppChatConfigType, AppSchema } from './type'; +import { SourceMemberType } from 'support/user/type'; export type AppVersionSchemaType = { _id: string; @@ -20,4 +22,5 @@ export type VersionListItemType = { time: Date; isPublish: boolean | undefined; tmbId: string; + sourceMember: SourceMemberType; }; diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index 7a772f78d481..70d3211d1b8c 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -11,6 +11,7 @@ import { import { DatasetPermission } from '../../support/permission/dataset/controller'; import { Permission } from '../../support/permission/controller'; import { APIFileServer, FeishuServer, YuqueServer } from './apiDataset'; +import { SourceMemberType } from 'support/user/type'; export type DatasetSchemaType = { _id: string; @@ -165,6 +166,7 @@ export type DatasetListItemType = { vectorModel: VectorModelItemType; inheritPermission: boolean; private?: boolean; + sourceMember?: SourceMemberType; }; export type DatasetItemType = Omit & { diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts index d0bc5b9197fb..691fc96c9a89 100644 --- a/packages/global/core/workflow/type/node.d.ts +++ b/packages/global/core/workflow/type/node.d.ts @@ -89,6 +89,7 @@ export type NodeTemplateListItemType = { hasTokenFee?: boolean; // 是否配置积分 instructions?: string; // 使用说明 courseUrl?: string; // 教程链接 + sourceMember?: SourceMember; }; export type NodeTemplateListType = { diff --git a/packages/global/support/user/team/controller.d.ts b/packages/global/support/user/team/controller.d.ts index 98a64af30359..fb331d0de226 100644 --- a/packages/global/support/user/team/controller.d.ts +++ b/packages/global/support/user/team/controller.d.ts @@ -12,6 +12,7 @@ export type CreateTeamProps = { avatar?: string; defaultTeam?: boolean; memberName?: string; + memberAvatar?: string; }; export type UpdateTeamProps = Omit & { name?: string; diff --git a/packages/global/support/user/team/org/constant.ts b/packages/global/support/user/team/org/constant.ts index 370cdad82901..c5aea21abb34 100644 --- a/packages/global/support/user/team/org/constant.ts +++ b/packages/global/support/user/team/org/constant.ts @@ -5,8 +5,6 @@ export const OrgMemberCollectionName = 'team_org_members'; export const getOrgChildrenPath = (org: OrgSchemaType) => `${org.path}/${org.pathId}`; -// export enum OrgMemberRole { -// owner = 'owner', -// admin = 'admin', -// member = 'member' -// } +export enum SyncOrgSourceEnum { + wecom = 'wecom' +} diff --git a/packages/global/support/user/team/type.d.ts b/packages/global/support/user/team/type.d.ts index 9c7dc12b42ba..6f063576f8eb 100644 --- a/packages/global/support/user/team/type.d.ts +++ b/packages/global/support/user/team/type.d.ts @@ -44,6 +44,7 @@ export type TeamMemberSchema = { name: string; role: `${TeamMemberRoleEnum}`; status: `${TeamMemberStatusEnum}`; + avatar: string; defaultTeam: boolean; }; diff --git a/packages/global/support/user/type.d.ts b/packages/global/support/user/type.d.ts index 0f8729266d76..13f6dd52281a 100644 --- a/packages/global/support/user/type.d.ts +++ b/packages/global/support/user/type.d.ts @@ -1,12 +1,12 @@ import { TeamPermission } from '../permission/user/controller'; import { UserStatusEnum } from './constant'; +import { TeamMemberStatusEnum } from './team/constant'; import { TeamTmbItemType } from './team/type'; export type UserModelSchema = { _id: string; username: string; password: string; - avatar: string; promotionRate: number; inviterId?: string; openaiKey: string; @@ -22,7 +22,7 @@ export type UserModelSchema = { export type UserType = { _id: string; username: string; - avatar: string; + avatar: string; // it should be team member's avatar after 4.8.18 timezone: string; promotionRate: UserModelSchema['promotionRate']; team: TeamTmbItemType; @@ -30,3 +30,9 @@ export type UserType = { notificationAccount?: string; permission: TeamPermission; }; + +export type SourceMemberType = { + name: string; + avatar: string; + status: `${TeamMemberStatusEnum}`; +}; diff --git a/packages/service/common/api/pagination.ts b/packages/service/common/api/pagination.ts new file mode 100644 index 000000000000..052cd41f58ad --- /dev/null +++ b/packages/service/common/api/pagination.ts @@ -0,0 +1,21 @@ +import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; +import { ApiRequestProps } from '../../type/next'; + +export function parsePaginationRequest(req: ApiRequestProps) { + const { + pageSize = 10, + pageNum = 1, + offset = 0 + } = Object.keys(req.body).includes('pageSize') + ? req.body + : Object.keys(req.query).includes('pageSize') + ? req.query + : {}; + if (!pageSize || (pageNum === undefined && offset === undefined)) { + throw new Error(CommonErrEnum.missingParams); + } + return { + pageSize: Number(pageSize), + offset: offset ? Number(offset) : (Number(pageNum) - 1) * Number(pageSize) + }; +} diff --git a/packages/service/common/middle/cors.ts b/packages/service/common/middle/cors.ts index 73b294162603..294dbe5a4426 100644 --- a/packages/service/common/middle/cors.ts +++ b/packages/service/common/middle/cors.ts @@ -2,7 +2,7 @@ import type { NextApiResponse, NextApiRequest } from 'next'; import NextCors from 'nextjs-cors'; export async function withNextCors(req: NextApiRequest, res: NextApiResponse) { - const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE']; + const methods = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE']; const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(','); const origin = req.headers.origin; diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts index f233c66b19d0..19b7058f6cad 100644 --- a/packages/service/support/permission/controller.ts +++ b/packages/service/support/permission/controller.ts @@ -17,7 +17,6 @@ import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type'; -import { UserModelSchema } from '@fastgpt/global/support/user/type'; import { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type'; import { getOrgIdSetWithParentByTmbId } from './org/controllers'; @@ -151,13 +150,9 @@ export const getClbsAndGroupsWithInfo = async ({ $exists: true } }) - .populate<{ tmb: TeamMemberSchema & { user: UserModelSchema } }>({ + .populate<{ tmb: TeamMemberSchema }>({ path: 'tmb', - select: 'name userId role', - populate: { - path: 'user', - select: 'avatar' - } + select: 'name userId avatar' }) .lean(), MongoResourcePermission.find({ diff --git a/packages/service/support/user/controller.ts b/packages/service/support/user/controller.ts index cf432b214cc1..ab28aac72c63 100644 --- a/packages/service/support/user/controller.ts +++ b/packages/service/support/user/controller.ts @@ -41,7 +41,7 @@ export async function getUserDetail({ return { _id: user._id, username: user.username, - avatar: user.avatar, + avatar: tmb.avatar, timezone: user.timezone, promotionRate: user.promotionRate, team: tmb, diff --git a/packages/service/support/user/schema.ts b/packages/service/support/user/schema.ts index 10094ee6fbbb..78234fd34bb4 100644 --- a/packages/service/support/user/schema.ts +++ b/packages/service/support/user/schema.ts @@ -3,7 +3,6 @@ const { Schema } = connectionMongo; import { hashStr } from '@fastgpt/global/common/string/tools'; import type { UserModelSchema } from '@fastgpt/global/support/user/type'; import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant'; -import { getRandomUserAvatar } from '@fastgpt/global/support/user/utils'; export const userCollectionName = 'users'; @@ -33,11 +32,6 @@ const UserSchema = new Schema({ type: Date, default: () => new Date() }, - avatar: { - type: String, - default: () => getRandomUserAvatar() - }, - promotionRate: { type: Number, default: 15 @@ -62,7 +56,10 @@ const UserSchema = new Schema({ ref: userCollectionName }, fastgpt_sem: Object, - sourceDomain: String + sourceDomain: String, + + /** @deprecated */ + avatar: String }); try { diff --git a/packages/service/support/user/team/controller.ts b/packages/service/support/user/team/controller.ts index 451e02f5b680..d48d9bf0b2b5 100644 --- a/packages/service/support/user/team/controller.ts +++ b/packages/service/support/user/team/controller.ts @@ -37,7 +37,7 @@ async function getTeamMember(match: Record): Promise = T & { - offset: number; - pageSize: number; -}; -export type PaginationResponse = { +import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; + +type PaginationProps = T & { + pageSize: number | string; +} & RequireOnlyOne<{ + offset: number | string; + pageNum: number | string; + }>; + +type PaginationResponse = { total: number; list: T[]; }; diff --git a/packages/web/components/common/MySelect/index.tsx b/packages/web/components/common/MySelect/index.tsx index 7d503d21ba57..d9401b38cf69 100644 --- a/packages/web/components/common/MySelect/index.tsx +++ b/packages/web/components/common/MySelect/index.tsx @@ -21,7 +21,15 @@ import type { ButtonProps, MenuItemProps } from '@chakra-ui/react'; import MyIcon from '../Icon'; import { useRequest2 } from '../../../hooks/useRequest'; import MyDivider from '../MyDivider'; +import { useScrollPagination } from '../../../hooks/useScrollPagination'; +/** 选择组件 Props 类型 + * value: 选中的值 + * placeholder: 占位符 + * list: 列表数据 + * isLoading: 是否加载中 + * ScrollData: 分页滚动数据控制器 [useScrollPagination] 的返回值 + * */ export type SelectProps = ButtonProps & { value?: T; placeholder?: string; @@ -34,6 +42,7 @@ export type SelectProps = ButtonProps & { }[]; isLoading?: boolean; onchange?: (val: T) => any | Promise; + ScrollData?: ReturnType['ScrollData']; }; const MySelect = ( @@ -44,6 +53,7 @@ const MySelect = ( list = [], onchange, isLoading = false, + ScrollData, ...props }: SelectProps, ref: ForwardedRef<{ @@ -154,39 +164,45 @@ const MySelect = ( maxH={'40vh'} overflowY={'auto'} > - {list.map((item, i) => ( - - { + const component = list.map((item, i) => ( + + { + if (onChange && value !== item.value) { + onChange(item.value); } - : { - color: 'myGray.900' - })} - onClick={() => { - if (onChange && value !== item.value) { - onChange(item.value); - } - }} - whiteSpace={'pre-wrap'} - fontSize={'sm'} - display={'block'} - > - {item.label} - {item.description && ( - - {item.description} - - )} - - {item.showBorder && } - - ))} + }} + whiteSpace={'pre-wrap'} + fontSize={'sm'} + display={'block'} + > + {item.label} + {item.description && ( + + {item.description} + + )} + + {item.showBorder && } + + )); + if (ScrollData) { + return {component}; + } + return component; + })()} diff --git a/packages/web/components/common/UserBox/index.tsx b/packages/web/components/common/UserBox/index.tsx new file mode 100644 index 000000000000..b57e96ea354e --- /dev/null +++ b/packages/web/components/common/UserBox/index.tsx @@ -0,0 +1,23 @@ +import { Box, HStack, type StackProps } from '@chakra-ui/react'; +import { SourceMemberType } from '@fastgpt/global/support/user/type'; +import React from 'react'; +import Avatar from '../Avatar'; +import { useTranslation } from 'next-i18next'; +import Tag from '../Tag'; + +export type UserBoxProps = { + sourceMember: SourceMemberType; + avatarSize?: string; +} & StackProps; +function UserBox({ sourceMember, avatarSize = '1.25rem', ...props }: UserBoxProps) { + const { t } = useTranslation(); + return ( + + + {sourceMember.name} + {sourceMember.status === 'leave' && {t('account_team:leaved')}} + + ); +} + +export default React.memo(UserBox); diff --git a/packages/web/hooks/usePagination.tsx b/packages/web/hooks/usePagination.tsx index 477c3267237b..5e65d11f34e3 100644 --- a/packages/web/hooks/usePagination.tsx +++ b/packages/web/hooks/usePagination.tsx @@ -4,7 +4,6 @@ import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons'; import { useTranslation } from 'next-i18next'; import { useToast } from './useToast'; import { getErrText } from '@fastgpt/global/common/error/utils'; - import { useBoolean, useLockFn, @@ -14,37 +13,33 @@ import { useThrottleEffect } from 'ahooks'; -const thresholdVal = 200; +import { PaginationProps, PaginationResponse } from '../common/fetch/type'; -type PagingData = { - pageNum: number; - pageSize: number; - data: T[]; - total?: number; -}; +const thresholdVal = 200; -export function usePagination({ - api, - pageSize = 10, - params = {}, - defaultRequest = true, - type = 'button', - onChange, - refreshDeps, - scrollLoadType = 'bottom', - EmptyTip -}: { - api: (data: any) => Promise>; - pageSize?: number; - params?: Record; - defaultRequest?: boolean; - type?: 'button' | 'scroll'; - onChange?: (pageNum: number) => void; - refreshDeps?: any[]; - throttleWait?: number; - scrollLoadType?: 'top' | 'bottom'; - EmptyTip?: React.JSX.Element; -}) { +export function usePagination( + api: (data: PaginationProps) => Promise>, + { + pageSize = 10, + params, + defaultRequest = true, + type = 'button', + onChange, + refreshDeps, + scrollLoadType = 'bottom', + EmptyTip + }: { + pageSize?: number; + params?: DataT; + defaultRequest?: boolean; + type?: 'button' | 'scroll'; + onChange?: (pageNum: number) => void; + refreshDeps?: any[]; + throttleWait?: number; + scrollLoadType?: 'top' | 'bottom'; + EmptyTip?: React.JSX.Element; + } +) { const { toast } = useToast(); const { t } = useTranslation(); @@ -64,7 +59,7 @@ export function usePagination({ setTrue(); try { - const res: PagingData = await api({ + const res = await api({ pageNum: num, pageSize, ...params @@ -93,13 +88,13 @@ export function usePagination({ ); } - setData((prevData) => (num === 1 ? res.data : [...res.data, ...prevData])); + setData((prevData) => (num === 1 ? res.list : [...res.list, ...prevData])); adjustScrollPosition(); } else { - setData((prevData) => (num === 1 ? res.data : [...prevData, ...res.data])); + setData((prevData) => (num === 1 ? res.list : [...prevData, ...res.list])); } } else { - setData(res.data); + setData(res.list); } onChange?.(num); diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json index e562ef7c9989..fcaad74c485b 100644 --- a/packages/web/i18n/zh-CN/account_team.json +++ b/packages/web/i18n/zh-CN/account_team.json @@ -35,5 +35,9 @@ "user_team_invite_member": "邀请成员", "user_team_leave_team": "离开团队", "user_team_leave_team_failed": "离开团队失败", - "waiting": "待接受" + "leaved": "已离职", + "waiting": "待接受", + "sync_immediately": "立即同步", + "sync_member_failed": "同步成员失败", + "sync_member_success": "同步成员成功" } diff --git a/projects/app/src/components/core/app/InputGuideConfig.tsx b/projects/app/src/components/core/app/InputGuideConfig.tsx index 13c3920ba2c0..91a83d24af2e 100644 --- a/projects/app/src/components/core/app/InputGuideConfig.tsx +++ b/projects/app/src/components/core/app/InputGuideConfig.tsx @@ -54,7 +54,7 @@ const InputGuideConfig = ({ onChange: (e: ChatInputGuideConfigType) => void; }) => { const { t } = useTranslation(); - const { chatT, commonT } = useI18n(); + const { chatT } = useI18n(); const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen: isOpenLexiconConfig, @@ -220,7 +220,7 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () => }); const { run: createNewData, loading: isCreating } = useRequest2( - (textList: string[]) => { + async (textList: string[]) => { if (textList.filter(Boolean).length === 0) { return Promise.resolve(); } diff --git a/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx b/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx index 501f441c41e8..5818a39d78b4 100644 --- a/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx +++ b/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx @@ -1,4 +1,4 @@ -import { useUserStore } from '@/web/support/user/useUserStore'; +import { getTeamMembers } from '@/web/support/user/team/api'; import { Box, Flex, @@ -15,6 +15,7 @@ import Icon from '@fastgpt/web/components/common/Icon'; import MyModal from '@fastgpt/web/components/common/MyModal'; import MyTag from '@fastgpt/web/components/common/Tag'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; import { useTranslation } from 'next-i18next'; import React, { useState } from 'react'; @@ -31,13 +32,12 @@ export function ChangeOwnerModal({ onChangeOwner }: ChangeOwnerModalProps & { onClose: () => void }) { const { t } = useTranslation(); - const { loadAndGetTeamMembers } = useUserStore(); - const [inputValue, setInputValue] = React.useState(''); - const { data: teamMembers = [] } = useRequest2(loadAndGetTeamMembers, { - manual: false + const { data: teamMembers, ScrollData } = useScrollPagination(getTeamMembers, { + pageSize: 15 }); + const memberList = teamMembers.filter((item) => { return item.memberName.includes(inputValue); }); @@ -101,11 +101,6 @@ export function ChangeOwnerModal({ onOpenMemberListMenu(); setSelectedMember(null); }} - // onBlur={() => { - // setTimeout(() => { - // onCloseMemberListMenu(); - // }, 10); - // }} {...(selectedMember && { pl: '10' })} /> @@ -123,26 +118,28 @@ export function ChangeOwnerModal({ maxH={'300px'} overflow={'auto'} > - {memberList.map((item) => ( - { - setInputValue(item.memberName); - setSelectedMember(item); - onCloseMemberListMenu(); - }} - > - - - {item.memberName} - - - ))} + + {memberList.map((item) => ( + { + setInputValue(item.memberName); + setSelectedMember(item); + onCloseMemberListMenu(); + }} + > + + + {item.memberName} + + + ))} + )} diff --git a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx index e51049b33a1a..46ab85954d76 100644 --- a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx @@ -33,6 +33,10 @@ import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type import { 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 } from '@/web/support/user/team/org/api'; +import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; const HoverBoxStyle = { bgColor: 'myGray.50', @@ -47,30 +51,27 @@ function MemberModal({ addPermissionOnly?: boolean; }) { const { t } = useTranslation(); - const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, loadAndGetOrgs } = useUserStore(); - + const { userInfo, myGroups } = useUserStore(); const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList); - const [searchText, setSearchText] = useState(''); const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>(); + const { data: members, ScrollData } = useScrollPagination(getTeamMembers, { + pageSize: 15 + }); - const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } = - useRequest2( - async () => { - if (!userInfo?.team?.teamId) return [[], []]; - return Promise.all([ - loadAndGetTeamMembers(true), - loadAndGetGroups(true), - loadAndGetOrgs(true) - ]); - }, - { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - } - ); + const { data: [groups = [], orgs = []] = [], loading: loadingGroupsAndOrgs } = useRequest2( + async () => { + if (!userInfo?.team?.teamId) return [[], []]; + return Promise.all([getGroupList(), getOrgList()]); + }, + { + manual: false, + refreshDeps: [userInfo?.team?.teamId] + } + ); const [parentPath, setParentPath] = useState(''); + const paths = useMemo(() => { const splitPath = parentPath.split('/').filter(Boolean); return splitPath @@ -212,7 +213,7 @@ function MemberModal({ h={'100%'} maxH={'90vh'} isCentered - isLoading={loadingMembersAndGroups} + isLoading={loadingGroupsAndOrgs} > - {filterMembers.map((member) => { - const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId); - const disabled = addOnly && collaborator !== undefined; - const onChange = () => { - if (disabled) return; - setSelectedMembers((state) => { - if (state.includes(member.tmbId)) { - return state.filter((v) => v !== member.tmbId); - } - return [...state, member.tmbId]; - }); - }; - return ( - - - - - {member.memberName} - - - - ); - })} - {filterOrgs.map((org) => { - const collaborator = collaboratorList?.find((v) => v.orgId === org._id); - const disabled = addOnly && collaborator !== undefined; - const onChange = () => { - if (disabled) return; - setSelectedOrgIdList((state) => { - if (state.includes(org._id)) { - return state.filter((v) => v !== org._id); - } - return [...state, org._id]; - }); - }; - return ( - - - - - {org.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); + return ( + + + + + {member.memberName} + + + + ); + })} + {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} + + + )} + + {org.count && ( - - {org.count} - + { + setParentPath(getOrgChildrenPath(org)); + }} + /> )} - - {org.count && ( - { - e.stopPropagation(); - setParentPath(getOrgChildrenPath(org)); - }} + ); + })} + {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 ( + + - )} - - ); - })} - {filterGroups.map((group) => { - const collaborator = collaboratorList?.find((v) => v.groupId === group._id); - const disabled = addOnly && collaborator !== undefined; - const onChange = () => { - if (disabled) return; - setSelectedGroupIdList((state) => { - if (state.includes(group._id)) { - return state.filter((v) => v !== group._id); - } - return [...state, group._id]; - }); - }; - return ( - - - - - {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} - - - - ); - })} + + + {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} + + + + ); + })} + diff --git a/projects/app/src/global/core/api/appReq.d.ts b/projects/app/src/global/core/api/appReq.d.ts index 7668e5355fa8..59a039afc158 100644 --- a/projects/app/src/global/core/api/appReq.d.ts +++ b/projects/app/src/global/core/api/appReq.d.ts @@ -1,7 +1,7 @@ -import { RequestPaging } from '@/types'; +import { PaginationProps } from '@fastgpt/web/common/fetch/type'; -export type GetAppChatLogsParams = RequestPaging & { +export type GetAppChatLogsParams = PaginationProps<{ appId: string; dateStart: Date; dateEnd: Date; -}; +}>; diff --git a/projects/app/src/global/core/api/datasetReq.d.ts b/projects/app/src/global/core/api/datasetReq.d.ts index e896df0e2066..22c69146640f 100644 --- a/projects/app/src/global/core/api/datasetReq.d.ts +++ b/projects/app/src/global/core/api/datasetReq.d.ts @@ -3,24 +3,24 @@ import { DatasetCollectionTypeEnum, DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; -import type { RequestPaging } from '@/types'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; import type { SearchTestItemType } from '@/types/core/dataset'; import { UploadChunkItemType } from '@fastgpt/global/core/dataset/type'; import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type'; import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; +import { PaginationProps } from '@fastgpt/web/common/fetch/type'; /* ===== dataset ===== */ /* ======= collections =========== */ -export type GetDatasetCollectionsProps = RequestPaging & { +export type GetDatasetCollectionsProps = PaginationProps<{ datasetId: string; parentId?: string; searchText?: string; filterTags?: string[]; simple?: boolean; selectFolder?: boolean; -}; +}>; /* ==== data ===== */ diff --git a/projects/app/src/pages/account/bill/components/BillTable.tsx b/projects/app/src/pages/account/bill/components/BillTable.tsx index 08c744692d81..228de85cb68d 100644 --- a/projects/app/src/pages/account/bill/components/BillTable.tsx +++ b/projects/app/src/pages/account/bill/components/BillTable.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useMemo, useEffect } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import { Button, Table, @@ -25,7 +25,6 @@ import { billStatusMap, billTypeMap } from '@fastgpt/global/support/wallet/bill/constants'; -// import { usePagination } from '@/web/common/hooks/usePagination'; import MyBox from '@fastgpt/web/components/common/MyBox'; import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants'; @@ -33,25 +32,23 @@ import MySelect from '@fastgpt/web/components/common/MySelect'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { usePagination } from '@fastgpt/web/hooks/usePagination'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; -import { useI18n } from '@/web/context/I18n'; const BillTable = () => { const { t } = useTranslation(); - const { commonT } = useI18n(); const { toast } = useToast(); - const [billType, setBillType] = useState(''); + const [billType, setBillType] = useState(undefined); const [billDetail, setBillDetail] = useState(); const billTypeList = useMemo( () => [ - { label: t('account_bill:all'), value: '' }, + { label: t('account_bill:all'), value: undefined }, ...Object.entries(billTypeMap).map(([key, value]) => ({ label: t(value.label as any), value: key })) ] as { label: string; - value: BillTypeEnum | ''; + value: BillTypeEnum | undefined; }[], [t] ); @@ -62,8 +59,7 @@ const BillTable = () => { Pagination, getData, total - } = usePagination({ - api: getBills, + } = usePagination(getBills, { pageSize: 20, params: { type: billType @@ -110,7 +106,7 @@ const BillTable = () => { # - + void }) { const { t } = useTranslation(); - const { commonT } = useI18n(); return ( { isLoading, Pagination, total - } = usePagination({ - api: getInvoiceRecords, + } = usePagination(getInvoiceRecords, { pageSize: 20 }); diff --git a/projects/app/src/pages/account/info/index.tsx b/projects/app/src/pages/account/info/index.tsx index 48c71cc31277..1adbff2167d6 100644 --- a/projects/app/src/pages/account/info/index.tsx +++ b/projects/app/src/pages/account/info/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useRef } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Box, Flex, @@ -160,6 +160,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => { color: 'myGray.900' }; + const isSyncMember = feConfigs.register_method?.includes('sync'); return ( {/* user info */} @@ -224,6 +225,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => { {t('account_info:member_name')}:  void }) => { const { feConfigs } = useSystemStore(); const { t } = useTranslation(); const { isPc } = useSystem(); - const { userInfo, updateUserInfo } = useUserStore(); - - const { reset } = useForm({ - defaultValues: userInfo as UserType - }); return ( diff --git a/projects/app/src/pages/account/inform.tsx b/projects/app/src/pages/account/inform.tsx index 176abe602ced..d0612c3385f7 100644 --- a/projects/app/src/pages/account/inform.tsx +++ b/projects/app/src/pages/account/inform.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { Box, Button, Flex, useTheme } from '@chakra-ui/react'; import { getInforms, readInform } from '@/web/support/user/inform/api'; -import type { UserInformSchema } from '@fastgpt/global/support/user/inform/type'; import { formatTimeToChatTime } from '@fastgpt/global/common/string/time'; import { usePagination } from '@fastgpt/web/hooks/usePagination'; import { useLoading } from '@fastgpt/web/hooks/useLoading'; import { useTranslation } from 'next-i18next'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; -import AccountContainer, { TabEnum } from './components/AccountContainer'; +import AccountContainer from './components/AccountContainer'; import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; const InformTable = () => { @@ -23,8 +22,7 @@ const InformTable = () => { Pagination, getData, pageNum - } = usePagination({ - api: getInforms, + } = usePagination(getInforms, { pageSize: 20 }); diff --git a/projects/app/src/pages/account/promotion.tsx b/projects/app/src/pages/account/promotion.tsx index be9ed529d191..5da3e0b48815 100644 --- a/projects/app/src/pages/account/promotion.tsx +++ b/projects/app/src/pages/account/promotion.tsx @@ -25,7 +25,7 @@ import { usePagination } from '@fastgpt/web/hooks/usePagination'; import { useLoading } from '@fastgpt/web/hooks/useLoading'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; -import AccountContainer, { TabEnum } from './components/AccountContainer'; +import AccountContainer from './components/AccountContainer'; import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; const Promotion = () => { @@ -41,8 +41,7 @@ const Promotion = () => { total, pageSize, Pagination - } = usePagination({ - api: getPromotionRecords, + } = usePagination(getPromotionRecords, { pageSize: 20 }); diff --git a/projects/app/src/pages/account/team/components/MemberTable.tsx b/projects/app/src/pages/account/team/components/MemberTable.tsx index 08b9af2bcfea..da2aa1532f9b 100644 --- a/projects/app/src/pages/account/team/components/MemberTable.tsx +++ b/projects/app/src/pages/account/team/components/MemberTable.tsx @@ -13,7 +13,6 @@ import { Tr, useDisclosure } from '@chakra-ui/react'; -import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { useTranslation } from 'next-i18next'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; @@ -30,6 +29,8 @@ import { useToast } from '@fastgpt/web/hooks/useToast'; import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { delLeaveTeam } from '@/web/support/user/team/api'; +import { postSyncMembers } from '@/web/support/user/api'; +import MyLoading from '@fastgpt/web/components/common/MyLoading'; const InviteModal = dynamic(() => import('./InviteModal')); const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal')); @@ -40,8 +41,16 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { userInfo, teamPlanStatus } = useUserStore(); const { feConfigs, setNotSufficientModalType } = useSystemStore(); - const { groups, refetchGroups, myTeams, refetchTeams, members, refetchMembers, onSwitchTeam } = - useContextSelector(TeamContext, (v) => v); + const { + groups, + refetchGroups, + myTeams, + refetchTeams, + members, + refetchMembers, + onSwitchTeam, + MemberScrollData + } = useContextSelector(TeamContext, (v) => v); const { isOpen: isOpenTeamTagsAsync, @@ -54,6 +63,8 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { type: 'delete' }); + const isSyncMember = feConfigs.register_method?.includes('sync'); + const { runAsync: onLeaveTeam } = useRequest2( async () => { const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0]; @@ -72,8 +83,17 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { content: t('account_team:confirm_leave_team') }); + const { runAsync: onSyncMember, loading: isSyncing } = useRequest2(postSyncMembers, { + onSuccess() { + refetchMembers(); + }, + successToast: t('account_team:sync_member_success'), + errorToast: t('account_team:sync_member_failed') + }); + return ( <> + {isSyncing && } {Tabs} @@ -91,7 +111,21 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { {t('account_team:label_sync')} )} - {userInfo?.team.permission.hasManagePer && ( + {userInfo?.team.permission.hasManagePer && isSyncMember && ( + + )} + {userInfo?.team.permission.hasManagePer && !isSyncMember && (