feedback7 stats: the local statistics page, ported from PR #323 by @2321Robin as submitted (record store ui/stats.js, viewer screens/stats.js, stats.css, entries on the title / lobby / room screens, 20 tests); golden: unchanged

- public/js/ui/stats.js: one record per finished match in localStorage (sp.pref.stats, envelope {v, records}, at most
  1000, quota shrink of the oldest), content-id dedupe of the lobby's result replay, a MIGRATIONS chain and
  normalizeRecord (defaults for missing fields, unknown fields kept, a newer envelope refused), merge-by-id export /
  import, aggregateStats.
- public/js/screens/stats.js: the viewer (总览 / 策略 / 称号 / 战斗累计 / 最近对局, a row re-views that match's
  settlement), 导出 / 导入 / 清空.
- Applied from the PR's own diff after reading it in full (no network, no new dependency, server untouched); only the
  UI strings of the page are added to the four packs (en from the PR; ja / ko / zh-TW translated for this commit).
- The follow-up commits rework the page (the counting rule for matches left early, storage bounds, the layout).
- Tests: test/ui/stats.test.js 20 / 20; i18n extract 0, check --all --strict clean.
This commit is contained in:
2321Robin authored and sganggs committed 2026-10-08 20:05:09 +08:00
1 parent ae22335cfe
commit 438a6b521a
13 files changed
+1407 -5

No files matched your search

+10 -1
View File
@@ -49,6 +49,8 @@ import { settingsStore } from './ui/settings.js';
import { GuideHost } from './ui/guide.js';
import { installDeviceSupport } from './ui/device.js';
import { LoadoutHost } from './screens/loadout.js';
import { StatsHost } from './screens/stats.js';
import { recordResult } from './ui/stats.js';
import { installLoadoutSync, installOwnershipSync, installDiySync } from './ui/loadoutSync.js';
import { startBuildGuard } from './ui/buildGuard.js';
import { initLang, useLang, tickerText } from './ui/lang.js';
@@ -219,7 +221,13 @@ function wireNet() {
net.on('m.public', (msg) => { matchAt = Date.now(); store.patch('match', { public: payload(msg) }); maybeFinishRestore(); });
net.on('m.private', (msg) => { matchAt = Date.now(); store.patch('match', { private: payload(msg) }); });
net.on('m.field', (msg) => store.patch('match', { field: payload(msg) }));
net.on('m.result', (msg) => store.patch('match', { result: payload(msg) }));
net.on('m.result', (msg) => {
const res = payload(msg);
store.patch('match', { result: res });
// 本机统计 (#18): every arrival, including the lobby's result replay after a reconnect / reload —
// replays dedupe by content id inside recordResult (spectator seats' copies build no record at all)
recordResult(res, { myId: store.get().me.playerId, roomMode: store.get().room?.mode ?? null, now: Date.now() });
});
net.on('m.toast', (msg) => {
const kind = ['info', 'success', 'warn', 'error'].includes(msg.kind) ? msg.kind : 'info';
// msgid + params (server ≥ 0.2.0) or the text itself as a msgid, in the current language
@@ -287,6 +295,7 @@ function App() {
<${UiHosts} />
<${GuideHost} />
<${LoadoutHost} />
<${StatsHost} />
</div>`;
}