Explorar el Código

✨ feat(layout): refine footer visibility logic to target CardPro component pages

- Replace blanket console route footer hiding with specific page targeting
- Only hide footer on pages that use CardPro component:
  * /console/channel (channels management)
  * /console/log (usage logs)
  * /console/redemption (redemption codes)
  * /console/user (user management)
  * /console/token (token management)
  * /console/midjourney (midjourney logs)
  * /console/task (task logs)
  * /console/models (model management)
  * /pricing (pricing page)
- Footer now displays on other console pages (dashboard, settings, topup, etc.)
- Improves UI consistency by showing footer where CardPro's internal pagination isn't used

This change ensures footer is only hidden when CardPro component provides its own
pagination/footer functionality, while preserving footer visibility on other pages
that benefit from the global footer navigation.
t0ng7u hace 3 meses
padre
commit
df19a8de5d

+ 1 - 1
web/jsconfig.json

@@ -6,4 +6,4 @@
     }
   },
   "include": ["src/**/*"]
-}
+}

+ 3 - 1
web/src/components/common/modals/TwoFactorAuthModal.jsx

@@ -135,7 +135,9 @@ const TwoFactorAuthModal = ({
             autoFocus
           />
           <Typography.Text type='tertiary' size='small' className='mt-2 block'>
-            {t('支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。')}
+            {t(
+              '支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。',
+            )}
           </Typography.Text>
         </div>
       </div>

+ 13 - 3
web/src/components/layout/PageLayout.jsx

@@ -48,9 +48,19 @@ const PageLayout = () => {
   const { i18n } = useTranslation();
   const location = useLocation();
 
-  const shouldHideFooter =
-    location.pathname.startsWith('/console') ||
-    location.pathname === '/pricing';
+  const cardProPages = [
+    '/console/channel',
+    '/console/log',
+    '/console/redemption',
+    '/console/user',
+    '/console/token',
+    '/console/midjourney',
+    '/console/task',
+    '/console/models',
+    '/pricing',
+  ];
+
+  const shouldHideFooter = cardProPages.includes(location.pathname);
 
   const shouldInnerPadding =
     location.pathname.includes('/console') &&

+ 51 - 23
web/src/components/settings/SystemSetting.jsx

@@ -46,7 +46,6 @@ import { useTranslation } from 'react-i18next';
 const SystemSetting = () => {
   const { t } = useTranslation();
   let [inputs, setInputs] = useState({
-    
     PasswordLoginEnabled: '',
     PasswordRegisterEnabled: '',
     EmailVerificationEnabled: '',
@@ -212,7 +211,9 @@ const SystemSetting = () => {
       setInputs(newInputs);
       setOriginInputs(newInputs);
       // 同步模式布尔到本地状态
-      if (typeof newInputs['fetch_setting.domain_filter_mode'] !== 'undefined') {
+      if (
+        typeof newInputs['fetch_setting.domain_filter_mode'] !== 'undefined'
+      ) {
         setDomainFilterMode(!!newInputs['fetch_setting.domain_filter_mode']);
       }
       if (typeof newInputs['fetch_setting.ip_filter_mode'] !== 'undefined') {
@@ -749,14 +750,17 @@ const SystemSetting = () => {
                         noLabel
                         extraText={t('SSRF防护开关详细说明')}
                         onChange={(e) =>
-                          handleCheckboxChange('fetch_setting.enable_ssrf_protection', e)
+                          handleCheckboxChange(
+                            'fetch_setting.enable_ssrf_protection',
+                            e,
+                          )
                         }
                       >
                         {t('启用SSRF防护(推荐开启以保护服务器安全)')}
                       </Form.Checkbox>
                     </Col>
                   </Row>
-                  
+
                   <Row
                     gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
                     style={{ marginTop: 16 }}
@@ -767,14 +771,19 @@ const SystemSetting = () => {
                         noLabel
                         extraText={t('私有IP访问详细说明')}
                         onChange={(e) =>
-                          handleCheckboxChange('fetch_setting.allow_private_ip', e)
+                          handleCheckboxChange(
+                            'fetch_setting.allow_private_ip',
+                            e,
+                          )
                         }
                       >
-                        {t('允许访问私有IP地址(127.0.0.1、192.168.x.x等内网地址)')}
+                        {t(
+                          '允许访问私有IP地址(127.0.0.1、192.168.x.x等内网地址)',
+                        )}
                       </Form.Checkbox>
                     </Col>
                   </Row>
-                  
+
                   <Row
                     gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
                     style={{ marginTop: 16 }}
@@ -785,7 +794,10 @@ const SystemSetting = () => {
                         noLabel
                         extraText={t('域名IP过滤详细说明')}
                         onChange={(e) =>
-                          handleCheckboxChange('fetch_setting.apply_ip_filter_for_domain', e)
+                          handleCheckboxChange(
+                            'fetch_setting.apply_ip_filter_for_domain',
+                            e,
+                          )
                         }
                         style={{ marginBottom: 8 }}
                       >
@@ -794,17 +806,23 @@ const SystemSetting = () => {
                       <Text strong>
                         {t(domainFilterMode ? '域名白名单' : '域名黑名单')}
                       </Text>
-                      <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
-                        {t('支持通配符格式,如:example.com, *.api.example.com')}
+                      <Text
+                        type='secondary'
+                        style={{ display: 'block', marginBottom: 8 }}
+                      >
+                        {t(
+                          '支持通配符格式,如:example.com, *.api.example.com',
+                        )}
                       </Text>
                       <Radio.Group
                         type='button'
                         value={domainFilterMode ? 'whitelist' : 'blacklist'}
                         onChange={(val) => {
-                          const selected = val && val.target ? val.target.value : val;
+                          const selected =
+                            val && val.target ? val.target.value : val;
                           const isWhitelist = selected === 'whitelist';
                           setDomainFilterMode(isWhitelist);
-                          setInputs(prev => ({
+                          setInputs((prev) => ({
                             ...prev,
                             'fetch_setting.domain_filter_mode': isWhitelist,
                           }));
@@ -819,9 +837,9 @@ const SystemSetting = () => {
                         onChange={(value) => {
                           setDomainList(value);
                           // 触发Form的onChange事件
-                          setInputs(prev => ({
+                          setInputs((prev) => ({
                             ...prev,
-                            'fetch_setting.domain_list': value
+                            'fetch_setting.domain_list': value,
                           }));
                         }}
                         placeholder={t('输入域名后回车,如:example.com')}
@@ -838,17 +856,21 @@ const SystemSetting = () => {
                       <Text strong>
                         {t(ipFilterMode ? 'IP白名单' : 'IP黑名单')}
                       </Text>
-                      <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
+                      <Text
+                        type='secondary'
+                        style={{ display: 'block', marginBottom: 8 }}
+                      >
                         {t('支持CIDR格式,如:8.8.8.8, 192.168.1.0/24')}
                       </Text>
                       <Radio.Group
                         type='button'
                         value={ipFilterMode ? 'whitelist' : 'blacklist'}
                         onChange={(val) => {
-                          const selected = val && val.target ? val.target.value : val;
+                          const selected =
+                            val && val.target ? val.target.value : val;
                           const isWhitelist = selected === 'whitelist';
                           setIpFilterMode(isWhitelist);
-                          setInputs(prev => ({
+                          setInputs((prev) => ({
                             ...prev,
                             'fetch_setting.ip_filter_mode': isWhitelist,
                           }));
@@ -863,9 +885,9 @@ const SystemSetting = () => {
                         onChange={(value) => {
                           setIpList(value);
                           // 触发Form的onChange事件
-                          setInputs(prev => ({
+                          setInputs((prev) => ({
                             ...prev,
-                            'fetch_setting.ip_list': value
+                            'fetch_setting.ip_list': value,
                           }));
                         }}
                         placeholder={t('输入IP地址后回车,如:8.8.8.8')}
@@ -880,7 +902,10 @@ const SystemSetting = () => {
                   >
                     <Col xs={24} sm={24} md={24} lg={24} xl={24}>
                       <Text strong>{t('允许的端口')}</Text>
-                      <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
+                      <Text
+                        type='secondary'
+                        style={{ display: 'block', marginBottom: 8 }}
+                      >
                         {t('支持单个端口和端口范围,如:80, 443, 8000-8999')}
                       </Text>
                       <TagInput
@@ -888,15 +913,18 @@ const SystemSetting = () => {
                         onChange={(value) => {
                           setAllowedPorts(value);
                           // 触发Form的onChange事件
-                          setInputs(prev => ({
+                          setInputs((prev) => ({
                             ...prev,
-                            'fetch_setting.allowed_ports': value
+                            'fetch_setting.allowed_ports': value,
                           }));
                         }}
                         placeholder={t('输入端口后回车,如:80 或 8000-8999')}
                         style={{ width: '100%' }}
                       />
-                      <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
+                      <Text
+                        type='secondary'
+                        style={{ display: 'block', marginBottom: 8 }}
+                      >
                         {t('端口配置详细说明')}
                       </Text>
                     </Col>

+ 8 - 23
web/src/components/table/channels/modals/EditChannelModal.jsx

@@ -91,22 +91,7 @@ const REGION_EXAMPLE = {
 
 // 支持并且已适配通过接口获取模型列表的渠道类型
 const MODEL_FETCHABLE_TYPES = new Set([
-  1,
-  4,
-  14,
-  34,
-  17,
-  26,
-  24,
-  47,
-  25,
-  20,
-  23,
-  31,
-  35,
-  40,
-  42,
-  48,
+  1, 4, 14, 34, 17, 26, 24, 47, 25, 20, 23, 31, 35, 40, 42, 48,
   43,
 ]);
 
@@ -279,8 +264,8 @@ const EditChannelModal = (props) => {
   const scrollToSection = (sectionKey) => {
     const sectionElement = formSectionRefs.current[sectionKey];
     if (sectionElement) {
-      sectionElement.scrollIntoView({ 
-        behavior: 'smooth', 
+      sectionElement.scrollIntoView({
+        behavior: 'smooth',
         block: 'start',
         inline: 'nearest'
       });
@@ -301,7 +286,7 @@ const EditChannelModal = (props) => {
     } else {
       newIndex = currentSectionIndex < availableSections.length - 1 ? currentSectionIndex + 1 : 0;
     }
-    
+
     setCurrentSectionIndex(newIndex);
     scrollToSection(availableSections[newIndex]);
   };
@@ -1340,7 +1325,7 @@ const EditChannelModal = (props) => {
                 type='tertiary'
                 icon={<IconChevronUp />}
                 onClick={() => navigateToSection('up')}
-                style={{ 
+                style={{
                   borderRadius: '50%',
                   width: '32px',
                   height: '32px',
@@ -1356,7 +1341,7 @@ const EditChannelModal = (props) => {
                 type='tertiary'
                 icon={<IconChevronDown />}
                 onClick={() => navigateToSection('down')}
-                style={{ 
+                style={{
                   borderRadius: '50%',
                   width: '32px',
                   height: '32px',
@@ -1398,8 +1383,8 @@ const EditChannelModal = (props) => {
         >
           {() => (
             <Spin spinning={loading}>
-              <div 
-                className='p-2' 
+              <div
+                className='p-2'
                 ref={formContainerRef}
               >
                 <div ref={el => formSectionRefs.current.basicInfo = el}>

+ 2 - 2
web/src/components/table/mj-logs/MjLogsFilters.jsx

@@ -56,10 +56,10 @@ const MjLogsFilters = ({
               showClear
               pure
               size='small'
-              presets={DATE_RANGE_PRESETS.map(preset => ({
+              presets={DATE_RANGE_PRESETS.map((preset) => ({
                 text: t(preset.text),
                 start: preset.start(),
-                end: preset.end()
+                end: preset.end(),
               }))}
             />
           </div>

+ 3 - 2
web/src/components/table/task-logs/TaskLogsColumnDefs.jsx

@@ -36,8 +36,9 @@ import {
 } from 'lucide-react';
 import {
   TASK_ACTION_FIRST_TAIL_GENERATE,
-  TASK_ACTION_GENERATE, TASK_ACTION_REFERENCE_GENERATE,
-  TASK_ACTION_TEXT_GENERATE
+  TASK_ACTION_GENERATE,
+  TASK_ACTION_REFERENCE_GENERATE,
+  TASK_ACTION_TEXT_GENERATE,
 } from '../../../constants/common.constant';
 import { CHANNEL_OPTIONS } from '../../../constants/channel.constants';
 

+ 2 - 2
web/src/components/table/task-logs/TaskLogsFilters.jsx

@@ -56,10 +56,10 @@ const TaskLogsFilters = ({
               showClear
               pure
               size='small'
-              presets={DATE_RANGE_PRESETS.map(preset => ({
+              presets={DATE_RANGE_PRESETS.map((preset) => ({
                 text: t(preset.text),
                 start: preset.start(),
-                end: preset.end()
+                end: preset.end(),
               }))}
             />
           </div>

+ 2 - 2
web/src/components/table/usage-logs/UsageLogsFilters.jsx

@@ -57,10 +57,10 @@ const LogsFilters = ({
               showClear
               pure
               size='small'
-              presets={DATE_RANGE_PRESETS.map(preset => ({
+              presets={DATE_RANGE_PRESETS.map((preset) => ({
                 text: t(preset.text),
                 start: preset.start(),
-                end: preset.end()
+                end: preset.end(),
               }))}
             />
           </div>

+ 57 - 25
web/src/components/topup/RechargeCard.jsx

@@ -30,7 +30,8 @@ import {
   Space,
   Row,
   Col,
-  Spin, Tooltip
+  Spin,
+  Tooltip,
 } from '@douyinfe/semi-ui';
 import { SiAlipay, SiWechat, SiStripe } from 'react-icons/si';
 import { CreditCard, Coins, Wallet, BarChart2, TrendingUp } from 'lucide-react';
@@ -266,7 +267,8 @@ const RechargeCard = ({
                         {payMethods && payMethods.length > 0 ? (
                           <Space wrap>
                             {payMethods.map((payMethod) => {
-                              const minTopupVal = Number(payMethod.min_topup) || 0;
+                              const minTopupVal =
+                                Number(payMethod.min_topup) || 0;
                               const isStripe = payMethod.type === 'stripe';
                               const disabled =
                                 (!enableOnlineTopUp && !isStripe) ||
@@ -280,7 +282,9 @@ const RechargeCard = ({
                                   type='tertiary'
                                   onClick={() => preTopUp(payMethod.type)}
                                   disabled={disabled}
-                                  loading={paymentLoading && payWay === payMethod.type}
+                                  loading={
+                                    paymentLoading && payWay === payMethod.type
+                                  }
                                   icon={
                                     payMethod.type === 'alipay' ? (
                                       <SiAlipay size={18} color='#1677FF' />
@@ -291,7 +295,10 @@ const RechargeCard = ({
                                     ) : (
                                       <CreditCard
                                         size={18}
-                                        color={payMethod.color || 'var(--semi-color-text-2)'}
+                                        color={
+                                          payMethod.color ||
+                                          'var(--semi-color-text-2)'
+                                        }
                                       />
                                     )
                                   }
@@ -301,12 +308,22 @@ const RechargeCard = ({
                                 </Button>
                               );
 
-                              return disabled && minTopupVal > Number(topUpCount || 0) ? (
-                                <Tooltip content={t('此支付方式最低充值金额为') + ' ' + minTopupVal} key={payMethod.type}>
+                              return disabled &&
+                                minTopupVal > Number(topUpCount || 0) ? (
+                                <Tooltip
+                                  content={
+                                    t('此支付方式最低充值金额为') +
+                                    ' ' +
+                                    minTopupVal
+                                  }
+                                  key={payMethod.type}
+                                >
                                   {buttonEl}
                                 </Tooltip>
                               ) : (
-                                <React.Fragment key={payMethod.type}>{buttonEl}</React.Fragment>
+                                <React.Fragment key={payMethod.type}>
+                                  {buttonEl}
+                                </React.Fragment>
                               );
                             })}
                           </Space>
@@ -324,23 +341,27 @@ const RechargeCard = ({
                   <Form.Slot label={t('选择充值额度')}>
                     <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 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;
-                        
+
                         return (
                           <Card
                             key={index}
                             style={{
                               cursor: 'pointer',
-                              border: selectedPreset === preset.value 
-                                ? '2px solid var(--semi-color-primary)' 
-                                : '1px solid var(--semi-color-border)',
+                              border:
+                                selectedPreset === preset.value
+                                  ? '2px solid var(--semi-color-primary)'
+                                  : '1px solid var(--semi-color-border)',
                               height: '100%',
-                              width: '100%'
+                              width: '100%',
                             }}
                             bodyStyle={{ padding: '12px' }}
                             onClick={() => {
@@ -352,24 +373,35 @@ const RechargeCard = ({
                             }}
                           >
                             <div style={{ textAlign: 'center' }}>
-                              <Typography.Title heading={6} style={{ margin: '0 0 8px 0' }}>
+                              <Typography.Title
+                                heading={6}
+                                style={{ margin: '0 0 8px 0' }}
+                              >
                                 <Coins size={18} />
                                 {formatLargeNumber(preset.value)}
                                 {hasDiscount && (
-                                   <Tag style={{ marginLeft: 4 }} color="green">
-                                   {t('折').includes('off') ?
-                                     ((1 - parseFloat(discount)) * 100).toFixed(1) :
-                                     (discount * 10).toFixed(1)}{t('折')}
-                                 </Tag>
+                                  <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' 
-                              }}>
+                              <div
+                                style={{
+                                  color: 'var(--semi-color-text-2)',
+                                  fontSize: '12px',
+                                  margin: '4px 0',
+                                }}
+                              >
                                 {t('实付')} {actualPay.toFixed(2)},
-                                {hasDiscount ? `${t('节省')} ${save.toFixed(2)}` : `${t('节省')} 0.00`}
+                                {hasDiscount
+                                  ? `${t('节省')} ${save.toFixed(2)}`
+                                  : `${t('节省')} 0.00`}
                               </div>
                             </div>
                           </Card>

+ 20 - 11
web/src/components/topup/index.jsx

@@ -80,11 +80,11 @@ const TopUp = () => {
   // 预设充值额度选项
   const [presetAmounts, setPresetAmounts] = useState([]);
   const [selectedPreset, setSelectedPreset] = useState(null);
-  
+
   // 充值配置信息
   const [topupInfo, setTopupInfo] = useState({
     amount_options: [],
-    discount: {}
+    discount: {},
   });
 
   const topUp = async () => {
@@ -262,9 +262,9 @@ const TopUp = () => {
       if (success) {
         setTopupInfo({
           amount_options: data.amount_options || [],
-          discount: data.discount || {}
+          discount: data.discount || {},
         });
-        
+
         // 处理支付方式
         let payMethods = data.pay_methods || [];
         try {
@@ -280,10 +280,15 @@ const TopUp = () => {
             payMethods = payMethods.map((method) => {
               // 规范化最小充值数
               const normalizedMinTopup = Number(method.min_topup);
-              method.min_topup = Number.isFinite(normalizedMinTopup) ? normalizedMinTopup : 0;
+              method.min_topup = Number.isFinite(normalizedMinTopup)
+                ? normalizedMinTopup
+                : 0;
 
               // Stripe 的最小充值从后端字段回填
-              if (method.type === 'stripe' && (!method.min_topup || method.min_topup <= 0)) {
+              if (
+                method.type === 'stripe' &&
+                (!method.min_topup || method.min_topup <= 0)
+              ) {
                 const stripeMin = Number(data.stripe_min_topup);
                 if (Number.isFinite(stripeMin)) {
                   method.min_topup = stripeMin;
@@ -313,7 +318,11 @@ const TopUp = () => {
           setPayMethods(payMethods);
           const enableStripeTopUp = data.enable_stripe_topup || false;
           const enableOnlineTopUp = data.enable_online_topup || false;
-          const minTopUpValue = enableOnlineTopUp? data.min_topup : enableStripeTopUp? data.stripe_min_topup : 1;
+          const minTopUpValue = enableOnlineTopUp
+            ? data.min_topup
+            : enableStripeTopUp
+              ? data.stripe_min_topup
+              : 1;
           setEnableOnlineTopUp(enableOnlineTopUp);
           setEnableStripeTopUp(enableStripeTopUp);
           setMinTopUp(minTopUpValue);
@@ -330,12 +339,12 @@ const TopUp = () => {
           console.log('解析支付方式失败:', e);
           setPayMethods([]);
         }
-        
+
         // 如果有自定义充值数量选项,使用它们替换默认的预设选项
         if (data.amount_options && data.amount_options.length > 0) {
-          const customPresets = data.amount_options.map(amount => ({
+          const customPresets = data.amount_options.map((amount) => ({
             value: amount,
-            discount: data.discount[amount] || 1.0
+            discount: data.discount[amount] || 1.0,
           }));
           setPresetAmounts(customPresets);
         }
@@ -483,7 +492,7 @@ const TopUp = () => {
   const selectPresetAmount = (preset) => {
     setTopUpCount(preset.value);
     setSelectedPreset(preset.value);
-    
+
     // 计算实际支付金额,考虑折扣
     const discount = preset.discount || topupInfo.discount[preset.value] || 1.0;
     const discountedAmount = preset.value * priceRatio * discount;

+ 4 - 3
web/src/components/topup/modals/PaymentConfirmModal.jsx

@@ -40,9 +40,10 @@ const PaymentConfirmModal = ({
   amountNumber,
   discountRate,
 }) => {
-  const hasDiscount = discountRate && discountRate > 0 && discountRate < 1 && amountNumber > 0;
-  const originalAmount = hasDiscount ? (amountNumber / discountRate) : 0;
-  const discountAmount = hasDiscount ? (originalAmount - amountNumber) : 0;
+  const hasDiscount =
+    discountRate && discountRate > 0 && discountRate < 1 && amountNumber > 0;
+  const originalAmount = hasDiscount ? amountNumber / discountRate : 0;
+  const discountAmount = hasDiscount ? originalAmount - amountNumber : 0;
   return (
     <Modal
       title={

+ 5 - 5
web/src/constants/console.constants.js

@@ -24,26 +24,26 @@ export const DATE_RANGE_PRESETS = [
   {
     text: '今天',
     start: () => dayjs().startOf('day').toDate(),
-    end: () => dayjs().endOf('day').toDate()
+    end: () => dayjs().endOf('day').toDate(),
   },
   {
     text: '近 7 天',
     start: () => dayjs().subtract(6, 'day').startOf('day').toDate(),
-    end: () => dayjs().endOf('day').toDate()
+    end: () => dayjs().endOf('day').toDate(),
   },
   {
     text: '本周',
     start: () => dayjs().startOf('week').toDate(),
-    end: () => dayjs().endOf('week').toDate()
+    end: () => dayjs().endOf('week').toDate(),
   },
   {
     text: '近 30 天',
     start: () => dayjs().subtract(29, 'day').startOf('day').toDate(),
-    end: () => dayjs().endOf('day').toDate()
+    end: () => dayjs().endOf('day').toDate(),
   },
   {
     text: '本月',
     start: () => dayjs().startOf('month').toDate(),
-    end: () => dayjs().endOf('month').toDate()
+    end: () => dayjs().endOf('month').toDate(),
   },
 ];

+ 0 - 2
web/src/helpers/api.js

@@ -131,13 +131,11 @@ export const buildApiPayload = (
     seed: 'seed',
   };
 
-
   Object.entries(parameterMappings).forEach(([key, param]) => {
     const enabled = parameterEnabled[key];
     const value = inputs[param];
     const hasValue = value !== undefined && value !== null;
 
-
     if (enabled && hasValue) {
       payload[param] = value;
     }

+ 1 - 1
web/src/helpers/render.jsx

@@ -1074,7 +1074,7 @@ export function renderModelPrice(
       (completionTokens / 1000000) * completionRatioPrice * groupRatio +
       (webSearchCallCount / 1000) * webSearchPrice * groupRatio +
       (fileSearchCallCount / 1000) * fileSearchPrice * groupRatio +
-      (imageGenerationCallPrice * groupRatio);
+      imageGenerationCallPrice * groupRatio;
 
     return (
       <>

+ 4 - 1
web/src/hooks/common/useSidebar.js

@@ -183,7 +183,10 @@ export const useSidebar = () => {
     sidebarEventTarget.addEventListener(SIDEBAR_REFRESH_EVENT, handleRefresh);
 
     return () => {
-      sidebarEventTarget.removeEventListener(SIDEBAR_REFRESH_EVENT, handleRefresh);
+      sidebarEventTarget.removeEventListener(
+        SIDEBAR_REFRESH_EVENT,
+        handleRefresh,
+      );
     };
   }, [adminConfig]);
 

+ 14 - 13
web/src/pages/Setting/Operation/SettingsGeneral.jsx

@@ -130,19 +130,20 @@ export default function GeneralSettings(props) {
                   showClear
                 />
               </Col>
-              {inputs.QuotaPerUnit !== '500000' && inputs.QuotaPerUnit !== 500000 && (
-                <Col xs={24} sm={12} md={8} lg={8} xl={8}>
-                  <Form.Input
-                    field={'QuotaPerUnit'}
-                    label={t('单位美元额度')}
-                    initValue={''}
-                    placeholder={t('一单位货币能兑换的额度')}
-                    onChange={handleFieldChange('QuotaPerUnit')}
-                    showClear
-                    onClick={() => setShowQuotaWarning(true)}
-                  />
-                </Col>
-              )}
+              {inputs.QuotaPerUnit !== '500000' &&
+                inputs.QuotaPerUnit !== 500000 && (
+                  <Col xs={24} sm={12} md={8} lg={8} xl={8}>
+                    <Form.Input
+                      field={'QuotaPerUnit'}
+                      label={t('单位美元额度')}
+                      initValue={''}
+                      placeholder={t('一单位货币能兑换的额度')}
+                      onChange={handleFieldChange('QuotaPerUnit')}
+                      showClear
+                      onClick={() => setShowQuotaWarning(true)}
+                    />
+                  </Col>
+                )}
               <Col xs={24} sm={12} md={8} lg={8} xl={8}>
                 <Form.Input
                   field={'USDExchangeRate'}

+ 2 - 1
web/src/pages/Setting/Operation/SettingsMonitoring.jsx

@@ -128,7 +128,8 @@ export default function SettingsMonitoring(props) {
                   onChange={(value) =>
                     setInputs({
                       ...inputs,
-                      'monitor_setting.auto_test_channel_minutes': parseInt(value),
+                      'monitor_setting.auto_test_channel_minutes':
+                        parseInt(value),
                     })
                   }
                 />

+ 31 - 11
web/src/pages/Setting/Payment/SettingsPaymentGateway.jsx

@@ -118,14 +118,20 @@ export default function SettingsPaymentGateway(props) {
       }
     }
 
-    if (originInputs['AmountOptions'] !== inputs.AmountOptions && inputs.AmountOptions.trim() !== '') {
+    if (
+      originInputs['AmountOptions'] !== inputs.AmountOptions &&
+      inputs.AmountOptions.trim() !== ''
+    ) {
       if (!verifyJSON(inputs.AmountOptions)) {
         showError(t('自定义充值数量选项不是合法的 JSON 数组'));
         return;
       }
     }
 
-    if (originInputs['AmountDiscount'] !== inputs.AmountDiscount && inputs.AmountDiscount.trim() !== '') {
+    if (
+      originInputs['AmountDiscount'] !== inputs.AmountDiscount &&
+      inputs.AmountDiscount.trim() !== ''
+    ) {
       if (!verifyJSON(inputs.AmountDiscount)) {
         showError(t('充值金额折扣配置不是合法的 JSON 对象'));
         return;
@@ -163,10 +169,16 @@ export default function SettingsPaymentGateway(props) {
         options.push({ key: 'PayMethods', value: inputs.PayMethods });
       }
       if (originInputs['AmountOptions'] !== inputs.AmountOptions) {
-        options.push({ key: 'payment_setting.amount_options', value: inputs.AmountOptions });
+        options.push({
+          key: 'payment_setting.amount_options',
+          value: inputs.AmountOptions,
+        });
       }
       if (originInputs['AmountDiscount'] !== inputs.AmountDiscount) {
-        options.push({ key: 'payment_setting.amount_discount', value: inputs.AmountDiscount });
+        options.push({
+          key: 'payment_setting.amount_discount',
+          value: inputs.AmountDiscount,
+        });
       }
 
       // 发送请求
@@ -273,7 +285,7 @@ export default function SettingsPaymentGateway(props) {
             placeholder={t('为一个 JSON 文本')}
             autosize
           />
-          
+
           <Row
             gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
             style={{ marginTop: 16 }}
@@ -282,13 +294,17 @@ export default function SettingsPaymentGateway(props) {
               <Form.TextArea
                 field='AmountOptions'
                 label={t('自定义充值数量选项')}
-                placeholder={t('为一个 JSON 数组,例如:[10, 20, 50, 100, 200, 500]')}
+                placeholder={t(
+                  '为一个 JSON 数组,例如:[10, 20, 50, 100, 200, 500]',
+                )}
                 autosize
-                extraText={t('设置用户可选择的充值数量选项,例如:[10, 20, 50, 100, 200, 500]')}
+                extraText={t(
+                  '设置用户可选择的充值数量选项,例如:[10, 20, 50, 100, 200, 500]',
+                )}
               />
             </Col>
           </Row>
-          
+
           <Row
             gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
             style={{ marginTop: 16 }}
@@ -297,13 +313,17 @@ export default function SettingsPaymentGateway(props) {
               <Form.TextArea
                 field='AmountDiscount'
                 label={t('充值金额折扣配置')}
-                placeholder={t('为一个 JSON 对象,例如:{"100": 0.95, "200": 0.9, "500": 0.85}')}
+                placeholder={t(
+                  '为一个 JSON 对象,例如:{"100": 0.95, "200": 0.9, "500": 0.85}',
+                )}
                 autosize
-                extraText={t('设置不同充值金额对应的折扣,键为充值金额,值为折扣率,例如:{"100": 0.95, "200": 0.9, "500": 0.85}')}
+                extraText={t(
+                  '设置不同充值金额对应的折扣,键为充值金额,值为折扣率,例如:{"100": 0.95, "200": 0.9, "500": 0.85}',
+                )}
               />
             </Col>
           </Row>
-          
+
           <Button onClick={submitPayAddress}>{t('更新支付设置')}</Button>
         </Form.Section>
       </Form>

+ 17 - 11
web/src/pages/Setting/Ratio/ModelRatioSettings.jsx

@@ -226,8 +226,12 @@ export default function ModelRatioSettings(props) {
           <Col xs={24} sm={16}>
             <Form.TextArea
               label={t('图片输入倍率(仅部分模型支持该计费)')}
-              extraText={t('图片输入相关的倍率设置,键为模型名称,值为倍率,仅部分模型支持该计费')}
-              placeholder={t('为一个 JSON 文本,键为模型名称,值为倍率,例如:{"gpt-image-1": 2}')}
+              extraText={t(
+                '图片输入相关的倍率设置,键为模型名称,值为倍率,仅部分模型支持该计费',
+              )}
+              placeholder={t(
+                '为一个 JSON 文本,键为模型名称,值为倍率,例如:{"gpt-image-1": 2}',
+              )}
               field={'ImageRatio'}
               autosize={{ minRows: 6, maxRows: 12 }}
               trigger='blur'
@@ -238,9 +242,7 @@ export default function ModelRatioSettings(props) {
                   message: '不是合法的 JSON 字符串',
                 },
               ]}
-              onChange={(value) =>
-                setInputs({ ...inputs, ImageRatio: value })
-              }
+              onChange={(value) => setInputs({ ...inputs, ImageRatio: value })}
             />
           </Col>
         </Row>
@@ -249,7 +251,9 @@ export default function ModelRatioSettings(props) {
             <Form.TextArea
               label={t('音频倍率(仅部分模型支持该计费)')}
               extraText={t('音频输入相关的倍率设置,键为模型名称,值为倍率')}
-              placeholder={t('为一个 JSON 文本,键为模型名称,值为倍率,例如:{"gpt-4o-audio-preview": 16}')}
+              placeholder={t(
+                '为一个 JSON 文本,键为模型名称,值为倍率,例如:{"gpt-4o-audio-preview": 16}',
+              )}
               field={'AudioRatio'}
               autosize={{ minRows: 6, maxRows: 12 }}
               trigger='blur'
@@ -260,9 +264,7 @@ export default function ModelRatioSettings(props) {
                   message: '不是合法的 JSON 字符串',
                 },
               ]}
-              onChange={(value) =>
-                setInputs({ ...inputs, AudioRatio: value })
-              }
+              onChange={(value) => setInputs({ ...inputs, AudioRatio: value })}
             />
           </Col>
         </Row>
@@ -270,8 +272,12 @@ export default function ModelRatioSettings(props) {
           <Col xs={24} sm={16}>
             <Form.TextArea
               label={t('音频补全倍率(仅部分模型支持该计费)')}
-              extraText={t('音频输出补全相关的倍率设置,键为模型名称,值为倍率')}
-              placeholder={t('为一个 JSON 文本,键为模型名称,值为倍率,例如:{"gpt-4o-realtime": 2}')}
+              extraText={t(
+                '音频输出补全相关的倍率设置,键为模型名称,值为倍率',
+              )}
+              placeholder={t(
+                '为一个 JSON 文本,键为模型名称,值为倍率,例如:{"gpt-4o-realtime": 2}',
+              )}
               field={'AudioCompletionRatio'}
               autosize={{ minRows: 6, maxRows: 12 }}
               trigger='blur'