mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-02 20:58:12 +00:00
160
projects/app/src/pages/account/components/BillDetail.tsx
Normal file
160
projects/app/src/pages/account/components/BillDetail.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
ModalBody,
|
||||
Flex,
|
||||
Box,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer
|
||||
} from '@chakra-ui/react';
|
||||
import { BillItemType } from '@fastgpt/global/support/wallet/bill/type.d';
|
||||
import dayjs from 'dayjs';
|
||||
import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const filterBillList = useMemo(
|
||||
() => bill.list.filter((item) => item && item.moduleName),
|
||||
[bill.list]
|
||||
);
|
||||
|
||||
const {
|
||||
hasModel,
|
||||
hasTokens,
|
||||
hasInputTokens,
|
||||
hasOutputTokens,
|
||||
hasCharsLen,
|
||||
hasDuration,
|
||||
hasDataLen,
|
||||
hasDatasetSize
|
||||
} = useMemo(() => {
|
||||
let hasModel = false;
|
||||
let hasTokens = false;
|
||||
let hasInputTokens = false;
|
||||
let hasOutputTokens = false;
|
||||
let hasCharsLen = false;
|
||||
let hasDuration = false;
|
||||
let hasDataLen = false;
|
||||
let hasDatasetSize = false;
|
||||
|
||||
bill.list.forEach((item) => {
|
||||
if (item.model !== undefined) {
|
||||
hasModel = true;
|
||||
}
|
||||
if (typeof item.tokenLen === 'number') {
|
||||
hasTokens = true;
|
||||
}
|
||||
if (typeof item.inputTokens === 'number') {
|
||||
hasInputTokens = true;
|
||||
}
|
||||
if (typeof item.outputTokens === 'number') {
|
||||
hasOutputTokens = true;
|
||||
}
|
||||
if (typeof item.charsLength === 'number') {
|
||||
hasCharsLen = true;
|
||||
}
|
||||
if (typeof item.duration === 'number') {
|
||||
hasDuration = true;
|
||||
}
|
||||
if (typeof item.datasetSize === 'number') {
|
||||
hasDatasetSize = true;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
hasModel,
|
||||
hasTokens,
|
||||
hasInputTokens,
|
||||
hasOutputTokens,
|
||||
hasCharsLen,
|
||||
hasDuration,
|
||||
hasDataLen,
|
||||
hasDatasetSize
|
||||
};
|
||||
}, [bill.list]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/bill.svg"
|
||||
title={t('user.Bill Detail')}
|
||||
maxW={['90vw', '700px']}
|
||||
>
|
||||
<ModalBody>
|
||||
{/* <Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.bill username')}:</Box>
|
||||
<Box>{t(bill.memberName)}</Box>
|
||||
</Flex> */}
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.Number')}:</Box>
|
||||
<Box>{bill.id}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.Time')}:</Box>
|
||||
<Box>{dayjs(bill.time).format('YYYY/MM/DD HH:mm:ss')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.App name')}:</Box>
|
||||
<Box>{t(bill.appName) || '-'}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.Source')}:</Box>
|
||||
<Box>{t(BillSourceMap[bill.source]?.label)}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.Total')}:</Box>
|
||||
<Box fontWeight={'bold'}>{bill.total}元</Box>
|
||||
</Flex>
|
||||
<Box pb={4}>
|
||||
<Box flex={'0 0 80px'} mb={1}>
|
||||
{t('wallet.bill.Bill Module')}
|
||||
</Box>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('wallet.bill.Module name')}</Th>
|
||||
{hasModel && <Th>{t('wallet.bill.Ai model')}</Th>}
|
||||
{hasTokens && <Th>{t('wallet.bill.Token Length')}</Th>}
|
||||
{hasInputTokens && <Th>{t('wallet.bill.Input Token Length')}</Th>}
|
||||
{hasOutputTokens && <Th>{t('wallet.bill.Output Token Length')}</Th>}
|
||||
{hasCharsLen && <Th>{t('wallet.bill.Text Length')}</Th>}
|
||||
{hasDuration && <Th>{t('wallet.bill.Duration')}</Th>}
|
||||
{hasDatasetSize && (
|
||||
<Th>{t('support.wallet.subscription.type.extraDatasetSize')}</Th>
|
||||
)}
|
||||
<Th>费用(¥)</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{filterBillList.map((item, i) => (
|
||||
<Tr key={i}>
|
||||
<Td>{t(item.moduleName)}</Td>
|
||||
{hasModel && <Td>{item.model ?? '-'}</Td>}
|
||||
{hasTokens && <Td>{item.tokenLen ?? '-'}</Td>}
|
||||
{hasInputTokens && <Td>{item.inputTokens ?? '-'}</Td>}
|
||||
{hasOutputTokens && <Td>{item.outputTokens ?? '-'}</Td>}
|
||||
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
|
||||
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
|
||||
{hasDatasetSize && <Td>{item.datasetSize ?? '-'}</Td>}
|
||||
<Td>{formatStorePrice2Read(item.amount)}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default BillDetail;
|
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
@@ -10,38 +9,43 @@ import {
|
||||
TableContainer,
|
||||
Flex,
|
||||
Box,
|
||||
ModalBody
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import { getBills, checkBalancePayResult } from '@/web/support/wallet/bill/api';
|
||||
import type { BillSchemaType } from '@fastgpt/global/support/wallet/bill/type.d';
|
||||
import { BillSourceEnum, BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { getUserBills } from '@/web/support/wallet/bill/api';
|
||||
import type { BillItemType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import { usePagination } from '@/web/common/hooks/usePagination';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import dayjs from 'dayjs';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
|
||||
import { addDays } from 'date-fns';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import {
|
||||
BillTypeEnum,
|
||||
billPayWayMap,
|
||||
billStatusMap,
|
||||
billTypeMap
|
||||
} from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { usePagination } from '@/web/common/hooks/usePagination';
|
||||
import MyBox from '@/components/common/MyBox';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import Avatar from '@/components/Avatar';
|
||||
const BillDetail = dynamic(() => import('./BillDetail'));
|
||||
|
||||
const BillTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [billType, setBillType] = useState<`${BillTypeEnum}` | ''>('');
|
||||
const [billDetail, setBillDetail] = useState<BillSchemaType>();
|
||||
const { Loading } = useLoading();
|
||||
const [dateRange, setDateRange] = useState<DateRangeType>({
|
||||
from: addDays(new Date(), -7),
|
||||
to: new Date()
|
||||
});
|
||||
const [billSource, setBillSource] = useState<`${BillSourceEnum}` | ''>('');
|
||||
const { isPc } = useSystemStore();
|
||||
const { userInfo } = useUserStore();
|
||||
const [billDetail, setBillDetail] = useState<BillItemType>();
|
||||
|
||||
const billTypeList = useMemo(
|
||||
const sourceList = useMemo(
|
||||
() => [
|
||||
{ label: t('common.All'), value: '' },
|
||||
...Object.entries(billTypeMap).map(([key, value]) => ({
|
||||
...Object.entries(BillSourceMap).map(([key, value]) => ({
|
||||
label: t(value.label),
|
||||
value: key
|
||||
}))
|
||||
@@ -49,193 +53,134 @@ const BillTable = () => {
|
||||
[t]
|
||||
);
|
||||
|
||||
const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId);
|
||||
const { data: members = [] } = useQuery(['getMembers', userInfo?.team?.teamId], () => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
return getTeamMembers(userInfo.team.teamId);
|
||||
});
|
||||
const tmbList = useMemo(
|
||||
() =>
|
||||
members.map((item) => ({
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={'16px'} mr={1} />
|
||||
{item.memberName}
|
||||
</Flex>
|
||||
),
|
||||
value: item.tmbId
|
||||
})),
|
||||
[members]
|
||||
);
|
||||
|
||||
const {
|
||||
data: bills,
|
||||
isLoading,
|
||||
Pagination,
|
||||
getData,
|
||||
total
|
||||
} = usePagination<BillSchemaType>({
|
||||
api: getBills,
|
||||
pageSize: 20,
|
||||
getData
|
||||
} = usePagination<BillItemType>({
|
||||
api: getUserBills,
|
||||
pageSize: isPc ? 20 : 10,
|
||||
params: {
|
||||
type: billType
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
source: billSource,
|
||||
teamMemberId: selectTmbId
|
||||
},
|
||||
defaultRequest: false
|
||||
});
|
||||
|
||||
const { mutate: handleRefreshPayOrder, isLoading: isRefreshing } = useRequest({
|
||||
mutationFn: async (payId: string) => {
|
||||
try {
|
||||
const data = await checkBalancePayResult(payId);
|
||||
toast({
|
||||
title: data,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: error?.message,
|
||||
status: 'warning'
|
||||
});
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
getData(1);
|
||||
} catch (error) {}
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getData(1);
|
||||
}, [billType]);
|
||||
}, [billSource, selectTmbId]);
|
||||
|
||||
return (
|
||||
<MyBox
|
||||
isLoading={isLoading || isRefreshing}
|
||||
position={'relative'}
|
||||
h={'100%'}
|
||||
overflow={'overlay'}
|
||||
py={[0, 5]}
|
||||
px={[3, 8]}
|
||||
>
|
||||
<TableContainer>
|
||||
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
|
||||
<Flex
|
||||
flexDir={['column', 'row']}
|
||||
gap={2}
|
||||
w={'100%'}
|
||||
px={[3, 8]}
|
||||
alignItems={['flex-end', 'center']}
|
||||
>
|
||||
{tmbList.length > 1 && userInfo?.team?.canWrite && (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box mr={2} flexShrink={0}>
|
||||
{t('support.user.team.member')}
|
||||
</Box>
|
||||
<MySelect
|
||||
size={'sm'}
|
||||
minW={'100px'}
|
||||
list={tmbList}
|
||||
value={selectTmbId}
|
||||
onchange={setSelectTmbId}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Box flex={'1'} />
|
||||
<Flex alignItems={'center'} gap={3}>
|
||||
<DateRangePicker
|
||||
defaultDate={dateRange}
|
||||
position="bottom"
|
||||
onChange={setDateRange}
|
||||
onSuccess={() => getData(1)}
|
||||
/>
|
||||
<Pagination />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<TableContainer px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>#</Th>
|
||||
{/* <Th>{t('user.team.Member Name')}</Th> */}
|
||||
<Th>{t('user.Time')}</Th>
|
||||
<Th>
|
||||
<MySelect
|
||||
list={billTypeList}
|
||||
value={billType}
|
||||
list={sourceList}
|
||||
value={billSource}
|
||||
size={'sm'}
|
||||
onchange={(e) => {
|
||||
setBillType(e);
|
||||
setBillSource(e);
|
||||
}}
|
||||
w={'130px'}
|
||||
></MySelect>
|
||||
</Th>
|
||||
<Th>{t('user.Time')}</Th>
|
||||
<Th>{t('support.wallet.Amount')}</Th>
|
||||
<Th>{t('support.wallet.bill.Status')}</Th>
|
||||
<Th>{t('user.Application Name')}</Th>
|
||||
<Th>{t('user.Total Amount')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{bills.map((item, i) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{i + 1}</Td>
|
||||
<Td>{t(billTypeMap[item.type]?.label)}</Td>
|
||||
{bills.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
{/* <Td>{item.memberName}</Td> */}
|
||||
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
|
||||
<Td>{t(BillSourceMap[item.source]?.label)}</Td>
|
||||
<Td>{t(item.appName) || '-'}</Td>
|
||||
<Td>{item.total}元</Td>
|
||||
<Td>
|
||||
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
|
||||
</Td>
|
||||
<Td>{formatStorePrice2Read(item.price)}元</Td>
|
||||
<Td>{t(billStatusMap[item.status]?.label)}</Td>
|
||||
<Td>
|
||||
{item.status === 'NOTPAY' && (
|
||||
<Button mr={4} onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
|
||||
{t('common.Update')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant={'whiteBase'} size={'sm'} onClick={() => setBillDetail(item)}>
|
||||
{t('common.Detail')}
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={() => setBillDetail(item)}>
|
||||
详情
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{total >= 20 && (
|
||||
<Flex mt={3} justifyContent={'flex-end'}>
|
||||
<Pagination />
|
||||
</Flex>
|
||||
)}
|
||||
{!isLoading && bills.length === 0 && (
|
||||
<Flex
|
||||
mt={'20vh'}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
{t('support.wallet.noBill')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</TableContainer>
|
||||
|
||||
{!!billDetail && (
|
||||
<BillDetailModal bill={billDetail} onClose={() => setBillDetail(undefined)} />
|
||||
{!isLoading && bills.length === 0 && (
|
||||
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
无使用记录~
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</MyBox>
|
||||
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
{!!billDetail && <BillDetail bill={billDetail} onClose={() => setBillDetail(undefined)} />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default BillTable;
|
||||
|
||||
function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/bill.svg"
|
||||
title={t('support.wallet.usage.Usage Detail')}
|
||||
maxW={['90vw', '700px']}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.bill.Number')}:</Box>
|
||||
<Box>{bill.orderId}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.usage.Time')}:</Box>
|
||||
<Box>{dayjs(bill.createTime).format('YYYY/MM/DD HH:mm:ss')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.bill.Status')}:</Box>
|
||||
<Box>{t(billStatusMap[bill.status]?.label)}</Box>
|
||||
</Flex>
|
||||
{!!bill.metadata?.payWay && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.bill.payWay.Way')}:</Box>
|
||||
<Box>{t(billPayWayMap[bill.metadata.payWay]?.label)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.Amount')}:</Box>
|
||||
<Box>{formatStorePrice2Read(bill.price)}元</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.bill.Type')}:</Box>
|
||||
<Box>{t(billTypeMap[bill.type]?.label)}</Box>
|
||||
</Flex>
|
||||
{!!bill.metadata?.subMode && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.mode.Period')}:</Box>
|
||||
<Box>{t(subModeMap[bill.metadata.subMode]?.label)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!bill.metadata?.standSubLevel && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Stand plan level')}:</Box>
|
||||
<Box>{t(standardSubLevelMap[bill.metadata.standSubLevel]?.label)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{bill.metadata?.datasetSize !== undefined && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Extra dataset size')}:</Box>
|
||||
<Box>{bill.metadata?.datasetSize}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{bill.metadata?.extraPoints !== undefined && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Extra ai points')}:</Box>
|
||||
<Box>{bill.metadata.extraPoints}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
export default React.memo(BillTable);
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
useDisclosure,
|
||||
useTheme,
|
||||
Divider,
|
||||
Select,
|
||||
Input,
|
||||
Link,
|
||||
Progress,
|
||||
Grid
|
||||
Progress
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
@@ -21,68 +22,35 @@ import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { timezoneList } from '@fastgpt/global/common/time/timezone';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { langMap, setLngStore } from '@/web/common/utils/i18n';
|
||||
import { useRouter } from 'next/router';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import MySelect from '@/components/Select';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { putUpdateMemberName } from '@/web/support/user/team/api';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { getTeamDatasetValidSub } from '@/web/support/wallet/sub/api';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
|
||||
|
||||
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
||||
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
|
||||
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
|
||||
const PayModal = dynamic(() => import('./PayModal'));
|
||||
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
|
||||
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
|
||||
const SubDatasetModal = dynamic(() => import('@/components/support/wallet/SubDatasetModal'));
|
||||
|
||||
const Account = () => {
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
const { initUserInfo } = useUserStore();
|
||||
|
||||
useQuery(['init'], initUserInfo);
|
||||
|
||||
return (
|
||||
<Box py={[3, '28px']} px={['5vw', '64px']}>
|
||||
{isPc ? (
|
||||
<Flex justifyContent={'center'}>
|
||||
<Box flex={'0 0 330px'}>
|
||||
<MyInfo />
|
||||
<Box mt={9}>
|
||||
<Other />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box ml={'45px'} flex={'1 0 0'} maxW={'600px'}>
|
||||
<PlanUsage />
|
||||
</Box>
|
||||
</Flex>
|
||||
) : (
|
||||
<>
|
||||
<MyInfo />
|
||||
<PlanUsage />
|
||||
<Other />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Account);
|
||||
|
||||
const MyInfo = () => {
|
||||
const UserInfo = () => {
|
||||
const theme = useTheme();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, updateUserInfo } = useUserStore();
|
||||
const router = useRouter();
|
||||
const { feConfigs, systemVersion } = useSystemStore();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
|
||||
const timezones = useRef(timezoneList());
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
@@ -95,11 +63,13 @@ const MyInfo = () => {
|
||||
onClose: onCloseUpdatePsw,
|
||||
onOpen: onOpenUpdatePsw
|
||||
} = useDisclosure();
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenStandardModal,
|
||||
onClose: onCloseStandardModal,
|
||||
onOpen: onOpenStandardModal
|
||||
isOpen: isOpenSubDatasetModal,
|
||||
onClose: onCloseSubDatasetModal,
|
||||
onOpen: onOpenSubDatasetModal
|
||||
} = useDisclosure();
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
multiple: false
|
||||
@@ -147,73 +117,81 @@ const MyInfo = () => {
|
||||
[onclickSave, t, toast, userInfo]
|
||||
);
|
||||
|
||||
useQuery(['init'], initUserInfo, {
|
||||
onSuccess(res) {
|
||||
reset(res);
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
data: teamSubPlan = { totalPoints: 0, usedPoints: 0, datasetMaxSize: 800, usedDatasetSize: 0 }
|
||||
} = useQuery(['getTeamDatasetValidSub'], getTeamDatasetValidSub);
|
||||
const datasetUsageMap = useMemo(() => {
|
||||
const rate = teamSubPlan.usedDatasetSize / teamSubPlan.datasetMaxSize;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamSubPlan.datasetMaxSize || t('common.Unlimited'),
|
||||
usedSize: teamSubPlan.usedDatasetSize
|
||||
};
|
||||
}, [teamSubPlan.usedDatasetSize, teamSubPlan.datasetMaxSize, t]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* user info */}
|
||||
{isPc && (
|
||||
<Flex alignItems={'center'} fontSize={'xl'} h={'30px'}>
|
||||
<MyIcon mr={2} name={'acount/user'} w={'20px'} />
|
||||
{t('support.user.User self info')}
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Box mt={[0, 6]}>
|
||||
{isPc ? (
|
||||
<Flex alignItems={'center'} cursor={'pointer'}>
|
||||
<Box flex={'0 0 80px'}>{t('support.user.Avatar')}: </Box>
|
||||
|
||||
<MyTooltip label={t('common.avatar.Select Avatar')}>
|
||||
<Box
|
||||
w={['44px', '56px']}
|
||||
h={['44px', '56px']}
|
||||
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%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
onClick={onOpenSelectFile}
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
py={[2, 10]}
|
||||
justifyContent={'center'}
|
||||
alignItems={'flex-start'}
|
||||
>
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
onClick={onOpenSelectFile}
|
||||
>
|
||||
<MyTooltip label={'更换头像'}>
|
||||
<Box
|
||||
w={['44px', '54px']}
|
||||
h={['44px', '54px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
>
|
||||
<MyTooltip label={'更换头像'}>
|
||||
<Box
|
||||
w={['44px', '54px']}
|
||||
h={['44px', '54px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
>
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
|
||||
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
|
||||
<MyIcon mr={1} name={'edit'} w={'14px'} />
|
||||
{t('user.Replace')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
|
||||
<MyIcon mr={1} name={'edit'} w={'14px'} />
|
||||
{t('user.Replace')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
display={['flex', 'block']}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
ml={[0, 10]}
|
||||
mt={[6, 0]}
|
||||
>
|
||||
{feConfigs.isPlus && (
|
||||
<Flex mt={[0, 4]} alignItems={'center'}>
|
||||
<Flex mb={4} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Member Name')}: </Box>
|
||||
<Input
|
||||
flex={'1 0 0'}
|
||||
flex={1}
|
||||
defaultValue={userInfo?.team?.memberName || 'Member'}
|
||||
title={t('user.Edit name')}
|
||||
borderColor={'transparent'}
|
||||
pl={'10px'}
|
||||
transform={'translateX(-11px)'}
|
||||
maxLength={20}
|
||||
onBlur={(e) => {
|
||||
@@ -226,265 +204,109 @@ const MyInfo = () => {
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} mt={6}>
|
||||
<Flex alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Account')}: </Box>
|
||||
<Box flex={1}>{userInfo?.username}</Box>
|
||||
</Flex>
|
||||
{feConfigs.isPlus && (
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdatePsw}>
|
||||
{t('user.Change')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Team')}: </Box>
|
||||
<Box flex={1}>
|
||||
<TeamMenu />
|
||||
</Box>
|
||||
</Flex>
|
||||
{feConfigs.isPlus && (
|
||||
<Box mt={6} whiteSpace={'nowrap'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'} fontSize={'md'}>
|
||||
{t('user.team.Balance')}:
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
|
||||
<Button size={'sm'} ml={5} onClick={onOpenPayModal}>
|
||||
{t('user.Pay')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
||||
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const PlanUsage = () => {
|
||||
const { isPc } = useSystemStore();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, initUserInfo, teamPlanStatus } = useUserStore();
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
|
||||
const planName = useMemo(() => {
|
||||
if (!teamPlanStatus?.standard?.currentSubLevel) return '';
|
||||
return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label;
|
||||
}, [teamPlanStatus?.standard?.currentSubLevel]);
|
||||
const standardPlan = teamPlanStatus?.standard;
|
||||
|
||||
useQuery(['init'], initUserInfo, {
|
||||
onSuccess(res) {
|
||||
reset(res);
|
||||
}
|
||||
});
|
||||
|
||||
const datasetUsageMap = useMemo(() => {
|
||||
if (!teamPlanStatus) {
|
||||
return {
|
||||
colorScheme: 'green',
|
||||
value: 0,
|
||||
maxSize: t('common.Unlimited'),
|
||||
usedSize: 0
|
||||
};
|
||||
}
|
||||
const rate = teamPlanStatus.usedDatasetSize / teamPlanStatus.datasetMaxSize;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamPlanStatus.datasetMaxSize || t('common.Unlimited'),
|
||||
usedSize: teamPlanStatus.usedDatasetSize
|
||||
};
|
||||
}, [teamPlanStatus, t]);
|
||||
const aiPointsUsageMap = useMemo(() => {
|
||||
if (!teamPlanStatus) {
|
||||
return {
|
||||
colorScheme: 'green',
|
||||
value: 0,
|
||||
maxSize: t('common.Unlimited'),
|
||||
usedSize: 0
|
||||
};
|
||||
}
|
||||
|
||||
const rate = teamPlanStatus.usedPoints / teamPlanStatus.totalPoints;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamPlanStatus.totalPoints || t('common.Unlimited'),
|
||||
usedSize: teamPlanStatus.usedPoints
|
||||
};
|
||||
}, [teamPlanStatus, t]);
|
||||
|
||||
return standardPlan ? (
|
||||
<Box mt={[6, 0]}>
|
||||
<Flex fontSize={'xl'} h={'30px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon mr={2} name={'acount/plans'} w={'20px'} />
|
||||
{t('support.wallet.subscription.Team plan and usage')}
|
||||
</Flex>
|
||||
<Button
|
||||
ml={4}
|
||||
variant={'whitePrimary'}
|
||||
size={'sm'}
|
||||
onClick={() => router.push(AI_POINT_USAGE_CARD_ROUTE)}
|
||||
>
|
||||
{t('support.user.Price')}
|
||||
</Button>
|
||||
{/* <Button ml={4} variant={'whitePrimary'} size={'sm'}>
|
||||
套餐详情
|
||||
</Button> */}
|
||||
</Flex>
|
||||
<Box
|
||||
mt={[3, 6]}
|
||||
bg={'white'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Flex px={[5, 10]} py={[3, 6]}>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Language')}: </Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<Box color={'myGray.600'} fontSize="sm">
|
||||
{t('support.wallet.subscription.Current plan')}
|
||||
</Box>
|
||||
<Box fontWeight={'bold'} fontSize="xl">
|
||||
{t(planName)}
|
||||
</Box>
|
||||
<Flex mt="3" color={'#485264'} fontSize="sm">
|
||||
<Box>{t('common.Expired Time')}:</Box>
|
||||
<Box ml={2}>{formatTime2YMDHM(standardPlan?.expiredTime)}</Box>
|
||||
</Flex>
|
||||
<MySelect
|
||||
value={i18n.language}
|
||||
list={Object.entries(langMap).map(([key, lang]) => ({
|
||||
label: lang.label,
|
||||
value: key
|
||||
}))}
|
||||
onchange={(val: any) => {
|
||||
const lang = val;
|
||||
setLngStore(lang);
|
||||
router.replace(router.basePath, router.asPath, { locale: lang });
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button onClick={() => router.push('/price')}>
|
||||
{t('support.wallet.subscription.Upgrade plan')}
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Timezone')}: </Box>
|
||||
<Select
|
||||
value={userInfo?.timezone}
|
||||
onChange={(e) => {
|
||||
if (!userInfo) return;
|
||||
onclickSave({ ...userInfo, timezone: e.target.value });
|
||||
}}
|
||||
>
|
||||
{timezones.current.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={['sm', 'md']} variant={'whitePrimary'} ml={5} onClick={onOpenUpdatePsw}>
|
||||
{t('user.Change')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box py={3} borderTopWidth={'1px'} borderTopColor={'borderColor.base'}>
|
||||
<Box py={[0, 3]} px={[5, 10]} overflow={'auto'}>
|
||||
<StandardPlanContentList
|
||||
level={standardPlan?.currentSubLevel}
|
||||
mode={standardPlan.currentMode}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
mt={6}
|
||||
bg={'white'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
borderRadius={'md'}
|
||||
px={[5, 10]}
|
||||
py={[4, 7]}
|
||||
>
|
||||
<Box width={'100%'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontWeight={'bold'}>{t('support.user.team.Dataset usage')}</Box>
|
||||
<Box color={'myGray.600'} ml={2}>
|
||||
{datasetUsageMap.usedSize}/{datasetUsageMap.maxSize}
|
||||
{feConfigs.isPlus && (
|
||||
<>
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'} fontSize={'md'}>
|
||||
{t('user.team.Balance')}:
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
|
||||
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>
|
||||
{t('user.Pay')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
{feConfigs?.show_pay && (
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'} fontSize={'md'}>
|
||||
{t('support.user.team.Dataset usage')}: {datasetUsageMap.usedSize}/
|
||||
{datasetUsageMap.maxSize}
|
||||
</Box>
|
||||
{userInfo?.team?.canWrite && (
|
||||
<Button size={'sm'} onClick={onOpenSubDatasetModal}>
|
||||
{t('support.wallet.Buy more')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Box mt={1}>
|
||||
<Progress
|
||||
value={datasetUsageMap.value}
|
||||
colorScheme={datasetUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.base'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<Progress
|
||||
value={datasetUsageMap.value}
|
||||
colorScheme={datasetUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mt="9" width={'100%'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontWeight={'bold'}>{t('support.wallet.subscription.AI points')}</Box>
|
||||
<Box color={'myGray.600'} ml={2}>
|
||||
{Math.round(teamPlanStatus.usedPoints)}/{teamPlanStatus.totalPoints}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<Progress
|
||||
value={aiPointsUsageMap.value}
|
||||
colorScheme={aiPointsUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex></Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
const Other = () => {
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { feConfigs, systemVersion } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, updateUserInfo, initUserInfo, teamPlanStatus } = useUserStore();
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
|
||||
const onclickSave = useCallback(
|
||||
async (data: UserType) => {
|
||||
await updateUserInfo({
|
||||
avatar: data.avatar,
|
||||
timezone: data.timezone,
|
||||
openaiAccount: data.openaiAccount
|
||||
});
|
||||
reset(data);
|
||||
toast({
|
||||
title: '更新数据成功',
|
||||
status: 'success'
|
||||
});
|
||||
},
|
||||
[reset, toast, updateUserInfo]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Grid gridGap={4} mt={3}>
|
||||
{feConfigs?.docUrl && (
|
||||
<Link
|
||||
bg={'white'}
|
||||
href={getDocPath('/docs/intro')}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
mt={4}
|
||||
w={['85%', '300px']}
|
||||
py={3}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
@@ -504,53 +326,64 @@ const Other = () => {
|
||||
</Box>
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href={feConfigs.chatbotUrl}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
py={3}
|
||||
px={6}
|
||||
bg={'white'}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
userSelect={'none'}
|
||||
textDecoration={'none !important'}
|
||||
>
|
||||
<MyIcon name={'core/app/aiLight'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('common.system.Help Chatbot')}
|
||||
</Box>
|
||||
</Link>
|
||||
|
||||
{feConfigs?.show_openai_account && (
|
||||
<Flex
|
||||
bg={'white'}
|
||||
py={4}
|
||||
{feConfigs?.chatbotUrl && (
|
||||
<Link
|
||||
href={feConfigs.chatbotUrl}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
mt={4}
|
||||
w={['85%', '300px']}
|
||||
py={3}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
onClick={onOpenOpenai}
|
||||
textDecoration={'none !important'}
|
||||
>
|
||||
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
|
||||
<MyIcon name={'core/app/aiLight'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
OpenAI/OneAPI 账号
|
||||
{t('common.system.Help Chatbot')}
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
h={'9px'}
|
||||
borderRadius={'50%'}
|
||||
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
|
||||
/>
|
||||
</Flex>
|
||||
</Link>
|
||||
)}
|
||||
</Grid>
|
||||
{feConfigs?.show_openai_account && (
|
||||
<>
|
||||
<Divider my={3} />
|
||||
|
||||
<MyTooltip label={'点击配置账号'}>
|
||||
<Flex
|
||||
w={['85%', '300px']}
|
||||
py={4}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
bg={'myWhite.300'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
onClick={onOpenOpenai}
|
||||
>
|
||||
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
OpenAI/OneAPI 账号
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
h={'9px'}
|
||||
borderRadius={'50%'}
|
||||
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
||||
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
|
||||
{isOpenOpenai && userInfo && (
|
||||
<OpenAIAccountModal
|
||||
defaultData={userInfo?.openaiAccount}
|
||||
@@ -563,6 +396,10 @@ const Other = () => {
|
||||
onClose={onCloseOpenai}
|
||||
/>
|
||||
)}
|
||||
{isOpenSubDatasetModal && <SubDatasetModal onClose={onCloseSubDatasetModal} />}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UserInfo);
|
||||
|
@@ -1,439 +0,0 @@
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
useDisclosure,
|
||||
useTheme,
|
||||
Divider,
|
||||
Select,
|
||||
Input,
|
||||
Link,
|
||||
Progress
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import type { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { timezoneList } from '@fastgpt/global/common/time/timezone';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { langMap, setLngStore } from '@/web/common/utils/i18n';
|
||||
import { useRouter } from 'next/router';
|
||||
import MySelect from '@/components/Select';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { putUpdateMemberName } from '@/web/support/user/team/api';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { getTeamPlanStatus } from '@/web/support/wallet/sub/api';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
|
||||
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
|
||||
const PayModal = dynamic(() => import('./PayModal'));
|
||||
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
|
||||
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
|
||||
|
||||
const UserInfo = () => {
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const { feConfigs, systemVersion } = useSystemStore();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
|
||||
const timezones = useRef(timezoneList());
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
isOpen: isOpenPayModal,
|
||||
onClose: onClosePayModal,
|
||||
onOpen: onOpenPayModal
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenUpdatePsw,
|
||||
onClose: onCloseUpdatePsw,
|
||||
onOpen: onOpenUpdatePsw
|
||||
} = useDisclosure();
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
const onclickSave = useCallback(
|
||||
async (data: UserType) => {
|
||||
await updateUserInfo({
|
||||
avatar: data.avatar,
|
||||
timezone: data.timezone,
|
||||
openaiAccount: data.openaiAccount
|
||||
});
|
||||
reset(data);
|
||||
toast({
|
||||
title: '更新数据成功',
|
||||
status: 'success'
|
||||
});
|
||||
},
|
||||
[reset, toast, updateUserInfo]
|
||||
);
|
||||
|
||||
const onSelectFile = useCallback(
|
||||
async (e: File[]) => {
|
||||
const file = e[0];
|
||||
if (!file || !userInfo) return;
|
||||
try {
|
||||
const src = await compressImgFileAndUpload({
|
||||
type: MongoImageTypeEnum.userAvatar,
|
||||
file,
|
||||
maxW: 300,
|
||||
maxH: 300
|
||||
});
|
||||
|
||||
onclickSave({
|
||||
...userInfo,
|
||||
avatar: src
|
||||
});
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: typeof err === 'string' ? err : t('common.error.Select avatar failed'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
},
|
||||
[onclickSave, t, toast, userInfo]
|
||||
);
|
||||
|
||||
useQuery(['init'], initUserInfo, {
|
||||
onSuccess(res) {
|
||||
reset(res);
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
data: teamSubPlan = {
|
||||
totalPoints: 0,
|
||||
usedPoints: 0,
|
||||
datasetMaxSize: 800,
|
||||
usedDatasetSize: 0
|
||||
}
|
||||
} = useQuery(['getTeamPlanStatus'], getTeamPlanStatus);
|
||||
const datasetUsageMap = useMemo(() => {
|
||||
const rate = teamSubPlan.usedDatasetSize / teamSubPlan.datasetMaxSize;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamSubPlan.datasetMaxSize || t('common.Unlimited'),
|
||||
usedSize: teamSubPlan.usedDatasetSize
|
||||
};
|
||||
}, [teamSubPlan.usedDatasetSize, teamSubPlan.datasetMaxSize, t]);
|
||||
const aiPointsUsageMap = useMemo(() => {
|
||||
const rate = teamSubPlan.usedPoints / teamSubPlan.totalPoints;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamSubPlan.totalPoints || t('common.Unlimited'),
|
||||
usedSize: teamSubPlan.usedPoints
|
||||
};
|
||||
}, [teamSubPlan.usedPoints, teamSubPlan.totalPoints, t]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
py={[2, 10]}
|
||||
justifyContent={'center'}
|
||||
alignItems={'flex-start'}
|
||||
>
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
onClick={onOpenSelectFile}
|
||||
>
|
||||
<MyTooltip label={'更换头像'}>
|
||||
<Box
|
||||
w={['44px', '54px']}
|
||||
h={['44px', '54px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
>
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
|
||||
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
|
||||
<MyIcon mr={1} name={'edit'} w={'14px'} />
|
||||
{t('user.Replace')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
display={['flex', 'block']}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
ml={[0, 10]}
|
||||
mt={[6, 0]}
|
||||
>
|
||||
{feConfigs.isPlus && (
|
||||
<Flex mb={4} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Member Name')}: </Box>
|
||||
<Input
|
||||
flex={1}
|
||||
defaultValue={userInfo?.team?.memberName || 'Member'}
|
||||
title={t('user.Edit name')}
|
||||
borderColor={'transparent'}
|
||||
pl={'10px'}
|
||||
transform={'translateX(-11px)'}
|
||||
maxLength={20}
|
||||
onBlur={(e) => {
|
||||
const val = e.target.value;
|
||||
if (val === userInfo?.team?.memberName) return;
|
||||
try {
|
||||
putUpdateMemberName(val);
|
||||
} catch (error) {}
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Account')}: </Box>
|
||||
<Box flex={1}>{userInfo?.username}</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Team')}: </Box>
|
||||
<Box flex={1}>
|
||||
<TeamMenu />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Language')}: </Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect
|
||||
value={i18n.language}
|
||||
list={Object.entries(langMap).map(([key, lang]) => ({
|
||||
label: lang.label,
|
||||
value: key
|
||||
}))}
|
||||
onchange={(val: any) => {
|
||||
const lang = val;
|
||||
setLngStore(lang);
|
||||
router.replace(router.basePath, router.asPath, { locale: lang });
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Timezone')}: </Box>
|
||||
<Select
|
||||
value={userInfo?.timezone}
|
||||
onChange={(e) => {
|
||||
if (!userInfo) return;
|
||||
onclickSave({ ...userInfo, timezone: e.target.value });
|
||||
}}
|
||||
>
|
||||
{timezones.current.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={['sm', 'md']} variant={'whitePrimary'} ml={5} onClick={onOpenUpdatePsw}>
|
||||
{t('user.Change')}
|
||||
</Button>
|
||||
</Flex>
|
||||
{feConfigs.isPlus && (
|
||||
<>
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'} fontSize={'md'}>
|
||||
{t('user.team.Balance')}:
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
|
||||
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>
|
||||
{t('user.Pay')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
{feConfigs?.show_pay && (
|
||||
<>
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'} fontSize={'md'}>
|
||||
{t('support.user.team.Dataset usage')}: {datasetUsageMap.usedSize}/
|
||||
{datasetUsageMap.maxSize}
|
||||
</Box>
|
||||
{userInfo?.team?.canWrite && (
|
||||
<Button size={'sm'} onClick={() => router.push('/price')}>
|
||||
{t('support.wallet.Buy more')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Box mt={1}>
|
||||
<Progress
|
||||
value={datasetUsageMap.value}
|
||||
colorScheme={datasetUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.base'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'} fontSize={'md'}>
|
||||
AI积分: {Math.round(teamSubPlan.usedPoints)}/{teamSubPlan.totalPoints}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box mt={1}>
|
||||
<Progress
|
||||
value={aiPointsUsageMap.value}
|
||||
colorScheme={aiPointsUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.base'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{feConfigs?.docUrl && (
|
||||
<Link
|
||||
href={getDocPath('/docs/intro')}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
mt={4}
|
||||
w={['85%', '300px']}
|
||||
py={3}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
userSelect={'none'}
|
||||
textDecoration={'none !important'}
|
||||
>
|
||||
<MyIcon name={'common/courseLight'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('system.Help Document')}
|
||||
</Box>
|
||||
<Box w={'8px'} h={'8px'} borderRadius={'50%'} bg={'#67c13b'} />
|
||||
<Box fontSize={'md'} ml={2}>
|
||||
V{systemVersion}
|
||||
</Box>
|
||||
</Link>
|
||||
)}
|
||||
{feConfigs?.chatbotUrl && (
|
||||
<Link
|
||||
href={feConfigs.chatbotUrl}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
mt={4}
|
||||
w={['85%', '300px']}
|
||||
py={3}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
userSelect={'none'}
|
||||
textDecoration={'none !important'}
|
||||
>
|
||||
<MyIcon name={'core/app/aiLight'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('common.system.Help Chatbot')}
|
||||
</Box>
|
||||
</Link>
|
||||
)}
|
||||
{feConfigs?.show_openai_account && (
|
||||
<>
|
||||
<Divider my={3} />
|
||||
|
||||
<MyTooltip label={'点击配置账号'}>
|
||||
<Flex
|
||||
w={['85%', '300px']}
|
||||
py={4}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
bg={'myWhite.300'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
onClick={onOpenOpenai}
|
||||
>
|
||||
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
OpenAI/OneAPI 账号
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
h={'9px'}
|
||||
borderRadius={'50%'}
|
||||
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
||||
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
|
||||
{isOpenOpenai && userInfo && (
|
||||
<OpenAIAccountModal
|
||||
defaultData={userInfo?.openaiAccount}
|
||||
onSuccess={(data) =>
|
||||
onclickSave({
|
||||
...userInfo,
|
||||
openaiAccount: data
|
||||
})
|
||||
}
|
||||
onClose={onCloseOpenai}
|
||||
/>
|
||||
)}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UserInfo);
|
@@ -1,45 +1,37 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { ModalFooter, ModalBody, Button, Input, Box, Grid } from '@chakra-ui/react';
|
||||
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
|
||||
import { getPayCode, checkPayResult } from '@/web/support/wallet/pay/api';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
|
||||
const PayModal = ({
|
||||
onClose,
|
||||
defaultValue,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultValue?: number;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => any;
|
||||
}) => {
|
||||
const PayModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [inputVal, setInputVal] = useState<number | undefined>(defaultValue);
|
||||
const [inputVal, setInputVal] = useState<number | ''>('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
const [payId, setPayId] = useState('');
|
||||
|
||||
const handleClickPay = useCallback(async () => {
|
||||
if (!inputVal || inputVal <= 0 || isNaN(+inputVal)) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
// 获取支付二维码
|
||||
const res = await getWxPayQRCode({
|
||||
type: BillTypeEnum.balance,
|
||||
balance: inputVal
|
||||
});
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
const res = await getPayCode(inputVal);
|
||||
new window.QRCode(document.getElementById('payQRCode'), {
|
||||
text: res.codeUrl,
|
||||
width: 128,
|
||||
height: 128,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: window.QRCode.CorrectLevel.H
|
||||
});
|
||||
setPayId(res.payId);
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
@@ -49,48 +41,84 @@ const PayModal = ({
|
||||
setLoading(false);
|
||||
}, [inputVal, toast]);
|
||||
|
||||
useQuery(
|
||||
[payId],
|
||||
() => {
|
||||
if (!payId) return null;
|
||||
return checkPayResult(payId);
|
||||
},
|
||||
{
|
||||
enabled: !!payId,
|
||||
refetchInterval: 3000,
|
||||
onSuccess(res) {
|
||||
if (!res) return;
|
||||
toast({
|
||||
title: res,
|
||||
status: 'success'
|
||||
});
|
||||
router.reload();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose} title={t('user.Pay')} iconSrc="/imgs/modal/pay.svg">
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={payId ? undefined : onClose}
|
||||
title={t('user.Pay')}
|
||||
iconSrc="/imgs/modal/pay.svg"
|
||||
>
|
||||
<ModalBody px={0} display={'flex'} flexDirection={'column'}>
|
||||
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
|
||||
{[10, 20, 50, 100, 200, 500].map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
variant={item === inputVal ? 'solid' : 'outline'}
|
||||
onClick={() => setInputVal(item)}
|
||||
>
|
||||
{item}元
|
||||
</Button>
|
||||
))}
|
||||
</Grid>
|
||||
<Box px={6}>
|
||||
<Input
|
||||
value={inputVal}
|
||||
type={'number'}
|
||||
step={1}
|
||||
placeholder={'其他金额,请取整数'}
|
||||
onChange={(e) => {
|
||||
setInputVal(Math.floor(+e.target.value));
|
||||
}}
|
||||
></Input>
|
||||
{!payId && (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
|
||||
{[10, 20, 50, 100, 200, 500].map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
variant={item === inputVal ? 'solid' : 'outline'}
|
||||
onClick={() => setInputVal(item)}
|
||||
>
|
||||
{item}元
|
||||
</Button>
|
||||
))}
|
||||
</Grid>
|
||||
<Box px={6}>
|
||||
<Input
|
||||
value={inputVal}
|
||||
type={'number'}
|
||||
step={1}
|
||||
placeholder={'其他金额,请取整数'}
|
||||
onChange={(e) => {
|
||||
setInputVal(Math.floor(+e.target.value));
|
||||
}}
|
||||
></Input>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{/* 付费二维码 */}
|
||||
<Box textAlign={'center'}>
|
||||
{payId && <Box mb={3}>请微信扫码支付: {inputVal}元,请勿关闭页面</Box>}
|
||||
<Box id={'payQRCode'} display={'inline-block'}></Box>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
ml={3}
|
||||
isLoading={loading}
|
||||
isDisabled={!inputVal || inputVal === 0}
|
||||
onClick={handleClickPay}
|
||||
>
|
||||
获取充值二维码
|
||||
</Button>
|
||||
{!payId && (
|
||||
<>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
ml={3}
|
||||
isLoading={loading}
|
||||
isDisabled={!inputVal || inputVal === 0}
|
||||
onClick={handleClickPay}
|
||||
>
|
||||
获取充值二维码
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</ModalFooter>
|
||||
|
||||
{!!qrPayData && <QRCodePayModal {...qrPayData} onSuccess={onSuccess} />}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
108
projects/app/src/pages/account/components/PayRecordTable.tsx
Normal file
108
projects/app/src/pages/account/components/PayRecordTable.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Flex,
|
||||
Box
|
||||
} from '@chakra-ui/react';
|
||||
import { getPayOrders, checkPayResult } from '@/web/support/wallet/pay/api';
|
||||
import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d';
|
||||
import dayjs from 'dayjs';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const PayRecordTable = () => {
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const [payOrders, setPayOrders] = useState<PaySchema[]>([]);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { isInitialLoading, refetch } = useQuery(['initPayOrder'], getPayOrders, {
|
||||
onSuccess(res) {
|
||||
setPayOrders(res);
|
||||
}
|
||||
});
|
||||
|
||||
const handleRefreshPayOrder = useCallback(
|
||||
async (payId: string) => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const data = await checkPayResult(payId);
|
||||
toast({
|
||||
title: data,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: error?.message,
|
||||
status: 'warning'
|
||||
});
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
refetch();
|
||||
} catch (error) {}
|
||||
|
||||
setIsLoading(false);
|
||||
},
|
||||
[refetch, setIsLoading, toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box position={'relative'} h={'100%'} overflow={'overlay'}>
|
||||
{!isInitialLoading && payOrders.length === 0 ? (
|
||||
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} justifyContent={'center'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
无支付记录~
|
||||
</Box>
|
||||
</Flex>
|
||||
) : (
|
||||
<TableContainer py={[0, 5]} px={[3, 8]}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>订单号</Th>
|
||||
<Th>时间</Th>
|
||||
<Th>金额</Th>
|
||||
<Th>状态</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{payOrders.map((item) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.orderId}</Td>
|
||||
<Td>
|
||||
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
|
||||
</Td>
|
||||
<Td>{formatStorePrice2Read(item.price)}元</Td>
|
||||
<Td>{item.status}</Td>
|
||||
<Td>
|
||||
{item.status === 'NOTPAY' && (
|
||||
<Button onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
|
||||
更新
|
||||
</Button>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
<Loading loading={isInitialLoading} fixed={false} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PayRecordTable;
|
@@ -1,119 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
ModalBody,
|
||||
Flex,
|
||||
Box,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer
|
||||
} from '@chakra-ui/react';
|
||||
import { UsageItemType } from '@fastgpt/global/support/wallet/usage/type.d';
|
||||
import dayjs from 'dayjs';
|
||||
import { UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
|
||||
const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const filterBillList = useMemo(
|
||||
() => usage.list.filter((item) => item && item.moduleName),
|
||||
[usage.list]
|
||||
);
|
||||
|
||||
const { hasModel, hasCharsLen, hasDuration } = useMemo(() => {
|
||||
let hasModel = false;
|
||||
let hasCharsLen = false;
|
||||
let hasDuration = false;
|
||||
let hasDataLen = false;
|
||||
|
||||
usage.list.forEach((item) => {
|
||||
if (item.model !== undefined) {
|
||||
hasModel = true;
|
||||
}
|
||||
|
||||
if (typeof item.charsLength === 'number') {
|
||||
hasCharsLen = true;
|
||||
}
|
||||
if (typeof item.duration === 'number') {
|
||||
hasDuration = true;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
hasModel,
|
||||
hasCharsLen,
|
||||
hasDuration,
|
||||
hasDataLen
|
||||
};
|
||||
}, [usage.list]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/bill.svg"
|
||||
title={t('support.wallet.usage.Usage Detail')}
|
||||
maxW={['90vw', '700px']}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.bill.Number')}:</Box>
|
||||
<Box>{usage.id}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.usage.Time')}:</Box>
|
||||
<Box>{dayjs(usage.time).format('YYYY/MM/DD HH:mm:ss')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.usage.App name')}:</Box>
|
||||
<Box>{t(usage.appName) || '-'}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.usage.Source')}:</Box>
|
||||
<Box>{t(UsageSourceMap[usage.source]?.label)}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.usage.Total points')}:</Box>
|
||||
<Box fontWeight={'bold'}>{formatNumber(usage.totalPoints)}</Box>
|
||||
</Flex>
|
||||
<Box pb={4}>
|
||||
<Box flex={'0 0 80px'} mb={1}>
|
||||
{t('support.wallet.usage.Bill Module')}
|
||||
</Box>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('support.wallet.usage.Module name')}</Th>
|
||||
{hasModel && <Th>{t('support.wallet.usage.Ai model')}</Th>}
|
||||
{hasCharsLen && <Th>{t('support.wallet.usage.Text Length')}</Th>}
|
||||
{hasDuration && <Th>{t('support.wallet.usage.Duration')}</Th>}
|
||||
|
||||
<Th>{t('support.wallet.usage.Total points')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{filterBillList.map((item, i) => (
|
||||
<Tr key={i}>
|
||||
<Td>{t(item.moduleName)}</Td>
|
||||
{hasModel && <Td>{item.model ?? '-'}</Td>}
|
||||
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
|
||||
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
|
||||
<Td>{formatNumber(item.amount)}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsageDetail;
|
@@ -1,189 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Flex,
|
||||
Box,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import { UsageSourceEnum, UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { getUserUsages } from '@/web/support/wallet/usage/api';
|
||||
import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
|
||||
import { usePagination } from '@/web/common/hooks/usePagination';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import dayjs from 'dayjs';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
|
||||
import { addDays } from 'date-fns';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { formatNumber } from '../../../../../../packages/global/common/math/tools';
|
||||
const UsageDetail = dynamic(() => import('./UsageDetail'));
|
||||
|
||||
const UsageTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading } = useLoading();
|
||||
const [dateRange, setDateRange] = useState<DateRangeType>({
|
||||
from: addDays(new Date(), -7),
|
||||
to: new Date()
|
||||
});
|
||||
const [usageSource, setUsageSource] = useState<`${UsageSourceEnum}` | ''>('');
|
||||
const { isPc } = useSystemStore();
|
||||
const { userInfo } = useUserStore();
|
||||
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
|
||||
|
||||
const sourceList = useMemo(
|
||||
() => [
|
||||
{ label: t('common.All'), value: '' },
|
||||
...Object.entries(UsageSourceMap).map(([key, value]) => ({
|
||||
label: t(value.label),
|
||||
value: key
|
||||
}))
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId);
|
||||
const { data: members = [] } = useQuery(['getMembers', userInfo?.team?.teamId], () => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
return getTeamMembers(userInfo.team.teamId);
|
||||
});
|
||||
const tmbList = useMemo(
|
||||
() =>
|
||||
members.map((item) => ({
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={'16px'} mr={1} />
|
||||
{item.memberName}
|
||||
</Flex>
|
||||
),
|
||||
value: item.tmbId
|
||||
})),
|
||||
[members]
|
||||
);
|
||||
|
||||
const {
|
||||
data: usages,
|
||||
isLoading,
|
||||
Pagination,
|
||||
getData
|
||||
} = usePagination<UsageItemType>({
|
||||
api: getUserUsages,
|
||||
pageSize: isPc ? 20 : 10,
|
||||
params: {
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
source: usageSource,
|
||||
teamMemberId: selectTmbId
|
||||
},
|
||||
defaultRequest: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getData(1);
|
||||
}, [usageSource, selectTmbId]);
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
|
||||
<Flex
|
||||
flexDir={['column', 'row']}
|
||||
gap={2}
|
||||
w={'100%'}
|
||||
px={[3, 8]}
|
||||
alignItems={['flex-end', 'center']}
|
||||
>
|
||||
{tmbList.length > 1 && userInfo?.team?.canWrite && (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box mr={2} flexShrink={0}>
|
||||
{t('support.user.team.member')}
|
||||
</Box>
|
||||
<MySelect
|
||||
size={'sm'}
|
||||
minW={'100px'}
|
||||
list={tmbList}
|
||||
value={selectTmbId}
|
||||
onchange={setSelectTmbId}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Box flex={'1'} />
|
||||
<Flex alignItems={'center'} gap={3}>
|
||||
<DateRangePicker
|
||||
defaultDate={dateRange}
|
||||
position="bottom"
|
||||
onChange={setDateRange}
|
||||
onSuccess={() => getData(1)}
|
||||
/>
|
||||
<Pagination />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<TableContainer px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
{/* <Th>{t('user.team.Member Name')}</Th> */}
|
||||
<Th>{t('user.Time')}</Th>
|
||||
<Th>
|
||||
<MySelect
|
||||
list={sourceList}
|
||||
value={usageSource}
|
||||
size={'sm'}
|
||||
onchange={(e) => {
|
||||
setUsageSource(e);
|
||||
}}
|
||||
w={'130px'}
|
||||
></MySelect>
|
||||
</Th>
|
||||
<Th>{t('user.Application Name')}</Th>
|
||||
<Th>{t('support.wallet.usage.Total points')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{usages.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
{/* <Td>{item.memberName}</Td> */}
|
||||
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
|
||||
<Td>{t(UsageSourceMap[item.source]?.label) || '-'}</Td>
|
||||
<Td>{t(item.appName) || '-'}</Td>
|
||||
<Td>{formatNumber(item.totalPoints) || 0}</Td>
|
||||
<Td>
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={() => setUsageDetail(item)}>
|
||||
详情
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{!isLoading && usages.length === 0 && (
|
||||
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
无使用记录~
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
{!!usageDetail && (
|
||||
<UsageDetail usage={usageDetail} onClose={() => setUsageDetail(undefined)} />
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UsageTable);
|
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import type { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
|
||||
const StandDetailModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="acount/plansBlue"
|
||||
title={t('user.OpenAI Account Setting')}
|
||||
>
|
||||
<ModalBody></ModalBody>
|
||||
<ModalFooter></ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default StandDetailModal;
|
@@ -14,16 +14,18 @@ import { useTranslation } from 'next-i18next';
|
||||
import Script from 'next/script';
|
||||
|
||||
const Promotion = dynamic(() => import('./components/Promotion'));
|
||||
const UsageTable = dynamic(() => import('./components/UsageTable'));
|
||||
const BillTable = dynamic(() => import('./components/BillTable'));
|
||||
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'));
|
||||
const InformTable = dynamic(() => import('./components/InformTable'));
|
||||
const ApiKeyTable = dynamic(() => import('./components/ApiKeyTable'));
|
||||
const PriceBox = dynamic(() => import('@/components/support/wallet/Price'));
|
||||
|
||||
enum TabEnum {
|
||||
'info' = 'info',
|
||||
'promotion' = 'promotion',
|
||||
'usage' = 'usage',
|
||||
'bill' = 'bill',
|
||||
'price' = 'price',
|
||||
'pay' = 'pay',
|
||||
'inform' = 'inform',
|
||||
'apikey' = 'apikey',
|
||||
'loginout' = 'loginout'
|
||||
@@ -43,18 +45,27 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
...(feConfigs?.isPlus
|
||||
? [
|
||||
{
|
||||
icon: 'support/usage/usageRecordLight',
|
||||
icon: 'support/bill/billRecordLight',
|
||||
label: t('user.Usage Record'),
|
||||
id: TabEnum.usage
|
||||
id: TabEnum.bill
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.show_pay && userInfo?.team.canWrite
|
||||
? [
|
||||
{
|
||||
icon: 'support/bill/payRecordLight',
|
||||
label: t('support.wallet.Bills'),
|
||||
id: TabEnum.bill
|
||||
icon: 'support/pay/payRecordLight',
|
||||
label: t('user.Recharge Record'),
|
||||
id: TabEnum.pay
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.show_pay
|
||||
? [
|
||||
{
|
||||
icon: 'support/pay/priceLight',
|
||||
label: t('support.user.Price'),
|
||||
id: TabEnum.price
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -97,6 +108,11 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
content: '确认退出登录?'
|
||||
});
|
||||
const {
|
||||
isOpen: isOpenPriceBox,
|
||||
onOpen: onOpenPriceBox,
|
||||
onClose: onClosePriceBox
|
||||
} = useDisclosure();
|
||||
|
||||
const router = useRouter();
|
||||
const theme = useTheme();
|
||||
@@ -108,6 +124,8 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
setUserInfo(null);
|
||||
router.replace('/login');
|
||||
})();
|
||||
} else if (tab === TabEnum.price) {
|
||||
onOpenPriceBox();
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
@@ -116,7 +134,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
});
|
||||
}
|
||||
},
|
||||
[openConfirm, router, setUserInfo]
|
||||
[onOpenPriceBox, openConfirm, router, setUserInfo]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -160,14 +178,16 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
|
||||
{currentTab === TabEnum.info && <UserInfo />}
|
||||
{currentTab === TabEnum.promotion && <Promotion />}
|
||||
{currentTab === TabEnum.usage && <UsageTable />}
|
||||
{currentTab === TabEnum.bill && <BillTable />}
|
||||
{currentTab === TabEnum.pay && <PayRecordTable />}
|
||||
{currentTab === TabEnum.inform && <InformTable />}
|
||||
{currentTab === TabEnum.apikey && <ApiKeyTable />}
|
||||
</Box>
|
||||
</Flex>
|
||||
<ConfirmModal />
|
||||
</PageContainer>
|
||||
|
||||
{isOpenPriceBox && <PriceBox onClose={onClosePriceBox} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user