Commit f19f1aec by t0ng7u

💄 style(pricing): enhance card view UI and skeleton loading experience (#1365)

- Increase skeleton card count from 6 to 10 for better visual coverage
- Extend minimum skeleton display duration from 500ms to 1000ms for smoother UX
- Add circle shape to all pricing tags for consistent rounded design
- Apply circle styling to billing type, popularity, endpoint, and context tags

This commit improves the visual consistency and user experience of the pricing
card view by standardizing tag appearance and optimizing skeleton loading timing.
parent 7b04596b
......@@ -50,7 +50,7 @@ const CardTable = ({
setShowSkeleton(true);
} else {
const elapsed = Date.now() - loadingStartRef.current;
const remaining = Math.max(0, 500 - elapsed);
const remaining = Math.max(0, 1000 - elapsed);
if (remaining === 0) {
setShowSkeleton(false);
} else {
......
......@@ -65,7 +65,7 @@ const SelectableButtonGroup = ({
setShowSkeleton(true);
} else {
const elapsed = Date.now() - loadingStartRef.current;
const remaining = Math.max(0, 500 - elapsed);
const remaining = Math.max(0, 1000 - elapsed);
if (remaining === 0) {
setShowSkeleton(false);
} else {
......
......@@ -219,7 +219,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
useEffect(() => {
if (statusState?.status !== undefined) {
const elapsed = Date.now() - loadingStartRef.current;
const remaining = Math.max(0, 500 - elapsed);
const remaining = Math.max(0, 1000 - elapsed);
const timer = setTimeout(() => {
setIsLoading(false);
}, remaining);
......
/*
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 { Card } from '@douyinfe/semi-ui';
import { IconVerify, IconLayers, IconInfoCircle } from '@douyinfe/semi-icons';
import { AlertCircle } from 'lucide-react';
const PricingHeader = ({
userState,
groupRatio,
selectedGroup,
models,
t
}) => {
return (
<Card
className="!rounded-2xl !border-0 !shadow-md overflow-hidden mb-6"
style={{
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 25%, #a855f7 50%, #c084fc 75%, #d8b4fe 100%)',
position: 'relative'
}}
bodyStyle={{ padding: 0 }}
>
<div className="relative p-6 sm:p-8" style={{ color: 'white' }}>
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4 lg:gap-6">
<div className="flex items-start">
<div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-white/10 flex items-center justify-center mr-3 sm:mr-4">
<IconLayers size="extra-large" className="text-white" />
</div>
<div className="flex-1 min-w-0">
<div className="text-base sm:text-lg font-semibold mb-1 sm:mb-2">
{t('模型定价')}
</div>
<div className="text-sm text-white/80">
{userState.user ? (
<div className="flex items-center">
<IconVerify className="mr-1.5 flex-shrink-0" size="small" />
<span className="truncate">
{t('当前分组')}: {userState.user.group}{t('倍率')}: {groupRatio[userState.user.group]}
</span>
</div>
) : (
<div className="flex items-center">
<AlertCircle size={14} className="mr-1.5 flex-shrink-0" />
<span className="truncate">
{t('未登录,使用默认分组倍率:')}{groupRatio['default']}
</span>
</div>
)}
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-2 sm:gap-3 mt-2 lg:mt-0">
<div
className="text-center px-2 py-2 sm:px-3 sm:py-2.5 bg-white/10 rounded-lg backdrop-blur-sm hover:bg-white/20 transition-colors duration-200"
style={{ backdropFilter: 'blur(10px)' }}
>
<div className="text-xs text-white/70 mb-0.5">{t('分组倍率')}</div>
<div className="text-sm sm:text-base font-semibold">{groupRatio[selectedGroup] || '1.0'}x</div>
</div>
<div
className="text-center px-2 py-2 sm:px-3 sm:py-2.5 bg-white/10 rounded-lg backdrop-blur-sm hover:bg-white/20 transition-colors duration-200"
style={{ backdropFilter: 'blur(10px)' }}
>
<div className="text-xs text-white/70 mb-0.5">{t('可用模型')}</div>
<div className="text-sm sm:text-base font-semibold">
{models.filter(m => m.enable_groups.includes(selectedGroup)).length}
</div>
</div>
<div
className="text-center px-2 py-2 sm:px-3 sm:py-2.5 bg-white/10 rounded-lg backdrop-blur-sm hover:bg-white/20 transition-colors duration-200"
style={{ backdropFilter: 'blur(10px)' }}
>
<div className="text-xs text-white/70 mb-0.5">{t('计费类型')}</div>
<div className="text-sm sm:text-base font-semibold">2</div>
</div>
</div>
</div>
{/* 计费说明 */}
<div className="mt-4 sm:mt-5">
<div className="flex items-start">
<div
className="w-full flex items-start space-x-2 px-3 py-2 sm:px-4 sm:py-2.5 rounded-lg text-xs sm:text-sm"
style={{
backgroundColor: 'rgba(255, 255, 255, 0.2)',
color: 'white',
backdropFilter: 'blur(10px)'
}}
>
<IconInfoCircle className="flex-shrink-0 mt-0.5" size="small" />
<span>
{t('按量计费费用 = 分组倍率 × 模型倍率 × (提示token数 + 补全token数 × 补全倍率)/ 500000 (单位:美元)')}
</span>
</div>
</div>
</div>
<div className="absolute top-0 left-0 w-full h-2 bg-gradient-to-r from-yellow-400 via-orange-400 to-red-400" style={{ opacity: 0.6 }}></div>
</div>
</Card>
);
};
export default PricingHeader;
\ No newline at end of file
......@@ -29,6 +29,8 @@ const PricingDisplaySettings = ({
setCurrency,
showRatio,
setShowRatio,
viewMode,
setViewMode,
loading = false,
t
}) => {
......@@ -50,6 +52,10 @@ const PricingDisplaySettings = ({
</Tooltip>
</span>
),
},
{
value: 'tableView',
label: t('表格视图')
}
];
......@@ -59,10 +65,16 @@ const PricingDisplaySettings = ({
];
const handleChange = (value) => {
if (value === 'recharge') {
setShowWithRecharge(!showWithRecharge);
} else if (value === 'ratio') {
setShowRatio(!showRatio);
switch (value) {
case 'recharge':
setShowWithRecharge(!showWithRecharge);
break;
case 'ratio':
setShowRatio(!showRatio);
break;
case 'tableView':
setViewMode(viewMode === 'table' ? 'card' : 'table');
break;
}
};
......@@ -70,6 +82,7 @@ const PricingDisplaySettings = ({
const activeValues = [];
if (showWithRecharge) activeValues.push('recharge');
if (showRatio) activeValues.push('ratio');
if (viewMode === 'table') activeValues.push('tableView');
return activeValues;
};
......
......@@ -19,43 +19,19 @@ For commercial licensing, please contact support@quantumnous.com
import React from 'react';
import PricingSearchBar from './PricingSearchBar';
import PricingTable from './PricingTable';
import PricingView from './PricingView';
const PricingContent = ({ isMobile, sidebarProps, ...props }) => {
return (
<div
className={isMobile ? "" : "pricing-scroll-hide"}
style={isMobile ? {
height: '100%',
display: 'flex',
flexDirection: 'column',
overflow: 'auto'
} : {}}
>
<div className={isMobile ? "pricing-content-mobile" : "pricing-scroll-hide"}>
{/* 固定的搜索和操作区域 */}
<div
style={{
padding: '16px 24px',
borderBottom: '1px solid var(--semi-color-border)',
backgroundColor: 'var(--semi-color-bg-0)',
flexShrink: 0,
position: 'sticky',
top: 0,
zIndex: 5,
}}
>
<div className="pricing-search-header">
<PricingSearchBar {...props} isMobile={isMobile} sidebarProps={sidebarProps} />
</div>
{/* 可滚动的内容区域 */}
<div
style={{
flex: 1,
overflow: 'auto',
...(isMobile && { minHeight: 0 })
}}
>
<PricingTable {...props} />
<div className={isMobile ? "pricing-view-container-mobile" : "pricing-view-container"}>
<PricingView {...props} viewMode={sidebarProps.viewMode} />
</div>
</div>
);
......
......@@ -21,56 +21,46 @@ import React from 'react';
import { Layout, ImagePreview } from '@douyinfe/semi-ui';
import PricingSidebar from './PricingSidebar';
import PricingContent from './PricingContent';
import { useModelPricingData } from '../../../hooks/model-pricing/useModelPricingData';
import { useIsMobile } from '../../../hooks/common/useIsMobile';
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);
const [viewMode, setViewMode] = React.useState('card');
const allProps = {
...pricingData,
showRatio,
setShowRatio,
viewMode,
setViewMode
};
return (
<div className="bg-white">
<Layout style={{ height: 'calc(100vh - 60px)', overflow: 'hidden', marginTop: '60px' }}>
{/* 左侧边栏 - 只在桌面端显示 */}
<Layout className="pricing-layout">
{!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'
}}
className="pricing-scroll-hide pricing-sidebar"
width={460}
>
<PricingSidebar {...pricingData} showRatio={showRatio} setShowRatio={setShowRatio} />
<PricingSidebar {...allProps} />
</Sider>
)}
{/* 右侧内容区 */}
<Content
className="pricing-scroll-hide"
style={{
height: 'calc(100vh - 60px)',
backgroundColor: 'var(--semi-color-bg-0)',
display: 'flex',
flexDirection: 'column'
}}
className="pricing-scroll-hide pricing-content"
>
<PricingContent
{...pricingData}
showRatio={showRatio}
<PricingContent
{...allProps}
isMobile={isMobile}
sidebarProps={{ ...pricingData, showRatio, setShowRatio }}
sidebarProps={allProps}
/>
</Content>
</Layout>
{/* 倍率说明图预览 */}
<ImagePreview
src={pricingData.modalImageUrl}
visible={pricingData.isModalOpenurl}
......
......@@ -20,7 +20,7 @@ For commercial licensing, please contact support@quantumnous.com
import React, { useMemo, useState } from 'react';
import { Input, Button } from '@douyinfe/semi-ui';
import { IconSearch, IconCopy, IconFilter } from '@douyinfe/semi-icons';
import PricingFilterModal from './modal/PricingFilterModal';
import PricingFilterModal from '../modal/PricingFilterModal';
const PricingSearchBar = ({
selectedRowKeys,
......
......@@ -19,11 +19,11 @@ For commercial licensing, please contact support@quantumnous.com
import React from 'react';
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';
import { resetPricingFilters } from '../../../helpers/utils';
import PricingCategories from '../filter/PricingCategories';
import PricingGroups from '../filter/PricingGroups';
import PricingQuotaTypes from '../filter/PricingQuotaTypes';
import PricingDisplaySettings from '../filter/PricingDisplaySettings';
import { resetPricingFilters } from '../../../../helpers/utils';
const PricingSidebar = ({
showWithRecharge,
......@@ -34,10 +34,14 @@ const PricingSidebar = ({
setActiveKey,
showRatio,
setShowRatio,
viewMode,
setViewMode,
filterGroup,
setFilterGroup,
filterQuotaType,
setFilterQuotaType,
currentPage,
setCurrentPage,
loading,
t,
...categoryProps
......@@ -51,8 +55,10 @@ const PricingSidebar = ({
setShowWithRecharge,
setCurrency,
setShowRatio,
setViewMode,
setFilterGroup,
setFilterQuotaType,
setCurrentPage,
});
return (
......@@ -78,15 +84,36 @@ const PricingSidebar = ({
setCurrency={setCurrency}
showRatio={showRatio}
setShowRatio={setShowRatio}
viewMode={viewMode}
setViewMode={setViewMode}
loading={loading}
t={t}
/>
<PricingCategories {...categoryProps} setActiveKey={setActiveKey} loading={loading} t={t} />
<PricingCategories
{...categoryProps}
setActiveKey={setActiveKey}
loading={loading}
t={t}
/>
<PricingGroups filterGroup={filterGroup} setFilterGroup={setFilterGroup} usableGroup={categoryProps.usableGroup} groupRatio={categoryProps.groupRatio} models={categoryProps.models} loading={loading} t={t} />
<PricingGroups
filterGroup={filterGroup}
setFilterGroup={setFilterGroup}
usableGroup={categoryProps.usableGroup}
groupRatio={categoryProps.groupRatio}
models={categoryProps.models}
loading={loading}
t={t}
/>
<PricingQuotaTypes filterQuotaType={filterQuotaType} setFilterQuotaType={setFilterQuotaType} models={categoryProps.models} loading={loading} t={t} />
<PricingQuotaTypes
filterQuotaType={filterQuotaType}
setFilterQuotaType={setFilterQuotaType}
models={categoryProps.models}
loading={loading}
t={t}
/>
</div>
);
};
......
......@@ -17,5 +17,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
// 为了向后兼容,这里重新导出新的 PricingPage 组件
export { default } from './PricingPage';
\ No newline at end of file
import React from 'react';
import PricingTable from '../view/PricingTable';
import PricingCardView from '../view/PricingCardView';
const PricingView = ({
viewMode = 'table',
...props
}) => {
return viewMode === 'card' ?
<PricingCardView {...props} /> :
<PricingTable {...props} />;
};
export default PricingView;
\ No newline at end of file
......@@ -40,10 +40,14 @@ const PricingFilterModal = ({
setActiveKey,
showRatio,
setShowRatio,
viewMode,
setViewMode,
filterGroup,
setFilterGroup,
filterQuotaType,
setFilterQuotaType,
currentPage,
setCurrentPage,
loading,
...categoryProps
} = sidebarProps;
......@@ -56,14 +60,12 @@ const PricingFilterModal = ({
setShowWithRecharge,
setCurrency,
setShowRatio,
setViewMode,
setFilterGroup,
setFilterQuotaType,
setCurrentPage,
});
const handleConfirm = () => {
onClose();
};
const footer = (
<div className="flex justify-end">
<Button
......@@ -76,7 +78,7 @@ const PricingFilterModal = ({
<Button
theme="solid"
type="primary"
onClick={handleConfirm}
onClick={onClose}
>
{t('确定')}
</Button>
......@@ -106,6 +108,8 @@ const PricingFilterModal = ({
setCurrency={setCurrency}
showRatio={showRatio}
setShowRatio={setShowRatio}
viewMode={viewMode}
setViewMode={setViewMode}
loading={loading}
t={t}
/>
......
......@@ -32,18 +32,15 @@ const PricingTable = ({
pageSize,
setPageSize,
selectedGroup,
usableGroup,
groupRatio,
copyText,
setModalImageUrl,
setIsModalOpenurl,
currency,
showWithRecharge,
tokenUnit,
setTokenUnit,
displayPrice,
filteredValue,
handleGroupClick,
searchValue,
showRatio,
compactMode = false,
t
......@@ -53,43 +50,37 @@ const PricingTable = ({
return getPricingTableColumns({
t,
selectedGroup,
usableGroup,
groupRatio,
copyText,
setModalImageUrl,
setIsModalOpenurl,
currency,
showWithRecharge,
tokenUnit,
setTokenUnit,
displayPrice,
handleGroupClick,
showRatio,
});
}, [
t,
selectedGroup,
usableGroup,
groupRatio,
copyText,
setModalImageUrl,
setIsModalOpenurl,
currency,
showWithRecharge,
tokenUnit,
setTokenUnit,
displayPrice,
handleGroupClick,
showRatio,
]);
// 更新列定义中的 filteredValue
// 更新列定义中的 searchValue
const processedColumns = useMemo(() => {
const cols = columns.map(column => {
if (column.dataIndex === 'model_name') {
return {
...column,
filteredValue
filteredValue: searchValue ? [searchValue] : []
};
}
return column;
......@@ -100,7 +91,7 @@ const PricingTable = ({
return cols.map(({ fixed, ...rest }) => rest);
}
return cols;
}, [columns, filteredValue, compactMode]);
}, [columns, searchValue, compactMode]);
const ModelTable = useMemo(() => (
<Card className="!rounded-xl overflow-hidden" bordered={false}>
......
......@@ -20,7 +20,7 @@ For commercial licensing, please contact support@quantumnous.com
import React from 'react';
import { Tag, Space, Tooltip, Switch } from '@douyinfe/semi-ui';
import { IconHelpCircle } from '@douyinfe/semi-icons';
import { renderModelTag, stringToColor } from '../../../helpers';
import { renderModelTag, stringToColor, calculateModelPrice } from '../../../../helpers';
function renderQuotaType(type, t) {
switch (type) {
......@@ -158,38 +158,30 @@ export const getPricingTableColumns = ({
),
dataIndex: 'model_price',
render: (text, record, index) => {
if (record.quota_type === 0) {
const inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
const completionRatioPriceUSD =
record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
const rawDisplayInput = displayPrice(inputRatioPriceUSD);
const rawDisplayCompletion = displayPrice(completionRatioPriceUSD);
const numInput = parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor;
const numCompletion = parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor;
const priceData = calculateModelPrice({
record,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency
});
const displayInput = `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(3)}`;
const displayCompletion = `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(3)}`;
if (priceData.isPerToken) {
return (
<div className="space-y-1">
<div className="text-gray-700">
{t('提示')} {displayInput} / 1{unitLabel} tokens
{t('提示')} {priceData.inputPrice} / 1{priceData.unitLabel} tokens
</div>
<div className="text-gray-700">
{t('补全')} {displayCompletion} / 1{unitLabel} tokens
{t('补全')} {priceData.completionPrice} / 1{priceData.unitLabel} tokens
</div>
</div>
);
} else {
const priceUSD = parseFloat(text) * groupRatio[selectedGroup];
const displayVal = displayPrice(priceUSD);
return (
<div className="text-gray-700">
{t('模型价格')}{displayVal}
{t('模型价格')}{priceData.price}
</div>
);
}
......
......@@ -40,7 +40,7 @@ const LogsActions = ({
setShowSkeleton(true);
} else {
const elapsed = Date.now() - loadingStartRef.current;
const remaining = Math.max(0, 500 - elapsed);
const remaining = Math.max(0, 1000 - elapsed);
if (remaining === 0) {
setShowSkeleton(false);
} else {
......
......@@ -569,6 +569,59 @@ export const modelSelectFilter = (input, option) => {
};
// -------------------------------
// 模型定价计算工具函数
export const calculateModelPrice = ({
record,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency,
precision = 3
}) => {
if (record.quota_type === 0) {
// 按量计费
const inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
const completionRatioPriceUSD =
record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
const rawDisplayInput = displayPrice(inputRatioPriceUSD);
const rawDisplayCompletion = displayPrice(completionRatioPriceUSD);
const numInput = parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor;
const numCompletion = parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor;
return {
inputPrice: `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(precision)}`,
completionPrice: `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(precision)}`,
unitLabel,
isPerToken: true
};
} else {
// 按次计费
const priceUSD = parseFloat(record.model_price) * groupRatio[selectedGroup];
const displayVal = displayPrice(priceUSD);
return {
price: displayVal,
isPerToken: false
};
}
};
// 格式化价格信息为字符串(用于卡片视图)
export const formatPriceInfo = (priceData, t) => {
if (priceData.isPerToken) {
return `${t('输入')} ${priceData.inputPrice}/${priceData.unitLabel} ${t('输出')} ${priceData.completionPrice}/${priceData.unitLabel}`;
} else {
return `${t('模型价格')} ${priceData.price}`;
}
};
// -------------------------------
// CardPro 分页配置函数
// 用于创建 CardPro 的 paginationArea 配置
export const createCardProPagination = ({
......@@ -626,8 +679,10 @@ export const resetPricingFilters = ({
setShowWithRecharge,
setCurrency,
setShowRatio,
setViewMode,
setFilterGroup,
setFilterQuotaType,
setCurrentPage,
}) => {
// 重置搜索
if (typeof handleChange === 'function') {
......@@ -658,6 +713,11 @@ export const resetPricingFilters = ({
setShowRatio(false);
}
// 重置视图模式
if (typeof setViewMode === 'function') {
setViewMode('card');
}
// 重置分组筛选
if (typeof setFilterGroup === 'function') {
setFilterGroup('all');
......@@ -667,4 +727,9 @@ export const resetPricingFilters = ({
if (typeof setFilterQuotaType === 'function') {
setFilterQuotaType('all');
}
// 重置当前页面
if (typeof setCurrentPage === 'function') {
setCurrentPage(1);
}
};
......@@ -178,7 +178,7 @@ export const useDashboardData = (userState, userDispatch, statusState) => {
}
} finally {
const elapsed = Date.now() - startTime;
const remainingTime = Math.max(0, 500 - elapsed);
const remainingTime = Math.max(0, 1000 - elapsed);
setTimeout(() => {
setLoading(false);
}, remainingTime);
......
......@@ -26,18 +26,17 @@ import { StatusContext } from '../../context/Status/index.js';
export const useModelPricingData = () => {
const { t } = useTranslation();
const [filteredValue, setFilteredValue] = useState([]);
const [searchValue, setSearchValue] = useState('');
const compositionRef = useRef({ isComposition: false });
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
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 [filterGroup, setFilterGroup] = useState('all'); // 用于 Table 的可用分组筛选,“all” 表示不过滤
const [filterQuotaType, setFilterQuotaType] = useState('all'); // 计费类型筛选: 'all' | 0 | 1
const [activeKey, setActiveKey] = useState('all');
const [pageSize, setPageSize] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [currency, setCurrency] = useState('USD');
const [showWithRecharge, setShowWithRecharge] = useState(false);
const [tokenUnit, setTokenUnit] = useState('M');
......@@ -95,15 +94,15 @@ export const useModelPricingData = () => {
}
// 搜索筛选
if (filteredValue.length > 0) {
const searchTerm = filteredValue[0].toLowerCase();
if (searchValue.length > 0) {
const searchTerm = searchValue.toLowerCase();
result = result.filter(model =>
model.model_name.toLowerCase().includes(searchTerm)
);
}
return result;
}, [activeKey, models, filteredValue, filterGroup, filterQuotaType]);
}, [activeKey, models, searchValue, filterGroup, filterQuotaType]);
const rowSelection = useMemo(
() => ({
......@@ -183,8 +182,8 @@ export const useModelPricingData = () => {
if (compositionRef.current.isComposition) {
return;
}
const newFilteredValue = value ? [value] : [];
setFilteredValue(newFilteredValue);
const newSearchValue = value ? value : '';
setSearchValue(newSearchValue);
};
const handleCompositionStart = () => {
......@@ -194,8 +193,8 @@ export const useModelPricingData = () => {
const handleCompositionEnd = (event) => {
compositionRef.current.isComposition = false;
const value = event.target.value;
const newFilteredValue = value ? [value] : [];
setFilteredValue(newFilteredValue);
const newSearchValue = value ? value : '';
setSearchValue(newSearchValue);
};
const handleGroupClick = (group) => {
......@@ -214,10 +213,15 @@ export const useModelPricingData = () => {
refresh().then();
}, []);
// 当筛选条件变化时重置到第一页
useEffect(() => {
setCurrentPage(1);
}, [activeKey, filterGroup, filterQuotaType, searchValue]);
return {
// 状态
filteredValue,
setFilteredValue,
searchValue,
setSearchValue,
selectedRowKeys,
setSelectedRowKeys,
modalImageUrl,
......@@ -234,6 +238,8 @@ export const useModelPricingData = () => {
setActiveKey,
pageSize,
setPageSize,
currentPage,
setCurrentPage,
currency,
setCurrency,
showWithRecharge,
......
......@@ -617,4 +617,59 @@ html:not(.dark) .blur-ball-teal {
height: calc(100vh - 77px);
max-height: calc(100vh - 77px);
}
}
/* ==================== 模型定价页面布局 ==================== */
.pricing-layout {
height: calc(100vh - 60px);
overflow: hidden;
margin-top: 60px;
}
.pricing-sidebar {
min-width: 460px;
max-width: 460px;
height: calc(100vh - 60px);
background-color: var(--semi-color-bg-0);
border-right: 1px solid var(--semi-color-border);
overflow: auto;
}
.pricing-content {
height: calc(100vh - 60px);
background-color: var(--semi-color-bg-0);
display: flex;
flex-direction: column;
}
.pricing-pagination-divider {
border-color: var(--semi-color-border);
}
.pricing-content-mobile {
height: 100%;
display: flex;
flex-direction: column;
overflow: auto;
}
.pricing-search-header {
padding: 16px 24px;
border-bottom: 1px solid var(--semi-color-border);
background-color: var(--semi-color-bg-0);
flex-shrink: 0;
position: sticky;
top: 0;
z-index: 5;
}
.pricing-view-container {
flex: 1;
overflow: auto;
}
.pricing-view-container-mobile {
flex: 1;
overflow: auto;
min-height: 0;
}
\ No newline at end of file
......@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
*/
import React from 'react';
import ModelPricingPage from '../../components/table/model-pricing';
import ModelPricingPage from '../../components/table/model-pricing/layout/PricingPage';
const Pricing = () => (
<>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment