- 设计文档.md: 架构与分层、目录结构、周期口径与边界规则、前端组件与交互、 后端接口清单、关键流程时序、非功能设计、测试验证、风险与演进 - 数据库设计文档.md: ER、5 张表逐字段、索引与外键级联、关键设计决策 (date 用 VARCHAR(10) 的原因 / 不冗余计数 / seq 幂等 / 归档语义 / 模板 JSON)、 容量估算、典型 SQL、迁移与回滚、V0.3 预留 - 界面设计.html: 420x820 静态高保真原型 8 屏 (打卡/日历/清单/新建弹窗/ 完成特效/明天计划视图/清单完成/空态) + 设计规范、组件状态表、动效与适配约定
890 lines
66 KiB
HTML
890 lines
66 KiB
HTML
<!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 < done < 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 > 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>全部达标且存在任一打卡项当日次数 > 期望</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 > 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>
|