feat(client): RMIC::2026 界面重做、可计算的版本机制与固件版本联动
- feat(client): 界面换成墨黑蓝底 + 单支红的矩形语言,切角全部去掉,仅装甲条保留平行四边形 - feat(client): 自托管 HarmonyOS Sans SC 与 JetBrains Mono,中文与拉丁数字逐字回退 - feat(client): 新增启动序列、首启门、用户端 HUD、裁判端控制台、设置抽屉与固件维护页 - feat(client): 裁判端中缝与下方控制台可拖,鼠标落在哪一路下方就看哪一路 - feat(client): 设置抽屉常驻实时预览,与画面共用同一份准星绘制代码 - feat(client): 动效改用 Material 3 的缓动曲线与时长,逐条替换掉自造的贝塞尔 - feat(version): 版本号与 CHANGELOG 由 scripts/version.mjs 从 git 历史算出,不再手写 - feat(firmware): Hello 报文携带语义化版本、构建号与自报协议号,负载向后兼容 - feat(client): 固件版本显示在绑定列表与裁判端每一格;协议不匹配时给出具体原因 - feat(firmware): 固件包版本与客户端同源,都从 version.json 读 - fix(client): getCurrentWindow() 在没有 Tauri 注入时同步抛异常,浏览器预览整棵树被卸载 - fix(client): 启动序列的淡出 effect 清掉了自己刚设的 setTimeout,界面永远停在启动页 - fix(client): 设置抽屉左侧导航的锚点没挂到 section 上,且用错了坐标系 - fix(protocol): Hello 改用宽松帧头解析,让协议不匹配的板子能被解释而不是静默消失 - fix(build): Windows 上 bindgen 加载不了 libclang 的同目录依赖,构建脚本补上搜索路径 - fix(build): esp-idf-sys 在长路径下拒绝构建,固件构建改用短的目标目录 - build(client): 队标图标,含光学尺寸分级与手工拼的多尺寸 ICO - build(ci): 守门 job 增加版本一致性检查,产物名改为 rmic-2026-* - docs: 补充界面语言、版本机制与协议通告布局的说明
This commit is contained in:
@@ -3,6 +3,12 @@
|
||||
*
|
||||
* 它只是**入口**,不是闸门:真正拦住改绑的是后端的 `require_unlocked`。所以这里即使
|
||||
* 被人绕过、甚至被改成永远返回成功,配置也改不动。
|
||||
*
|
||||
* # 它是"校验"而不是"弹窗"
|
||||
*
|
||||
* 这一层出现时,背后的一切都停了:整屏压暗、面板上扫过一条校验线、左下角挂着 `LOCKED`。
|
||||
* 现场有人要改绑定,多半是在出问题的时候;那时候界面必须明确地说"现在这一步是在验证身份",
|
||||
* 而不是给出一个和别处一样的灰色小框。
|
||||
*/
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
@@ -49,38 +55,52 @@ export function UnlockDialog({ reason, onUnlocked, onCancel }: Props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overlay" role="dialog" aria-modal="true">
|
||||
<div className="dialog">
|
||||
<h2 className="dialog__title">需要密码</h2>
|
||||
<p className="dialog__hint">{reason}</p>
|
||||
<div className="overlay" role="dialog" aria-modal="true" aria-label="需要密码">
|
||||
<div className={`lock plate${error ? " is-wrong" : ""}`}>
|
||||
<span className="lock__scan" aria-hidden="true" />
|
||||
<span className="lock__tag label">LOCKED</span>
|
||||
|
||||
<input
|
||||
ref={input}
|
||||
className="input"
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder="密码"
|
||||
autoComplete="off"
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") void submit();
|
||||
if (event.key === "Escape") onCancel();
|
||||
}}
|
||||
/>
|
||||
<header className="lock__head">
|
||||
<h2 className="lock__title">需要密码</h2>
|
||||
<p className="lock__reason">{reason}</p>
|
||||
</header>
|
||||
|
||||
{error && <p className="dialog__error">{error}</p>}
|
||||
<label className="lock__field">
|
||||
<span className="lock__field-label label">ACCESS KEY</span>
|
||||
<input
|
||||
ref={input}
|
||||
className="input input--lg"
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder="输入现场密码"
|
||||
autoComplete="off"
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") void submit();
|
||||
if (event.key === "Escape") onCancel();
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<p className="dialog__note">
|
||||
{error && <p className="lock__error">{error}</p>}
|
||||
|
||||
<p className="lock__note">
|
||||
密码只挡现场随手改绑,不是安全边界:配置文件与程序都在本机,愿意改的人绕得过去。
|
||||
忘了密码就删掉配置里的 password 一行。
|
||||
</p>
|
||||
|
||||
<div className="dialog__actions">
|
||||
<button className="button button--ghost" onClick={onCancel}>
|
||||
<div className="lock__actions">
|
||||
<button type="button" className="btn btn--ghost" onClick={onCancel}>
|
||||
取消
|
||||
</button>
|
||||
<button className="button button--primary" onClick={() => void submit()} disabled={busy}>
|
||||
{busy ? "校验中…" : "解锁"}
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn--primary"
|
||||
onClick={() => void submit()}
|
||||
disabled={busy || password.length === 0}
|
||||
>
|
||||
{busy ? "校验中…" : "校验并解锁"}
|
||||
<span className="btn__key">⏎</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user