登录用户查看渠道定价时,按用户分组倍率和渠道个人倍率计算实际折扣价 并在价格列展示划线原价与折扣价,新增 /pricing/user/*model 接口。 Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>master
| @@ -301,7 +301,23 @@ func GetChannelPricingByModelWithChannelInfo(c *gin.Context) { | |||
| return | |||
| } | |||
| common.ApiSuccess(c, list) | |||
| loggedIn, userId, userGroup, groupRatio := resolveCurrentUser(c) | |||
| if loggedIn { | |||
| for _, item := range list { | |||
| userChannelRatio := model.GetUserChannelRatio(userId, modelName, item.ChannelId) | |||
| item.UserRatio = groupRatio * userChannelRatio | |||
| } | |||
| } | |||
| c.JSON(200, gin.H{ | |||
| "success": true, | |||
| "message": "", | |||
| "data": list, | |||
| "logged_in": loggedIn, | |||
| "group": userGroup, | |||
| "group_ratio": groupRatio, | |||
| }) | |||
| } | |||
| // GetChannelPricingWithTags 获取渠道定价(带标签详情) | |||
| @@ -1,6 +1,10 @@ | |||
| package controller | |||
| import ( | |||
| "fmt" | |||
| "math" | |||
| "strings" | |||
| "github.com/QuantumNous/new-api/common" | |||
| "github.com/QuantumNous/new-api/model" | |||
| "github.com/QuantumNous/new-api/service" | |||
| @@ -9,6 +13,25 @@ import ( | |||
| "github.com/gin-gonic/gin" | |||
| ) | |||
| // resolveCurrentUser 从 gin.Context 解析当前登录用户信息。 | |||
| // 未登录或查询失败时 loggedIn=false,groupRatio=1.0。 | |||
| func resolveCurrentUser(c *gin.Context) (loggedIn bool, userId int, userGroup string, groupRatio float64) { | |||
| groupRatio = 1.0 | |||
| raw, exists := c.Get("id") | |||
| if !exists { | |||
| return | |||
| } | |||
| userId = raw.(int) | |||
| user, err := model.GetUserCache(userId) | |||
| if err != nil { | |||
| return | |||
| } | |||
| loggedIn = true | |||
| userGroup = user.Group | |||
| groupRatio = service.GetUserGroupRatio(userGroup, userGroup) | |||
| return | |||
| } | |||
| func filterPricingByUsableGroups(pricing []model.Pricing, usableGroup map[string]string) []model.Pricing { | |||
| if len(pricing) == 0 { | |||
| return pricing | |||
| @@ -76,6 +99,95 @@ func GetPricing(c *gin.Context) { | |||
| }) | |||
| } | |||
| // GetUserPricing 获取用户对指定模型的价格信息(原价 vs 用户价) | |||
| func GetUserPricing(c *gin.Context) { | |||
| modelName := c.Param("model") | |||
| // *model 通配符返回值带前导 /,需要去掉 | |||
| modelName = strings.TrimPrefix(modelName, "/") | |||
| if modelName == "" { | |||
| common.ApiErrorMsg(c, "模型名不能为空") | |||
| return | |||
| } | |||
| pricingData := model.GetPricingByModel(modelName) | |||
| if pricingData == nil { | |||
| common.ApiErrorMsg(c, "未找到该模型的定价信息") | |||
| return | |||
| } | |||
| loggedIn, userId, userGroup, groupRatio := resolveCurrentUser(c) | |||
| if !loggedIn { | |||
| respondOriginalPrice(c, pricingData) | |||
| return | |||
| } | |||
| bestUserChannelRatio := model.GetBestUserChannelRatio(userId, modelName) | |||
| totalRatio := groupRatio * bestUserChannelRatio | |||
| savingsPercent := int(math.Round((1 - totalRatio) * 100)) | |||
| result := gin.H{ | |||
| "success": true, | |||
| "model_name": pricingData.ModelName, | |||
| "quota_type": pricingData.QuotaType, | |||
| "group": userGroup, | |||
| "group_ratio": groupRatio, | |||
| "user_channel_ratio": bestUserChannelRatio, | |||
| "savings_percent": savingsPercent, | |||
| "logged_in": true, | |||
| } | |||
| if pricingData.QuotaType == model.QuotaTypeByTokens { | |||
| originalInput := pricingData.ModelRatio * 2 | |||
| originalOutput := pricingData.ModelRatio * pricingData.CompletionRatio * 2 | |||
| result["original_input"] = originalInput | |||
| result["original_output"] = originalOutput | |||
| result["user_input"] = originalInput * totalRatio | |||
| result["user_output"] = originalOutput * totalRatio | |||
| } else { | |||
| result["original_price"] = pricingData.ModelPrice | |||
| result["user_price"] = pricingData.ModelPrice * totalRatio | |||
| } | |||
| if savingsPercent > 0 { | |||
| result["discount"] = formatDiscount(totalRatio) | |||
| } | |||
| c.JSON(200, result) | |||
| } | |||
| // respondOriginalPrice 未登录或用户查询失败时,只返回原价 | |||
| func respondOriginalPrice(c *gin.Context, pricingData *model.Pricing) { | |||
| result := gin.H{ | |||
| "success": true, | |||
| "model_name": pricingData.ModelName, | |||
| "quota_type": pricingData.QuotaType, | |||
| "logged_in": false, | |||
| } | |||
| if pricingData.QuotaType == model.QuotaTypeByTokens { | |||
| result["original_input"] = pricingData.ModelRatio * 2 | |||
| result["original_output"] = pricingData.ModelRatio * pricingData.CompletionRatio * 2 | |||
| } else { | |||
| result["original_price"] = pricingData.ModelPrice | |||
| } | |||
| c.JSON(200, result) | |||
| } | |||
| // formatDiscount 将倍率转换为中文折扣格式 | |||
| func formatDiscount(ratio float64) string { | |||
| if ratio <= 0 { | |||
| return "免费" | |||
| } | |||
| discount := int(math.Round(ratio * 10)) | |||
| if discount >= 10 { | |||
| return "" | |||
| } | |||
| remainder := int(math.Round(ratio*100)) % 10 | |||
| if remainder == 0 { | |||
| return fmt.Sprintf("%d折", discount) | |||
| } | |||
| return fmt.Sprintf("%.1f折", ratio*10) | |||
| } | |||
| func ResetModelRatio(c *gin.Context) { | |||
| defaultStr := ratio_setting.DefaultModelRatio2JSONString() | |||
| err := model.UpdateOption("ModelRatio", defaultStr) | |||
| @@ -267,6 +267,7 @@ type ChannelPricingWithChannel struct { | |||
| AudioRatio float64 `json:"audio_ratio"` | |||
| AudioCompletionRatio float64 `json:"audio_completion_ratio"` | |||
| IsDefault bool `json:"is_default"` | |||
| UserRatio float64 `json:"user_ratio"` | |||
| } | |||
| // GetChannelPricingByModelWithChannelInfo 获取指定模型的渠道定价(带渠道信息) | |||
| @@ -44,6 +44,7 @@ type PricingVendor struct { | |||
| var ( | |||
| pricingMap []Pricing | |||
| pricingByModel map[string]*Pricing | |||
| vendorsList []PricingVendor | |||
| supportedEndpointMap map[string]common.EndpointInfo | |||
| lastGetPricingTime time.Time | |||
| @@ -60,6 +61,19 @@ var ( | |||
| modelSupportEndpointsLock = sync.RWMutex{} | |||
| ) | |||
| // SetTestPricing 设置测试用的定价缓存(仅用于测试) | |||
| func SetTestPricing(pricing []Pricing) { | |||
| updatePricingLock.Lock() | |||
| defer updatePricingLock.Unlock() | |||
| pricingMap = pricing | |||
| idx := make(map[string]*Pricing, len(pricing)) | |||
| for i := range pricing { | |||
| idx[pricing[i].ModelName] = &pricingMap[i] | |||
| } | |||
| pricingByModel = idx | |||
| lastGetPricingTime = time.Now() | |||
| } | |||
| func GetPricing() []Pricing { | |||
| if time.Since(lastGetPricingTime) > time.Minute*1 || len(pricingMap) == 0 { | |||
| updatePricingLock.Lock() | |||
| @@ -74,6 +88,15 @@ func GetPricing() []Pricing { | |||
| return pricingMap | |||
| } | |||
| // GetPricingByModel 从缓存中查找指定模型的定价信息 | |||
| func GetPricingByModel(modelName string) *Pricing { | |||
| GetPricing() // 确保缓存已刷新 | |||
| modelEnableGroupsLock.RLock() | |||
| p := pricingByModel[modelName] | |||
| modelEnableGroupsLock.RUnlock() | |||
| return p | |||
| } | |||
| // GetVendors 返回当前定价接口使用到的供应商信息 | |||
| func GetVendors() []PricingVendor { | |||
| if time.Since(lastGetPricingTime) > time.Minute*1 || len(pricingMap) == 0 { | |||
| @@ -364,10 +387,14 @@ func updatePricing() { | |||
| modelEnableGroupsLock.Lock() | |||
| modelEnableGroups = make(map[string][]string) | |||
| modelQuotaTypeMap = make(map[string]int) | |||
| for _, p := range pricingMap { | |||
| byModel := make(map[string]*Pricing, len(pricingMap)) | |||
| for i := range pricingMap { | |||
| p := &pricingMap[i] | |||
| modelEnableGroups[p.ModelName] = p.EnableGroup | |||
| modelQuotaTypeMap[p.ModelName] = p.QuotaType | |||
| byModel[p.ModelName] = p | |||
| } | |||
| pricingByModel = byModel | |||
| modelEnableGroupsLock.Unlock() | |||
| lastGetPricingTime = time.Now() | |||
| @@ -2,6 +2,7 @@ package model | |||
| import ( | |||
| "strconv" | |||
| "strings" | |||
| "sync" | |||
| "github.com/QuantumNous/new-api/common" | |||
| @@ -112,3 +113,17 @@ func GetUserChannelRatiosByUserId(userId int) ([]*UserChannelRatio, error) { | |||
| err := DB.Where("user_id = ?", userId).Find(&list).Error | |||
| return list, err | |||
| } | |||
| // GetBestUserChannelRatio 从内存缓存中查找指定用户+模型的最低渠道倍率(未命中返回 1.0) | |||
| func GetBestUserChannelRatio(userId int, modelName string) float64 { | |||
| best := 1.0 | |||
| prefix := strconv.Itoa(userId) + ":" + modelName + ":" | |||
| userChannelRatioCacheLock.RLock() | |||
| for key, ratio := range userChannelRatioCache { | |||
| if len(key) > len(prefix) && strings.HasPrefix(key, prefix) && ratio < best { | |||
| best = ratio | |||
| } | |||
| } | |||
| userChannelRatioCacheLock.RUnlock() | |||
| return best | |||
| } | |||
| @@ -32,6 +32,7 @@ func SetApiRouter(router *gin.Engine) { | |||
| //apiRouter.GET("/midjourney", controller.GetMidjourney) | |||
| apiRouter.GET("/home_page_content", controller.GetHomePageContent) | |||
| apiRouter.GET("/pricing", middleware.TryUserAuth(), controller.GetPricing) | |||
| apiRouter.GET("/pricing/user/*model", middleware.TryUserAuth(), controller.GetUserPricing) | |||
| apiRouter.GET("/channel-pricing/model/*name", middleware.TryUserAuth(), controller.GetChannelPricingByModelWithChannelInfo) | |||
| apiRouter.GET("/captcha", controller.GetCaptcha) | |||
| apiRouter.GET("/verification", middleware.EmailVerificationRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) | |||
| @@ -37,6 +37,7 @@ const PricingPage = () => { | |||
| setShowRatio, | |||
| viewMode, | |||
| setViewMode, | |||
| showTryButton: true, | |||
| }; | |||
| return ( | |||
| @@ -122,7 +122,7 @@ const PricingSidebar = ({ | |||
| t={t} | |||
| /> | |||
| <PricingGroups | |||
| {/* <PricingGroups | |||
| filterGroup={filterGroup} | |||
| setFilterGroup={handleGroupClick} | |||
| usableGroup={categoryProps.usableGroup} | |||
| @@ -130,7 +130,7 @@ const PricingSidebar = ({ | |||
| models={groupCountModels} | |||
| loading={loading} | |||
| t={t} | |||
| /> | |||
| /> */} | |||
| <PricingQuotaTypes | |||
| filterQuotaType={filterQuotaType} | |||
| @@ -61,7 +61,7 @@ const ModelDetailSideSheet = ({ | |||
| borderBottom: '1px solid var(--semi-color-border)', | |||
| }} | |||
| visible={visible} | |||
| width={isMobile ? '100%' : 600} | |||
| width={isMobile ? '100%' : 750} | |||
| closeIcon={ | |||
| <Button | |||
| className='semi-button-tertiary semi-button-size-small semi-button-borderless' | |||
| @@ -17,10 +17,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. | |||
| For commercial licensing, please contact support@quantumnous.com | |||
| */ | |||
| import React, { useState, useEffect, useMemo } from 'react'; | |||
| import React, { useState, useEffect } from 'react'; | |||
| import { Card, Avatar, Typography, Table, Tag, Spin, Banner, Tooltip } from '@douyinfe/semi-ui'; | |||
| import { IconServer, IconCopy } from '@douyinfe/semi-icons'; | |||
| import { API, copy, showSuccess } from '../../../../../helpers'; | |||
| import { IconServer, IconCopy, IconArrowDown } from '@douyinfe/semi-icons'; | |||
| import { API, copy, showSuccess, getCurrencyConfig } from '../../../../../helpers'; | |||
| const { Text } = Typography; | |||
| @@ -33,6 +33,7 @@ const ChannelPricingCard = ({ | |||
| }) => { | |||
| const [loading, setLoading] = useState(false); | |||
| const [channelPricingData, setChannelPricingData] = useState([]); | |||
| const [priceMeta, setPriceMeta] = useState({ loggedIn: false, group: '', groupRatio: 1.0 }); | |||
| useEffect(() => { | |||
| const fetchChannelPricing = async () => { | |||
| @@ -41,14 +42,18 @@ const ChannelPricingCard = ({ | |||
| setLoading(true); | |||
| try { | |||
| const res = await API.get(`/api/channel-pricing/model/${encodeURIComponent(modelName)}`); | |||
| const { success, data } = res.data; | |||
| if (success && Array.isArray(data)) { | |||
| setChannelPricingData(data); | |||
| const result = res.data; | |||
| if (result.success && Array.isArray(result.data)) { | |||
| setChannelPricingData(result.data); | |||
| setPriceMeta({ | |||
| loggedIn: result.logged_in || false, | |||
| group: result.group || '', | |||
| groupRatio: result.group_ratio || 1.0, | |||
| }); | |||
| } else { | |||
| setChannelPricingData([]); | |||
| } | |||
| } catch (error) { | |||
| // 静默处理错误,不显示给用户 | |||
| setChannelPricingData([]); | |||
| } finally { | |||
| setLoading(false); | |||
| @@ -58,23 +63,7 @@ const ChannelPricingCard = ({ | |||
| fetchChannelPricing(); | |||
| }, [modelName]); | |||
| // 获取货币符号 | |||
| const currencySymbol = useMemo(() => { | |||
| if (currency === 'CNY') return '¥'; | |||
| if (currency === 'CUSTOM') { | |||
| try { | |||
| const statusStr = localStorage.getItem('status'); | |||
| if (statusStr) { | |||
| const s = JSON.parse(statusStr); | |||
| return s?.custom_currency_symbol || '¤'; | |||
| } | |||
| } catch (e) { | |||
| // ignore | |||
| } | |||
| return '¤'; | |||
| } | |||
| return '$'; | |||
| }, [currency]); | |||
| const currencySymbol = getCurrencyConfig().symbol; | |||
| // 格式化价格显示 | |||
| const formatPrice = (price) => { | |||
| @@ -105,7 +94,7 @@ const ChannelPricingCard = ({ | |||
| key: item.channel_id || index, | |||
| channelId: item.channel_id, | |||
| channelName: item.channel_public_name || ('通道' + (index + 1)), | |||
| channelTags: item.tags || [], // 渠道定价的标签列表 | |||
| channelTags: item.tags || [], | |||
| channelType: item.channel_type, | |||
| quotaType: item.quota_type, | |||
| modelRatio: item.model_ratio, | |||
| @@ -115,6 +104,7 @@ const ChannelPricingCard = ({ | |||
| cacheRatio: item.cache_ratio, | |||
| cacheCreationRatio: item.cache_creation_ratio, | |||
| isDefault: item.is_default || false, | |||
| userRatio: item.user_ratio ?? 1.0, | |||
| })); | |||
| // 判断是否存在按次计费的渠道 | |||
| @@ -180,7 +170,55 @@ const ChannelPricingCard = ({ | |||
| }, | |||
| ]; | |||
| // 渲染带折扣的价格 | |||
| const renderDiscountedPrice = (originalPrice, record, unit) => { | |||
| const { userRatio } = record; | |||
| const hasDiscount = priceMeta.loggedIn && userRatio < 1; | |||
| if (hasDiscount) { | |||
| const discountedPrice = originalPrice * userRatio; | |||
| return ( | |||
| <div style={{ whiteSpace: 'nowrap' }}> | |||
| <div style={{ textDecoration: 'line-through', color: '#999', fontSize: '13px' }}> | |||
| {formatPrice(originalPrice)} / {unit} | |||
| </div> | |||
| <div style={{ fontSize: '15px', fontWeight: 700 }}> | |||
| {formatPrice(discountedPrice)} / {unit} | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| return ( | |||
| <div style={{ whiteSpace: 'nowrap' }}> | |||
| <span className='font-semibold'> | |||
| {formatPrice(originalPrice)} | |||
| </span> | |||
| <span className='text-xs text-gray-500'> / {unit}</span> | |||
| </div> | |||
| ); | |||
| }; | |||
| // 折扣列(仅登录且有折扣时显示) | |||
| const discountColumn = priceMeta.loggedIn | |||
| ? [ | |||
| { | |||
| title: t('折扣'), | |||
| dataIndex: 'userRatio', | |||
| render: (userRatio) => { | |||
| if (userRatio >= 1) return '-'; | |||
| return ( | |||
| <Tag color='red' size='small' shape='circle' style={{ fontSize: '12px', lineHeight: '18px' }}> | |||
| <IconArrowDown size='tiny' style={{ marginRight: 2, verticalAlign: 'middle' }} />{Math.round((1 - userRatio) * 100)}% | |||
| </Tag> | |||
| ); | |||
| }, | |||
| }, | |||
| ] | |||
| : []; | |||
| // 只有存在按量计费的渠道时才显示输入/输出价格列 | |||
| const tokenUnitLabel = tokenUnit === 'K' ? '1K' : '1M'; | |||
| const tokenBasedColumns = hasTokenBased | |||
| ? [ | |||
| { | |||
| @@ -188,18 +226,8 @@ const ChannelPricingCard = ({ | |||
| dataIndex: 'modelRatio', | |||
| render: (ratio, record) => { | |||
| if (record.quotaType !== 0) return '-'; | |||
| // 输入价格 = model_ratio * 2 (渠道定价不乘分组倍率) | |||
| const inputPrice = ratio * 2; | |||
| return ( | |||
| <> | |||
| <div className='font-semibold text-orange-600'> | |||
| {formatPrice(inputPrice)} | |||
| </div> | |||
| <div className='text-xs text-gray-500'> | |||
| / {tokenUnit === 'K' ? '1K' : '1M'} tokens | |||
| </div> | |||
| </> | |||
| ); | |||
| return renderDiscountedPrice(inputPrice, record, `${tokenUnitLabel} tokens`); | |||
| }, | |||
| }, | |||
| { | |||
| @@ -207,18 +235,8 @@ const ChannelPricingCard = ({ | |||
| dataIndex: 'completionRatio', | |||
| render: (completionRatio, record) => { | |||
| if (record.quotaType !== 0) return '-'; | |||
| // 输出价格 = model_ratio * completion_ratio * 2 | |||
| const outputPrice = record.modelRatio * completionRatio * 2; | |||
| return ( | |||
| <> | |||
| <div className='font-semibold text-orange-600'> | |||
| {formatPrice(outputPrice)} | |||
| </div> | |||
| <div className='text-xs text-gray-500'> | |||
| / {tokenUnit === 'K' ? '1K' : '1M'} tokens | |||
| </div> | |||
| </> | |||
| ); | |||
| return renderDiscountedPrice(outputPrice, record, `${tokenUnitLabel} tokens`); | |||
| }, | |||
| }, | |||
| ] | |||
| @@ -231,16 +249,8 @@ const ChannelPricingCard = ({ | |||
| const renderCachePrice = (v, record) => { | |||
| if (record.quotaType !== 0) return '-'; | |||
| return ( | |||
| <> | |||
| <div className='font-semibold text-orange-600'> | |||
| {formatPrice(record.modelRatio * v * 2)} | |||
| </div> | |||
| <div className='text-xs text-gray-500'> | |||
| / {tokenUnit === 'K' ? '1K' : '1M'} tokens | |||
| </div> | |||
| </> | |||
| ); | |||
| const cachePrice = record.modelRatio * v * 2; | |||
| return renderDiscountedPrice(cachePrice, record, `${tokenUnitLabel} tokens`); | |||
| }; | |||
| const advancedColumns = hasAdvancedPricing | |||
| @@ -266,20 +276,13 @@ const ChannelPricingCard = ({ | |||
| dataIndex: 'modelPrice', | |||
| render: (price, record) => { | |||
| if (record.quotaType !== 1) return '-'; | |||
| return ( | |||
| <> | |||
| <div className='font-semibold text-orange-600'> | |||
| {displayPrice(price)} | |||
| </div> | |||
| <div className='text-xs text-gray-500'>/ {t('次')}</div> | |||
| </> | |||
| ); | |||
| return renderDiscountedPrice(price, record, t('次')); | |||
| }, | |||
| }, | |||
| ] | |||
| : []; | |||
| const columns = [...baseColumns, ...tokenBasedColumns, ...advancedColumns, ...callBasedColumn]; | |||
| const columns = [...baseColumns, ...discountColumn, ...tokenBasedColumns, ...advancedColumns, ...callBasedColumn]; | |||
| return ( | |||
| <Card className='!rounded-2xl shadow-sm border-0 mb-6'> | |||
| @@ -105,7 +105,7 @@ const FilterModalContent = ({ sidebarProps, t }) => { | |||
| t={t} | |||
| /> | |||
| <PricingGroups | |||
| {/* <PricingGroups | |||
| filterGroup={filterGroup} | |||
| setFilterGroup={setFilterGroup} | |||
| usableGroup={categoryProps.usableGroup} | |||
| @@ -113,7 +113,7 @@ const FilterModalContent = ({ sidebarProps, t }) => { | |||
| models={groupCountModels} | |||
| loading={loading} | |||
| t={t} | |||
| /> | |||
| /> */} | |||
| <PricingQuotaTypes | |||
| filterQuotaType={filterQuotaType} | |||
| @@ -184,7 +184,7 @@ export const getPricingTableColumns = ({ | |||
| modelNameColumn, | |||
| vendorColumn, | |||
| descriptionColumn, | |||
| tagsColumn, | |||
| // tagsColumn, | |||
| quotaColumn, | |||
| ]; | |||
| @@ -255,7 +255,7 @@ export const getPricingTableColumns = ({ | |||
| }; | |||
| const columns = [...baseColumns]; | |||
| columns.push(endpointColumn); | |||
| // columns.push(endpointColumn); | |||
| if (showRatio) { | |||
| columns.push(ratioColumn); | |||
| } | |||
| @@ -451,12 +451,12 @@ export const getTokensColumns = ({ | |||
| key: 'quota_usage', | |||
| render: (text, record) => renderQuotaUsage(text, record, t), | |||
| }, | |||
| { | |||
| title: t('分组'), | |||
| dataIndex: 'group', | |||
| key: 'group', | |||
| render: (text, record) => renderGroupColumn(text, record, t), | |||
| }, | |||
| // { | |||
| // title: t('分组'), | |||
| // dataIndex: 'group', | |||
| // key: 'group', | |||
| // render: (text, record) => renderGroupColumn(text, record, t), | |||
| // }, | |||
| { | |||
| title: t('密钥'), | |||
| key: 'token_key', | |||
| @@ -378,7 +378,7 @@ const EditTokenModal = (props) => { | |||
| showClear | |||
| /> | |||
| </Col> | |||
| <Col span={24}> | |||
| {/* <Col span={24}> | |||
| {groups.length > 0 ? ( | |||
| <Form.Select | |||
| field='group' | |||
| @@ -412,7 +412,7 @@ const EditTokenModal = (props) => { | |||
| '开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道', | |||
| )} | |||
| /> | |||
| </Col> | |||
| </Col> */} | |||
| <Col xs={24} sm={24} md={24} lg={10} xl={10}> | |||
| <Form.DatePicker | |||
| field='expired_time' | |||
| @@ -890,14 +890,14 @@ const TopUp = () => { | |||
| allSubscriptions={allSubscriptions} | |||
| reloadSubscriptionSelf={getSubscriptionSelf} | |||
| /> | |||
| <InvitationCard | |||
| {/* <InvitationCard | |||
| t={t} | |||
| userState={userState} | |||
| renderQuota={renderQuota} | |||
| setOpenTransfer={setOpenTransfer} | |||
| affLink={affLink} | |||
| handleAffLinkClick={handleAffLinkClick} | |||
| /> | |||
| /> */} | |||
| </div> | |||
| </div> | |||
| ); | |||