feat(HEL-546): 数据中枢 A/B/C 三方向落地为可用页面

- 基于第七版稳定基线(be647bb)重构 admin 前端为模块化架构:
  tokens.css(设计token唯一来源) + shared.css(公共组件) + core.js(状态/API/事件总线/轮询/路由)
  + main.js(入口) + layouts/{flowline,ledger,strata}.{css,js}(三方向独立布局)
  + layouts/shared.js(三方向共用数据视图与操作绑定)
- 三个页面(A装配线/B值班台账/C地层剖面)通过 ?layout= 查询参数独立可达、可刷新、可前进后退导航,
  共享登录态、真实后端数据(overview/sources/jobs/batches/datasets/audit)、错误处理与主题
- 六大板块(总览/数据源/调度任务/盘后发布/数据集/审计)在三个方向均可查看与操作(探测/触发/回滚/补数)
- 修正健康状态语义:CircuitBreaker closed/half_open/open 与适配器 ok/error/empty/unconfigured/unknown
  统一归一化为 ok/warn/error/unconfigured/unknown 供三方向一致展示
- 事件驱动动效:A 水平接力光梭、B 行级高亮+实时调用跑马灯、C 纵向贯穿光点+分层标记;
  统一使用 transform/opacity/WAAPI,避免 transition:all,支持 prefers-reduced-motion 静态降级
- 修复响应式布局在 1024/1280 断点因 CSS Grid 默认 min-width:auto 被内部宽表格撑爆轨道的问题
- 修复布局切换/前进后退时残留 setTimeout 回调在 unmount 后访问 null root 导致的报错(mounted 标志位+
  safeTimeout+统一清理定时器)
- 自测:Playwright 全断点(1440/1280/1024)x 双主题矩阵截图、90 次连续布局切换+前进后退压力测试无报错、
  探测/触发/回滚danger操作流程验证、reduced-motion 验证;后端 pytest 全量 133 用例通过,无回归
- 未改动:登录/鉴权契约、后端 API、'问天'冻结区、生产数据/权限

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
总工
2026-09-13 16:28:50 +08:00
co-authored by Cursor multica-agent
parent be647bbaba
commit 011ecc0d1a
14 changed files with 1915 additions and 1666 deletions
+91
View File
@@ -0,0 +1,91 @@
"use strict";
/* ==========================================================================
xiaobai-datahub 管理后台 · 启动壳
会话/登录/改密/登出/主题按钮/退出确认 —— 与具体布局无关,三页共用同一份。
========================================================================== */
(function () {
const C = window.Core;
const { $ } = C;
function show(id) {
["login-view", "change-view", "shell"].forEach((key) => { $(key).hidden = key !== id; });
}
async function boot() {
try {
const session = await C.api("/admin/api/session");
C.state.csrf = session.csrf;
$("who").textContent = session.username;
if (session.must_change) { show("change-view"); return; }
show("shell");
enterShell();
} catch {
show("login-view");
}
}
$("login-form").addEventListener("submit", async (event) => {
event.preventDefault();
const form = new FormData(event.target);
$("login-error").hidden = true;
try {
const result = await C.api("/admin/api/login", {
method: "POST",
body: JSON.stringify({ username: form.get("username"), password: form.get("password") }),
});
C.state.csrf = result.csrf;
if (result.must_change) show("change-view");
else { show("shell"); enterShell(); }
} catch (err) {
$("login-error").hidden = false;
$("login-error").textContent = err.message;
}
});
$("change-form").addEventListener("submit", async (event) => {
event.preventDefault();
const form = new FormData(event.target);
try {
await C.api("/admin/api/change-password", {
method: "POST",
body: JSON.stringify({ current: form.get("current"), new_password: form.get("new_password") }),
});
show("shell");
enterShell();
} catch (err) {
$("change-error").hidden = false;
$("change-error").textContent = err.message;
}
});
$("logout-btn").addEventListener("click", async () => {
await C.api("/admin/api/logout", { method: "POST", body: "{}" });
C.Poller.stopAll();
Object.values(window.HUB_LAYOUTS || {}).forEach((impl) => { try { impl.unmount && impl.unmount(); } catch { /* ignore */ } });
show("login-view");
});
$("theme-btn").addEventListener("click", () => C.toggleTheme());
C.Bus.on("theme", (theme) => { $("theme-btn").textContent = theme === "night" ? "日间" : "夜间"; });
function updateCrumb() {
const el = $("crumb");
if (!el) return;
if (!C.state.online) { el.textContent = "网络已断开 · 已暂停实时"; return; }
if (!C.state.visible) { el.textContent = "已切至后台 · 已暂停动效"; return; }
const d = C.state.data.overview;
el.textContent = d ? `8766 · ${d.trade_date} · 持续运转` : "8766 · 四源汇流 · 持续运转";
}
C.Bus.on("runtime", updateCrumb);
C.Bus.on("data", updateCrumb);
function enterShell() {
C.bootTheme();
C.applyReduced(C.REDUCE_MQ.matches);
C.Poller.startAll();
updateCrumb();
C.Router.boot();
}
boot();
})();