From f19095f96df59a257588d7755753eb2cc86ea69b Mon Sep 17 00:00:00 2001 From: feeeei Date: Mon, 24 Aug 2026 11:23:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=B9=BF=E5=9C=BA=EF=BC=9A?= =?UTF-8?q?=E5=88=86=E6=97=B6=E6=97=B6=E6=AE=B5=E8=A1=8C=E6=98=8E=E7=A1=AE?= =?UTF-8?q?=E4=B8=8D=E5=90=AB=E9=AB=98=E5=B3=B0=E5=80=8D=E7=8E=87=E5=8F=A3?= =?UTF-8?q?=E5=BE=84=E5=B9=B6=E6=8A=AB=E9=9C=B2=E5=8F=A0=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实扣倍率为 基础 × 高峰 × 分时;时段行价格与整表一致,按不含高峰的口径展示 - 分组启用高峰时,时段行 tooltip 披露与高峰窗口重叠的部分实付再乘高峰倍率 - PlazaGroupSection 把高峰窗口描述与倍率传入价格表 --- .../modelPlaza/PlazaGroupSection.vue | 13 +++++++--- .../modelPlaza/PlazaModelPricingTable.vue | 21 +++++++++++++-- .../__tests__/PlazaGroupSection.spec.ts | 24 +++++++++++++++++ .../__tests__/PlazaModelPricingTable.spec.ts | 26 ++++++++++++++++++- frontend/src/i18n/locales/en/dashboard.ts | 2 ++ frontend/src/i18n/locales/zh/dashboard.ts | 1 + 6 files changed, 81 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/modelPlaza/PlazaGroupSection.vue b/frontend/src/components/modelPlaza/PlazaGroupSection.vue index 38bdea058e..53a9c77cf9 100644 --- a/frontend/src/components/modelPlaza/PlazaGroupSection.vue +++ b/frontend/src/components/modelPlaza/PlazaGroupSection.vue @@ -61,6 +61,8 @@ :user-rate-multiplier="group.user_rate_multiplier ?? null" :image-rate-independent="group.image_rate_independent" :image-rate-multiplier="group.image_rate_multiplier" + :peak-window="peakWindow" + :peak-rate-multiplier="group.peak_rate_multiplier" />

