Files
iboard/docs/V0.2/界面设计.html
T
cheney 9b4705870d docs(V0.2): 新增设计文档 / 数据库设计文档 / 界面设计静态原型
- 设计文档.md: 架构与分层、目录结构、周期口径与边界规则、前端组件与交互、
  后端接口清单、关键流程时序、非功能设计、测试验证、风险与演进
- 数据库设计文档.md: ER、5 张表逐字段、索引与外键级联、关键设计决策
  (date 用 VARCHAR(10) 的原因 / 不冗余计数 / seq 幂等 / 归档语义 / 模板 JSON)、
  容量估算、典型 SQL、迁移与回滚、V0.3 预留
- 界面设计.html: 420x820 静态高保真原型 8 屏 (打卡/日历/清单/新建弹窗/
  完成特效/明天计划视图/清单完成/空态) + 设计规范、组件状态表、动效与适配约定
2026-10-22 14:12:24 +08:00

890 lines
66 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}
/* ---------- 表格 ---------- */
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}
</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</span><span>分支 dev-0.2</span><span>antd-mobile 风格</span>
<span>底部 5 Tab(新增「打卡」)</span><span>时区 Asia/Shanghai</span><span>文档日期 2026-10-22</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">三个子视图(SwipeTabs 左右滑动切换)</span></h2>
<div class="stage">
<!-- ---------- 2.1 打卡视图 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</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">特效开</div>
<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"><path d="M4 11l8-6 8 6v8a1 1 0 0 1-1 1h-4v-6H9v6H5a1 1 0 0 1-1-1z"/></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="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"><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">打卡</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"><path d="M4 11l8-6 8 6v8a1 1 0 0 1-1 1h-4v-6H9v6H5a1 1 0 0 1-1-1z"/></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="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"><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">打卡</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 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"><path d="M4 11l8-6 8 6v8a1 1 0 0 1-1 1h-4v-6H9v6H5a1 1 0 0 1-1-1z"/></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="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"><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">打卡</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">特效开</div><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"><path d="M4 11l8-6 8 6v8a1 1 0 0 1-1 1h-4v-6H9v6H5a1 1 0 0 1-1-1z"/></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="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"><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">打卡</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">特效开</div><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"><path d="M4 11l8-6 8 6v8a1 1 0 0 1-1 1h-4v-6H9v6H5a1 1 0 0 1-1-1z"/></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="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"><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 轻震动。可在头部一键关闭(持久化开关),系统「减弱动效」时自动降级。</p>
</div>
</div>
<!-- ---------- 2.6 日历·明天(计划) ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</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"><path d="M4 11l8-6 8 6v8a1 1 0 0 1-1 1h-4v-6H9v6H5a1 1 0 0 1-1-1z"/></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="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"><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">打卡</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 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"><path d="M4 11l8-6 8 6v8a1 1 0 0 1-1 1h-4v-6H9v6H5a1 1 0 0 1-1-1z"/></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="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"><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">打卡</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">特效关</div><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"><path d="M4 11l8-6 8 6v8a1 1 0 0 1-1 1h-4v-6H9v6H5a1 1 0 0 1-1-1z"/></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="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"><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>三个视图均具备加载中 / 空态 / 错误(带重试)三态。空态给出明确引导按钮;头部「特效开 / 特效关」为全局动效开关,关闭后仅保留环形与对勾,不播放粒子与震动。</p>
</div>
</div>
</div>
<!-- ============ 3 组件状态 ============ -->
<h2>3 组件状态定义<span class="sub">Component States</span></h2>
<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 5 项每项 ≥ 40px 可点宽度</td></tr>
<tr><td>安全区</td><td>TabBar 使用 antd-mobile <code>safeArea</code>,页面底部留出 <code>tabbar-h + safe-bottom</code></td></tr>
<tr><td>减弱动效</td><td>系统开启「减弱动效」或用户关闭特效开关 → 不播放粒子 / 光环 / 震动,只保留环形与对勾</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>