chore: save current workspace progress
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
# EMPlanner 架构梳理与 LS 优化启动日报
|
||||
|
||||
> 日期:2026-08-04
|
||||
> 模块:emplanner
|
||||
> 证据原则:已验证事实、静态分析、对话发现和待验证风险必须分开表达。
|
||||
|
||||
## 1. 今日结论摘要
|
||||
|
||||
已依据 EMPlanner 当前说明和代码目录明确轨迹规划的分层架构与模块边界;LS 横向优化为当前开展方向,尚未在本日报中宣称完成或通过专项回归。
|
||||
|
||||
## 2. 今日完成的工作
|
||||
|
||||
- **确定 EMPlanner 轨迹规划分层架构**:明确从请求快照、方向段/Frenet 投影和静态走廊,到 LS、ST、轨迹装配、世界空间复核及上层滚动执行的职责划分。(证据:ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md)
|
||||
- **明确规划器与执行侧边界**:EMPlanner 仅产出经校验的时间轨迹;调度、轨迹交接、换向执行和控制命令由 TrajectoryExecution 承担。(证据:ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md、ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md)
|
||||
- **梳理 LS 优化的接口边界**:LS 使用 l、dl、ddl、dddl 变量,在静态走廊与车辆几何约束下求解;求解器通过 IQpSolver/QuadraticProgram 抽象接入。(证据:ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md、ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/)
|
||||
|
||||
## 3. 今日发现的问题
|
||||
|
||||
### 3.1 LS 横向优化处于开展阶段 `ls-optimization-in-progress`
|
||||
|
||||
- **证据等级**:对话发现
|
||||
- **实际现象**:当前已明确 LS 位于静态走廊之后、ST 之前,并承担横向路径优化与独立几何复核;本日报未取得本轮专项优化结果或回归输出。
|
||||
- **正确预期**:在不破坏静态走廊、导数、Frenet 分母和车辆曲率硬约束的前提下,形成可独立复核的横向候选轨迹。
|
||||
- **原因或当前假设**:优化参数、目标权重、收敛策略及专项场景的本轮验证证据尚待补充。
|
||||
- **影响**:LS 结果直接决定后续 ST 使用的实际 PathS 与轨迹可发布性,需先完成可行性和稳定性验证。
|
||||
|
||||
## 4. 问题如何被发现及证据
|
||||
|
||||
### ls-optimization-in-progress · LS 横向优化处于开展阶段
|
||||
|
||||
用户说明当前正在开展 LS 优化工作。
|
||||
|
||||
| 证据 | 引用 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| LS 架构说明 | ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md | 文档定义 LS SQP、独立横向几何复核及其与 ST 的数据边界。 |
|
||||
| 当前工作说明 | 用户需求(2026-08-04) | 当前开展 LS 优化工作。 |
|
||||
|
||||
## 5. 已采取的改善和验证结果
|
||||
|
||||
- **ls-optimization-in-progress / 架构与接口静态核对 / 通过**:README 与 Lateral 目录表明 LS 的数据流、变量及求解器边界已定义;未执行 LS 专项运行测试。
|
||||
- **EMPlanner 架构静态核对 / 通过**:核对 README、Lateral 目录和当日 EMPlanner 提交记录;本次未运行构建或自动化测试。
|
||||
|
||||
## 6. 尚未解决的风险与下一步
|
||||
|
||||
- **ls-optimization-in-progress**:确定 LS 优化目标、权重和约束调参方案。
|
||||
- **ls-optimization-in-progress**:针对典型前进、倒车及走廊边界场景执行专项回归。
|
||||
- **P0 / emplanner**:细化并实现 LS 优化策略;完成标志:横向优化目标、约束和候选筛选策略形成可审查实现。
|
||||
- **P1 / emplanner**:执行 LS 专项回归;完成标志:典型前进、倒车和窄走廊场景均有可复核的运行结果。
|
||||
- **P1 / emplanner**:联调 LS 到 ST 的 PathS 输入;完成标志:ST 仅消费 LS 已复核且严格递增的实际 PathS,并保留验证记录。
|
||||
|
||||
## 7. 证据索引
|
||||
|
||||
- **EMPlanner 架构说明**:ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md
|
||||
- **当日 EMPlanner 提交记录**:git log --since='2026-08-04 00:00' -- ClumsyPilot/ParkrobTrajplanner/EMPlanner
|
||||
- **LS/ST 观测计划**:docs/superpowers/plans/2026-08-04-em-observation-movementtest.md
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
<!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 {
|
||||
color-scheme: light;
|
||||
--ink: #142532;
|
||||
--muted: #60717c;
|
||||
--paper: #f3f6f5;
|
||||
--panel: #ffffff;
|
||||
--line: #ccd7d7;
|
||||
--teal: #0f6b70;
|
||||
--teal-soft: #dff1ef;
|
||||
--green: #24784c;
|
||||
--green-soft: #e6f4ea;
|
||||
--amber: #955d12;
|
||||
--amber-soft: #fff0d7;
|
||||
--red: #ad3541;
|
||||
--red-soft: #fbe8ea;
|
||||
--blue: #285f91;
|
||||
--blue-soft: #e3eff9;
|
||||
--shadow: 0 16px 44px rgba(27, 52, 60, .08);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: var(--paper); color: var(--ink); font: 16px/1.65 system-ui, -apple-system, "Segoe UI", sans-serif; }
|
||||
button { font: inherit; }
|
||||
button:focus-visible { outline: 3px solid #e7a628; outline-offset: 3px; }
|
||||
#daily-summary-app { width: min(1180px, 100%); margin: 0 auto; padding: clamp(18px, 4vw, 44px); }
|
||||
.hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 20px; align-items: end; border-bottom: 2px solid var(--ink); padding-bottom: 22px; }
|
||||
.eyebrow { margin: 0 0 5px; color: var(--teal); font-weight: 800; letter-spacing: .06em; }
|
||||
h1, h2, h3, p { margin-top: 0; }
|
||||
h1 { margin-bottom: 8px; font-size: clamp(1.8rem, 4vw, 2.65rem); line-height: 1.18; }
|
||||
h2 { font-size: clamp(1.25rem, 2.8vw, 1.65rem); line-height: 1.3; }
|
||||
h3 { font-size: 1.04rem; }
|
||||
.subtitle { max-width: 790px; margin-bottom: 0; color: var(--muted); }
|
||||
.date-chip { border: 1px solid var(--line); border-radius: 7px; padding: 7px 10px; background: var(--panel); font-weight: 800; white-space: nowrap; }
|
||||
.overview { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin: 22px 0; }
|
||||
.stat { border-top: 4px solid var(--teal); padding: 12px 4px 8px; }
|
||||
.stat:nth-child(2) { border-color: var(--red); }
|
||||
.stat:nth-child(3) { border-color: var(--green); }
|
||||
.stat:nth-child(4) { border-color: var(--amber); }
|
||||
.stat strong { display: block; font-size: 1.8rem; line-height: 1.1; }
|
||||
.stat span { color: var(--muted); }
|
||||
.workbench { display: grid; grid-template-columns: minmax(220px, 290px) minmax(0, 1fr); gap: 18px; align-items: start; }
|
||||
.navigator, .problem-lab { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); }
|
||||
.navigator { padding: 16px; }
|
||||
.navigator h2 { margin-bottom: 10px; }
|
||||
.evidence-filter-list, .issue-list, .solution-steps { display: grid; gap: 8px; }
|
||||
.evidence-filter-list { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-bottom: 16px; }
|
||||
.evidence-filter, .issue-button, .solution-step, .before-after-toggle, .step-control { min-height: 42px; border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; color: var(--ink); background: var(--panel); cursor: pointer; text-align: left; }
|
||||
.evidence-filter[aria-pressed="true"], .issue-button[aria-pressed="true"], .solution-step[aria-pressed="true"], .before-after-toggle[aria-pressed="true"] { border-color: var(--teal); color: #fff; background: var(--teal); }
|
||||
.issue-button small { display: block; margin-top: 2px; opacity: .76; }
|
||||
.problem-lab { overflow: hidden; }
|
||||
.problem-heading { padding: 20px clamp(16px, 3vw, 28px) 12px; border-bottom: 1px solid var(--line); }
|
||||
.badge { display: inline-flex; align-items: center; gap: 6px; margin-bottom: 7px; border: 1px solid currentColor; border-radius: 999px; padding: 3px 9px; font-size: .84rem; font-weight: 800; }
|
||||
.badge::before { content: "◆"; font-size: .72em; }
|
||||
.badge[data-level="已验证"] { color: var(--green); background: var(--green-soft); }
|
||||
.badge[data-level="静态分析"] { color: var(--amber); background: var(--amber-soft); }
|
||||
.badge[data-level="对话发现"] { color: var(--blue); background: var(--blue-soft); }
|
||||
.badge[data-level="待验证风险"], .badge[data-level="结论冲突"] { color: var(--red); background: var(--red-soft); }
|
||||
.problem-body { padding: 18px clamp(16px, 3vw, 28px) 26px; }
|
||||
.comparison { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.compare-panel { border-left: 4px solid var(--red); padding: 4px 0 4px 13px; }
|
||||
.compare-panel.expected { border-color: var(--green); }
|
||||
.compare-panel p { margin-bottom: 0; color: var(--muted); }
|
||||
.before-after-toggle { margin: 14px 0; text-align: center; }
|
||||
.cause-flow { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px; margin: 24px 0; }
|
||||
.cause-node { position: relative; min-height: 126px; border-top: 4px solid var(--blue); padding: 12px 4px 4px; }
|
||||
.cause-node:not(:last-child)::after { position: absolute; top: 45px; right: -14px; content: "→"; color: var(--muted); font-weight: 800; }
|
||||
.cause-node p { margin-bottom: 0; color: var(--muted); }
|
||||
.concept-note { border-left: 4px solid var(--amber); padding: 9px 12px; color: #6e4813; background: var(--amber-soft); }
|
||||
.solution-layout { display: grid; grid-template-columns: minmax(180px, .7fr) minmax(0, 1.3fr); gap: 16px; margin-top: 24px; }
|
||||
.solution-detail { min-height: 150px; border-left: 1px solid var(--line); padding-left: 18px; }
|
||||
.solution-detail p { color: var(--muted); }
|
||||
.step-controls { display: flex; gap: 8px; margin-top: 12px; }
|
||||
.step-control { text-align: center; }
|
||||
.validation-section, .roadmap-section { padding: 30px 0; border-bottom: 1px solid var(--line); }
|
||||
.validation-gates { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
|
||||
.validation-gate { border-top: 4px solid var(--line); padding: 12px 4px 4px; }
|
||||
.validation-gate[data-status="通过"], .validation-gate[data-status="完成"] { border-color: var(--green); }
|
||||
.validation-gate[data-status="失败"], .validation-gate[data-status="阻断"] { border-color: var(--red); }
|
||||
.validation-gate[data-status="待验证"] { border-color: var(--amber); }
|
||||
.validation-gate p { margin-bottom: 0; color: var(--muted); }
|
||||
.roadmap { display: grid; gap: 10px; }
|
||||
.roadmap-item { display: grid; grid-template-columns: 70px minmax(0, 1fr); gap: 12px; border-bottom: 1px solid var(--line); padding: 10px 0; }
|
||||
.roadmap-item strong:first-child { color: var(--teal); }
|
||||
.roadmap-item p { margin-bottom: 0; color: var(--muted); }
|
||||
.empty { color: var(--muted); font-style: italic; }
|
||||
footer { padding-top: 22px; color: var(--muted); font-size: .9rem; }
|
||||
@media (max-width: 820px) {
|
||||
.hero, .workbench, .solution-layout { grid-template-columns: 1fr; }
|
||||
.date-chip { justify-self: start; }
|
||||
.overview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.navigator { box-shadow: none; }
|
||||
.issue-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.solution-detail { border-top: 1px solid var(--line); border-left: 0; padding-top: 16px; padding-left: 0; }
|
||||
}
|
||||
@media (max-width: 560px) {
|
||||
.overview, .comparison, .cause-flow, .validation-gates, .issue-list { grid-template-columns: 1fr; }
|
||||
.cause-node:not(:last-child)::after { top: auto; right: auto; bottom: -17px; left: 50%; content: "↓"; }
|
||||
.roadmap-item { grid-template-columns: 52px minmax(0, 1fr); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { scroll-behavior: auto !important; transition: none !important; animation: none !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main id="daily-summary-app" data-selected-issue="">
|
||||
<header class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">开发日报 · 问题与改善交互说明</p>
|
||||
<h1 id="report-title">今日工作总结</h1>
|
||||
<p id="report-summary" class="subtitle"></p>
|
||||
</div>
|
||||
<span id="report-date" class="date-chip"></span>
|
||||
</header>
|
||||
|
||||
<section class="overview" aria-label="今日工作总览">
|
||||
<div class="stat"><strong id="achievement-count">0</strong><span>完成事项</span></div>
|
||||
<div class="stat"><strong id="issue-count">0</strong><span>发现问题</span></div>
|
||||
<div class="stat"><strong id="validation-count">0</strong><span>验证记录</span></div>
|
||||
<div class="stat"><strong id="next-count">0</strong><span>下一步</span></div>
|
||||
</section>
|
||||
|
||||
<div class="workbench">
|
||||
<aside class="navigator">
|
||||
<h2>选择问题</h2>
|
||||
<div id="evidence-filters" class="evidence-filter-list" aria-label="按证据等级筛选">
|
||||
<button type="button" class="evidence-filter" data-level="全部" aria-pressed="true">全部</button>
|
||||
</div>
|
||||
<div id="issue-list" class="issue-list"></div>
|
||||
</aside>
|
||||
|
||||
<section class="problem-lab" aria-live="polite">
|
||||
<div class="problem-heading">
|
||||
<span id="issue-evidence" class="badge" data-level="对话发现">对话发现</span>
|
||||
<h2 id="issue-title">暂无问题</h2>
|
||||
<p id="issue-meta" class="subtitle"></p>
|
||||
</div>
|
||||
<div class="problem-body">
|
||||
<button id="before-after-toggle" type="button" class="before-after-toggle" aria-pressed="false">切换为改善后视图</button>
|
||||
<div class="comparison">
|
||||
<article class="compare-panel">
|
||||
<h3 id="actual-label">实际发生</h3>
|
||||
<p id="issue-actual"></p>
|
||||
</article>
|
||||
<article class="compare-panel expected">
|
||||
<h3>正确预期</h3>
|
||||
<p id="issue-expected"></p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div id="cause-flow" class="cause-flow" aria-label="问题发现与原因链">
|
||||
<article class="cause-node"><h3>问题发现</h3><p id="issue-discovery"></p></article>
|
||||
<article class="cause-node"><h3>实际现象</h3><p id="issue-symptom"></p></article>
|
||||
<article class="cause-node"><h3>原因或假设</h3><p id="issue-cause"></p></article>
|
||||
<article class="cause-node"><h3>影响范围</h3><p id="issue-impact"></p></article>
|
||||
</div>
|
||||
<p id="concept-note" class="concept-note">概念示意:没有数值证据时不绘制虚构比例。</p>
|
||||
|
||||
<div class="solution-layout">
|
||||
<div>
|
||||
<h3>改善步骤</h3>
|
||||
<div id="solution-steps" class="solution-steps"></div>
|
||||
</div>
|
||||
<article class="solution-detail">
|
||||
<h3 id="solution-title">尚未记录改善</h3>
|
||||
<p id="solution-result">当前只保留问题和下一步,不把候选方案写成已完成。</p>
|
||||
<div class="step-controls">
|
||||
<button id="previous-solution" type="button" class="step-control">上一步</button>
|
||||
<button id="next-solution" type="button" class="step-control">下一步</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="validation-section">
|
||||
<h2>验证门</h2>
|
||||
<div id="validation-gates" class="validation-gates"></div>
|
||||
</section>
|
||||
|
||||
<section class="roadmap-section">
|
||||
<h2>下一步路线图</h2>
|
||||
<div id="roadmap" class="roadmap"></div>
|
||||
</section>
|
||||
|
||||
<footer>本页与 Markdown 主报告来自同一结构化事实源。键盘左右方向键可切换问题。</footer>
|
||||
|
||||
<template id="issue-button-template"><button type="button" class="issue-button" aria-pressed="false"><span></span><small></small></button></template>
|
||||
<template id="evidence-filter-template"><button type="button" class="evidence-filter" aria-pressed="false"></button></template>
|
||||
<template id="solution-step-template"><button type="button" class="solution-step" aria-pressed="false"></button></template>
|
||||
<template id="validation-gate-template"><article class="validation-gate"><h3></h3><strong></strong><p></p></article></template>
|
||||
<template id="roadmap-item-template"><article class="roadmap-item"><strong></strong><div><h3></h3><p></p></div></article></template>
|
||||
</main>
|
||||
|
||||
<script id="report-data" type="application/json">{"date":"2026-08-04","title":"EMPlanner 架构梳理与 LS 优化启动日报","summary":"已依据 EMPlanner 当前说明和代码目录明确轨迹规划的分层架构与模块边界;LS 横向优化为当前开展方向,尚未在本日报中宣称完成或通过专项回归。","modules":["emplanner"],"achievements":[{"title":"确定 EMPlanner 轨迹规划分层架构","detail":"明确从请求快照、方向段/Frenet 投影和静态走廊,到 LS、ST、轨迹装配、世界空间复核及上层滚动执行的职责划分。","evidence":["ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md"]},{"title":"明确规划器与执行侧边界","detail":"EMPlanner 仅产出经校验的时间轨迹;调度、轨迹交接、换向执行和控制命令由 TrajectoryExecution 承担。","evidence":["ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md","ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md"]},{"title":"梳理 LS 优化的接口边界","detail":"LS 使用 l、dl、ddl、dddl 变量,在静态走廊与车辆几何约束下求解;求解器通过 IQpSolver/QuadraticProgram 抽象接入。","evidence":["ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md","ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/"]}],"issues":[{"id":"ls-optimization-in-progress","title":"LS 横向优化处于开展阶段","module":"emplanner","evidence_level":"对话发现","discovery":"用户说明当前正在开展 LS 优化工作。","actual":"当前已明确 LS 位于静态走廊之后、ST 之前,并承担横向路径优化与独立几何复核;本日报未取得本轮专项优化结果或回归输出。","expected":"在不破坏静态走廊、导数、Frenet 分母和车辆曲率硬约束的前提下,形成可独立复核的横向候选轨迹。","cause":"优化参数、目标权重、收敛策略及专项场景的本轮验证证据尚待补充。","impact":"LS 结果直接决定后续 ST 使用的实际 PathS 与轨迹可发布性,需先完成可行性和稳定性验证。","improvements":[],"validation":[{"name":"架构与接口静态核对","status":"通过","detail":"README 与 Lateral 目录表明 LS 的数据流、变量及求解器边界已定义;未执行 LS 专项运行测试。"}],"next_steps":["确定 LS 优化目标、权重和约束调参方案。","针对典型前进、倒车及走廊边界场景执行专项回归。"],"evidence":[{"label":"LS 架构说明","reference":"ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md","result":"文档定义 LS SQP、独立横向几何复核及其与 ST 的数据边界。"},{"label":"当前工作说明","reference":"用户需求(2026-08-04)","result":"当前开展 LS 优化工作。"}],"conceptual_visual":true}],"validations":[{"name":"EMPlanner 架构静态核对","status":"通过","detail":"核对 README、Lateral 目录和当日 EMPlanner 提交记录;本次未运行构建或自动化测试。"}],"next_steps":[{"priority":"P0","module":"emplanner","action":"细化并实现 LS 优化策略","done_when":"横向优化目标、约束和候选筛选策略形成可审查实现。"},{"priority":"P1","module":"emplanner","action":"执行 LS 专项回归","done_when":"典型前进、倒车和窄走廊场景均有可复核的运行结果。"},{"priority":"P1","module":"emplanner","action":"联调 LS 到 ST 的 PathS 输入","done_when":"ST 仅消费 LS 已复核且严格递增的实际 PathS,并保留验证记录。"}],"sources":[{"label":"EMPlanner 架构说明","reference":"ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md"},{"label":"当日 EMPlanner 提交记录","reference":"git log --since='2026-08-04 00:00' -- ClumsyPilot/ParkrobTrajplanner/EMPlanner"},{"label":"LS/ST 观测计划","reference":"docs/superpowers/plans/2026-08-04-em-observation-movementtest.md"}]}</script>
|
||||
<script>'use strict';
|
||||
globalThis.DailySummaryVisuals = (() => {
|
||||
const registry = new Map();
|
||||
|
||||
function register(name, renderer) {
|
||||
if (!/^[a-z0-9][a-z0-9-]+$/.test(name) || typeof renderer !== 'function') {
|
||||
throw new TypeError('invalid visualization adapter');
|
||||
}
|
||||
registry.set(name, renderer);
|
||||
}
|
||||
|
||||
function select(name) {
|
||||
return registry.get(name) || registry.get('composite-scene');
|
||||
}
|
||||
|
||||
function render(view, root, context) {
|
||||
const renderer = select(view.adapter);
|
||||
if (!renderer) throw new Error('composite-scene adapter is not registered');
|
||||
return renderer(view, root, context);
|
||||
}
|
||||
|
||||
return { register, select, render };
|
||||
})();
|
||||
</script>
|
||||
<script> 'use strict';
|
||||
const data = JSON.parse(document.getElementById('report-data').textContent);
|
||||
const app = document.getElementById('daily-summary-app');
|
||||
const issues = Array.isArray(data.issues) ? data.issues : [];
|
||||
let filteredIssues = issues.slice();
|
||||
let selectedIssueId = filteredIssues[0]?.id || '';
|
||||
let selectedSolutionIndex = 0;
|
||||
let showingAfter = false;
|
||||
|
||||
function setText(id, value) {
|
||||
document.getElementById(id).textContent = value == null || value === '' ? '无已记录项' : String(value);
|
||||
}
|
||||
|
||||
function countIssueValidations() {
|
||||
return issues.reduce((total, issue) => total + (issue.validation?.length || 0), 0);
|
||||
}
|
||||
|
||||
function renderHeader() {
|
||||
setText('report-title', data.title);
|
||||
setText('report-summary', data.summary);
|
||||
setText('report-date', data.date);
|
||||
setText('achievement-count', data.achievements?.length || 0);
|
||||
setText('issue-count', issues.length);
|
||||
setText('validation-count', (data.validations?.length || 0) + countIssueValidations());
|
||||
setText('next-count', data.next_steps?.length || 0);
|
||||
}
|
||||
|
||||
function renderFilters() {
|
||||
const root = document.getElementById('evidence-filters');
|
||||
const levels = ['全部', ...new Set(issues.map((issue) => issue.evidence_level))];
|
||||
root.replaceChildren();
|
||||
levels.forEach((level, index) => {
|
||||
const button = document.getElementById('evidence-filter-template').content.firstElementChild.cloneNode(true);
|
||||
button.textContent = level;
|
||||
button.dataset.level = level;
|
||||
button.setAttribute('aria-pressed', String(index === 0));
|
||||
button.addEventListener('click', () => applyFilter(level, button));
|
||||
root.appendChild(button);
|
||||
});
|
||||
}
|
||||
|
||||
function applyFilter(level, activeButton) {
|
||||
document.querySelectorAll('.evidence-filter').forEach((button) => button.setAttribute('aria-pressed', String(button === activeButton)));
|
||||
filteredIssues = level === '全部' ? issues.slice() : issues.filter((issue) => issue.evidence_level === level);
|
||||
selectedIssueId = filteredIssues.some((issue) => issue.id === selectedIssueId) ? selectedIssueId : (filteredIssues[0]?.id || '');
|
||||
renderIssueList();
|
||||
renderSelectedIssue();
|
||||
}
|
||||
|
||||
function renderIssueList() {
|
||||
const root = document.getElementById('issue-list');
|
||||
root.replaceChildren();
|
||||
filteredIssues.forEach((issue) => {
|
||||
const button = document.getElementById('issue-button-template').content.firstElementChild.cloneNode(true);
|
||||
button.dataset.issueId = issue.id;
|
||||
button.querySelector('span').textContent = issue.title;
|
||||
button.querySelector('small').textContent = `${issue.evidence_level} · ${issue.module}`;
|
||||
button.setAttribute('aria-pressed', String(issue.id === selectedIssueId));
|
||||
button.addEventListener('click', () => selectIssue(issue.id));
|
||||
root.appendChild(button);
|
||||
});
|
||||
if (!filteredIssues.length) {
|
||||
const empty = document.createElement('p');
|
||||
empty.className = 'empty';
|
||||
empty.textContent = '该证据等级下没有问题。';
|
||||
root.appendChild(empty);
|
||||
}
|
||||
}
|
||||
|
||||
function selectIssue(issueId) {
|
||||
selectedIssueId = issueId;
|
||||
selectedSolutionIndex = 0;
|
||||
showingAfter = false;
|
||||
renderIssueList();
|
||||
renderSelectedIssue();
|
||||
}
|
||||
|
||||
function currentIssue() {
|
||||
return issues.find((issue) => issue.id === selectedIssueId);
|
||||
}
|
||||
|
||||
function renderSelectedIssue() {
|
||||
const issue = currentIssue();
|
||||
app.dataset.selectedIssue = issue?.id || '';
|
||||
if (!issue) {
|
||||
setText('issue-title', '暂无问题');
|
||||
setText('issue-meta', '当前筛选条件没有可展示的问题。');
|
||||
return;
|
||||
}
|
||||
const badge = document.getElementById('issue-evidence');
|
||||
badge.textContent = issue.evidence_level;
|
||||
badge.dataset.level = issue.evidence_level;
|
||||
setText('issue-title', issue.title);
|
||||
setText('issue-meta', `${issue.id} · ${issue.module}`);
|
||||
setText('issue-discovery', issue.discovery);
|
||||
setText('issue-symptom', issue.actual);
|
||||
setText('issue-cause', issue.cause);
|
||||
setText('issue-impact', issue.impact);
|
||||
setText('issue-actual', showingAfter ? improvementSummary(issue) : issue.actual);
|
||||
setText('actual-label', showingAfter ? '改善后状态' : '实际发生');
|
||||
setText('issue-expected', issue.expected);
|
||||
const toggle = document.getElementById('before-after-toggle');
|
||||
toggle.setAttribute('aria-pressed', String(showingAfter));
|
||||
toggle.textContent = showingAfter ? '返回改善前视图' : '切换为改善后视图';
|
||||
document.getElementById('concept-note').hidden = issue.conceptual_visual === false;
|
||||
renderSolutions(issue);
|
||||
renderValidations(issue);
|
||||
renderRoadmap(issue);
|
||||
}
|
||||
|
||||
function improvementSummary(issue) {
|
||||
if (!issue.improvements?.length) return '尚未记录已完成改善;保持原问题状态。';
|
||||
return issue.improvements.map((item) => `${item.status}:${item.action};${item.result}`).join(';');
|
||||
}
|
||||
|
||||
function renderSolutions(issue) {
|
||||
const steps = issue.improvements?.length ? issue.improvements : issue.next_steps.map((step) => ({ action: step, result: '待执行与验证', status: '下一步' }));
|
||||
const root = document.getElementById('solution-steps');
|
||||
root.replaceChildren();
|
||||
steps.forEach((step, index) => {
|
||||
const button = document.getElementById('solution-step-template').content.firstElementChild.cloneNode(true);
|
||||
button.textContent = `${index + 1}. ${step.action}`;
|
||||
button.setAttribute('aria-pressed', String(index === selectedSolutionIndex));
|
||||
button.addEventListener('click', () => { selectedSolutionIndex = index; renderSolutions(issue); });
|
||||
root.appendChild(button);
|
||||
});
|
||||
const selected = steps[selectedSolutionIndex];
|
||||
setText('solution-title', selected?.action || '尚未记录改善');
|
||||
setText('solution-result', selected ? `${selected.status} · ${selected.result}` : '当前只保留问题,不虚构解决结果。');
|
||||
document.getElementById('previous-solution').disabled = !selected || selectedSolutionIndex === 0;
|
||||
document.getElementById('next-solution').disabled = !selected || selectedSolutionIndex === steps.length - 1;
|
||||
}
|
||||
|
||||
function renderValidations(issue) {
|
||||
const values = [...(issue.validation || []), ...(data.validations || [])];
|
||||
const root = document.getElementById('validation-gates');
|
||||
root.replaceChildren();
|
||||
values.forEach((item) => {
|
||||
const gate = document.getElementById('validation-gate-template').content.firstElementChild.cloneNode(true);
|
||||
gate.dataset.status = item.status;
|
||||
gate.querySelector('h3').textContent = item.name;
|
||||
gate.querySelector('strong').textContent = item.status;
|
||||
gate.querySelector('p').textContent = item.detail;
|
||||
root.appendChild(gate);
|
||||
});
|
||||
if (!values.length) root.appendChild(emptyNode('尚未记录验证结果。'));
|
||||
}
|
||||
|
||||
function renderRoadmap(issue) {
|
||||
const root = document.getElementById('roadmap');
|
||||
root.replaceChildren();
|
||||
const issueSteps = (issue.next_steps || []).map((action) => ({ priority: '问题', module: issue.module, action, done_when: '完成专门验证后关闭。' }));
|
||||
[...issueSteps, ...(data.next_steps || [])].forEach((item) => {
|
||||
const row = document.getElementById('roadmap-item-template').content.firstElementChild.cloneNode(true);
|
||||
row.querySelector('strong').textContent = item.priority;
|
||||
row.querySelector('h3').textContent = `${item.module} · ${item.action}`;
|
||||
row.querySelector('p').textContent = `完成标志:${item.done_when}`;
|
||||
root.appendChild(row);
|
||||
});
|
||||
if (!root.children.length) root.appendChild(emptyNode('尚未记录下一步。'));
|
||||
}
|
||||
|
||||
function emptyNode(message) {
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.className = 'empty';
|
||||
paragraph.textContent = message;
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
function moveIssue(offset) {
|
||||
if (!filteredIssues.length) return;
|
||||
const index = Math.max(0, filteredIssues.findIndex((issue) => issue.id === selectedIssueId));
|
||||
const next = (index + offset + filteredIssues.length) % filteredIssues.length;
|
||||
selectIssue(filteredIssues[next].id);
|
||||
}
|
||||
|
||||
document.getElementById('before-after-toggle').addEventListener('click', () => { showingAfter = !showingAfter; renderSelectedIssue(); });
|
||||
document.getElementById('previous-solution').addEventListener('click', () => { if (selectedSolutionIndex > 0) { selectedSolutionIndex -= 1; renderSelectedIssue(); } });
|
||||
document.getElementById('next-solution').addEventListener('click', () => { const issue = currentIssue(); const count = issue?.improvements?.length || issue?.next_steps?.length || 0; if (selectedSolutionIndex < count - 1) { selectedSolutionIndex += 1; renderSelectedIssue(); } });
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.target.matches('button, input, select, textarea, [contenteditable="true"]')) return;
|
||||
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
moveIssue(event.key === 'ArrowLeft' ? -1 : 1);
|
||||
}
|
||||
});
|
||||
|
||||
renderHeader();
|
||||
renderFilters();
|
||||
renderIssueList();
|
||||
renderSelectedIssue();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user