ui-dashboard.html 10.7 KB
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" />
<title>DevLoop Dashboard · LemonBoss PRD</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,500;9..144,700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="assets/shell.css" />
</head>
<body data-page="ui-dashboard.html">
<div class="shell">
  <aside id="sidebar"></aside>
  <main class="page">
    <header class="page-head">
      <div class="crumb"><span class="pill">07 · UI</span><span class="sep">/</span><span>DevLoop Dashboard</span></div>
      <h1>DevLoop Dashboard</h1>
      <p class="deck">前端只有一个新页面:<code>DevLoopDashboard.vue</code>。复用 <code>PipelineEditor.vue</code> 的 DAG 渲染层,再套一个三层 Loop 状态条。</p>
    </header>
    <article class="body">

      <section>
        <h2>Wireframe</h2>
        <div class="mock">
          <div class="topbar">
            <span class="dot" style="background:#FF5F57"></span>
            <span class="dot" style="background:#FEBC2E"></span>
            <span class="dot" style="background:#28C840"></span>
            <span class="url">localhost:5173/devloop/p-731042</span>
          </div>

          <svg viewBox="0 0 1100 540" width="100%" role="img" aria-label="DevLoop Dashboard wireframe">
            <defs>
              <linearGradient id="card-g" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stop-color="#fff"/>
                <stop offset="100%" stop-color="#FAF7F1"/>
              </linearGradient>
              <marker id="arrU" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
                <path d="M0,0 L10,5 L0,10 Z" fill="#0E0E10"/>
              </marker>
            </defs>
            <rect x="0" y="0" width="1100" height="60" fill="#0E0E10"/>
            <text x="20" y="38" font-family="Fraunces" font-size="22" fill="#FAF7F1">Project p-731042 · Auto Auth System</text>
            <rect x="900" y="18" width="180" height="28" rx="14" fill="#6E48C3"/>
            <text x="990" y="37" font-family="JetBrains Mono" font-size="11" fill="#fff" text-anchor="middle">+ NEW REQUIREMENT</text>

            <g font-family="JetBrains Mono" font-size="11">
              <rect x="20" y="80" width="340" height="60" rx="6" fill="url(#card-g)" stroke="#0E8C5A"/>
              <text x="34" y="100" fill="#0E8C5A" font-weight="700">L1 · UNIT</text>
              <text x="34" y="120" fill="#0E0E10">8/12 passed · 1 testing · 3 pending</text>

              <rect x="380" y="80" width="340" height="60" rx="6" fill="url(#card-g)" stroke="#C77C2C"/>
              <text x="394" y="100" fill="#C77C2C" font-weight="700">L2 · INTEGRATION</text>
              <text x="394" y="120" fill="#0E0E10">last run · failed at 06:21 · re-queued</text>

              <rect x="740" y="80" width="340" height="60" rx="6" fill="url(#card-g)" stroke="#6E48C3"/>
              <text x="754" y="100" fill="#6E48C3" font-weight="700">L3 · HUMAN</text>
              <text x="754" y="120" fill="#0E0E10">rev 3 · awaiting next feedback</text>
            </g>

            <text x="20" y="170" font-family="JetBrains Mono" font-size="11" fill="#3B3B40">UNITS DAG</text>
            <g font-family="JetBrains Mono" font-size="10">
              <rect x="40"  y="190" width="120" height="44" rx="4" fill="#E2F2EA" stroke="#0E8C5A"/><text x="100" y="208" text-anchor="middle">auth-be</text><text x="100" y="222" text-anchor="middle" fill="#0E8C5A">passed</text>
              <rect x="220" y="190" width="120" height="44" rx="4" fill="#FFF6CC" stroke="#8a6b00"/><text x="280" y="208" text-anchor="middle">auth-fe</text><text x="280" y="222" text-anchor="middle" fill="#8a6b00">testing</text>
              <rect x="400" y="190" width="120" height="44" rx="4" fill="#E2F2EA" stroke="#0E8C5A"/><text x="460" y="208" text-anchor="middle">profile-be</text><text x="460" y="222" text-anchor="middle" fill="#0E8C5A">passed</text>
              <rect x="220" y="280" width="120" height="44" rx="4" fill="#fff" stroke="#0E0E10"/><text x="280" y="298" text-anchor="middle">oauth-be</text><text x="280" y="312" text-anchor="middle" fill="#3B3B40">pending</text>
              <rect x="400" y="280" width="120" height="44" rx="4" fill="#fff" stroke="#0E0E10"/><text x="460" y="298" text-anchor="middle">oauth-fe</text><text x="460" y="312" text-anchor="middle" fill="#3B3B40">pending</text>
              <rect x="580" y="190" width="120" height="44" rx="4" fill="#FCE7E2" stroke="#C7341A"/><text x="640" y="208" text-anchor="middle">session-mw</text><text x="640" y="222" text-anchor="middle" fill="#C7341A">failed × 2</text>
            </g>
            <g stroke="#0E0E10" stroke-width="1.2" fill="none" marker-end="url(#arrU)">
              <line x1="160" y1="212" x2="215" y2="212"/>
              <line x1="340" y1="212" x2="395" y2="212"/>
              <line x1="520" y1="212" x2="575" y2="212"/>
              <line x1="160" y1="220" x2="215" y2="290"/>
              <line x1="340" y1="290" x2="395" y2="290"/>
            </g>

            <rect x="20" y="360" width="700" height="160" rx="6" fill="#0E0E10"/>
            <text x="36" y="382" font-family="JetBrains Mono" font-size="11" fill="#86827a">LIVE · agent: tester · unit: auth-fe</text>
            <g font-family="JetBrains Mono" font-size="11" fill="#F3EFE5">
              <text x="36" y="406">› vitest run tests/login.spec.ts</text>
              <text x="36" y="424"><tspan fill="#A0E8B9">PASS</tspan>  tests/login.spec.ts &gt; valid creds</text>
              <text x="36" y="442"><tspan fill="#FF8A6E">FAIL</tspan>  tests/login.spec.ts &gt; invalid pwd</text>
              <text x="36" y="460">      Expected: 401  Received: 500</text>
              <text x="36" y="486" fill="#FFA94D">↻ retry budget 1/2 · re-dispatch coder…</text>
            </g>

            <rect x="740" y="360" width="340" height="160" rx="6" fill="#fff" stroke="#dcd5c4"/>
            <text x="756" y="382" font-family="JetBrains Mono" font-size="11" fill="#3B3B40">PRD · rev 3 · diff vs rev 2</text>
            <g font-family="JetBrains Mono" font-size="11">
              <text x="756" y="406" fill="#0E8C5A">+ Section: OAuth login (GitHub)</text>
              <text x="756" y="424" fill="#0E8C5A">+ NFR: 3rd-party token refresh ≤ 5s</text>
              <text x="756" y="442" fill="#C7341A">– 旧: 仅支持邮箱密码登录</text>
              <text x="756" y="468" fill="#3B3B40">[open full →]</text>
            </g>
          </svg>
        </div>
      </section>

      <section>
        <h2>四个核心区域</h2>
        <div class="grid-2">
          <div class="card l3">
            <h4>① Header</h4>
            <p>项目名 + 「+ NEW REQUIREMENT」按钮(L3 入口)。点开弹 textarea,提交后走 <code>POST .../requirement</code>。</p>
          </div>
          <div class="card">
            <h4>② Loop 状态栏</h4>
            <p>三张并排卡片显示 L1 / L2 / L3 当前状态。点击任意一张展开该 Loop 的详细历史时间线。</p>
          </div>
          <div class="card l1">
            <h4>③ Units DAG</h4>
            <p>复用 <code>PipelineEditor.vue</code> 的 SVG DAG 渲染。每个节点点击展开「该 Unit 的最近 3 次 run」抽屉。</p>
          </div>
          <div class="card l2">
            <h4>④ Live 流 + PRD diff</h4>
            <p>左:终端样式的 Agent stdout 流(SSE)。右:当前 PRD 与上一 rev 的 section 级 diff。</p>
          </div>
        </div>
      </section>

      <section>
        <h2>组件树</h2>
        <pre class="code"><span class="c">// web/src/views/DevLoopDashboard.vue</span>
