🎨 feat(model-pricing): refactor layout and component structure (#1365)
* Re-architected model-pricing page into modular components: * PricingPage / PricingSidebar / PricingContent * Removed obsolete `ModelPricing*` components and column defs * Introduced reusable `SelectableButtonGroup` in `common/ui` * Supports Row/Col grid (3 per row) * Optional collapsible mode with gradient mask & toggle * Rebuilt filter panels with the new button-group: * Model categories, token groups, and quota types * Added dynamic `tagCount` badges to display item totals * Extended `useModelPricingData` hook * Added `filterGroup` and `filterQuotaType` state and logic * Updated PricingTable columns & sidebar reset logic to respect new states * Ensured backward compatibility via re-export in `index.jsx` * Polished styling, icons and i18n keys
This commit is contained in:
147
web/src/components/common/ui/SelectableButtonGroup.jsx
Normal file
147
web/src/components/common/ui/SelectableButtonGroup.jsx
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
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, { useState, useRef } from 'react';
|
||||
import { Divider, Button, Tag, Row, Col, Collapsible } from '@douyinfe/semi-ui';
|
||||
import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons';
|
||||
|
||||
/**
|
||||
* 通用可选择按钮组组件
|
||||
*
|
||||
* @param {string} title 标题
|
||||
* @param {Array<{value:any,label:string,icon?:React.ReactNode,tagCount?:number}>} items 按钮项
|
||||
* @param {*} activeValue 当前激活的值
|
||||
* @param {(value:any)=>void} onChange 选择改变回调
|
||||
* @param {function} t i18n
|
||||
* @param {object} style 额外样式
|
||||
* @param {boolean} collapsible 是否支持折叠,默认true
|
||||
* @param {number} collapseHeight 折叠时的高度,默认200
|
||||
*/
|
||||
const SelectableButtonGroup = ({
|
||||
title,
|
||||
items = [],
|
||||
activeValue,
|
||||
onChange,
|
||||
t = (v) => v,
|
||||
style = {},
|
||||
collapsible = true,
|
||||
collapseHeight = 200
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const perRow = 3;
|
||||
const maxVisibleRows = Math.max(1, Math.floor(collapseHeight / 32)); // Approx row height 32
|
||||
const needCollapse = collapsible && items.length > perRow * maxVisibleRows;
|
||||
|
||||
const contentRef = useRef(null);
|
||||
|
||||
const maskStyle = isOpen
|
||||
? {}
|
||||
: {
|
||||
WebkitMaskImage:
|
||||
'linear-gradient(to bottom, black 0%, rgba(0, 0, 0, 1) 60%, rgba(0, 0, 0, 0.2) 80%, transparent 100%)',
|
||||
};
|
||||
|
||||
const toggle = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const linkStyle = {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
textAlign: 'center',
|
||||
bottom: -10,
|
||||
fontWeight: 400,
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px',
|
||||
color: 'var(--semi-color-text-2)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 4,
|
||||
};
|
||||
|
||||
const contentElement = (
|
||||
<Row gutter={[8, 8]} style={{ lineHeight: '32px', ...style }} ref={contentRef}>
|
||||
{items.map((item) => {
|
||||
const isActive = activeValue === item.value;
|
||||
return (
|
||||
<Col span={8} key={item.value}>
|
||||
<Button
|
||||
onClick={() => onChange(item.value)}
|
||||
theme={isActive ? 'solid' : 'outline'}
|
||||
type={isActive ? 'primary' : 'tertiary'}
|
||||
icon={item.icon}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<span style={{ marginRight: item.tagCount !== undefined ? 4 : 0 }}>{item.label}</span>
|
||||
{item.tagCount !== undefined && (
|
||||
<Tag
|
||||
color='white'
|
||||
shape="circle"
|
||||
size="small"
|
||||
>
|
||||
{item.tagCount}
|
||||
</Tag>
|
||||
)}
|
||||
</Button>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
{title && (
|
||||
<Divider margin="12px" align="left">
|
||||
{title}
|
||||
</Divider>
|
||||
)}
|
||||
{needCollapse ? (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Collapsible isOpen={isOpen} collapseHeight={collapseHeight} style={{ ...maskStyle }}>
|
||||
{contentElement}
|
||||
</Collapsible>
|
||||
{isOpen ? null : (
|
||||
<div onClick={toggle} style={{ ...linkStyle }}>
|
||||
<IconChevronDown size="small" />
|
||||
<span>{t('展开更多')}</span>
|
||||
</div>
|
||||
)}
|
||||
{isOpen && (
|
||||
<div onClick={toggle} style={{
|
||||
...linkStyle,
|
||||
position: 'static',
|
||||
marginTop: 8,
|
||||
bottom: 'auto'
|
||||
}}>
|
||||
<IconChevronUp size="small" />
|
||||
<span>{t('收起')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
contentElement
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectableButtonGroup;
|
||||
@@ -467,7 +467,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="text-semi-color-text-0 sticky top-0 z-50 transition-colors duration-300 bg-white/75 dark:bg-zinc-900/75 backdrop-blur-lg">
|
||||
<header className="text-semi-color-text-0 sticky top-0 z-50 transition-colors duration-300 bg-white/75 dark:bg-zinc-900/75 backdrop-blur-lg" style={{ borderBottom: '1px solid var(--semi-color-border)' }}>
|
||||
<NoticeModal
|
||||
visible={noticeVisible}
|
||||
onClose={handleNoticeClose}
|
||||
|
||||
@@ -42,7 +42,7 @@ const PageLayout = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
const shouldHideFooter = location.pathname.startsWith('/console');
|
||||
const shouldHideFooter = location.pathname.startsWith('/console') || location.pathname === '/pricing';
|
||||
|
||||
const shouldInnerPadding = location.pathname.includes('/console') &&
|
||||
!location.pathname.startsWith('/console/chat') &&
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
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, { useMemo } from 'react';
|
||||
import { Card, Input, Button, Space, Switch, Select } from '@douyinfe/semi-ui';
|
||||
import { IconSearch, IconCopy } from '@douyinfe/semi-icons';
|
||||
|
||||
const ModelPricingFilters = ({
|
||||
selectedRowKeys,
|
||||
copyText,
|
||||
showWithRecharge,
|
||||
setShowWithRecharge,
|
||||
currency,
|
||||
setCurrency,
|
||||
handleChange,
|
||||
handleCompositionStart,
|
||||
handleCompositionEnd,
|
||||
t
|
||||
}) => {
|
||||
const SearchAndActions = useMemo(() => (
|
||||
<Card className="!rounded-xl mb-6" bordered={false}>
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<div className="flex-1 min-w-[200px]">
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('模糊搜索模型名称')}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
onChange={handleChange}
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
theme='light'
|
||||
type='primary'
|
||||
icon={<IconCopy />}
|
||||
onClick={() => copyText(selectedRowKeys)}
|
||||
disabled={selectedRowKeys.length === 0}
|
||||
className="!bg-blue-500 hover:!bg-blue-600 text-white"
|
||||
>
|
||||
{t('复制选中模型')}
|
||||
</Button>
|
||||
|
||||
{/* 充值价格显示开关 */}
|
||||
<Space align="center">
|
||||
<span>{t('以充值价格显示')}</span>
|
||||
<Switch
|
||||
checked={showWithRecharge}
|
||||
onChange={setShowWithRecharge}
|
||||
size="small"
|
||||
/>
|
||||
{showWithRecharge && (
|
||||
<Select
|
||||
value={currency}
|
||||
onChange={setCurrency}
|
||||
size="small"
|
||||
style={{ width: 100 }}
|
||||
>
|
||||
<Select.Option value="USD">USD ($)</Select.Option>
|
||||
<Select.Option value="CNY">CNY (¥)</Select.Option>
|
||||
</Select>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
</Card>
|
||||
), [selectedRowKeys, t, showWithRecharge, currency, handleCompositionStart, handleCompositionEnd, handleChange, copyText, setShowWithRecharge, setCurrency]);
|
||||
|
||||
return SearchAndActions;
|
||||
};
|
||||
|
||||
export default ModelPricingFilters;
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
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 { Tabs, TabPane, Tag } from '@douyinfe/semi-ui';
|
||||
|
||||
const ModelPricingTabs = ({
|
||||
activeKey,
|
||||
setActiveKey,
|
||||
modelCategories,
|
||||
categoryCounts,
|
||||
availableCategories,
|
||||
t
|
||||
}) => {
|
||||
return (
|
||||
<Tabs
|
||||
activeKey={activeKey}
|
||||
type="card"
|
||||
collapsible
|
||||
onChange={key => setActiveKey(key)}
|
||||
className="mt-2"
|
||||
>
|
||||
{Object.entries(modelCategories)
|
||||
.filter(([key]) => availableCategories.includes(key))
|
||||
.map(([key, category]) => {
|
||||
const modelCount = categoryCounts[key] || 0;
|
||||
|
||||
return (
|
||||
<TabPane
|
||||
tab={
|
||||
<span className="flex items-center gap-2">
|
||||
{category.icon && <span className="w-4 h-4">{category.icon}</span>}
|
||||
{category.label}
|
||||
<Tag
|
||||
color={activeKey === key ? 'red' : 'grey'}
|
||||
shape='circle'
|
||||
>
|
||||
{modelCount}
|
||||
</Tag>
|
||||
</span>
|
||||
}
|
||||
itemKey={key}
|
||||
key={key}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelPricingTabs;
|
||||
52
web/src/components/table/model-pricing/PricingContent.jsx
Normal file
52
web/src/components/table/model-pricing/PricingContent.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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 PricingSearchBar from './PricingSearchBar.jsx';
|
||||
import PricingTable from './PricingTable.jsx';
|
||||
|
||||
const PricingContent = (props) => {
|
||||
return (
|
||||
<>
|
||||
{/* 固定的搜索和操作区域 */}
|
||||
<div
|
||||
style={{
|
||||
padding: '16px 24px',
|
||||
borderBottom: '1px solid var(--semi-color-border)',
|
||||
backgroundColor: 'var(--semi-color-bg-0)',
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
<PricingSearchBar {...props} />
|
||||
</div>
|
||||
|
||||
{/* 可滚动的内容区域 */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<PricingTable {...props} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PricingContent;
|
||||
@@ -22,7 +22,7 @@ import { Card } from '@douyinfe/semi-ui';
|
||||
import { IconVerify, IconLayers, IconInfoCircle } from '@douyinfe/semi-icons';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
|
||||
const ModelPricingHeader = ({
|
||||
const PricingHeader = ({
|
||||
userState,
|
||||
groupRatio,
|
||||
selectedGroup,
|
||||
@@ -120,4 +120,4 @@ const ModelPricingHeader = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelPricingHeader;
|
||||
export default PricingHeader;
|
||||
72
web/src/components/table/model-pricing/PricingPage.jsx
Normal file
72
web/src/components/table/model-pricing/PricingPage.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
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 { Layout, ImagePreview } from '@douyinfe/semi-ui';
|
||||
import PricingSidebar from './PricingSidebar.jsx';
|
||||
import PricingContent from './PricingContent.jsx';
|
||||
import { useModelPricingData } from '../../../hooks/model-pricing/useModelPricingData.js';
|
||||
|
||||
const PricingPage = () => {
|
||||
const pricingData = useModelPricingData();
|
||||
const { Sider, Content } = Layout;
|
||||
|
||||
// 显示倍率状态
|
||||
const [showRatio, setShowRatio] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<Layout style={{ height: 'calc(100vh - 60px)', overflow: 'hidden', marginTop: '60px' }}>
|
||||
{/* 左侧边栏 */}
|
||||
<Sider
|
||||
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
|
||||
style={{
|
||||
height: 'calc(100vh - 60px)',
|
||||
backgroundColor: 'var(--semi-color-bg-0)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<PricingContent {...pricingData} showRatio={showRatio} />
|
||||
</Content>
|
||||
</Layout>
|
||||
|
||||
{/* 倍率说明图预览 */}
|
||||
<ImagePreview
|
||||
src={pricingData.modalImageUrl}
|
||||
visible={pricingData.isModalOpenurl}
|
||||
onVisibleChange={(visible) => pricingData.setIsModalOpenurl(visible)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PricingPage;
|
||||
63
web/src/components/table/model-pricing/PricingSearchBar.jsx
Normal file
63
web/src/components/table/model-pricing/PricingSearchBar.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
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, { useMemo } from 'react';
|
||||
import { Input, Button } from '@douyinfe/semi-ui';
|
||||
import { IconSearch, IconCopy } from '@douyinfe/semi-icons';
|
||||
|
||||
const PricingSearchBar = ({
|
||||
selectedRowKeys,
|
||||
copyText,
|
||||
handleChange,
|
||||
handleCompositionStart,
|
||||
handleCompositionEnd,
|
||||
t
|
||||
}) => {
|
||||
const SearchAndActions = useMemo(() => (
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
{/* 搜索框 */}
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('模糊搜索模型名称')}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
onChange={handleChange}
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<Button
|
||||
theme='light'
|
||||
type='primary'
|
||||
icon={<IconCopy />}
|
||||
onClick={() => copyText(selectedRowKeys)}
|
||||
disabled={selectedRowKeys.length === 0}
|
||||
className="!bg-blue-500 hover:!bg-blue-600 text-white"
|
||||
>
|
||||
{t('复制选中模型')}
|
||||
</Button>
|
||||
</div>
|
||||
), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText]);
|
||||
|
||||
return SearchAndActions;
|
||||
};
|
||||
|
||||
export default PricingSearchBar;
|
||||
153
web/src/components/table/model-pricing/PricingSidebar.jsx
Normal file
153
web/src/components/table/model-pricing/PricingSidebar.jsx
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
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, 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';
|
||||
|
||||
const PricingSidebar = ({
|
||||
showWithRecharge,
|
||||
setShowWithRecharge,
|
||||
currency,
|
||||
setCurrency,
|
||||
handleChange,
|
||||
setActiveKey,
|
||||
showRatio,
|
||||
setShowRatio,
|
||||
filterGroup,
|
||||
setFilterGroup,
|
||||
filterQuotaType,
|
||||
setFilterQuotaType,
|
||||
t,
|
||||
...categoryProps
|
||||
}) => {
|
||||
|
||||
// 重置所有筛选条件
|
||||
const handleResetFilters = () => {
|
||||
// 重置搜索
|
||||
if (handleChange) {
|
||||
handleChange('');
|
||||
}
|
||||
|
||||
// 重置模型分类到默认
|
||||
if (setActiveKey && categoryProps.availableCategories?.length > 0) {
|
||||
setActiveKey(categoryProps.availableCategories[0]);
|
||||
}
|
||||
|
||||
// 重置充值价格显示
|
||||
if (setShowWithRecharge) {
|
||||
setShowWithRecharge(false);
|
||||
}
|
||||
|
||||
// 重置货币
|
||||
if (setCurrency) {
|
||||
setCurrency('USD');
|
||||
}
|
||||
|
||||
// 重置显示倍率
|
||||
setShowRatio(false);
|
||||
|
||||
// 重置分组筛选
|
||||
if (setFilterGroup) {
|
||||
setFilterGroup('all');
|
||||
}
|
||||
|
||||
// 重置计费类型筛选
|
||||
if (setFilterQuotaType) {
|
||||
setFilterQuotaType('all');
|
||||
}
|
||||
};
|
||||
|
||||
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"
|
||||
onClick={handleResetFilters}
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
{t('重置')}
|
||||
</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>
|
||||
|
||||
{/* 模型分类 */}
|
||||
<PricingCategories {...categoryProps} setActiveKey={setActiveKey} t={t} />
|
||||
|
||||
<PricingGroups filterGroup={filterGroup} setFilterGroup={setFilterGroup} usableGroup={categoryProps.usableGroup} models={categoryProps.models} t={t} />
|
||||
|
||||
<PricingQuotaTypes filterQuotaType={filterQuotaType} setFilterQuotaType={setFilterQuotaType} models={categoryProps.models} t={t} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PricingSidebar;
|
||||
@@ -23,9 +23,9 @@ import {
|
||||
IllustrationNoResult,
|
||||
IllustrationNoResultDark
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import { getModelPricingColumns } from './ModelPricingColumnDefs.js';
|
||||
import { getPricingTableColumns } from './PricingTableColumns.js';
|
||||
|
||||
const ModelPricingTable = ({
|
||||
const PricingTable = ({
|
||||
filteredModels,
|
||||
loading,
|
||||
rowSelection,
|
||||
@@ -44,10 +44,12 @@ const ModelPricingTable = ({
|
||||
displayPrice,
|
||||
filteredValue,
|
||||
handleGroupClick,
|
||||
showRatio,
|
||||
t
|
||||
}) => {
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return getModelPricingColumns({
|
||||
return getPricingTableColumns({
|
||||
t,
|
||||
selectedGroup,
|
||||
usableGroup,
|
||||
@@ -61,6 +63,7 @@ const ModelPricingTable = ({
|
||||
setTokenUnit,
|
||||
displayPrice,
|
||||
handleGroupClick,
|
||||
showRatio,
|
||||
});
|
||||
}, [
|
||||
t,
|
||||
@@ -76,6 +79,7 @@ const ModelPricingTable = ({
|
||||
setTokenUnit,
|
||||
displayPrice,
|
||||
handleGroupClick,
|
||||
showRatio,
|
||||
]);
|
||||
|
||||
// 更新列定义中的 filteredValue
|
||||
@@ -121,4 +125,4 @@ const ModelPricingTable = ({
|
||||
return ModelTable;
|
||||
};
|
||||
|
||||
export default ModelPricingTable;
|
||||
export default PricingTable;
|
||||
@@ -76,7 +76,7 @@ function renderSupportedEndpoints(endpoints) {
|
||||
);
|
||||
}
|
||||
|
||||
export const getModelPricingColumns = ({
|
||||
export const getPricingTableColumns = ({
|
||||
t,
|
||||
selectedGroup,
|
||||
usableGroup,
|
||||
@@ -90,8 +90,9 @@ export const getModelPricingColumns = ({
|
||||
setTokenUnit,
|
||||
displayPrice,
|
||||
handleGroupClick,
|
||||
showRatio,
|
||||
}) => {
|
||||
return [
|
||||
const baseColumns = [
|
||||
{
|
||||
title: t('可用性'),
|
||||
dataIndex: 'available',
|
||||
@@ -166,96 +167,109 @@ export const getModelPricingColumns = ({
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<div className="flex items-center space-x-1">
|
||||
<span>{t('倍率')}</span>
|
||||
<Tooltip content={t('倍率是为了方便换算不同价格的模型')}>
|
||||
<IconHelpCircle
|
||||
className="text-blue-500 cursor-pointer"
|
||||
onClick={() => {
|
||||
setModalImageUrl('/ratio.png');
|
||||
setIsModalOpenurl(true);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
];
|
||||
|
||||
// 倍率列 - 只有在showRatio为true时才包含
|
||||
const ratioColumn = {
|
||||
title: () => (
|
||||
<div className="flex items-center space-x-1">
|
||||
<span>{t('倍率')}</span>
|
||||
<Tooltip content={t('倍率是为了方便换算不同价格的模型')}>
|
||||
<IconHelpCircle
|
||||
className="text-blue-500 cursor-pointer"
|
||||
onClick={() => {
|
||||
setModalImageUrl('/ratio.png');
|
||||
setIsModalOpenurl(true);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'model_ratio',
|
||||
render: (text, record, index) => {
|
||||
let content = text;
|
||||
let completionRatio = parseFloat(record.completion_ratio.toFixed(3));
|
||||
content = (
|
||||
<div className="space-y-1">
|
||||
<div className="text-gray-700">
|
||||
{t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
|
||||
</div>
|
||||
<div className="text-gray-700">
|
||||
{t('补全倍率')}:
|
||||
{record.quota_type === 0 ? completionRatio : t('无')}
|
||||
</div>
|
||||
<div className="text-gray-700">
|
||||
{t('分组倍率')}:{groupRatio[selectedGroup]}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'model_ratio',
|
||||
render: (text, record, index) => {
|
||||
let content = text;
|
||||
let completionRatio = parseFloat(record.completion_ratio.toFixed(3));
|
||||
);
|
||||
return content;
|
||||
},
|
||||
};
|
||||
|
||||
// 价格列
|
||||
const priceColumn = {
|
||||
title: (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>{t('模型价格')}</span>
|
||||
{/* 计费单位切换 */}
|
||||
<Switch
|
||||
checked={tokenUnit === 'K'}
|
||||
onChange={(checked) => setTokenUnit(checked ? 'K' : 'M')}
|
||||
checkedText="K"
|
||||
uncheckedText="M"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'model_price',
|
||||
render: (text, record, index) => {
|
||||
let content = text;
|
||||
if (record.quota_type === 0) {
|
||||
let inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
|
||||
let completionRatioPriceUSD =
|
||||
record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
|
||||
|
||||
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
|
||||
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
|
||||
|
||||
let displayInput = displayPrice(inputRatioPriceUSD);
|
||||
let displayCompletion = displayPrice(completionRatioPriceUSD);
|
||||
|
||||
const divisor = unitDivisor;
|
||||
const numInput = parseFloat(displayInput.replace(/[^0-9.]/g, '')) / divisor;
|
||||
const numCompletion = parseFloat(displayCompletion.replace(/[^0-9.]/g, '')) / divisor;
|
||||
|
||||
displayInput = `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(3)}`;
|
||||
displayCompletion = `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(3)}`;
|
||||
content = (
|
||||
<div className="space-y-1">
|
||||
<div className="text-gray-700">
|
||||
{t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
|
||||
{t('提示')} {displayInput} / 1{unitLabel} tokens
|
||||
</div>
|
||||
<div className="text-gray-700">
|
||||
{t('补全倍率')}:
|
||||
{record.quota_type === 0 ? completionRatio : t('无')}
|
||||
</div>
|
||||
<div className="text-gray-700">
|
||||
{t('分组倍率')}:{groupRatio[selectedGroup]}
|
||||
{t('补全')} {displayCompletion} / 1{unitLabel} tokens
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return content;
|
||||
},
|
||||
} else {
|
||||
let priceUSD = parseFloat(text) * groupRatio[selectedGroup];
|
||||
let displayVal = displayPrice(priceUSD);
|
||||
content = (
|
||||
<div className="text-gray-700">
|
||||
{t('模型价格')}:{displayVal}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return content;
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>{t('模型价格')}</span>
|
||||
{/* 计费单位切换 */}
|
||||
<Switch
|
||||
checked={tokenUnit === 'K'}
|
||||
onChange={(checked) => setTokenUnit(checked ? 'K' : 'M')}
|
||||
checkedText="K"
|
||||
uncheckedText="M"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'model_price',
|
||||
render: (text, record, index) => {
|
||||
let content = text;
|
||||
if (record.quota_type === 0) {
|
||||
let inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
|
||||
let completionRatioPriceUSD =
|
||||
record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
|
||||
};
|
||||
|
||||
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
|
||||
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
|
||||
// 根据showRatio决定是否包含倍率列
|
||||
const columns = [...baseColumns];
|
||||
if (showRatio) {
|
||||
columns.push(ratioColumn);
|
||||
}
|
||||
columns.push(priceColumn);
|
||||
|
||||
let displayInput = displayPrice(inputRatioPriceUSD);
|
||||
let displayCompletion = displayPrice(completionRatioPriceUSD);
|
||||
|
||||
const divisor = unitDivisor;
|
||||
const numInput = parseFloat(displayInput.replace(/[^0-9.]/g, '')) / divisor;
|
||||
const numCompletion = parseFloat(displayCompletion.replace(/[^0-9.]/g, '')) / divisor;
|
||||
|
||||
displayInput = `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(3)}`;
|
||||
displayCompletion = `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(3)}`;
|
||||
content = (
|
||||
<div className="space-y-1">
|
||||
<div className="text-gray-700">
|
||||
{t('提示')} {displayInput} / 1{unitLabel} tokens
|
||||
</div>
|
||||
<div className="text-gray-700">
|
||||
{t('补全')} {displayCompletion} / 1{unitLabel} tokens
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let priceUSD = parseFloat(text) * groupRatio[selectedGroup];
|
||||
let displayVal = displayPrice(priceUSD);
|
||||
content = (
|
||||
<div className="text-gray-700">
|
||||
{t('模型价格')}:{displayVal}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return content;
|
||||
},
|
||||
},
|
||||
];
|
||||
return columns;
|
||||
};
|
||||
@@ -17,50 +17,5 @@ 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 { Layout, Card, ImagePreview } from '@douyinfe/semi-ui';
|
||||
import ModelPricingTabs from './ModelPricingTabs.jsx';
|
||||
import ModelPricingFilters from './ModelPricingFilters.jsx';
|
||||
import ModelPricingTable from './ModelPricingTable.jsx';
|
||||
import ModelPricingHeader from './ModelPricingHeader.jsx';
|
||||
import { useModelPricingData } from '../../../hooks/model-pricing/useModelPricingData.js';
|
||||
|
||||
const ModelPricingPage = () => {
|
||||
const modelPricingData = useModelPricingData();
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50">
|
||||
<Layout>
|
||||
<Layout.Content>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-full">
|
||||
{/* 主卡片容器 */}
|
||||
<Card bordered={false} className="!rounded-2xl shadow-lg border-0">
|
||||
{/* 顶部状态卡片 */}
|
||||
<ModelPricingHeader {...modelPricingData} />
|
||||
|
||||
{/* 模型分类 Tabs */}
|
||||
<div className="mb-6">
|
||||
<ModelPricingTabs {...modelPricingData} />
|
||||
|
||||
{/* 搜索和表格区域 */}
|
||||
<ModelPricingFilters {...modelPricingData} />
|
||||
<ModelPricingTable {...modelPricingData} />
|
||||
</div>
|
||||
|
||||
{/* 倍率说明图预览 */}
|
||||
<ImagePreview
|
||||
src={modelPricingData.modalImageUrl}
|
||||
visible={modelPricingData.isModalOpenurl}
|
||||
onVisibleChange={(visible) => modelPricingData.setIsModalOpenurl(visible)}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelPricingPage;
|
||||
// 为了向后兼容,这里重新导出新的 PricingPage 组件
|
||||
export { default } from './PricingPage.jsx';
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup.jsx';
|
||||
|
||||
const PricingCategories = ({ activeKey, setActiveKey, modelCategories, categoryCounts, availableCategories, t }) => {
|
||||
const items = Object.entries(modelCategories)
|
||||
.filter(([key]) => availableCategories.includes(key))
|
||||
.map(([key, category]) => ({
|
||||
value: key,
|
||||
label: category.label,
|
||||
icon: category.icon,
|
||||
tagCount: categoryCounts[key] || 0,
|
||||
}));
|
||||
|
||||
return (
|
||||
<SelectableButtonGroup
|
||||
title={t('模型分类')}
|
||||
items={items}
|
||||
activeValue={activeKey}
|
||||
onChange={setActiveKey}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PricingCategories;
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
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 SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup.jsx';
|
||||
|
||||
/**
|
||||
* 分组筛选组件
|
||||
* @param {string} filterGroup 当前选中的分组,'all' 表示不过滤
|
||||
* @param {Function} setFilterGroup 设置选中分组
|
||||
* @param {Record<string, any>} usableGroup 后端返回的可用分组对象
|
||||
* @param {Function} t i18n
|
||||
*/
|
||||
const PricingGroups = ({ filterGroup, setFilterGroup, usableGroup = {}, models = [], t }) => {
|
||||
const groups = ['all', ...Object.keys(usableGroup)];
|
||||
|
||||
const items = groups.map((g) => {
|
||||
let count = 0;
|
||||
if (g === 'all') {
|
||||
count = models.length;
|
||||
} else {
|
||||
count = models.filter(m => m.enable_groups && m.enable_groups.includes(g)).length;
|
||||
}
|
||||
return {
|
||||
value: g,
|
||||
label: g === 'all' ? t('全部分组') : g,
|
||||
tagCount: count,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<SelectableButtonGroup
|
||||
title={t('可用令牌分组')}
|
||||
items={items}
|
||||
activeValue={filterGroup}
|
||||
onChange={setFilterGroup}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PricingGroups;
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup.jsx';
|
||||
|
||||
/**
|
||||
* 计费类型筛选组件
|
||||
* @param {string|'all'|0|1} filterQuotaType 当前值
|
||||
* @param {Function} setFilterQuotaType setter
|
||||
* @param {Function} t i18n
|
||||
*/
|
||||
const PricingQuotaTypes = ({ filterQuotaType, setFilterQuotaType, models = [], t }) => {
|
||||
const qtyCount = (type) => models.filter(m => type === 'all' ? true : m.quota_type === type).length;
|
||||
|
||||
const items = [
|
||||
{ value: 'all', label: t('全部类型'), tagCount: qtyCount('all') },
|
||||
{ value: 0, label: t('按量计费'), tagCount: qtyCount(0) },
|
||||
{ value: 1, label: t('按次计费'), tagCount: qtyCount(1) },
|
||||
];
|
||||
|
||||
return (
|
||||
<SelectableButtonGroup
|
||||
title={t('计费类型')}
|
||||
items={items}
|
||||
activeValue={filterQuotaType}
|
||||
onChange={setFilterQuotaType}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PricingQuotaTypes;
|
||||
@@ -32,6 +32,10 @@ export const useModelPricingData = () => {
|
||||
const [modalImageUrl, setModalImageUrl] = useState('');
|
||||
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
|
||||
const [selectedGroup, setSelectedGroup] = useState('default');
|
||||
// 用于 Table 的可用分组筛选,“all” 表示不过滤
|
||||
const [filterGroup, setFilterGroup] = useState('all');
|
||||
// 计费类型筛选: 'all' | 0 | 1
|
||||
const [filterQuotaType, setFilterQuotaType] = useState('all');
|
||||
const [activeKey, setActiveKey] = useState('all');
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [currency, setCurrency] = useState('USD');
|
||||
@@ -75,10 +79,22 @@ export const useModelPricingData = () => {
|
||||
const filteredModels = useMemo(() => {
|
||||
let result = models;
|
||||
|
||||
// 分类筛选
|
||||
if (activeKey !== 'all') {
|
||||
result = result.filter(model => modelCategories[activeKey].filter(model));
|
||||
}
|
||||
|
||||
// 分组筛选
|
||||
if (filterGroup !== 'all') {
|
||||
result = result.filter(model => model.enable_groups.includes(filterGroup));
|
||||
}
|
||||
|
||||
// 计费类型筛选
|
||||
if (filterQuotaType !== 'all') {
|
||||
result = result.filter(model => model.quota_type === filterQuotaType);
|
||||
}
|
||||
|
||||
// 搜索筛选
|
||||
if (filteredValue.length > 0) {
|
||||
const searchTerm = filteredValue[0].toLowerCase();
|
||||
result = result.filter(model =>
|
||||
@@ -87,7 +103,7 @@ export const useModelPricingData = () => {
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [activeKey, models, filteredValue]);
|
||||
}, [activeKey, models, filteredValue, filterGroup, filterQuotaType]);
|
||||
|
||||
const rowSelection = useMemo(
|
||||
() => ({
|
||||
@@ -184,6 +200,8 @@ export const useModelPricingData = () => {
|
||||
|
||||
const handleGroupClick = (group) => {
|
||||
setSelectedGroup(group);
|
||||
// 同时将分组过滤设置为该分组
|
||||
setFilterGroup(group);
|
||||
showInfo(
|
||||
t('当前查看的分组为:{{group}},倍率为:{{ratio}}', {
|
||||
group: group,
|
||||
@@ -208,6 +226,10 @@ export const useModelPricingData = () => {
|
||||
setIsModalOpenurl,
|
||||
selectedGroup,
|
||||
setSelectedGroup,
|
||||
filterGroup,
|
||||
setFilterGroup,
|
||||
filterQuotaType,
|
||||
setFilterQuotaType,
|
||||
activeKey,
|
||||
setActiveKey,
|
||||
pageSize,
|
||||
|
||||
@@ -21,9 +21,9 @@ import React from 'react';
|
||||
import ModelPricingPage from '../../components/table/model-pricing';
|
||||
|
||||
const Pricing = () => (
|
||||
<div className="mt-[60px] px-2">
|
||||
<>
|
||||
<ModelPricingPage />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
export default Pricing;
|
||||
|
||||
Reference in New Issue
Block a user