{{ t('modelPlaza.detail.noModels') }} @@ -88,14 +90,19 @@ const props = defineProps<{ const { t } = useI18n() const appStore = useAppStore() -const peakNote = computed(() => { +/** 高峰窗口描述(含倍率与服务器时区标注);分组未启用高峰为空串。 */ +const peakWindow = computed(() => { if (!hasPeakRate(props.group)) return '' - const window = formatPeakRateWindow( + return formatPeakRateWindow( props.group, serverTimezoneLabel(appStore.cachedPublicSettings?.server_utc_offset) ) +}) + +const peakNote = computed(() => { + if (!peakWindow.value) return '' return t('modelPlaza.detail.peakNote', { - window, + window: peakWindow.value, multiplier: props.group.peak_rate_multiplier }) }) diff --git a/frontend/src/components/modelPlaza/PlazaModelPricingTable.vue b/frontend/src/components/modelPlaza/PlazaModelPricingTable.vue index 7a51549109..d4aac37a07 100644 --- a/frontend/src/components/modelPlaza/PlazaModelPricingTable.vue +++ b/frontend/src/components/modelPlaza/PlazaModelPricingTable.vue @@ -308,6 +308,13 @@ const props = defineProps<{ /** 生图独立倍率:true 时图片计费模型的实付倍率取 imageRateMultiplier,不取分组/专属倍率。 */ imageRateIndependent?: boolean imageRateMultiplier?: number | null + /** + * 高峰窗口描述(含倍率与服务器时区标注),空串/缺省 = 分组未启用高峰。 + * 表格所有价格均为不含高峰因子的口径,该窗口仅用于分时时段行的 tooltip 披露: + * 与高峰重叠的部分实付还会再乘高峰倍率。 + */ + peakWindow?: string + peakRateMultiplier?: number | null }>() const { t } = useI18n() @@ -428,12 +435,22 @@ function timePeriods(m: PlazaModel): PlazaTimePricingPeriod[] { return m.time_pricing?.periods ?? [] } -/** 时段行 tooltip:仅工作日生效的配置换用带周末回落说明的文案。 */ +/** + * 时段行 tooltip:仅工作日生效的配置换用带周末回落说明的文案; + * 分组启用高峰倍率时追加披露——本行价格不含高峰因子,与高峰窗口重叠的部分实付再乘高峰倍率。 + */ function timePricingRowHint(m: PlazaModel): string { const key = m.time_pricing?.weekdays_only ? 'modelPlaza.table.timePricingRowHintWeekdays' : 'modelPlaza.table.timePricingRowHint' - return t(key, { timezone: m.time_pricing?.timezone }) + let hint = t(key, { timezone: m.time_pricing?.timezone }) + if (props.peakWindow) { + hint += t('modelPlaza.table.timePricingRowHintPeak', { + window: props.peakWindow, + multiplier: props.peakRateMultiplier ?? 1 + }) + } + return hint } /** “00:30–08:30”;整分钟的 HH:mm:ss 省略秒。 */ diff --git a/frontend/src/components/modelPlaza/__tests__/PlazaGroupSection.spec.ts b/frontend/src/components/modelPlaza/__tests__/PlazaGroupSection.spec.ts index ed30f10431..aad4e25a35 100644 --- a/frontend/src/components/modelPlaza/__tests__/PlazaGroupSection.spec.ts +++ b/frontend/src/components/modelPlaza/__tests__/PlazaGroupSection.spec.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { mount } from '@vue/test-utils' import PlazaGroupSection from '../PlazaGroupSection.vue' +import PlazaModelPricingTable from '../PlazaModelPricingTable.vue' import type { ModelPlazaGroup, PlazaModel } from '@/api/modelPlaza' vi.mock('vue-i18n', async () => { @@ -113,3 +114,26 @@ describe('PlazaGroupSection 长上下文说明', () => { expect(wrapper.text()).not.toContain(NOTE) }) }) + +describe('PlazaGroupSection 高峰配置传递', () => { + it('分组启用高峰时把窗口描述与倍率传给价格表', () => { + const wrapper = mountSection( + group({ + subscription_type: 'subscription', + peak_rate_enabled: true, + peak_start: '14:00', + peak_end: '18:00', + peak_rate_multiplier: 1.5 + }) + ) + const table = wrapper.findComponent(PlazaModelPricingTable) + // appStore mock 无 server_utc_offset,窗口描述不带时区标注 + expect(table.props('peakWindow')).toBe('14:00-18:00 ×1.5') + expect(table.props('peakRateMultiplier')).toBe(1.5) + }) + + it('分组未启用高峰时窗口描述为空串', () => { + const wrapper = mountSection(group()) + expect(wrapper.findComponent(PlazaModelPricingTable).props('peakWindow')).toBe('') + }) +}) diff --git a/frontend/src/components/modelPlaza/__tests__/PlazaModelPricingTable.spec.ts b/frontend/src/components/modelPlaza/__tests__/PlazaModelPricingTable.spec.ts index 78a5c0c76b..b4acf6efe5 100644 --- a/frontend/src/components/modelPlaza/__tests__/PlazaModelPricingTable.spec.ts +++ b/frontend/src/components/modelPlaza/__tests__/PlazaModelPricingTable.spec.ts @@ -43,7 +43,12 @@ function mountTable( models: PlazaModel[], rateMultiplier: number, userRateMultiplier?: number | null, - extraProps?: { imageRateIndependent?: boolean; imageRateMultiplier?: number | null } + extraProps?: { + imageRateIndependent?: boolean + imageRateMultiplier?: number | null + peakWindow?: string + peakRateMultiplier?: number | null + } ) { return mount(PlazaModelPricingTable, { props: { models, rateMultiplier, userRateMultiplier: userRateMultiplier ?? null, ...extraProps } @@ -602,6 +607,25 @@ describe('PlazaModelPricingTable 分时计价', () => { expect(wrapper.find('[title="modelPlaza.table.timePricingRowHint"]').exists()).toBe(true) }) + it('分组启用高峰倍率时时段行 tooltip 追加高峰披露,价格与倍率列保持不含高峰的口径', () => { + const wrapper = mountTable([timePricedModel()], 0.8, null, { + peakWindow: '14:00-18:00 ×1.5 (UTC+08:00)', + peakRateMultiplier: 1.5 + }) + const nightCells = wrapper.findAll('tbody tr')[1].findAll('td') + const title = nightCells[0].find('[title*="modelPlaza.table.timePricingRowHint"]').attributes('title') + expect(title).toContain('modelPlaza.table.timePricingRowHintPeak') + // 行内数字仍是 基础倍率 × 时段倍率(0.8 × 0.5),高峰只进披露不进价格 + expect(nightCells[1].text()).toContain('$1.20') + expect(nightCells[7].text()).toContain('0.4x') + }) + + it('分组未启用高峰(peakWindow 缺省)时 tooltip 不含高峰披露', () => { + const wrapper = mountTable([timePricedModel()], 1) + const badge = wrapper.find('[title*="modelPlaza.table.timePricingRowHint"]') + expect(badge.attributes('title')).not.toContain('modelPlaza.table.timePricingRowHintPeak') + }) + it('无分时倍率时只有一行,不渲染时段标注', () => { const wrapper = mountTable([tokenModel()], 1) expect(wrapper.findAll('tbody tr')).toHaveLength(1) diff --git a/frontend/src/i18n/locales/en/dashboard.ts b/frontend/src/i18n/locales/en/dashboard.ts index 6e9a901cc7..65fa9f480c 100644 --- a/frontend/src/i18n/locales/en/dashboard.ts +++ b/frontend/src/i18n/locales/en/dashboard.ts @@ -607,6 +607,8 @@ export default { timePricingRowHint: 'Requests made within this period ({timezone} time) are billed at the prices in this row', timePricingRowHintWeekdays: 'On weekdays (Mon–Fri) only, requests made within this period ({timezone} time) are billed at the prices in this row; weekends use the standard prices', + timePricingRowHintPeak: + '; prices in this row exclude the peak-hour rate — where this period overlaps the peak hours {window}, the overlapping portion is additionally multiplied by ×{multiplier}', timePricingWeekdays: 'Weekdays', timePricingRateHint: 'Effective rate {rate} × period multiplier {multiplier}', paidPrice: 'Your Price (Discounted)', diff --git a/frontend/src/i18n/locales/zh/dashboard.ts b/frontend/src/i18n/locales/zh/dashboard.ts index c741aa60fb..044c2e1bf0 100644 --- a/frontend/src/i18n/locales/zh/dashboard.ts +++ b/frontend/src/i18n/locales/zh/dashboard.ts @@ -612,6 +612,7 @@ export default { timePricingRowHint: '按 {timezone} 时间,在该时段内发起的请求按本行价格计费', timePricingRowHintWeekdays: '按 {timezone} 时间,仅工作日(周一至周五)在该时段内发起的请求按本行价格计费,周末全天按标准价', + timePricingRowHintPeak: ';本行价格未含高峰倍率,与高峰时段 {window} 重叠的部分实付再乘 ×{multiplier}', timePricingWeekdays: '工作日', timePricingRateHint: '生效倍率 {rate} × 时段倍率 {multiplier}', paidPrice: '实付价格(折后)',