fix: 对齐 API USD 小数展示
This commit is contained in:
+15
-3
@@ -12,6 +12,18 @@ function number(value, digits = 0) {
|
|||||||
return Number(value).toLocaleString('zh-CN', { maximumFractionDigits: digits, minimumFractionDigits: digits })
|
return Number(value).toLocaleString('zh-CN', { maximumFractionDigits: digits, minimumFractionDigits: digits })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function usd(value, digits = 3) {
|
||||||
|
const numeric = Number(value)
|
||||||
|
if (value === null || value === undefined || !Number.isFinite(numeric)) return '<span class="usd-value is-empty">—</span>'
|
||||||
|
const formatter = new Intl.NumberFormat('zh-CN', { maximumFractionDigits: digits, minimumFractionDigits: digits })
|
||||||
|
const parts = formatter.formatToParts(numeric)
|
||||||
|
const whole = parts.filter(({ type }) => ['minusSign', 'plusSign', 'integer', 'group'].includes(type)).map(({ value: part }) => part).join('')
|
||||||
|
const point = parts.find(({ type }) => type === 'decimal')?.value ?? '.'
|
||||||
|
const fraction = parts.find(({ type }) => type === 'fraction')?.value ?? ''.padEnd(digits, '0')
|
||||||
|
const label = `$${formatter.format(numeric)}`
|
||||||
|
return `<span class="usd-value" aria-label="${escapeHtml(label)}"><span class="usd-symbol" aria-hidden="true">$</span><span class="usd-whole" aria-hidden="true">${escapeHtml(whole)}</span><span class="usd-point" aria-hidden="true">${escapeHtml(point)}</span><span class="usd-fraction" aria-hidden="true">${escapeHtml(fraction)}</span></span>`
|
||||||
|
}
|
||||||
|
|
||||||
function compact(value) {
|
function compact(value) {
|
||||||
if (value === null || value === undefined) return '—'
|
if (value === null || value === undefined) return '—'
|
||||||
return new Intl.NumberFormat('zh-CN', { notation: 'compact', maximumFractionDigits: 2 }).format(Number(value))
|
return new Intl.NumberFormat('zh-CN', { notation: 'compact', maximumFractionDigits: 2 }).format(Number(value))
|
||||||
@@ -134,7 +146,7 @@ function renderScoreRows() {
|
|||||||
<td>${row.ttftP95Ms == null ? '—' : `${number(row.ttftP95Ms)} ms`}</td>
|
<td>${row.ttftP95Ms == null ? '—' : `${number(row.ttftP95Ms)} ms`}</td>
|
||||||
<td>${compact(usage.requestCount)}</td>
|
<td>${compact(usage.requestCount)}</td>
|
||||||
<td>${compact(usage.tokenCount)}</td>
|
<td>${compact(usage.tokenCount)}</td>
|
||||||
<td>${usage.apiAmountUsd == null ? '—' : `$${number(usage.apiAmountUsd, 3)}`}</td>
|
<td class="usd-cell">${usd(usage.apiAmountUsd)}</td>
|
||||||
<td>${number(row.failoverRecovered)} / ${number(row.failoverRequests)}</td>
|
<td>${number(row.failoverRecovered)} / ${number(row.failoverRequests)}</td>
|
||||||
<td><span class="availability ${row.currentlyAvailable ? 'is-up' : 'is-down'}">${row.currentlyAvailable ? '可用' : '不可用'}</span></td>
|
<td><span class="availability ${row.currentlyAvailable ? 'is-up' : 'is-down'}">${row.currentlyAvailable ? '可用' : '不可用'}</span></td>
|
||||||
</tr>`
|
</tr>`
|
||||||
@@ -180,10 +192,10 @@ async function rankingPage() {
|
|||||||
const data = await requestJson('/api/ranking')
|
const data = await requestJson('/api/ranking')
|
||||||
const ranking = data.ranking
|
const ranking = data.ranking
|
||||||
$('#ranking-range').textContent = `${ranking.startDate} 至 ${ranking.endDate}`
|
$('#ranking-range').textContent = `${ranking.startDate} 至 ${ranking.endDate}`
|
||||||
$('#ranking-cost').textContent = `$${number(ranking.totals.actualCost, 3)}`
|
$('#ranking-cost').innerHTML = usd(ranking.totals.actualCost)
|
||||||
$('#ranking-requests').textContent = compact(ranking.totals.requests)
|
$('#ranking-requests').textContent = compact(ranking.totals.requests)
|
||||||
$('#ranking-tokens').textContent = compact(ranking.totals.tokens)
|
$('#ranking-tokens').textContent = compact(ranking.totals.tokens)
|
||||||
$('#ranking-body').innerHTML = ranking.rows.length ? ranking.rows.map((row) => `<tr><td>${String(row.rank).padStart(2, '0')}</td><td class="account-cell"><b>${escapeHtml(row.displayName)}</b></td><td>$${number(row.actualCost, 3)}</td><td>${compact(row.requests)}</td><td>${compact(row.tokens)}</td></tr>`).join('') : '<tr><td colspan="5" class="empty">当前窗口暂无用量</td></tr>'
|
$('#ranking-body').innerHTML = ranking.rows.length ? ranking.rows.map((row) => `<tr><td>${String(row.rank).padStart(2, '0')}</td><td class="account-cell"><b>${escapeHtml(row.displayName)}</b></td><td class="usd-cell">${usd(row.actualCost)}</td><td>${compact(row.requests)}</td><td>${compact(row.tokens)}</td></tr>`).join('') : '<tr><td colspan="5" class="empty">当前窗口暂无用量</td></tr>'
|
||||||
}
|
}
|
||||||
|
|
||||||
function creditLabel(status) {
|
function creditLabel(status) {
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
<main class="workspace">
|
<main class="workspace">
|
||||||
<header class="page-head"><div><p class="eyebrow">USER FLOW / DAILY</p><h1>用户用量</h1></div><span id="ranking-range" class="state-badge">读取中</span></header>
|
<header class="page-head"><div><p class="eyebrow">USER FLOW / DAILY</p><h1>用户用量</h1></div><span id="ranking-range" class="state-badge">读取中</span></header>
|
||||||
<section class="metric-strip"><div><span>标准 API 用量</span><strong id="ranking-cost">—</strong></div><div><span>请求</span><strong id="ranking-requests">—</strong></div><div><span>Token</span><strong id="ranking-tokens">—</strong></div></section>
|
<section class="metric-strip"><div><span>标准 API 用量</span><strong id="ranking-cost">—</strong></div><div><span>请求</span><strong id="ranking-requests">—</strong></div><div><span>Token</span><strong id="ranking-tokens">—</strong></div></section>
|
||||||
<section class="table-section"><div class="table-wrap"><table class="data-table"><thead><tr><th>名次</th><th>用户</th><th>API USD</th><th>请求</th><th>Token</th></tr></thead><tbody id="ranking-body"><tr><td colspan="5" class="empty">正在读取用量...</td></tr></tbody></table></div></section>
|
<section class="table-section"><div class="table-wrap"><table class="data-table"><thead><tr><th>名次</th><th>用户</th><th class="usd-cell">API USD</th><th>请求</th><th>Token</th></tr></thead><tbody id="ranking-body"><tr><td colspan="5" class="empty">正在读取用量...</td></tr></tbody></table></div></section>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table class="data-table" aria-label="所有分组账号评分">
|
<table class="data-table" aria-label="所有分组账号评分">
|
||||||
<thead><tr><th>账号</th><th>分组</th><th>优先级</th><th>评分</th><th>等级</th><th>置信度</th><th>尝试</th><th>失败率</th><th>TTFT P95</th><th>请求</th><th>Token</th><th>API USD</th><th>切号恢复</th><th>可用</th></tr></thead>
|
<thead><tr><th>账号</th><th>分组</th><th>优先级</th><th>评分</th><th>等级</th><th>置信度</th><th>尝试</th><th>失败率</th><th>TTFT P95</th><th>请求</th><th>Token</th><th class="usd-cell">API USD</th><th>切号恢复</th><th>可用</th></tr></thead>
|
||||||
<tbody id="score-body"><tr><td colspan="14" class="empty">正在读取评分快照...</td></tr></tbody>
|
<tbody id="score-body"><tr><td colspan="14" class="empty">正在读取评分快照...</td></tr></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -67,6 +67,14 @@ button:disabled { cursor: wait; opacity: .48; }
|
|||||||
.data-table th { height: 40px; padding: 0 12px; position: sticky; top: 0; z-index: 1; text-align: left; white-space: nowrap; background: var(--surface-raised); color: var(--muted); font: 10px/1 var(--mono); border-bottom: 1px solid var(--line); }
|
.data-table th { height: 40px; padding: 0 12px; position: sticky; top: 0; z-index: 1; text-align: left; white-space: nowrap; background: var(--surface-raised); color: var(--muted); font: 10px/1 var(--mono); border-bottom: 1px solid var(--line); }
|
||||||
.data-table td { height: 52px; padding: 8px 12px; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
.data-table td { height: 52px; padding: 8px 12px; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||||
.data-table tbody tr:hover { background: rgba(185,228,71,.045); }
|
.data-table tbody tr:hover { background: rgba(185,228,71,.045); }
|
||||||
|
.usd-cell { text-align: right !important; }
|
||||||
|
.usd-value { display: inline-grid; grid-template-columns: 1ch 9ch .65ch 3ch; justify-content: end; align-items: baseline; font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
||||||
|
.usd-symbol { color: var(--muted); text-align: left; }
|
||||||
|
.usd-whole { color: var(--paper); text-align: right; font-weight: 650; }
|
||||||
|
.usd-point, .usd-fraction { color: var(--info); }
|
||||||
|
.usd-point { text-align: center; }
|
||||||
|
.usd-fraction { text-align: left; font-weight: 550; }
|
||||||
|
.usd-value.is-empty { display: inline-block; width: 13.65ch; color: var(--muted); text-align: right; }
|
||||||
.account-cell { min-width: 230px; }
|
.account-cell { min-width: 230px; }
|
||||||
.account-cell b { display: block; max-width: 360px; overflow: hidden; text-overflow: ellipsis; }
|
.account-cell b { display: block; max-width: 360px; overflow: hidden; text-overflow: ellipsis; }
|
||||||
.account-cell small { display: block; color: var(--muted); margin-top: 4px; font: 10px/1 var(--mono); }
|
.account-cell small { display: block; color: var(--muted); margin-top: 4px; font: 10px/1 var(--mono); }
|
||||||
|
|||||||
Reference in New Issue
Block a user