diff --git a/web/src/components/common/ui/SelectableButtonGroup.jsx b/web/src/components/common/ui/SelectableButtonGroup.jsx index a75c537e..dd7fd8ab 100644 --- a/web/src/components/common/ui/SelectableButtonGroup.jsx +++ b/web/src/components/common/ui/SelectableButtonGroup.jsx @@ -17,9 +17,9 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { useIsMobile } from '../../../hooks/common/useIsMobile'; -import { Divider, Button, Tag, Row, Col, Collapsible, Checkbox } from '@douyinfe/semi-ui'; +import { Divider, Button, Tag, Row, Col, Collapsible, Checkbox, Skeleton } from '@douyinfe/semi-ui'; import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons'; /** @@ -34,6 +34,7 @@ import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons'; * @param {boolean} collapsible 是否支持折叠,默认true * @param {number} collapseHeight 折叠时的高度,默认200 * @param {boolean} withCheckbox 是否启用前缀 Checkbox 来控制激活状态 + * @param {boolean} loading 是否处于加载状态 */ const SelectableButtonGroup = ({ title, @@ -44,16 +45,36 @@ const SelectableButtonGroup = ({ style = {}, collapsible = true, collapseHeight = 200, - withCheckbox = false + withCheckbox = false, + loading = false }) => { const [isOpen, setIsOpen] = useState(false); + const [showSkeleton, setShowSkeleton] = useState(loading); + const [skeletonCount] = useState(6); 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; + const loadingStartRef = useRef(Date.now()); const contentRef = useRef(null); + useEffect(() => { + if (loading) { + loadingStartRef.current = Date.now(); + setShowSkeleton(true); + } else { + const elapsed = Date.now() - loadingStartRef.current; + const remaining = Math.max(0, 500 - elapsed); + if (remaining === 0) { + setShowSkeleton(false); + } else { + const timer = setTimeout(() => setShowSkeleton(false), remaining); + return () => clearTimeout(timer); + } + } + }, [loading]); + const maskStyle = isOpen ? {} : { @@ -81,14 +102,57 @@ const SelectableButtonGroup = ({ gap: 4, }; - const contentElement = ( + const renderSkeletonButtons = () => { + + const placeholder = ( + + {Array.from({ length: skeletonCount }).map((_, index) => ( + + + {withCheckbox && ( + + )} + + + + ))} + + ); + + return ( + + ); + }; + + const contentElement = showSkeleton ? renderSkeletonButtons() : ( {items.map((item) => { const isActive = Array.isArray(activeValue) ? activeValue.includes(item.value) : activeValue === item.value; - // 当启用前缀 Checkbox 时,按钮本身不可点击,仅 Checkbox 可控制状态切换 if (withCheckbox) { return ( {title && ( - {title} + {showSkeleton ? ( + + ) : ( + title + )} )} - {needCollapse ? ( + {needCollapse && !showSkeleton ? ( {contentElement} diff --git a/web/src/components/table/model-pricing/PricingSidebar.jsx b/web/src/components/table/model-pricing/PricingSidebar.jsx index 8605f5c9..39afed0f 100644 --- a/web/src/components/table/model-pricing/PricingSidebar.jsx +++ b/web/src/components/table/model-pricing/PricingSidebar.jsx @@ -38,6 +38,7 @@ const PricingSidebar = ({ setFilterGroup, filterQuotaType, setFilterQuotaType, + loading, t, ...categoryProps }) => { @@ -77,14 +78,15 @@ const PricingSidebar = ({ setCurrency={setCurrency} showRatio={showRatio} setShowRatio={setShowRatio} + loading={loading} t={t} /> - + - + - + ); }; diff --git a/web/src/components/table/model-pricing/filter/PricingCategories.jsx b/web/src/components/table/model-pricing/filter/PricingCategories.jsx index 22eb98a2..7a979508 100644 --- a/web/src/components/table/model-pricing/filter/PricingCategories.jsx +++ b/web/src/components/table/model-pricing/filter/PricingCategories.jsx @@ -20,7 +20,7 @@ For commercial licensing, please contact support@quantumnous.com import React from 'react'; import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup'; -const PricingCategories = ({ activeKey, setActiveKey, modelCategories, categoryCounts, availableCategories, t }) => { +const PricingCategories = ({ activeKey, setActiveKey, modelCategories, categoryCounts, availableCategories, loading = false, t }) => { const items = Object.entries(modelCategories) .filter(([key]) => availableCategories.includes(key)) .map(([key, category]) => ({ @@ -36,6 +36,7 @@ const PricingCategories = ({ activeKey, setActiveKey, modelCategories, categoryC items={items} activeValue={activeKey} onChange={setActiveKey} + loading={loading} t={t} /> ); diff --git a/web/src/components/table/model-pricing/filter/PricingDisplaySettings.jsx b/web/src/components/table/model-pricing/filter/PricingDisplaySettings.jsx index 31296a1b..9d4d8312 100644 --- a/web/src/components/table/model-pricing/filter/PricingDisplaySettings.jsx +++ b/web/src/components/table/model-pricing/filter/PricingDisplaySettings.jsx @@ -29,6 +29,7 @@ const PricingDisplaySettings = ({ setCurrency, showRatio, setShowRatio, + loading = false, t }) => { const items = [ @@ -81,6 +82,7 @@ const PricingDisplaySettings = ({ onChange={handleChange} withCheckbox collapsible={false} + loading={loading} t={t} /> @@ -91,6 +93,7 @@ const PricingDisplaySettings = ({ activeValue={currency} onChange={setCurrency} collapsible={false} + loading={loading} t={t} /> )} diff --git a/web/src/components/table/model-pricing/filter/PricingGroups.jsx b/web/src/components/table/model-pricing/filter/PricingGroups.jsx index 4b851748..4ce67ff9 100644 --- a/web/src/components/table/model-pricing/filter/PricingGroups.jsx +++ b/web/src/components/table/model-pricing/filter/PricingGroups.jsx @@ -25,9 +25,11 @@ import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup'; * @param {string} filterGroup 当前选中的分组,'all' 表示不过滤 * @param {Function} setFilterGroup 设置选中分组 * @param {Record} usableGroup 后端返回的可用分组对象 + * @param {Array} models 模型列表 + * @param {boolean} loading 是否加载中 * @param {Function} t i18n */ -const PricingGroups = ({ filterGroup, setFilterGroup, usableGroup = {}, models = [], t }) => { +const PricingGroups = ({ filterGroup, setFilterGroup, usableGroup = {}, models = [], loading = false, t }) => { const groups = ['all', ...Object.keys(usableGroup)]; const items = groups.map((g) => { @@ -50,6 +52,7 @@ const PricingGroups = ({ filterGroup, setFilterGroup, usableGroup = {}, models = items={items} activeValue={filterGroup} onChange={setFilterGroup} + loading={loading} t={t} /> ); diff --git a/web/src/components/table/model-pricing/filter/PricingQuotaTypes.jsx b/web/src/components/table/model-pricing/filter/PricingQuotaTypes.jsx index 5e6dcceb..bce56abe 100644 --- a/web/src/components/table/model-pricing/filter/PricingQuotaTypes.jsx +++ b/web/src/components/table/model-pricing/filter/PricingQuotaTypes.jsx @@ -24,9 +24,11 @@ import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup'; * 计费类型筛选组件 * @param {string|'all'|0|1} filterQuotaType 当前值 * @param {Function} setFilterQuotaType setter + * @param {Array} models 模型列表 + * @param {boolean} loading 是否加载中 * @param {Function} t i18n */ -const PricingQuotaTypes = ({ filterQuotaType, setFilterQuotaType, models = [], t }) => { +const PricingQuotaTypes = ({ filterQuotaType, setFilterQuotaType, models = [], loading = false, t }) => { const qtyCount = (type) => models.filter(m => type === 'all' ? true : m.quota_type === type).length; const items = [ @@ -41,6 +43,7 @@ const PricingQuotaTypes = ({ filterQuotaType, setFilterQuotaType, models = [], t items={items} activeValue={filterQuotaType} onChange={setFilterQuotaType} + loading={loading} t={t} /> ); diff --git a/web/src/components/table/model-pricing/modal/PricingFilterModal.jsx b/web/src/components/table/model-pricing/modal/PricingFilterModal.jsx index 483104f7..6182fb01 100644 --- a/web/src/components/table/model-pricing/modal/PricingFilterModal.jsx +++ b/web/src/components/table/model-pricing/modal/PricingFilterModal.jsx @@ -44,6 +44,7 @@ const PricingFilterModal = ({ setFilterGroup, filterQuotaType, setFilterQuotaType, + loading, ...categoryProps } = sidebarProps; @@ -105,16 +106,18 @@ const PricingFilterModal = ({ setCurrency={setCurrency} showRatio={showRatio} setShowRatio={setShowRatio} + loading={loading} t={t} /> - + @@ -122,6 +125,7 @@ const PricingFilterModal = ({ filterQuotaType={filterQuotaType} setFilterQuotaType={setFilterQuotaType} models={categoryProps.models} + loading={loading} t={t} />