<span class="k">&lt;template&gt;</span>
  <span class="k">&lt;PageLayout&gt;</span>
    <span class="k">&lt;DashboardHeader</span> :project=<span class="s">"project"</span> @feedback=<span class="s">"onFeedback"</span> <span class="k">/&gt;</span>
    <span class="k">&lt;LoopStatusBar</span>   :l1=<span class="s">"l1Stats"</span> :l2=<span class="s">"l2Stats"</span> :l3=<span class="s">"l3Stats"</span> <span class="k">/&gt;</span>
    <span class="k">&lt;UnitsDAG</span>        :units=<span class="s">"units"</span> @select=<span class="s">"openUnitDrawer"</span> <span class="k">/&gt;</span>
    <span class="k">&lt;LiveAgentStream</span> :stream=<span class="s">"sse"</span> <span class="k">/&gt;</span>
    <span class="k">&lt;PrdDiffPanel</span>    :prd=<span class="s">"prdHtml"</span>   :history=<span class="s">"prdHistory"</span> <span class="k">/&gt;</span>
    <span class="k">&lt;UnitDrawer</span>      v-if=<span class="s">"drawer"</span> :unit=<span class="s">"drawer"</span> @close=<span class="s">"drawer=null"</span> <span class="k">/&gt;</span>
    <span class="k">&lt;FeedbackModal</span>   v-if=<span class="s">"modalOpen"</span> @submit=<span class="s">"sendRequirement"</span> <span class="k">/&gt;</span>
  <span class="k">&lt;/PageLayout&gt;</span>
