Bläddra i källkod

feat: add display pricing frontend helpers

master
fengsilin 1 vecka sedan
förälder
incheckning
cc97834e5b
2 ändrade filer med 193 tillägg och 0 borttagningar
  1. +101
    -0
      web/src/helpers/utils.jsx
  2. +92
    -0
      web/src/helpers/utils.test.js

+ 101
- 0
web/src/helpers/utils.jsx Visa fil

@@ -609,6 +609,93 @@ export const selectFilter = (input, option) => {

// -------------------------------
// 模型定价计算工具函数
export const hasDisplayPricing = (record) =>
Array.isArray(record?.display_pricing) && record.display_pricing.length > 0;

export const normalizeDisplayPricingItems = (record) => {
if (!hasDisplayPricing(record)) return [];
return [...record.display_pricing]
.map((item) => {
const discountRate =
Number(item.discount_rate) > 0 && Number(item.discount_rate) <= 1
? Number(item.discount_rate)
: 1;
const price = Number(item.price || 0);
return {
...item,
price,
discount_rate: discountRate,
effectivePrice: price * discountRate,
unit: item.unit || 'call',
sort_order: Number(item.sort_order || 0),
};
})
.sort((a, b) => {
if (a.sort_order !== b.sort_order) return a.sort_order - b.sort_order;
return String(a.specification || '').localeCompare(
String(b.specification || ''),
);
});
};

export const formatDisplayPricingItem = ({
item,
displayPrice,
t = (key) => key,
}) => {
const unit = item.unit || 'call';
const effectivePrice = Number(item.effectivePrice ?? item.price ?? 0);
const hasDiscount = item.discount_rate > 0 && item.discount_rate < 1;
return {
...item,
formattedPrice: `${displayPrice(effectivePrice)} / ${t(unit)}`,
originalPrice: hasDiscount
? `${displayPrice(Number(item.price || 0))} / ${t(unit)}`
: '',
discountText: hasDiscount
? `${Math.round(item.discount_rate * 100) / 10}折`
: '-',
};
};

export const summarizeDisplayPricing = ({
record,
displayPrice,
t = (key) => key,
}) => {
const items = normalizeDisplayPricingItems(record);
if (items.length === 0) return null;

const groups = new Map();
items.forEach((item) => {
const unit = item.unit || 'call';
const list = groups.get(unit) || [];
list.push(item);
groups.set(unit, list);
});

const lines = Array.from(groups.entries()).map(([unit, list]) => {
const prices = list.map((item) => Number(item.effectivePrice || 0));
const min = Math.min(...prices);
const max = Math.max(...prices);
const priceText =
min === max
? displayPrice(min)
: `${displayPrice(min)} - ${displayPrice(max)}`;
return `${priceText} / ${t(unit)}`;
});

return {
isDisplayPricing: true,
price: lines.join('\n'),
unit: '',
details: items
.slice(0, 3)
.map((item) => formatDisplayPricingItem({ item, displayPrice, t })),
hiddenDetailCount: Math.max(0, items.length - 3),
};
};

export const hasMatrixPricing = (record) =>
!!(
record?.pricing_config &&
@@ -724,6 +811,7 @@ export const calculateModelPrice = ({
displayPrice,
currency,
precision = 4,
t = (key) => key,
}) => {
// 1. 选择实际使用的分组
const ratios = groupRatio || {};
@@ -754,6 +842,19 @@ export const calculateModelPrice = ({
}

// 2. 根据计费类型计算价格
if (hasDisplayPricing(record)) {
const summary = summarizeDisplayPricing({
record,
displayPrice,
t,
});
return {
...summary,
usedGroup,
usedGroupRatio,
};
}

if (hasMatrixPricing(record)) {
const summary = summarizeMatrixPricing({
record,


+ 92
- 0
web/src/helpers/utils.test.js Visa fil

@@ -18,7 +18,10 @@ globalThis.localStorage = {
const {
calculateModelPrice,
formatMatrixPrice,
hasDisplayPricing,
hasMatrixPricing,
normalizeDisplayPricingItems,
summarizeDisplayPricing,
summarizeMatrixPricing,
} = await import('./utils.jsx');

@@ -119,4 +122,93 @@ describe('model pricing helpers', () => {
usedGroupRatio: 1,
});
});

it('normalizes and sorts display pricing items', () => {
const record = {
display_pricing: [
{
specification: '1080p',
price: 0.58333,
unit: 'second',
sort_order: 3,
discount_rate: 0,
},
{
specification: '768p-6s',
price: 0.33333,
unit: 'second',
sort_order: 1,
discount_rate: 0.8,
},
],
};

expect(hasDisplayPricing(record)).toBe(true);
const items = normalizeDisplayPricingItems(record);
expect(items.map((item) => item.specification)).toEqual([
'768p-6s',
'1080p',
]);
expect(items[0].effectivePrice).toBeCloseTo(0.266664);
expect(items[1].discount_rate).toBe(1);
});

it('summarizes display pricing by unit', () => {
const summary = summarizeDisplayPricing({
record: {
display_pricing: [
{
specification: '768p-6s',
price: 0.33333,
unit: 'second',
sort_order: 1,
discount_rate: 1,
},
{
specification: '1080p',
price: 0.58333,
unit: 'second',
sort_order: 2,
discount_rate: 1,
},
],
},
displayPrice,
t: (key) => key,
});

expect(summary.isDisplayPricing).toBe(true);
expect(summary.price).toBe('$0.333 - $0.583 / second');
expect(summary.details).toHaveLength(2);
});

it('calculateModelPrice prefers display pricing over matrix pricing', () => {
const result = calculateModelPrice({
record: {
quota_type: 1,
model_price: 99,
pricing_config: {
billing_unit: 'per_call',
table: [{ duration: 5, price: 88, source: 'manual' }],
},
display_pricing: [
{
specification: '768p-6s',
price: 0.33333,
unit: 'second',
sort_order: 1,
discount_rate: 1,
},
],
},
selectedGroup: 'default',
groupRatio: { default: 1 },
tokenUnit: 'M',
displayPrice,
currency: 'USD',
});

expect(result.isDisplayPricing).toBe(true);
expect(result.price).toBe('$0.333 / second');
});
});

Laddar…
Avbryt
Spara