🔧 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.
This commit is contained in:
@@ -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%' }}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
</div>
|
||||
), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText]);
|
||||
|
||||
return SearchAndActions;
|
||||
{/* 移动端筛选按钮 */}
|
||||
{isMobile && (
|
||||
<Button
|
||||
theme="outline"
|
||||
type='tertiary'
|
||||
icon={<IconFilter />}
|
||||
onClick={() => setShowFilterModal(true)}
|
||||
>
|
||||
{t('筛选')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText, isMobile]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{SearchAndActions}
|
||||
|
||||
{/* 移动端筛选Modal */}
|
||||
{isMobile && (
|
||||
<PricingFilterModal
|
||||
visible={showFilterModal}
|
||||
onClose={() => setShowFilterModal(false)}
|
||||
sidebarProps={sidebarProps}
|
||||
t={t}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PricingSearchBar;
|
||||
@@ -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} />
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
IllustrationNoResult,
|
||||
IllustrationNoResultDark
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import { getPricingTableColumns } from './PricingTableColumns.js';
|
||||
import { getPricingTableColumns } from './PricingTableColumns';
|
||||
|
||||
const PricingTable = ({
|
||||
filteredModels,
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
* 分组筛选组件
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
* 计费类型筛选组件
|
||||
@@ -18,4 +18,4 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
// 为了向后兼容,这里重新导出新的 PricingPage 组件
|
||||
export { default } from './PricingPage.jsx';
|
||||
export { default } from './PricingPage';
|
||||
@@ -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;
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user