Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77e057c580 | ||
|
|
75cabe8339 |
@@ -0,0 +1,627 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>已归档任务管理 · 界面重设计(原型待评审)</title>
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#f0f2f5; --page:#fff; --card:#fff;
|
||||||
|
--text:#333; --muted:#666; --weak:#999; --line:#eee;
|
||||||
|
--primary:#1677ff; --danger:#ff3141; --success:#00b578;
|
||||||
|
--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)}
|
||||||
|
p.desc{color:var(--muted);font-size:13px;margin-bottom:16px}
|
||||||
|
|
||||||
|
/* ---------- 改动要点 ---------- */
|
||||||
|
.changes{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
|
||||||
|
.chg{background:#fff;border:1px solid #e6e8eb;border-radius:12px;padding:14px 16px}
|
||||||
|
.chg .k{font-size:11px;color:var(--primary);font-weight:700;letter-spacing:.6px}
|
||||||
|
.chg h4{font-size:14px;font-weight:700;margin:5px 0 6px}
|
||||||
|
.chg p{font-size:12.5px;color:var(--muted);line-height:1.65}
|
||||||
|
.chg .old{color:var(--danger);font-size:12px;text-decoration:line-through}
|
||||||
|
|
||||||
|
/* ---------- 手机框 ---------- */
|
||||||
|
.stage{display:flex;gap:34px;flex-wrap:wrap;align-items:flex-start;margin-top:18px}
|
||||||
|
.phone{
|
||||||
|
width:400px;height:812px;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:112px;height:19px;background:#111827;border-radius:12px;opacity:.9;z-index:5}
|
||||||
|
.phone.sm{width:340px;height:700px}
|
||||||
|
.phone .nav{
|
||||||
|
height:var(--nav-h);margin-top:26px;flex-shrink:0;display:flex;align-items:center;gap:10px;
|
||||||
|
padding:0 14px;background:#fff;border-bottom:1px solid var(--line);
|
||||||
|
}
|
||||||
|
.phone .nav .back{font-size:20px;color:#666;line-height:1;cursor:pointer}
|
||||||
|
.phone .nav .title{font-size:16px;font-weight:700}
|
||||||
|
.phone .nav .proj{margin-left:auto;font-size:11px;color:var(--weak);background:#f5f6f8;border-radius:999px;padding:2px 9px;max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||||
|
.phone .body{flex:1;min-height:0;overflow-y:auto;background:#f5f5f5;padding-bottom:18px}
|
||||||
|
|
||||||
|
/* ---------- 筛选卡 ---------- */
|
||||||
|
.fcard{background:#fff;margin:10px 12px 0;border-radius:12px;padding:4px 14px 12px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
|
||||||
|
.frow{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid #f4f5f7}
|
||||||
|
.frow:last-of-type{border-bottom:0}
|
||||||
|
.frow > .lbl{width:56px;flex-shrink:0;font-size:12.5px;font-weight:600;color:var(--text)}
|
||||||
|
.frow > .ctl{flex:1;min-width:0}
|
||||||
|
/* 标题输入框前的提示 */
|
||||||
|
.kwbox{display:flex;align-items:center;gap:7px;background:#f7f8fa;border:1px solid #eceef1;border-radius:8px;padding:6px 9px}
|
||||||
|
.kwbox:focus-within{border-color:var(--primary);background:#fff}
|
||||||
|
.kwbox .ph{font-size:11.5px;color:var(--weak);flex-shrink:0}
|
||||||
|
.kwbox input{flex:1;min-width:0;border:0;background:transparent;font-size:13px;outline:none;color:var(--text)}
|
||||||
|
.kwbox input::placeholder{color:#c2c6cc}
|
||||||
|
.kwbox .clr{font-size:12px;color:#c2c6cc;cursor:pointer;flex-shrink:0}
|
||||||
|
|
||||||
|
.chips{display:flex;flex-wrap:wrap;gap:6px}
|
||||||
|
.chip{
|
||||||
|
font-size:12px;padding:4px 11px;border-radius:999px;border:1px solid #e6e8eb;
|
||||||
|
background:#fafbfc;color:var(--muted);cursor:pointer;white-space:nowrap;transition:.15s;
|
||||||
|
}
|
||||||
|
.chip:hover{border-color:#c9d3e0}
|
||||||
|
.chip.on{background:#eaf3ff;border-color:var(--primary);color:var(--primary);font-weight:600}
|
||||||
|
.chip .x{margin-left:5px;color:#9aa4b2}
|
||||||
|
|
||||||
|
.range{display:flex;align-items:center;gap:7px;margin-top:8px}
|
||||||
|
.range .dp{font-size:12px;padding:4px 10px;border:1px dashed #d9dee5;border-radius:7px;color:var(--weak);cursor:pointer;background:#fff}
|
||||||
|
.range .dp.on{color:var(--text);border-style:solid;border-color:#d9dee5}
|
||||||
|
.range .sep{color:#c2c6cc;font-size:12px}
|
||||||
|
|
||||||
|
.factions{display:flex;gap:9px;padding-top:12px}
|
||||||
|
.btn{flex:1;height:36px;border-radius:9px;border:0;font-size:13.5px;font-weight:600;cursor:pointer;transition:.15s}
|
||||||
|
.btn.ghost{background:#f4f5f7;color:var(--muted);flex:0 0 84px}
|
||||||
|
.btn.primary{background:var(--primary);color:#fff;box-shadow:0 2px 8px rgba(22,119,255,.26)}
|
||||||
|
.btn.primary:active{transform:translateY(1px)}
|
||||||
|
.btn .badge{display:inline-block;min-width:16px;height:16px;line-height:16px;border-radius:8px;background:rgba(255,255,255,.28);font-size:10.5px;margin-left:5px;padding:0 4px}
|
||||||
|
|
||||||
|
/* ---------- 已应用条件 ---------- */
|
||||||
|
.applied{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:11px 14px 3px}
|
||||||
|
.applied .tag{font-size:11.5px;background:#eaf3ff;color:var(--primary);border-radius:6px;padding:2px 7px;display:flex;align-items:center;gap:5px}
|
||||||
|
.applied .tag b{font-weight:600}
|
||||||
|
.applied .tag .x{cursor:pointer;opacity:.6}
|
||||||
|
.applied .cnt{margin-left:auto;font-size:11.5px;color:var(--weak)}
|
||||||
|
|
||||||
|
/* ---------- 列表 ---------- */
|
||||||
|
.list{padding:8px 12px 0;display:flex;flex-direction:column;gap:8px}
|
||||||
|
.item{background:#fff;border-radius:10px;padding:11px 12px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
|
||||||
|
.item .top{display:flex;gap:8px;align-items:flex-start}
|
||||||
|
.item .ttl{flex:1;min-width:0;font-size:13.5px;font-weight:600;line-height:1.42;word-break:break-word}
|
||||||
|
.item .num{font-size:10px;color:#9aa4b2;background:#f5f6f8;border-radius:4px;padding:1px 5px;flex-shrink:0;margin-top:1px}
|
||||||
|
.item .meta{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-top:7px;font-size:10.5px;color:var(--weak)}
|
||||||
|
.item .meta .col{color:var(--muted)}
|
||||||
|
.item .meta .dot{width:3px;height:3px;border-radius:50%;background:#d5d9e0}
|
||||||
|
.item .acts{display:flex;gap:7px;justify-content:flex-end;margin-top:9px;padding-top:9px;border-top:1px dashed #f0f1f3}
|
||||||
|
.mini{font-size:11.5px;padding:3px 12px;border-radius:999px;border:1px solid var(--primary);color:var(--primary);background:#fff;cursor:pointer;white-space:nowrap}
|
||||||
|
.mini.danger{border-color:#ffd4d7;color:var(--danger)}
|
||||||
|
.kind{font-size:10px;line-height:1;font-weight:600;color:#fff;border-radius:4px;padding:3px 5px}
|
||||||
|
|
||||||
|
.empty{text-align:center;padding:56px 20px;color:#b8bcc4;font-size:13px}
|
||||||
|
.empty .ico{font-size:34px;opacity:.35;margin-bottom:10px}
|
||||||
|
.more{display:block;width:calc(100% - 24px);margin:12px auto 0;height:34px;border-radius:9px;border:1px solid #e6e8eb;background:#fff;color:var(--muted);font-size:12.5px;cursor:pointer}
|
||||||
|
|
||||||
|
.toast{
|
||||||
|
position:absolute;left:50%;bottom:56px;transform:translateX(-50%) translateY(8px);
|
||||||
|
background:rgba(0,0,0,.78);color:#fff;font-size:12.5px;padding:8px 16px;border-radius:8px;
|
||||||
|
opacity:0;transition:.22s;z-index:9;pointer-events:none;white-space:nowrap;
|
||||||
|
}
|
||||||
|
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
|
||||||
|
|
||||||
|
.caption{margin-top:12px;background:#fff;border:1px solid #e6e8eb;border-radius:10px;padding:11px 14px;width:400px}
|
||||||
|
.caption b{font-size:13px;display:block;margin-bottom:4px}
|
||||||
|
.caption span{font-size:12px;color:var(--muted);line-height:1.65}
|
||||||
|
|
||||||
|
.notes{flex:1;min-width:300px;display:flex;flex-direction:column;gap:12px}
|
||||||
|
.note{background:#fff;border:1px solid #e6e8eb;border-radius:12px;padding:14px 16px}
|
||||||
|
.note h4{font-size:13px;font-weight:700;margin-bottom:7px;display:flex;align-items:center;gap:7px}
|
||||||
|
.note h4 i{width:18px;height:18px;border-radius:50%;background:#eaf3ff;color:var(--primary);font-size:11px;font-style:normal;display:flex;align-items:center;justify-content:center;font-weight:700}
|
||||||
|
.note ul{list-style:none;font-size:12.5px;color:var(--muted);line-height:1.85}
|
||||||
|
.note ul li::before{content:"·";color:#c2c6cc;margin-right:6px}
|
||||||
|
.note code{background:#f5f6f8;border-radius:4px;padding:1px 5px;font-size:11.5px;color:#c7254e}
|
||||||
|
|
||||||
|
table{width:100%;border-collapse:collapse;background:#fff;border-radius:12px;overflow:hidden;border:1px solid #e6e8eb;font-size:12.5px}
|
||||||
|
th,td{padding:10px 14px;text-align:left;border-bottom:1px solid #f0f1f3;vertical-align:top}
|
||||||
|
th{background:#fafbfc;font-size:12px;color:var(--muted);font-weight:600}
|
||||||
|
tr:last-child td{border-bottom:0}
|
||||||
|
td .no{color:var(--danger)}
|
||||||
|
td .yes{color:var(--success);font-weight:600}
|
||||||
|
.variants{display:flex;gap:26px;flex-wrap:wrap;margin-top:16px}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrap">
|
||||||
|
|
||||||
|
<div class="doc-head">
|
||||||
|
<h1>已归档任务管理 · 界面重设计</h1>
|
||||||
|
<p>对应需求 T-ARC-04/05/06。本文件是<strong>评审用原型</strong>,不含业务代码改动;确认后再落地。</p>
|
||||||
|
<div class="meta">
|
||||||
|
<span>原型 · 待评审</span>
|
||||||
|
<span>2026-10-28</span>
|
||||||
|
<span>可交互:点预设 / 筛选 / 恢复</span>
|
||||||
|
<span>范围:任务模块 › 项目 › 已归档</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>改动要点 <span class="sub">对照你提出的 4 点</span></h2>
|
||||||
|
<div class="changes">
|
||||||
|
<div class="chg">
|
||||||
|
<div class="k">01</div>
|
||||||
|
<h4>去掉「类型」过滤</h4>
|
||||||
|
<p>现版本的「全部类型」下拉整体移除。任务类型是弱维度,归档场景几乎不按类型找东西,反而占掉一横行高。</p>
|
||||||
|
<p class="old">全部类型 ▾</p>
|
||||||
|
</div>
|
||||||
|
<div class="chg">
|
||||||
|
<div class="k">02</div>
|
||||||
|
<h4>输入框前加提示</h4>
|
||||||
|
<p>关键词输入框前加固定标签「标题」,输入框内用灰字占位「输入关键词」——标签常驻,占位随输入消失,不会出现"不知道搜什么"。</p>
|
||||||
|
<p class="old">搜索任务标题(无标签)</p>
|
||||||
|
</div>
|
||||||
|
<div class="chg">
|
||||||
|
<div class="k">03</div>
|
||||||
|
<h4>归档时间快捷选择</h4>
|
||||||
|
<p>新增 <b>全部 / 本周 / 本月 / 本季度</b> 四个一键预设,选完自动算出起止日期并显示;需要精确区间时再选「自定义」。</p>
|
||||||
|
<p class="old">开始日期 ~ 结束日期(纯手点)</p>
|
||||||
|
</div>
|
||||||
|
<div class="chg">
|
||||||
|
<div class="k">04</div>
|
||||||
|
<h4>补上「筛选」按钮</h4>
|
||||||
|
<p>现版本改任何条件都立即查询、没有确认动作。改为:改条件 → 点<b>筛选</b>才生效,按钮上显示生效条件数;另给「重置」。</p>
|
||||||
|
<p class="old">无按钮(自动即时查询)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>主界面 <span class="sub">可交互 · 点一下试试</span></h2>
|
||||||
|
<div class="stage">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="phone" id="phone">
|
||||||
|
<div class="nav">
|
||||||
|
<span class="back">‹</span>
|
||||||
|
<span class="title">已归档任务</span>
|
||||||
|
<span class="proj">Admin的任务</span>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<!-- 筛选卡 -->
|
||||||
|
<div class="fcard">
|
||||||
|
<div class="frow">
|
||||||
|
<span class="lbl">标题</span>
|
||||||
|
<span class="ctl">
|
||||||
|
<span class="kwbox">
|
||||||
|
<input id="kw" placeholder="输入关键词" />
|
||||||
|
<span class="clr" id="kwClear" style="display:none">✕</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="frow">
|
||||||
|
<span class="lbl">看板列</span>
|
||||||
|
<span class="ctl">
|
||||||
|
<span class="chips" id="colChips">
|
||||||
|
<span class="chip on" data-col="">全部</span>
|
||||||
|
<span class="chip" data-col="Plan">Plan</span>
|
||||||
|
<span class="chip" data-col="Doing">Doing</span>
|
||||||
|
<span class="chip" data-col="Done">Done</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="frow">
|
||||||
|
<span class="lbl">归档时间</span>
|
||||||
|
<span class="ctl">
|
||||||
|
<span class="chips" id="timeChips">
|
||||||
|
<span class="chip on" data-t="all">全部</span>
|
||||||
|
<span class="chip" data-t="week">本周</span>
|
||||||
|
<span class="chip" data-t="month">本月</span>
|
||||||
|
<span class="chip" data-t="quarter">本季度</span>
|
||||||
|
<span class="chip" data-t="custom">自定义</span>
|
||||||
|
</span>
|
||||||
|
<span class="range" id="rangeRow" style="display:none">
|
||||||
|
<span class="dp" id="dpFrom">开始日期</span>
|
||||||
|
<span class="sep">~</span>
|
||||||
|
<span class="dp" id="dpTo">结束日期</span>
|
||||||
|
</span>
|
||||||
|
<div id="rangeHint" style="font-size:11px;color:#9aa4b2;margin-top:6px"></div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="factions">
|
||||||
|
<button class="btn ghost" id="resetBtn">重置</button>
|
||||||
|
<button class="btn primary" id="filterBtn">筛选</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 已应用条件 -->
|
||||||
|
<div class="applied" id="appliedBar" style="display:none"></div>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<div class="list" id="list"></div>
|
||||||
|
<button class="more" id="moreBtn">加载更多</button>
|
||||||
|
</div>
|
||||||
|
<div class="toast" id="toast"></div>
|
||||||
|
</div>
|
||||||
|
<div class="caption">
|
||||||
|
<b>主界面</b>
|
||||||
|
<span>筛选卡固定三行:标题 / 看板列 / 归档时间,外加操作行。改动不再即时生效,点「筛选」统一提交。</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notes">
|
||||||
|
<div class="note">
|
||||||
|
<h4><i>1</i>为什么改成"点按钮才筛选"</h4>
|
||||||
|
<ul>
|
||||||
|
<li>现在每改一个条件就重查一次,连点三个条件就发三次请求</li>
|
||||||
|
<li>输入关键词时每敲一个字都可能触发(失焦也查),列表会闪</li>
|
||||||
|
<li>改成显式提交后,用户能先把条件填完整,再一次性看结果</li>
|
||||||
|
<li>按钮上带数字徽标,一眼知道当前生效了几个条件</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="note">
|
||||||
|
<h4><i>2</i>快捷时间怎么算</h4>
|
||||||
|
<ul>
|
||||||
|
<li><b>本周</b>:周一 00:00 ~ 今天(不跨到未来)</li>
|
||||||
|
<li><b>本月</b>:1 号 00:00 ~ 今天</li>
|
||||||
|
<li><b>本季度</b>:当季首月 1 号 00:00 ~ 今天</li>
|
||||||
|
<li>选完在下方灰字直接显示实际区间,避免"以为选的是整周"</li>
|
||||||
|
<li>「自定义」才展开两个日期选择器,默认收起</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="note">
|
||||||
|
<h4><i>3</i>已应用条件可单独撤销</h4>
|
||||||
|
<ul>
|
||||||
|
<li>筛选后在列表上方显示条件标签,每个带 <code>×</code> 可单独去掉</li>
|
||||||
|
<li>右侧显示命中条数,避免"筛完不知道有没有筛到"</li>
|
||||||
|
<li>无结果时给明确空状态,而不是一片留白</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="note">
|
||||||
|
<h4><i>4</i>列表行重排</h4>
|
||||||
|
<ul>
|
||||||
|
<li>标题独占一行(原来和类型/列/时间挤在下面)</li>
|
||||||
|
<li>元信息收成一行:<code>#编号</code> · 看板列 · 归档于 日期</li>
|
||||||
|
<li>「恢复 / 删除」从右侧竖排改到底部右侧横排,减少每行高度</li>
|
||||||
|
<li>类型标签保留展示(只是不再作为筛选项)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>状态变体</h2>
|
||||||
|
<div class="variants">
|
||||||
|
<div>
|
||||||
|
<div class="phone sm">
|
||||||
|
<div class="nav"><span class="back">‹</span><span class="title">已归档任务</span></div>
|
||||||
|
<div class="body">
|
||||||
|
<div class="fcard">
|
||||||
|
<div class="frow"><span class="lbl">标题</span><span class="ctl"><span class="kwbox"><input placeholder="输入关键词" /></span></span></div>
|
||||||
|
<div class="frow"><span class="lbl">看板列</span><span class="ctl"><span class="chips"><span class="chip on">全部</span><span class="chip">Plan</span><span class="chip">Done</span></span></span></div>
|
||||||
|
<div class="frow"><span class="lbl">归档时间</span><span class="ctl">
|
||||||
|
<span class="chips"><span class="chip">全部</span><span class="chip on">本周</span><span class="chip">本月</span><span class="chip">本季度</span><span class="chip">自定义</span></span>
|
||||||
|
<div style="font-size:11px;color:#9aa4b2;margin-top:6px">2026-10-26 ~ 2026-10-28</div>
|
||||||
|
</span></div>
|
||||||
|
<div class="factions"><button class="btn ghost">重置</button><button class="btn primary">筛选<span class="badge">2</span></button></div>
|
||||||
|
</div>
|
||||||
|
<div class="applied">
|
||||||
|
<span class="tag"><b>本周</b><span class="x">×</span></span>
|
||||||
|
<span class="tag"><b>Done</b><span class="x">×</span></span>
|
||||||
|
<span class="cnt">共 3 条</span>
|
||||||
|
</div>
|
||||||
|
<div class="list">
|
||||||
|
<div class="item">
|
||||||
|
<div class="top"><div class="ttl">任务详情页滚动优化</div><span class="num">#22</span></div>
|
||||||
|
<div class="meta"><span class="kind" style="background:#1677ff">优化</span><span class="col">Done</span><span class="dot"></span><span>归档于 10-27</span></div>
|
||||||
|
<div class="acts"><span class="mini">恢复</span><span class="mini danger">删除</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="top"><div class="ttl">桌面端。</div><span class="num">#5</span></div>
|
||||||
|
<div class="meta"><span class="kind" style="background:#fa8c16">需求</span><span class="col">Done</span><span class="dot"></span><span>归档于 10-26</span></div>
|
||||||
|
<div class="acts"><span class="mini">恢复</span><span class="mini danger">删除</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="caption"><b>已应用条件 + 命中条数</b><span>条件可逐个撤销;时间预设自动显示实际区间。</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="phone sm">
|
||||||
|
<div class="nav"><span class="back">‹</span><span class="title">已归档任务</span></div>
|
||||||
|
<div class="body">
|
||||||
|
<div class="fcard">
|
||||||
|
<div class="frow"><span class="lbl">标题</span><span class="ctl"><span class="kwbox"><input value="日报" /><span class="clr">✕</span></span></span></div>
|
||||||
|
<div class="frow"><span class="lbl">看板列</span><span class="ctl"><span class="chips"><span class="chip on">全部</span><span class="chip">Done</span></span></span></div>
|
||||||
|
<div class="frow"><span class="lbl">归档时间</span><span class="ctl">
|
||||||
|
<span class="chips"><span class="chip">全部</span><span class="chip">本周</span><span class="chip">本月</span><span class="chip">本季度</span><span class="chip on">自定义</span></span>
|
||||||
|
<span class="range"><span class="dp on">2026-09-01</span><span class="sep">~</span><span class="dp on">2026-09-30</span></span>
|
||||||
|
</span></div>
|
||||||
|
<div class="factions"><button class="btn ghost">重置</button><button class="btn primary">筛选<span class="badge">3</span></button></div>
|
||||||
|
</div>
|
||||||
|
<div class="applied">
|
||||||
|
<span class="tag"><b>标题: 日报</b><span class="x">×</span></span>
|
||||||
|
<span class="tag"><b>09-01~09-30</b><span class="x">×</span></span>
|
||||||
|
<span class="cnt">共 0 条</span>
|
||||||
|
</div>
|
||||||
|
<div class="empty"><div class="ico">🗂</div>没有符合条件的归档任务<br /><span style="font-size:11.5px;color:#c2c6cc">试试放宽时间范围或清空关键词</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="caption"><b>自定义区间 + 空结果</b><span>选「自定义」才展开日期;空结果给可操作的建议文案。</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="phone sm">
|
||||||
|
<div class="nav"><span class="back">‹</span><span class="title">已归档任务</span></div>
|
||||||
|
<div class="body">
|
||||||
|
<div class="fcard">
|
||||||
|
<div class="frow"><span class="lbl">标题</span><span class="ctl"><span class="kwbox"><input placeholder="输入关键词" /></span></span></div>
|
||||||
|
<div class="frow"><span class="lbl">看板列</span><span class="ctl"><span class="chips"><span class="chip on">全部</span><span class="chip">Plan</span><span class="chip">Doing</span><span class="chip">Done</span></span></span></div>
|
||||||
|
<div class="frow"><span class="lbl">归档时间</span><span class="ctl"><span class="chips"><span class="chip on">全部</span><span class="chip">本周</span><span class="chip">本月</span><span class="chip">本季度</span><span class="chip">自定义</span></span></span></div>
|
||||||
|
<div class="factions"><button class="btn ghost">重置</button><button class="btn primary">筛选</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="empty"><div class="ico">📭</div>这个项目还没有归档任务<br /><span style="font-size:11.5px;color:#c2c6cc">任务完成后可在 ⋯ 菜单里归档</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="caption"><b>初始空状态</b><span>一个都没归档时,告诉用户归档入口在哪。</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>与现版本对照</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><th style="width:130px">项</th><th style="width:38%">现在</th><th>建议</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>类型过滤</td><td class="no">「全部类型」下拉 + 独占一行</td><td class="yes">移除</td></tr>
|
||||||
|
<tr><td>关键词</td><td class="no">无标签,占位「搜索任务标题」</td><td class="yes">左侧常驻标签「标题」+ 占位「输入关键词」</td></tr>
|
||||||
|
<tr><td>归档时间</td><td class="no">两个日期按钮,无快捷项</td><td class="yes">全部 / 本周 / 本月 / 本季度 / 自定义</td></tr>
|
||||||
|
<tr><td>查询触发</td><td class="no">改条件即时查询,无按钮</td><td class="yes">「重置 / 筛选」,按钮带生效条件数</td></tr>
|
||||||
|
<tr><td>条件可见性</td><td class="no">条件散在控件里,看不出生效了什么</td><td class="yes">列表上方条件标签 + 命中条数,可单独撤销</td></tr>
|
||||||
|
<tr><td>列表行</td><td class="no">标题与元信息挤在一起,右侧竖排两按钮</td><td class="yes">标题独占行;元信息一行;按钮移到底部右侧</td></tr>
|
||||||
|
<tr><td>空状态</td><td class="no">一行灰字「没有符合条件的归档任务」</td><td class="yes">区分「还没归档过」与「筛选无结果」,给出下一步建议</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>待你确认 <span class="sub">确认后我再改代码</span></h2>
|
||||||
|
<div class="notes" style="max-width:820px">
|
||||||
|
<div class="note">
|
||||||
|
<ul>
|
||||||
|
<li>「筛选」改为显式提交,是否符合你的使用习惯?(也可以保留"改预设即时生效、只有关键词需要点按钮")</li>
|
||||||
|
<li>看板列是否也保留?还是只留 标题 + 归档时间 两个条件更清爽?</li>
|
||||||
|
<li>列表行的「恢复 / 删除」放底部右侧可以吗?还是希望点行进入详情/预览?</li>
|
||||||
|
<li>快捷时间是否需要再加「近 7 天 / 近 30 天」?</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
// ---- 原型状态 ----
|
||||||
|
const state = { kw:'', col:'', time:'all', from:'', to:'', applied:{ kw:'', col:'', time:'all', from:'', to:'' } }
|
||||||
|
|
||||||
|
const MOCK = [
|
||||||
|
{ id:1, title:'任务详情页滚动优化', num:'#22', kind:'优化', kindColor:'#1677ff', col:'Done', at:'2026-10-27' },
|
||||||
|
{ id:2, title:'桌面端。', num:'#5', kind:'需求', kindColor:'#fa8c16', col:'Done', at:'2026-10-26' },
|
||||||
|
{ id:3, title:'任务详情中切换看板列是提示不存在。', num:'#23', kind:'Bug', kindColor:'#ff3141', col:'Done', at:'2026-10-25' },
|
||||||
|
{ id:4, title:'windows 电脑加入监控', num:'#24', kind:'需求', kindColor:'#fa8c16', col:'Plan', at:'2026-10-21' },
|
||||||
|
{ id:5, title:'补日报', num:'#28', kind:'需求', kindColor:'#fa8c16', col:'Doing', at:'2026-10-12' },
|
||||||
|
]
|
||||||
|
let rows = MOCK.slice()
|
||||||
|
|
||||||
|
const $ = (id) => document.getElementById(id)
|
||||||
|
const toastEl = $('toast')
|
||||||
|
let toastTimer = null
|
||||||
|
function toast(msg){
|
||||||
|
toastEl.textContent = msg
|
||||||
|
toastEl.classList.add('show')
|
||||||
|
clearTimeout(toastTimer)
|
||||||
|
toastTimer = setTimeout(() => toastEl.classList.remove('show'), 1600)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 快捷时间区间 ----
|
||||||
|
function pad(n){ return String(n).padStart(2,'0') }
|
||||||
|
function fmt(d){ return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}` }
|
||||||
|
function presetRange(t){
|
||||||
|
const now = new Date()
|
||||||
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
|
if (t === 'week') {
|
||||||
|
const dow = (today.getDay() + 6) % 7 // 周一=0
|
||||||
|
const mon = new Date(today); mon.setDate(today.getDate() - dow)
|
||||||
|
return { from: fmt(mon), to: fmt(today) }
|
||||||
|
}
|
||||||
|
if (t === 'month') return { from: fmt(new Date(today.getFullYear(), today.getMonth(), 1)), to: fmt(today) }
|
||||||
|
if (t === 'quarter') {
|
||||||
|
const qm = Math.floor(today.getMonth() / 3) * 3
|
||||||
|
return { from: fmt(new Date(today.getFullYear(), qm, 1)), to: fmt(today) }
|
||||||
|
}
|
||||||
|
return { from:'', to:'' }
|
||||||
|
}
|
||||||
|
function rangeLabel(t){
|
||||||
|
if (t === 'custom') return ''
|
||||||
|
const r = presetRange(t)
|
||||||
|
return r.from ? `${r.from} ~ ${r.to}` : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 渲染 ----
|
||||||
|
function render(){
|
||||||
|
// 时间行
|
||||||
|
const custom = state.time === 'custom'
|
||||||
|
$('rangeRow').style.display = custom ? 'flex' : 'none'
|
||||||
|
const hint = rangeLabel(state.time)
|
||||||
|
$('rangeHint').textContent = hint
|
||||||
|
$('dpFrom').textContent = state.from || '开始日期'
|
||||||
|
$('dpTo').textContent = state.to || '结束日期'
|
||||||
|
$('dpFrom').classList.toggle('on', !!state.from)
|
||||||
|
$('dpTo').classList.toggle('on', !!state.to)
|
||||||
|
$('kwClear').style.display = state.kw ? 'block' : 'none'
|
||||||
|
|
||||||
|
// 生效条件数
|
||||||
|
const n = countApplied()
|
||||||
|
const btn = $('filterBtn')
|
||||||
|
btn.innerHTML = n ? `筛选<span class="badge">${n}</span>` : '筛选'
|
||||||
|
|
||||||
|
// 已应用条
|
||||||
|
const bar = $('appliedBar')
|
||||||
|
const tags = []
|
||||||
|
const a = state.applied
|
||||||
|
if (a.kw) tags.push(`<span class="tag"><b>标题: ${a.kw}</b><span class="x" data-clear="kw">×</span></span>`)
|
||||||
|
if (a.col) tags.push(`<span class="tag"><b>看板列: ${a.col}</b><span class="x" data-clear="col">×</span></span>`)
|
||||||
|
if (a.time === 'custom') {
|
||||||
|
if (a.from || a.to) tags.push(`<span class="tag"><b>${a.from || '…'} ~ ${a.to || '…'}</b><span class="x" data-clear="time">×</span></span>`)
|
||||||
|
} else if (a.time !== 'all') {
|
||||||
|
const name = { week:'本周', month:'本月', quarter:'本季度' }[a.time]
|
||||||
|
tags.push(`<span class="tag"><b>${name}</b><span class="x" data-clear="time">×</span></span>`)
|
||||||
|
}
|
||||||
|
if (tags.length) {
|
||||||
|
bar.style.display = 'flex'
|
||||||
|
bar.innerHTML = tags.join('') + `<span class="cnt">共 ${rows.length} 条</span>`
|
||||||
|
} else {
|
||||||
|
bar.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 列表
|
||||||
|
const list = $('list')
|
||||||
|
if (!rows.length) {
|
||||||
|
const never = !tags.length
|
||||||
|
list.innerHTML = never
|
||||||
|
? `<div class="empty"><div class="ico">📭</div>这个项目还没有归档任务<br /><span style="font-size:11.5px;color:#c2c6cc">任务完成后可在 ⋯ 菜单里归档</span></div>`
|
||||||
|
: `<div class="empty"><div class="ico">🗂</div>没有符合条件的归档任务<br /><span style="font-size:11.5px;color:#c2c6cc">试试放宽时间范围或清空关键词</span></div>`
|
||||||
|
$('moreBtn').style.display = 'none'
|
||||||
|
} else {
|
||||||
|
$('moreBtn').style.display = 'block'
|
||||||
|
list.innerHTML = rows.map(r => `
|
||||||
|
<div class="item" data-id="${r.id}">
|
||||||
|
<div class="top"><div class="ttl">${r.title}</div><span class="num">${r.num}</span></div>
|
||||||
|
<div class="meta">
|
||||||
|
<span class="kind" style="background:${r.kindColor}">${r.kind}</span>
|
||||||
|
<span class="col">${r.col}</span><span class="dot"></span><span>归档于 ${r.at}</span>
|
||||||
|
</div>
|
||||||
|
<div class="acts"><span class="mini" data-act="restore">恢复</span><span class="mini danger" data-act="remove">删除</span></div>
|
||||||
|
</div>`).join('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function countApplied(){
|
||||||
|
const a = state.applied
|
||||||
|
let n = 0
|
||||||
|
if (a.kw) n++
|
||||||
|
if (a.col) n++
|
||||||
|
if (a.time === 'custom') { if (a.from || a.to) n++ } else if (a.time !== 'all') n++
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilter(){
|
||||||
|
state.applied = { kw: state.kw, col: state.col, time: state.time, from: state.from, to: state.to }
|
||||||
|
const a = state.applied
|
||||||
|
rows = MOCK.filter(r => {
|
||||||
|
if (a.kw && !r.title.includes(a.kw)) return false
|
||||||
|
if (a.col && r.col !== a.col) return false
|
||||||
|
if (a.time !== 'custom' && a.time !== 'all') {
|
||||||
|
const rg = presetRange(a.time)
|
||||||
|
if (rg.from && (r.at < rg.from || r.at > rg.to)) return false
|
||||||
|
}
|
||||||
|
if (a.time === 'custom') {
|
||||||
|
if (a.from && r.at < a.from) return false
|
||||||
|
if (a.to && r.at > a.to) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
render()
|
||||||
|
toast(countApplied() ? `已应用 ${countApplied()} 个条件 · ${rows.length} 条` : `显示全部 ${rows.length} 条`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 事件 ----
|
||||||
|
$('kw').addEventListener('input', (e) => { state.kw = e.target.value.trim(); $('kwClear').style.display = state.kw ? 'block' : 'none' })
|
||||||
|
$('kw').addEventListener('keydown', (e) => { if (e.key === 'Enter') applyFilter() })
|
||||||
|
$('kwClear').addEventListener('click', () => { state.kw = ''; $('kw').value = ''; render() })
|
||||||
|
|
||||||
|
document.querySelectorAll('#colChips .chip').forEach(el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
document.querySelectorAll('#colChips .chip').forEach(x => x.classList.remove('on'))
|
||||||
|
el.classList.add('on')
|
||||||
|
state.col = el.dataset.col || ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
document.querySelectorAll('#timeChips .chip').forEach(el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
document.querySelectorAll('#timeChips .chip').forEach(x => x.classList.remove('on'))
|
||||||
|
el.classList.add('on')
|
||||||
|
state.time = el.dataset.t
|
||||||
|
if (state.time !== 'custom') { state.from = ''; state.to = '' }
|
||||||
|
render()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
$('dpFrom').addEventListener('click', () => {
|
||||||
|
const v = prompt('开始日期 (YYYY-MM-DD)', state.from || '2026-10-01')
|
||||||
|
if (v !== null) { state.from = v.trim(); render() }
|
||||||
|
})
|
||||||
|
$('dpTo').addEventListener('click', () => {
|
||||||
|
const v = prompt('结束日期 (YYYY-MM-DD)', state.to || '2026-10-28')
|
||||||
|
if (v !== null) { state.to = v.trim(); render() }
|
||||||
|
})
|
||||||
|
|
||||||
|
$('filterBtn').addEventListener('click', applyFilter)
|
||||||
|
|
||||||
|
$('resetBtn').addEventListener('click', () => {
|
||||||
|
state.kw = ''; state.col = ''; state.time = 'all'; state.from = ''; state.to = ''
|
||||||
|
state.applied = { kw:'', col:'', time:'all', from:'', to:'' }
|
||||||
|
$('kw').value = ''
|
||||||
|
document.querySelectorAll('#colChips .chip').forEach((x, i) => x.classList.toggle('on', i === 0))
|
||||||
|
document.querySelectorAll('#timeChips .chip').forEach((x, i) => x.classList.toggle('on', i === 0))
|
||||||
|
rows = MOCK.slice()
|
||||||
|
render()
|
||||||
|
toast('已重置')
|
||||||
|
})
|
||||||
|
|
||||||
|
$('appliedBar').addEventListener('click', (e) => {
|
||||||
|
const x = e.target.closest('.x'); if (!x) return
|
||||||
|
const k = x.dataset.clear
|
||||||
|
if (k === 'kw') state.applied.kw = ''
|
||||||
|
if (k === 'col') state.applied.col = ''
|
||||||
|
if (k === 'time') { state.applied.time = 'all'; state.applied.from = ''; state.applied.to = '' }
|
||||||
|
const a = state.applied
|
||||||
|
rows = MOCK.filter(r => {
|
||||||
|
if (a.kw && !r.title.includes(a.kw)) return false
|
||||||
|
if (a.col && r.col !== a.col) return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
render()
|
||||||
|
toast('已移除该条件')
|
||||||
|
})
|
||||||
|
|
||||||
|
$('list').addEventListener('click', (e) => {
|
||||||
|
const btn = e.target.closest('[data-act]'); if (!btn) return
|
||||||
|
const item = btn.closest('.item')
|
||||||
|
const id = Number(item.dataset.id)
|
||||||
|
const row = rows.find(r => r.id === id)
|
||||||
|
if (btn.dataset.act === 'restore') {
|
||||||
|
toast(`已恢复「${row.title}」`)
|
||||||
|
} else {
|
||||||
|
toast(`已删除「${row.title}」(原型演示,不实际删除)`)
|
||||||
|
}
|
||||||
|
item.style.opacity = '.35'
|
||||||
|
item.style.pointerEvents = 'none'
|
||||||
|
})
|
||||||
|
|
||||||
|
$('moreBtn').addEventListener('click', () => toast('加载更多(原型演示)'))
|
||||||
|
|
||||||
|
render()
|
||||||
|
})()
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+6
-4
@@ -708,14 +708,16 @@ export const taskApi = {
|
|||||||
archive: async (id) => {
|
archive: async (id) => {
|
||||||
const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)])
|
const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)])
|
||||||
if (!task) throw new Error('任务不存在或无权限')
|
if (!task) throw new Error('任务不存在或无权限')
|
||||||
const now = now()
|
// 不能写 const now = now(): 同名 const 会遮蔽模块级 now, 右侧求值落入 TDZ,
|
||||||
|
// 压缩后报 "Cannot access 'a' before initialization"
|
||||||
|
const ts = now()
|
||||||
await db().writeTransaction(async (tx) => {
|
await db().writeTransaction(async (tx) => {
|
||||||
const kids = await tx.getAll('SELECT id FROM Task WHERE parentId = ?', [String(task.id)])
|
const kids = await tx.getAll('SELECT id FROM Task WHERE parentId = ?', [String(task.id)])
|
||||||
const ids = [String(task.id), ...kids.map((k) => String(k.id))]
|
const ids = [String(task.id), ...kids.map((k) => String(k.id))]
|
||||||
const ph = ids.map(() => '?').join(',')
|
const ph = ids.map(() => '?').join(',')
|
||||||
await tx.execute(
|
await tx.execute(
|
||||||
`UPDATE Task SET archived = 1, archivedAt = ?, archiveSource = 'manual', updatedAt = ? WHERE id IN (${ph})`,
|
`UPDATE Task SET archived = 1, archivedAt = ?, archiveSource = 'manual', updatedAt = ? WHERE id IN (${ph})`,
|
||||||
[now, now, ...ids],
|
[ts, ts, ...ids],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return { ok: true, count: 1 }
|
return { ok: true, count: 1 }
|
||||||
@@ -725,14 +727,14 @@ export const taskApi = {
|
|||||||
unarchive: async (id) => {
|
unarchive: async (id) => {
|
||||||
const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)])
|
const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)])
|
||||||
if (!task) throw new Error('任务不存在或无权限')
|
if (!task) throw new Error('任务不存在或无权限')
|
||||||
const now = now()
|
const ts = now()
|
||||||
await db().writeTransaction(async (tx) => {
|
await db().writeTransaction(async (tx) => {
|
||||||
const kids = await tx.getAll('SELECT id FROM Task WHERE parentId = ?', [String(task.id)])
|
const kids = await tx.getAll('SELECT id FROM Task WHERE parentId = ?', [String(task.id)])
|
||||||
const ids = [String(task.id), ...kids.map((k) => String(k.id))]
|
const ids = [String(task.id), ...kids.map((k) => String(k.id))]
|
||||||
const ph = ids.map(() => '?').join(',')
|
const ph = ids.map(() => '?').join(',')
|
||||||
await tx.execute(
|
await tx.execute(
|
||||||
`UPDATE Task SET archived = 0, archivedAt = NULL, archiveSource = NULL, updatedAt = ? WHERE id IN (${ph})`,
|
`UPDATE Task SET archived = 0, archivedAt = NULL, archiveSource = NULL, updatedAt = ? WHERE id IN (${ph})`,
|
||||||
[now, ...ids],
|
[ts, ...ids],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return { ok: true, count: 1 }
|
return { ok: true, count: 1 }
|
||||||
|
|||||||
Reference in New Issue
Block a user