mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-06 07:28:56 +00:00
sub plan page (#885)
* perf: insert mongo dataset data session * perf: dataset data index * remove delay * rename bill schema * rename bill record * perf: bill table * perf: prompt * perf: sub plan * change the usage count * feat: usage bill * publish usages * doc * 新增团队聊天功能 (#20) * perf: doc * feat 添加标签部分 feat 信息团队标签配置 feat 新增团队同步管理 feat team分享页面 feat 完成team分享页面 feat 实现模糊搜索 style 格式化 fix 修复迷糊匹配 style 样式修改 fix 团队标签功能修复 * fix 修复鉴权功能 * merge 合并代码 * fix 修复引用错误 * fix 修复pr问题 * fix 修复ts格式问题 --------- Co-authored-by: archer <545436317@qq.com> Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com> * update extra plan * fix: ts * format * perf: bill field * feat: standard plan * fix: ts * feat 个人账号页面修改 (#22) * feat 添加标签部分 feat 信息团队标签配置 feat 新增团队同步管理 feat team分享页面 feat 完成team分享页面 feat 实现模糊搜索 style 格式化 fix 修复迷糊匹配 style 样式修改 fix 团队标签功能修复 * fix 修复鉴权功能 * merge 合并代码 * fix 修复引用错误 * fix 修复pr问题 * fix 修复ts格式问题 * feat 修改个人账号页 --------- Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com> * fix chunk index; error page text * feat: dataset process Integral prediction * feat: stand plan field * feat: sub plan limit * perf: index * query extension * perf: share link push app name * perf: plan point unit * perf: get sub plan * perf: account page --------- Co-authored-by: yst <77910600+yu-and-liu@users.noreply.github.com> Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com>
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, Button, Flex, Grid } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Grid, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { postCheckStandardSub, postUpdateStandardSub } from '@/web/support/wallet/sub/api';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
@@ -11,9 +10,21 @@ import { StandardSubPlanParams } from '@fastgpt/global/support/wallet/sub/api';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { StandardSubPlanUpdateResponse } from '@fastgpt/global/support/wallet/sub/api.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
|
||||
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
||||
|
||||
type ConfirmPayModalProps = {
|
||||
teamBalance: number;
|
||||
totalPrice: number;
|
||||
payPrice: number;
|
||||
|
||||
planProps: StandardSubPlanParams;
|
||||
};
|
||||
|
||||
const Standard = ({
|
||||
standardPlan,
|
||||
@@ -25,7 +36,7 @@ const Standard = ({
|
||||
const { t } = useTranslation();
|
||||
const { subPlans, feConfigs } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
const [confirmPayData, setConfirmPayData] = useState<ConfirmPayModalProps>();
|
||||
|
||||
const [selectSubMode, setSelectSubMode] = useState<`${SubModeEnum}`>(SubModeEnum.month);
|
||||
|
||||
@@ -41,12 +52,12 @@ const Standard = ({
|
||||
maxDatasetAmount: value.maxDatasetAmount,
|
||||
chatHistoryStoreDuration: value.chatHistoryStoreDuration,
|
||||
maxDatasetSize: value.maxDatasetSize,
|
||||
customApiKey: value.customApiKey,
|
||||
customCopyright: value.customCopyright,
|
||||
permissionCustomApiKey: value.permissionCustomApiKey,
|
||||
permissionCustomCopyright: value.permissionCustomCopyright,
|
||||
trainingWeight: value.trainingWeight,
|
||||
reRankWeight: value.reRankWeight,
|
||||
permissionReRank: value.permissionReRank,
|
||||
totalPoints: value.totalPoints * (selectSubMode === SubModeEnum.month ? 1 : 12),
|
||||
websiteSyncInterval: value.websiteSyncInterval
|
||||
permissionWebsiteSync: value.permissionWebsiteSync
|
||||
};
|
||||
})
|
||||
: [];
|
||||
@@ -64,41 +75,21 @@ const Standard = ({
|
||||
const { mutate: onclickPreCheckStandPlan, isLoading: isCheckingStandardPlan } = useRequest({
|
||||
mutationFn: (data: StandardSubPlanParams) => postCheckStandardSub(data),
|
||||
onSuccess(res: StandardSubPlanUpdateResponse) {
|
||||
if (!res.balanceEnough) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('support.wallet.Balance not enough tip')
|
||||
});
|
||||
}
|
||||
if (res.payPrice === undefined) {
|
||||
onclickUpdateStandardPlan({
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
});
|
||||
} else if (res.payPrice > 0) {
|
||||
openConfirm(
|
||||
() =>
|
||||
onclickUpdateStandardPlan({
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
}),
|
||||
undefined,
|
||||
t('support.wallet.subscription.Standard plan pay confirm', {
|
||||
payPrice: formatStorePrice2Read(res.payPrice).toFixed(2)
|
||||
})
|
||||
)();
|
||||
} else {
|
||||
openConfirm(
|
||||
() =>
|
||||
onclickUpdateStandardPlan({
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
}),
|
||||
undefined,
|
||||
t('support.wallet.subscription.Refund plan and pay confirm', {
|
||||
amount: formatStorePrice2Read(Math.abs(res.payPrice)).toFixed(2)
|
||||
})
|
||||
)();
|
||||
setConfirmPayData({
|
||||
teamBalance: res.teamBalance,
|
||||
totalPrice: res.planPrice,
|
||||
payPrice: res.payPrice,
|
||||
planProps: {
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -138,148 +129,114 @@ const Standard = ({
|
||||
gap={[4, 6, 8]}
|
||||
w={'100%'}
|
||||
>
|
||||
{standardSubList.map((item) => (
|
||||
<Box
|
||||
key={item.level}
|
||||
bg={'rgba(255, 255, 255, 0.90)'}
|
||||
p={'28px'}
|
||||
borderRadius={'2xl'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
boxShadow={'1.5'}
|
||||
>
|
||||
<Box fontSize={'lg'} fontWeight={'500'}>
|
||||
{t(item.label)}
|
||||
</Box>
|
||||
<Box fontSize={['32px', '42px']} fontWeight={'bold'}>
|
||||
¥{item.price}
|
||||
</Box>
|
||||
<Box color={'myGray.500'} h={'40px'}>
|
||||
{t(item.desc, { title: feConfigs?.systemTitle })}
|
||||
</Box>
|
||||
{(() => {
|
||||
if (item.level === StandardSubLevelEnum.free && selectSubMode === SubModeEnum.year) {
|
||||
return (
|
||||
<Button isDisabled mt={4} mb={6} w={'100%'} variant={'solid'}>
|
||||
{t('support.wallet.subscription.Nonsupport')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (
|
||||
item.level === standardPlan?.currentSubLevel &&
|
||||
selectSubMode === standardPlan?.currentMode
|
||||
) {
|
||||
return (
|
||||
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
|
||||
{t('support.wallet.subscription.Current plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (
|
||||
item.level === standardPlan?.nextSubLevel &&
|
||||
selectSubMode === standardPlan?.nextMode
|
||||
) {
|
||||
return (
|
||||
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
|
||||
{t('support.wallet.subscription.Next plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
mt={4}
|
||||
mb={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={isUpdatingStandardPlan || isCheckingStandardPlan}
|
||||
onClick={() =>
|
||||
onclickPreCheckStandPlan({
|
||||
level: item.level,
|
||||
mode: selectSubMode
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('support.wallet.subscription.Buy now')}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
{standardSubList.map((item) => {
|
||||
const isCurrentPlan =
|
||||
item.level === standardPlan?.currentSubLevel &&
|
||||
selectSubMode === standardPlan?.currentMode;
|
||||
|
||||
{/* function list */}
|
||||
<Grid gap={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max members', {
|
||||
amount: item.maxTeamMember
|
||||
return (
|
||||
<Box
|
||||
key={item.level}
|
||||
flex={'1 0 0'}
|
||||
bg={'rgba(255, 255, 255, 0.90)'}
|
||||
p={'28px'}
|
||||
borderRadius={'2xl'}
|
||||
borderWidth={'1.5px'}
|
||||
boxShadow={'1.5'}
|
||||
{...(isCurrentPlan
|
||||
? {
|
||||
borderColor: 'primary.600'
|
||||
}
|
||||
: {
|
||||
borderColor: 'myGray.150'
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max app', {
|
||||
amount: item.maxAppAmount
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max dataset', {
|
||||
amount: item.maxDatasetAmount
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.History store', {
|
||||
amount: item.chatHistoryStoreDuration
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max dataset size', {
|
||||
amount: item.maxDatasetSize
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Points', {
|
||||
amount: item.totalPoints
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.Training weight', {
|
||||
weight: item.trainingWeight
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
{!!item.customApiKey && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>个人API Key</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!item.websiteSyncInterval && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>{item.websiteSyncInterval} h/次 web站点同步</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
))}
|
||||
>
|
||||
<Box fontSize={'lg'} fontWeight={'500'}>
|
||||
{t(item.label)}
|
||||
</Box>
|
||||
<Box fontSize={['32px', '42px']} fontWeight={'bold'}>
|
||||
¥{item.price}
|
||||
</Box>
|
||||
<Box color={'myGray.500'} h={'40px'} fontSize={'xs'}>
|
||||
{t(item.desc, { title: feConfigs?.systemTitle })}
|
||||
</Box>
|
||||
{(() => {
|
||||
if (
|
||||
item.level === StandardSubLevelEnum.free &&
|
||||
selectSubMode === SubModeEnum.year
|
||||
) {
|
||||
return (
|
||||
<Button isDisabled mt={4} mb={6} w={'100%'} variant={'solid'}>
|
||||
{t('support.wallet.subscription.Nonsupport')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (
|
||||
item.level === standardPlan?.nextSubLevel &&
|
||||
selectSubMode === standardPlan?.nextMode
|
||||
) {
|
||||
return (
|
||||
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
|
||||
{t('support.wallet.subscription.Next plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (isCurrentPlan) {
|
||||
return (
|
||||
<Button
|
||||
mt={4}
|
||||
mb={6}
|
||||
w={'100%'}
|
||||
variant={'whiteBase'}
|
||||
isDisabled={
|
||||
item.level === standardPlan?.nextSubLevel &&
|
||||
selectSubMode === standardPlan?.nextMode
|
||||
}
|
||||
onClick={() =>
|
||||
onclickPreCheckStandPlan({
|
||||
level: item.level,
|
||||
mode: selectSubMode
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('support.wallet.subscription.Current plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
mt={4}
|
||||
mb={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={isUpdatingStandardPlan || isCheckingStandardPlan}
|
||||
onClick={() =>
|
||||
onclickPreCheckStandPlan({
|
||||
level: item.level,
|
||||
mode: selectSubMode
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('support.wallet.subscription.Buy now')}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* function list */}
|
||||
<StandardPlanContentList level={item.level} mode={selectSubMode} />
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
|
||||
<ConfirmModal />
|
||||
{!!confirmPayData && (
|
||||
<ConfirmPayModal
|
||||
{...confirmPayData}
|
||||
onClose={() => setConfirmPayData(undefined)}
|
||||
onConfirmPay={() => onclickUpdateStandardPlan(confirmPayData.planProps)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -338,3 +295,87 @@ const RowTabs = ({
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ConfirmPayModal = ({
|
||||
teamBalance,
|
||||
totalPrice,
|
||||
payPrice,
|
||||
onClose,
|
||||
onConfirmPay
|
||||
}: ConfirmPayModalProps & { onClose: () => void; onConfirmPay: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
|
||||
const formatPayPrice = Math.ceil(formatStorePrice2Read(payPrice));
|
||||
const formatTeamBalance = Math.floor(formatStorePrice2Read(teamBalance));
|
||||
|
||||
const { mutate: handleClickPay, isLoading } = useRequest({
|
||||
mutationFn: async (amount: number) => {
|
||||
// 获取支付二维码
|
||||
return getWxPayQRCode({
|
||||
type: BillTypeEnum.balance,
|
||||
balance: amount
|
||||
});
|
||||
},
|
||||
onSuccess(res) {
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="modal/confirmPay"
|
||||
title={t('support.wallet.Confirm pay')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody py={5} px={9}>
|
||||
<Flex>
|
||||
<Box flex={'0 0 100px'}>新套餐价格</Box>
|
||||
<Box>{formatStorePrice2Read(totalPrice)}元</Box>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Box flex={'0 0 100px'}>旧套餐余额</Box>
|
||||
<Box>{Math.floor(formatStorePrice2Read(totalPrice - payPrice))}元</Box>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Box flex={'0 0 100px'}>需支付</Box>
|
||||
<Box>{formatPayPrice}元</Box>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
borderTopWidth={'1px'}
|
||||
borderTopColor={'borderColor.base'}
|
||||
mx={9}
|
||||
justifyContent={'flex-start'}
|
||||
px={0}
|
||||
>
|
||||
<Box>账号余额: </Box>
|
||||
<Box ml={2} flex={1}>
|
||||
{formatTeamBalance}元
|
||||
</Box>
|
||||
{teamBalance >= payPrice ? (
|
||||
<Button size={'sm'} onClick={onConfirmPay}>
|
||||
确认支付
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size={'sm'}
|
||||
isLoading={isLoading}
|
||||
onClick={() => {
|
||||
handleClickPay(Math.ceil(formatStorePrice2Read(payPrice - teamBalance)));
|
||||
}}
|
||||
>
|
||||
余额不足,去充值
|
||||
</Button>
|
||||
)}
|
||||
</ModalFooter>
|
||||
|
||||
{!!qrPayData && <QRCodePayModal {...qrPayData} onSuccess={onConfirmPay} />}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user