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
This commit is contained in:
Finley Ge
2025-01-13 11:22:20 +08:00
committed by archer
parent a8d456f448
commit ec0cef09a2
73 changed files with 883 additions and 757 deletions

View File

@@ -1,8 +1,13 @@
export type PaginationProps<T = {}> = T & {
offset: number;
pageSize: number;
};
export type PaginationResponse<T = any> = {
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
type PaginationProps<T = {}> = T & {
pageSize: number | string;
} & RequireOnlyOne<{
offset: number | string;
pageNum: number | string;
}>;
type PaginationResponse<T = {}> = {
total: number;
list: T[];
};

View File

@@ -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<T = any> = ButtonProps & {
value?: T;
placeholder?: string;
@@ -34,6 +42,7 @@ export type SelectProps<T = any> = ButtonProps & {
}[];
isLoading?: boolean;
onchange?: (val: T) => any | Promise<any>;
ScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
};
const MySelect = <T = any,>(
@@ -44,6 +53,7 @@ const MySelect = <T = any,>(
list = [],
onchange,
isLoading = false,
ScrollData,
...props
}: SelectProps<T>,
ref: ForwardedRef<{
@@ -154,39 +164,45 @@ const MySelect = <T = any,>(
maxH={'40vh'}
overflowY={'auto'}
>
{list.map((item, i) => (
<Box key={i}>
<MenuItem
{...menuItemStyles}
{...(value === item.value
? {
ref: SelectedItemRef,
color: 'primary.700',
bg: 'myGray.100',
fontWeight: '600'
{(() => {
const component = list.map((item, i) => (
<Box key={i}>
<MenuItem
{...menuItemStyles}
{...(value === item.value
? {
ref: SelectedItemRef,
color: 'primary.700',
bg: 'myGray.100',
fontWeight: '600'
}
: {
color: 'myGray.900'
})}
onClick={() => {
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'}
>
<Box>{item.label}</Box>
{item.description && (
<Box color={'myGray.500'} fontSize={'xs'}>
{item.description}
</Box>
)}
</MenuItem>
{item.showBorder && <MyDivider my={2} />}
</Box>
))}
}}
whiteSpace={'pre-wrap'}
fontSize={'sm'}
display={'block'}
>
<Box>{item.label}</Box>
{item.description && (
<Box color={'myGray.500'} fontSize={'xs'}>
{item.description}
</Box>
)}
</MenuItem>
{item.showBorder && <MyDivider my={2} />}
</Box>
));
if (ScrollData) {
return <ScrollData>{component}</ScrollData>;
}
return component;
})()}
</MenuList>
</Menu>
</Box>

View File

@@ -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 (
<HStack space="1" {...props}>
<Avatar src={sourceMember.avatar} w={avatarSize} />
<Box>{sourceMember.name}</Box>
{sourceMember.status === 'leave' && <Tag color="gray">{t('account_team:leaved')}</Tag>}
</HStack>
);
}
export default React.memo(UserBox);

View File

@@ -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';
import { PaginationProps, PaginationResponse } from '../common/fetch/type';
const thresholdVal = 200;
type PagingData<T> = {
pageNum: number;
pageSize: number;
data: T[];
total?: number;
};
export function usePagination<ResT = any>({
api,
pageSize = 10,
params = {},
defaultRequest = true,
type = 'button',
onChange,
refreshDeps,
scrollLoadType = 'bottom',
EmptyTip
}: {
api: (data: any) => Promise<PagingData<ResT>>;
pageSize?: number;
params?: Record<string, any>;
defaultRequest?: boolean;
type?: 'button' | 'scroll';
onChange?: (pageNum: number) => void;
refreshDeps?: any[];
throttleWait?: number;
scrollLoadType?: 'top' | 'bottom';
EmptyTip?: React.JSX.Element;
}) {
export function usePagination<DataT, ResT = {}>(
api: (data: PaginationProps<DataT>) => Promise<PaginationResponse<ResT>>,
{
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<ResT = any>({
setTrue();
try {
const res: PagingData<ResT> = await api({
const res = await api({
pageNum: num,
pageSize,
...params
@@ -93,13 +88,13 @@ export function usePagination<ResT = any>({
);
}
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);

View File

@@ -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": "同步成员成功"
}