<span class="k">&lt;/template&gt;</span></pre>
        <p class="sig">总行数预估 ~400,含模板 + script + 样式。其中 UnitsDAG 大约 120 行——这里有最高的复用率(参考 PipelineEditor)。</p>
      </section>

      <section>
        <h2>路由 + i18n</h2>
        <p><b>路由:</b><code>{ path: '/devloop/:pid', name: 'DevLoop', component: DevLoopDashboard }</code> 加进 <code>web/src/router/index.js</code>。</p>
        <p><b>i18n:</b>新增 key 前缀 <code>devloop.*</code>。涉及的标签:<code>loop.l1</code> · <code>loop.l2</code> · <code>loop.l3</code> · <code>state.passed</code> · <code>state.testing</code> · <code>btn.newRequirement</code> 等 ~20 个。中英文必填,其他 4 种语言可走 fallback。</p>
      </section>

      <section>
        <h2>性能考虑</h2>
        <ul>
          <li><b>SSE:</b>使用浏览器原生 <code>EventSource</code>,断线自动重连。</li>
          <li><b>大 stream:</b>LiveAgentStream 保留最近 500 行,老的丢;用户想看完整日志走「open in new tab」按钮跳到 <code>/stream.log</code>。</li>
          <li><b>PRD diff:</b>使用客户端 <code>diff-dom</code>,不在后端计算(项目 PRD 一般 &lt; 50KB,浏览器算得动)。</li>
          <li><b>DAG 大于 50 Unit:</b>切换到 d3-force 自动布局,否则手动栅格。</li>
        </ul>
      </section>

    </article>
    <nav class="prev-next"></nav>
    <footer class="site"><div><b>Section</b>07 · UI</div><div><b>Page</b>ui-dashboard.html</div></footer>
  </main>
</div>
<script src="assets/shell.js"></script>
</body>
</html>