docs(design): §7 UI 改真实 token 视觉稿(替换字符画)

用 design/colors_and_type.css 真实色值(clay/sand 暖色浅色主题)做 HTML/CSS
还原稿,替换粗糙 ASCII:A 设备列表(在线绿点光晕/离线灰点+最后登录/本机标)
+ B 行尾 ⋯ 操作菜单(强制退出/清除登录信息危险红) + C 清除危险二次确认弹窗。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-29 00:05:15 +08:00
parent e8261e8984
commit 7fd2fed441
+111 -13
View File
@@ -43,6 +43,45 @@
.back{display:inline-block;margin-bottom:24px;font-size:13px}
.lvl{font-weight:700}
.phase{font-weight:700;color:var(--accent)}
/* —— UI 视觉稿:还原 app 暖色 token(浅色主题),嵌在暗色文档中作截图式预览 —— */
.mock-wrap{display:flex;flex-wrap:wrap;gap:20px;margin:18px 0 6px}
.mock{--m-bg:#FAF8F4;--m-surface:#FFFFFF;--m-sub:#F2EEE7;--m-fg1:#1F1C18;--m-fg2:#6B6253;--m-fg3:#8C8270;
--m-accent:#B96A3D;--m-accentsub:#FAF3ED;--m-accentbd:#E6C7AC;--m-border:#E6DFD3;--m-ok:#5B8C5A;--m-danger:#C0533B;
background:var(--m-bg);border-radius:20px;padding:18px;width:344px;box-shadow:0 10px 34px rgba(0,0,0,.4);border:1px solid rgba(0,0,0,.15)}
.mock *{box-sizing:border-box}
.mock .cap{font-size:11px;font-weight:700;letter-spacing:.05em;color:#b0a491;margin:0 0 11px;text-transform:uppercase}
.mock .panel{background:var(--m-surface);border:1px solid var(--m-border);border-radius:14px;overflow:hidden}
.mock .row{display:flex;align-items:center;gap:12px;padding:13px 14px;border-bottom:1px solid var(--m-border)}
.mock .row:last-child{border-bottom:none}
.mock .ava{width:38px;height:38px;border-radius:10px;background:var(--m-accentsub);display:flex;align-items:center;justify-content:center;font-size:18px;flex:none}
.mock .meta{flex:1;min-width:0}
.mock .nm{display:flex;align-items:center;gap:7px;font-size:14.5px;font-weight:600;color:var(--m-fg1)}
.mock .badge{font-size:10px;font-weight:700;padding:1px 7px;border-radius:6px;background:var(--m-accentsub);color:var(--m-accent);border:1px solid var(--m-accentbd)}
.mock .sub2{font-size:12px;color:var(--m-fg3);margin-top:3px}
.mock .stat{display:flex;align-items:center;gap:5px;font-size:12px;font-weight:600;flex:none}
.mock .dot{width:7px;height:7px;border-radius:50%}
.mock .on{color:var(--m-ok)} .mock .on .dot{background:var(--m-ok);box-shadow:0 0 0 3px rgba(91,140,90,.18)}
.mock .off{color:#a89e8e} .mock .off .dot{background:#cfc6b8}
.mock .kebab{flex:none;width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;color:var(--m-fg3);font-size:17px}
.mock .kebab.act{background:var(--m-sub);color:var(--m-fg1)}
.mock .foot{font-size:12px;color:var(--m-fg3);margin-top:12px;padding-left:2px}
.mock .foot b{color:var(--m-accent);font-weight:700}
.mock .menu{background:var(--m-surface);border:1px solid var(--m-border);border-radius:13px;padding:6px;box-shadow:0 12px 30px rgba(0,0,0,.16);width:216px;margin-top:6px}
.mock .mi{display:flex;align-items:center;gap:11px;padding:10px 11px;border-radius:9px;font-size:13.5px;font-weight:500;color:var(--m-fg1)}
.mock .mi .ic{font-size:14px;width:17px;text-align:center}
.mock .mi.danger{color:var(--m-danger)}
.mock .mi.sel{background:var(--m-sub)}
.mock.dlg{display:flex;flex-direction:column;justify-content:center;background:linear-gradient(160deg,#efe7dc,#e6dccf)}
.mock .sheet{background:var(--m-surface);border-radius:18px;padding:22px 20px;text-align:center;box-shadow:0 18px 50px rgba(31,28,24,.22)}
.mock .warnico{width:48px;height:48px;border-radius:50%;background:rgba(192,83,59,.12);color:var(--m-danger);display:flex;align-items:center;justify-content:center;font-size:22px;margin:2px auto 13px}
.mock .dt{font-size:16px;font-weight:700;color:var(--m-fg1)}
.mock .dd{font-size:12.5px;color:var(--m-fg2);margin:9px 4px 18px;line-height:1.55}
.mock .dd b{color:var(--m-fg1)}
.mock .btns{display:flex;gap:10px}
.mock .btn{flex:1;padding:11px;border-radius:999px;font-size:13.5px;font-weight:600;text-align:center}
.mock .btn.ghost{background:var(--m-sub);color:var(--m-fg2)}
.mock .btn.danger{background:var(--m-danger);color:#fff}
.mock .pin{display:inline-block;font-size:10px;color:#b0a491;margin-left:6px}
</style>
</head>
<body>
@@ -287,19 +326,78 @@ usage_device_daily(user_id, device_id, date, bytes_up, bytes_down, minutes_used)
<h2>7. 前端 UI(「我的 → 我的设备」)</h2>
<p>沿用既有 <code>DevicesScreen</code> 卡片列表,行内信息扩展 + 行尾操作。颜色/字号/间距全走唯一真相源 token(<code>pangolin_theme</code>),不硬编码。</p>
<pre>
┌─────────────────────────────────────────────────────────────┐
│ [▦] MacBook Pro ● 在线 [ ⋯ ] │ ⋯ 菜单:
│ macOS · v1.0.10 · 本机 │ ├ 强制退出
├─────────────────────────────────────────────────────────────┤ └ 清除登录信息
│ [▦] iPhone 15 ○ 离线 [ ⋯ ] │
│ iOS · v1.0.9 · 最后登录 2 小时前 │
├─────────────────────────────────────────────────────────────┤
│ [▦] Windows-PC ○ 离线 [ ⋯ ] │
│ windows · v1.0.10 · 最后登录 3 天前 │
└─────────────────────────────────────────────────────────────┘
PRO 套餐最多 5 台设备同时在线 · 已用 3 / 5
</pre>
<div class="mock-wrap">
<!-- A · 设备列表 -->
<div class="mock">
<div class="cap">我的设备 · 列表</div>
<div class="panel">
<div class="row">
<div class="ava">💻</div>
<div class="meta">
<div class="nm">MacBook Pro <span class="badge">本机</span></div>
<div class="sub2">macOS · v1.0.10</div>
</div>
<div class="stat on"><span class="dot"></span>在线</div>
<div class="kebab"></div>
</div>
<div class="row">
<div class="ava">📱</div>
<div class="meta">
<div class="nm">iPhone 15</div>
<div class="sub2">iOS · v1.0.9 · 最后登录 2 小时前</div>
</div>
<div class="stat off"><span class="dot"></span>离线</div>
<div class="kebab act"></div>
</div>
<div class="row">
<div class="ava">🖥️</div>
<div class="meta">
<div class="nm">DESKTOP-7F3K</div>
<div class="sub2">windows · v1.0.10 · 最后登录 3 天前</div>
</div>
<div class="stat off"><span class="dot"></span>离线</div>
<div class="kebab"></div>
</div>
</div>
<div class="foot">PRO 套餐最多 5 台同时在线 · 已用 <b>3 / 5</b></div>
</div>
<!-- B · 行尾操作菜单 -->
<div class="mock">
<div class="cap">行尾 ⋯ 操作菜单</div>
<div class="panel" style="border-radius:13px">
<div class="row" style="border-bottom:none;background:#FAF3ED55">
<div class="ava">📱</div>
<div class="meta">
<div class="nm">iPhone 15</div>
<div class="sub2">iOS · v1.0.9</div>
</div>
<div class="kebab act"></div>
</div>
</div>
<div class="menu">
<div class="mi"><span class="ic"></span>强制退出<span class="pin">踢下线,设备留存</span></div>
<div class="mi danger sel"><span class="ic">🗑</span>清除登录信息<span class="pin" style="color:#d09c8c">彻底移除</span></div>
</div>
</div>
<!-- C · 危险二次确认 -->
<div class="mock dlg">
<div class="cap">清除登录信息 · 二次确认</div>
<div class="sheet">
<div class="warnico">⚠️</div>
<div class="dt">清除「iPhone 15」?</div>
<div class="dd">将从设备列表<b>彻底移除</b>此设备,并吊销其登录会话与数据面凭证。该设备需<b>重新登录</b>才能再次使用。</div>
<div class="btns">
<div class="btn ghost">取消</div>
<div class="btn danger">清除</div>
</div>
</div>
</div>
</div>
<p class="small">↑ 用项目真实 token 配色(clay/sand 暖色浅色主题)的还原稿。三态:<b>A</b> 列表(在线=绿点光晕 / 离线=灰点 + 最后登录;本机带「本机」标)→ <b>B</b> 行尾 ⋯ 弹出菜单(强制退出 / 清除登录信息·危险红)→ <b>C</b> 清除前危险二次确认。强制退出走相同弹窗、文案改为「踢下线、设备保留」。</p>
<ul>
<li><b>model</b> <code>Device</code><code>clientVersion</code> / <code>online</code>(bool) / <code>lastLogin</code>(DateTime?)。</li>
<li><b>设备身份</b>:弃用 <code>_kDeviceId='mac-001'</code>,首启生成 UUID v4 存 <code>flutter_secure_storage</code>;登录/注册请求体带 <code>device:{id,name,platform,client_version}</code></li>