Files
iboard/docs/V0.2/界面设计.html
T
cheney 072f304cc2
Docker Build and Push / build-image (push) Successful in 4m14s
docs(V0.2): 界面设计同步 2026-10-23 调整 (含日历+打卡合并草案) + 设计文档 §11
界面设计.html
- 新增 2.12「V0.2.1 界面调整」章节: 头部统一 / 日历·打卡合并 / 模板分类 / 宏观 tab 溢出
- 已实现部分 (①③④) 与待确认草案 (②) 分别标注
- 2.9 宏观截图同步新 tab 顺序与右侧透支提示; 2.3 / 2.7 清单补「模板」分段
- 全部原型头部改用 23px 紧凑头部, 「我」页去掉头部条

设计文档.md
- 新增 §11 界面调整: 变更清单、头部统一对照表、模板接口与排序口径、宏观 tab 顺序与溢出实现、
  待确认的日历+打卡合并方案 (含确认后需改动的文件与样式段)
2026-10-23 16:41:04 +08:00

1705 lines
124 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>iBoard V0.2 界面设计 · 打卡 / 日历 / 清单</title>
<style>
:root{
--bg:#f0f2f5; --page:#ffffff; --card:#ffffff;
--text:#333333; --muted:#666666; --weak:#999999; --line:#eeeeee;
--primary:#1677ff; --success:#00b578; --warn:#ffc53d; --over:#a855f7;
--danger:#ff3141; --orange:#ff8f1f; --teal:#14b8a6; --pink:#f43f9e;
--nav-h:45px; --tab-h:72px;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
background:var(--bg); color:#1f2328; font-size:14px; line-height:1.6;
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei',Roboto,sans-serif;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1400px;margin:0 auto;padding:36px 24px 72px}
.doc-head{border-bottom:1px solid #e3e6ea;padding-bottom:20px;margin-bottom:28px}
.doc-head h1{font-size:26px;font-weight:700;letter-spacing:.5px}
.doc-head p{color:var(--muted);font-size:13px;margin-top:6px}
.meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.meta span{font-size:12px;background:#fff;border:1px solid #e3e6ea;border-radius:999px;padding:3px 12px;color:var(--muted)}
h2{font-size:19px;font-weight:700;margin:40px 0 6px;display:flex;align-items:center;gap:10px}
h2::before{content:"";width:4px;height:17px;background:var(--primary);border-radius:2px}
h2 .sub{font-size:12px;font-weight:400;color:var(--weak)}
h3{font-size:15px;font-weight:700;margin:22px 0 10px;color:#24292f}
p.desc{color:var(--muted);font-size:13px;margin-bottom:16px}
/* ---------- 设计规范 ---------- */
.tokens{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:18px}
.panel{background:#fff;border:1px solid #e6e8eb;border-radius:12px;padding:16px 18px}
.panel h4{font-size:13px;color:var(--muted);font-weight:600;margin-bottom:12px;letter-spacing:.4px}
.swatches{display:flex;flex-wrap:wrap;gap:10px}
.sw{width:88px;text-align:center}
.sw i{display:block;height:44px;border-radius:8px;border:1px solid rgba(0,0,0,.06)}
.sw b{display:block;font-size:11px;font-weight:600;margin-top:5px}
.sw em{display:block;font-size:10px;font-style:normal;color:var(--weak)}
.kv{display:grid;grid-template-columns:96px 1fr;gap:6px 12px;font-size:12px}
.kv span{color:var(--weak)}
.kv b{font-weight:600}
.type-row{display:flex;align-items:baseline;gap:12px;padding:6px 0;border-bottom:1px dashed var(--line);font-size:12px}
.type-row:last-child{border-bottom:none}
.type-row em{font-style:normal;color:var(--weak);width:132px;flex-shrink:0}
.type-row span{color:#444}
/* ---------- 手机 ---------- */
.stage{display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:30px 26px;justify-items:center}
.shot{width:420px}
.phone{
width:420px;height:820px;background:var(--page);position:relative;overflow:hidden;
border:1px solid #d8dde3;border-radius:22px;box-shadow:0 10px 24px rgba(15,23,42,.08);
display:flex;flex-direction:column;
}
.phone::after{content:"";position:absolute;top:8px;left:50%;transform:translateX(-50%);width:118px;height:20px;background:#111827;border-radius:12px;opacity:.9}
.navbar{height:var(--nav-h);margin-top:28px;flex-shrink:0;display:flex;align-items:center;justify-content:center;
font-size:17px;font-weight:600;border-bottom:1px solid var(--line);background:#fff}
.body{flex:1;min-height:0;overflow:hidden;background:var(--bg);padding:0 0 8px}
.tabbar{height:var(--tab-h);flex-shrink:0;background:#fff;border-top:1px solid var(--line);
display:flex;align-items:flex-start;padding-top:7px}
.tab{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;color:var(--weak);font-size:11px}
.tab.on{color:var(--primary)}
.tab svg{width:23px;height:23px}
.caption{margin-top:10px;background:#fff;border:1px solid #e6e8eb;border-radius:10px;padding:10px 14px}
.caption b{font-size:13px}
.caption p{font-size:12px;color:var(--muted);margin-top:3px}
.tag{display:inline-block;font-size:11px;color:var(--primary);background:#f0f5ff;border-radius:4px;padding:1px 6px;margin-left:6px}
/* ---------- 打卡视图 ---------- */
.habit-head{display:flex;justify-content:space-between;align-items:flex-end;padding:12px 14px 8px}
.habit-date{font-size:18px;font-weight:700}
.habit-summary{font-size:12px;color:var(--muted);margin-top:2px}
.habit-summary strong{color:var(--primary);font-size:14px}
.badge{font-size:11px;color:#ad6800;background:#fff7e6;border-radius:4px;padding:1px 5px;margin-left:6px}
.mini-btn{font-size:11px;border:1px solid var(--primary);color:var(--primary);border-radius:999px;padding:3px 10px;background:#fff}
.mini-btn.solid{background:var(--primary);color:#fff;border-color:var(--primary)}
.mini-btn.grey{border-color:#dcdcdc;color:var(--muted)}
.head-right{display:flex;gap:6px}
.habit-list{padding:0 12px;display:flex;flex-direction:column;gap:10px}
.habit-card{position:relative;background:#fff;border-radius:10px;padding:12px;box-shadow:0 1px 2px rgba(0,0,0,.04);
display:grid;grid-template-columns:auto 1fr auto;grid-template-areas:"ring main actions" "foot foot foot";gap:8px 12px}
.habit-card.done{background:#f7fbf8}
.ring{grid-area:ring;width:58px;height:58px;position:relative;display:flex;align-items:center;justify-content:center}
.ring svg{display:block}
.ring-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:16px;font-weight:700}
.ring-center em{font-style:normal;font-size:10px;color:var(--muted);font-weight:400}
.ring-over{position:absolute;top:-4px;right:-10px;font-size:10px;color:#fff;background:var(--orange);border-radius:999px;padding:0 5px}
.habit-main{grid-area:main;min-width:0}
.habit-name{font-size:15px;font-weight:600}
.habit-meta{font-size:12px;color:var(--muted);margin-top:3px}
.streak{margin-left:8px;color:var(--orange);background:rgba(255,143,31,.12);border-radius:4px;padding:1px 6px}
.habit-range{font-size:11px;color:var(--muted);margin-top:3px}
.over-txt{margin-left:8px;color:var(--orange)}
.habit-actions{grid-area:actions;display:flex;align-items:center}
.habit-foot{grid-area:foot;display:flex;gap:14px;padding-top:8px;border-top:1px dashed var(--line)}
.link{font-size:12px;color:var(--primary)}
.link.danger{color:var(--danger)}
/* ---------- 日历视图 ---------- */
.cal-month-bar{display:flex;align-items:center;gap:10px;padding:12px 14px 6px}
.cal-month{font-size:16px;font-weight:700}
.cal-arrow{font-size:20px;color:var(--muted);padding:0 4px;line-height:1}
.cal-today{margin-left:auto;font-size:12px;color:var(--primary)}
.seg{display:flex;margin:4px 14px 10px;background:#f2f3f5;border-radius:8px;padding:3px}
.seg div{flex:1;text-align:center;font-size:13px;padding:5px 0;border-radius:6px;color:var(--muted)}
.seg div.on{background:#fff;color:var(--text);font-weight:600;box-shadow:0 1px 2px rgba(0,0,0,.06)}
.cal-grid{margin:0 12px;background:#fff;border-radius:10px;padding:8px 6px 10px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.grid7{display:grid;grid-template-columns:repeat(7,1fr)}
.wd span{text-align:center;font-size:11px;color:var(--muted);padding:4px 0}
.day{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:5px 0;border-radius:8px}
.day .num{font-size:13px}
.day.out .num{color:#c8c8c8}
.day.future .num{color:#b0b0b0}
.day.today .num{color:var(--primary);font-weight:700}
.day.sel{background:var(--primary)}
.day.sel .num{color:#fff;font-weight:600}
.dot{width:5px;height:5px;border-radius:50%;margin-top:3px}
.dot.partial{background:var(--warn)} .dot.full{background:var(--success)} .dot.over{background:var(--over)}
.day.sel .dot{background:#fff}
.cal-rate{font-size:12px;color:var(--muted);padding:8px 14px}
.cal-rate strong{color:var(--primary)}
.cal-detail-title{font-size:14px;font-weight:600;padding:6px 14px 8px}
.cal-item{display:flex;align-items:center;gap:8px;background:#fff;border-radius:8px;padding:10px 12px;margin:0 12px 8px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.cal-item.done{background:#f7fbf8}
.cdot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.cname{font-size:14px;font-weight:600}
.cmeta{font-size:11px;color:var(--muted);flex:1;text-align:right}
.cplan{font-size:12px;color:var(--muted)}
/* ---------- 清单视图 ---------- */
.list-head{display:flex;align-items:center;gap:8px;padding:12px 14px 10px}
.list-head .seg{flex:1;margin:0}
.checklist-card{background:#fff;border-radius:10px;padding:12px;margin:0 12px 10px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.checklist-card.done{background:#f7fbf8}
.cl-title-row{display:flex;align-items:center;gap:8px}
.cl-title{font-size:15px;font-weight:600}
.cl-scene{font-size:11px;color:var(--primary);background:#f0f5ff;border-radius:4px;padding:1px 6px}
.progress-row{display:flex;align-items:center;gap:8px;margin-top:8px}
.bar{flex:1;height:6px;border-radius:3px;background:#f0f0f0;overflow:hidden}
.bar i{display:block;height:100%;border-radius:3px;background:var(--success)}
.count{font-size:12px;color:var(--muted)}
.arrow{color:var(--muted);transform:rotate(90deg)}
.cl-body{margin-top:10px;padding-top:8px;border-top:1px dashed var(--line)}
.cl-item{display:flex;align-items:center;gap:8px;padding:7px 0}
.box{width:20px;height:20px;border-radius:50%;border:1.5px solid #d0d0d0;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.cl-item.done .box{background:var(--success);border-color:var(--success)}
.box svg{opacity:0} .cl-item.done .box svg{opacity:1}
.cl-item-title{flex:1;font-size:14px}
.cl-item.done .cl-item-title{color:#a0a0a0;text-decoration:line-through}
.cl-del{color:#ccc;font-size:18px;line-height:1;padding:0 4px}
.cl-add{display:flex;gap:8px;margin-top:8px}
.cl-add .inp{flex:1;border:1px solid var(--line);border-radius:6px;padding:6px 8px;font-size:13px;color:var(--weak);background:#fff}
.cl-actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:10px}
/* ---------- 弹窗 ---------- */
.mask{position:absolute;inset:28px 0 0 0;background:rgba(0,0,0,.45);z-index:9}
.sheet{position:absolute;left:0;right:0;bottom:0;background:#fff;border-radius:12px 12px 0 0;padding:16px 16px 12px;z-index:10;max-height:560px;overflow:hidden}
.sheet-title{font-size:16px;font-weight:700;margin-bottom:12px}
.field{margin-bottom:13px}
.field label{display:block;font-size:12px;color:var(--muted);margin-bottom:6px}
.inp{border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:14px;background:#fff;color:var(--text)}
.inp.ph{color:#b8bcc4}
.chips{display:flex;gap:8px}
.chip{flex:1;text-align:center;font-size:13px;padding:6px 0;border-radius:8px;background:#f5f6f8;color:var(--muted)}
.chip.on{background:var(--primary);color:#fff;font-weight:600}
.stepper{display:inline-flex;align-items:center;gap:10px}
.stepper span{width:28px;height:28px;border-radius:6px;background:#f5f6f8;display:flex;align-items:center;justify-content:center;color:var(--muted)}
.stepper span.act{background:var(--primary);color:#fff}
.stepper b{font-size:16px;min-width:24px;text-align:center}
.colors{display:flex;gap:10px;flex-wrap:wrap}
.cd{width:26px;height:26px;border-radius:50%;border:2px solid transparent}
.cd.on{border-color:rgba(0,0,0,.25);transform:scale(1.12)}
.btn-primary{display:block;text-align:center;background:var(--primary);color:#fff;border-radius:8px;padding:10px 0;font-size:15px;font-weight:600;margin-top:18px}
.btn-ghost{display:block;text-align:center;border:1px solid var(--line);border-radius:8px;padding:9px 0;font-size:15px;color:var(--muted);margin-top:8px;background:#fff}
/* ---------- 特效 ---------- */
.fx{position:absolute;inset:0;pointer-events:none}
.fx .halo{position:absolute;left:50%;top:50%;width:88px;height:88px;margin:-44px 0 0 -44px;border-radius:50%;border:2px solid var(--success);opacity:.55}
.fx .halo2{width:108px;height:108px;margin:-54px 0 0 -54px;opacity:.25}
.fx i{position:absolute;left:50%;top:50%;width:6px;height:6px;border-radius:50%;}
/* ---------- 空态 ---------- */
.empty{text-align:center;padding:70px 20px;color:var(--weak)}
.empty .ico{width:64px;height:64px;margin:0 auto 12px;border-radius:50%;background:#f2f3f5;display:flex;align-items:center;justify-content:center;color:#c4c8ce;font-size:30px}
.empty p{font-size:13px;margin-bottom:12px}
/* ---------- 统一主 Tab 头部(V0.2.1 调整:高度减半,标题主色小字) ---------- */
.navbar-sm{height:23px;margin-top:28px;flex-shrink:0;display:flex;align-items:center;justify-content:center;
font-size:12px;font-weight:600;letter-spacing:1px;color:var(--primary);background:var(--bg)}
/* 「我」页:不设头部,头像区直接顶到状态栏下方 */
.me-head.flush{padding-top:24px}
/* ---------- 打卡 · 日历与打卡合并视图(V0.2.1 草案) ---------- */
.mv-cal{background:#fff;border-radius:10px;margin:8px 12px 0;padding:6px 6px 8px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.mv-month{display:flex;align-items:center;gap:8px;padding:2px 4px 6px}
.mv-month b{font-size:14px}
.mv-month .arrow{font-size:17px;color:var(--muted);line-height:1;padding:0 3px}
.mv-month .today{margin-left:auto;font-size:11px;color:var(--primary)}
.mv-grid{display:grid;grid-template-columns:repeat(7,1fr)}
.mv-wd span{text-align:center;font-size:10px;color:var(--muted);padding:1px 0 3px;display:block}
.mv-day{height:31px;margin:1px 0;display:flex;flex-direction:column;align-items:center;justify-content:center;
border-radius:6px;font-size:12px;line-height:1.15;color:var(--text)}
.mv-day.out{color:#c8c8c8}
.mv-day.future{color:#b0b0b0}
.mv-day.today{color:var(--primary);font-weight:700}
.mv-day.sel{background:var(--primary);color:#fff;font-weight:600}
.mv-day i{width:4px;height:4px;border-radius:50%;margin-top:2px}
.mv-day i.partial{background:var(--warn)} .mv-day i.full{background:var(--success)} .mv-day i.over{background:var(--over)}
.mv-day.sel i{background:#fff}
.mv-rate{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:var(--muted);padding:7px 6px 0}
.mv-rate b{color:var(--primary)}
.mv-daybar{display:flex;align-items:center;gap:8px;padding:10px 14px 2px;font-size:13px;font-weight:600}
.mv-daybar .badge{font-weight:400}
.mv-daybar .sum{margin-left:auto;font-size:11px;color:var(--muted);font-weight:400}
.mv-groups{padding-bottom:12px}
.mv-ghead{display:flex;align-items:center;gap:6px;padding:9px 14px 5px;font-size:11px;font-weight:600;color:var(--muted)}
.mv-ghead::before{content:"";width:3px;height:11px;border-radius:2px;background:var(--primary)}
.mv-ghead .cnt{margin-left:auto;font-weight:400;color:var(--weak)}
.mv-card{position:relative;display:flex;align-items:center;gap:9px;background:#fff;border-radius:9px;
padding:8px 10px;margin:0 12px 7px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.mv-card.done{background:#f7fbf8}
.mv-ring{width:42px;height:42px;position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.mv-ring svg{display:block}
.mv-ring .c{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}
.mv-ring .c em{font-style:normal;font-size:9px;color:var(--muted);font-weight:400}
.mv-ring .ov{position:absolute;top:-3px;right:-5px;font-size:9px;color:#fff;background:var(--orange);border-radius:999px;padding:0 4px}
.mv-main{flex:1;min-width:0}
.mv-name{font-size:14px;font-weight:600;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.mv-meta{font-size:10px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.mv-meta .st{color:var(--orange);background:rgba(255,143,31,.12);border-radius:3px;padding:0 4px;margin-left:5px}
.mv-btn{font-size:11px;border-radius:999px;padding:4px 12px;background:var(--primary);color:#fff;flex-shrink:0;white-space:nowrap}
.mv-btn.grey{background:#f2f3f5;color:var(--muted)}
.mv-more{color:#c0c4cc;font-size:15px;line-height:1;padding:4px 2px 4px 6px;flex-shrink:0}
/* 操作面板(⋯ 触发) */
.mv-sheet .sheet-title{font-size:15px;margin-bottom:8px}
.mv-sheet-row{display:flex;align-items:center;justify-content:space-between;padding:12px 4px;border-top:1px solid var(--line);font-size:14px}
.mv-sheet-row.danger{color:var(--danger)}
.mv-sheet-row .k{font-size:11px;color:var(--weak)}
/* ---------- 清单 · 模板分类(V0.2.1 草案) ---------- */
.tpl-card{background:#fff;border-radius:10px;padding:11px 12px;margin:0 12px 10px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.tpl-row{display:flex;align-items:center;gap:7px}
.tpl-name{font-size:14px;font-weight:600}
.tpl-badge{font-size:10px;color:var(--muted);background:#f2f3f5;border-radius:4px;padding:1px 5px}
.tpl-num{margin-left:auto;font-size:11px;color:var(--muted)}
.tpl-chips{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}
.tpl-chip{font-size:10px;color:var(--muted);background:#f7f8fa;border-radius:4px;padding:2px 6px}
.tpl-chip.more{color:var(--primary);background:#f0f5ff}
.tpl-acts{display:flex;gap:14px;margin-top:9px;padding-top:7px;border-top:1px dashed var(--line)}
/* 新建清单(无场景,模板从右侧滑出选择) */
.pick-row{display:flex;align-items:center;gap:8px;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:14px;color:var(--weak)}
.pick-row .meta{margin-left:auto;display:flex;align-items:center;gap:4px;font-size:11px;color:var(--muted)}
.side-panel{position:absolute;top:28px;right:0;bottom:0;width:78%;background:#fff;z-index:11;
box-shadow:-6px 0 18px rgba(15,23,42,.12);display:flex;flex-direction:column;padding:14px 0 0}
.side-head{display:flex;align-items:center;padding:0 16px 4px}
.side-head b{font-size:16px}
.side-head .x{margin-left:auto;font-size:20px;color:#c8c8c8;line-height:1}
.side-tip{font-size:11px;color:var(--muted);padding:0 16px 8px}
.side-item{display:flex;align-items:baseline;gap:8px;padding:11px 16px}
.side-item.on{background:#f0f5ff}
.side-item .nm{flex:1;font-size:14px;font-weight:600;display:flex;align-items:center;gap:6px}
.side-item.on .nm{color:var(--primary)}
.side-item .sub{font-size:11px;color:var(--muted)}
/* ---------- 宏观 · tab 溢出提示(V0.2.1 草案) ---------- */
.mtabs-wrap{position:relative;background:#fff;border-bottom:1px solid var(--line)}
.mtabs-wrap .mtabs{border-bottom:none}
.mtabs-fade{position:absolute;top:0;right:0;bottom:0;width:52px;display:flex;align-items:center;justify-content:flex-end;
padding-right:8px;background:linear-gradient(to right,rgba(255,255,255,0),#fff 55%,#fff)}
.mtabs-fade .hint{width:19px;height:19px;border-radius:50%;background:#fff;border:1px solid #e3e6ea;
display:flex;align-items:center;justify-content:center;color:var(--primary);font-size:13px;font-weight:700;
box-shadow:0 1px 4px rgba(0,0,0,.14)}
/* ---------- 表格 ---------- */ table{width:100%;border-collapse:collapse;background:#fff;border:1px solid #e6e8eb;border-radius:10px;overflow:hidden;font-size:13px}
th,td{border-bottom:1px solid var(--line);padding:9px 12px;text-align:left;vertical-align:middle}
th{background:#fafbfc;font-weight:600;color:#444;white-space:nowrap}
tr:last-child td{border-bottom:none}
td code{background:#f4f6f8;border-radius:4px;padding:1px 5px;font-size:12px}
.legend{display:flex;flex-wrap:wrap;gap:18px;font-size:12px;color:var(--muted);margin:10px 0 0}
.legend span{display:flex;align-items:center;gap:6px}
.note{font-size:12px;color:var(--weak);margin-top:8px}
.flow{background:#fff;border:1px solid #e6e8eb;border-radius:12px;padding:16px 18px;font-size:13px;color:#333;white-space:pre;overflow-x:auto;line-height:1.7}
/* ---------- 宏观子 Tab(6 项,可横向滚动) ---------- */
.mtabs{display:flex;gap:18px;padding:10px 14px 8px;overflow-x:auto;background:#fff;border-bottom:1px solid var(--line);font-size:13px;color:var(--muted);white-space:nowrap}
.mtabs::-webkit-scrollbar{display:none}
.mtabs .on{color:var(--primary);font-weight:600;position:relative}
.mtabs .on::after{content:"";position:absolute;left:50%;transform:translateX(-50%);bottom:-9px;width:22px;height:3px;border-radius:2px;background:var(--primary)}
.mtabs .badge-dot{display:inline-block;min-width:15px;height:15px;line-height:15px;padding:0 4px;margin-left:3px;
background:var(--danger);color:#fff;font-size:10px;border-radius:8px;text-align:center;vertical-align:1px}
/* ---------- 资讯卡片(报警 / 新闻) ---------- */
.feed{padding:10px 14px 14px;display:flex;flex-direction:column;gap:8px}
.feed-card{background:#fff;border:1px solid #eef0f2;border-radius:10px;padding:10px 12px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.feed-title{font-size:14px;font-weight:600;line-height:1.45;color:var(--text)}
.feed-sub{font-size:12px;color:var(--muted);margin-top:4px;line-height:1.5}
.feed-meta{display:flex;gap:10px;font-size:11px;color:var(--weak);margin-top:6px}
.feed-meta .rk{color:var(--primary)}
.alert-name{color:var(--danger);font-weight:600}
/* ---------- 我的页 ---------- */
.me-head{display:flex;flex-direction:column;align-items:center;gap:4px;padding:18px 0 16px;background:#fff}
.me-avatar{width:60px;height:60px;border-radius:50%;background:#e8effd;color:var(--primary);
display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:600}
.me-name{font-size:16px;font-weight:600}
.me-mail{font-size:12px;color:var(--weak)}
.me-group{margin:10px 12px 0;background:#fff;border-radius:10px;overflow:hidden;border:1px solid #eef0f2}
.me-group .g-head{font-size:12px;color:var(--weak);padding:9px 14px 6px}
.me-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 14px;border-top:1px solid var(--line);font-size:14px}
.me-group .g-head + .me-row{border-top:none}
.me-row .val{font-size:12px;color:var(--weak)}
.me-row .hint{font-size:11px;color:var(--weak);margin-top:2px;line-height:1.45}
.sw{width:42px;height:25px;border-radius:999px;background:#e5e7eb;position:relative;flex-shrink:0;transition:background .2s}
.sw::after{content:"";position:absolute;top:2px;left:2px;width:21px;height:21px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.2)}
.sw.on{background:var(--success)}
.sw.on::after{left:19px}
.me-actions{display:flex;gap:8px;padding:12px 14px;border-top:1px solid var(--line)}
.me-actions .mini-btn{flex:1;text-align:center;padding:7px 0;font-size:13px}
.me-actions .mini-btn.ghost{border-color:#e5e7eb;color:var(--muted)}
</style>
</head>
<body>
<div class="wrap">
<div class="doc-head">
<h1>iBoard V0.2 · 界面设计</h1>
<p>打卡 / 日历 / 清单 —— 移动端静态高保真原型(设计基线 420 × 820,适配 375~430px)</p>
<div class="meta">
<span>版本 V0.2(0.2.0)</span><span>分支 dev-0.2</span><span>antd-mobile 风格</span>
<span>底部 4 Tab(打卡默认)</span><span>时区 Asia/Shanghai</span><span>文档日期 2026-10-22 · 修订 2026-10-23</span>
</div>
</div>
<!-- ============ 1 设计规范 ============ -->
<h2>1 设计规范<span class="sub">Design Tokens</span></h2>
<p class="desc">沿用项目既有 <code>:root</code> 变量(<code>src/styles/globals.css</code>),本模块不引入新主题。</p>
<div class="tokens">
<div class="panel">
<h4>语义色</h4>
<div class="swatches">
<div class="sw"><i style="background:#1677ff"></i><b>#1677ff</b><em>主色 / 今日 / 链接</em></div>
<div class="sw"><i style="background:#00b578"></i><b>#00b578</b><em>完成 / 进度条</em></div>
<div class="sw"><i style="background:#ffc53d"></i><b>#ffc53d</b><em>部分完成 / 粒子</em></div>
<div class="sw"><i style="background:#a855f7"></i><b>#a855f7</b><em>超额完成</em></div>
<div class="sw"><i style="background:#ff8f1f"></i><b>#ff8f1f</b><em>连续天数 / 超额徽标</em></div>
<div class="sw"><i style="background:#ff3141"></i><b>#ff3141</b><em>危险操作</em></div>
<div class="sw"><i style="background:#f5f5f5"></i><b>#f5f5f5</b><em>页面底色</em></div>
<div class="sw"><i style="background:#f7fbf8"></i><b>#f7fbf8</b><em>已完成卡片底</em></div>
</div>
<div class="legend" style="margin-top:14px">
<span><i class="dot full"></i>日历·全部完成</span>
<span><i class="dot partial"></i>日历·部分完成</span>
<span><i class="dot over"></i>日历·超额</span>
</div>
</div>
<div class="panel">
<h4>布局与圆角</h4>
<div class="kv">
<span>视口</span><b>420 × 820(375~430 自适应)</b>
<span>NavBar 高</span><b>45px</b>
<span>TabBar 高</span><b>72px(含安全区)</b>
<span>页面内边距</span><b>左右 12px</b>
<span>卡片圆角</span><b>10px</b>
<span>卡片间距</span><b>10px 纵向</b>
<span>卡片阴影</span><b>0 1px 2px rgba(0,0,0,.04)</b>
<span>环形尺寸</span><b>58 × 58,环宽 6px</b>
<span>最小点按区</span><b>≥ 40 × 40</b>
</div>
</div>
<div class="panel">
<h4>字号层级</h4>
<div class="type-row"><em>18 / 700</em><span style="font-size:18px;font-weight:700">日期标题「10月22日」</span></div>
<div class="type-row"><em>17 / 600</em><span style="font-size:17px;font-weight:600">NavBar 标题「打卡」</span></div>
<div class="type-row"><em>16 / 700</em><span style="font-size:16px;font-weight:700">环形中心数字 / 弹窗标题</span></div>
<div class="type-row"><em>15 / 600</em><span style="font-size:15px;font-weight:600">打卡项名 / 清单标题</span></div>
<div class="type-row"><em>13 / 400</em><span style="font-size:13px">正文 / 日历日期数字</span></div>
<div class="type-row"><em>12 / 400</em><span style="font-size:12px;color:#666">次要信息 / 操作链接</span></div>
<div class="type-row"><em>11 / 400</em><span style="font-size:11px;color:#999">备注 / 周期范围 / 徽标</span></div>
</div>
</div>
<!-- ============ 2 主界面 ============ -->
<h2>2 主界面<span class="sub">三个子视图(V0.2 现状;合并方案见 2.12 ②)</span></h2>
<div class="stage">
<!-- ---------- 2.1 打卡视图 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="habit-head">
<div>
<div class="habit-date">10月22日 周四</div>
<div class="habit-summary">已完成 <strong>2</strong> / 4</div>
</div>
<div class="head-right">
<div class="mini-btn solid">+ 新建</div>
</div>
</div>
<div class="habit-list">
<!-- 未完成:每天 2 次,0/2 -->
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#1677ff" stroke-width="6" stroke-linecap="round"
stroke-dasharray="163.4" stroke-dashoffset="163.4" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">0<em>/2</em></div>
</div>
<div class="habit-main">
<div class="habit-name">喝水</div>
<div class="habit-meta">每天 2 次<span class="streak">连续 12</span></div>
<div class="habit-range">今日</div>
</div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
<!-- 未完成:每周 3 次,1/3 = 33.3% -->
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#ff8f1f" stroke-width="6" stroke-linecap="round"
stroke-dasharray="163.4" stroke-dashoffset="108.9" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">1<em>/3</em></div>
</div>
<div class="habit-main">
<div class="habit-name">健身</div>
<div class="habit-meta">每周 3 次<span class="streak">连续 5</span></div>
<div class="habit-range">10-19 ~ 10-25</div>
</div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
<!-- 已完成:每月 12 次,12/12 闭合 -->
<div class="habit-card done">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#00b578" stroke-width="6" stroke-linecap="round"
stroke-dasharray="163.4" stroke-dashoffset="0" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M4 12.5l5 5L20 6.5" stroke="#00b578" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
<div class="habit-main">
<div class="habit-name">读书</div>
<div class="habit-meta">每月 12 次<span class="streak">连续 3</span></div>
<div class="habit-range">10-01 ~ 10-31</div>
</div>
<div class="habit-actions"><div class="mini-btn grey">再打一次</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
<!-- 已完成 + 超额:每天 1 次,打了 2 次 -->
<div class="habit-card done">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#a855f7" stroke-width="6" stroke-linecap="round"
stroke-dasharray="163.4" stroke-dashoffset="0" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M4 12.5l5 5L20 6.5" stroke="#a855f7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<span class="ring-over">+1</span>
</div>
<div class="habit-main">
<div class="habit-name">吃药</div>
<div class="habit-meta">每天 1 次<span class="streak">连续 30</span></div>
<div class="habit-range">今日<span class="over-txt">超额 1</span></div>
</div>
<div class="habit-actions"><div class="mini-btn grey">再打一次</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.1 打卡视图 · 今天<span class="tag">默认进入</span></b>
<p>头部日期 + 完成数;未完成卡片排在前面。环形中心未达标显示 <code>done/target</code>,达标显示对勾(每天 1 次打完即刻闭合);超额在右上角显示 <code>+n</code>。点环或「打卡」按钮均可打卡,卡片底部提供撤销 / 编辑 / 归档。</p>
</div>
</div>
<!-- ---------- 2.2 日历视图 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px">
<div>打卡</div><div class="on">日历</div><div>清单</div>
</div>
<div class="cal-month-bar">
<span class="cal-arrow">‹</span><span class="cal-month">2026年10月</span><span class="cal-arrow">›</span>
<span class="cal-today">回到今天</span>
</div>
<div class="seg">
<div>昨天</div><div class="on">今天</div><div>明天</div>
</div>
<div class="cal-grid">
<div class="grid7 wd"><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span></div>
<div class="grid7">
<div class="day out"><span class="num">28</span></div>
<div class="day out"><span class="num">29</span></div>
<div class="day out"><span class="num">30</span></div>
<div class="day"><span class="num">1</span><i class="dot full"></i></div>
<div class="day"><span class="num">2</span><i class="dot full"></i></div>
<div class="day"><span class="num">3</span><i class="dot partial"></i></div>
<div class="day"><span class="num">4</span><i class="dot full"></i></div>
<div class="day"><span class="num">5</span></div>
<div class="day"><span class="num">6</span><i class="dot full"></i></div>
<div class="day"><span class="num">7</span><i class="dot over"></i></div>
<div class="day"><span class="num">8</span><i class="dot full"></i></div>
<div class="day"><span class="num">9</span><i class="dot partial"></i></div>
<div class="day"><span class="num">10</span><i class="dot full"></i></div>
<div class="day"><span class="num">11</span><i class="dot full"></i></div>
<div class="day"><span class="num">12</span></div>
<div class="day"><span class="num">13</span></div>
<div class="day"><span class="num">14</span><i class="dot full"></i></div>
<div class="day"><span class="num">15</span><i class="dot full"></i></div>
<div class="day"><span class="num">16</span><i class="dot partial"></i></div>
<div class="day"><span class="num">17</span><i class="dot full"></i></div>
<div class="day"><span class="num">18</span><i class="dot full"></i></div>
<div class="day"><span class="num">19</span><i class="dot full"></i></div>
<div class="day"><span class="num">20</span><i class="dot full"></i></div>
<div class="day"><span class="num">21</span><i class="dot full"></i></div>
<div class="day today sel"><span class="num">22</span><i class="dot partial"></i></div>
<div class="day future"><span class="num">23</span></div>
<div class="day future"><span class="num">24</span></div>
<div class="day future"><span class="num">25</span></div>
<div class="day future"><span class="num">26</span></div>
<div class="day future"><span class="num">27</span></div>
<div class="day future"><span class="num">28</span></div>
<div class="day future"><span class="num">29</span></div>
<div class="day future"><span class="num">30</span></div>
<div class="day future"><span class="num">31</span></div>
<div class="day out"><span class="num">1</span></div>
</div>
</div>
<div class="cal-rate">本月完成率 <strong>82%</strong></div>
<div class="cal-detail-title">今天 · 2026-10-22</div>
<div class="cal-item done">
<i class="cdot" style="background:#1677ff"></i><span class="cname">喝水</span>
<span class="cmeta">每天 2 次 · 当日 2/2</span>
<span class="mini-btn grey">已达标</span>
</div>
<div class="cal-item">
<i class="cdot" style="background:#ff8f1f"></i><span class="cname">健身</span>
<span class="cmeta">每周 3 次 · 当日 0/1</span>
<span class="mini-btn solid">打卡</span>
</div>
<div class="cal-item done">
<i class="cdot" style="background:#00b578"></i><span class="cname">读书</span>
<span class="cmeta">每月 12 次 · 当日 1/1</span>
<span class="mini-btn grey">已达标</span>
</div>
<div class="cal-item done">
<i class="cdot" style="background:#a855f7"></i><span class="cname">吃药</span>
<span class="cmeta">每天 1 次 · 当日 2/1</span>
<span class="mini-btn grey">已达标</span>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.2 日历视图 · 今天选中</b>
<p>周一起始月历 + 「昨天 / 今天 / 明天」快捷分段;日期下方状态点:绿=全部完成、黄=部分完成、紫=超额、无点=未完成或未来。今天用主色数字并选中高亮,未来日期数字弱化。下方为该日明细,可直接打卡;底部展示本月完成率。</p>
</div>
</div>
<!-- ---------- 2.3 清单视图 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px">
<div>打卡</div><div>日历</div><div class="on">清单</div>
</div>
<div class="list-head">
<div class="seg"><div class="on">进行中</div><div>已归档</div><div>模板</div></div>
<div class="mini-btn solid">+ 新建</div>
</div>
<div class="checklist-card">
<div class="cl-title-row"><span class="cl-title">出差 · 杭州</span><span class="cl-scene">出差</span></div>
<div class="progress-row">
<div class="bar"><i style="width:66.7%"></i></div><span class="count">8/12</span><span class="arrow">›</span>
</div>
<div class="cl-body">
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">身份证 / 证件</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">手机充电器 + 充电宝</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">笔记本电脑 + 电源</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">耳机</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">换洗衣物</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">洗漱包</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">常用药</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">车票 / 机票 / 酒店确认</span><span class="cl-del">×</span></div>
<div class="cl-item"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">雨伞</span><span class="cl-del">×</span></div>
<div class="cl-item"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">工牌 / 名片</span><span class="cl-del">×</span></div>
<div class="cl-item"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">报销单据</span><span class="cl-del">×</span></div>
<div class="cl-item"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">现金零钱</span><span class="cl-del">×</span></div>
<div class="cl-add"><div class="inp ph">添加检查项</div><div class="mini-btn solid">添加</div></div>
<div class="cl-actions">
<span class="link">全选</span><span class="link">全不选</span><span class="link">复制</span>
<span class="link">存为模板</span><span class="link">归档</span><span class="link danger">删除</span>
</div>
</div>
</div>
<div class="checklist-card">
<div class="cl-title-row"><span class="cl-title">回老家 · 国庆</span><span class="cl-scene">回老家</span></div>
<div class="progress-row">
<div class="bar"><i style="width:0%"></i></div><span class="count">0/12</span><span class="arrow" style="transform:none">›</span>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.3 清单视图 · 展开态</b>
<p>卡片头部常显标题、场景标签、进度条与 <code>8/12</code>;点击展开逐条勾选(勾选后绿色圆底、文字加删除线并弱化)。底部可继续添加检查项,并提供全选 / 全不选 / 复制 / 存为模板 / 归档 / 删除。收起态只保留头部一行。</p>
</div>
</div>
<!-- ---------- 2.4 新建打卡项弹窗 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="habit-head">
<div>
<div class="habit-date">10月22日 周四</div>
<div class="habit-summary">已完成 <strong>2</strong> / 4</div>
</div>
<div class="head-right"><div class="mini-btn solid">+ 新建</div></div>
</div>
<div class="habit-list">
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#1677ff" stroke-width="6" stroke-linecap="round" stroke-dasharray="163.4" stroke-dashoffset="163.4" transform="rotate(-90 29 29)"/>
</svg><div class="ring-center">0<em>/2</em></div>
</div>
<div class="habit-main"><div class="habit-name">喝水</div><div class="habit-meta">每天 2 次</div><div class="habit-range">今日</div></div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
</div>
</div>
<div class="mask"></div>
<div class="sheet">
<div class="sheet-title">新建打卡项</div>
<div class="field"><label>名称</label><div class="inp ph">例如:喝水 / 健身 / 读书</div></div>
<div class="field"><label>周期</label>
<div class="chips"><div class="chip on">每天</div><div class="chip">每周</div><div class="chip">每月</div><div class="chip">每年</div></div>
</div>
<div class="field"><label>目标次数(周期内)</label>
<div class="stepper"><span>−</span><b>1</b><span class="act">+</span></div>
</div>
<div class="field"><label>颜色</label>
<div class="colors">
<span class="cd on" style="background:#1677ff"></span><span class="cd" style="background:#00b578"></span>
<span class="cd" style="background:#ff8f1f"></span><span class="cd" style="background:#ff3141"></span>
<span class="cd" style="background:#a855f7"></span><span class="cd" style="background:#14b8a6"></span>
<span class="cd" style="background:#f43f9e"></span>
</div>
</div>
<div class="field"><label>备注(可选)</label><div class="inp ph">补充说明</div></div>
<div class="btn-primary">保存</div>
<div class="btn-ghost">取消</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.4 新建 / 编辑打卡项(底部弹层)</b>
<p>名称 ≤20 字;周期四选一(每天 / 每周 / 每月 / 每年);目标次数用 Stepper,最小 1;颜色 7 色预设;备注可选。编辑态标题变为「编辑打卡项」并回填当前值。</p>
</div>
</div>
<!-- ---------- 2.5 完成特效 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="habit-head">
<div><div class="habit-date">10月22日 周四</div><div class="habit-summary">已完成 <strong>3</strong> / 4</div></div>
<div class="head-right"><div class="mini-btn solid">+ 新建</div></div>
</div>
<div class="habit-list">
<div class="habit-card done" style="overflow:visible">
<div class="ring">
<!-- 特效层:光环 + 14 颗粒子(静态呈现 t=250ms 时的形态) -->
<div class="fx">
<div class="halo"></div><div class="halo halo2"></div>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(0deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(26deg) translateY(-40px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(52deg) translateY(-34px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(78deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(104deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(130deg) translateY(-40px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(156deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(182deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(208deg) translateY(-34px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(234deg) translateY(-40px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(260deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(286deg) translateY(-46px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(312deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(338deg) translateY(-40px)"></i>
</div>
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#00b578" stroke-width="6" stroke-linecap="round" stroke-dasharray="163.4" stroke-dashoffset="0" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#00b578" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
</div>
<div class="habit-main"><div class="habit-name">吃药</div><div class="habit-meta">每天 1 次<span class="streak">连续 30</span></div><div class="habit-range">今日</div></div>
<div class="habit-actions"><div class="mini-btn grey">再打一次</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#ff8f1f" stroke-width="6" stroke-linecap="round" stroke-dasharray="163.4" stroke-dashoffset="108.9" transform="rotate(-90 29 29)"/>
</svg><div class="ring-center">1<em>/3</em></div>
</div>
<div class="habit-main"><div class="habit-name">健身</div><div class="habit-meta">每周 3 次</div><div class="habit-range">10-19 ~ 10-25</div></div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.5 达标瞬间 · 完成特效</b>
<p>环形闭合 + 对勾缩放入场(0.32s);同时 14 颗粒子沿圆周扩散(26~58px,交替主色与金色)+ 主色光环脉冲(0.7s),总时长 ≤900ms,<code>pointer-events:none</code> 不阻塞后续操作;并触发 18ms 轻震动。<b>特效恒开</b>(V0.2 设计变更:头部不再有开关)。</p>
</div>
</div>
<!-- ---------- 2.6 日历·明天(计划) ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px"><div>打卡</div><div class="on">日历</div><div>清单</div></div>
<div class="cal-month-bar"><span class="cal-arrow">‹</span><span class="cal-month">2026年10月</span><span class="cal-arrow">›</span><span class="cal-today">回到今天</span></div>
<div class="seg"><div>昨天</div><div>今天</div><div class="on">明天</div></div>
<div class="cal-grid">
<div class="grid7 wd"><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span></div>
<div class="grid7">
<div class="day out"><span class="num">28</span></div><div class="day out"><span class="num">29</span></div><div class="day out"><span class="num">30</span></div>
<div class="day"><span class="num">1</span><i class="dot full"></i></div>
<div class="day"><span class="num">2</span><i class="dot full"></i></div>
<div class="day"><span class="num">3</span><i class="dot partial"></i></div>
<div class="day"><span class="num">4</span><i class="dot full"></i></div>
<div class="day"><span class="num">5</span></div>
<div class="day"><span class="num">6</span><i class="dot full"></i></div>
<div class="day"><span class="num">7</span><i class="dot over"></i></div>
<div class="day"><span class="num">8</span><i class="dot full"></i></div>
<div class="day"><span class="num">9</span><i class="dot partial"></i></div>
<div class="day"><span class="num">10</span><i class="dot full"></i></div>
<div class="day"><span class="num">11</span><i class="dot full"></i></div>
<div class="day"><span class="num">12</span></div><div class="day"><span class="num">13</span></div>
<div class="day"><span class="num">14</span><i class="dot full"></i></div>
<div class="day"><span class="num">15</span><i class="dot full"></i></div>
<div class="day"><span class="num">16</span><i class="dot partial"></i></div>
<div class="day"><span class="num">17</span><i class="dot full"></i></div>
<div class="day"><span class="num">18</span><i class="dot full"></i></div>
<div class="day"><span class="num">19</span><i class="dot full"></i></div>
<div class="day"><span class="num">20</span><i class="dot full"></i></div>
<div class="day today"><span class="num">21</span><i class="dot full"></i></div>
<div class="day"><span class="num">22</span><i class="dot partial"></i></div>
<div class="day future sel"><span class="num">23</span></div>
<div class="day future"><span class="num">24</span></div>
<div class="day future"><span class="num">25</span></div>
<div class="day future"><span class="num">26</span></div><div class="day future"><span class="num">27</span></div>
<div class="day future"><span class="num">28</span></div><div class="day future"><span class="num">29</span></div>
<div class="day future"><span class="num">30</span></div><div class="day future"><span class="num">31</span></div>
<div class="day out"><span class="num">1</span></div>
</div>
</div>
<div class="cal-rate">本月完成率 <strong>82%</strong></div>
<div class="cal-detail-title">明天 · 2026-10-23<span class="badge">计划(不可打卡)</span></div>
<div class="cal-item"><i class="cdot" style="background:#1677ff"></i><span class="cname">喝水</span><span class="cmeta">每天 2 次 · 当日 0/2</span><span class="cplan">待办</span></div>
<div class="cal-item"><i class="cdot" style="background:#ff8f1f"></i><span class="cname">健身</span><span class="cmeta">每周 3 次 · 当日 0/1</span><span class="cplan">待办</span></div>
<div class="cal-item"><i class="cdot" style="background:#00b578"></i><span class="cname">读书</span><span class="cmeta">每月 12 次 · 当日 0/1</span><span class="cplan">待办</span></div>
<div class="cal-item"><i class="cdot" style="background:#a855f7"></i><span class="cname">吃药</span><span class="cmeta">每天 1 次 · 当日 0/1</span><span class="cplan">待办</span></div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.6 日历 · 明天(计划,只读)</b>
<p>未来日期不允许打卡:标题右侧显示「计划(不可打卡)」徽标,条目右侧只显示「待办」文案而不渲染按钮,月历上未来日期数字弱化且不打点。昨天视图则相反——可补打最近 7 天内的漏打卡。</p>
</div>
</div>
<!-- ---------- 2.7 清单完成 + 空态 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px"><div>打卡</div><div>日历</div><div class="on">清单</div></div>
<div class="list-head">
<div class="seg"><div class="on">进行中</div><div>已归档</div><div>模板</div></div>
<div class="mini-btn solid">+ 新建</div>
</div>
<div class="checklist-card done" style="position:relative;overflow:visible">
<div class="cl-title-row"><span class="cl-title">出差 · 杭州</span><span class="cl-scene">出差</span></div>
<div class="progress-row"><div class="bar"><i style="width:100%"></i></div><span class="count">12/12</span><span class="arrow">›</span></div>
<div class="fx" style="left:50%;top:34px;right:auto;bottom:auto;width:0;height:0">
<div class="halo" style="border-color:#00b578"></div>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(10deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(46deg) translateY(-56px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(92deg) translateY(-46px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(140deg) translateY(-62px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(190deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(240deg) translateY(-56px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(300deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(340deg) translateY(-62px)"></i>
</div>
</div>
<div class="checklist-card">
<div class="cl-title-row"><span class="cl-title">回老家 · 国庆</span><span class="cl-scene">回老家</span></div>
<div class="progress-row"><div class="bar"><i style="width:0%"></i></div><span class="count">0/12</span><span class="arrow" style="transform:none">›</span></div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.7 清单全部完成(12/12)</b>
<p>进度条 100%、计数变为 <code>12/12</code>、卡片底色转 <code>#f7fbf8</code>,并播放与打卡一致的完成特效。清单完成后用户通常选择「归档」或「复制」复用。</p>
</div>
</div>
<!-- ---------- 2.8 空态与归档 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="habit-head">
<div><div class="habit-date">10月22日 周四</div><div class="habit-summary">已完成 <strong>0</strong> / 0</div></div>
<div class="head-right"><div class="mini-btn solid">+ 新建</div></div>
</div>
<div class="empty">
<div class="ico">○</div>
<p>还没有打卡项</p>
<div style="display:inline-block" class="mini-btn solid">创建第一个打卡项</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.8 空态</b>
<p>三个视图均具备加载中 / 空态 / 错误(带重试)三态。空态给出明确引导按钮。<b>V0.2 设计变更</b>:打卡页头部不再有「特效开 / 特效关」开关,完成特效与触感反馈<b>恒开</b>,没有任何关闭入口。</p>
</div>
</div>
<!-- ---------- 2.9 宏观(原首页报警/新闻并入) ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar-sm">宏观</div>
<div class="body">
<div class="mtabs-wrap">
<div class="mtabs">
<div>报警<span class="badge-dot">3</span></div><div class="on">新闻</div>
<div>油价</div><div>经济数据</div><div>策略研究</div><div>经济文章</div>
</div>
<div class="mtabs-fade"><span class="hint">›</span></div>
</div>
<div class="feed">
<div class="feed-card">
<div class="feed-title">央行开展 5000 亿元 MLF 操作,中标利率持平</div>
<div class="feed-sub">本月 MLF 净投放 2000 亿元,市场流动性保持合理充裕…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-22</span><span class="rk">#1</span></div>
</div>
<div class="feed-card">
<div class="feed-title">9 月社零同比增长 3.2%,服务消费延续回暖</div>
<div class="feed-sub">餐饮收入增速回升至 5.1%,可选消费分化明显…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-21</span><span class="rk">#2</span></div>
</div>
<div class="feed-card">
<div class="feed-title">美联储会议纪要偏鹰,美元指数小幅走强</div>
<div class="feed-sub">点阵图显示年内或仍有一次降息空间,但分歧加大…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-21</span><span class="rk">#3</span></div>
</div>
<div class="feed-card">
<div class="feed-title">OPEC+ 维持减产节奏,国际油价日内涨逾 1%</div>
<div class="feed-sub">布伦特原油重回 78 美元上方,化工链成本端承压…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-20</span><span class="rk">#4</span></div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.9 宏观 · 原「首页」报警/新闻并入<span class="tag">设计变更</span></b>
<p>「首页」Tab 取消,报警与新闻并入宏观。<b>2026-10-23 调整</b>:两者移到<b>最左</b>(报警 → 新闻 → 油价 → 经济数据 → 策略研究 → 经济文章),<b>默认选中「新闻」</b>;共 6 个子 Tab 可横向滚动,当前项底部有 3px 主色指示条;报警数量以红色角标挂在子 Tab 上。tab 条右侧增加<b>渐变遮罩 + 箭头</b>,提示还有未展示的 tab(详见 2.12 ④)。新闻详情为 <code>/macro/news/:id</code>,底部「宏观」保持高亮。</p>
</div>
</div>
<!-- ---------- 2.10 我的 · OTA(开发版本 关 = 默认) ---------- -->
<div class="shot">
<div class="phone">
<div class="body">
<div class="me-head flush">
<div class="me-avatar">C</div>
<div class="me-name">Cheney</div>
<div class="me-mail">qian.huang@sunyard.com</div>
</div>
<div class="me-group">
<div class="g-head">前端升级 (OTA)</div>
<div class="me-row"><span>当前版本</span><span class="val">v0.2.0 · 20261022.1530</span></div>
<div class="me-row"><span>运行资源</span><span class="val">内置</span></div>
<div class="me-row">
<div>
<div>开发版本</div>
<div class="hint">按版本号判新:仅当服务端版本号更高时才提示更新</div>
</div>
<div class="sw"></div>
</div>
<div class="me-actions">
<div class="mini-btn solid">检查更新</div>
<div class="mini-btn ghost">立即升级</div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.10 我的 · OTA「开发版本」关闭(默认)<span class="tag">新功能</span></b>
<p>开关默认关闭 = <b>按版本号判新</b>:取服务端 <code>latest.json</code> 的 <code>version</code>(如 <code>0.2.0-ota</code>),与当前运行版本按数字字段逐段比较,<b>更高</b>才提示更新。用于生产,避免旧分支被重建(sequence 变大但版本更小)把设备拉回旧版。开关值保存在设备端 <code>localStorage.iboard_ota_dev_mode</code>。</p>
</div>
</div>
<!-- ---------- 2.11 我的 · OTA(开发版本 开) ---------- -->
<div class="shot">
<div class="phone">
<div class="body">
<div class="me-head flush">
<div class="me-avatar">C</div>
<div class="me-name">Cheney</div>
<div class="me-mail">qian.huang@sunyard.com</div>
</div>
<div class="me-group">
<div class="g-head">前端升级 (OTA)</div>
<div class="me-row"><span>当前版本</span><span class="val">v0.2.0 · 20261022.1530</span></div>
<div class="me-row"><span>运行资源</span><span class="val">内置</span></div>
<div class="me-row">
<div>
<div>开发版本</div>
<div class="hint">按 build sequence 判新:每次构建都算新版本,便于开发联调</div>
</div>
<div class="sw on"></div>
</div>
<div class="me-row"><span>新版本</span><span class="val">v0.2.0-ota (288 KB)</span></div>
<div class="me-actions">
<div class="mini-btn solid">立即升级</div>
<div class="mini-btn ghost">回滚</div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.11 我的 · OTA「开发版本」打开</b>
<p>开关打开 = <b>按 build sequence 判新</b>:走 hotswap 插件的 <code>checkUpdate()</code>,只要服务端 <code>sequence</code> 比本地大就算新版本——每次 CI 构建 sequence 都会变(默认取时间戳),所以任何一次提交都能推到设备上,适合开发联调。切换开关会清空上一次的检查结果,避免上一种口径的结论误导。</p>
</div>
</div>
</div>
<!-- ============ 2.12 V0.2.1 界面调整草案 ============ -->
<h2>2.12 V0.2.1 界面调整<span class="sub">头部统一 / 日历·打卡合并 / 模板分类 / 宏观 tab 溢出</span></h2>
<p class="desc">本节为 2026-10-23 提出的调整方案。其中 <b>①头部统一 / ③模板分类 / ④宏观 tab 溢出</b> 已按本节实现;
<b>②日历与打卡合并</b> 为待确认草案,<b>确认后再改代码</b>。</p>
<h3>① 四个主 Tab 头部统一</h3>
<table>
<tr><th>页</th><th>调整前</th><th>调整后</th></tr>
<tr><td>打卡</td><td>NavBar 45px,标题「打卡」17px 深色</td><td><b>23px</b>,标题「打卡」12px 主色加粗,字距 1px</td></tr>
<tr><td>宏观</td><td>NavBar 45px,<b>无标题</b>(空白)</td><td>23px + 标题「宏观」(同打卡规格)</td></tr>
<tr><td>监控</td><td>NavBar 45px,<b>无标题</b>(空白)</td><td>23px + 标题「监控」(同打卡规格)</td></tr>
<tr><td>我</td><td>NavBar 45px 空白条</td><td><b>整条去掉</b>,蓝色头像区直接顶到状态栏下方</td></tr>
</table>
<p class="note">实现为独立组件 <code>PageHeader</code>,只被打卡 / 宏观 / 监控 引用;「我」页不引用,因此三者互不影响。
头部背景取页面底色 <code>#f5f5f5</code>(不再是卡片白),视觉上更轻。</p>
<div class="stage">
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="habit-head">
<div><div class="habit-date">10月23日 周五</div><div class="habit-summary">已完成 <strong>2</strong> / 10</div></div>
<div class="head-right"><div class="mini-btn solid">+ 新建</div></div>
</div>
<div class="habit-list">
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#1677ff" stroke-width="6" stroke-linecap="round" stroke-dasharray="163.4" stroke-dashoffset="81.7" transform="rotate(-90 29 29)"/>
</svg><div class="ring-center">1<em>/2</em></div>
</div>
<div class="habit-main"><div class="habit-name">喝水</div><div class="habit-meta">每天 2 次<span class="streak">连续 12</span></div><div class="habit-range">今日</div></div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#ff8f1f" stroke-width="6" stroke-linecap="round" stroke-dasharray="163.4" stroke-dashoffset="163.4" transform="rotate(-90 29 29)"/>
</svg><div class="ring-center">0<em>/1</em></div>
</div>
<div class="habit-main"><div class="habit-name">俯卧撑</div><div class="habit-meta">每周 1 次</div><div class="habit-range">10-19 ~ 10-25</div></div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>①-a 打卡(头部已缩至 23px)<span class="tag">已实现</span></b>
<p>原 45px 白色 NavBar 换成 23px 底色条 + 主色标题,页面上移,首屏多出一张卡片的高度。</p>
</div>
</div>
<div class="shot">
<div class="phone">
<div class="navbar-sm">宏观</div>
<div class="body">
<div class="mtabs-wrap">
<div class="mtabs">
<div>报警<span class="badge-dot">1</span></div><div class="on">新闻</div><div>油价</div>
<div>经济数据</div><div>策略研究</div><div>经济文章</div>
</div>
<div class="mtabs-fade"><span class="hint">›</span></div>
</div>
<div class="feed">
<div class="feed-card">
<div class="feed-title">央行开展 5000 亿元 MLF 操作,中标利率持平</div>
<div class="feed-sub">本月 MLF 净投放 2000 亿元,市场流动性保持合理充裕…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-22</span><span class="rk">#1</span></div>
</div>
<div class="feed-card">
<div class="feed-title">9 月社零同比增长 3.2%,服务消费延续回暖</div>
<div class="feed-sub">餐饮收入增速回升至 5.1%,可选消费分化明显…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-21</span><span class="rk">#2</span></div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>①-b + ④ 宏观:头部标题化 · 报警/新闻置最左 · 右侧溢出提示<span class="tag">已实现</span></b>
<p><b>顺序</b>:报警 → 新闻 → 油价 → 经济数据 → 策略研究 → 经济文章,<b>默认选中「新闻」</b>(<code>/macro</code> 自动跳 <code>/macro/news</code>)。<br>
<b>溢出提示</b>:tab 条仍可向右滚动时,右边缘出现白色渐变遮罩 + 主色圆底箭头「›」(1.5s 轻微左右摆动,可点击向右翻页),滚到末尾自动消失;切换 tab 时自动把激活项滚到可视区中间。<br>
antd-mobile Tabs 自带的右侧渐变遮罩只有 30px 且无箭头,用户难以察觉,故叠加此提示层。</p>
</div>
</div>
</div>
<h3>② 打卡:日历 + 打卡合并(<span style="color:var(--danger)">待确认草案</span>)</h3>
<p class="desc">把原来的「打卡」「日历」两个 tab 合成一个:<b>上半部日历,下半部打卡卡片</b>。取舍按下述规则:</p>
<table>
<tr><th>项</th><th>规则</th></tr>
<tr><td>页面结构</td><td>SwipeTabs 从 3 个减为 <b>2 个</b>:<b>日历(含打卡)</b> / <b>清单</b></td></tr>
<tr><td>上方日历</td><td>保留月历网格 + 月份左右切换 + 「回到今天」;<b>去掉</b>「昨天 / 今天 / 明天」分段控件(点日期即可,节省 38px 高度)</td></tr>
<tr><td>日历高度</td><td>日期格子 38px → <b>31px</b>,去掉 6 行补位中的空行;整块约 240px</td></tr>
<tr><td>下方打卡</td><td>沿用现有打卡卡片样式,但<b>紧凑化</b>:内边距 12px → 8px,环形 58px → <b>42px</b>(环宽 6 → 5),行高压缩,卡片间距 10 → 7px</td></tr>
<tr><td>卡片内容</td><td>保留环形进度(含达标对勾 / 超额 +n)、名称、周期与目标、连续天数、打卡按钮;<b>撤销 / 编辑 / 归档收进「⋯」</b>,减少一行高度</td></tr>
<tr><td>分组</td><td>按周期类型分组:<b>每天 → 每周 → 每月</b>,每组一个小标题(左侧主色竖条 + 组名 + 条数);<b>去掉「每年」分组</b>(已有年目标项并入「每月」区末尾的「其他周期」小标题,或建议直接隐藏年视图)</td></tr>
<tr><td>联动</td><td>点日历某日 → 下方卡片即该日数据(进度仍按该日所属周期算);未来日期只读,按钮变灰显示「计划」</td></tr>
<tr><td>空态</td><td>日历下方直接显示「该日没有打卡项」+「+ 新建」引导</td></tr>
</table>
<div class="stage">
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px">
<div class="on">日历</div><div>清单</div>
</div>
<div class="mv-cal">
<div class="mv-month">
<span class="arrow">‹</span><b>2026年10月</b><span class="arrow">›</span>
<span class="today">回到今天</span>
</div>
<div class="mv-grid mv-wd"><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span></div>
<div class="mv-grid">
<div class="mv-day out">29</div><div class="mv-day out">30</div>
<div class="mv-day">1<i class="full"></i></div><div class="mv-day">2<i class="full"></i></div>
<div class="mv-day">3<i class="partial"></i></div><div class="mv-day">4<i class="full"></i></div><div class="mv-day">5</div>
<div class="mv-day">6<i class="full"></i></div><div class="mv-day">7<i class="over"></i></div>
<div class="mv-day">8<i class="full"></i></div><div class="mv-day">9<i class="partial"></i></div>
<div class="mv-day">10<i class="full"></i></div><div class="mv-day">11<i class="full"></i></div><div class="mv-day">12</div>
<div class="mv-day">13</div><div class="mv-day">14<i class="full"></i></div><div class="mv-day">15<i class="full"></i></div>
<div class="mv-day">16<i class="partial"></i></div><div class="mv-day">17<i class="full"></i></div><div class="mv-day">18<i class="full"></i></div><div class="mv-day">19<i class="full"></i></div>
<div class="mv-day">20<i class="full"></i></div><div class="mv-day">21<i class="full"></i></div>
<div class="mv-day today sel">22<i class="partial"></i></div><div class="mv-day future">23</div><div class="mv-day future">24</div><div class="mv-day future">25</div>
<div class="mv-day future">26</div><div class="mv-day future">27</div><div class="mv-day future">28</div>
<div class="mv-day future">29</div><div class="mv-day future">30</div><div class="mv-day future">31</div><div class="mv-day out">1</div>
</div>
<div class="mv-rate"><span>本月完成率 <b>82%</b></span><span>今天 · 2026-10-22</span></div>
</div>
<div class="mv-daybar">今天<span class="badge">已完成 2 / 4</span><span class="sum">点环即打卡</span></div>
<div class="mv-groups">
<div class="mv-ghead">每天<span class="cnt">2 项</span></div>
<div class="mv-card">
<div class="mv-ring">
<svg width="42" height="42" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ececf0" stroke-width="5"/>
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#1677ff" stroke-width="5" stroke-linecap="round" stroke-dasharray="116.2" stroke-dashoffset="58.1" transform="rotate(-90 21 21)"/>
</svg>
<div class="c">1<em>/2</em></div>
</div>
<div class="mv-main">
<div class="mv-name">喝水</div>
<div class="mv-meta">每天 2 次 · 今日 1/2<span class="st">连续 12</span></div>
</div>
<div class="mv-btn">打卡</div>
<div class="mv-more">⋯</div>
</div>
<div class="mv-card done">
<div class="mv-ring">
<svg width="42" height="42" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ececf0" stroke-width="5"/>
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#a855f7" stroke-width="5" stroke-linecap="round" stroke-dasharray="116.2" stroke-dashoffset="0" transform="rotate(-90 21 21)"/>
</svg>
<div class="c"><svg width="15" height="15" viewBox="0 0 24 24" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#a855f7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
<span class="ov">+1</span>
</div>
<div class="mv-main">
<div class="mv-name">吃药</div>
<div class="mv-meta">每天 1 次 · 今日 2/1<span class="st">连续 30</span></div>
</div>
<div class="mv-btn grey">再打一次</div>
<div class="mv-more">⋯</div>
</div>
<div class="mv-ghead">每周<span class="cnt">1 项</span></div>
<div class="mv-card">
<div class="mv-ring">
<svg width="42" height="42" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ececf0" stroke-width="5"/>
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ff8f1f" stroke-width="5" stroke-linecap="round" stroke-dasharray="116.2" stroke-dashoffset="77.5" transform="rotate(-90 21 21)"/>
</svg>
<div class="c">1<em>/3</em></div>
</div>
<div class="mv-main">
<div class="mv-name">健身</div>
<div class="mv-meta">每周 3 次 · 本周 1/3<span class="st">连续 5</span></div>
</div>
<div class="mv-btn">打卡</div>
<div class="mv-more">⋯</div>
</div>
<div class="mv-ghead">每月<span class="cnt">1 项</span></div>
<div class="mv-card">
<div class="mv-ring">
<svg width="42" height="42" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ececf0" stroke-width="5"/>
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#00b578" stroke-width="5" stroke-linecap="round" stroke-dasharray="116.2" stroke-dashoffset="116.2" transform="rotate(-90 21 21)"/>
</svg>
<div class="c">0<em>/12</em></div>
</div>
<div class="mv-main">
<div class="mv-name">读书</div>
<div class="mv-meta">每月 12 次 · 本月 0/12</div>
</div>
<div class="mv-btn">打卡</div>
<div class="mv-more">⋯</div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>②-a 合并后的主视图(日历在上 · 打卡在下,按天 / 周 / 月分组)<span class="tag" style="background:#fff1f0;color:#ff3141">待确认</span></b>
<p>卡片整体压缩:环形 42px、内边距 8px、行距 7px,单卡高度约 58px(原 92px),一屏可看 4~5 项。分组顺序固定为 <b>每天 → 每周 → 每月</b>,
组标题左侧 3px 主色竖条;<b>无「每年」分组</b>。日历顶部的「昨天 / 今天 / 明天」分段已去掉,直接点日期切换,选中日靠「今天」主色数字与蓝色实心块识别。</p>
</div>
</div>
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px"><div class="on">日历</div><div>清单</div></div>
<div class="mv-cal">
<div class="mv-month"><span class="arrow">‹</span><b>2026年10月</b><span class="arrow">›</span><span class="today">回到今天</span></div>
<div class="mv-grid mv-wd"><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span></div>
<div class="mv-grid">
<div class="mv-day out">29</div><div class="mv-day out">30</div>
<div class="mv-day">1<i class="full"></i></div><div class="mv-day">2<i class="full"></i></div>
<div class="mv-day">3<i class="partial"></i></div><div class="mv-day">4<i class="full"></i></div><div class="mv-day">5</div>
<div class="mv-day">6<i class="full"></i></div><div class="mv-day">7<i class="over"></i></div>
<div class="mv-day">8<i class="full"></i></div><div class="mv-day">9<i class="partial"></i></div>
<div class="mv-day">10<i class="full"></i></div><div class="mv-day">11<i class="full"></i></div><div class="mv-day">12</div>
<div class="mv-day">13</div><div class="mv-day">14<i class="full"></i></div><div class="mv-day">15<i class="full"></i></div>
<div class="mv-day">16<i class="partial"></i></div><div class="mv-day">17<i class="full"></i></div><div class="mv-day">18<i class="full"></i></div><div class="mv-day">19<i class="full"></i></div>
<div class="mv-day">20<i class="full"></i></div><div class="mv-day">21<i class="full"></i></div>
<div class="mv-day today">22<i class="partial"></i></div><div class="mv-day future sel">23</div><div class="mv-day future">24</div><div class="mv-day future">25</div>
<div class="mv-day future">26</div><div class="mv-day future">27</div><div class="mv-day future">28</div>
<div class="mv-day future">29</div><div class="mv-day future">30</div><div class="mv-day future">31</div><div class="mv-day out">1</div>
</div>
<div class="mv-rate"><span>本月完成率 <b>82%</b></span><span>明天 · 2026-10-23</span></div>
</div>
<div class="mv-daybar">明天<span class="badge">计划(不可打卡)</span></div>
<div class="mv-groups">
<div class="mv-ghead">每天<span class="cnt">2 项</span></div>
<div class="mv-card">
<div class="mv-ring">
<svg width="42" height="42" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ececf0" stroke-width="5"/>
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#1677ff" stroke-width="5" stroke-linecap="round" stroke-dasharray="116.2" stroke-dashoffset="116.2" transform="rotate(-90 21 21)"/>
</svg><div class="c">0<em>/2</em></div>
</div>
<div class="mv-main"><div class="mv-name">喝水</div><div class="mv-meta">每天 2 次 · 当日 0/2</div></div>
<div class="mv-btn grey">计划</div><div class="mv-more">⋯</div>
</div>
<div class="mv-card">
<div class="mv-ring">
<svg width="42" height="42" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ececf0" stroke-width="5"/>
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#a855f7" stroke-width="5" stroke-linecap="round" stroke-dasharray="116.2" stroke-dashoffset="116.2" transform="rotate(-90 21 21)"/>
</svg><div class="c">0<em>/1</em></div>
</div>
<div class="mv-main"><div class="mv-name">吃药</div><div class="mv-meta">每天 1 次 · 当日 0/1</div></div>
<div class="mv-btn grey">计划</div><div class="mv-more">⋯</div>
</div>
<div class="mv-ghead">每周<span class="cnt">1 项</span></div>
<div class="mv-card">
<div class="mv-ring">
<svg width="42" height="42" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ececf0" stroke-width="5"/>
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ff8f1f" stroke-width="5" stroke-linecap="round" stroke-dasharray="116.2" stroke-dashoffset="77.5" transform="rotate(-90 21 21)"/>
</svg><div class="c">1<em>/3</em></div>
</div>
<div class="mv-main"><div class="mv-name">健身</div><div class="mv-meta">每周 3 次 · 本周 1/3</div></div>
<div class="mv-btn grey">计划</div><div class="mv-more">⋯</div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>②-b 未来日期(计划态)</b>
<p>选中未来日期时,打卡按钮统一变灰显示「计划」,点击不生效;环形仍显示该日所属周期的既有进度(此处举例为周期初)。
补打窗口仍是最近 7 天,超出时同样只读。</p>
</div>
</div>
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px"><div class="on">日历</div><div>清单</div></div>
<div class="mv-cal">
<div class="mv-month"><span class="arrow">‹</span><b>2026年10月</b><span class="arrow">›</span><span class="today">回到今天</span></div>
<div class="mv-grid mv-wd"><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span></div>
<div class="mv-grid">
<div class="mv-day out">29</div><div class="mv-day out">30</div>
<div class="mv-day">1<i class="full"></i></div><div class="mv-day">2<i class="full"></i></div>
<div class="mv-day">3<i class="partial"></i></div><div class="mv-day">4<i class="full"></i></div><div class="mv-day">5</div>
<div class="mv-day">6<i class="full"></i></div><div class="mv-day">7<i class="over"></i></div>
<div class="mv-day">8<i class="full"></i></div><div class="mv-day">9<i class="partial"></i></div>
<div class="mv-day">10<i class="full"></i></div><div class="mv-day">11<i class="full"></i></div><div class="mv-day">12</div>
<div class="mv-day">13</div><div class="mv-day">14<i class="full"></i></div><div class="mv-day">15<i class="full"></i></div>
<div class="mv-day">16<i class="partial"></i></div><div class="mv-day">17<i class="full"></i></div><div class="mv-day">18<i class="full"></i></div><div class="mv-day">19<i class="full"></i></div>
<div class="mv-day">20<i class="full"></i></div><div class="mv-day">21<i class="full"></i></div>
<div class="mv-day today">22<i class="partial"></i></div><div class="mv-day future">23</div><div class="mv-day future">24</div><div class="mv-day future">25</div>
<div class="mv-day future">26</div><div class="mv-day future">27</div><div class="mv-day future">28</div>
<div class="mv-day future">29</div><div class="mv-day future">30</div><div class="mv-day future">31</div><div class="mv-day out">1</div>
</div>
<div class="mv-rate"><span>本月完成率 <b>82%</b></span><span>今天 · 2026-10-22</span></div>
</div>
<div class="mv-daybar">今天<span class="badge">已完成 2 / 4</span><span class="sum">点环即打卡</span></div>
<div class="mv-groups">
<div class="mv-ghead">每天<span class="cnt">2 项</span></div>
<div class="mv-card">
<div class="mv-ring">
<svg width="42" height="42" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ececf0" stroke-width="5"/>
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#1677ff" stroke-width="5" stroke-linecap="round" stroke-dasharray="116.2" stroke-dashoffset="58.1" transform="rotate(-90 21 21)"/>
</svg><div class="c">1<em>/2</em></div>
</div>
<div class="mv-main"><div class="mv-name">喝水</div><div class="mv-meta">每天 2 次 · 今日 1/2</div></div>
<div class="mv-btn">打卡</div><div class="mv-more">⋯</div>
</div>
<div class="mv-card done">
<div class="mv-ring">
<svg width="42" height="42" viewBox="0 0 42 42">
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#ececf0" stroke-width="5"/>
<circle cx="21" cy="21" r="18.5" fill="none" stroke="#a855f7" stroke-width="5" stroke-linecap="round" stroke-dasharray="116.2" stroke-dashoffset="0" transform="rotate(-90 21 21)"/>
</svg>
<div class="c"><svg width="15" height="15" viewBox="0 0 24 24" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#a855f7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
<span class="ov">+1</span>
</div>
<div class="mv-main"><div class="mv-name">吃药</div><div class="mv-meta">每天 1 次 · 今日 2/1</div></div>
<div class="mv-btn grey">再打一次</div><div class="mv-more">⋯</div>
</div>
</div>
</div>
<div class="mask" style="inset:28px 0 0 0"></div>
<div class="sheet mv-sheet">
<div class="sheet-title">吃药 · 每天 1 次</div>
<div class="mv-sheet-row"><span>撤销上次打卡</span><span class="k">今日 2 次</span></div>
<div class="mv-sheet-row"><span>编辑打卡项</span><span class="k">名称 / 周期 / 目标</span></div>
<div class="mv-sheet-row danger"><span>归档</span><span class="k">历史记录保留</span></div>
<div class="btn-ghost" style="margin-top:10px">取消</div>
</div>
<div class="tabbar">
<div class="tab on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>②-c 卡片「⋯」操作面板</b>
<p>紧凑化后卡片不再常显三个文字链接,改由右上角「⋯」唤出底部面板:撤销 / 编辑 / 归档。面板项 44px 高,
「归档」用危险色;撤销仅在当日有记录时可用,否则置灰。</p>
</div>
</div>
</div>
<h3>③ 清单:新增「模板」分类(与进行中 / 已归档并列)<span class="tag">已实现</span></h3>
<p class="desc">清单页分段从 2 个变为 <b>3 个</b>:<b>进行中 / 已归档 / 模板</b>。模板拥有独立的增删改查,<b>按名称字母顺序</b>(中文按拼音、数字按数值)排序;
新建清单时<b>只保留名称</b>,<b>去掉场景</b>,模板改为点击后在<b>右侧滑出</b>的面板里选择。</p>
<div class="stage">
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px"><div>日历</div><div class="on">清单</div></div>
<div class="list-head">
<div class="seg"><div>进行中</div><div>已归档</div><div class="on">模板</div></div>
<div class="mini-btn solid">+ 新建模板</div>
</div>
<div class="tpl-card">
<div class="tpl-row"><span class="tpl-name">出差</span><span class="tpl-badge">预置</span><span class="tpl-num">11 项</span></div>
<div class="tpl-chips">
<span class="tpl-chip">身份证 / 证件</span><span class="tpl-chip">手机充电器 + 充电宝</span>
<span class="tpl-chip">笔记本电脑 + 电源</span><span class="tpl-chip">耳机</span><span class="tpl-chip more">+7</span>
</div>
<div class="tpl-acts"><span class="link">由此新建</span></div>
</div>
<div class="tpl-card">
<div class="tpl-row"><span class="tpl-name">回老家</span><span class="tpl-badge">预置</span><span class="tpl-num">12 项</span></div>
<div class="tpl-chips">
<span class="tpl-chip">身份证 / 证件</span><span class="tpl-chip">钥匙</span>
<span class="tpl-chip">手机充电器 + 充电宝</span><span class="tpl-chip">换洗衣物</span><span class="tpl-chip more">+8</span>
</div>
<div class="tpl-acts"><span class="link">由此新建</span></div>
</div>
<div class="tpl-card">
<div class="tpl-row"><span class="tpl-name">露营装备</span><span class="tpl-num">2 项</span></div>
<div class="tpl-chips"><span class="tpl-chip">帐篷</span><span class="tpl-chip">睡袋</span></div>
<div class="tpl-acts"><span class="link">由此新建</span><span class="link">编辑</span><span class="link danger">删除</span></div>
</div>
</div>
<div class="tabbar">
<div class="tab on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>③-a 模板分类<span class="tag">已实现</span></b>
<p>模板卡片:名称 + 「预置」标记 + 项数;下方最多展示 4 个检查项摘要(超出折叠为 <code>+n</code>);底部操作「由此新建 / 编辑 / 删除」。
<b>系统预置模板不可编辑、不可删除</b>(只保留「由此新建」)。右上角按钮随分段切换:模板页为「+ 新建模板」,其余为「+ 新建」。</p>
</div>
</div>
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px"><div>日历</div><div class="on">清单</div></div>
<div class="mask" style="inset:28px 0 0 0"></div>
<div class="sheet">
<div class="sheet-title">新建检查清单</div>
<div class="field"><label>名称</label><div class="inp">周末露营</div></div>
<div class="field">
<label>模板</label>
<div class="pick-row">露营装备<span class="meta">2 项 <span style="color:#c8c8c8;font-size:15px">›</span></span></div>
</div>
<div class="btn-primary">创建</div>
<div class="btn-ghost">取消</div>
</div>
<div class="side-panel">
<div class="side-head"><b>选择模板</b><span class="x">×</span></div>
<div class="side-tip">按名称字母顺序排列</div>
<div class="side-item"><span class="nm">不使用模板</span><span class="sub">空白清单</span></div>
<div class="side-item"><span class="nm">出差<span class="tpl-badge">预置</span></span><span class="sub">11 项</span></div>
<div class="side-item"><span class="nm">回老家<span class="tpl-badge">预置</span></span><span class="sub">12 项</span></div>
<div class="side-item on"><span class="nm">露营装备</span><span class="sub">2 项</span></div>
</div>
</div>
<div class="tabbar">
<div class="tab on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>③-b 新建清单 + 模板右侧滑出面板<span class="tag">已实现</span></b>
<p>表单只剩两项:<b>名称</b>(≤20 字,必填)与 <b>模板</b>(可空)。<b>场景选择器已移除</b>——场景不再由用户填写,新建时沿用所选模板自带的场景(无模板则为空),历史数据仍正常展示场景标签。<br>
点模板行从<b>右侧滑出 78% 宽面板</b>:首项固定为「不使用模板」,其后按字母顺序列出全部模板(预置带标记、右侧显示项数),选中项主色高亮,点遮罩或 × 关闭。</p>
</div>
</div>
<div class="shot">
<div class="phone">
<div class="navbar-sm">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px"><div>日历</div><div class="on">清单</div></div>
<div class="mask" style="inset:28px 0 0 0"></div>
<div class="sheet">
<div class="sheet-title">编辑模板</div>
<div class="field"><label>模板名称</label><div class="inp">露营装备</div></div>
<div class="field">
<label>检查项(3)</label>
<div style="display:flex;flex-direction:column;gap:6px">
<div style="display:flex;align-items:center;gap:8px"><span style="width:16px;text-align:right;font-size:11px;color:#999">1</span><div class="inp" style="flex:1">帐篷</div><span style="color:#ccc;font-size:17px">×</span></div>
<div style="display:flex;align-items:center;gap:8px"><span style="width:16px;text-align:right;font-size:11px;color:#999">2</span><div class="inp" style="flex:1">睡袋</div><span style="color:#ccc;font-size:17px">×</span></div>
<div style="display:flex;align-items:center;gap:8px"><span style="width:16px;text-align:right;font-size:11px;color:#999">3</span><div class="inp ph" style="flex:1">检查项内容</div><span style="color:#ccc;font-size:17px">×</span></div>
</div>
<div style="margin-top:8px;border:1px solid var(--line);border-radius:7px;text-align:center;padding:6px 0;font-size:12px;color:var(--muted)">+ 添加检查项</div>
</div>
<div class="btn-primary">保存</div>
<div class="btn-ghost">取消</div>
</div>
</div>
<div class="tabbar">
<div class="tab on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>③-c 模板编辑(增删改)<span class="tag">已实现</span></b>
<p>新建 / 编辑共用同一弹层:模板名称(≤20 字)+ 检查项列表(逐条 ≤40 字,可删除、可追加)。保存时至少需 1 条有效检查项,名称为空或全空则拦截并 Toast 提示。
删除模板需二次确认,且不影响已用该模板创建过的清单。</p>
</div>
</div>
</div>
<h3>④ 补充:模板接口</h3>
<table>
<tr><th>方法</th><th>路径</th><th>说明</th></tr>
<tr><td>GET</td><td><code>/api/checklists/templates</code></td><td>模板列表(含预置),<b>服务端按名称字母顺序排序</b></td></tr>
<tr><td>GET</td><td><code>/api/checklists/templates/:id</code></td><td>模板详情</td></tr>
<tr><td>POST</td><td><code>/api/checklists/templates</code></td><td>新建模板 <code>{ name, items[] }</code></td></tr>
<tr><td>PUT</td><td><code>/api/checklists/templates/:id</code></td><td>改名称 / 检查项(预置模板拒绝)</td></tr>
<tr><td>DELETE</td><td><code>/api/checklists/templates/:id</code></td><td>删自建模板(预置模板拒绝)</td></tr>
</table>
<p class="note">排序口径:<code>Intl.Collator('zh-Hans-CN', { numeric: true, sensitivity: 'base' })</code>,中文按拼音、数字按数值比较,前后端一致。</p>
<!-- ============ 3 组件状态 ============ -->
<h2>3 组件状态定义<span class="sub">Component States</span></h2>
<h3>3.0 页面头部(PageHeader)</h3>
<table>
<tr><th>项</th><th>取值</th></tr>
<tr><td>高度</td><td>23px(原 NavBar 45px 的一半),<code>flex: 0 0 23px</code> 不参与压缩</td></tr>
<tr><td>标题字号 / 字重</td><td>12px / 600</td></tr>
<tr><td>标题颜色</td><td>主色 <code>#1677ff</code></td></tr>
<tr><td>背景</td><td>页面底色 <code>#f5f5f5</code></td></tr>
<tr><td>适用范围</td><td>打卡 / 宏观 / 监控;<b>「我」页不使用</b></td></tr>
</table>
<p class="note">实现为独立组件 <code>src/components/PageHeader.jsx</code>,三页各自引用、互不影响;「我」页移除 NavBar 后头像区直接贴顶。</p>
<h3>3.1 环形进度(尺寸 58,环宽 6,r=26,周长 163.4)</h3>
<table>
<tr><th>状态</th><th>条件</th><th>环形</th><th>中心</th><th>卡片底</th><th>按钮</th></tr>
<tr><td>未开始</td><td><code>done = 0</code></td><td>仅灰底环</td><td><code>0/2</code></td><td>白</td><td>打卡(主色)</td></tr>
<tr><td>进行中</td><td><code>0 &lt; done &lt; target</code></td><td>按 <code>done/target</code> 填充(45°…330°)</td><td><code>1/3</code></td><td>白</td><td>打卡(主色)</td></tr>
<tr><td>已达标</td><td><code>done = target</code></td><td>闭合整圈</td><td>对勾(主色)</td><td><code>#f7fbf8</code></td><td>再打一次(灰)</td></tr>
<tr><td>超额</td><td><code>done &gt; target</code></td><td>闭合 + 右上角 <code>+n</code> 橙标</td><td>对勾</td><td><code>#f7fbf8</code></td><td>再打一次(灰)</td></tr>
<tr><td>每日 1 次达标</td><td><code>target = 1</code> 且已打卡</td><td><b>直接整圈闭合</b>,不做渐增生长期</td><td>对勾</td><td><code>#f7fbf8</code></td><td>再打一次</td></tr>
</table>
<p class="note">环形进度 = 周期完成度(<code>done / n</code>);日历日标记 = 当日份额(<code>当日次数 / 日均期望</code>),两者口径不同,互不影响。</p>
<h3>3.2 日历日期</h3>
<table>
<tr><th>状态</th><th>判定</th><th>视觉</th></tr>
<tr><td>全部完成 <code>full</code></td><td>所有打卡项当日次数 ≥ 日均期望,且无超额</td><td>绿色圆点</td></tr>
<tr><td>超额 <code>over</code></td><td>全部达标且存在任一打卡项当日次数 &gt; 期望</td><td>紫色圆点</td></tr>
<tr><td>部分完成 <code>partial</code></td><td>有完成但未全部完成</td><td>黄色圆点</td></tr>
<tr><td>无 <code>empty</code></td><td>无记录 / 当天无打卡项 / 未来日期</td><td>不显示圆点</td></tr>
<tr><td>今天</td><td><code>date = today</code></td><td>数字主色加粗</td></tr>
<tr><td>选中</td><td>用户点击</td><td>主色实心底,数字与圆点转白</td></tr>
<tr><td>非本月 / 未来</td><td>月份不符 / <code>date &gt; today</code></td><td>数字弱化(#c8c8c8 / #b0b0b0)</td></tr>
</table>
<h3>3.3 清单检查项</h3>
<table>
<tr><th>状态</th><th>勾选框</th><th>文字</th><th>触发</th></tr>
<tr><td>未完成</td><td>1.5px 灰边圆圈,对勾透明</td><td>#333</td><td>点击圆圈或文字切换</td></tr>
<tr><td>已完成</td><td>绿底绿边,白色对勾显现(0.2s)</td><td>#a0a0a0 + 删除线</td><td>同上,写 <code>doneAt</code></td></tr>
<tr><td>整单完成</td><td>—</td><td>进度条 100%、卡片转绿</td><td>最后一项勾选时播放特效</td></tr>
</table>
<!-- ============ 4 交互与动效 ============ -->
<h2>4 交互与动效<span class="sub">Motion</span></h2>
<table>
<tr><th>动效</th><th>时长</th><th>曲线 / 实现</th><th>说明</th></tr>
<tr><td>环形增长</td><td>450ms</td><td><code>cubic-bezier(.4,0,.2,1)</code>,<code>stroke-dashoffset</code> 过渡</td><td>12 点方向顺时针增长</td></tr>
<tr><td>对勾入场</td><td>320ms</td><td><code>scale(.4) → 1</code> + 透明度</td><td>达标瞬间出现</td></tr>
<tr><td>完成粒子</td><td>≤900ms</td><td>14 颗粒子按角度 <code>--a</code>、距离 <code>--d</code> 扩散,交错 40ms 延迟</td><td>不阻塞点击</td></tr>
<tr><td>光环脉冲</td><td>700ms</td><td>主色圆环放大淡出</td><td>与粒子同时开始</td></tr>
<tr><td>触感反馈</td><td>18ms</td><td><code>navigator.vibrate(18)</code></td><td>WebView 不支持则静默跳过</td></tr>
<tr><td>进度条填充</td><td>400ms</td><td><code>width</code> 过渡</td><td>清单勾选后更新</td></tr>
<tr><td>视图切换</td><td>—</td><td>SwipeTabs 跟手横滑</td><td>与既有模块一致</td></tr>
</table>
<h3>4.1 打卡时序</h3>
<div class="flow">用户点环 / 点「打卡」
└─ 前端乐观更新:done+1,重算 percent / completed / over,环形立即增长
└─ POST /api/checkin/habits/:id/check
├─ 200 → 用服务端 item 覆盖本地;若 completed → 触发特效 + 震动
├─ 400 → 回滚 + Toast(未来日期 / 超出 7 天补打窗口)
├─ 409 → 视为已打卡,刷新一次(幂等)
└─ 网络失败 → 回滚 + Toast「打卡失败」</div>
<!-- ============ 5 适配与可达性 ============ -->
<h2>5 适配与可达性</h2>
<table>
<tr><th>项</th><th>约定</th></tr>
<tr><td>视口</td><td>设计稿 420×820;375~430px 内元素不换行、不裁切;底部 TabBar 4 项每项 ≥ 44px 可点宽度</td></tr>
<tr><td>安全区</td><td>TabBar 使用 antd-mobile <code>safeArea</code>,页面底部留出 <code>tabbar-h + safe-bottom</code></td></tr>
<tr><td>动效</td><td><b>特效恒开</b>(V0.2 设计变更,无开关):单次 ≤900ms、<code>pointer-events:none</code>,不阻塞连续打卡;不再响应 <code>prefers-reduced-motion</code></td></tr>
<tr><td>大字号</td><td>卡片采用 grid 自适应列宽,文字超长换行不截断;名称限制 20 字、检查项 40 字</td></tr>
<tr><td>深色模式</td><td>V0.2 沿用现有浅色主题,暂不适配</td></tr>
</table>
<p class="note" style="margin-top:26px">本文档为静态设计稿,不含脚本逻辑;实现对应 <code>src/pages/checkin/*.jsx</code>、<code>src/components/RingProgress.jsx</code> 与样式段(<code>src/styles/globals.css</code>)。接口与数据口径见《设计文档.md》与《数据库设计文档.md》。</p>
</div>
</body>
</html>