From fee95041076902bb128c2b33aa00425ce53a0573 Mon Sep 17 00:00:00 2001 From: fengsilin Date: Wed, 1 Jul 2026 12:16:42 +0800 Subject: [PATCH] feat: render display pricing on pricing page --- .../modal/components/ModelPricingTable.jsx | 71 ++++++++++++++++++- .../modal/components/UserPriceComparison.jsx | 11 ++- .../view/card/PricingCardView.jsx | 42 ++++++++++- .../view/table/PricingTableColumns.jsx | 26 +++++++ web/src/i18n/locales/en.json | 11 ++- web/src/i18n/locales/zh-CN.json | 11 ++- 6 files changed, 165 insertions(+), 7 deletions(-) diff --git a/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx b/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx index ca38963..9ec627b 100644 --- a/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx +++ b/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx @@ -22,9 +22,12 @@ import { Card, Avatar, Typography, Table, Tag } from '@douyinfe/semi-ui'; import { IconCoinMoneyStroked } from '@douyinfe/semi-icons'; import { calculateModelPrice, + formatDisplayPricingItem, formatMatrixPrice, getMatrixPricingUnit, + hasDisplayPricing, hasMatrixPricing, + normalizeDisplayPricingItems, } from '../../../../../helpers'; const { Text } = Typography; @@ -44,6 +47,62 @@ const ModelPricingTable = ({ ? modelData.enable_groups : []; const autoChain = autoGroups.filter((g) => modelEnableGroups.includes(g)); + const renderDisplayPricingTable = () => { + const items = normalizeDisplayPricingItems(modelData).map((item) => + formatDisplayPricingItem({ item, displayPrice, t }), + ); + const columns = [ + { + title: t('规格'), + dataIndex: 'specification', + render: (text) => {text}, + }, + { + title: t('供应商说明'), + dataIndex: 'official_supplier_tip', + render: (text) => {text || '-'}, + }, + { + title: t('价格'), + dataIndex: 'formattedPrice', + render: (text, record) => ( +
+
{text}
+ {record.originalPrice && ( +
+ {t('原价')} {record.originalPrice} +
+ )} +
+ ), + }, + { + title: t('折扣'), + dataIndex: 'discountText', + render: (text) => text || '-', + }, + ]; + + return ( + <> +
+ {t('展示价格仅用于估算,实际消耗以任务完成后的积分扣减为准。')} +
+ ({ + ...item, + key: `${item.specification}-${index}`, + }))} + columns={columns} + pagination={false} + size='small' + bordered={false} + className='!rounded-lg' + /> + + ); + }; + const renderMatrixPriceTable = () => { const cfg = modelData?.pricing_config; const dimensions = cfg?.dimensions || []; @@ -124,6 +183,9 @@ const ModelPricingTable = ({ }; const renderGroupPriceTable = () => { + if (hasDisplayPricing(modelData)) { + return renderDisplayPricingTable(); + } if (hasMatrixPricing(modelData)) { return renderMatrixPriceTable(); } @@ -144,6 +206,7 @@ const ModelPricingTable = ({ tokenUnit, displayPrice, currency, + t, }) : { inputPrice: '-', outputPrice: '-', price: '-' }; @@ -322,9 +385,13 @@ const ModelPricingTable = ({
- {t('分组价格')} + + {hasDisplayPricing(modelData) ? t('展示价格') : t('分组价格')} +
- {t('不同用户分组的价格信息')} + {hasDisplayPricing(modelData) + ? t('展示价格,仅用于估算') + : t('不同用户分组的价格信息')}
diff --git a/web/src/components/table/model-pricing/modal/components/UserPriceComparison.jsx b/web/src/components/table/model-pricing/modal/components/UserPriceComparison.jsx index a103a89..ff35788 100644 --- a/web/src/components/table/model-pricing/modal/components/UserPriceComparison.jsx +++ b/web/src/components/table/model-pricing/modal/components/UserPriceComparison.jsx @@ -7,17 +7,22 @@ const { Text } = Typography; const UserPriceComparison = ({ modelName, + modelData, currency, tokenUnit, displayPrice, t, }) => { + const hasDisplayPricing = + Array.isArray(modelData?.display_pricing) && + modelData.display_pricing.length > 0; + const [loading, setLoading] = useState(false); const [priceData, setPriceData] = useState(null); useEffect(() => { const fetchUserPrice = async () => { - if (!modelName) return; + if (!modelName || hasDisplayPricing) return; setLoading(true); try { const res = await API.get( @@ -37,7 +42,7 @@ const UserPriceComparison = ({ } }; fetchUserPrice(); - }, [modelName]); + }, [modelName, hasDisplayPricing]); const currencySymbol = useMemo(() => { if (currency === 'CNY') return '¥'; @@ -74,6 +79,8 @@ const UserPriceComparison = ({ return `${currencySymbol}${num.toFixed(4)}`; }; + if (hasDisplayPricing) return null; + if (loading) { return ( diff --git a/web/src/components/table/model-pricing/view/card/PricingCardView.jsx b/web/src/components/table/model-pricing/view/card/PricingCardView.jsx index 0418390..5e16842 100644 --- a/web/src/components/table/model-pricing/view/card/PricingCardView.jsx +++ b/web/src/components/table/model-pricing/view/card/PricingCardView.jsx @@ -38,6 +38,7 @@ import { import { stringToColor, getLobeHubIcon, + hasDisplayPricing, hasMatrixPricing, calculateModelPrice, } from '../../../../../helpers'; @@ -163,8 +164,41 @@ const PricingCardView = ({ tokenUnit, displayPrice, currency, + t, }); + if (priceData?.isDisplayPricing) { + return ( +
+ {String(priceData.price || '') + .split('\n') + .map((line) => ( +
+ {line} +
+ ))} +
{t('展示价格')}
+ {(priceData.details || []).map((detail, detailIndex) => ( +
+ {detail.specification} + + {detail.formattedPrice} + +
+ ))} + {priceData.hiddenDetailCount > 0 && ( +
+ + {priceData.hiddenDetailCount} +
+ )} +
+ ); + } + if (!priceData?.isMatrix) { return null; } @@ -206,7 +240,13 @@ const PricingCardView = ({ - ); - if (hasMatrixPricing(record)) { + if (hasDisplayPricing(record)) { + billingTag = ( + + {t('展示价格')} + + ); + } else if (hasMatrixPricing(record)) { billingTag = ( {t('矩阵计费')} diff --git a/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx b/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx index 2c34870..e9cff94 100644 --- a/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx +++ b/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx @@ -24,6 +24,7 @@ import { renderModelTag, stringToColor, calculateModelPrice, + hasDisplayPricing, hasMatrixPricing, getLobeHubIcon, } from '../../../../../helpers'; @@ -53,6 +54,13 @@ function renderQuotaType(type, t) { } function renderBillingType(record, t) { + if (hasDisplayPricing(record)) { + return ( + + {t('展示价格')} + + ); + } if (hasMatrixPricing(record)) { return ( @@ -137,6 +145,7 @@ export const getPricingTableColumns = ({ tokenUnit, displayPrice, currency, + t, }); priceDataCache.set(record, cache); } @@ -244,6 +253,23 @@ export const getPricingTableColumns = ({ render: (text, record, index) => { const priceData = getPriceData(record); + if (priceData.isDisplayPricing) { + return ( +
+ {String(priceData.price || '') + .split('\n') + .map((line) => ( +
+ {line} +
+ ))} +
+ {t('展示价格,仅用于估算')} +
+
+ ); + } + if (priceData.isMatrix) { return (
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index ed3b03e..cbc0e49 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -3295,5 +3295,14 @@ "重试启动失败": "Failed to start retry", "重试已启动": "Retry started", "验证完成": "Verification complete", - "暂无数据": "No data" + "暂无数据": "No data", + "展示价格,仅用于估算": "Display price, estimate only", + "展示价格仅用于估算,实际消耗以任务完成后的积分扣减为准。": "Display price is only an estimate. Actual consumption is based on the final quota deduction after the task completes.", + "规格": "Specification", + "供应商说明": "Supplier tip", + "折扣": "Discount", + "second": "second", + "call": "call", + "image": "image", + "video": "video" } diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index f082d8e..1c8848c 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -3249,5 +3249,14 @@ "重试启动失败": "重试启动失败", "重试已启动": "重试已启动", "验证完成": "验证完成", - "暂无数据": "暂无数据" + "暂无数据": "暂无数据", + "展示价格,仅用于估算": "展示价格,仅用于估算", + "展示价格仅用于估算,实际消耗以任务完成后的积分扣减为准。": "展示价格仅用于估算,实际消耗以任务完成后的积分扣减为准。", + "规格": "规格", + "供应商说明": "供应商说明", + "折扣": "折扣", + "second": "秒", + "call": "次", + "image": "张图片", + "video": "个视频" }