From 662c790fe4f03d1d7bc30c764e73964b8e210a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=85=92=E5=B7=9D=E6=88=B7?= <76519998+chuanhu9@users.noreply.github.com> Date: Wed, 23 Jul 2025 18:22:28 +0800 Subject: [PATCH] feat: enhance wallet billing features (#5293) * feat: enhance wallet billing features with new dataset and points options * chore: removed local state for dataset month and replaced it with form state management * chore: remove redundant state --- packages/global/support/wallet/bill/api.d.ts | 1 + packages/global/support/wallet/bill/tools.ts | 37 ++ packages/global/support/wallet/bill/type.d.ts | 13 + .../web/components/common/MySelect/index.tsx | 14 +- packages/web/i18n/en/common.json | 21 +- packages/web/i18n/zh-CN/common.json | 15 + packages/web/i18n/zh-Hant/common.json | 15 + .../app/src/pageComponents/login/index.tsx | 10 +- .../src/pageComponents/price/ExtraPlan.tsx | 423 ++++++++++++------ projects/app/src/pages/price/index.tsx | 2 +- 10 files changed, 394 insertions(+), 157 deletions(-) create mode 100644 packages/global/support/wallet/bill/tools.ts diff --git a/packages/global/support/wallet/bill/api.d.ts b/packages/global/support/wallet/bill/api.d.ts index f1b79ae8a..d5ec2878e 100644 --- a/packages/global/support/wallet/bill/api.d.ts +++ b/packages/global/support/wallet/bill/api.d.ts @@ -16,6 +16,7 @@ export type CreateStandPlanBill = { type CreateExtractPointsBill = { type: BillTypeEnum.extraPoints; extraPoints: number; + month: number; }; type CreateExtractDatasetBill = { type: BillTypeEnum.extraDatasetSub; diff --git a/packages/global/support/wallet/bill/tools.ts b/packages/global/support/wallet/bill/tools.ts new file mode 100644 index 000000000..e4e09a734 --- /dev/null +++ b/packages/global/support/wallet/bill/tools.ts @@ -0,0 +1,37 @@ +import type { PriceOption } from './type'; + +// 根据积分获取月份 +export const getMonthByPoints = (points: number) => { + if (points >= 200) return 12; + if (points >= 100) return 6; + if (points >= 50) return 3; + return 1; +}; + +// 根据月份获取积分下限 +export const getMinPointsByMonth = (month: number): number => { + switch (month) { + case 12: + return 200; + case 6: + return 100; + case 3: + return 50; + case 1: + return 1; + default: + return 1; + } +}; + +// 计算额外资源包的所需付款价格 +export const calculatePrice = (unitPrice: number, option: PriceOption) => { + switch (option.type) { + case 'points': + return unitPrice * option.points * 1; + case 'dataset': + return unitPrice * option.size * option.month; + default: + throw new Error('Invalid price option type'); + } +}; diff --git a/packages/global/support/wallet/bill/type.d.ts b/packages/global/support/wallet/bill/type.d.ts index b9e981d37..2a73af374 100644 --- a/packages/global/support/wallet/bill/type.d.ts +++ b/packages/global/support/wallet/bill/type.d.ts @@ -50,3 +50,16 @@ export type InvoiceSchemaType = { finishTime?: Date; file?: Buffer; } & InvoiceType; + +export type AIPointsPriceOption = { + type: 'points'; + points: number; +}; + +export type DatasetPriceOption = { + type: 'dataset'; + size: number; + month: number; +}; + +export type PriceOption = AIPointsPriceOption | DatasetPriceOption; diff --git a/packages/web/components/common/MySelect/index.tsx b/packages/web/components/common/MySelect/index.tsx index a83010777..6e707b633 100644 --- a/packages/web/components/common/MySelect/index.tsx +++ b/packages/web/components/common/MySelect/index.tsx @@ -74,6 +74,7 @@ export const menuItemStyles: MenuItemProps = { const MySelect = ( { + bg = '#fff', placeholder, value, valueLabel, @@ -223,6 +224,7 @@ const MySelect = ( _active={{ transform: 'none' }} + bg={bg ? (isOpen ? '#fff' : bg) : '#fff'} color={isOpen ? 'primary.700' : 'myGray.700'} borderColor={isInvalid ? 'red.500' : isOpen ? 'primary.300' : 'myGray.200'} boxShadow={ @@ -232,17 +234,7 @@ const MySelect = ( : '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)' : 'none' } - _hover={ - isInvalid - ? { - borderColor: 'red.400', - boxShadow: '0px 0px 0px 2.4px rgba(255, 0, 0, 0.15)' - } - : { - borderColor: 'primary.300', - boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)' - } - } + _hover={isInvalid ? { borderColor: 'red.400' } : { borderColor: 'primary.300' }} {...props} > diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index ce21dbf56..0fb505d00 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -469,7 +469,7 @@ "core.dataset.data.Search data placeholder": "Search Related Data", "core.dataset.data.Too Long": "Total Length Exceeded", "core.dataset.data.Updated": "Updated", - "core.dataset.data.group": "Group", + "core.dataset.data.group": " Groups", "core.dataset.data.unit": "Items", "core.dataset.embedding model tip": "The index model can convert natural language into vectors for semantic search.\nNote that different index models cannot be used together. Once an index model is selected, it cannot be changed.", "core.dataset.error.Data not found": "Data Not Found or Deleted", @@ -1119,6 +1119,8 @@ "support.wallet.bill_tag.default_header": "Default Header", "support.wallet.bill_tag.invoice": "Invoice Records", "support.wallet.billable_invoice": "Billable Invoice", + "support.wallet.buy_ai_points": "Buy AI points", + "support.wallet.buy_dataset_capacity": "Buy Knowledge Base Index", "support.wallet.buy_resource": "Buy Resource Pack", "support.wallet.has_invoice": "Invoiced", "support.wallet.invoice_amount": "Invoice Amount", @@ -1142,20 +1144,33 @@ "support.wallet.subscription.AI points usage tip": "Each time the AI model is called, a certain amount of AI points will be consumed. For specific calculation standards, please refer to the 'Pricing' above.", "support.wallet.subscription.Ai points": "AI Points Calculation Standards", "support.wallet.subscription.Current plan": "Current Package", - "support.wallet.subscription.Extra ai points": "AI points", + "support.wallet.subscription.Dataset size": "Knowledge Base Index", + "support.wallet.subscription.Extra ai points": "AI Points", + "support.wallet.subscription.Extra ai points description": "The purchase amount of points is intelligently linked to the validity period. The more you buy, the longer you use it.", + "support.wallet.subscription.Extra dataset description": "Supports extending the validity period for the knowledge base index based on actual needs", "support.wallet.subscription.Extra dataset size": "Extra Dataset Capacity", + "support.wallet.subscription.Extra dataset unit": " Groups/1 Month", "support.wallet.subscription.Extra plan": "Extra Resource Pack", "support.wallet.subscription.Extra plan tip": "When the standard package is not enough, you can purchase extra resource packs to continue using", "support.wallet.subscription.FAQ": "FAQ", "support.wallet.subscription.Month amount": "Months", "support.wallet.subscription.Next plan": "Future Package", + "support.wallet.subscription.Points amount": "AI Points", "support.wallet.subscription.Stand plan level": "Subscription Package", "support.wallet.subscription.Sub plan": "Subscription Package", "support.wallet.subscription.Sub plan tip": "Free to use [{{title}}] or upgrade to a higher package", "support.wallet.subscription.Team plan and usage": "Package and Usage", "support.wallet.subscription.Training weight": "Training Priority: {{weight}}", "support.wallet.subscription.Update extra ai points": "Extra AI Points", + "support.wallet.subscription.Update extra ai points tips": "Purchase points will take effect immediately and will automatically expire after expiration.", "support.wallet.subscription.Update extra dataset size": "Storage", + "support.wallet.subscription.Update extra dataset tips": "After the knowledge base index expires, the existing data is still retained and the data cannot be added or modified.", + "support.wallet.subscription.Update extra expire": "Validity period", + "support.wallet.subscription.Update extra expire 1 month": "1 Month", + "support.wallet.subscription.Update extra expire 12 months": "12 Months", + "support.wallet.subscription.Update extra expire 3 months": "3 Months", + "support.wallet.subscription.Update extra expire 6 months": "6 Months", + "support.wallet.subscription.Update extra price": "Price", "support.wallet.subscription.Upgrade plan": "Upgrade Package", "support.wallet.subscription.ai_model": "AI Language Model", "support.wallet.subscription.function.History store": "{{amount}} Days of Chat History Retention", @@ -1168,7 +1183,7 @@ "support.wallet.subscription.mode.Period": "Subscription Period", "support.wallet.subscription.mode.Year": "Year", "support.wallet.subscription.mode.Year sale": "Two Months Free", - "support.wallet.subscription.point": "Points", + "support.wallet.subscription.point": " Points", "support.wallet.subscription.standardSubLevel.custom": "Custom", "support.wallet.subscription.standardSubLevel.enterprise": "Enterprise", "support.wallet.subscription.standardSubLevel.enterprise_desc": "Suitable for small and medium-sized enterprises to build Dataset applications in production environments", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index ec9ba9aae..0c1472ff7 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -1143,6 +1143,7 @@ "support.wallet.subscription.Ai points": "AI 积分计算标准", "support.wallet.subscription.Current plan": "当前套餐", "support.wallet.subscription.Extra ai points": "额外 AI 积分", + "support.wallet.subscription.Extra ai points description": "积分购买量与有效期智能联动, 买的越多用的越久", "support.wallet.subscription.Extra dataset size": "额外知识库容量", "support.wallet.subscription.Extra plan": "额外资源包", "support.wallet.subscription.Extra plan tip": "标准套餐不够时,您可以购买额外资源包继续使用", @@ -1156,6 +1157,20 @@ "support.wallet.subscription.Training weight": "训练优先级:{{weight}}", "support.wallet.subscription.Update extra ai points": "额外 AI 积分", "support.wallet.subscription.Update extra dataset size": "额外存储量", + "support.wallet.subscription.Update extra expire 1 month": "1个月", + "support.wallet.subscription.Update extra expire 12 months": "12个月", + "support.wallet.subscription.Update extra expire 3 months": "3个月", + "support.wallet.subscription.Update extra expire 6 months": "6个月", + "support.wallet.buy_ai_points": "购买 AI 积分", + "support.wallet.subscription.Update extra expire": "有效期", + "support.wallet.subscription.Update extra price": "价格", + "support.wallet.subscription.Update extra dataset tips": "知识库索引量到期后,仍保留已有数据,无法增改数据", + "support.wallet.subscription.Dataset size": "知识库索引量", + "support.wallet.buy_dataset_capacity": "购买知识库索引量", + "support.wallet.subscription.Extra dataset description": "支持根据实际需求为知识库索引量延长有效期", + "support.wallet.subscription.Extra dataset unit": "组/1个月", + "support.wallet.subscription.Update extra ai points tips": "购买积分即刻生效,过期后将自动失效", + "support.wallet.subscription.Points amount": "积分量", "support.wallet.subscription.Upgrade plan": "升级套餐", "support.wallet.subscription.ai_model": "AI语言模型", "support.wallet.subscription.eval_items_count": "单次评测数据条数: {{count}} 条", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index 8173dea40..5812409fa 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -1119,6 +1119,8 @@ "support.wallet.bill_tag.default_header": "預設抬頭", "support.wallet.bill_tag.invoice": "發票紀錄", "support.wallet.billable_invoice": "可開立發票的帳單", + "support.wallet.buy_ai_points": "購買 AI 積分", + "support.wallet.buy_dataset_capacity": "購買知識庫索引量", "support.wallet.buy_resource": "購買資源包", "support.wallet.has_invoice": "已開立發票", "support.wallet.invoice_amount": "發票金額", @@ -1142,20 +1144,33 @@ "support.wallet.subscription.AI points usage tip": "每次呼叫 AI 模型時,都會消耗一定的 AI 點數。具體的計算標準可參考上方的「計費標準」", "support.wallet.subscription.Ai points": "AI 點數計算標準", "support.wallet.subscription.Current plan": "目前方案", + "support.wallet.subscription.Dataset size": "知識庫索引量", "support.wallet.subscription.Extra ai points": "額外 AI 點數", + "support.wallet.subscription.Extra ai points description": "積分購買量與有效期智能聯動, 買的越多用的越久", + "support.wallet.subscription.Extra dataset description": "支持根據實際需求為知識庫索引量延長有效期", "support.wallet.subscription.Extra dataset size": "額外知識庫容量", + "support.wallet.subscription.Extra dataset unit": "組/1個月", "support.wallet.subscription.Extra plan": "額外資源包", "support.wallet.subscription.Extra plan tip": "當標準方案不足時,您可以購買額外資源包繼續使用", "support.wallet.subscription.FAQ": "常見問題", "support.wallet.subscription.Month amount": "月數", "support.wallet.subscription.Next plan": "未來方案", + "support.wallet.subscription.Points amount": "積分量", "support.wallet.subscription.Stand plan level": "訂閱方案", "support.wallet.subscription.Sub plan": "訂閱方案", "support.wallet.subscription.Sub plan tip": "免費使用【{{title}}】或升級更進階的方案", "support.wallet.subscription.Team plan and usage": "方案與使用量", "support.wallet.subscription.Training weight": "訓練優先權:{{weight}}", "support.wallet.subscription.Update extra ai points": "額外 AI 點數", + "support.wallet.subscription.Update extra ai points tips": "購買積分即刻生效,過期後將自動失效", "support.wallet.subscription.Update extra dataset size": "額外儲存空間", + "support.wallet.subscription.Update extra dataset tips": "知識庫索引量到期後,仍保留已有數據,無法增改數據", + "support.wallet.subscription.Update extra expire": "有效期", + "support.wallet.subscription.Update extra expire 1 month": "1個月", + "support.wallet.subscription.Update extra expire 12 months": "12個月", + "support.wallet.subscription.Update extra expire 3 months": "3個月", + "support.wallet.subscription.Update extra expire 6 months": "6個月", + "support.wallet.subscription.Update extra price": "價格", "support.wallet.subscription.Upgrade plan": "升級方案", "support.wallet.subscription.ai_model": "AI 語言模型", "support.wallet.subscription.function.History store": "{{amount}} 天對話紀錄保留", diff --git a/projects/app/src/pageComponents/login/index.tsx b/projects/app/src/pageComponents/login/index.tsx index 27277b868..67e4953e0 100644 --- a/projects/app/src/pageComponents/login/index.tsx +++ b/projects/app/src/pageComponents/login/index.tsx @@ -244,14 +244,8 @@ export const LoginContainer = ({ {/* main content area */} - - {pageType && DynamicComponent ? ( - DynamicComponent - ) : ( -
- -
- )} + + {pageType && DynamicComponent ? DynamicComponent : } {/* custom content */} diff --git a/projects/app/src/pageComponents/price/ExtraPlan.tsx b/projects/app/src/pageComponents/price/ExtraPlan.tsx index 7a869ffb8..9cd9ea971 100644 --- a/projects/app/src/pageComponents/price/ExtraPlan.tsx +++ b/projects/app/src/pageComponents/price/ExtraPlan.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Grid, Button, VStack } from '@chakra-ui/react'; +import { Box, Flex, Grid, Button, VStack, HStack } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import React, { useState } from 'react'; import { useSystemStore } from '@/web/common/system/useSystemStore'; @@ -10,6 +10,12 @@ import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants'; import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal'; import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import MySelect from '@fastgpt/web/components/common/MySelect'; +import { + getMonthByPoints, + getMinPointsByMonth, + calculatePrice +} from '@fastgpt/global/support/wallet/bill/tools'; const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => { const { t } = useTranslation(); @@ -17,14 +23,30 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => { const { subPlans } = useSystemStore(); const [qrPayData, setQRPayData] = useState(); - // extra dataset + const expireSelectorOptions: { label: string; value: number }[] = [ + { label: t('common:support.wallet.subscription.Update extra expire 1 month'), value: 1 }, + { label: t('common:support.wallet.subscription.Update extra expire 3 months'), value: 3 }, + { label: t('common:support.wallet.subscription.Update extra expire 6 months'), value: 6 }, + { label: t('common:support.wallet.subscription.Update extra expire 12 months'), value: 12 } + ]; + + // 额外的知识库索引量 const extraDatasetPrice = subPlans?.extraDatasetSize?.price || 0; - const { register: registerDatasetSize, handleSubmit: handleSubmitDatasetSize } = useForm({ + const { + watch: watchDatasetSize, + register: registerDatasetSize, + handleSubmit: handleSubmitDatasetSize, + setValue: setValueDatasetSize + } = useForm({ defaultValues: { - datasetSize: 0, + datasetSize: 1, month: 1 } }); + + const watchedDatasetSize = watchDatasetSize('datasetSize'); + const watchedDatasetMonth = watchDatasetSize('month'); + const { runAsync: onclickBuyDatasetSize, loading: isLoadingBuyDatasetSize } = useRequest2( async ({ datasetSize, month }: { datasetSize: number; month: number }) => { datasetSize = Math.ceil(datasetSize); @@ -56,18 +78,27 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => { // extra ai points const extraPointsPrice = subPlans?.extraPoints?.price || 0; - const { register: registerExtraPoints, handleSubmit: handleSubmitExtraPoints } = useForm({ + const { + watch: watchExtraPoints, + setValue: setValueExtraPoints, + getValues: getValuesExtraPoints + } = useForm({ defaultValues: { - points: 0, + points: 1, month: 1 } }); - const { runAsync: onclickBuyExtraPoints, loading: isLoadingBuyExtraPoints } = useRequest2( - async ({ points }: { points: number }) => { - points = Math.ceil(points); - const month = 1; - const payAmount = points * month * extraPointsPrice; + // 监听积分和月份变化 + const watchedPoints = watchExtraPoints('points'); + const watchedMonth = watchExtraPoints('month'); + + const { runAsync: onclickBuyExtraPoints, loading: isLoadingBuyExtraPoints } = useRequest2( + async ({ points, month }: { points: number; month: number }) => { + points = Math.ceil(points); + month = Math.ceil(month); + + const payAmount = points * 1 * extraPointsPrice; if (payAmount === 0) { return toast({ @@ -78,6 +109,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => { const res = await postCreatePayBill({ type: BillTypeEnum.extraPoints, + month, extraPoints: points }); @@ -95,89 +127,9 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => { return ( - - - - - {t('common:support.wallet.subscription.Extra dataset size')} - - - {`¥${extraDatasetPrice}/1000` + t('common:core.dataset.data.group')} - - /{t('common:month')} - - - - - - - - - {t('common:support.wallet.buy_resource')} - - - - {t('common:support.wallet.subscription.Month amount')} - - - - - {t('common:month')} - - - - - - {t('common:support.wallet.subscription.Update extra dataset size')} - - - - - 000{t('common:core.dataset.data.unit')} - - - - - - {/* points */} - void }) => { borderWidth={'1px'} borderColor={'myGray.150'} boxShadow={'1.5'} + gap={4} > - - - + + + {t('common:support.wallet.subscription.Extra ai points')} - + {`¥${extraPointsPrice}/1000` + t('common:support.wallet.subscription.point')} - - /{t('common:month')} - - + + {t('common:support.wallet.subscription.Extra ai points description')} + + void }) => { fill={'none'} /> - - - - {t('common:support.wallet.buy_resource')} + + + + {t('common:support.wallet.buy_ai_points')} - - - {t('common:support.wallet.subscription.Month amount')} - - - 1 - {t('common:month')} - - - - - {t('common:support.wallet.subscription.Update extra ai points')} + + + {t('common:support.wallet.subscription.Points amount')} { + // 过滤掉NaN和无效输入 + if (val === ('' as unknown as number) || val === null || val === undefined) { + setValueExtraPoints('points', undefined as unknown as number); + } else if (!isNaN(val) && val >= 0) { + setValueExtraPoints('points', val as unknown as number); + // 当用户输入积分时,自动更新月份 + const expectedMonth = getMonthByPoints(val); + if (expectedMonth !== watchedMonth) { + setValueExtraPoints('month', expectedMonth); + } + } + }} /> - - {'000' + t('common:support.wallet.subscription.point')} + +  {`X 1000${t('common:support.wallet.subscription.point')}`} + + + {t('common:support.wallet.subscription.Update extra expire')} + + + { + setValueExtraPoints('month', val); + // 当用户选择月份时,设置积分为该月份的最小值 + const minPoints = getMinPointsByMonth(val); + setValueExtraPoints('points', minPoints); + }} + /> + + + + + {t('common:support.wallet.subscription.Update extra price')} + + + {`¥${(() => { + const price = calculatePrice(extraPointsPrice, { + type: 'points', + points: watchedPoints + }); + return Number.isNaN(price) ? 0 : price; + })()}`} + + + + + + + + + {t('common:support.wallet.subscription.Update extra ai points tips')} + + - - + + + {/* dataset */} + + + + + {t('common:support.wallet.subscription.Extra dataset size')} + + + {`¥${extraDatasetPrice}/1000${t('common:support.wallet.subscription.Extra dataset unit')}`} + + + {t('common:support.wallet.subscription.Extra dataset description')} + + + + + + + + + {t('common:support.wallet.buy_dataset_capacity')} + + + + {t('common:support.wallet.subscription.Dataset size')} + + + + +  {`X 1000${t('common:core.dataset.data.group')}`} + + + + + + {t('common:support.wallet.subscription.Update extra expire')} + + + setValueDatasetSize('month', val)} + /> + + + + + {t('common:support.wallet.subscription.Update extra price')} + + + {`¥${(() => { + const price = calculatePrice(extraDatasetPrice, { + type: 'dataset', + size: watchedDatasetSize, + month: watchedDatasetMonth + }); + return Number.isNaN(price) ? 0 : price; + })()}`} + + + + + + + + + {t('common:support.wallet.subscription.Update extra dataset tips')} + + + + {!!qrPayData && } diff --git a/projects/app/src/pages/price/index.tsx b/projects/app/src/pages/price/index.tsx index e58cd6ee0..8783e7e4d 100644 --- a/projects/app/src/pages/price/index.tsx +++ b/projects/app/src/pages/price/index.tsx @@ -48,7 +48,7 @@ const PriceBox = () => { {t('common:support.wallet.subscription.Sub plan')} - + {t('common:support.wallet.subscription.Sub plan tip', { title: feConfigs?.systemTitle })}