/* views/artifacts.js —— 成果列表:四层归属、出身、证据、筛选叠加、排序。 * 既用于"成果"页,也被"记录层"页复用(固定 layer=record)。 */ (function () { "use strict"; const T = window.KF_TAXONOMY; const U = window.KF_UTIL; const Q = window.KF_QUERY; const UI = window.KF_UI; const esc = U.esc; function rowOf(a, ctx) { const p = ctx.db.projectById[a.projectId]; return { ...a, priority: p ? p.priority : "", category: p ? p.category : "", projectTitle: p ? p.title : "", evidenceKey: a.evidenceLevel }; } function rowsFor(db, filterLayer) { const base = filterLayer ? db.artifacts.filter((a) => a.layer === filterLayer) : db.artifacts; const ctx = { db }; return base.map((a) => rowOf(a, ctx)); } /* 排序:数字/日期维度按方向,文本维度固定方向 */ const SORTS = { newest: { label: "最新提交", fn: (a, b) => new Date(b.createdAt) - new Date(a.createdAt) }, evidence: { label: "证据强度", fn: (a, b) => b.evidenceRank - a.evidenceRank || new Date(b.createdAt) - new Date(a.createdAt) }, owner: { label: "按成员", fn: (a, b) => a.ownerName.localeCompare(b.ownerName, "zh") }, stage: { label: "按阶段", fn: (a, b) => (b.stageObj ? b.stageObj.no : 0) - (a.stageObj ? a.stageObj.no : 0) } }; function buildFacets(db, rows, c, opts) { const P = T.PRIORITIES; const dims = []; if (!opts.priorityLocked) { const pc = Q.facetCounts(rows, c, "priority", (r) => r.priority); dims.push({ key: "priority", label: "优先级", options: P.map((v) => ({ value: v, label: v, count: pc.get(v) || 0 })) }); } const sc = Q.facetCounts(rows, c, "stage", (r) => (r.stageObj ? r.stageObj.code : null)); dims.push({ key: "stage", label: "阶段", options: T.STAGES.map((s) => ({ value: s.code, label: s.code, count: sc.get(s.code) || 0 })) }); const cats = [...new Set(rows.map((r) => r.category).filter(Boolean))].sort((a, b) => a.localeCompare(b, "zh")); const cc = Q.facetCounts(rows, c, "category", (r) => r.category); dims.push({ key: "category", label: "品类", options: cats.map((v) => ({ value: v, label: v, count: cc.get(v) || 0 })) }); const ec = Q.facetCounts(rows, c, "evidence", (r) => r.evidenceKey); dims.push({ key: "evidence", label: "证据", options: T.EVIDENCE.slice(0, 3).map((e) => ({ value: e.key, label: e.label, count: ec.get(e.key) || 0 })) }); if (!opts.layerLocked) { const lc = Q.facetCounts(rows, c, "layer", (r) => r.layer); dims.push({ key: "layer", label: "层", multi: true, options: T.LAYERS.map((l) => ({ value: l.key, label: l.en, count: lc.get(l.key) || 0 })) }); } const mc = Q.facetCounts(rows, c, "member", (r) => r.ownerName); const members = [...mc.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], "zh")); dims.push({ key: "member", label: "成员", multi: true, options: members.map(([v, n]) => ({ value: v, label: v, count: n })) }); /* 每个维度的当前选中值,供筛选条回显 */ dims.forEach((d) => { d.active = c[d.key] || []; }); return dims; } /* 单行。带报告的成果在标题上直接给入口——不点进抽屉也能翻开读 */ function row(r) { const ttl = r.report ? `${esc(r.shortTitle)}` : `${esc(r.title)}`; return ` ${ttl}${r.report ? `报告` : ""}${esc(r.typeLabel)}${r.projectTitle ? " \u00b7 " + esc(r.projectTitle) : ""} ${UI.person(r.ownerName)} ${UI.stageCell(r.stageObj, r.stage)} ${UI.priorityBadge(r.priority)} ${UI.layerBadge(r.layer)}${r.report ? ` 报告` : ""}${r.citedByInsight ? ` 被引用` : ""} ${UI.evidenceBadge(r)}${r.evidenceUnaligned ? ` 未对齐` : ""} ${U.fmtDate(r.createdAt, true)} `; } function header(sorted) { const th = (key, label, cls) => { const on = sorted === key; return `${label}${on ? "↓" : "↕"}`; }; return `${th("stage", "阶段")}${th("evidence", "证据")}${th("newest", "提交时间")} `; } /* 结果为空时,逐项去掉一个条件看还剩几条,告诉用户是哪项卡住了 */ function relaxHint(rows, c) { const parts = []; const q = window.KF_QUERY; for (const dim of ["priority", "stage", "category", "evidence", "layer", "member"]) { const vals = c[dim]; if (!vals.length || vals.length < 1) continue; const n = rows.filter((r) => q.test(r, { ...c, [dim]: [] })).length; parts.push(`${dimLabel(dim)}=${vals.join("、")} 时只剩 ${n} 条`); } if (c.q) parts.push(`搜索「${c.q}」时只剩 ${rows.filter((r) => q.test(r, { ...c, q: "" })).length} 条`); return parts.length ? "单独去掉某一项后: " + parts.join(";") + "。" : ""; } function dimLabel(d) { return ({ priority: "优先级", stage: "阶段", category: "品类", evidence: "证据", layer: "层", member: "成员" })[d] || d; } /* 页面主体 */ function listView(db, c, opts) { const rowsAll = rowsFor(db, opts.filterLayer); const rows = rowsAll.filter((r) => Q.test(r, c)); const sortKey = SORTS[c.sort] ? c.sort : (opts.defaultSort || "newest"); const sorted = (opts.sortLocked ? rowsAll.slice() : rows.slice()); sorted.sort(SORTS[sortKey].fn); /* ?focus=:从报告/抽屉跳回来时,标出"你要找的那条";只影响排序,不隐藏其他行 */ const focusA = c.focus ? db.artifactById[c.focus] : null; window.__KF_FOCUS = focusA ? focusA.id : ""; if (focusA) { const i = sorted.findIndex((r) => r.id === focusA.id); if (i > 0) sorted.unshift(sorted.splice(i, 1)[0]); } const dims = buildFacets(db, rowsAll, c, opts); const filterHtml = filterBarFor(dims, c, rows.length, opts.unit || "条成果"); let body; if (!rowsAll.length) { body = UI.empty({ title: opts.emptyTitle || "还没有归集的成果", body: opts.emptyBody || "学员对话结束后,上传 skill 会把成果写进库;刷新或等待自动刷新的那一刻,这里就会出现带出身(谁、何时、哪一步、有没有原文)的记录。", actions: opts.emptyActions }); } else if (!rows.length) { body = UI.empty({ title: "当前条件下没有成果", body: `已有 ${rowsAll.length} 条成果,但没有同时满足全部条件的。${relaxHint(rowsAll, c)}`, actions: [{ label: "清除全部筛选", act: "clear-filters" }] }); } else { body = `
${header(opts.sortLocked ? null : sortKey)}${sorted.map(row).join("")}
成果成员优先级层
`; } return `

${esc(opts.title)}

${opts.lede}

${opts.sortLocked ? "" : sortSeg(sortKey)}
${filterHtml}
${UI.activeLine(c)}
${focusA ? `
${UI.icons.link}正在定位:${esc(focusA.shortTitle)} · ${esc(focusA.ownerName || "未知")} · ${esc(focusA.stage)}清除定位
` : ""}
${body}
`; } /* 筛选条:优先复用 common.filterBar,这里补充 layer 锁定提示 */ function filterBarFor(dims, c, n, unit) { return UI.filterBar(dims, c, n, unit); } function sortSeg(cur) { return `
${Object.entries(SORTS).map(([k, s]) => ``).join("")}
`; } window.KF_VIEW_ARTIFACTS = { listView, rowsFor, SORTS }; })();