* add manager change memberName and update inform UI

* change icon and some inform ui

* change for comment

* fix for comment
This commit is contained in:
gggaaallleee
2025-03-27 10:04:29 +08:00
committed by GitHub
parent 29a10c1389
commit cb29076e5b
25 changed files with 474 additions and 189 deletions

View File

@@ -5,7 +5,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { CloseIcon } from '@chakra-ui/icons';
import { readInform } from '@/web/support/user/inform/api';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import Markdown from '@/components/Markdown';
const ImportantInform = ({
informs,
refetch
@@ -28,32 +28,61 @@ const ImportantInform = ({
{informs.map((inform) => (
<Flex
key={inform._id}
bg={'primary.015'}
py={3}
py={4}
px={5}
fontSize={'md'}
borderRadius={'lg'}
boxShadow={'4'}
borderWidth={'1px'}
backgroundColor={'white'}
borderColor={'borderColor.base'}
minW={['200px', '400px']}
alignItems={'flex-start'}
mb={3}
backdropFilter={'blur(30px)'}
>
<MyIcon name={'support/user/informLight'} w={'16px'} mr={2} />
<Box flex={'1 0 0'}>
<Box fontWeight={'bold'}>{inform.title}</Box>
<Box fontSize={'sm'}>{inform.content}</Box>
</Box>
<CloseIcon
cursor={'pointer'}
_hover={{
color: 'primary.700'
}}
w={'12px'}
onClick={() => onClickClose(inform._id)}
<MyIcon
name={'support/user/informLight'}
w={5}
h={5}
mr={2}
mt={'2px'}
color="blue.600"
/>
<Box flex={'1 0 0'}>
<Box
fontWeight="bold"
fontSize="16px"
lineHeight="24px"
letterSpacing="0.15px"
fontFamily="'PingFang SC', sans-serif"
color=" #24282C"
>
{inform.title}
</Box>
<Box
pt={1}
fontSize="14px"
lineHeight="20px"
letterSpacing="0.25px"
fontFamily="'PingFang SC', sans-serif"
fontWeight="400"
color="#24282C"
>
<Markdown source={inform?.content} />
</Box>
</Box>
<Box
cursor={'pointer'}
p={1}
pt={0}
borderRadius={'4px'}
_hover={{
backgroundColor: 'rgba(17, 24, 36, 0.05)'
}}
onClick={() => onClickClose(inform._id)}
>
<CloseIcon w={'12px'} />
</Box>
</Flex>
))}
</Box>

View File

@@ -0,0 +1,73 @@
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import Markdown from '@/components/Markdown';
import React from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import MyDivider from '@fastgpt/web/components/common/MyDivider';
const NotificationDetailsModal = ({ inform, onClose }: { inform: any; onClose: () => void }) => {
const { t } = useTranslation();
const textStyles = {
title: {
color: 'grayModern.900',
fontSize: '20px',
fontWeight: 'medium',
lineHeight: 6,
letterSpacing: '0.15px'
},
time: {
color: 'grayModern.500',
fontSize: '12px',
lineHeight: 5,
letterSpacing: '0.25px'
}
};
return (
<MyModal
isOpen={!!inform}
iconSrc={'support/user/informLight'}
title={t('account_inform:notification_detail')}
onClose={onClose}
iconColor="blue.600"
maxW="680px"
maxH="80vh"
>
<Flex flexDirection="column" p={8}>
<Flex
{...textStyles.time}
fontFamily="PingFang SC"
display="flex"
justifyContent="space-between"
alignItems="center"
alignSelf="stretch"
>
<Box {...textStyles.title} fontFamily="PingFang SC">
{inform.title}
</Box>
<Box {...textStyles.time} ml={3} flex={1} fontFamily="PingFang SC">
{t(formatTimeToChatTime(inform.time) as any).replace('#', ':')}
</Box>
<MyTag
colorSchema={inform.teamId ? 'green' : 'blue'}
mr={2}
fontSize="xs"
fontWeight="medium"
showDot={false}
type="fill"
>
{inform.teamId ? t('account_inform:team') : t('account_inform:system')}
</MyTag>
</Flex>
<MyDivider my={4} />
<Box fontSize="sm" lineHeight={1.8}>
<Markdown source={inform?.content} />
</Box>
</Flex>
</MyModal>
);
};
export default React.memo(NotificationDetailsModal);

View File

@@ -17,7 +17,12 @@ import {
import { useTranslation } from 'next-i18next';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { delRemoveMember, postRestoreMember } from '@/web/support/user/team/api';
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
import {
delRemoveMember,
postRestoreMember,
putUpdateMemberNameByManager
} from '@/web/support/user/team/api';
import Tag from '@fastgpt/web/components/common/Tag';
import Icon from '@fastgpt/web/components/common/Icon';
import { useContextSelector } from 'use-context-selector';
@@ -40,6 +45,7 @@ import OrgTags from '@/components/support/user/team/OrgTags';
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import { useState } from 'react';
import { downloadFetch } from '@/web/common/system/utils';
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
const InviteModal = dynamic(() => import('./Invite/InviteModal'));
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
@@ -128,6 +134,30 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const isLoading = isUpdateInvite || isSyncing;
const { EditModal: EditMemberNameModal, onOpenModal: openEditMemberName } = useEditTextarea({
title: t('account_team:edit_member'),
tip: t('account_team:edit_member_tip'),
canEmpty: false,
rows: 1
});
const handleEditMemberName = (tmbId: string, memberName: string) => {
openEditMemberName({
defaultVal: memberName,
onSuccess: (newName: string) => {
return putUpdateMemberNameByManager(tmbId, newName).then(() => {
Promise.all([refetchGroups(), refetchMembers()]);
});
},
onError: (err) => {
toast({
title: '',
status: 'error'
});
}
});
};
return (
<>
{isLoading && <MyLoading />}
@@ -222,7 +252,9 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
{t('account_team:user_name')}
</Th>
<Th bgColor="myGray.100">{t('common:contact_way')}</Th>
<Th bgColor="myGray.100">{t('account_team:org')}</Th>
<Th bgColor="myGray.100" pl={9}>
{t('account_team:org')}
</Th>
<Th bgColor="myGray.100">{t('account_team:join_update_time')}</Th>
<Th borderRightRadius="6px" bgColor="myGray.100">
{t('common:common.Action')}
@@ -262,7 +294,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
})()}
</Td>
<Td maxW={'300px'}>
<VStack gap={0}>
<VStack gap={0} alignItems="flex-start">
<Box>{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}</Box>
<Box>
{member.updateTime
@@ -276,29 +308,45 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
member.role !== TeamMemberRoleEnum.owner &&
member.tmbId !== userInfo?.team.tmbId &&
(member.status === TeamMemberStatusEnum.active ? (
<Icon
name={'common/trash'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'red.600',
bgColor: 'myGray.100'
}}
onClick={() => {
openRemoveMember(
() =>
delRemoveMember(member.tmbId).then(() =>
Promise.all([refetchGroups(), refetchMembers()])
),
undefined,
t('account_team:remove_tip', {
username: member.memberName
})
)();
}}
/>
<>
<Icon
name={'edit'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'blue.600',
bgColor: 'myGray.100'
}}
onClick={() =>
handleEditMemberName(member.tmbId, member.memberName)
}
/>
<Icon
name={'common/trash'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'red.600',
bgColor: 'myGray.100'
}}
onClick={() => {
openRemoveMember(
() =>
delRemoveMember(member.tmbId).then(() =>
Promise.all([refetchGroups(), refetchMembers()])
),
undefined,
t('account_team:remove_tip', {
username: member.memberName
})
)();
}}
/>
</>
) : (
member.status === TeamMemberStatusEnum.forbidden && (
<Icon
@@ -331,6 +379,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
</Table>
<ConfirmRemoveMemberModal />
<ConfirmRestoreMemberModal />
<EditMemberNameModal />
</TableContainer>
</MemberScrollData>
</Box>

View File

@@ -45,6 +45,7 @@ import TeamSelector from '@/pageComponents/account/TeamSelector';
import { getWorkorderURL } from '@/web/common/workorder/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useMount } from 'ahooks';
import MyDivider from '@fastgpt/web/components/common/MyDivider';
const StandDetailModal = dynamic(
() => import('@/pageComponents/account/info/standardDetailModal'),
@@ -76,7 +77,7 @@ const Info = () => {
<Flex justifyContent={'center'} maxW={'1080px'}>
<Box flex={'0 0 330px'}>
<MyInfo onOpenContact={onOpenContact} />
<Box mt={9}>
<Box mt={6}>
<Other onOpenContact={onOpenContact} />
</Box>
</Box>
@@ -162,8 +163,23 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
const labelStyles: BoxProps = {
flex: '0 0 80px',
fontSize: 'sm',
color: 'myGray.900'
color: 'var(--light-general-on-surface-lowest, var(--Gray-Modern-500, #667085))',
fontFamily: '"PingFang SC"',
fontSize: '14px',
fontStyle: 'normal',
fontWeight: 400,
lineHeight: '20px',
letterSpacing: '0.25px'
};
const titleStyles: BoxProps = {
color: 'var(--light-general-on-surface, var(--Gray-Modern-900, #111824))',
fontFamily: '"PingFang SC"',
fontSize: '16px',
fontStyle: 'normal',
fontWeight: 500,
lineHeight: '24px',
letterSpacing: '0.15px'
};
const isSyncMember = feConfigs.register_method?.includes('sync');
@@ -171,27 +187,69 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
<Box>
{/* user info */}
{isPc && (
<Flex alignItems={'center'} fontSize={'md'} h={'30px'}>
<MyIcon mr={2} name={'support/user/userLight'} w={'1.25rem'} />
{t('account_info:personal_information')}
<Flex alignItems={'center'} h={'30px'} {...titleStyles}>
<MyIcon mr={2} name={'core/dataset/fileCollection'} w={'1.25rem'} />
{t('account_info:general_info')}
</Flex>
)}
<Box mt={[0, 6]} fontSize={'sm'}>
<Flex alignItems={'center'}>
<Box {...labelStyles}>{t('account_info:user_account')}&nbsp;</Box>
<Box flex={1}>{userInfo?.username}</Box>
</Flex>
{feConfigs?.isPlus && (
<Flex mt={4} alignItems={'center'}>
<Box {...labelStyles}>{t('account_info:password')}&nbsp;</Box>
<Box flex={1}>*****</Box>
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdatePsw}>
{t('account_info:change')}
</Button>
</Flex>
)}
{feConfigs?.isPlus && (
<Flex mt={4} alignItems={'center'}>
<Box {...labelStyles}>{t('common:contact_way')}&nbsp;</Box>
<Box flex={1} {...(!userInfo?.contact ? { color: 'red.600' } : {})}>
{userInfo?.contact ? userInfo?.contact : t('account_info:please_bind_contact')}
</Box>
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdateContact}>
{t('account_info:change')}
</Button>
</Flex>
)}
<MyDivider my={6} />
{isPc && (
<Flex alignItems={'center'} h={'30px'} {...titleStyles} mt={6}>
<MyIcon mr={2} name={'support/team/group'} w={'1.25rem'} />
{t('account_info:team_info')}
</Flex>
)}
{feConfigs.isPlus && (
<Flex mt={6} alignItems={'center'}>
<Box {...labelStyles}>{t('account_info:user_team_team_name')}&nbsp;</Box>
<Flex flex={'1 0 0'} w={0} align={'center'}>
<TeamSelector height={'28px'} w={'100%'} showManage />
</Flex>
</Flex>
)}
{isPc ? (
<Flex alignItems={'center'} cursor={'pointer'}>
<Box {...labelStyles}>{t('account_info:avatar')}:&nbsp;</Box>
<Flex mt={4} alignItems={'center'} cursor={'pointer'}>
<Box {...labelStyles}>{t('account_info:avatar')}&nbsp;</Box>
<MyTooltip label={t('account_info:select_avatar')}>
<Box
w={['44px', '56px']}
h={['44px', '56px']}
w={['22px', '32px']}
h={['22px', '32px']}
borderRadius={'50%'}
border={theme.borders.base}
overflow={'hidden'}
p={'2px'}
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
mb={2}
onClick={onOpenSelectFile}
>
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
@@ -228,7 +286,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
)}
{feConfigs?.isPlus && (
<Flex mt={[0, 4]} alignItems={'center'}>
<Box {...labelStyles}>{t('account_info:member_name')}:&nbsp;</Box>
<Box {...labelStyles}>{t('account_info:member_name')}&nbsp;</Box>
<Input
flex={'1 0 0'}
disabled={isSyncMember}
@@ -248,43 +306,10 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
/>
</Flex>
)}
<Flex alignItems={'center'} mt={6}>
<Box {...labelStyles}>{t('account_info:user_account')}:&nbsp;</Box>
<Box flex={1}>{userInfo?.username}</Box>
</Flex>
{feConfigs?.isPlus && (
<Flex mt={6} alignItems={'center'}>
<Box {...labelStyles}>{t('account_info:password')}:&nbsp;</Box>
<Box flex={1}>*****</Box>
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdatePsw}>
{t('account_info:change')}
</Button>
</Flex>
)}
{feConfigs?.isPlus && (
<Flex mt={6} alignItems={'center'}>
<Box {...labelStyles}>{t('common:contact_way')}:&nbsp;</Box>
<Box flex={1} {...(!userInfo?.contact ? { color: 'red.600' } : {})}>
{userInfo?.contact ? userInfo?.contact : t('account_info:please_bind_contact')}
</Box>
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdateContact}>
{t('account_info:change')}
</Button>
</Flex>
)}
{feConfigs.isPlus && (
<Flex mt={6} alignItems={'center'}>
<Box {...labelStyles}>{t('account_info:user_team_team_name')}:&nbsp;</Box>
<Flex flex={'1 0 0'} w={0} align={'center'}>
<TeamSelector height={'28px'} w={'100%'} showManage />
</Flex>
</Flex>
)}
{feConfigs?.isPlus && (userInfo?.team?.balance ?? 0) > 0 && (
<Box mt={6} whiteSpace={'nowrap'}>
<Box mt={4} whiteSpace={'nowrap'}>
<Flex alignItems={'center'}>
<Box {...labelStyles}>{t('account_info:team_balance')}:&nbsp;</Box>
<Box {...labelStyles}>{t('account_info:team_balance')}&nbsp;</Box>
<Box flex={1}>
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong>{' '}
{t('account_info:yuan')}
@@ -298,6 +323,8 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
</Flex>
</Box>
)}
<MyDivider my={6} />
</Box>
{isOpenConversionModal && (
<ConversionModal onClose={onCloseConversionModal} onOpenContact={onOpenContact} />
@@ -418,7 +445,16 @@ const PlanUsage = () => {
return standardPlan ? (
<Box mt={[6, 0]}>
<Flex fontSize={['md', 'lg']} h={'30px'}>
<Flex alignItems={'center'}>
<Flex
alignItems={'center'}
color="var(--light-general-on-surface, var(--Gray-Modern-900, #111824))"
fontFamily='"PingFang SC"'
fontSize="16px"
fontStyle="normal"
fontWeight={500}
lineHeight="24px"
letterSpacing="0.15px"
>
<MyIcon mr={2} name={'support/account/plans'} w={'20px'} />
{t('account_info:package_and_usage')}
</Flex>
@@ -601,7 +637,7 @@ const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
return (
<Box>
<Grid gridGap={4} mt={3}>
<Grid gridGap={4}>
{feConfigs?.docUrl && (
<Link
href={getDocPath('/docs/intro')}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Box, Button, Flex, useTheme } from '@chakra-ui/react';
import React, { useState } from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { getInforms, readInform } from '@/web/support/user/inform/api';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { usePagination } from '@fastgpt/web/hooks/usePagination';
@@ -8,11 +8,31 @@ import { useTranslation } from 'next-i18next';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import AccountContainer from '@/pageComponents/account/AccountContainer';
import { serviceSideProps } from '@/web/common/i18n/utils';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import Markdown from '@/components/Markdown';
import NotificationDetailsModal from '@/pageComponents/account/NotificationDetailsModal';
const InformTable = () => {
const { t } = useTranslation();
const theme = useTheme();
const { Loading } = useLoading();
const [selectedInform, setSelectedInform] = useState<any>(null);
const textStyles = {
title: {
color: '#111824',
fontSize: 'md',
fontWeight: 'bold',
lineHeight: 6,
letterSpacing: '0.15px'
},
time: {
color: '#667085',
fontSize: 'sm',
lineHeight: 5,
letterSpacing: '0.25px'
}
};
const {
data: informs,
@@ -28,63 +48,105 @@ const InformTable = () => {
return (
<AccountContainer>
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
<Box px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
<Flex flexDirection="column" py={[0, 5]} h="100%" position="relative">
<Box
px={[3, 8]}
position="relative"
flex="1 0 0"
h={0}
overflowY="auto"
display="flex"
flexDirection="column"
alignItems="center"
>
{informs.map((item) => (
<Box
key={item._id}
border={theme.borders.md}
py={2}
px={4}
borderRadius={'md'}
position={'relative'}
_notLast={{ mb: 3 }}
py={5}
px={6}
maxH="168px"
maxW="800px"
minW="200px"
width="100%"
borderRadius="md"
position="relative"
_notLast={{ mb: 4 }}
_hover={{
border: '1px solid #94B5FF',
cursor: 'pointer'
}}
onClick={() => {
if (!item.read) {
readInform(item._id).then(() => getData(pageNum));
}
setSelectedInform(item);
}}
>
<Flex alignItems={'center'}>
<Box fontWeight={'bold'}>{item.title}</Box>
<Box ml={2} color={'myGray.500'} flex={'1 0 0'}>
({t(formatTimeToChatTime(item.time) as any).replace('#', ':')})
<Flex alignItems="center">
<Box {...textStyles.title}>
{item.teamId ? `${item.teamName}` : ''}
{item.title}
</Box>
{!item.read && (
<Button
variant={'whitePrimary'}
size={'xs'}
onClick={async () => {
if (!item.read) {
await readInform(item._id);
getData(pageNum);
}
}}
>
{t('account_inform:read')}
</Button>
)}
<Flex ml={3} flex={1} alignItems="center">
<Box {...textStyles.time}>
{t(formatTimeToChatTime(item.time) as any).replace('#', ':')}
</Box>
{!item.read && <Box w={2} h={2} borderRadius="full" bg="red.600" ml={3} />}
</Flex>
<MyTag
colorSchema={item.teamId ? 'green' : 'blue'}
mr={2}
fontSize="xs"
fontWeight="medium"
showDot={false}
type="fill"
>
{item.teamId ? t('account_inform:team') : t('account_inform:system')}
</MyTag>
</Flex>
<Box mt={2} fontSize={'sm'} color={'myGray.600'} whiteSpace={'pre-wrap'}>
{item.content}
<Box
mt={2}
fontSize="sm"
fontWeight={400}
color="#485264"
overflow="hidden"
maxHeight={24}
sx={{
lineHeight: '16px',
'& h1, & h2, & h3, & h4, & h5, & h6': {
my: '0 !important',
py: 0.5,
display: 'block',
lineHeight: 'normal'
},
'& p': {
my: 0
}
}}
noOfLines={6}
>
<Markdown source={item.content} />
</Box>
{!item.read && (
<>
<Box
w={'5px'}
h={'5px'}
borderRadius={'10px'}
bg={'red.600'}
position={'absolute'}
top={'8px'}
left={'8px'}
/>
</>
)}
</Box>
))}
{!isLoading && informs.length === 0 && (
<EmptyTip text={t('account_inform:no_notifications')}></EmptyTip>
<EmptyTip text={t('account_inform:no_notifications')} />
)}
</Box>
{selectedInform && (
<NotificationDetailsModal
inform={selectedInform}
onClose={() => setSelectedInform(null)}
/>
)}
{total > pageSize && (
<Flex w={'100%'} mt={4} px={[3, 8]} justifyContent={'flex-end'}>
<Flex w="100%" mt={4} px={[3, 8]} justifyContent="flex-end">
<Pagination />
</Flex>
)}

View File

@@ -1,15 +1,15 @@
import { GET, POST } from '@/web/common/api/request';
import type { UserInformSchema } from '@fastgpt/global/support/user/inform/type';
import type { UserInformType } from '@fastgpt/global/support/user/inform/type';
import type { SystemMsgModalValueType } from '@fastgpt/service/support/user/inform/type';
import type { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
export const getInforms = (data: PaginationProps) =>
POST<PaginationResponse<UserInformSchema>>(`/proApi/support/user/inform/list`, data);
POST<PaginationResponse<UserInformType>>(`/proApi/support/user/inform/list`, data);
export const getUnreadCount = () =>
GET<{
unReadCount: number;
importantInforms: UserInformSchema[];
importantInforms: UserInformType[];
}>(`/proApi/support/user/inform/countUnread`);
export const readInform = (id: string) => GET(`/proApi/support/user/inform/read`, { id });

View File

@@ -40,6 +40,8 @@ export const getTeamMembers = (props: PaginationProps<{ withLeaved?: boolean }>)
// export const postInviteTeamMember = (data: InviteMemberProps) =>
// POST<InviteMemberResponse>(`/proApi/support/user/team/member/invite`, data);
export const putUpdateMemberNameByManager = (tmbId: string, name: string) =>
PUT(`/proApi/support/user/team/member/updateNameByManager`, { tmbId, name });
export const putUpdateMemberName = (name: string) =>
PUT(`/proApi/support/user/team/member/updateName`, { name });