Просмотр исходного кода

Merge pull request #2878 from QuantumNous/feat/hide-subscription-card-when-no-plans

✨ refactor(wallet): Top-up layout to embed subscription plans into the recharge card tabs
Calcium-Ion 1 неделя назад
Родитель
Сommit
e57bac7c91

+ 521 - 458
web/src/components/topup/RechargeCard.jsx

@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact [email protected]
 */
 
-import React, { useRef } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
 import {
   Avatar,
   Typography,
@@ -32,6 +32,8 @@ import {
   Col,
   Spin,
   Tooltip,
+  Tabs,
+  TabPane,
 } from '@douyinfe/semi-ui';
 import { SiAlipay, SiWechat, SiStripe } from 'react-icons/si';
 import {
@@ -41,10 +43,12 @@ import {
   BarChart2,
   TrendingUp,
   Receipt,
+  Sparkles,
 } from 'lucide-react';
 import { IconGift } from '@douyinfe/semi-icons';
 import { useMinimumLoadingTime } from '../../hooks/common/useMinimumLoadingTime';
 import { getCurrencyConfig } from '../../helpers/render';
+import SubscriptionPlansCard from './SubscriptionPlansCard';
 
 const { Text } = Typography;
 
@@ -83,506 +87,565 @@ const RechargeCard = ({
   statusLoading,
   topupInfo,
   onOpenHistory,
+  subscriptionLoading = false,
+  subscriptionPlans = [],
+  billingPreference,
+  onChangeBillingPreference,
+  activeSubscriptions = [],
+  allSubscriptions = [],
+  reloadSubscriptionSelf,
 }) => {
   const onlineFormApiRef = useRef(null);
   const redeemFormApiRef = useRef(null);
+  const initialTabSetRef = useRef(false);
   const showAmountSkeleton = useMinimumLoadingTime(amountLoading);
-  console.log(
-    ' enabled screem ?',
-    enableCreemTopUp,
-    ' products ?',
-    creemProducts,
-  );
-  return (
-    <Card className='!rounded-2xl shadow-sm border-0'>
-      {/* 卡片头部 */}
-      <div className='flex items-center justify-between mb-4'>
-        <div className='flex items-center'>
-          <Avatar size='small' color='blue' className='mr-3 shadow-md'>
-            <CreditCard size={16} />
-          </Avatar>
-          <div>
-            <Typography.Text className='text-lg font-medium'>
-              {t('账户充值')}
-            </Typography.Text>
-            <div className='text-xs'>{t('多种充值方式,安全便捷')}</div>
-          </div>
-        </div>
-        <Button
-          icon={<Receipt size={16} />}
-          theme='solid'
-          onClick={onOpenHistory}
-        >
-          {t('账单')}
-        </Button>
-      </div>
+  const [activeTab, setActiveTab] = useState('topup');
+  const shouldShowSubscription =
+    !subscriptionLoading && subscriptionPlans.length > 0;
 
-      <Space vertical style={{ width: '100%' }}>
-        {/* 统计数据 */}
-        <Card
-          className='!rounded-xl w-full'
-          cover={
-            <div
-              className='relative h-30'
-              style={{
-                '--palette-primary-darkerChannel': '37 99 235',
-                backgroundImage: `linear-gradient(0deg, rgba(var(--palette-primary-darkerChannel) / 80%), rgba(var(--palette-primary-darkerChannel) / 80%)), url('/cover-4.webp')`,
-                backgroundSize: 'cover',
-                backgroundPosition: 'center',
-                backgroundRepeat: 'no-repeat',
-              }}
-            >
-              <div className='relative z-10 h-full flex flex-col justify-between p-4'>
-                <div className='flex justify-between items-center'>
-                  <Text strong style={{ color: 'white', fontSize: '16px' }}>
-                    {t('账户统计')}
-                  </Text>
-                </div>
+  useEffect(() => {
+    if (initialTabSetRef.current) return;
+    if (subscriptionLoading) return;
+    setActiveTab(shouldShowSubscription ? 'subscription' : 'topup');
+    initialTabSetRef.current = true;
+  }, [shouldShowSubscription, subscriptionLoading]);
+
+  useEffect(() => {
+    if (!shouldShowSubscription && activeTab !== 'topup') {
+      setActiveTab('topup');
+    }
+  }, [shouldShowSubscription, activeTab]);
+  const topupContent = (
+    <Space vertical style={{ width: '100%' }}>
+      {/* 统计数据 */}
+      <Card
+        className='!rounded-xl w-full'
+        cover={
+          <div
+            className='relative h-30'
+            style={{
+              '--palette-primary-darkerChannel': '37 99 235',
+              backgroundImage: `linear-gradient(0deg, rgba(var(--palette-primary-darkerChannel) / 80%), rgba(var(--palette-primary-darkerChannel) / 80%)), url('/cover-4.webp')`,
+              backgroundSize: 'cover',
+              backgroundPosition: 'center',
+              backgroundRepeat: 'no-repeat',
+            }}
+          >
+            <div className='relative z-10 h-full flex flex-col justify-between p-4'>
+              <div className='flex justify-between items-center'>
+                <Text strong style={{ color: 'white', fontSize: '16px' }}>
+                  {t('账户统计')}
+                </Text>
+              </div>
 
-                {/* 统计数据 */}
-                <div className='grid grid-cols-3 gap-6 mt-4'>
-                  {/* 当前余额 */}
-                  <div className='text-center'>
-                    <div
-                      className='text-base sm:text-2xl font-bold mb-2'
-                      style={{ color: 'white' }}
+              {/* 统计数据 */}
+              <div className='grid grid-cols-3 gap-6 mt-4'>
+                {/* 当前余额 */}
+                <div className='text-center'>
+                  <div
+                    className='text-base sm:text-2xl font-bold mb-2'
+                    style={{ color: 'white' }}
+                  >
+                    {renderQuota(userState?.user?.quota)}
+                  </div>
+                  <div className='flex items-center justify-center text-sm'>
+                    <Wallet
+                      size={14}
+                      className='mr-1'
+                      style={{ color: 'rgba(255,255,255,0.8)' }}
+                    />
+                    <Text
+                      style={{
+                        color: 'rgba(255,255,255,0.8)',
+                        fontSize: '12px',
+                      }}
                     >
-                      {renderQuota(userState?.user?.quota)}
-                    </div>
-                    <div className='flex items-center justify-center text-sm'>
-                      <Wallet
-                        size={14}
-                        className='mr-1'
-                        style={{ color: 'rgba(255,255,255,0.8)' }}
-                      />
-                      <Text
-                        style={{
-                          color: 'rgba(255,255,255,0.8)',
-                          fontSize: '12px',
-                        }}
-                      >
-                        {t('当前余额')}
-                      </Text>
-                    </div>
+                      {t('当前余额')}
+                    </Text>
                   </div>
+                </div>
 
-                  {/* 历史消耗 */}
-                  <div className='text-center'>
-                    <div
-                      className='text-base sm:text-2xl font-bold mb-2'
-                      style={{ color: 'white' }}
+                {/* 历史消耗 */}
+                <div className='text-center'>
+                  <div
+                    className='text-base sm:text-2xl font-bold mb-2'
+                    style={{ color: 'white' }}
+                  >
+                    {renderQuota(userState?.user?.used_quota)}
+                  </div>
+                  <div className='flex items-center justify-center text-sm'>
+                    <TrendingUp
+                      size={14}
+                      className='mr-1'
+                      style={{ color: 'rgba(255,255,255,0.8)' }}
+                    />
+                    <Text
+                      style={{
+                        color: 'rgba(255,255,255,0.8)',
+                        fontSize: '12px',
+                      }}
                     >
-                      {renderQuota(userState?.user?.used_quota)}
-                    </div>
-                    <div className='flex items-center justify-center text-sm'>
-                      <TrendingUp
-                        size={14}
-                        className='mr-1'
-                        style={{ color: 'rgba(255,255,255,0.8)' }}
-                      />
-                      <Text
-                        style={{
-                          color: 'rgba(255,255,255,0.8)',
-                          fontSize: '12px',
-                        }}
-                      >
-                        {t('历史消耗')}
-                      </Text>
-                    </div>
+                      {t('历史消耗')}
+                    </Text>
                   </div>
+                </div>
 
-                  {/* 请求次数 */}
-                  <div className='text-center'>
-                    <div
-                      className='text-base sm:text-2xl font-bold mb-2'
-                      style={{ color: 'white' }}
+                {/* 请求次数 */}
+                <div className='text-center'>
+                  <div
+                    className='text-base sm:text-2xl font-bold mb-2'
+                    style={{ color: 'white' }}
+                  >
+                    {userState?.user?.request_count || 0}
+                  </div>
+                  <div className='flex items-center justify-center text-sm'>
+                    <BarChart2
+                      size={14}
+                      className='mr-1'
+                      style={{ color: 'rgba(255,255,255,0.8)' }}
+                    />
+                    <Text
+                      style={{
+                        color: 'rgba(255,255,255,0.8)',
+                        fontSize: '12px',
+                      }}
                     >
-                      {userState?.user?.request_count || 0}
-                    </div>
-                    <div className='flex items-center justify-center text-sm'>
-                      <BarChart2
-                        size={14}
-                        className='mr-1'
-                        style={{ color: 'rgba(255,255,255,0.8)' }}
-                      />
-                      <Text
-                        style={{
-                          color: 'rgba(255,255,255,0.8)',
-                          fontSize: '12px',
-                        }}
-                      >
-                        {t('请求次数')}
-                      </Text>
-                    </div>
+                      {t('请求次数')}
+                    </Text>
                   </div>
                 </div>
               </div>
             </div>
-          }
-        >
-          {/* 在线充值表单 */}
-          {statusLoading ? (
-            <div className='py-8 flex justify-center'>
-              <Spin size='large' />
-            </div>
-          ) : enableOnlineTopUp || enableStripeTopUp || enableCreemTopUp ? (
-            <Form
-              getFormApi={(api) => (onlineFormApiRef.current = api)}
-              initValues={{ topUpCount: topUpCount }}
-            >
-              <div className='space-y-6'>
-                {(enableOnlineTopUp || enableStripeTopUp) && (
-                  <Row gutter={12}>
-                    <Col xs={24} sm={24} md={24} lg={10} xl={10}>
-                      <Form.InputNumber
-                        field='topUpCount'
-                        label={t('充值数量')}
-                        disabled={!enableOnlineTopUp && !enableStripeTopUp}
-                        placeholder={
-                          t('充值数量,最低 ') + renderQuotaWithAmount(minTopUp)
-                        }
-                        value={topUpCount}
-                        min={minTopUp}
-                        max={999999999}
-                        step={1}
-                        precision={0}
-                        onChange={async (value) => {
-                          if (value && value >= 1) {
-                            setTopUpCount(value);
-                            setSelectedPreset(null);
-                            await getAmount(value);
-                          }
-                        }}
-                        onBlur={(e) => {
-                          const value = parseInt(e.target.value);
-                          if (!value || value < 1) {
-                            setTopUpCount(1);
-                            getAmount(1);
-                          }
-                        }}
-                        formatter={(value) => (value ? `${value}` : '')}
-                        parser={(value) =>
-                          value ? parseInt(value.replace(/[^\d]/g, '')) : 0
+          </div>
+        }
+      >
+        {/* 在线充值表单 */}
+        {statusLoading ? (
+          <div className='py-8 flex justify-center'>
+            <Spin size='large' />
+          </div>
+        ) : enableOnlineTopUp || enableStripeTopUp || enableCreemTopUp ? (
+          <Form
+            getFormApi={(api) => (onlineFormApiRef.current = api)}
+            initValues={{ topUpCount: topUpCount }}
+          >
+            <div className='space-y-6'>
+              {(enableOnlineTopUp || enableStripeTopUp) && (
+                <Row gutter={12}>
+                  <Col xs={24} sm={24} md={24} lg={10} xl={10}>
+                    <Form.InputNumber
+                      field='topUpCount'
+                      label={t('充值数量')}
+                      disabled={!enableOnlineTopUp && !enableStripeTopUp}
+                      placeholder={
+                        t('充值数量,最低 ') + renderQuotaWithAmount(minTopUp)
+                      }
+                      value={topUpCount}
+                      min={minTopUp}
+                      max={999999999}
+                      step={1}
+                      precision={0}
+                      onChange={async (value) => {
+                        if (value && value >= 1) {
+                          setTopUpCount(value);
+                          setSelectedPreset(null);
+                          await getAmount(value);
                         }
-                        extraText={
-                          <Skeleton
-                            loading={showAmountSkeleton}
-                            active
-                            placeholder={
-                              <Skeleton.Title
-                                style={{
-                                  width: 120,
-                                  height: 20,
-                                  borderRadius: 6,
-                                }}
-                              />
-                            }
-                          >
-                            <Text type='secondary' className='text-red-600'>
-                              {t('实付金额:')}
-                              <span style={{ color: 'red' }}>
-                                {renderAmount()}
-                              </span>
-                            </Text>
-                          </Skeleton>
+                      }}
+                      onBlur={(e) => {
+                        const value = parseInt(e.target.value);
+                        if (!value || value < 1) {
+                          setTopUpCount(1);
+                          getAmount(1);
                         }
-                        style={{ width: '100%' }}
-                      />
-                    </Col>
-                    <Col xs={24} sm={24} md={24} lg={14} xl={14}>
-                      <Form.Slot label={t('选择支付方式')}>
-                        {payMethods && payMethods.length > 0 ? (
-                          <Space wrap>
-                            {payMethods.map((payMethod) => {
-                              const minTopupVal =
-                                Number(payMethod.min_topup) || 0;
-                              const isStripe = payMethod.type === 'stripe';
-                              const disabled =
-                                (!enableOnlineTopUp && !isStripe) ||
-                                (!enableStripeTopUp && isStripe) ||
-                                minTopupVal > Number(topUpCount || 0);
-
-                              const buttonEl = (
-                                <Button
-                                  key={payMethod.type}
-                                  theme='outline'
-                                  type='tertiary'
-                                  onClick={() => preTopUp(payMethod.type)}
-                                  disabled={disabled}
-                                  loading={
-                                    paymentLoading && payWay === payMethod.type
-                                  }
-                                  icon={
-                                    payMethod.type === 'alipay' ? (
-                                      <SiAlipay size={18} color='#1677FF' />
-                                    ) : payMethod.type === 'wxpay' ? (
-                                      <SiWechat size={18} color='#07C160' />
-                                    ) : payMethod.type === 'stripe' ? (
-                                      <SiStripe size={18} color='#635BFF' />
-                                    ) : (
-                                      <CreditCard
-                                        size={18}
-                                        color={
-                                          payMethod.color ||
-                                          'var(--semi-color-text-2)'
-                                        }
-                                      />
-                                    )
-                                  }
-                                  className='!rounded-lg !px-4 !py-2'
-                                >
-                                  {payMethod.name}
-                                </Button>
-                              );
-
-                              return disabled &&
-                                minTopupVal > Number(topUpCount || 0) ? (
-                                <Tooltip
-                                  content={
-                                    t('此支付方式最低充值金额为') +
-                                    ' ' +
-                                    minTopupVal
-                                  }
-                                  key={payMethod.type}
-                                >
-                                  {buttonEl}
-                                </Tooltip>
-                              ) : (
-                                <React.Fragment key={payMethod.type}>
-                                  {buttonEl}
-                                </React.Fragment>
-                              );
-                            })}
-                          </Space>
-                        ) : (
-                          <div className='text-gray-500 text-sm p-3 bg-gray-50 rounded-lg border border-dashed border-gray-300'>
-                            {t('暂无可用的支付方式,请联系管理员配置')}
-                          </div>
-                        )}
-                      </Form.Slot>
-                    </Col>
-                  </Row>
-                )}
-
-                {(enableOnlineTopUp || enableStripeTopUp) && (
-                  <Form.Slot
-                    label={
-                      <div className='flex items-center gap-2'>
-                        <span>{t('选择充值额度')}</span>
-                        {(() => {
-                          const { symbol, rate, type } = getCurrencyConfig();
-                          if (type === 'USD') return null;
-
-                          return (
-                            <span
+                      }}
+                      formatter={(value) => (value ? `${value}` : '')}
+                      parser={(value) =>
+                        value ? parseInt(value.replace(/[^\d]/g, '')) : 0
+                      }
+                      extraText={
+                        <Skeleton
+                          loading={showAmountSkeleton}
+                          active
+                          placeholder={
+                            <Skeleton.Title
                               style={{
-                                color: 'var(--semi-color-text-2)',
-                                fontSize: '12px',
-                                fontWeight: 'normal',
+                                width: 120,
+                                height: 20,
+                                borderRadius: 6,
                               }}
-                            >
-                              (1 $ = {rate.toFixed(2)} {symbol})
+                            />
+                          }
+                        >
+                          <Text type='secondary' className='text-red-600'>
+                            {t('实付金额:')}
+                            <span style={{ color: 'red' }}>
+                              {renderAmount()}
                             </span>
-                          );
-                        })()}
-                      </div>
-                    }
-                  >
-                    <div className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2'>
-                      {presetAmounts.map((preset, index) => {
-                        const discount =
-                          preset.discount ||
-                          topupInfo?.discount?.[preset.value] ||
-                          1.0;
-                        const originalPrice = preset.value * priceRatio;
-                        const discountedPrice = originalPrice * discount;
-                        const hasDiscount = discount < 1.0;
-                        const actualPay = discountedPrice;
-                        const save = originalPrice - discountedPrice;
+                          </Text>
+                        </Skeleton>
+                      }
+                      style={{ width: '100%' }}
+                    />
+                  </Col>
+                  <Col xs={24} sm={24} md={24} lg={14} xl={14}>
+                    <Form.Slot label={t('选择支付方式')}>
+                      {payMethods && payMethods.length > 0 ? (
+                        <Space wrap>
+                          {payMethods.map((payMethod) => {
+                            const minTopupVal = Number(payMethod.min_topup) || 0;
+                            const isStripe = payMethod.type === 'stripe';
+                            const disabled =
+                              (!enableOnlineTopUp && !isStripe) ||
+                              (!enableStripeTopUp && isStripe) ||
+                              minTopupVal > Number(topUpCount || 0);
 
-                        // 根据当前货币类型换算显示金额和数量
-                        const { symbol, rate, type } = getCurrencyConfig();
-                        const statusStr = localStorage.getItem('status');
-                        let usdRate = 7; // 默认CNY汇率
-                        try {
-                          if (statusStr) {
-                            const s = JSON.parse(statusStr);
-                            usdRate = s?.usd_exchange_rate || 7;
-                          }
-                        } catch (e) {}
+                            const buttonEl = (
+                              <Button
+                                key={payMethod.type}
+                                theme='outline'
+                                type='tertiary'
+                                onClick={() => preTopUp(payMethod.type)}
+                                disabled={disabled}
+                                loading={
+                                  paymentLoading && payWay === payMethod.type
+                                }
+                                icon={
+                                  payMethod.type === 'alipay' ? (
+                                    <SiAlipay size={18} color='#1677FF' />
+                                  ) : payMethod.type === 'wxpay' ? (
+                                    <SiWechat size={18} color='#07C160' />
+                                  ) : payMethod.type === 'stripe' ? (
+                                    <SiStripe size={18} color='#635BFF' />
+                                  ) : (
+                                    <CreditCard
+                                      size={18}
+                                      color={
+                                        payMethod.color ||
+                                        'var(--semi-color-text-2)'
+                                      }
+                                    />
+                                  )
+                                }
+                                className='!rounded-lg !px-4 !py-2'
+                              >
+                                {payMethod.name}
+                              </Button>
+                            );
 
-                        let displayValue = preset.value; // 显示的数量
-                        let displayActualPay = actualPay;
-                        let displaySave = save;
+                            return disabled &&
+                              minTopupVal > Number(topUpCount || 0) ? (
+                              <Tooltip
+                                content={
+                                  t('此支付方式最低充值金额为') +
+                                  ' ' +
+                                  minTopupVal
+                                }
+                                key={payMethod.type}
+                              >
+                                {buttonEl}
+                              </Tooltip>
+                            ) : (
+                              <React.Fragment key={payMethod.type}>
+                                {buttonEl}
+                              </React.Fragment>
+                            );
+                          })}
+                        </Space>
+                      ) : (
+                        <div className='text-gray-500 text-sm p-3 bg-gray-50 rounded-lg border border-dashed border-gray-300'>
+                          {t('暂无可用的支付方式,请联系管理员配置')}
+                        </div>
+                      )}
+                    </Form.Slot>
+                  </Col>
+                </Row>
+              )}
 
-                        if (type === 'USD') {
-                          // 数量保持USD,价格从CNY转USD
-                          displayActualPay = actualPay / usdRate;
-                          displaySave = save / usdRate;
-                        } else if (type === 'CNY') {
-                          // 数量转CNY,价格已是CNY
-                          displayValue = preset.value * usdRate;
-                        } else if (type === 'CUSTOM') {
-                          // 数量和价格都转自定义货币
-                          displayValue = preset.value * rate;
-                          displayActualPay = (actualPay / usdRate) * rate;
-                          displaySave = (save / usdRate) * rate;
-                        }
+              {(enableOnlineTopUp || enableStripeTopUp) && (
+                <Form.Slot
+                  label={
+                    <div className='flex items-center gap-2'>
+                      <span>{t('选择充值额度')}</span>
+                      {(() => {
+                        const { symbol, rate, type } = getCurrencyConfig();
+                        if (type === 'USD') return null;
 
                         return (
-                          <Card
-                            key={index}
+                          <span
                             style={{
-                              cursor: 'pointer',
-                              border:
-                                selectedPreset === preset.value
-                                  ? '2px solid var(--semi-color-primary)'
-                                  : '1px solid var(--semi-color-border)',
-                              height: '100%',
-                              width: '100%',
-                            }}
-                            bodyStyle={{ padding: '12px' }}
-                            onClick={() => {
-                              selectPresetAmount(preset);
-                              onlineFormApiRef.current?.setValue(
-                                'topUpCount',
-                                preset.value,
-                              );
+                              color: 'var(--semi-color-text-2)',
+                              fontSize: '12px',
+                              fontWeight: 'normal',
                             }}
                           >
-                            <div style={{ textAlign: 'center' }}>
-                              <Typography.Title
-                                heading={6}
-                                style={{ margin: '0 0 8px 0' }}
-                              >
-                                <Coins size={18} />
-                                {formatLargeNumber(displayValue)} {symbol}
-                                {hasDiscount && (
-                                  <Tag style={{ marginLeft: 4 }} color='green'>
-                                    {t('折').includes('off')
-                                      ? (
-                                          (1 - parseFloat(discount)) *
-                                          100
-                                        ).toFixed(1)
-                                      : (discount * 10).toFixed(1)}
-                                    {t('折')}
-                                  </Tag>
-                                )}
-                              </Typography.Title>
-                              <div
-                                style={{
-                                  color: 'var(--semi-color-text-2)',
-                                  fontSize: '12px',
-                                  margin: '4px 0',
-                                }}
-                              >
-                                {t('实付')} {symbol}
-                                {displayActualPay.toFixed(2)},
-                                {hasDiscount
-                                  ? `${t('节省')} ${symbol}${displaySave.toFixed(2)}`
-                                  : `${t('节省')} ${symbol}0.00`}
-                              </div>
-                            </div>
-                          </Card>
+                            (1 $ = {rate.toFixed(2)} {symbol})
+                          </span>
                         );
-                      })}
+                      })()}
                     </div>
-                  </Form.Slot>
-                )}
+                  }
+                >
+                  <div className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2'>
+                    {presetAmounts.map((preset, index) => {
+                      const discount =
+                        preset.discount || topupInfo?.discount?.[preset.value] || 1.0;
+                      const originalPrice = preset.value * priceRatio;
+                      const discountedPrice = originalPrice * discount;
+                      const hasDiscount = discount < 1.0;
+                      const actualPay = discountedPrice;
+                      const save = originalPrice - discountedPrice;
+
+                      // 根据当前货币类型换算显示金额和数量
+                      const { symbol, rate, type } = getCurrencyConfig();
+                      const statusStr = localStorage.getItem('status');
+                      let usdRate = 7; // 默认CNY汇率
+                      try {
+                        if (statusStr) {
+                          const s = JSON.parse(statusStr);
+                          usdRate = s?.usd_exchange_rate || 7;
+                        }
+                      } catch (e) { }
+
+                      let displayValue = preset.value; // 显示的数量
+                      let displayActualPay = actualPay;
+                      let displaySave = save;
 
-                {/* Creem 充值区域 */}
-                {enableCreemTopUp && creemProducts.length > 0 && (
-                  <Form.Slot label={t('Creem 充值')}>
-                    <div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3'>
-                      {creemProducts.map((product, index) => (
+                      if (type === 'USD') {
+                        // 数量保持USD,价格从CNY转USD
+                        displayActualPay = actualPay / usdRate;
+                        displaySave = save / usdRate;
+                      } else if (type === 'CNY') {
+                        // 数量转CNY,价格已是CNY
+                        displayValue = preset.value * usdRate;
+                      } else if (type === 'CUSTOM') {
+                        // 数量和价格都转自定义货币
+                        displayValue = preset.value * rate;
+                        displayActualPay = (actualPay / usdRate) * rate;
+                        displaySave = (save / usdRate) * rate;
+                      }
+
+                      return (
                         <Card
                           key={index}
-                          onClick={() => creemPreTopUp(product)}
-                          className='cursor-pointer !rounded-2xl transition-all hover:shadow-md border-gray-200 hover:border-gray-300'
-                          bodyStyle={{ textAlign: 'center', padding: '16px' }}
+                          style={{
+                            cursor: 'pointer',
+                            border:
+                              selectedPreset === preset.value
+                                ? '2px solid var(--semi-color-primary)'
+                                : '1px solid var(--semi-color-border)',
+                            height: '100%',
+                            width: '100%',
+                          }}
+                          bodyStyle={{ padding: '12px' }}
+                          onClick={() => {
+                            selectPresetAmount(preset);
+                            onlineFormApiRef.current?.setValue(
+                              'topUpCount',
+                              preset.value,
+                            );
+                          }}
                         >
-                          <div className='font-medium text-lg mb-2'>
-                            {product.name}
-                          </div>
-                          <div className='text-sm text-gray-600 mb-2'>
-                            {t('充值额度')}: {product.quota}
-                          </div>
-                          <div className='text-lg font-semibold text-blue-600'>
-                            {product.currency === 'EUR' ? '€' : '$'}
-                            {product.price}
+                          <div style={{ textAlign: 'center' }}>
+                            <Typography.Title
+                              heading={6}
+                              style={{ margin: '0 0 8px 0' }}
+                            >
+                              <Coins size={18} />
+                              {formatLargeNumber(displayValue)} {symbol}
+                              {hasDiscount && (
+                                <Tag style={{ marginLeft: 4 }} color='green'>
+                                  {t('折').includes('off')
+                                    ? ((1 - parseFloat(discount)) * 100).toFixed(1)
+                                    : (discount * 10).toFixed(1)}
+                                  {t('折')}
+                                </Tag>
+                              )}
+                            </Typography.Title>
+                            <div
+                              style={{
+                                color: 'var(--semi-color-text-2)',
+                                fontSize: '12px',
+                                margin: '4px 0',
+                              }}
+                            >
+                              {t('实付')} {symbol}
+                              {displayActualPay.toFixed(2)},
+                              {hasDiscount
+                                ? `${t('节省')} ${symbol}${displaySave.toFixed(2)}`
+                                : `${t('节省')} ${symbol}0.00`}
+                            </div>
                           </div>
                         </Card>
-                      ))}
-                    </div>
-                  </Form.Slot>
-                )}
-              </div>
-            </Form>
-          ) : (
-            <Banner
-              type='info'
-              description={t(
-                '管理员未开启在线充值功能,请联系管理员开启或使用兑换码充值。',
+                      );
+                    })}
+                  </div>
+                </Form.Slot>
               )}
-              className='!rounded-xl'
-              closeIcon={null}
-            />
-          )}
-        </Card>
 
-        {/* 兑换码充值 */}
-        <Card
-          className='!rounded-xl w-full'
-          title={
-            <Text type='tertiary' strong>
-              {t('兑换码充值')}
-            </Text>
-          }
+              {/* Creem 充值区域 */}
+              {enableCreemTopUp && creemProducts.length > 0 && (
+                <Form.Slot label={t('Creem 充值')}>
+                  <div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3'>
+                    {creemProducts.map((product, index) => (
+                      <Card
+                        key={index}
+                        onClick={() => creemPreTopUp(product)}
+                        className='cursor-pointer !rounded-2xl transition-all hover:shadow-md border-gray-200 hover:border-gray-300'
+                        bodyStyle={{ textAlign: 'center', padding: '16px' }}
+                      >
+                        <div className='font-medium text-lg mb-2'>
+                          {product.name}
+                        </div>
+                        <div className='text-sm text-gray-600 mb-2'>
+                          {t('充值额度')}: {product.quota}
+                        </div>
+                        <div className='text-lg font-semibold text-blue-600'>
+                          {product.currency === 'EUR' ? '€' : '$'}
+                          {product.price}
+                        </div>
+                      </Card>
+                    ))}
+                  </div>
+                </Form.Slot>
+              )}
+            </div>
+          </Form>
+        ) : (
+          <Banner
+            type='info'
+            description={t(
+              '管理员未开启在线充值功能,请联系管理员开启或使用兑换码充值。',
+            )}
+            className='!rounded-xl'
+            closeIcon={null}
+          />
+        )}
+      </Card>
+
+      {/* 兑换码充值 */}
+      <Card
+        className='!rounded-xl w-full'
+        title={
+          <Text type='tertiary' strong>
+            {t('兑换码充值')}
+          </Text>
+        }
+      >
+        <Form
+          getFormApi={(api) => (redeemFormApiRef.current = api)}
+          initValues={{ redemptionCode: redemptionCode }}
         >
-          <Form
-            getFormApi={(api) => (redeemFormApiRef.current = api)}
-            initValues={{ redemptionCode: redemptionCode }}
-          >
-            <Form.Input
-              field='redemptionCode'
-              noLabel={true}
-              placeholder={t('请输入兑换码')}
-              value={redemptionCode}
-              onChange={(value) => setRedemptionCode(value)}
-              prefix={<IconGift />}
-              suffix={
-                <div className='flex items-center gap-2'>
-                  <Button
-                    type='primary'
-                    theme='solid'
-                    onClick={topUp}
-                    loading={isSubmitting}
+          <Form.Input
+            field='redemptionCode'
+            noLabel={true}
+            placeholder={t('请输入兑换码')}
+            value={redemptionCode}
+            onChange={(value) => setRedemptionCode(value)}
+            prefix={<IconGift />}
+            suffix={
+              <div className='flex items-center gap-2'>
+                <Button
+                  type='primary'
+                  theme='solid'
+                  onClick={topUp}
+                  loading={isSubmitting}
+                >
+                  {t('兑换额度')}
+                </Button>
+              </div>
+            }
+            showClear
+            style={{ width: '100%' }}
+            extraText={
+              topUpLink && (
+                <Text type='tertiary'>
+                  {t('在找兑换码?')}
+                  <Text
+                    type='secondary'
+                    underline
+                    className='cursor-pointer'
+                    onClick={openTopUpLink}
                   >
-                    {t('兑换额度')}
-                  </Button>
-                </div>
-              }
-              showClear
-              style={{ width: '100%' }}
-              extraText={
-                topUpLink && (
-                  <Text type='tertiary'>
-                    {t('在找兑换码?')}
-                    <Text
-                      type='secondary'
-                      underline
-                      className='cursor-pointer'
-                      onClick={openTopUpLink}
-                    >
-                      {t('购买兑换码')}
-                    </Text>
+                    {t('购买兑换码')}
                   </Text>
-                )
-              }
-            />
-          </Form>
-        </Card>
-      </Space>
+                </Text>
+              )
+            }
+          />
+        </Form>
+      </Card>
+    </Space>
+  );
+
+  return (
+    <Card className='!rounded-2xl shadow-sm border-0'>
+      {/* 卡片头部 */}
+      <div className='flex items-center justify-between mb-4'>
+        <div className='flex items-center'>
+          <Avatar size='small' color='blue' className='mr-3 shadow-md'>
+            <CreditCard size={16} />
+          </Avatar>
+          <div>
+            <Typography.Text className='text-lg font-medium'>
+              {t('账户充值')}
+            </Typography.Text>
+            <div className='text-xs'>{t('多种充值方式,安全便捷')}</div>
+          </div>
+        </div>
+        <Button
+          icon={<Receipt size={16} />}
+          theme='solid'
+          onClick={onOpenHistory}
+        >
+          {t('账单')}
+        </Button>
+      </div>
+
+      {shouldShowSubscription ? (
+        <Tabs type='card' activeKey={activeTab} onChange={setActiveTab}>
+          <TabPane
+            tab={
+              <div className='flex items-center gap-2'>
+                <Sparkles size={16} />
+                {t('订阅套餐')}
+              </div>
+            }
+            itemKey='subscription'
+          >
+            <div className='py-2'>
+              <SubscriptionPlansCard
+                t={t}
+                loading={subscriptionLoading}
+                plans={subscriptionPlans}
+                payMethods={payMethods}
+                enableOnlineTopUp={enableOnlineTopUp}
+                enableStripeTopUp={enableStripeTopUp}
+                enableCreemTopUp={enableCreemTopUp}
+                billingPreference={billingPreference}
+                onChangeBillingPreference={onChangeBillingPreference}
+                activeSubscriptions={activeSubscriptions}
+                allSubscriptions={allSubscriptions}
+                reloadSubscriptionSelf={reloadSubscriptionSelf}
+                withCard={false}
+              />
+            </div>
+          </TabPane>
+          <TabPane
+            tab={
+              <div className='flex items-center gap-2'>
+                <Wallet size={16} />
+                {t('额度充值')}
+              </div>
+            }
+            itemKey='topup'
+          >
+            <div className='py-2'>{topupContent}</div>
+          </TabPane>
+        </Tabs>
+      ) : (
+        topupContent
+      )}
     </Card>
   );
 };

+ 54 - 56
web/src/components/topup/SubscriptionPlansCard.jsx

@@ -19,7 +19,6 @@ For commercial licensing, please contact [email protected]
 
 import React, { useMemo, useState } from 'react';
 import {
-  Avatar,
   Badge,
   Button,
   Card,
@@ -33,7 +32,7 @@ import {
 } from '@douyinfe/semi-ui';
 import { API, showError, showSuccess, renderQuota } from '../../helpers';
 import { getCurrencyConfig } from '../../helpers/render';
-import { Crown, RefreshCw, Sparkles } from 'lucide-react';
+import { RefreshCw, Sparkles } from 'lucide-react';
 import SubscriptionPurchaseModal from './modals/SubscriptionPurchaseModal';
 import {
   formatSubscriptionDuration,
@@ -83,6 +82,7 @@ const SubscriptionPlansCard = ({
   activeSubscriptions = [],
   allSubscriptions = [],
   reloadSubscriptionSelf,
+  withCard = true,
 }) => {
   const [open, setOpen] = useState(false);
   const [selectedPlan, setSelectedPlan] = useState(null);
@@ -241,33 +241,9 @@ const SubscriptionPlansCard = ({
     return Math.round((used / total) * 100);
   };
 
-  return (
-    <Card className='!rounded-2xl shadow-sm border-0'>
+  const cardContent = (
+    <>
       {/* 卡片头部 */}
-      <div className='flex items-center justify-between mb-3'>
-        <div className='flex items-center'>
-          <Avatar size='small' color='violet' className='mr-3 shadow-md'>
-            <Crown size={16} />
-          </Avatar>
-          <div>
-            <Text className='text-lg font-medium'>{t('订阅套餐')}</Text>
-            <div className='text-xs'>{t('购买订阅获得模型额度/次数')}</div>
-          </div>
-        </div>
-        {/* 扣费策略 - 右上角 */}
-        <Select
-          value={billingPreference}
-          onChange={onChangeBillingPreference}
-          size='small'
-          optionList={[
-            { value: 'subscription_first', label: t('优先订阅') },
-            { value: 'wallet_first', label: t('优先钱包') },
-            { value: 'subscription_only', label: t('仅用订阅') },
-            { value: 'wallet_only', label: t('仅用钱包') },
-          ]}
-        />
-      </div>
-
       {loading ? (
         <div className='space-y-4'>
           {/* 我的订阅骨架屏 */}
@@ -281,7 +257,7 @@ const SubscriptionPlansCard = ({
             </div>
           </Card>
           {/* 套餐列表骨架屏 */}
-          <div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full'>
+          <div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full px-1'>
             {[1, 2, 3].map((i) => (
               <Card
                 key={i}
@@ -317,8 +293,8 @@ const SubscriptionPlansCard = ({
         <Space vertical style={{ width: '100%' }} spacing={8}>
           {/* 当前订阅状态 */}
           <Card className='!rounded-xl w-full' bodyStyle={{ padding: '12px' }}>
-            <div className='flex items-center justify-between mb-2'>
-              <div className='flex items-center gap-2'>
+            <div className='flex items-center justify-between mb-2 gap-3'>
+              <div className='flex items-center gap-2 flex-1 min-w-0'>
                 <Text strong>{t('我的订阅')}</Text>
                 {hasActiveSubscription ? (
                   <Tag
@@ -341,19 +317,32 @@ const SubscriptionPlansCard = ({
                   </Tag>
                 )}
               </div>
-              <Button
-                size='small'
-                theme='light'
-                type='tertiary'
-                icon={
-                  <RefreshCw
-                    size={12}
-                    className={refreshing ? 'animate-spin' : ''}
-                  />
-                }
-                onClick={handleRefresh}
-                loading={refreshing}
-              />
+              <div className='flex items-center gap-2'>
+                <Select
+                  value={billingPreference}
+                  onChange={onChangeBillingPreference}
+                  size='small'
+                  optionList={[
+                    { value: 'subscription_first', label: t('优先订阅') },
+                    { value: 'wallet_first', label: t('优先钱包') },
+                    { value: 'subscription_only', label: t('仅用订阅') },
+                    { value: 'wallet_only', label: t('仅用钱包') },
+                  ]}
+                />
+                <Button
+                  size='small'
+                  theme='light'
+                  type='tertiary'
+                  icon={
+                    <RefreshCw
+                      size={12}
+                      className={refreshing ? 'animate-spin' : ''}
+                    />
+                  }
+                  onClick={handleRefresh}
+                  loading={refreshing}
+                />
+              </div>
             </div>
 
             {hasAnySubscription ? (
@@ -451,7 +440,7 @@ const SubscriptionPlansCard = ({
 
           {/* 可购买套餐 - 标准定价卡片 */}
           {plans.length > 0 ? (
-            <div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full'>
+            <div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full px-1'>
               {plans.map((p, index) => {
                 const plan = p?.plan;
                 const totalAmount = Number(plan?.total_amount || 0);
@@ -482,9 +471,9 @@ const SubscriptionPlansCard = ({
                   resetLabel ? { label: resetLabel } : null,
                   totalAmount > 0
                     ? {
-                        label: totalLabel,
-                        tooltip: `${t('原生额度')}:${totalAmount}`,
-                      }
+                      label: totalLabel,
+                      tooltip: `${t('原生额度')}:${totalAmount}`,
+                    }
                     : { label: totalLabel },
                   limitLabel ? { label: limitLabel } : null,
                   upgradeLabel ? { label: upgradeLabel } : null,
@@ -493,9 +482,8 @@ const SubscriptionPlansCard = ({
                 return (
                   <Card
                     key={plan?.id}
-                    className={`!rounded-xl transition-all hover:shadow-lg w-full h-full ${
-                      isPopular ? 'ring-2 ring-purple-500' : ''
-                    }`}
+                    className={`!rounded-xl transition-all hover:shadow-lg w-full h-full ${isPopular ? 'ring-2 ring-purple-500' : ''
+                      }`}
                     bodyStyle={{ padding: 0 }}
                   >
                     <div className='p-4 h-full flex flex-col'>
@@ -583,7 +571,7 @@ const SubscriptionPlansCard = ({
                           const buttonEl = (
                             <Button
                               theme='outline'
-                              type='tertiary'
+                              type='primary'
                               block
                               disabled={reached}
                               onClick={() => {
@@ -614,6 +602,16 @@ const SubscriptionPlansCard = ({
           )}
         </Space>
       )}
+    </>
+  );
+
+  return (
+    <>
+      {withCard ? (
+        <Card className='!rounded-2xl shadow-sm border-0'>{cardContent}</Card>
+      ) : (
+        <div className='space-y-3'>{cardContent}</div>
+      )}
 
       {/* 购买确认弹窗 */}
       <SubscriptionPurchaseModal
@@ -631,16 +629,16 @@ const SubscriptionPlansCard = ({
         purchaseLimitInfo={
           selectedPlan?.plan?.id
             ? {
-                limit: Number(selectedPlan?.plan?.max_purchase_per_user || 0),
-                count: getPlanPurchaseCount(selectedPlan?.plan?.id),
-              }
+              limit: Number(selectedPlan?.plan?.max_purchase_per_user || 0),
+              count: getPlanPurchaseCount(selectedPlan?.plan?.id),
+            }
             : null
         }
         onPayStripe={payStripe}
         onPayCreem={payCreem}
         onPayEpay={payEpay}
       />
-    </Card>
+    </>
   );
 };
 

+ 52 - 67
web/src/components/topup/index.jsx

@@ -35,7 +35,6 @@ import { StatusContext } from '../../context/Status';
 
 import RechargeCard from './RechargeCard';
 import InvitationCard from './InvitationCard';
-import SubscriptionPlansCard from './SubscriptionPlansCard';
 import TransferModal from './modals/TransferModal';
 import PaymentConfirmModal from './modals/PaymentConfirmModal';
 import TopupHistoryModal from './modals/TopupHistoryModal';
@@ -733,72 +732,58 @@ const TopUp = () => {
       </Modal>
 
       {/* 主布局区域 */}
-      <div className='grid grid-cols-1 lg:grid-cols-12 gap-6'>
-        {/* 左侧 - 订阅套餐 */}
-        <div className='lg:col-span-7'>
-          <SubscriptionPlansCard
-            t={t}
-            loading={subscriptionLoading}
-            plans={subscriptionPlans}
-            payMethods={payMethods}
-            enableOnlineTopUp={enableOnlineTopUp}
-            enableStripeTopUp={enableStripeTopUp}
-            enableCreemTopUp={enableCreemTopUp}
-            billingPreference={billingPreference}
-            onChangeBillingPreference={updateBillingPreference}
-            activeSubscriptions={activeSubscriptions}
-            allSubscriptions={allSubscriptions}
-            reloadSubscriptionSelf={getSubscriptionSelf}
-          />
-        </div>
-
-        {/* 右侧 - 账户充值 + 邀请奖励 */}
-        <div className='lg:col-span-5 flex flex-col gap-6'>
-          <RechargeCard
-            t={t}
-            enableOnlineTopUp={enableOnlineTopUp}
-            enableStripeTopUp={enableStripeTopUp}
-            enableCreemTopUp={enableCreemTopUp}
-            creemProducts={creemProducts}
-            creemPreTopUp={creemPreTopUp}
-            presetAmounts={presetAmounts}
-            selectedPreset={selectedPreset}
-            selectPresetAmount={selectPresetAmount}
-            formatLargeNumber={formatLargeNumber}
-            priceRatio={priceRatio}
-            topUpCount={topUpCount}
-            minTopUp={minTopUp}
-            renderQuotaWithAmount={renderQuotaWithAmount}
-            getAmount={getAmount}
-            setTopUpCount={setTopUpCount}
-            setSelectedPreset={setSelectedPreset}
-            renderAmount={renderAmount}
-            amountLoading={amountLoading}
-            payMethods={payMethods}
-            preTopUp={preTopUp}
-            paymentLoading={paymentLoading}
-            payWay={payWay}
-            redemptionCode={redemptionCode}
-            setRedemptionCode={setRedemptionCode}
-            topUp={topUp}
-            isSubmitting={isSubmitting}
-            topUpLink={topUpLink}
-            openTopUpLink={openTopUpLink}
-            userState={userState}
-            renderQuota={renderQuota}
-            statusLoading={statusLoading}
-            topupInfo={topupInfo}
-            onOpenHistory={handleOpenHistory}
-          />
-          <InvitationCard
-            t={t}
-            userState={userState}
-            renderQuota={renderQuota}
-            setOpenTransfer={setOpenTransfer}
-            affLink={affLink}
-            handleAffLinkClick={handleAffLinkClick}
-          />
-        </div>
+      <div className='grid grid-cols-1 lg:grid-cols-2 gap-6'>
+        <RechargeCard
+          t={t}
+          enableOnlineTopUp={enableOnlineTopUp}
+          enableStripeTopUp={enableStripeTopUp}
+          enableCreemTopUp={enableCreemTopUp}
+          creemProducts={creemProducts}
+          creemPreTopUp={creemPreTopUp}
+          presetAmounts={presetAmounts}
+          selectedPreset={selectedPreset}
+          selectPresetAmount={selectPresetAmount}
+          formatLargeNumber={formatLargeNumber}
+          priceRatio={priceRatio}
+          topUpCount={topUpCount}
+          minTopUp={minTopUp}
+          renderQuotaWithAmount={renderQuotaWithAmount}
+          getAmount={getAmount}
+          setTopUpCount={setTopUpCount}
+          setSelectedPreset={setSelectedPreset}
+          renderAmount={renderAmount}
+          amountLoading={amountLoading}
+          payMethods={payMethods}
+          preTopUp={preTopUp}
+          paymentLoading={paymentLoading}
+          payWay={payWay}
+          redemptionCode={redemptionCode}
+          setRedemptionCode={setRedemptionCode}
+          topUp={topUp}
+          isSubmitting={isSubmitting}
+          topUpLink={topUpLink}
+          openTopUpLink={openTopUpLink}
+          userState={userState}
+          renderQuota={renderQuota}
+          statusLoading={statusLoading}
+          topupInfo={topupInfo}
+          onOpenHistory={handleOpenHistory}
+          subscriptionLoading={subscriptionLoading}
+          subscriptionPlans={subscriptionPlans}
+          billingPreference={billingPreference}
+          onChangeBillingPreference={updateBillingPreference}
+          activeSubscriptions={activeSubscriptions}
+          allSubscriptions={allSubscriptions}
+          reloadSubscriptionSelf={getSubscriptionSelf}
+        />
+        <InvitationCard
+          t={t}
+          userState={userState}
+          renderQuota={renderQuota}
+          setOpenTransfer={setOpenTransfer}
+          affLink={affLink}
+          handleAffLinkClick={handleAffLinkClick}
+        />
       </div>
     </div>
   );

+ 1 - 1
web/src/i18n/locales/en.json

@@ -2721,7 +2721,7 @@
     "绑定订阅套餐": "Bind Subscription Plan",
     "绑定后会立即生成用户订阅(无需支付),有效期按套餐配置计算。": "After binding, a user subscription is created immediately (no payment required); validity follows the plan configuration.",
     "订阅套餐": "Subscription Plans",
-    "购买订阅获得模型额度/次数": "Purchase a subscription to get model quota/usage",
+    "额度充值": "Quota Top-up",
     "优先订阅": "Subscription first",
     "优先钱包": "Wallet first",
     "仅用订阅": "Subscription only",

+ 1 - 1
web/src/i18n/locales/fr.json

@@ -2684,7 +2684,7 @@
     "绑定订阅套餐": "Lier un plan d'abonnement",
     "绑定后会立即生成用户订阅(无需支付),有效期按套餐配置计算。": "Après liaison, un abonnement utilisateur est créé immédiatement (sans paiement) ; la validité suit la configuration du plan.",
     "订阅套餐": "Plans d'abonnement",
-    "购买订阅获得模型额度/次数": "Acheter un abonnement pour obtenir des quotas/usages de modèles",
+    "额度充值": "Recharge de quota",
     "优先订阅": "Abonnement en priorité",
     "优先钱包": "Portefeuille en priorité",
     "仅用订阅": "Abonnement uniquement",

+ 1 - 1
web/src/i18n/locales/ja.json

@@ -2667,7 +2667,7 @@
     "绑定订阅套餐": "サブスクリプションプランを紐付け",
     "绑定后会立即生成用户订阅(无需支付),有效期按套餐配置计算。": "紐付け後、ユーザーサブスクリプションが即時に作成されます(支払い不要)。有効期限はプラン設定に従います。",
     "订阅套餐": "サブスクリプションプラン",
-    "购买订阅获得模型额度/次数": "サブスクリプション購入でモデルのクォータ/回数を取得",
+    "额度充值": "クォータ補充",
     "优先订阅": "サブスクリプション優先",
     "优先钱包": "ウォレット優先",
     "仅用订阅": "サブスクリプションのみ",

+ 1 - 1
web/src/i18n/locales/ru.json

@@ -2697,7 +2697,7 @@
     "绑定订阅套餐": "Привязать план подписки",
     "绑定后会立即生成用户订阅(无需支付),有效期按套餐配置计算。": "После привязки подписка будет создана сразу (без оплаты); срок действия рассчитывается по настройкам плана.",
     "订阅套餐": "Планы подписки",
-    "购买订阅获得模型额度/次数": "Купите подписку, чтобы получить лимит/количество использования моделей",
+    "额度充值": "Пополнение квоты",
     "优先订阅": "Сначала подписка",
     "优先钱包": "Сначала кошелек",
     "仅用订阅": "Только подписка",

+ 1 - 1
web/src/i18n/locales/vi.json

@@ -3246,7 +3246,7 @@
     "绑定订阅套餐": "Liên kết gói đăng ký",
     "绑定后会立即生成用户订阅(无需支付),有效期按套餐配置计算。": "Sau khi liên kết, sẽ tạo đăng ký cho người dùng ngay (không cần thanh toán); thời hạn theo cấu hình gói.",
     "订阅套餐": "Gói đăng ký",
-    "购买订阅获得模型额度/次数": "Mua đăng ký để nhận hạn mức/lượt dùng mô hình",
+    "额度充值": "Nạp hạn mức",
     "优先订阅": "Ưu tiên đăng ký",
     "优先钱包": "Ưu tiên ví",
     "仅用订阅": "Chỉ dùng đăng ký",

+ 1 - 1
web/src/i18n/locales/zh.json

@@ -2706,7 +2706,7 @@
     "绑定订阅套餐": "绑定订阅套餐",
     "绑定后会立即生成用户订阅(无需支付),有效期按套餐配置计算。": "绑定后会立即生成用户订阅(无需支付),有效期按套餐配置计算。",
     "订阅套餐": "订阅套餐",
-    "购买订阅获得模型额度/次数": "购买订阅获得模型额度/次数",
+    "额度充值": "额度充值",
     "优先订阅": "优先订阅",
     "优先钱包": "优先钱包",
     "仅用订阅": "仅用订阅",