feat(原型设计): 新增知识库管理与流程状态卡片

This commit is contained in:
2026-06-03 23:00:22 +08:00
parent 54fc1baa4c
commit 3da774e537

View File

@@ -355,6 +355,21 @@
font-size: 12px;
}
.task-status-line {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.task-note {
color: var(--muted);
font-size: 12px;
line-height: 1.6;
min-height: 40px;
}
.task-meta {
display: flex;
justify-content: space-between;
@@ -488,6 +503,155 @@
gap: 14px;
}
.phase-board {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.phase-card {
padding: 14px;
border-radius: var(--radius-lg);
background: #f8fafb;
border: 1px solid #dbe4eb;
min-height: 142px;
display: grid;
gap: 10px;
align-content: start;
}
.phase-card.running {
background: #eef4fb;
border-color: #bfd2e3;
box-shadow: inset 0 0 0 1px rgba(32, 74, 112, 0.06);
}
.phase-card.completed {
background: #eff8f2;
border-color: #c8dfcf;
}
.phase-card.blocked {
background: #fff3ef;
border-color: #efc7bc;
}
.phase-card.pending {
background: #f8fafb;
border-color: #dbe4eb;
}
.phase-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 10px;
}
.phase-card strong {
font-size: 15px;
line-height: 1.4;
}
.phase-state {
font-size: 12px;
font-weight: 700;
color: var(--muted);
line-height: 1.4;
}
.phase-note {
color: var(--muted);
font-size: 12px;
line-height: 1.7;
}
.phase-card.running .phase-state {
color: var(--navy);
}
.phase-card.completed .phase-state {
color: var(--success);
}
.phase-card.blocked .phase-state {
color: var(--danger);
}
.knowledge-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
}
.knowledge-card {
padding: 16px;
border-radius: var(--radius-lg);
background: var(--panel-strong);
border: 1px solid var(--line);
display: grid;
gap: 12px;
}
.knowledge-card p {
margin: 0;
color: var(--muted);
line-height: 1.7;
font-size: 13px;
}
.knowledge-stat {
font-size: 28px;
font-weight: 800;
color: var(--navy-strong);
line-height: 1;
}
.knowledge-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.upload-zone {
border: 1px dashed #a7bacb;
background: linear-gradient(180deg, #f9fbfc 0%, #f3f7fa 100%);
border-radius: var(--radius-lg);
padding: 20px;
display: grid;
gap: 12px;
}
.upload-zone strong {
font-size: 16px;
}
.upload-list {
display: grid;
gap: 10px;
}
.upload-item {
padding: 12px 14px;
border-radius: var(--radius-md);
border: 1px solid #dbe4eb;
background: #f8fafb;
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
align-items: center;
}
.upload-item strong {
display: block;
margin-bottom: 6px;
}
.upload-item .muted {
color: #5f7285;
line-height: 1.6;
}
.alert {
padding: 14px 16px;
border-radius: var(--radius-md);
@@ -848,7 +1012,9 @@
.metric-grid,
.task-grid,
.copy-grid {
.copy-grid,
.phase-board,
.knowledge-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@@ -865,7 +1031,9 @@
.metric-grid,
.task-grid,
.copy-grid {
.copy-grid,
.phase-board,
.knowledge-grid {
grid-template-columns: 1fr;
}
@@ -959,6 +1127,56 @@
{ id: "risk", name: "风险预警", status: "已完成", metric: "高风险", detail: "综合结论不通过,建议先整改" },
{ id: "word", name: "Word 回填导出", status: "已阻断", metric: "仅草稿可导出", detail: "正式版被高风险与冲突字段拦截" },
],
taskStatusMap: {
pending: "待执行",
running: "执行中",
completed: "已完成",
blocked: "已阻断"
},
processFlows: {
import: [
{ key: "upload", title: "资料上传", state: "completed", stateText: "上传完成", note: "18 份文件和 1 个压缩包已建立批次。" },
{ key: "parse", title: "文档解析", state: "running", stateText: "解析中", note: "正在提取目录结构、页数与文档元数据。" },
{ key: "recognize", title: "章节点识别", state: "pending", stateText: "待执行", note: "待文档解析完成后自动识别 CH 章节节点。" },
{ key: "summary", title: "目录汇总", state: "pending", stateText: "待执行", note: "将生成 registration_overview_report 并写入工作台。" }
],
completeness: [
{ key: "load-rules", title: "加载规则包", state: "completed", stateText: "已完成", note: "已装载 NMPA IVD 注册资料规则包。" },
{ key: "compare-items", title: "必交项比对", state: "running", stateText: "核查中", note: "对照 CH1 资料要求检查缺失项与错放项。" },
{ key: "evidence", title: "法规证据关联", state: "pending", stateText: "待执行", note: "待生成命中法规条款与证据摘要。" },
{ key: "risk-tag", title: "完整性风险判定", state: "pending", stateText: "待执行", note: "将输出完整性风险等级和责任角色。" }
],
fields: [
{ key: "ocr", title: "文本准备", state: "completed", stateText: "已完成", note: "OCR / 文本抽取结果已就绪。" },
{ key: "extract", title: "字段抽取", state: "running", stateText: "抽取中", note: "按字段 Schema 从说明书与申请表中抽取字段。" },
{ key: "pool", title: "字段池写入", state: "pending", stateText: "待执行", note: "将沉淀字段值、来源、置信度与回填标记。" },
{ key: "review", title: "待复核标记", state: "pending", stateText: "待执行", note: "对低置信度字段追加人工复核状态。" }
],
consistency: [
{ key: "load-pool", title: "加载字段池", state: "completed", stateText: "已完成", note: "已读取统一字段池和强一致字段定义。" },
{ key: "compare", title: "来源对比", state: "running", stateText: "核查中", note: "正在比较说明书、申请表和章节文档取值。" },
{ key: "mix-risk", title: "混档风险分析", state: "pending", stateText: "待执行", note: "将输出疑似混档与来源偏差解释。" },
{ key: "conflict", title: "冲突结论生成", state: "pending", stateText: "待执行", note: "生成冲突字段列表和建议采用值。" }
],
risk: [
{ key: "collect", title: "风险汇总", state: "completed", stateText: "已完成", note: "已汇总完整性、一致性和待复核结论。" },
{ key: "grade", title: "风险评级", state: "running", stateText: "判定中", note: "按高 / 中 / 低规则综合判定当前批次。" },
{ key: "rectify", title: "整改建议生成", state: "pending", stateText: "待执行", note: "将结合责任角色输出整改动作。" },
{ key: "gate", title: "导出闸门决策", state: "pending", stateText: "待执行", note: "将决定是否允许正式版回填导出。" }
],
word: [
{ key: "mapping", title: "模板映射加载", state: "completed", stateText: "已完成", note: "已读取 Word 模板占位符和字段映射。" },
{ key: "fill", title: "字段回填", state: "running", stateText: "回填中", note: "正在把可回填字段写入草稿模板。" },
{ key: "block", title: "拦截校验", state: "blocked", stateText: "已阻断", note: "正式版被高风险和冲突字段命中拦截。" },
{ key: "download", title: "导出结果生成", state: "pending", stateText: "待执行", note: "仅草稿下载入口会在拦截后继续保留。" }
],
notification: [
{ key: "compose", title: "消息编排", state: "completed", stateText: "已完成", note: "已生成飞书交互卡片内容。" },
{ key: "mention", title: "责任人映射", state: "running", stateText: "匹配中", note: "正在匹配章节责任人与风险责任人账号。" },
{ key: "link", title: "Web 详情链接", state: "pending", stateText: "待执行", note: "待拼装批次详情跳转链接。" },
{ key: "send", title: "发送回执", state: "pending", stateText: "待执行", note: "发送后将写入 message_id 和 sent_at。" }
]
},
importTimeline: [
{ step: "创建批次", status: "已完成", detail: "创建 SUB-20260603-001来源角色为 submission。" },
{ step: "文件校验", status: "已完成", detail: "18 份文件通过校验1 份 DOC 标记待复核。" },
@@ -973,6 +1191,28 @@
{ path: "第1章 监管信息/CH1.9 产品申报前沟通的说明.doc", type: "DOC", pages: "待复核", confidence: "低", chapter: "CH1.9", name: "沟通说明", status: "待复核", hit: "是" },
{ path: "说明书/目标产品说明书.docx", type: "DOCX", pages: "18", confidence: "精确", chapter: "CH3", name: "产品说明书", status: "已汇总", hit: "是" },
],
knowledgeBase: {
summary: [
{ label: "知识库资料数", value: "27", note: "法规、模板、示例资料统一管理" },
{ label: "待入库任务", value: "3", note: "含 1 个法规包更新和 2 个业务资料" },
{ label: "有效切片数", value: "486", note: "支持 RAG 召回与规则辅助解释" },
{ label: "启用规则包", value: "4", note: "注册审核与一致性规则均已启用" }
],
uploadQueue: [
{ name: "体外诊断试剂注册申报资料要求及说明_2026修订版.docx", type: "法规资料", owner: "法规管理员", state: "文档解析中", detail: "已上传,正在提取章节标题与法规条款。" },
{ name: "说明书模板映射补充包.zip", type: "模板资料", owner: "数据治理专员", state: "解析完成", detail: "已识别 12 个占位符映射,待进入数据处理。" },
{ name: "注册申报常见问题汇编.docx", type: "业务知识", owner: "注册经理", state: "数据处理中", detail: "正在生成切片、关键词和召回标签。" },
{ name: "临床评价参考示例.pdf", type: "示例资料", owner: "医学专员", state: "处理完成", detail: "已入知识库并开放检索使用。" }
],
cards: [
{ title: "法规规则包", value: "4", desc: "维护完整性检查、一致性判断和风险等级规则。", actions: ["新增规则包", "上传新版", "停用旧版"] },
{ title: "RAG 文档源", value: "27", desc: "手动上传法规原文、模板文档、示例资料和内部 SOP。", actions: ["上传文档", "编辑标签", "重新入库"] },
{ title: "切片与召回", value: "486", desc: "查看切片状态、命中场景、阈值和是否可召回。", actions: ["新增切片", "拆分切片", "重建向量"] },
{ title: "字段 Schema", value: "38", desc: "统一字段标准、是否可回填、强一致约束和来源优先级。", actions: ["新增字段", "编辑字段", "复制版本"] },
{ title: "模板与映射", value: "9", desc: "维护 Word 模板、占位符映射和导出阻断约束。", actions: ["上传模板", "编辑映射", "预览模板"] },
{ title: "通知与责任人", value: "12", desc: "维护责任角色映射、飞书群聊、机器人和消息模板。", actions: ["新增映射", "发送测试", "查看回执"] }
]
},
completeness: {
summary: [
{ label: "必交项", value: "8" },
@@ -1263,6 +1503,7 @@
const navConfig = [
{ id: "workspace", name: "审核任务工作台", subtitle: "7 个流程任务卡片和执行状态" },
{ id: "knowledge", name: "知识库管理页", subtitle: "手动上传资料、入库状态、治理动作" },
{ id: "import", name: "资料包导入页", subtitle: "上传、目录、页数、章节点" },
{ id: "completeness", name: "法规完整性检查页", subtitle: "缺失项、错放项、法规依据" },
{ id: "fields", name: "字段抽取与字段池页", subtitle: "字段表、来源、置信度、待复核" },
@@ -1279,6 +1520,7 @@
const pageTitle = document.getElementById("pageTitle");
const governanceDrawer = document.getElementById("governanceDrawer");
const overlay = document.getElementById("overlay");
let activePageId = "workspace";
function metricCards(items) {
return `<div class="metric-grid">${items.map(item => `
@@ -1373,7 +1615,76 @@
`;
}
function renderProcessFlow(flowKey) {
const items = data.processFlows[flowKey] || [];
return `
<div class="phase-board">
${items.map(item => `
<div class="phase-card ${item.state}">
<div class="phase-head">
<strong>${item.title}</strong>
<span class="phase-state">${item.stateText}</span>
</div>
<span class="tag ${item.state === "blocked" ? "high" : item.state === "running" ? "medium" : item.state === "completed" ? "low" : ""}">
${data.taskStatusMap[item.state] || item.stateText}
</span>
<div class="phase-note">${item.note}</div>
</div>
`).join("")}
</div>
`;
}
function buildLiveTasks(currentPageId) {
return data.tasks.map(task => {
const flow = data.processFlows[task.id];
if (!flow) {
return task;
}
const blocked = flow.find(item => item.state === "blocked");
const running = currentPageId === task.id ? flow.find(item => item.state === "running") : null;
const pendingCount = flow.filter(item => item.state === "pending").length;
const completedCount = flow.filter(item => item.state === "completed").length;
if (blocked) {
return {
...task,
status: "已阻断",
metric: blocked.stateText,
detail: blocked.note
};
}
if (running) {
return {
...task,
status: running.stateText,
metric: `${completedCount}/${flow.length} 已完成`,
detail: running.note
};
}
if (pendingCount === 0) {
return {
...task,
status: "已完成",
metric: `${flow.length}/${flow.length} 已完成`,
detail: task.detail
};
}
return {
...task,
status: "待执行",
metric: `${completedCount}/${flow.length} 已完成`,
detail: task.detail
};
});
}
function renderWorkspacePage() {
const liveTasks = buildLiveTasks(activePageId);
return `
<section class="page active" data-page="workspace">
<div class="hero">
@@ -1413,17 +1724,20 @@
<div class="section-title">
<div>
<h3>七个流程任务卡片</h3>
<p>点击卡片可以直接切换到对应页面。</p>
<p>点击卡片切换流程页面后,状态会实时更新为当前动作。</p>
</div>
</div>
<div class="task-grid">
${data.tasks.map(task => `
<div class="task-card ${task.id === "risk" ? "active" : ""}" data-page-jump="${task.id}">
<span class="tag ${task.status === "已阻断" ? "high" : ""}">${task.status}</span>
${liveTasks.map(task => `
<div class="task-card ${task.id === activePageId ? "active" : ""}" data-page-jump="${task.id}">
<div class="task-status-line">
<span class="tag ${task.status === "已阻断" ? "high" : task.status.includes("中") ? "medium" : ""}">${task.status}</span>
<span class="muted">${task.metric}</span>
</div>
<h4>${task.name}</h4>
<p>${task.detail}</p>
<div class="task-note">${task.detail}</div>
<div class="task-meta">
<span>${task.metric}</span>
<span>进入流程</span>
<span>查看详情</span>
</div>
</div>
@@ -1521,6 +1835,98 @@
`;
}
function renderKnowledgePage() {
return `
<section class="page" data-page="knowledge">
${metricCards(data.knowledgeBase.summary)}
<div class="hero">
<div class="panel">
<div class="section-title">
<div>
<h3>知识库管理</h3>
<p>手动上传法规、模板、示例资料和内部业务知识,统一进入 Agent 可用知识库。</p>
</div>
<button class="secondary-btn" data-open-governance="sources">打开治理中心</button>
</div>
<div class="upload-zone">
<strong>手动上传知识库资料</strong>
<div class="lead">支持批量上传 <code>pdf / docx / doc / zip</code>,并按“法规资料 / 模板资料 / 示例资料 / 内部 SOP”进行分类。</div>
<div class="split-actions">
<button class="primary-btn">上传法规资料</button>
<button class="secondary-btn">上传业务资料</button>
<button class="secondary-btn">导入模板压缩包</button>
</div>
</div>
</div>
<div class="panel">
<div class="section-title">
<div>
<h3>入库处理状态</h3>
<p>通过卡片实时展示每份资料的处理动作。</p>
</div>
</div>
<div class="upload-list">
${data.knowledgeBase.uploadQueue.map(item => `
<div class="upload-item">
<div>
<strong>${item.name}</strong>
<div class="muted">${item.type} · 责任人:${item.owner}<br />${item.detail}</div>
</div>
<span class="tag ${item.state.includes("处理中") || item.state.includes("解析中") ? "medium" : item.state.includes("完成") ? "low" : ""}">${item.state}</span>
</div>
`).join("")}
</div>
</div>
</div>
<div class="panel">
<div class="section-title">
<div>
<h3>知识库能力卡片</h3>
<p>原有治理内容收拢为卡片式入口,适合演示 Agent 依赖的知识管理能力。</p>
</div>
</div>
<div class="knowledge-grid">
${data.knowledgeBase.cards.map(card => `
<div class="knowledge-card">
<div class="muted">${card.title}</div>
<div class="knowledge-stat">${card.value}</div>
<p>${card.desc}</p>
<div class="knowledge-actions">
${card.actions.map(action => `<button class="mini-btn">${action}</button>`).join("")}
</div>
</div>
`).join("")}
</div>
</div>
<div class="two-col" style="margin-top: 18px;">
<div class="panel">
<div class="section-title">
<div>
<h3>入库动作流</h3>
<p>模拟上传后的后台流水线动作。</p>
</div>
</div>
${renderProcessFlow("import")}
</div>
<div class="panel">
<div class="section-title">
<div>
<h3>Agent 如何使用知识库</h3>
<p>上传并入库后,后续页面会自动引用这些知识进行解释和核查。</p>
</div>
</div>
<div class="check-list">
<div class="check-item">法规资料进入 RAG 文档源,用于完整性检查时的法规依据解释。</div>
<div class="check-item">模板资料进入模板映射中心,用于 Word 回填与导出拦截。</div>
<div class="check-item">业务示例进入示例知识库,用于字段抽取提示和冲突解释增强。</div>
<div class="check-item">责任人和通知配置会驱动飞书通知中的 @ 与 Web 详情链接。</div>
</div>
</div>
</div>
</section>
`;
}
function renderImportPage() {
return `
<section class="page" data-page="import">
@@ -1553,21 +1959,10 @@
<div class="section-title">
<div>
<h3>处理流水线</h3>
<p>展示导入后每个环节的处理状态。</p>
<p>进入本流程后,状态卡片会切换到当前执行动作。</p>
</div>
</div>
<div class="timeline">
${data.importTimeline.map((item, idx) => `
<div class="timeline-step">
<div class="step-no">${idx + 1}</div>
<div>
<strong>${item.step}</strong>
<div class="muted" style="margin-top: 6px;">${item.detail}</div>
</div>
<span class="tag ${item.status.includes("部分") ? "medium" : ""}">${item.status}</span>
</div>
`).join("")}
</div>
${renderProcessFlow("import")}
</div>
<div class="panel">
<div class="section-title">
@@ -2142,6 +2537,7 @@
const pageTemplates = {
workspace: renderWorkspacePage,
knowledge: renderKnowledgePage,
import: renderImportPage,
completeness: renderCompletenessPage,
fields: renderFieldsPage,
@@ -2206,6 +2602,8 @@
}
function setActivePage(pageId) {
activePageId = pageId;
renderPages();
document.querySelectorAll(".page").forEach(page => {
page.classList.toggle("active", page.dataset.page === pageId);
});
@@ -2275,34 +2673,34 @@
panel.classList.toggle("active", panel.dataset.governancePanel === id);
});
}
if (event.target.closest("#draftBtn")) {
document.getElementById("downloadBox").innerHTML = `
<div>
<strong>注册申报表格_回填草稿_v2.docx</strong>
<div class="muted" style="margin-top: 8px;">状态:草稿已重新生成 · 版式校验:通过 · 时间2026-06-03 10:42</div>
</div>
<button class="primary-btn">下载新草稿</button>
`;
}
if (event.target.closest("#formalBtn")) {
alert("正式导出已被阻断:当前批次存在高风险和冲突字段,请先完成整改。");
}
if (event.target.closest("#sendBtn")) {
document.getElementById("receiptBox").className = "alert success";
document.getElementById("receiptBox").innerHTML = `
已发送至飞书群聊 oc_demo_chat<br />
message_id${data.notification.receipt.id}<br />
sent_at${data.notification.receipt.time}
`;
}
});
document.getElementById("openDrawerBtn").addEventListener("click", () => openGovernance("rules"));
document.getElementById("closeDrawerBtn").addEventListener("click", closeGovernance);
overlay.addEventListener("click", closeGovernance);
document.getElementById("draftBtn")?.addEventListener("click", () => {
document.getElementById("downloadBox").innerHTML = `
<div>
<strong>注册申报表格_回填草稿_v2.docx</strong>
<div class="muted" style="margin-top: 8px;">状态:草稿已重新生成 · 版式校验:通过 · 时间2026-06-03 10:42</div>
</div>
<button class="primary-btn">下载新草稿</button>
`;
});
document.getElementById("formalBtn")?.addEventListener("click", () => {
alert("正式导出已被阻断:当前批次存在高风险和冲突字段,请先完成整改。");
});
document.getElementById("sendBtn")?.addEventListener("click", () => {
document.getElementById("receiptBox").className = "alert success";
document.getElementById("receiptBox").innerHTML = `
已发送至飞书群聊 oc_demo_chat<br />
message_id${data.notification.receipt.id}<br />
sent_at${data.notification.receipt.time}
`;
});
</script>
</body>
</html>