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

🔧 refactor(pricing-filters): extract display settings & improve mobile layout (#1365)

* **PricingDisplaySettings.jsx**
  • Extracted display settings (recharge price, currency, ratio toggle) from PricingSidebar
  • Maintains complete styling and functionality as standalone component

* **SelectableButtonGroup.jsx**
  • Added isMobile detection with conditional Col spans
  • Mobile: `span={12}` (2 buttons per row) for better touch experience
  • Desktop: preserved responsive grid `xs={24} sm={24} md={24} lg={12} xl={8}`

* **PricingSidebar.jsx**
  • Updated imports to use new PricingDisplaySettings component
  • Simplified component structure while preserving reset logic

These changes enhance code modularity and provide optimized mobile UX for filter button groups across the pricing interface.
t0ng7u 7 месяцев назад
Родитель
Сommit
c15e753a0a

+ 10 - 2
web/src/components/common/ui/SelectableButtonGroup.jsx

@@ -18,6 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 import React, { useState, useRef } from 'react';
+import { useIsMobile } from '../../../hooks/common/useIsMobile';
 import { Divider, Button, Tag, Row, Col, Collapsible } from '@douyinfe/semi-ui';
 import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons';
 
@@ -44,6 +45,7 @@ const SelectableButtonGroup = ({
   collapseHeight = 200
 }) => {
   const [isOpen, setIsOpen] = useState(false);
+  const isMobile = useIsMobile();
   const perRow = 3;
   const maxVisibleRows = Math.max(1, Math.floor(collapseHeight / 32)); // Approx row height 32
   const needCollapse = collapsible && items.length > perRow * maxVisibleRows;
@@ -82,10 +84,16 @@ const SelectableButtonGroup = ({
       {items.map((item) => {
         const isActive = activeValue === item.value;
         return (
-          <Col xs={24} sm={24} md={24} lg={12} xl={8} key={item.value}>
+          <Col
+            {...(isMobile
+              ? { span: 12 }
+              : { xs: 24, sm: 24, md: 24, lg: 12, xl: 8 }
+            )}
+            key={item.value}
+          >
             <Button
               onClick={() => onChange(item.value)}
-              theme={isActive ? 'solid' : 'outline'}
+              theme={isActive ? 'light' : 'outline'}
               type={isActive ? 'primary' : 'tertiary'}
               icon={item.icon}
               style={{ width: '100%' }}

+ 15 - 6
web/src/components/table/model-pricing/PricingContent.jsx

@@ -18,12 +18,20 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 import React from 'react';
-import PricingSearchBar from './PricingSearchBar.jsx';
-import PricingTable from './PricingTable.jsx';
+import PricingSearchBar from './PricingSearchBar';
+import PricingTable from './PricingTable';
 
-const PricingContent = (props) => {
+const PricingContent = ({ isMobile, sidebarProps, ...props }) => {
   return (
-    <div className="pricing-scroll-hide">
+    <div
+      className={isMobile ? "" : "pricing-scroll-hide"}
+      style={isMobile ? {
+        height: '100%',
+        display: 'flex',
+        flexDirection: 'column',
+        overflow: 'auto'
+      } : {}}
+    >
       {/* 固定的搜索和操作区域 */}
       <div
         style={{
@@ -36,14 +44,15 @@ const PricingContent = (props) => {
           zIndex: 5,
         }}
       >
-        <PricingSearchBar {...props} />
+        <PricingSearchBar {...props} isMobile={isMobile} sidebarProps={sidebarProps} />
       </div>
 
       {/* 可滚动的内容区域 */}
       <div
         style={{
           flex: 1,
-          overflow: 'auto'
+          overflow: 'auto',
+          ...(isMobile && { minHeight: 0 })
         }}
       >
         <PricingTable {...props} />

+ 26 - 17
web/src/components/table/model-pricing/PricingPage.jsx

@@ -19,13 +19,15 @@ For commercial licensing, please contact support@quantumnous.com
 
 import React from 'react';
 import { Layout, ImagePreview } from '@douyinfe/semi-ui';
-import PricingSidebar from './PricingSidebar.jsx';
-import PricingContent from './PricingContent.jsx';
-import { useModelPricingData } from '../../../hooks/model-pricing/useModelPricingData.js';
+import PricingSidebar from './PricingSidebar';
+import PricingContent from './PricingContent';
+import { useModelPricingData } from '../../../hooks/model-pricing/useModelPricingData';
+import { useIsMobile } from '../../../hooks/common/useIsMobile';
 
 const PricingPage = () => {
   const pricingData = useModelPricingData();
   const { Sider, Content } = Layout;
+  const isMobile = useIsMobile();
 
   // 显示倍率状态
   const [showRatio, setShowRatio] = React.useState(false);
@@ -33,19 +35,21 @@ const PricingPage = () => {
   return (
     <div className="bg-white">
       <Layout style={{ height: 'calc(100vh - 60px)', overflow: 'hidden', marginTop: '60px' }}>
-        {/* 左侧边栏 */}
-        <Sider
-          className="pricing-scroll-hide"
-          style={{
-            width: 460,
-            height: 'calc(100vh - 60px)',
-            backgroundColor: 'var(--semi-color-bg-0)',
-            borderRight: '1px solid var(--semi-color-border)',
-            overflow: 'auto'
-          }}
-        >
-          <PricingSidebar {...pricingData} showRatio={showRatio} setShowRatio={setShowRatio} />
-        </Sider>
+        {/* 左侧边栏 - 只在桌面端显示 */}
+        {!isMobile && (
+          <Sider
+            className="pricing-scroll-hide"
+            style={{
+              width: 460,
+              height: 'calc(100vh - 60px)',
+              backgroundColor: 'var(--semi-color-bg-0)',
+              borderRight: '1px solid var(--semi-color-border)',
+              overflow: 'auto'
+            }}
+          >
+            <PricingSidebar {...pricingData} showRatio={showRatio} setShowRatio={setShowRatio} />
+          </Sider>
+        )}
 
         {/* 右侧内容区 */}
         <Content
@@ -57,7 +61,12 @@ const PricingPage = () => {
             flexDirection: 'column'
           }}
         >
-          <PricingContent {...pricingData} showRatio={showRatio} />
+          <PricingContent 
+            {...pricingData} 
+            showRatio={showRatio} 
+            isMobile={isMobile}
+            sidebarProps={{ ...pricingData, showRatio, setShowRatio }}
+          />
         </Content>
       </Layout>
 

+ 37 - 6
web/src/components/table/model-pricing/PricingSearchBar.jsx

@@ -17,9 +17,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact support@quantumnous.com
 */
 
-import React, { useMemo } from 'react';
+import React, { useMemo, useState } from 'react';
 import { Input, Button } from '@douyinfe/semi-ui';
-import { IconSearch, IconCopy } from '@douyinfe/semi-icons';
+import { IconSearch, IconCopy, IconFilter } from '@douyinfe/semi-icons';
+import PricingFilterModal from './modal/PricingFilterModal';
 
 const PricingSearchBar = ({
   selectedRowKeys,
@@ -27,8 +28,12 @@ const PricingSearchBar = ({
   handleChange,
   handleCompositionStart,
   handleCompositionEnd,
+  isMobile,
+  sidebarProps,
   t
 }) => {
+  const [showFilterModal, setShowFilterModal] = useState(false);
+
   const SearchAndActions = useMemo(() => (
     <div className="flex items-center gap-4 w-full">
       {/* 搜索框 */}
@@ -45,19 +50,45 @@ const PricingSearchBar = ({
 
       {/* 操作按钮 */}
       <Button
-        theme='light'
+        theme='outline'
         type='primary'
         icon={<IconCopy />}
         onClick={() => copyText(selectedRowKeys)}
         disabled={selectedRowKeys.length === 0}
         className="!bg-blue-500 hover:!bg-blue-600 text-white"
       >
-        {t('复制选中模型')}
+        {t('复制')}
       </Button>
+
+      {/* 移动端筛选按钮 */}
+      {isMobile && (
+        <Button
+          theme="outline"
+          type='tertiary'
+          icon={<IconFilter />}
+          onClick={() => setShowFilterModal(true)}
+        >
+          {t('筛选')}
+        </Button>
+      )}
     </div>
-  ), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText]);
+  ), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText, isMobile]);
 
-  return SearchAndActions;
+  return (
+    <>
+      {SearchAndActions}
+
+      {/* 移动端筛选Modal */}
+      {isMobile && (
+        <PricingFilterModal
+          visible={showFilterModal}
+          onClose={() => setShowFilterModal(false)}
+          sidebarProps={sidebarProps}
+          t={t}
+        />
+      )}
+    </>
+  );
 };
 
 export default PricingSearchBar; 

+ 15 - 53
web/src/components/table/model-pricing/PricingSidebar.jsx

@@ -18,11 +18,11 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 import React from 'react';
-import { Divider, Button, Switch, Select, Tooltip } from '@douyinfe/semi-ui';
-import { IconHelpCircle } from '@douyinfe/semi-icons';
-import PricingCategories from './sidebar/PricingCategories.jsx';
-import PricingGroups from './sidebar/PricingGroups.jsx';
-import PricingQuotaTypes from './sidebar/PricingQuotaTypes.jsx';
+import { Button } from '@douyinfe/semi-ui';
+import PricingCategories from './filter/PricingCategories';
+import PricingGroups from './filter/PricingGroups';
+import PricingQuotaTypes from './filter/PricingQuotaTypes';
+import PricingDisplaySettings from './filter/PricingDisplaySettings';
 
 const PricingSidebar = ({
   showWithRecharge,
@@ -79,13 +79,13 @@ const PricingSidebar = ({
 
   return (
     <div className="p-4">
-      {/* 筛选标题和重置按钮 */}
       <div className="flex items-center justify-between mb-6">
         <div className="text-lg font-semibold text-gray-800">
           {t('筛选')}
         </div>
         <Button
           theme="outline"
+          type='tertiary'
           onClick={handleResetFilters}
           className="text-gray-500 hover:text-gray-700"
         >
@@ -93,54 +93,16 @@ const PricingSidebar = ({
         </Button>
       </div>
 
-      {/* 显示设置 */}
-      <div className="mb-6">
-        <Divider margin='12px' align='left'>
-          {t('显示设置')}
-        </Divider>
-        <div className="px-2">
-          <div className="flex items-center justify-between mb-3">
-            <span className="text-sm text-gray-700">{t('以充值价格显示')}</span>
-            <Switch
-              checked={showWithRecharge}
-              onChange={setShowWithRecharge}
-              size="small"
-            />
-          </div>
-          {showWithRecharge && (
-            <div className="mt-2 mb-3">
-              <div className="text-xs text-gray-500 mb-1">{t('货币单位')}</div>
-              <Select
-                value={currency}
-                onChange={setCurrency}
-                size="small"
-                className="w-full"
-              >
-                <Select.Option value="USD">USD ($)</Select.Option>
-                <Select.Option value="CNY">CNY (¥)</Select.Option>
-              </Select>
-            </div>
-          )}
-          <div className="flex items-center justify-between">
-            <div className="flex items-center gap-1">
-              <span className="text-sm text-gray-700">{t('显示倍率')}</span>
-              <Tooltip content={t('倍率是用于系统计算不同模型的最终价格用的,如果您不理解倍率,请忽略')}>
-                <IconHelpCircle
-                  size="small"
-                  style={{ color: 'var(--semi-color-text-2)', cursor: 'help' }}
-                />
-              </Tooltip>
-            </div>
-            <Switch
-              checked={showRatio}
-              onChange={setShowRatio}
-              size="small"
-            />
-          </div>
-        </div>
-      </div>
+      <PricingDisplaySettings
+        showWithRecharge={showWithRecharge}
+        setShowWithRecharge={setShowWithRecharge}
+        currency={currency}
+        setCurrency={setCurrency}
+        showRatio={showRatio}
+        setShowRatio={setShowRatio}
+        t={t}
+      />
 
-      {/* 模型分类 */}
       <PricingCategories {...categoryProps} setActiveKey={setActiveKey} t={t} />
 
       <PricingGroups filterGroup={filterGroup} setFilterGroup={setFilterGroup} usableGroup={categoryProps.usableGroup} models={categoryProps.models} t={t} />

+ 1 - 1
web/src/components/table/model-pricing/PricingTable.jsx

@@ -23,7 +23,7 @@ import {
   IllustrationNoResult,
   IllustrationNoResultDark
 } from '@douyinfe/semi-illustrations';
-import { getPricingTableColumns } from './PricingTableColumns.js';
+import { getPricingTableColumns } from './PricingTableColumns';
 
 const PricingTable = ({
   filteredModels,

+ 1 - 1
web/src/components/table/model-pricing/sidebar/PricingCategories.jsx → web/src/components/table/model-pricing/filter/PricingCategories.jsx

@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 import React from 'react';
-import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup.jsx';
+import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup';
 
 const PricingCategories = ({ activeKey, setActiveKey, modelCategories, categoryCounts, availableCategories, t }) => {
   const items = Object.entries(modelCategories)

+ 82 - 0
web/src/components/table/model-pricing/filter/PricingDisplaySettings.jsx

@@ -0,0 +1,82 @@
+/*
+Copyright (C) 2025 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+
+import React from 'react';
+import { Divider, Switch, Select, Tooltip } from '@douyinfe/semi-ui';
+import { IconHelpCircle } from '@douyinfe/semi-icons';
+
+const PricingDisplaySettings = ({
+  showWithRecharge,
+  setShowWithRecharge,
+  currency,
+  setCurrency,
+  showRatio,
+  setShowRatio,
+  t
+}) => {
+  return (
+    <div className="mb-6">
+      <Divider margin='12px' align='left'>
+        {t('显示设置')}
+      </Divider>
+      <div className="px-2">
+        <div className="flex items-center justify-between mb-3">
+          <span className="text-sm text-gray-700">{t('以充值价格显示')}</span>
+          <Switch
+            checked={showWithRecharge}
+            onChange={setShowWithRecharge}
+            size="small"
+          />
+        </div>
+        {showWithRecharge && (
+          <div className="mt-2 mb-3">
+            <div className="text-xs text-gray-500 mb-1">{t('货币单位')}</div>
+            <Select
+              value={currency}
+              onChange={setCurrency}
+              size="small"
+              className="w-full"
+            >
+              <Select.Option value="USD">USD ($)</Select.Option>
+              <Select.Option value="CNY">CNY (¥)</Select.Option>
+            </Select>
+          </div>
+        )}
+        <div className="flex items-center justify-between">
+          <div className="flex items-center gap-1">
+            <span className="text-sm text-gray-700">{t('显示倍率')}</span>
+            <Tooltip content={t('倍率是用于系统计算不同模型的最终价格用的,如果您不理解倍率,请忽略')}>
+              <IconHelpCircle
+                size="small"
+                style={{ color: 'var(--semi-color-text-2)', cursor: 'help' }}
+              />
+            </Tooltip>
+          </div>
+          <Switch
+            checked={showRatio}
+            onChange={setShowRatio}
+            size="small"
+          />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default PricingDisplaySettings; 

+ 1 - 1
web/src/components/table/model-pricing/sidebar/PricingGroups.jsx → web/src/components/table/model-pricing/filter/PricingGroups.jsx

@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 import React from 'react';
-import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup.jsx';
+import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup';
 
 /**
  * 分组筛选组件

+ 1 - 1
web/src/components/table/model-pricing/sidebar/PricingQuotaTypes.jsx → web/src/components/table/model-pricing/filter/PricingQuotaTypes.jsx

@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 import React from 'react';
-import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup.jsx';
+import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup';
 
 /**
  * 计费类型筛选组件

+ 1 - 1
web/src/components/table/model-pricing/index.jsx

@@ -18,4 +18,4 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 // 为了向后兼容,这里重新导出新的 PricingPage 组件
-export { default } from './PricingPage.jsx'; 
+export { default } from './PricingPage'; 

+ 48 - 0
web/src/components/table/model-pricing/modal/PricingFilterModal.jsx

@@ -0,0 +1,48 @@
+/*
+Copyright (C) 2025 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+
+import React from 'react';
+import { Modal } from '@douyinfe/semi-ui';
+import PricingSidebar from '../PricingSidebar';
+
+const PricingFilterModal = ({
+  visible,
+  onClose,
+  sidebarProps,
+  t
+}) => {
+  return (
+    <Modal
+      title={t('筛选')}
+      visible={visible}
+      onCancel={onClose}
+      footer={null}
+      style={{ width: '100%', height: '100%', margin: 0 }}
+      bodyStyle={{ 
+        padding: 0, 
+        height: 'calc(100vh - 110px)', 
+        overflow: 'auto' 
+      }}
+    >
+      <PricingSidebar {...sidebarProps} />
+    </Modal>
+  );
+};
+
+export default PricingFilterModal; 

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

@@ -699,7 +699,6 @@
   "个": "indivual",
   "倍率是本站的计算方式,不同模型有着不同的倍率,并非官方价格的多少倍,请务必知晓。": "The magnification is the calculation method of this website. Different models have different magnifications, which are not multiples of the official price. Please be sure to know.",
   "所有各厂聊天模型请统一使用OpenAI方式请求,支持OpenAI官方库<br/>Claude()Claude官方格式请求": "Please use the OpenAI method to request all chat models from each factory, and support the OpenAI official library<br/>Claude()Claude official format request",
-  "复制选中模型": "Copy selected model",
   "分组说明": "Group description",
   "倍率是为了方便换算不同价格的模型": "The magnification is to facilitate the conversion of models with different prices.",
   "点击查看倍率说明": "Click to view the magnification description",