fix: 合并跨分组账号评分

This commit is contained in:
root
2026-07-16 16:43:11 +02:00
parent 44f4f358f6
commit 4eb09e9831
6 changed files with 240 additions and 13 deletions
+35 -7
View File
@@ -87,14 +87,36 @@ async function loginPage() {
}
let scoreRows = []
let scoreRefreshedAt = null
let scoreNextRefreshAt = null
function gradeClass(value) {
const grade = String(value ?? '').toLowerCase()
if (grade === 'excellent' || grade === 'good') return 'grade-good'
if (grade === 'poor' || grade === 'critical' || grade === 'insufficient') return 'grade-risk'
if (grade === 'a' || grade === 'b' || grade === 'excellent' || grade === 'good') return 'grade-good'
if (grade === 'd' || grade === 'e' || grade === 'poor' || grade === 'critical' || grade === 'insufficient') return 'grade-risk'
return 'grade-mid'
}
function groupLabels(row) {
const groups = Array.isArray(row.groupNames) && row.groupNames.length ? row.groupNames : [row.groupName].filter(Boolean)
return `<div class="group-list">${groups.map((group) => `<span>${escapeHtml(group)}</span>`).join('')}</div>`
}
function countdown(value) {
if (!value) return '--:--'
const remaining = Math.max(0, new Date(value).getTime() - Date.now())
if (remaining === 0) return '等待刷新'
const totalSeconds = Math.ceil(remaining / 1000)
const minutes = Math.floor(totalSeconds / 60)
const seconds = totalSeconds % 60
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
}
function renderRefreshClock() {
$('#score-updated-time').textContent = scoreRefreshedAt ? `北京时间 ${time(scoreRefreshedAt)}` : '尚无成功快照'
$('#score-countdown').textContent = countdown(scoreNextRefreshAt)
}
function renderScoreRows() {
const term = ($('#score-filter')?.value ?? '').trim().toLowerCase()
const rows = scoreRows.filter((row) => `${row.accountName} ${row.groupName}`.toLowerCase().includes(term))
@@ -102,9 +124,9 @@ function renderScoreRows() {
const usage = row.usage ?? {}
return `<tr>
<td class="account-cell"><b>${escapeHtml(row.accountName)}</b><small>#${escapeHtml(row.accountId)}</small></td>
<td>${escapeHtml(row.groupName)}</td>
<td>${groupLabels(row)}</td>
<td>${number(row.priority)}</td>
<td><span class="score-value ${gradeClass(row.grade)}">${number(row.score)}</span></td>
<td><span class="score-value ${gradeClass(row.grade)}">${number(row.score, 1)}</span></td>
<td>${escapeHtml(row.grade ?? '—')}</td>
<td>${escapeHtml(row.confidence ?? '—')}</td>
<td>${number(row.observedAttempts)}</td>
@@ -128,7 +150,9 @@ function renderScores(data) {
$('#metric-window').textContent = data.window ?? '—'
$('#score-state').textContent = ({ ready: '已更新', refreshing: '刷新中', stale: '使用旧快照', unavailable: '暂无快照' })[data.status] ?? data.status
$('#score-state').dataset.state = data.status
$('#score-updated').textContent = data.refreshedAt ? `北京时间 ${time(data.refreshedAt)} · 下次 ${time(data.nextRefreshAt)}` : (data.error ?? '尚无成功快照')
scoreRefreshedAt = data.refreshedAt ?? scoreRefreshedAt
scoreNextRefreshAt = data.nextRefreshAt ?? scoreNextRefreshAt
renderRefreshClock()
renderScoreRows()
}
@@ -139,12 +163,16 @@ async function scoresPage() {
button.disabled = true
$('#score-state').textContent = '刷新中'
try { renderScores(await requestJson('/api/scores/refresh', { method: 'POST', body: '{}' }, 200000)) }
catch (error) { $('#score-updated').textContent = error instanceof Error ? error.message : String(error) }
catch (error) { $('#score-updated-time').textContent = error instanceof Error ? error.message : String(error) }
finally { button.disabled = false }
})
renderScores(await requestJson('/api/scores'))
setInterval(renderRefreshClock, 1000)
setInterval(async () => {
if (!document.hidden) renderScores(await requestJson('/api/scores').catch(() => ({ ok: false, accounts: scoreRows })))
if (!document.hidden) {
const data = await requestJson('/api/scores').catch(() => null)
if (data) renderScores(data)
}
}, 30000)
}
+5 -1
View File
@@ -28,7 +28,11 @@
<section class="table-section">
<div class="table-toolbar">
<div class="filter-field"><span>筛选</span><input id="score-filter" type="search" placeholder="账号或分组" /></div>
<p id="score-updated">尚无成功快照</p>
<div id="score-updated" class="refresh-clock">
<span>上次更新时间</span>
<strong id="score-updated-time">尚无成功快照</strong>
<small>距离下次更新 <b id="score-countdown">--:--</b></small>
</div>
</div>
<div class="table-wrap">
<table class="data-table" aria-label="所有分组账号评分">
+9 -2
View File
@@ -54,7 +54,11 @@ button:disabled { cursor: wait; opacity: .48; }
.table-section { padding-top: 20px; }
.table-toolbar { min-height: 48px; display: flex; justify-content: space-between; align-items: center; gap: 18px; }
.table-toolbar p { color: var(--muted); font: 11px/1 var(--mono); }
.refresh-clock { min-width: 310px; display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 4px 18px; padding: 10px 14px; border-left: 3px solid var(--signal); background: var(--surface); }
.refresh-clock > span { grid-column: 1 / -1; color: var(--muted); font-size: 10px; }
.refresh-clock strong { color: var(--paper); font: 650 16px/1.2 var(--mono); }
.refresh-clock small { color: var(--muted); font: 10px/1.2 var(--mono); white-space: nowrap; }
.refresh-clock b { color: var(--signal); font-size: 14px; }
.filter-field { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 12px; }
.filter-field input { width: 260px; height: 34px; border: 1px solid var(--line); border-radius: 3px; background: var(--surface); color: var(--paper); padding: 0 10px; outline: 0; }
.filter-field input:focus { border-color: var(--signal); }
@@ -66,7 +70,9 @@ button:disabled { cursor: wait; opacity: .48; }
.account-cell { min-width: 230px; }
.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); }
.score-value { display: inline-grid; place-items: center; width: 34px; height: 26px; border-radius: 3px; font: 700 12px/1 var(--mono); }
.score-value { display: inline-grid; place-items: center; min-width: 44px; height: 26px; padding: 0 6px; border-radius: 3px; font: 700 12px/1 var(--mono); }
.group-list { display: flex; flex-wrap: wrap; gap: 4px; max-width: 250px; }
.group-list span { padding: 3px 6px; border: 1px solid var(--line); border-radius: 3px; color: var(--muted); font: 10px/1.2 var(--mono); }
.grade-good { color: var(--signal); background: rgba(185,228,71,.1); }
.grade-mid { color: var(--info); background: rgba(112,183,212,.1); }
.grade-risk { color: var(--alert); background: rgba(255,107,74,.1); }
@@ -126,6 +132,7 @@ button:disabled { cursor: wait; opacity: .48; }
.metric-strip { grid-template-columns: repeat(2, 1fr); overflow: hidden; }
.metric-strip > div { border-bottom: 1px solid var(--line); padding-left: 12px; }
.table-toolbar { align-items: stretch; flex-direction: column; padding-bottom: 12px; }
.refresh-clock { min-width: 0; width: 100%; }
.filter-field input { flex: 1; width: auto; }
.lottery-grid { grid-template-columns: 1fr; }
.draw-console { border-right: 0; border-bottom: 1px solid var(--line); padding: 36px 0; }