| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- /* views/members.js —— 按人:成员列表与成员详情(他交了什么、卡在哪)。 */
- (function () {
- "use strict";
- const U = window.KF_UTIL;
- const Q = window.KF_QUERY;
- const UI = window.KF_UI;
- const esc = U.esc;
- function memberStatus(m) {
- if (m.done) return { key: "done", label: "已提交", cls: "done" };
- if (m.msgCount >= 5) return { key: "stuck", label: "在对话,未提交", cls: "stuck" };
- return { key: "idle", label: "未开始 / 未提交", cls: "idle" };
- }
- function listView(db, c) {
- const q = c.q;
- const rows = db.members.filter((m) => {
- if (c.member.length && !c.member.includes(m.name)) return false;
- if (q && !m.name.toLowerCase().includes(q)) return false;
- return true;
- }).sort((a, b) => (a.done - b.done) || b.msgCount - a.msgCount || a.name.localeCompare(b.name, "zh"));
- const total = db.members.length;
- const done = db.members.filter((m) => m.done).length;
- const body = !total
- ? UI.empty({ title: "还没有成员", body: "学员打开与分身的会话后,KfSession 会记录姓名、组、当前阶段和消息数;这里会按「交没交」排序。" })
- : !rows.length
- ? UI.empty({ title: "没有匹配的成员", body: "换个名字试试,或清除筛选。", actions: [{ label: "清除筛选", act: "clear-filters" }] })
- : `<div class="panel tbl-wrap"><table class="tbl"><thead><tr>
- <th>成员</th><th>组</th><th>阶段</th><th class="r">消息</th><th class="r">成果</th><th>最后活动</th><th>状态</th>
- </tr></thead><tbody>${rows.map((m) => {
- const s = memberStatus(m);
- return `<tr class="clickable" data-act="nav" data-href="#/member/${encodeURIComponent(m.name)}">
- <td class="cell-title">${UI.person(m.name)}</td>
- <td data-l="组">${esc(m.groupId || "—")}</td>
- <td data-l="阶段">${UI.stageCell(m.stageObj, "—")}</td>
- <td data-l="消息" class="num-cell">${m.msgCount}</td>
- <td data-l="成果" class="num-cell">${m.artifactIds.length}</td>
- <td data-l="最后活动" class="when">${esc(U.fmtAgo(m.lastActiveAt))}</td>
- <td data-l="状态"><span class="state"><span class="pdot ${s.cls}"></span>${s.label}</span></td>
- </tr>`;
- }).join("")}</tbody></table></div>`;
- return `<div class="view-head"><div>
- <h1>成员</h1>
- <p class="lede">${total} 人参与,${done} 人已提交成果。未提交的排前面,现场可以逐个盯进度。</p>
- </div></div>
- ${UI.activeLine(c)}
- ${body}`;
- }
- function detail(db, name) {
- const m = db.members.find((x) => x.name === name);
- if (!m) return null;
- const arts = m.artifactIds.map((id) => db.artifactById[id]).sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
- const projects = m.projectIds.map((id) => db.projectById[id]);
- const sessions = db.sessions.filter((s) => s.studentName === name);
- const s = memberStatus(m);
- const head = `<div class="flat detail-head">
- <div>
- <div class="kicker"><span class="state"><span class="pdot ${s.cls}"></span>${s.label}</span><span>${esc(m.groupId || "未分组")}</span></div>
- <h1>${esc(m.name)}</h1>
- <div class="kv">
- <div>当前阶段<b>${m.stageObj ? esc(m.stageObj.label) : "—"}</b></div>
- <div>对话消息<b class="num">${m.msgCount}</b></div>
- <div>最后活动<b>${esc(U.fmtAgo(m.lastActiveAt))}</b></div>
- </div>
- </div>
- <div class="integrity">
- <div class="row"><span>成果</span><span>${arts.length}</span></div>
- <div class="row"><span>项目</span><span>${projects.length}</span></div>
- <div class="row"><span>会话</span><span>${sessions.length}</span></div>
- </div>
- </div>`;
- const artList = arts.length ? `<div class="panel">${arts.map((a) => `
- <div class="trace-item" style="padding:12px 20px">
- <span class="idx">${esc(U.fmtDate(a.createdAt))}</span>
- <div><div class="t" data-act="open-artifact" data-id="${esc(a.id)}">${esc(a.title)}</div>
- <div class="m">${UI.stageCell(a.stageObj, a.stage)}${UI.layerBadge(a.layer)}${UI.evidenceBadge(a)}<span>${esc(a.typeLabel)}</span></div>
- </div>
- <span class="tiny muted">${a.quoteCount} 条原文</span>
- </div>`).join("")}</div>`
- : UI.empty({ sm: true, title: "这个人还没有交成果", body: `已有 ${m.msgCount} 条对话。对话结束后,成果经上传 skill 入库,会出现在这里。` });
- const projList = projects.length ? `<div class="panel tbl-wrap"><table class="tbl"><thead><tr>
- <th>项目</th><th>阶段</th><th>优先级</th><th class="r">评分</th><th>状态</th></tr></thead><tbody>
- ${projects.map((p) => `<tr class="clickable" data-act="nav" data-href="#/project/${encodeURIComponent(p.id)}">
- <td class="cell-title"><span class="ttl">${esc(p.title)}</span></td>
- <td data-l="阶段">${UI.stageCell(p.stageObj, p.stage)}</td>
- <td data-l="优先级">${UI.priorityBadge(p.priority)}</td>
- <td data-l="评分" class="num-cell">${p.score == null ? "—" : p.score.toFixed(1)}</td>
- <td data-l="状态">${UI.stateBadge(p.status, p.statusLabel)}</td></tr>`).join("")}
- </tbody></table></div>`
- : UI.empty({ sm: true, title: "还没有立项", body: "在 S3 机会识别之后,学员可以从机会卡立项;立项后这里会列出项目。" });
- return `<div>
- <div style="margin-bottom:18px">${UI.crumb([{ label: "成员", href: "#/members" }, { label: m.name }])}</div>
- ${head}
- <div class="sec"><h2>成果</h2><span class="hint">${arts.length} 条</span></div>${artList}
- <div class="sec"><h2>项目</h2></div>${projList}
- <div class="sec"><h2>过滤此人的归纳 / 记录</h2></div>
- <div class="note">${UI.icons.info}<span>查看 <a href="#/artifacts?member=${encodeURIComponent(m.name)}">他的全部成果</a>,或 <a href="#/insights?member=${encodeURIComponent(m.name)}">引用过他成果的归纳</a>。</span></div>
- </div>`;
- }
- window.KF_VIEW_MEMBERS = { listView, detail };
- })();
|