chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次) - .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…), 目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪 - .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/) - .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*) - .gitignore 补:备份件(*.bak-*) - 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
This commit is contained in:
1 parent
30b46dbd0c
commit
c1b5e4d966
735 files changed
+153192
-2415
No files matched your search
+143
-77
@@ -40,6 +40,11 @@
|
||||
真因:grid **默认 `stretch`** ⇒ 12 张卡片全在「已完成」、另三列是空的,
|
||||
但四列**被拉成等高** ⇒ 空列白占 1100px、整体高度虚胖。
|
||||
✅ 改 `start` ⇒ 每列**各自撑自己的高度**,空列就是一个小盒子。*/
|
||||
/* 🔴🔴 **四列并排等宽**(2026-10-06 用户「之前是这样吗 现在是改乱后的样子」⇒ 恢复)。
|
||||
⛔ 别再动这四行 —— 我曾两度以「空列白占屏 / 卡片看不到」为由改过列宽,
|
||||
改成 flex+空列收窄(三窄一宽),用户判为「改乱了」。
|
||||
📌 判据:**四态列是固定维度,必须并排等宽**(一眼看全四个状态区),
|
||||
"某个状态是空的"不是布局问题 —— ⛔ 不许因空列去改列宽。*/
|
||||
.cols{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;
|
||||
align-items:start}
|
||||
@media(max-width:1100px){.cols{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
@@ -79,6 +84,8 @@
|
||||
display:flex;gap:9px;flex-wrap:wrap}
|
||||
.badge{display:inline-block;border-radius:5px;padding:1px 6px;font-size:11px;
|
||||
border:1px solid var(--line);color:var(--dim)}
|
||||
/* 🔴 「当前目标」标记(一个区常有多个目标,得看得出现在在跑哪个) */
|
||||
.badge.cur{background:rgba(76,110,245,.2);border-color:rgba(76,110,245,.6);color:#b9c8ff}
|
||||
.empty{color:var(--dim2);font-size:12px;text-align:center;padding:18px 6px}
|
||||
.warnline{margin:0 0 12px;padding:10px 12px;border-radius:8px;
|
||||
background:rgba(240,160,32,.09);border:1px solid rgba(240,160,32,.35);
|
||||
@@ -169,10 +176,39 @@
|
||||
.sess:hover{border-color:var(--accent)}
|
||||
.sess .sn{flex:1;word-break:break-word;font-size:13px}
|
||||
.sess .go{color:var(--accent);font-size:12px;flex:none}
|
||||
/* 🔴 旧「行式」判据样式(.flow)—— 2026-10-06 用户:「判据用卡片显示,现在列表字段都挤成竖列了」。
|
||||
⛔ 保留定义仅作兼容,新代码一律用下面 `.pcard`(卡片式)。 */
|
||||
.flow{display:flex;align-items:center;gap:9px;padding:6px 0;border-bottom:1px dashed var(--line);font-size:13px}
|
||||
.flow:last-child{border-bottom:0}
|
||||
.flow .fn{flex:1}
|
||||
.flow .fc{color:var(--dim2);font-size:12px}
|
||||
|
||||
/* ── 🔴🔴 **判据卡片**(2026-10-06 用户:「判据 用卡片显示,现在列表字段都挤成竖列了」)──
|
||||
病根:旧 `.flow{display:flex}` 把「判据键」和「判据值」**摆在同一行的左右两侧**
|
||||
⇒ 抽屉宽度只有 ~560px,判据值又长(实测一条 100+ 字)⇒ **被压成窄竖条**,几乎不可读。
|
||||
✅ 判据卡=**垂直布局**:① 首行=键 + 通过/未过标记(右对齐)② 下面=值全文,
|
||||
`word-break` 让它自然换行铺满卡宽(⛔ 不再被挤成竖列)。
|
||||
📌 一卡一条判据,卡与卡之间留白 —— 比"虚线分割的行列表"好读得多。 */
|
||||
.pcards{display:flex;flex-direction:column;gap:9px}
|
||||
.pcard{background:var(--panel2);border:1px solid var(--line);border-left:3px solid var(--line);
|
||||
border-radius:8px;padding:9px 11px}
|
||||
.pcard.ok{border-left-color:var(--ok)}
|
||||
.pcard.no{border-left-color:var(--bad)}
|
||||
.pcard .phead{display:flex;align-items:baseline;gap:9px;margin-bottom:5px}
|
||||
/* 键:等宽字体(判据键多半是 `V1`/`U27`/`A6` 这类机读标号) */
|
||||
.pcard .pk{font-family:ui-monospace,Consolas,monospace;font-size:12.5px;
|
||||
font-weight:600;word-break:break-all}
|
||||
.pcard .pv{font-size:12.5px;color:var(--dim);word-break:break-word;line-height:1.6}
|
||||
/* 通过/未过标记:靠右、不换行 */
|
||||
.pcard .pst{margin-left:auto;flex:none;font-size:11px;border-radius:5px;padding:1px 7px;
|
||||
border:1px solid var(--line)}
|
||||
.pcard.ok .pst{background:rgba(63,185,80,.15);border-color:rgba(63,185,80,.5);color:#8fe39a}
|
||||
.pcard.no .pst{background:rgba(229,83,75,.15);border-color:rgba(229,83,75,.5);color:#f2a09a}
|
||||
/* 任务细账(工作流分组)也改卡片:名称一行、计数一行(⛔ 不再左右挤) */
|
||||
.fcard{background:var(--panel2);border:1px solid var(--line);border-radius:8px;
|
||||
padding:8px 11px;margin-bottom:7px}
|
||||
.fcard .fname{font-size:12.5px;font-weight:600;margin-bottom:3px;word-break:break-word}
|
||||
.fcard .fmeta{font-size:12px;color:var(--dim2)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -363,9 +399,14 @@ function load(manual){
|
||||
// ⚠️ 我上一版写成「全部(N 个目标)」,是把两个维度混了 —— 已纠正。
|
||||
sel.innerHTML = '<option value="-1">全部('+d.projects.length+' 个工作区)</option>'
|
||||
+ d.projects.map(function(p,i){
|
||||
// 有目标 ⇒ 目录名 + 目标名;没目标 ⇒ 只目录名(⛔ 不印「未命名」那种看不懂的话)
|
||||
var lab = p.dir + (p.has_goal && p.name ? '('+p.name+')' : '');
|
||||
return '<option value="'+i+'">'+esc(lab)+'</option>'; }).join("");
|
||||
// 🔴🔴 **只印工作区名(`dir`)**(用户 2026-10-06 再次纠正:
|
||||
// 「工作区下拉筛选为什么要显示目标信息」)。
|
||||
// ⛔ 别拼 `p.name`(目标名)—— 目标名可能很长(实测那条 30+ 字),
|
||||
// 把它塞进选项 ⇒ 下拉被撑爆、且**语义错了**(这个控件选的是工作区)。
|
||||
// ⚠️ 我上一版就写了「有目标 ⇒ 目录名 + 目标名」,注释里明明写着
|
||||
// 「跟目标没关系」,代码却照样拼了目标名 —— 注释与代码打架,以本条为准。
|
||||
// 📌 目标信息该显示在**别处**(概要区/卡片),⛔ 不在这个下拉里。
|
||||
return '<option value="'+i+'">'+esc(p.dir)+'</option>'; }).join("");
|
||||
if(STATE.cur >= d.projects.length) STATE.cur = -1;
|
||||
sel.value = String(STATE.cur);
|
||||
}
|
||||
@@ -375,42 +416,42 @@ function load(manual){
|
||||
});
|
||||
}
|
||||
|
||||
/* 🔴 卡片状态来自台账;这里按「目标」分组渲染。
|
||||
`STATE.cur === -1` ⇒ **全部目标**(默认):一屏看完本机所有项目的卡片,按目标分段。
|
||||
`STATE.cur >= 0` ⇒ 只看某一个目标(原行为)。 */
|
||||
/* 🔴🔴 **列键 → 颜色**(2026-10-06 四列改为「目标」后,列键换成 lifecycle 口径)。
|
||||
⚠️ 列键改名了:`waiting/active/blocked/done`(⛔ 旧的 `pending/running` 已废)。*/
|
||||
function _stateDot(key){
|
||||
return key==="done" ? "var(--ok)" : key==="blocked" ? "var(--bad)"
|
||||
: key==="running" ? "var(--accent)" : "var(--dim2)";
|
||||
: key==="active" ? "var(--accent)" : "var(--dim2)";
|
||||
}
|
||||
|
||||
/* 🔴🔴🔴 **卡片 = 目标**(2026-10-06 用户三次定案:
|
||||
「下面的 待执行 执行中 有阻碍 已完成 里面显示的都是目标 目标就是项目」
|
||||
+「但是你现在 已完成里面显示的 就不是目标信息,连一个目标的名称都没有」)。
|
||||
⛔ 改前这里渲染的是 `tasks.json` 的**台账条目** ⇒ 标题印出来是任务 id(`3b-v1`/`dark-tokens`),
|
||||
**一个目标名都没有** —— 用户看的正是这张屏。
|
||||
✅ 现在服务端 `cards[]` 一条 = 一个目标,字段:`title`(目标名)/`life`/`current`/`_ws`。
|
||||
📌 卡片三样:**目标名**(主标题,必须是人话标题)|**状态**badge|**工作区名**(末行归属)。
|
||||
⛔ 没有 `by`/`line`/`artifact`/`t_end` 了 —— 那些是**任务**字段,目标没有。
|
||||
🔴 当前目标(`current`)用一个小标记区分:一个区常有好几个目标,得看得出"现在在跑哪个"。*/
|
||||
function _cardHtml(t){
|
||||
var dot = _stateDot(t.state), bits = [];
|
||||
// 🔴 用户 2026-10-06:「工作区名称要不要 放到卡片下面」⇒ 采纳。
|
||||
// 卡片结构改成**三段**:① 标题 ② 受阻原因(有才显示)③ **卡片下方一行**=
|
||||
// `工作区名` + 线/执行者/时间。
|
||||
// ⚠️ 工作区名**从 `.meta` 里挪出去单列** —— 它是这一屏唯一的"归属"信息,
|
||||
// 混在其它 badge 里会被忽略。
|
||||
// 标签取 `_ws`(目录名,如 `ai1net-dsh-server`),⛔ 不是 `_goal`(目标短名,
|
||||
// 用户明确「不要显示 本机协作这种」)。
|
||||
if(t.line) bits.push('<span class="badge">'+esc(t.line)+'</span>');
|
||||
if(t.by) bits.push('<span class="badge">'+esc(t.by)+'</span>');
|
||||
var when = t.t_end ? fmtWhen(t.t_end) : (t.t_start ? fmtWhen(t.t_start) : "");
|
||||
if(when) bits.push('<span class="badge when">'+esc(when)+'</span>');
|
||||
// 🔴 本工作区的卡片换色突出(2026-10-06 用户要);判定来自服务端 `_self`,⛔ 前端不猜
|
||||
var dot = _stateDot(t.state);
|
||||
var ws = t._ws
|
||||
? '<div class="wsrow"><span class="wsname">'+esc(t._ws)+'</span>'+
|
||||
(t._self ? '<span class="wsself">本工作区</span>' : '')+'</div>'
|
||||
: '';
|
||||
var badges = '<span class="badge">'+esc(t.life||"")+'</span>';
|
||||
if(t.current) badges += '<span class="badge cur">当前</span>';
|
||||
return '<div class="card'+(t._self?" is-self":"")+'" style="border-left-color:'+dot+
|
||||
'" data-tid="'+esc(t.id)+'" data-pw="'+esc(t._ws||"")+'">'+
|
||||
'<div class="t">'+esc(t.title||t.id)+'</div>'+
|
||||
(t.block_reason ? '<div class="meta" style="color:#f2a09a">受阻:'+esc(t.block_reason)+'</div>' : '')+
|
||||
(bits.length ? '<div class="meta">'+bits.join("")+'</div>' : '')+
|
||||
// 🔴 末行=工作区(用户要的「放到卡片下面」)
|
||||
(t._ws ? '<div class="wsrow"><span class="wsname">'+esc(t._ws)+'</span>'+
|
||||
(t._self ? '<span class="wsself">本工作区</span>' : '')+'</div>' : '')+
|
||||
'</div>';
|
||||
// 🔴 主标题=**目标名**(人话标题);⛔ 别回落到 id(那正是"看着不像目标"的原因)
|
||||
'<div class="t">'+esc(t.title || "(未命名目标)")+'</div>'+
|
||||
'<div class="meta">'+badges+'</div>'+
|
||||
ws+'</div>';
|
||||
}
|
||||
|
||||
function _colHtml(key, label, list){
|
||||
var inner = list.length ? list.map(_cardHtml).join("")
|
||||
: '<div class="empty">(此列无卡片)</div>';
|
||||
: '<div class="empty">(此列无目标)</div>';
|
||||
// 🔴 四态列**一律等宽**(⛔ 别给空列收窄;2026-10-06 用户「现在是改乱后的样子」)。
|
||||
return '<section class="col"><div class="colhead"><span class="dot" style="background:'+
|
||||
_stateDot(key)+'"></span><span class="coltitle">'+esc(label)+'</span><span class="count">'+
|
||||
list.length+'</span></div><div class="cards">'+inner+'</div></section>';
|
||||
@@ -432,22 +473,28 @@ function renderAll(){
|
||||
var cols = document.getElementById("cols");
|
||||
document.getElementById("btndel").disabled = true; // ⛔ 「全部」不是单个工作区,删不得
|
||||
|
||||
var tot = {total:0, done:0, open:0, blocked:0};
|
||||
// 🔴🔴 **两个维度分开报**(2026-10-06 用户定案:四列是「目标」维度):
|
||||
// ① 工作区=目录(装了机制的)② 目标=登记过的那件事(**=四列里的卡片**,
|
||||
// 🔴 一个工作区可有**多个**目标:当前 1 个 + 历史 N 个)
|
||||
// ③ 任务=目标下的台账条目(细账,⛔ 不再上四列)。
|
||||
var nWs = ps.length;
|
||||
var nGoal = 0;
|
||||
ps.forEach(function(p){ nGoal += (p.n_goals || 0); });
|
||||
var gcol = {waiting:0, active:0, blocked:0, done:0};
|
||||
ps.forEach(function(p){
|
||||
tot.total += p.queue.total; tot.done += p.queue.done;
|
||||
tot.open += p.queue.open; tot.blocked += p.queue.blocked;
|
||||
var g = p.goal_by_col || {};
|
||||
for(var k in gcol) gcol[k] += (g[k] || 0);
|
||||
});
|
||||
// 🔴 两个维度**分开报**(用户 2026-10-06 纠正:「下拉框是选工作区的 跟目标没关系」):
|
||||
// 工作区=目录(装了机制的);目标=工作区里登记的那件事。
|
||||
// ⚠️ 我上一版写「目标 N 个」用的是**工作区条数** ⇒ 把两个维度混成了一个数(已改)。
|
||||
var nWs = ps.length, nGoal = ps.filter(function(p){ return p.has_goal; }).length;
|
||||
var s = [];
|
||||
s.push('<span class="chip">工作区 <b>'+nWs+'</b> 个</span>');
|
||||
s.push('<span class="chip">其中已登记目标 <b>'+nGoal+'</b> 个</span>');
|
||||
s.push('<span class="chip">台账合计 <b>'+tot.total+'</b> 件</span>');
|
||||
s.push('<span class="chip">已完成 <b>'+tot.done+'</b></span>');
|
||||
s.push('<span class="chip">未完 <b>'+tot.open+'</b></span>');
|
||||
if(tot.blocked) s.push('<span class="chip">受阻 <b>'+tot.blocked+'</b></span>');
|
||||
s.push('<span class="chip">目标 <b>'+nGoal+'</b> 个</span>');
|
||||
s.push('<span class="chip">其中执行中 <b>'+gcol.active+'</b></span>');
|
||||
s.push('<span class="chip">已完成 <b>'+gcol.done+'</b></span>');
|
||||
if(gcol.blocked) s.push('<span class="chip">有阻碍 <b>'+gcol.blocked+'</b></span>');
|
||||
// 任务细账(⛔ 与上面分栏:这是"目标之下的工作量")
|
||||
var taskTot = 0, taskDone = 0;
|
||||
ps.forEach(function(p){ taskTot += (p.queue&&p.queue.total)||0; taskDone += (p.queue&&p.queue.done)||0; });
|
||||
if(taskTot) s.push('<span class="chip">任务台账 <b>'+taskDone+' / '+taskTot+'</b></span>');
|
||||
sums.innerHTML = s.join("");
|
||||
|
||||
// 提示(同样收进「!」)
|
||||
@@ -457,10 +504,13 @@ function renderAll(){
|
||||
nt.push('<div class="iw">有 <b>'+noGoal.length+'</b> 个工作区<b>装了机制但还没登记目标</b>:'+
|
||||
esc(noGoal.map(function(x){ return x.dir; }).join("、"))+
|
||||
' ⇒ 在那些工作区里用 <code>goalctl declare --title "…"</code> 登记</div>');
|
||||
var notRun = ps.filter(function(x){ return x.life && x.life !== "进行中"; });
|
||||
if(notRun.length)
|
||||
nt.push('<div class="iw">有 <b>'+notRun.length+'</b> 个目标状态<b>不是「进行中」</b>:'+
|
||||
esc(notRun.map(function(x){ return x.dir+"("+(x.name||"—")+":"+x.life+")"; }).join("、"))+'</div>');
|
||||
// 🔴 有目标但**落不进四列**的(已暂停 / 状态读不出)⇒ 必须说出来,⛔ 不静默丢
|
||||
// (同族 P0:「作用域不许静默排除」—— 否则"目标怎么少了一个"永远查不出)
|
||||
var unknown = [];
|
||||
ps.forEach(function(x){ (x.life_unknown||[]).forEach(function(u){ unknown.push(u); }); });
|
||||
if(unknown.length)
|
||||
nt.push('<div class="iw">有 <b>'+unknown.length+'</b> 个目标<b>不在四列里</b>(已暂停/状态读不出):'+
|
||||
esc(unknown.join("、"))+'</div>');
|
||||
setInfo(nt);
|
||||
|
||||
if(!ps.length){
|
||||
@@ -501,18 +551,17 @@ function renderOne(){
|
||||
if(!p){ renderAll(); return; }
|
||||
document.getElementById("btndel").disabled = false;
|
||||
|
||||
// 概要
|
||||
// 概要(🔴 顺序=**目标在前、任务细账在后**;用户口径:四列装目标)
|
||||
var q = p.queue, s = [];
|
||||
s.push('<span class="chip">项目名 <b>'+esc(p.name)+'</b></span>');
|
||||
if(p.life) s.push('<span class="chip">目标状态 <b>'+esc(p.life)+'</b></span>');
|
||||
s.push('<span class="chip">目标名 <b>'+esc(p.name)+'</b></span>');
|
||||
s.push('<span class="chip">工作区 <b>'+esc(p.dir)+'</b></span>');
|
||||
s.push('<span class="chip">建立 <b>'+esc(fmtWhen(p.declared_at))+'</b></span>');
|
||||
s.push('<span class="chip">台账 <b>'+q.total+'</b> 件</span>');
|
||||
s.push('<span class="chip">已完成 <b>'+q.done+'</b></span>');
|
||||
s.push('<span class="chip">未完 <b>'+q.open+'</b></span>');
|
||||
if(q.blocked) s.push('<span class="chip">受阻 <b>'+q.blocked+'</b></span>');
|
||||
if(p.acc && p.acc.n) s.push('<span class="chip">验收 <b>'+p.acc.pass+' / '+p.acc.n+'</b></span>');
|
||||
s.push('<span class="chip">工作流 <b>'+p.flows.length+'</b></span>');
|
||||
if(p.life) s.push('<span class="chip">目标状态 <b>'+esc(p.life)+'</b></span>');
|
||||
// 任务细账(⛔ 与"目标"分栏:这是目标之下的工作量)
|
||||
if(q.total)
|
||||
s.push('<span class="chip">任务 <b>'+q.done+' / '+q.total+'</b></span>');
|
||||
if(q.blocked) s.push('<span class="chip">其中受阻 <b>'+q.blocked+'</b></span>');
|
||||
if(p.topics && p.topics.length)
|
||||
s.push('<span class="chip">任务类别 <b>'+esc(p.topics.join("、"))+'</b></span>');
|
||||
sums.innerHTML = s.join("");
|
||||
@@ -520,14 +569,18 @@ function renderOne(){
|
||||
// ── 提示:一律收进右上角「!」图标里(⛔ 不铺在工作台)──
|
||||
var nt = [];
|
||||
if(!p.has_goal)
|
||||
nt.push('<div class="iw">这个工作区<b>还没有登记目标</b>(只有机制骨架)⇒ 项目名是兜底的,卡片按台账显示。</div>');
|
||||
nt.push('<div class="iw">这个工作区<b>还没有登记目标</b>(只有机制骨架)⇒ 四列会是空的 —— '+
|
||||
'用 <code>goalctl declare --title "…"</code> 登记后才会出现目标卡。</div>');
|
||||
if(p.life && p.life !== "进行中")
|
||||
nt.push('<div class="iw">该目标状态为「'+esc(p.life)+'」('+esc(fmtWhen(p.life_at))+')。</div>');
|
||||
if(p.life_raw && p.life_raw !== p.life)
|
||||
nt.push('<div class="iw">该目标 <code>lifecycle</code> 原始值=「'+esc(p.life_raw)+
|
||||
'」,已归一为「'+esc(p.life||"(认不出)")+'」。</div>');
|
||||
setInfo(nt);
|
||||
|
||||
// 列(🔴 同样给每张卡片补 `_ws` / `_self`:单项目视图也要工作区标签+本区高亮)
|
||||
// 列(🔴 单目标视图 ⇒ 四列里最多就这一张目标卡,落它自己的状态列)
|
||||
var byState = {};
|
||||
p.cards.forEach(function(c){
|
||||
(p.cards || []).forEach(function(c){
|
||||
var x = {}; for(var k in c){ x[k] = c[k]; }
|
||||
x._ws = p.dir; x._goal = p.name; x._self = !!p.self; x._life = p.life || "";
|
||||
(byState[c.state] = byState[c.state] || []).push(x);
|
||||
@@ -553,40 +606,53 @@ function openDetail(tid, wsHint){
|
||||
|| proj[STATE.cur] || {};
|
||||
var t = (p.cards || []).filter(function(x){ return x.id === tid; })[0] || {};
|
||||
var label = {}; STATE.data.cols.forEach(function(c){ label[c.key] = c.label; });
|
||||
document.getElementById("dtitle").textContent = tid;
|
||||
// 🔴 抽屉标题=**目标名**(⛔ 不是 id:id 是 `vibe-product@cur` 这种机读串,人看不懂)
|
||||
document.getElementById("dtitle").textContent = t.title || p.name || tid;
|
||||
|
||||
var h = [];
|
||||
h.push('<dl class="kv">');
|
||||
h.push('<dt>所属项目</dt><dd>'+esc(p.name)+'</dd>');
|
||||
h.push('<dt>状态</dt><dd>'+esc(label[t.state]||t.state||"—")+'</dd>');
|
||||
if(t.line) h.push('<dt>工作流</dt><dd>'+esc(t.line)+'</dd>');
|
||||
if(t.by) h.push('<dt>负责</dt><dd>'+esc(t.by)+'</dd>');
|
||||
if(t.t_start) h.push('<dt>开始</dt><dd>'+esc(fmtWhen(t.t_start))+'</dd>');
|
||||
if(t.t_end) h.push('<dt>结束</dt><dd>'+esc(fmtWhen(t.t_end))+'</dd>');
|
||||
if(t.block_reason) h.push('<dt>受阻原因</dt><dd style="color:#f2a09a">'+esc(t.block_reason)+'</dd>');
|
||||
if(t.artifact) h.push('<dt>产出</dt><dd>'+esc(t.artifact)+'</dd>');
|
||||
h.push('<dt>工作区</dt><dd>'+esc(p.ws)+'</dd>');
|
||||
// 🔴🔴 卡片=**目标**(2026-10-06):详情页主档印**该目标自己**的字段。
|
||||
// ⛔ 不要用 `p.xxx`(`p` 是**工作区**,它的 `declared_at`/`topics`/`life` 只属于
|
||||
// **当前目标**)⇒ 点历史目标会读到当前目标的值(张冠李戴,同 `acc` 那个缺陷)。
|
||||
// ⛔ 也不再印任务的 `line`/`by`/`t_start`/`t_end`(目标没有这些)。
|
||||
h.push('<dt>目标</dt><dd>'+esc(t.title || p.name || "—")+'</dd>');
|
||||
h.push('<dt>状态</dt><dd>'+esc(t.life || label[t.state] || t.state || "—")+'</dd>');
|
||||
if(t.current) h.push('<dt>当前目标</dt><dd>是</dd>');
|
||||
if(t.short) h.push('<dt>目标 id</dt><dd>'+esc(t.short)+'</dd>');
|
||||
h.push('<dt>工作区</dt><dd>'+esc(p.dir || "—")+'</dd>');
|
||||
if(t.declared_at) h.push('<dt>登记于</dt><dd>'+esc(fmtWhen(t.declared_at))+'</dd>');
|
||||
if(t.life_at) h.push('<dt>状态更新</dt><dd>'+esc(fmtWhen(t.life_at))+'</dd>');
|
||||
if(t.topics && t.topics.length) h.push('<dt>任务类别</dt><dd>'+esc(t.topics.join("、"))+'</dd>');
|
||||
h.push('<dt>目标目录</dt><dd>'+esc(t.goal_dir || p.goal_dir || "—")+'</dd>');
|
||||
h.push('</dl>');
|
||||
|
||||
// 验收判据(本项目达成与否的唯一机读依据)
|
||||
h.push('<div class="sect"><h4>验收判据('+((p.acc&&p.acc.n)||0)+' 条 · 通过 '+((p.acc&&p.acc.pass)||0)+')</h4>');
|
||||
if(p.acc && p.acc.items && p.acc.items.length){
|
||||
p.acc.items.forEach(function(a){
|
||||
h.push('<div class="flow"><span class="fn">'+esc(a.k)+'</span>'+
|
||||
'<span class="fc" style="color:'+(a.pass?"#8fe39a":"#f2a09a")+'">'+
|
||||
(a.pass?"过":"未过")+' · '+esc(a.v.slice(0,120))+'</span></div>');
|
||||
// 验收判据(🔴🔴 2026-10-06 用户:「判据 用卡片显示,现在列表字段都挤成竖列了」)
|
||||
// ⇒ **一卡一条**:首行=键 + 通过标记(右对齐);下面=值**全文**(⛔ 不再 `slice(0,120)` 截断
|
||||
// —— 截断会让用户以为"判据就这么短",而实际一条上百字;卡片布局已能自然换行)。
|
||||
// 🔴🔴 **判据取卡片自己的 `t.acc`**(⛔ 不是 `p.acc`):一个区可有多个目标,各有各的判据,
|
||||
// 点历史目标的卡却显示当前目标的判据=**张冠李戴**(本轮实测到的缺陷)。
|
||||
var acc = t.acc || p.acc || {};
|
||||
h.push('<div class="sect"><h4>验收判据('+(acc.n||0)+' 条 · 通过 '+(acc.pass||0)+')</h4>');
|
||||
if(acc.items && acc.items.length){
|
||||
h.push('<div class="pcards">');
|
||||
acc.items.forEach(function(a){
|
||||
h.push('<div class="pcard '+(a.pass?"ok":"no")+'">'+
|
||||
'<div class="phead"><span class="pk">'+esc(a.k)+'</span>'+
|
||||
'<span class="pst">'+(a.pass?"过":"未过")+'</span></div>'+
|
||||
'<div class="pv">'+esc(a.v || "(空)")+'</div></div>');
|
||||
});
|
||||
h.push('</div>');
|
||||
} else h.push('<div class="empty">(无验收判据)</div>');
|
||||
h.push('</div>');
|
||||
|
||||
// 工作流
|
||||
h.push('<div class="sect"><h4>工作流(任务类别)</h4>');
|
||||
// 工作流(🔴 这一节=**任务维度**细账,⛔ 不属于目标本身;标题里点明,免混淆)
|
||||
h.push('<div class="sect"><h4>任务细账(工作流分组)</h4>');
|
||||
if((p.flows||[]).length){
|
||||
p.flows.forEach(function(f){
|
||||
h.push('<div class="flow"><span class="fn">'+esc(f.line)+'</span>'+
|
||||
'<span class="fc">共 '+f.total+' · 完成 '+f.done+' · 未完 '+f.open+'</span></div>');
|
||||
h.push('<div class="fcard"><div class="fname">'+esc(f.line)+'</div>'+
|
||||
'<div class="fmeta">共 '+f.total+' · 完成 '+f.done+' · 未完 '+f.open+'</div></div>');
|
||||
});
|
||||
} else h.push('<div class="empty">(无)</div>');
|
||||
} else h.push('<div class="empty">(无任务台账)</div>');
|
||||
h.push('</div>');
|
||||
|
||||
document.getElementById("dbody").innerHTML = h.join("");
|
||||
|
||||
Reference in new issue
Block a user