fix(web): 修复阶梯计费 Base64 解码失败与标签不匹配导致的显示错误 (#4530)
* fix(web): 修复阶梯计费表达式解析与匹配逻辑 - 优化 Base64 解码逻辑:引入 UTF-8 感知的解码方法(使用 TextDecoder/Uint8Array),替换原有的简单 `atob`,修复包含非拉丁字符时解码失败的问题。 - 增强阶梯标签匹配机制:新增标签规范化处理(移除空格、统一大小写、转换 `<`/`≤`/`<=` 等符号),确保日志记录中的标签能够与配置中的标签准确匹配。 - 将上述修复同步应用于 default 和 classic 两套前端主题。 * refactor(web): 完善 Base64 解码函数的类型声明 - 根据 CodeRabbitAI 的代码审查建议,将 `decodeBillingExprB64` 方法中 `Array.prototype.map` 回调函数的参数类型由 `any` 替换为更精确的 `number`。 - 提高了代码的类型安全性与可读性。 * fix(web): 修复动态价格明细表中阶梯高亮未能正确匹配的问题 - 在 default 主题的 `DynamicPricingBreakdown` 组件中,引入 `normalizeTierLabel` 函数。 - 替换原有对 `matchedTierLabel` 的严格相等判定,确保在包含全半角符号(如 `≤`/`<=`)或存在空格等格式不一致的场景下,日志详情中的表格依然能准确高亮(Matched)当前命中的对应计费阶梯。 * refactor(web): 移除阶梯计费标签不匹配时的强制兜底逻辑 - 在 default 和 classic 主题中,修改 `resolveMatchedTier` 和相关的阶梯匹配方法,当日志中 `matched_tier` 无法与表达式中的阶梯标签严格对应时,直接返回 `null` 而不再默认退化展示第一阶梯(`tiers[0]`)的价格。 - 遵循“数据准确性优先”的计费展示准则,防止因匹配失败而向用户展示猜测出的单价,避免产生账单误导及客诉风险。 - 在 Classic 主题账单卡片中,对于无法匹配的异常账单明确展示“未匹配到对应阶梯”的提示。 * fix(web): 修复阶梯计费标签正则匹配的短路问题 - 根据 CodeRabbitAI 的代码审查反馈,修正了 `normalizeLabel`(以及 `normalizeTierLabel`)函数中的正则表达式分支顺序。 - 将原本的 `/<|≤|<=/` 调整为 `/<=|≤|</`,以修复 JavaScript 正则引擎从左到右匹配时,会将 `<=` 中的 `<` 优先短路匹配,导致残留 `=` 号的问题。 - 确保了双字符操作符(如 `<=`、`>=`)现在能够被正确完整地替换为单字符(`<`、`>`),保证了计费阶梯日志匹配的准确性。 * fix(web): 完善阶梯计费未匹配展示 --------- Co-authored-by: CaIon <i@caion.me>
This commit is contained in:
+46
-38
@@ -115,51 +115,59 @@ function buildDetailSegments(
|
||||
const text = prices.join(' / ')
|
||||
return showUnit ? `${text}/M` : text
|
||||
}
|
||||
const isTieredExpr = other.billing_mode === 'tiered_expr'
|
||||
const tieredSummary = getTieredBillingSummary(other)
|
||||
if (tieredSummary) {
|
||||
const baseEntries = tieredSummary.priceEntries
|
||||
.filter((entry) => ['inputPrice', 'outputPrice'].includes(entry.field))
|
||||
.map((entry) => formatPriceCompact(entry.price))
|
||||
if (baseEntries.length > 0) {
|
||||
const tierLabel = tieredSummary.tier.label || t('Default')
|
||||
segments.push({
|
||||
text: `${tierLabel} · ${formatPriceList(baseEntries, true)}`,
|
||||
})
|
||||
}
|
||||
if (isTieredExpr) {
|
||||
if (tieredSummary) {
|
||||
const baseEntries = tieredSummary.priceEntries
|
||||
.filter((entry) => ['inputPrice', 'outputPrice'].includes(entry.field))
|
||||
.map((entry) => formatPriceCompact(entry.price))
|
||||
if (baseEntries.length > 0) {
|
||||
const tierLabel = tieredSummary.tier.label || t('Default')
|
||||
segments.push({
|
||||
text: `${tierLabel} · ${formatPriceList(baseEntries, true)}`,
|
||||
})
|
||||
}
|
||||
|
||||
const cacheEntries = tieredSummary.priceEntries
|
||||
.filter((entry) =>
|
||||
[
|
||||
'cacheReadPrice',
|
||||
'cacheCreatePrice',
|
||||
'cacheCreate1hPrice',
|
||||
].includes(entry.field)
|
||||
)
|
||||
.map((entry) => {
|
||||
return formatPriceCompact(entry.price)
|
||||
})
|
||||
if (cacheEntries.length > 0) {
|
||||
segments.push({
|
||||
text: `${t('Cache')} ${formatPriceList(cacheEntries, false)}`,
|
||||
muted: true,
|
||||
})
|
||||
}
|
||||
|
||||
const otherEntries = tieredSummary.priceEntries
|
||||
.filter(
|
||||
(entry) =>
|
||||
![
|
||||
'inputPrice',
|
||||
'outputPrice',
|
||||
const cacheEntries = tieredSummary.priceEntries
|
||||
.filter((entry) =>
|
||||
[
|
||||
'cacheReadPrice',
|
||||
'cacheCreatePrice',
|
||||
'cacheCreate1hPrice',
|
||||
].includes(entry.field)
|
||||
)
|
||||
.map((entry) => `${t(entry.shortLabel)} ${formatPrice(entry.price)}`)
|
||||
if (otherEntries.length > 0) {
|
||||
)
|
||||
.map((entry) => {
|
||||
return formatPriceCompact(entry.price)
|
||||
})
|
||||
if (cacheEntries.length > 0) {
|
||||
segments.push({
|
||||
text: `${t('Cache')} ${formatPriceList(cacheEntries, false)}`,
|
||||
muted: true,
|
||||
})
|
||||
}
|
||||
|
||||
const otherEntries = tieredSummary.priceEntries
|
||||
.filter(
|
||||
(entry) =>
|
||||
![
|
||||
'inputPrice',
|
||||
'outputPrice',
|
||||
'cacheReadPrice',
|
||||
'cacheCreatePrice',
|
||||
'cacheCreate1hPrice',
|
||||
].includes(entry.field)
|
||||
)
|
||||
.map((entry) => `${t(entry.shortLabel)} ${formatPrice(entry.price)}`)
|
||||
if (otherEntries.length > 0) {
|
||||
segments.push({
|
||||
text: otherEntries.join(' · '),
|
||||
muted: true,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
segments.push({
|
||||
text: otherEntries.join(' · '),
|
||||
text: `${t('Dynamic Pricing')} · ${t('No matching results')}`,
|
||||
muted: true,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user