Files
frontend-miniapp/docs/pdca/dashboard.html
lyf 8fed715235
Some checks failed
CI / verify (push) Has been cancelled
chore: sync latest project updates
2026-07-03 14:42:38 +08:00

1717 lines
73 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PDCA 项目进度驾驶舱</title>
<style>
:root {
color-scheme: light;
--bg: #f7f9f6;
--panel: #ffffff;
--panel-soft: #f0f5f2;
--ink: #17211b;
--muted: #647067;
--border: #dbe4dd;
--green: #2f8f72;
--blue: #2d7f99;
--violet: #7467c9;
--amber: #c77d24;
--red: #c84848;
--gray: #8a948d;
--shadow: 0 10px 28px rgba(25, 40, 31, 0.08);
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: Inter, "Microsoft YaHei", "PingFang SC", "Segoe UI", Arial, sans-serif;
font-size: 15px;
line-height: 1.45;
}
.page {
width: min(1180px, calc(100% - 32px));
margin: 0 auto;
padding: 28px 0 36px;
}
header {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
padding: 10px 0 20px;
border-bottom: 1px solid var(--border);
}
h1, h2, h3, p { margin: 0; }
h1 { font-size: clamp(24px, 3vw, 34px); font-weight: 760; letter-spacing: 0; }
h2 { font-size: 17px; margin-bottom: 14px; }
h3 { font-size: 14px; color: var(--muted); font-weight: 700; margin-bottom: 8px; }
.subtitle { margin-top: 6px; color: var(--muted); }
.meta { color: var(--muted); text-align: right; font-size: 13px; overflow-wrap: anywhere; }
a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }
button, input, select { font: inherit; }
button:focus-visible, input:focus-visible, select:focus-visible, a:focus-visible {
outline: 3px solid rgba(45, 127, 153, 0.28);
outline-offset: 2px;
}
.dashboard-nav {
position: sticky;
top: 0;
z-index: 20;
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 10px 0;
background: rgba(247, 249, 246, 0.94);
backdrop-filter: blur(8px);
}
.dashboard-nav a {
display: inline-flex;
min-height: 32px;
align-items: center;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--panel);
color: var(--ink);
padding: 5px 11px;
font-size: 12px;
font-weight: 700;
}
.dashboard-toolbar {
position: sticky;
top: 52px;
z-index: 19;
display: grid;
gap: 10px;
margin-top: 12px;
padding: 12px;
border: 1px solid var(--border);
border-radius: 8px;
background: rgba(255, 255, 255, 0.96);
box-shadow: var(--shadow);
backdrop-filter: blur(8px);
}
.toolbar-main {
display: grid;
grid-template-columns: minmax(260px, 2fr) minmax(160px, 1fr) minmax(180px, 1fr);
gap: 10px;
}
.toolbar-main label { display: grid; gap: 5px; min-width: 0; }
.toolbar-main label span { color: var(--muted); font-size: 12px; font-weight: 700; }
.toolbar-main input, .toolbar-main select {
width: 100%;
min-height: 38px;
border: 1px solid var(--border);
border-radius: 6px;
background: #fff;
color: var(--ink);
padding: 7px 10px;
}
.toolbar-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.chip-button, .tool-button {
min-height: 32px;
border: 1px solid var(--border);
border-radius: 999px;
background: #fff;
color: var(--ink);
cursor: pointer;
padding: 5px 11px;
font-size: 12px;
font-weight: 750;
}
.chip-button.is-active {
border-color: var(--blue);
background: rgba(45, 127, 153, 0.12);
color: var(--blue);
}
.tool-button {
border-radius: 6px;
background: var(--panel-soft);
}
.task-counter {
margin-left: auto;
color: var(--muted);
font-size: 12px;
font-weight: 700;
}
.task-counter strong { color: var(--ink); font-size: 15px; }
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 14px;
margin-top: 16px;
}
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: var(--shadow);
padding: 18px;
min-width: 0;
}
.span-3 { grid-column: span 3; }
.span-4 { grid-column: span 4; }
.span-5 { grid-column: span 5; }
.span-6 { grid-column: span 6; }
.span-7 { grid-column: span 7; }
.span-8 { grid-column: span 8; }
.span-12 { grid-column: span 12; }
.health-card {
color: #fff;
background: linear-gradient(135deg, var(--blue), var(--green));
border-color: transparent;
}
.health-card.health-red { background: linear-gradient(135deg, #9f3636, var(--red)); }
.health-card.health-yellow { background: linear-gradient(135deg, #9a631d, var(--amber)); }
.health-card.health-green { background: linear-gradient(135deg, #24795f, var(--green)); }
.health-label { font-size: 13px; opacity: 0.84; }
.health-value { display: block; font-size: 38px; line-height: 1; margin: 12px 0 10px; text-transform: uppercase; }
.health-reason { opacity: 0.92; overflow-wrap: anywhere; }
.ring-wrap { display: flex; align-items: center; gap: 16px; }
.ring {
width: 116px;
height: 116px;
flex: 0 0 116px;
border-radius: 50%;
display: grid;
place-items: center;
background: conic-gradient(var(--green) 75%, #e4ebe6 0);
}
.ring span {
width: 78px;
height: 78px;
display: grid;
place-items: center;
background: #fff;
border-radius: 50%;
font-size: 24px;
font-weight: 760;
}
.metrics {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 10px;
}
.metric {
background: var(--panel-soft);
border-left: 4px solid var(--gray);
border-radius: 6px;
padding: 11px 12px;
min-height: 68px;
}
.metric span { display: block; color: var(--muted); font-size: 12px; margin-bottom: 4px; }
.metric strong { font-size: 24px; overflow-wrap: anywhere; }
.metric-good { border-color: var(--green); }
.metric-info { border-color: var(--blue); }
.metric-warn { border-color: var(--amber); }
.metric-bad { border-color: var(--red); }
.baseline dl { display: grid; gap: 12px; margin: 0; }
.baseline dt { color: var(--muted); font-size: 12px; margin-bottom: 3px; }
.baseline dd { margin: 0; font-weight: 650; overflow-wrap: anywhere; }
.status-bar {
height: 18px;
display: flex;
overflow: hidden;
border: 1px solid var(--border);
border-radius: 6px;
background: #eef3ef;
}
.segment-done { background: var(--green); }
.segment-doing { background: var(--blue); }
.segment-review { background: var(--violet); }
.segment-blocked { background: var(--red); }
.segment-todo { background: var(--amber); }
.segment-unknown, .segment-empty { background: var(--gray); }
.legend {
display: flex;
flex-wrap: wrap;
gap: 8px 14px;
margin-top: 12px;
color: var(--muted);
font-size: 12px;
}
.legend span::before {
content: "";
display: inline-block;
width: 9px;
height: 9px;
margin-right: 6px;
border-radius: 2px;
background: var(--gray);
}
.legend .done::before { background: var(--green); }
.legend .doing::before { background: var(--blue); }
.legend .review::before { background: var(--violet); }
.legend .blocked::before { background: var(--red); }
.legend .todo::before { background: var(--amber); }
.phase-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
}
.phase {
min-height: 104px;
padding: 13px;
border: 1px solid var(--border);
border-radius: 8px;
background: #fbfcfb;
}
.phase span { color: var(--muted); font-size: 12px; }
.phase strong { display: block; font-size: 30px; margin: 7px 0 2px; }
.phase small { color: var(--muted); }
.phase-plan { border-top: 4px solid var(--blue); }
.phase-do { border-top: 4px solid var(--green); }
.phase-check { border-top: 4px solid var(--amber); }
.phase-act { border-top: 4px solid var(--violet); }
.dimension-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 10px;
}
.dimension-card {
min-height: 106px;
padding: 12px;
border: 1px solid var(--border);
border-left: 4px solid var(--gray);
border-radius: 8px;
background: #fbfcfb;
}
.dimension-card span { display: block; color: var(--muted); font-size: 12px; margin-bottom: 8px; }
.dimension-card strong { display: block; font-size: 20px; text-transform: uppercase; margin-bottom: 7px; }
.dimension-card p { color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
.dimension-card.status-green { border-left-color: var(--green); }
.dimension-card.status-yellow { border-left-color: var(--amber); }
.dimension-card.status-red { border-left-color: var(--red); }
.workstream-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 10px;
}
.workstream-card {
min-height: 138px;
border: 1px solid var(--border);
border-top: 4px solid var(--blue);
border-radius: 8px;
background: #fbfcfb;
padding: 13px;
cursor: pointer;
}
.workstream-card:hover, .workstream-card:focus-visible {
border-color: rgba(45, 127, 153, 0.55);
box-shadow: 0 0 0 3px rgba(45, 127, 153, 0.10);
}
.workstream-card.tone-good { border-top-color: var(--green); }
.workstream-card.tone-warn { border-top-color: var(--amber); }
.workstream-card.tone-bad { border-top-color: var(--red); }
.workstream-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 12px;
}
.workstream-head span { color: var(--muted); font-size: 13px; font-weight: 700; }
.workstream-head strong { font-size: 25px; }
.mini-progress {
height: 9px;
overflow: hidden;
border-radius: 999px;
background: #e4ebe6;
margin-bottom: 12px;
}
.mini-progress span {
display: block;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, var(--blue), var(--green));
}
.workstream-meta { display: grid; gap: 5px; color: var(--muted); font-size: 12px; }
.timeline { display: grid; gap: 0; }
.timeline-row {
display: grid;
grid-template-columns: 108px 24px 1fr;
gap: 10px;
position: relative;
padding: 0 0 14px;
}
.timeline-row::before {
content: "";
position: absolute;
left: 119px;
top: 16px;
bottom: -2px;
width: 2px;
background: var(--border);
}
.timeline-row:last-child::before { display: none; }
.timeline-date { color: var(--muted); font-size: 12px; padding-top: 2px; overflow-wrap: anywhere; }
.timeline-dot {
width: 14px;
height: 14px;
margin-top: 2px;
border-radius: 50%;
background: var(--gray);
border: 3px solid #fff;
box-shadow: 0 0 0 1px var(--border);
z-index: 1;
}
.timeline-row.status-done .timeline-dot { background: var(--green); }
.timeline-row.status-doing .timeline-dot { background: var(--blue); }
.timeline-row.status-review .timeline-dot { background: var(--violet); }
.timeline-row.status-blocked .timeline-dot { background: var(--red); }
.timeline-row.status-todo .timeline-dot { background: var(--amber); }
.timeline-body {
border: 1px solid var(--border);
border-radius: 7px;
background: #fbfcfb;
padding: 10px 12px;
}
.timeline-body strong { display: block; margin-bottom: 6px; overflow-wrap: anywhere; }
.timeline-body div { display: flex; flex-wrap: wrap; gap: 6px 12px; color: var(--muted); font-size: 12px; }
.risk-matrix { display: grid; gap: 8px; }
.matrix-title {
display: flex;
justify-content: space-between;
color: var(--muted);
font-size: 12px;
}
.matrix-grid {
display: grid;
grid-template-columns: 42px repeat(3, minmax(0, 1fr));
gap: 6px;
align-items: stretch;
}
.matrix-grid > div {
min-height: 54px;
border-radius: 6px;
padding: 8px;
background: #f2f6f3;
color: var(--muted);
font-size: 12px;
text-align: center;
}
.matrix-axis {
display: grid;
place-items: center;
font-weight: 700;
background: transparent !important;
border: 0;
}
.matrix-cell {
display: flex;
align-items: flex-start;
justify-content: flex-start;
flex-wrap: wrap;
gap: 5px;
text-align: left !important;
border: 1px solid var(--border);
}
.matrix-cell button, .matrix-cell em {
display: inline-flex;
min-width: 28px;
height: 24px;
align-items: center;
justify-content: center;
border-radius: 999px;
background: #fff;
color: var(--ink);
font-style: normal;
font-weight: 760;
font-size: 11px;
box-shadow: inset 0 0 0 1px rgba(23, 33, 27, 0.08);
}
.matrix-cell button {
border: 0;
cursor: pointer;
}
.risk-detail {
margin-top: 12px;
border: 1px solid var(--border);
border-left: 4px solid var(--red);
border-radius: 8px;
background: #fbfcfb;
padding: 12px;
}
.risk-detail[hidden] { display: none; }
.risk-detail strong { display: block; overflow-wrap: anywhere; }
.risk-detail p { margin-top: 5px; color: var(--muted); font-size: 12px; }
.risk-detail dl { display: grid; gap: 8px; margin: 10px 0 0; }
.risk-detail dt { color: var(--muted); font-size: 12px; }
.risk-detail dd { margin: 2px 0 0; overflow-wrap: anywhere; }
.matrix-high-high, .matrix-high-medium, .matrix-medium-high { background: rgba(200, 72, 72, 0.13) !important; }
.matrix-medium-medium, .matrix-high-low, .matrix-low-high { background: rgba(199, 125, 36, 0.15) !important; }
.matrix-low-low, .matrix-low-medium, .matrix-medium-low { background: rgba(47, 143, 114, 0.12) !important; }
.quality-gates h3 { margin-bottom: 10px; }
.quality-gates { display: grid; gap: 9px; }
.readiness {
display: grid;
grid-template-columns: minmax(150px, 0.8fr) minmax(0, 1.2fr);
gap: 12px;
border: 1px solid var(--border);
border-left: 5px solid var(--gray);
border-radius: 8px;
background: #fbfcfb;
padding: 12px;
}
.readiness-go { border-left-color: var(--green); }
.readiness-conditional-go { border-left-color: var(--amber); }
.readiness-no-go { border-left-color: var(--red); }
.readiness span, .readiness small { color: var(--muted); font-size: 12px; font-weight: 700; }
.readiness strong { display: block; margin: 4px 0; font-size: 24px; }
.readiness p { color: var(--muted); font-size: 12px; }
.readiness ul { margin: 5px 0 0; padding-left: 18px; color: var(--muted); font-size: 12px; }
.quality-row {
display: grid;
grid-template-columns: 74px 1fr;
gap: 8px 10px;
align-items: start;
border: 1px solid var(--border);
border-radius: 7px;
background: #fbfcfb;
padding: 10px 12px;
}
.quality-row strong { overflow-wrap: anywhere; }
.quality-row small { grid-column: 2; color: var(--muted); }
.quality-result {
display: inline-flex;
justify-content: center;
border-radius: 999px;
padding: 3px 8px;
color: var(--muted);
background: #eef3ef;
font-size: 12px;
font-weight: 750;
}
.quality-row.result-pass .quality-result { color: var(--green); background: rgba(47, 143, 114, 0.12); }
.quality-row.result-fail .quality-result { color: var(--red); background: rgba(200, 72, 72, 0.12); }
.quality-row.result-pending .quality-result { color: var(--amber); background: rgba(199, 125, 36, 0.12); }
.item-list { display: grid; gap: 10px; }
.item-row {
padding: 11px 12px;
border: 1px solid var(--border);
border-radius: 6px;
background: #fbfcfb;
}
.item-row strong { display: block; margin-bottom: 6px; overflow-wrap: anywhere; }
.item-row div { display: flex; flex-wrap: wrap; gap: 6px 12px; color: var(--muted); font-size: 12px; }
.rule-note {
color: var(--muted);
background: var(--panel-soft);
border-left: 4px solid var(--blue);
border-radius: 6px;
padding: 11px 12px;
margin-bottom: 12px;
}
.task-table-wrap {
overflow: auto;
max-height: 640px;
border: 1px solid var(--border);
border-radius: 8px;
}
.task-table {
width: 100%;
border-collapse: collapse;
min-width: 1120px;
font-size: 13px;
}
.task-table caption {
text-align: left;
color: var(--muted);
background: #fff;
padding: 10px 12px;
font-size: 12px;
font-weight: 700;
}
.task-table th, .task-table td {
border-bottom: 1px solid var(--border);
padding: 9px 10px;
text-align: left;
vertical-align: top;
background: #fff;
}
.task-table th {
color: var(--muted);
background: #f3f7f4;
font-size: 12px;
font-weight: 700;
position: sticky;
top: 0;
z-index: 2;
}
.task-table th:first-child, .task-table td:first-child {
position: sticky;
left: 0;
z-index: 3;
box-shadow: 1px 0 0 var(--border);
}
.task-table th:first-child { z-index: 4; }
.sort-button {
width: 100%;
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
padding: 0;
text-align: left;
font-size: 12px;
font-weight: 800;
}
.task-row.row-hard-blocked td { background: rgba(200, 72, 72, 0.06); }
.task-row.row-due-overdue td { box-shadow: inset 3px 0 0 var(--red); }
.task-row.row-due-today td { box-shadow: inset 3px 0 0 var(--amber); }
.task-row.row-missing-evidence td { color: #4c4337; }
.task-id { white-space: nowrap; font-weight: 700; }
.task-title { min-width: 220px; font-weight: 650; }
.task-evidence { max-width: 300px; overflow-wrap: anywhere; }
.link-button {
border: 0;
background: transparent;
color: var(--blue);
cursor: pointer;
padding: 0;
text-align: left;
font-size: 12px;
font-weight: 750;
}
.status-pill {
display: inline-block;
min-width: 54px;
border-radius: 999px;
padding: 3px 8px;
text-align: center;
font-size: 12px;
font-weight: 700;
color: var(--gray);
background: #eef3ef;
}
.status-pill.status-done { color: var(--green); background: rgba(47, 143, 114, 0.12); }
.status-pill.status-doing { color: var(--blue); background: rgba(45, 127, 153, 0.12); }
.status-pill.status-review { color: var(--violet); background: rgba(116, 103, 201, 0.12); }
.status-pill.status-blocked { color: var(--red); background: rgba(200, 72, 72, 0.12); }
.status-pill.status-todo { color: var(--amber); background: rgba(199, 125, 36, 0.12); }
.counted-yes { color: var(--green); font-weight: 750; }
.counted-warn { color: var(--amber); font-weight: 750; }
.counted-no { color: var(--muted); }
.empty { color: var(--muted); background: var(--panel-soft); padding: 12px; border-radius: 6px; }
footer { margin-top: 18px; color: var(--muted); font-size: 12px; text-align: right; }
@media (max-width: 920px) {
.span-3, .span-4, .span-5, .span-6, .span-7, .span-8 { grid-column: span 12; }
.metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dimension-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.workstream-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.toolbar-main { grid-template-columns: 1fr; }
.task-counter { margin-left: 0; width: 100%; }
}
@media (max-width: 620px) {
.page { width: min(100% - 20px, 1180px); padding-top: 16px; }
header { align-items: flex-start; flex-direction: column; }
.meta { text-align: left; }
.dashboard-nav { overflow-x: auto; flex-wrap: nowrap; padding-bottom: 8px; }
.dashboard-nav a { flex: 0 0 auto; }
.dashboard-toolbar { top: 48px; }
.chip-button, .tool-button { flex: 1 1 auto; justify-content: center; }
.ring-wrap { align-items: flex-start; flex-direction: column; }
.metrics, .phase-grid, .dimension-grid, .workstream-grid { grid-template-columns: 1fr; }
.readiness { grid-template-columns: 1fr; }
.timeline-row { grid-template-columns: 86px 20px 1fr; }
.timeline-row::before { left: 96px; }
.matrix-grid { grid-template-columns: 34px repeat(3, minmax(72px, 1fr)); overflow-x: auto; }
}
@media print {
.dashboard-nav, .dashboard-toolbar, .sort-button span { display: none !important; }
.page { width: 100%; padding: 0; }
.panel, .dashboard-toolbar { box-shadow: none; }
.task-table-wrap { max-height: none; overflow: visible; }
}
</style>
</head>
<body>
<div class="page">
<header>
<div>
<h1>PDCA 项目进度驾驶舱</h1>
<p class="subtitle">从 docs/pdca 生成的专业项目状态、风险和行动闭环面板</p>
</div>
<div class="meta">
<div>生成日期: 2026-06-12</div>
<div>项目: E:\MyWork\深圳国际艺术馆\museum-guide\museum-guide-v4.0\frontend-miniapp</div>
<div>Git master@2055b13 · 变更文件数19</div>
</div>
</header>
<nav class="dashboard-nav" aria-label="Dashboard sections">
<a href="#overview">健康状态</a>
<a href="#workstreams">核心工作包</a>
<a href="#milestones">里程碑</a>
<a href="#risks">风险矩阵</a>
<a href="#quality">质量门禁</a>
<a href="#task-details">任务明细与完成判定</a>
</nav>
<section class="dashboard-toolbar" aria-label="任务筛选">
<div class="toolbar-main">
<label>
<span>任务筛选</span>
<input id="task-search" type="search" placeholder="搜索编号、任务、负责人、证据、阻塞项" autocomplete="off">
</label>
<label>
<span>全部状态</span>
<select id="status-filter">
<option value="">全部状态</option>
<option value="done">已完成</option>
<option value="doing">进行中</option>
<option value="review">待复核</option>
<option value="blocked">阻塞</option>
<option value="todo">待办</option>
<option value="unknown">未知</option>
</select>
</label>
<label>
<span>全部工作包</span>
<select id="workstream-filter">
<option value="">全部工作包</option>
<option value="frontend">小程序前端</option>
<option value="integration">主服务联调</option>
<option value="deployment">测试部署</option>
<option value="navigation">三维导览路网</option>
<option value="governance">项目治理</option>
</select>
</label>
</div>
<div class="toolbar-actions" aria-label="Quick filters and actions">
<button type="button" class="chip-button" data-quick-filter="hard-blocked" aria-pressed="false">硬阻塞</button>
<button type="button" class="chip-button" data-quick-filter="due-today" aria-pressed="false">今日到期</button>
<button type="button" class="chip-button" data-quick-filter="overdue" aria-pressed="false">已逾期</button>
<button type="button" class="chip-button" data-quick-filter="missing-owner" aria-pressed="false">无负责人</button>
<button type="button" class="chip-button" data-quick-filter="missing-evidence" aria-pressed="false">缺证据</button>
<button type="button" class="chip-button" data-quick-filter="uncounted" aria-pressed="false">未计入完成率</button>
<span class="task-counter"><strong id="visible-task-count">0</strong>/<span id="total-task-count">12</span> 当前可见</span>
<button type="button" id="reset-filters" class="tool-button">重置</button>
<button type="button" id="export-visible" class="tool-button">导出 CSV</button>
<button type="button" id="print-dashboard" class="tool-button">打印</button>
</div>
</section>
<main class="grid">
<section id="overview" class="panel health-card health-red span-4">
<div class="health-label">健康状态</div>
<strong class="health-value">红色</strong>
<p class="health-reason">4 个任务有阻塞/依赖说明不等同硬阻塞1 个任务逾期3 个高影响风险仍开放2 个决策待确认</p>
</section>
<section class="panel span-4">
<h2>任务完成率</h2>
<div class="ring-wrap">
<div class="ring" aria-label="75%"><span>75%</span></div>
<div>
<p>已完成: 9/12</p>
<p class="subtitle">进行中/待复核: 1 · 硬阻塞: 0</p>
</div>
</div>
</section>
<section class="panel span-12">
<h2>任务总览</h2>
<div class="metrics">
<div class="metric metric-neutral">
<span>任务总览</span>
<strong>12</strong>
</div>
<div class="metric metric-good">
<span>已完成(有证据)</span>
<strong>9</strong>
</div>
<div class="metric metric-good">
<span>完成但缺证据</span>
<strong>0</strong>
</div>
<div class="metric metric-info">
<span>进行中/待复核</span>
<strong>1</strong>
</div>
<div class="metric metric-good">
<span>硬阻塞</span>
<strong>0</strong>
</div>
<div class="metric metric-warn">
<span>阻塞/依赖说明</span>
<strong>4</strong>
</div>
<div class="metric metric-warn">
<span>风险</span>
<strong>5</strong>
</div>
<div class="metric metric-neutral">
<span>检查</span>
<strong>通过 13 / 未通过 1</strong>
</div>
<div class="metric metric-info">
<span>行动</span>
<strong>7</strong>
</div>
<div class="metric metric-info">
<span>里程碑</span>
<strong>6</strong>
</div>
<div class="metric metric-warn">
<span>决策待确认</span>
<strong>2</strong>
</div>
</div>
</section>
<section class="panel span-12">
<h2>专业状态维度</h2>
<div class="dimension-grid">
<article class="dimension-card status-red">
<span>进度</span>
<strong>红色</strong>
<p>存在逾期任务或里程碑</p>
</article>
<article class="dimension-card status-yellow">
<span>范围</span>
<strong>黄色</strong>
<p>目标或里程碑仍待确认</p>
</article>
<article class="dimension-card status-red">
<span>风险</span>
<strong>红色</strong>
<p>高影响风险仍开放</p>
</article>
<article class="dimension-card status-red">
<span>证据</span>
<strong>红色</strong>
<p>存在未通过的验收检查</p>
</article>
<article class="dimension-card status-yellow">
<span>决策</span>
<strong>黄色</strong>
<p>需要干系人确认决策</p>
</article>
</div>
</section>
<section id="workstreams" class="panel span-12">
<h2>核心工作包</h2>
<div class="workstream-grid">
<article class="workstream-card workstream-frontend tone-warn" role="button" tabindex="0" data-workstream="frontend" aria-label="小程序前端">
<div class="workstream-head">
<span>小程序前端</span>
<strong>100%</strong>
</div>
<div class="mini-progress"><span style="width:100%;"></span></div>
<div class="workstream-meta">
<span>已验证完成 8/8</span>
<span>阻塞 0</span>
<span>关注项 1</span>
<span>下一到期 待确认</span>
<span>分类:关键词推断</span>
</div>
</article>
<article class="workstream-card workstream-navigation tone-warn" role="button" tabindex="0" data-workstream="navigation" aria-label="三维导览路网">
<div class="workstream-head">
<span>三维导览路网</span>
<strong>0%</strong>
</div>
<div class="mini-progress"><span style="width:0%;"></span></div>
<div class="workstream-meta">
<span>已验证完成 0/1</span>
<span>阻塞 0</span>
<span>关注项 1</span>
<span>下一到期 2026-06-13</span>
<span>分类:关键词推断</span>
</div>
</article>
<article class="workstream-card workstream-governance tone-warn" role="button" tabindex="0" data-workstream="governance" aria-label="项目治理">
<div class="workstream-head">
<span>项目治理</span>
<strong>33%</strong>
</div>
<div class="mini-progress"><span style="width:33%;"></span></div>
<div class="workstream-meta">
<span>已验证完成 1/3</span>
<span>阻塞 0</span>
<span>关注项 2</span>
<span>下一到期 2026-06-11</span>
<span>分类:关键词推断</span>
</div>
</article>
</div>
</section>
<section class="panel baseline span-5">
<h2>项目基线</h2>
<dl>
<div>
<dt>目标</dt>
<dd>待确认</dd>
</div>
<div>
<dt>当前里程碑</dt>
<dd>待确认</dd>
</div>
</dl>
</section>
<section id="milestones" class="panel span-7">
<h2>里程碑</h2>
<div class="timeline">
<article class="timeline-row status-done">
<div class="timeline-date">2026-06-10</div>
<div class="timeline-dot"></div>
<div class="timeline-body">
<strong>H5 测试服务器可访问</strong>
<div>
<span>M-001</span>
<span>已完成</span>
<span>负责人: Codex</span>
<span>截止: 2026-06-10</span>
<span>证据: https://guide.whaoyue.com/ 已部署Nginx reload 成功</span>
</div>
</div>
</article>
<article class="timeline-row status-done">
<div class="timeline-date">2026-06-10</div>
<div class="timeline-dot"></div>
<div class="timeline-body">
<strong>馆内 3D 模型资源请求修复</strong>
<div>
<span>M-002</span>
<span>已完成</span>
<span>负责人: Codex</span>
<span>截止: 2026-06-10</span>
<span>证据: manifest 200 JSONL1.glb 200 GLB缺失 GLB 404</span>
</div>
</div>
</article>
<article class="timeline-row status-done">
<div class="timeline-date">2026-06-10</div>
<div class="timeline-dot"></div>
<div class="timeline-body">
<strong>部署说明与回滚流程沉淀</strong>
<div>
<span>M-003</span>
<span>已完成</span>
<span>负责人: Codex</span>
<span>截止: 2026-06-10</span>
<span>证据: `docs/H5_DEPLOYMENT_GUIDE.md`</span>
</div>
</div>
</article>
<article class="timeline-row status-done">
<div class="timeline-date">2026-06-12</div>
<div class="timeline-dot"></div>
<div class="timeline-body">
<strong>2026-06-12 H5 功能更新部署到测试域名</strong>
<div>
<span>M-006</span>
<span>已完成</span>
<span>负责人: Codex</span>
<span>截止: 2026-06-12</span>
<span>证据: https://guide.whaoyue.com/ 引用 `index-Da0rB3mi.js`;备份 `/dmdata/nginx/html/_backups/guide-before-deploy-20260612032520.tar.gz`</span>
</div>
</div>
</article>
<article class="timeline-row status-todo">
<div class="timeline-date">待确认</div>
<div class="timeline-dot"></div>
<div class="timeline-body">
<strong>H5 关键用户流程闭环</strong>
<div>
<span>M-004</span>
<span>待办</span>
<span>负责人: 待确认</span>
<span>截止: 待确认</span>
<span>证据: `docs/QA/user-flow-closure-audit-2026-06-10.md`</span>
</div>
</div>
</article>
<article class="timeline-row status-todo">
<div class="timeline-date">待确认</div>
<div class="timeline-dot"></div>
<div class="timeline-body">
<strong>发布验收与交付确认</strong>
<div>
<span>M-005</span>
<span>待办</span>
<span>负责人: 待确认</span>
<span>截止: 待确认</span>
</div>
</div>
</article>
</div>
</section>
<section class="panel span-7">
<h2>任务状态分布</h2>
<div class="status-bar"><span class="segment segment-done" style="width:75%;" title="已完成: 9"></span><span class="segment segment-review" style="width:8%;" title="待复核: 1"></span><span class="segment segment-todo" style="width:17%;" title="待办: 2"></span></div>
<div class="legend">
<span class="done">已完成 9</span>
<span class="doing">进行中 0</span>
<span class="review">待复核 1</span>
<span class="blocked">阻塞 0</span>
<span class="todo">待办 2</span>
</div>
</section>
<section id="risks" class="panel span-5">
<h2>风险矩阵</h2>
<div class="risk-matrix">
<div class="matrix-title">
<span>影响 ↑</span>
<span>概率 →</span>
</div>
<div class="matrix-grid">
<div></div><div></div><div></div><div></div>
<div class="matrix-axis"></div><div class="matrix-cell matrix-high-low"></div><div class="matrix-cell matrix-high-medium"><button type="button" class="risk-chip" data-risk-id="R-003" title="模型静态资源缓存 30 天,错误资源上线后恢复慢">R-003</button><button type="button" class="risk-chip" data-risk-id="R-005" title="讲解内容改为资源包 POI 派生后,若未补正式文案/音频和人工验收,可能造成讲解体验与验收口径不一致">R-005</button></div><div class="matrix-cell matrix-high-high"><button type="button" class="risk-chip" data-risk-id="R-001" title="H5 用户流程闭环仍有断点,影响真实到馆任务完成">R-001</button></div><div class="matrix-axis"></div><div class="matrix-cell matrix-medium-low"></div><div class="matrix-cell matrix-medium-medium"><button type="button" class="risk-chip" data-risk-id="R-002" title="fresh browser 复测未完成,可能漏掉真实设备上的 3D 渲染问题">R-002</button><button type="button" class="risk-chip" data-risk-id="R-004" title="项目仍有多处未提交/未归属改动,发布边界可能不清">R-004</button></div><div class="matrix-cell matrix-medium-high"></div><div class="matrix-axis"></div><div class="matrix-cell matrix-low-low"></div><div class="matrix-cell matrix-low-medium"></div><div class="matrix-cell matrix-low-high"></div>
</div>
</div>
<aside id="risk-detail" class="risk-detail" tabindex="-1" hidden>
<h3>风险详情</h3>
<strong data-risk-title>点击矩阵中的风险编号查看详情。</strong>
<p data-risk-meta></p>
<dl>
<div>
<dt>负责人</dt>
<dd data-risk-owner>-</dd>
</div>
<div>
<dt>缓解措施</dt>
<dd data-risk-mitigation>-</dd>
</div>
</dl>
</aside>
</section>
<section class="panel span-6">
<h2>PDCA 阶段分布</h2>
<div class="phase-grid">
<div class="phase phase-plan">
<span>计划</span>
<strong>0</strong>
<small>已完成 0/0</small>
</div>
<div class="phase phase-do">
<span>执行</span>
<strong>7</strong>
<small>已完成 6/7</small>
</div>
<div class="phase phase-check">
<span>检查</span>
<strong>3</strong>
<small>已完成 1/3</small>
</div>
<div class="phase phase-act">
<span>行动</span>
<strong>2</strong>
<small>已完成 2/2</small>
</div>
</div>
</section>
<section id="quality" class="panel span-6">
<h2>质量门禁</h2>
<div class="quality-gates">
<article class="readiness readiness-no-go" aria-label="上线测试准入判定">
<div>
<span>上线测试准入判定</span>
<strong>No-Go</strong>
<p>基于硬阻塞、失败检查、逾期里程碑、高影响风险、证据缺口和待决策项自动判定。</p>
</div>
<div>
<small>判定依据</small>
<ul>
<li>1 个质量门禁未通过</li><li>2 个检查待完成</li><li>3 个高影响风险仍开放</li><li>2 个决策待确认</li>
</ul>
</div>
</article>
<h3>最近检查</h3>
<article class="quality-row result-pending">
<span class="quality-result">待检查</span>
<strong>讲解内容资源包 POI 派生改动验收</strong>
<small>2026-06-12 · C-016</small>
</article>
<article class="quality-row result-pass">
<span class="quality-result">通过</span>
<strong>2026-06-12 线上 HTTP 资源复核</strong>
<small>2026-06-12 · C-015</small>
</article>
<article class="quality-row result-pass">
<span class="quality-result">通过</span>
<strong>2026-06-12 当前工作区质量门复核</strong>
<small>2026-06-12 · C-014</small>
</article>
<article class="quality-row result-pass">
<span class="quality-result">通过</span>
<strong>2026-06-12 新模型资源公网响应</strong>
<small>2026-06-12 · C-013</small>
</article>
<article class="quality-row result-pass">
<span class="quality-result">通过</span>
<strong>2026-06-12 线上首页更新</strong>
<small>2026-06-12 · C-012</small>
</article>
<article class="quality-row result-pass">
<span class="quality-result">通过</span>
<strong>2026-06-12 H5 构建与类型检查</strong>
<small>2026-06-12 · C-011</small>
</article>
</div>
</section>
<section class="panel span-6">
<h2>阻塞与逾期</h2>
<div class="item-list">
<article class="item-row">
<strong>浏览器 fresh load 复测馆内 3D</strong>
<div><span>编号: T-007</span><span>状态: 待办</span><span>负责人: 待确认</span><span>截止日期: 2026-06-11</span><span>阻塞项: 当前 in-app browser 调试通道初始化异常,需人工或恢复工具后复测</span></div>
</article>
</div>
</section>
<section id="task-details" class="panel span-12">
<h2>任务明细与完成判定</h2>
<p class="rule-note"><strong>完成率统计口径:</strong>只有 status=done 且 evidence 非空的任务才计入完成率;标记完成但没有证据的任务会被列为证据缺口。</p>
<div class="task-table-wrap">
<table class="task-table" id="task-table">
<caption>任务明细表,可搜索、筛选、排序;完成率只统计已完成且有证据的任务。</caption>
<thead>
<tr><th scope="col"><button type="button" class="sort-button" data-sort="id" aria-label="排序 编号">编号 <span aria-hidden="true"></span></button></th><th scope="col"><button type="button" class="sort-button" data-sort="title" aria-label="排序 任务">任务 <span aria-hidden="true"></span></button></th><th scope="col"><button type="button" class="sort-button" data-sort="phase" aria-label="排序 阶段">阶段 <span aria-hidden="true"></span></button></th><th scope="col"><button type="button" class="sort-button" data-sort="status" aria-label="排序 状态">状态 <span aria-hidden="true"></span></button></th><th scope="col"><button type="button" class="sort-button" data-sort="owner" aria-label="排序 负责人">负责人 <span aria-hidden="true"></span></button></th><th scope="col"><button type="button" class="sort-button" data-sort="due" aria-label="排序 截止日期">截止日期 <span aria-hidden="true"></span></button></th><th scope="col"><button type="button" class="sort-button" data-sort="workstream" aria-label="排序 工作包">工作包 <span aria-hidden="true"></span></button></th><th scope="col"><button type="button" class="sort-button" data-sort="evidence" aria-label="排序 证据">证据 <span aria-hidden="true"></span></button></th><th scope="col"><button type="button" class="sort-button" data-sort="blockers" aria-label="排序 阻塞项">阻塞项 <span aria-hidden="true"></span></button></th><th scope="col"><button type="button" class="sort-button" data-sort="counted" aria-label="排序 计入完成率">计入完成率 <span aria-hidden="true"></span></button></th></tr>
</thead>
<tbody>
<tr class="task-row"
data-status="done"
data-owner="Codex"
data-owner-missing="false"
data-due="2026-06-10"
data-due-state="future"
data-phase="do"
data-workstream="frontend"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="true"
data-search="t-001 配置 h5 模型资源打包 do done codex 2026-06-10 `package.json` build:h5 调用 `scripts/copy-h5-nav-assets.cjs``pnpm build:h5` 通过">
<td class="task-id">T-001</td>
<td class="task-title">配置 H5 模型资源打包</td>
<td>执行</td>
<td><span class="status-pill status-done">已完成</span></td>
<td>Codex</td>
<td>2026-06-10</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="frontend">小程序前端</button></td>
<td class="task-evidence">`package.json` build:h5 调用 `scripts/copy-h5-nav-assets.cjs``pnpm build:h5` 通过</td>
<td>待确认</td>
<td><span class="counted-yes"></span></td>
</tr>
<tr class="task-row"
data-status="done"
data-owner="Codex"
data-owner-missing="false"
data-due="2026-06-10"
data-due-state="future"
data-phase="do"
data-workstream="frontend"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="true"
data-search="t-002 修复 nginx 模型静态资源规则 do done codex 2026-06-10 `/dmdata/nginx/conf.d/guide.whaoyue.com.conf` 增加 `/static/nav-assets/``docker exec nginx-server nginx -t` 通过">
<td class="task-id">T-002</td>
<td class="task-title">修复 Nginx 模型静态资源规则</td>
<td>执行</td>
<td><span class="status-pill status-done">已完成</span></td>
<td>Codex</td>
<td>2026-06-10</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="frontend">小程序前端</button></td>
<td class="task-evidence">`/dmdata/nginx/conf.d/guide.whaoyue.com.conf` 增加 `/static/nav-assets/``docker exec nginx-server nginx -t` 通过</td>
<td>待确认</td>
<td><span class="counted-yes"></span></td>
</tr>
<tr class="task-row"
data-status="done"
data-owner="Codex"
data-owner-missing="false"
data-due="2026-06-10"
data-due-state="future"
data-phase="do"
data-workstream="frontend"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="true"
data-search="t-003 部署 h5 到 guide.whaoyue.com do done codex 2026-06-10 服务器备份 `/dmdata/nginx/html/_backups/guide-before-floor-first-20260610033959.tar.gz`;线上文件数 48">
<td class="task-id">T-003</td>
<td class="task-title">部署 H5 到 guide.whaoyue.com</td>
<td>执行</td>
<td><span class="status-pill status-done">已完成</span></td>
<td>Codex</td>
<td>2026-06-10</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="frontend">小程序前端</button></td>
<td class="task-evidence">服务器备份 `/dmdata/nginx/html/_backups/guide-before-floor-first-20260610033959.tar.gz`;线上文件数 48</td>
<td>待确认</td>
<td><span class="counted-yes"></span></td>
</tr>
<tr class="task-row"
data-status="done"
data-owner="Codex"
data-owner-missing="false"
data-due="2026-06-10"
data-due-state="future"
data-phase="do"
data-workstream="frontend"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="true"
data-search="t-004 优化馆内 3d 首屏加载策略 do done codex 2026-06-10 `src/components/navigation/guidemapshell.vue` 传入 `initial-view=&quot;floor&quot;` 和当前楼层 id">
<td class="task-id">T-004</td>
<td class="task-title">优化馆内 3D 首屏加载策略</td>
<td>执行</td>
<td><span class="status-pill status-done">已完成</span></td>
<td>Codex</td>
<td>2026-06-10</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="frontend">小程序前端</button></td>
<td class="task-evidence">`src/components/navigation/GuideMapShell.vue` 传入 `initial-view=&quot;floor&quot;` 和当前楼层 ID</td>
<td>待确认</td>
<td><span class="counted-yes"></span></td>
</tr>
<tr class="task-row"
data-status="done"
data-owner="Codex"
data-owner-missing="false"
data-due="2026-06-10"
data-due-state="future"
data-phase="act"
data-workstream="frontend"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="true"
data-search="t-005 更新 h5 部署文档 act done codex 2026-06-10 `docs/h5_deployment_guide.md``readme.md` 已链接部署文档">
<td class="task-id">T-005</td>
<td class="task-title">更新 H5 部署文档</td>
<td>行动</td>
<td><span class="status-pill status-done">已完成</span></td>
<td>Codex</td>
<td>2026-06-10</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="frontend">小程序前端</button></td>
<td class="task-evidence">`docs/H5_DEPLOYMENT_GUIDE.md``README.md` 已链接部署文档</td>
<td>待确认</td>
<td><span class="counted-yes"></span></td>
</tr>
<tr class="task-row"
data-status="done"
data-owner="Codex"
data-owner-missing="false"
data-due="2026-06-10"
data-due-state="future"
data-phase="act"
data-workstream="governance"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="true"
data-search="t-006 生成并维护 pdca 项目进度 act done codex 2026-06-10 `docs/pdca/reports/2026-06-10.md``docs/pdca/dashboard.html``docs/pdca/dashboard.png`">
<td class="task-id">T-006</td>
<td class="task-title">生成并维护 PDCA 项目进度</td>
<td>行动</td>
<td><span class="status-pill status-done">已完成</span></td>
<td>Codex</td>
<td>2026-06-10</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="governance">项目治理</button></td>
<td class="task-evidence">`docs/pdca/reports/2026-06-10.md``docs/pdca/dashboard.html``docs/pdca/dashboard.png`</td>
<td>待确认</td>
<td><span class="counted-yes"></span></td>
</tr>
<tr class="task-row row-due-overdue"
data-status="todo"
data-owner="TBD"
data-owner-missing="true"
data-due="2026-06-11"
data-due-state="overdue"
data-phase="check"
data-workstream="governance"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="false"
data-search="t-007 浏览器 fresh load 复测馆内 3d check todo tbd 2026-06-11 当前 in-app browser 调试通道初始化异常,需人工或恢复工具后复测">
<td class="task-id">T-007</td>
<td class="task-title">浏览器 fresh load 复测馆内 3D</td>
<td>检查</td>
<td><span class="status-pill status-todo">待办</span></td>
<td>待确认</td>
<td>2026-06-11</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="governance">项目治理</button></td>
<td class="task-evidence">待确认</td>
<td>当前 in-app browser 调试通道初始化异常,需人工或恢复工具后复测</td>
<td><span class="counted-no">未完成</span></td>
</tr>
<tr class="task-row"
data-status="todo"
data-owner="TBD"
data-owner-missing="true"
data-due="TBD"
data-due-state="unknown"
data-phase="do"
data-workstream="governance"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="false"
data-search="t-008 修复 h5 用户流程闭环 p0/p1 问题 do todo tbd tbd `docs/qa/user-flow-closure-audit-2026-06-10.md` 需确认优先级、负责人和截止日期">
<td class="task-id">T-008</td>
<td class="task-title">修复 H5 用户流程闭环 P0/P1 问题</td>
<td>执行</td>
<td><span class="status-pill status-todo">待办</span></td>
<td>待确认</td>
<td>待确认</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="governance">项目治理</button></td>
<td class="task-evidence">`docs/QA/user-flow-closure-audit-2026-06-10.md`</td>
<td>需确认优先级、负责人和截止日期</td>
<td><span class="counted-no">未完成</span></td>
</tr>
<tr class="task-row"
data-status="done"
data-owner="Codex"
data-owner-missing="false"
data-due="2026-06-10"
data-due-state="future"
data-phase="do"
data-workstream="frontend"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="true"
data-search="t-009 上传域名根目录校验文件 do done codex 2026-06-10 `https://guide.whaoyue.com/gpl0svkeao.txt` 返回 `b772216640a14171ba5655085c8523be`">
<td class="task-id">T-009</td>
<td class="task-title">上传域名根目录校验文件</td>
<td>执行</td>
<td><span class="status-pill status-done">已完成</span></td>
<td>Codex</td>
<td>2026-06-10</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="frontend">小程序前端</button></td>
<td class="task-evidence">`https://guide.whaoyue.com/gpL0svkeao.txt` 返回 `b772216640a14171ba5655085c8523be`</td>
<td>待确认</td>
<td><span class="counted-yes"></span></td>
</tr>
<tr class="task-row"
data-status="done"
data-owner="Codex"
data-owner-missing="false"
data-due="2026-06-12"
data-due-state="future"
data-phase="do"
data-workstream="frontend"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="true"
data-search="t-010 部署 2026-06-12 h5 功能更新 do done codex 2026-06-12 `pnpm type-check``pnpm build:h5`;线上备份 `/dmdata/nginx/html/_backups/guide-before-deploy-20260612032520.tar.gz`;首页引用 `index-da0rb3mi.js`">
<td class="task-id">T-010</td>
<td class="task-title">部署 2026-06-12 H5 功能更新</td>
<td>执行</td>
<td><span class="status-pill status-done">已完成</span></td>
<td>Codex</td>
<td>2026-06-12</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="frontend">小程序前端</button></td>
<td class="task-evidence">`pnpm type-check``pnpm build:h5`;线上备份 `/dmdata/nginx/html/_backups/guide-before-deploy-20260612032520.tar.gz`;首页引用 `index-Da0rB3mi.js`</td>
<td>待确认</td>
<td><span class="counted-yes"></span></td>
</tr>
<tr class="task-row"
data-status="done"
data-owner="Codex"
data-owner-missing="false"
data-due="2026-06-12"
data-due-state="future"
data-phase="check"
data-workstream="frontend"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="true"
data-search="t-011 复核 2026-06-12 当前工作区质量门 check done codex 2026-06-12 `pnpm type-check` 通过;`pnpm lint` 通过且仅有 18 个 warning`pnpm build:h5` 通过并复制 `static/nav-assets``pnpm build:mp-weixin` 通过 sass legacy api warning 不阻塞;小程序发布仍不在本轮 h5 部署范围">
<td class="task-id">T-011</td>
<td class="task-title">复核 2026-06-12 当前工作区质量门</td>
<td>检查</td>
<td><span class="status-pill status-done">已完成</span></td>
<td>Codex</td>
<td>2026-06-12</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="frontend">小程序前端</button></td>
<td class="task-evidence">`pnpm type-check` 通过;`pnpm lint` 通过且仅有 18 个 warning`pnpm build:h5` 通过并复制 `static/nav-assets``pnpm build:mp-weixin` 通过</td>
<td>Sass legacy API warning 不阻塞;小程序发布仍不在本轮 H5 部署范围</td>
<td><span class="counted-yes"></span></td>
</tr>
<tr class="task-row"
data-status="review"
data-owner="TBD"
data-owner-missing="true"
data-due="2026-06-13"
data-due-state="future"
data-phase="check"
data-workstream="navigation"
data-hard-blocked="false"
data-missing-evidence="false"
data-counted="false"
data-search="t-012 梳理讲解内容数据源改动和交付边界 check review tbd 2026-06-13 `src/data/providers/staticmuseumcontentprovider.ts` 从导览资源包 poi 派生展厅/展项;`src/components/explain/explainlist.vue` 显示暂无音频;`src/view-models/explainviewmodels.ts` 透出 tags 需人工浏览器/真实设备确认讲解列表、详情和搜索展示是否符合下一轮交付口径">
<td class="task-id">T-012</td>
<td class="task-title">梳理讲解内容数据源改动和交付边界</td>
<td>检查</td>
<td><span class="status-pill status-review">待复核</span></td>
<td>待确认</td>
<td>2026-06-13</td>
<td><button type="button" class="link-button workstream-filter" data-workstream-filter="navigation">三维导览路网</button></td>
<td class="task-evidence">`src/data/providers/staticMuseumContentProvider.ts` 从导览资源包 POI 派生展厅/展项;`src/components/explain/ExplainList.vue` 显示暂无音频;`src/view-models/explainViewModels.ts` 透出 tags</td>
<td>需人工浏览器/真实设备确认讲解列表、详情和搜索展示是否符合下一轮交付口径</td>
<td><span class="counted-no">未完成</span></td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="panel span-4">
<h2>高影响风险</h2>
<div class="item-list">
<article class="item-row">
<strong>H5 用户流程闭环仍有断点,影响真实到馆任务完成</strong>
<div><span>编号: R-001</span><span>状态: 开放</span><span>缓解措施: 按 `docs/QA/user-flow-closure-audit-2026-06-10.md` 拆分搜索、详情、路线、设施选择、返回/取消/重试任务</span><span>负责人: 待确认</span></div>
</article>
<article class="item-row">
<strong>模型静态资源缓存 30 天,错误资源上线后恢复慢</strong>
<div><span>编号: R-003</span><span>状态: 缓解中</span><span>缓解措施: 模型包路径带版本号;缺失资源返回 404部署时使用 URL 参数强刷前端包</span><span>负责人: Codex</span></div>
</article>
<article class="item-row">
<strong>讲解内容改为资源包 POI 派生后,若未补正式文案/音频和人工验收,可能造成讲解体验与验收口径不一致</strong>
<div><span>编号: R-005</span><span>状态: 开放</span><span>缓解措施: 保留“暂无音频”显性状态;将资源包 POI 派生内容作为过渡方案复核;进入发布前补人工浏览器/真实设备验收和正式内容接入计划</span><span>负责人: 待确认</span></div>
</article>
</div>
</section>
<section class="panel span-4">
<h2>下一步行动</h2>
<div class="item-list">
<article class="item-row">
<strong>对 guide.whaoyue.com 执行 fresh browser/真实设备馆内 3D 冒烟测试</strong>
<div><span>编号: A-001</span><span>负责人: 待确认</span><span>截止日期: 2026-06-11</span><span>来源: C-008 / R-002</span></div>
</article>
<article class="item-row">
<strong>将用户流程闭环审计拆成可执行修复任务并排期</strong>
<div><span>编号: A-002</span><span>负责人: 待确认</span><span>截止日期: 2026-06-11</span><span>来源: C-007 / R-001</span></div>
</article>
<article class="item-row">
<strong>确认下一轮发布验收标准和负责人</strong>
<div><span>编号: A-003</span><span>负责人: 待确认</span><span>截止日期: 2026-06-11</span><span>来源: M-005</span></div>
</article>
<article class="item-row">
<strong>梳理当前工作区未提交改动,明确本轮交付边界</strong>
<div><span>编号: A-005</span><span>负责人: 待确认</span><span>截止日期: 2026-06-11</span><span>来源: R-004</span></div>
</article>
<article class="item-row">
<strong>对 2026-06-12 新部署版本执行人工浏览器冒烟测试</strong>
<div><span>编号: A-006</span><span>负责人: 待确认</span><span>截止日期: 2026-06-13</span><span>来源: C-012 / C-013</span></div>
</article>
</div>
</section>
<section class="panel span-4">
<h2>决策待确认</h2>
<div class="item-list">
<article class="item-row">
<strong>是否以用户流程闭环作为下一轮发布准入</strong>
<div><span>编号: D-004</span><span>负责人: 待确认</span><span>需要日期: 2026-06-11</span><span>建议: 建议纳入准入</span></div>
</article>
<article class="item-row">
<strong>是否接受“从导览资源包 POI 派生讲解展厅/展项”作为正式内容接入前的过渡方案</strong>
<div><span>编号: D-005</span><span>负责人: 待确认</span><span>需要日期: 2026-06-13</span><span>建议: 建议仅作为过渡展示方案纳入,发布前必须明确“暂无音频”和正式内容接入计划</span></div>
</article>
</div>
</section>
<section class="panel span-4">
<h2>缺少证据</h2>
<div class="item-list">
<p class="empty">暂无缺少证据的完成/复核状态任务。</p>
</div>
</section>
</main>
<footer>由项目内 PDCA 进度工具生成 · 来源:项目管理目录</footer>
</div>
<script id="dashboard-data" type="application/json">{"language": "zh", "date": "2026-06-12", "risks": [{"id": "R-001", "risk": "H5 用户流程闭环仍有断点,影响真实到馆任务完成", "probability": "高", "impact": "高", "status": "开放", "mitigation": "按 `docs/QA/user-flow-closure-audit-2026-06-10.md` 拆分搜索、详情、路线、设施选择、返回/取消/重试任务", "owner": "待确认"}, {"id": "R-002", "risk": "fresh browser 复测未完成,可能漏掉真实设备上的 3D 渲染问题", "probability": "中", "impact": "中", "status": "观察中", "mitigation": "使用浏览器强刷、移动端视口和真实设备补测馆内 3D保留 HTTP 资源验证作为最低证据", "owner": "待确认"}, {"id": "R-003", "risk": "模型静态资源缓存 30 天,错误资源上线后恢复慢", "probability": "中", "impact": "高", "status": "缓解中", "mitigation": "模型包路径带版本号;缺失资源返回 404部署时使用 URL 参数强刷前端包", "owner": "Codex"}, {"id": "R-004", "risk": "项目仍有多处未提交/未归属改动,发布边界可能不清", "probability": "中", "impact": "中", "status": "观察中", "mitigation": "当前 `git status --short` 有 16 项变更/未跟踪记录;发布前整理变更清单,确认哪些进入本轮交付,哪些留作后续", "owner": "待确认"}, {"id": "R-005", "risk": "讲解内容改为资源包 POI 派生后,若未补正式文案/音频和人工验收,可能造成讲解体验与验收口径不一致", "probability": "中", "impact": "高", "status": "开放", "mitigation": "保留“暂无音频”显性状态;将资源包 POI 派生内容作为过渡方案复核;进入发布前补人工浏览器/真实设备验收和正式内容接入计划", "owner": "待确认"}]}</script>
<script>
(() => {
const payloadEl = document.getElementById("dashboard-data");
const payload = payloadEl ? JSON.parse(payloadEl.textContent || "{}") : {};
const table = document.getElementById("task-table");
const tbody = table ? table.querySelector("tbody") : null;
const rows = tbody ? Array.from(tbody.querySelectorAll("tr")) : [];
const search = document.getElementById("task-search");
const statusFilter = document.getElementById("status-filter");
const workstreamFilter = document.getElementById("workstream-filter");
const quickButtons = Array.from(document.querySelectorAll("[data-quick-filter]"));
const countEl = document.getElementById("visible-task-count");
const totalEl = document.getElementById("total-task-count");
const activeQuick = new Set();
let sortState = { key: "", direction: 1 };
if (totalEl) totalEl.textContent = String(rows.length);
function normalize(value) {
return String(value || "").trim().toLowerCase();
}
function rowMatchesQuick(row, key) {
if (key === "hard-blocked") return row.dataset.hardBlocked === "true";
if (key === "due-today") return row.dataset.dueState === "today";
if (key === "overdue") return row.dataset.dueState === "overdue";
if (key === "missing-owner") return row.dataset.ownerMissing === "true";
if (key === "missing-evidence") return row.dataset.missingEvidence === "true";
if (key === "uncounted") return row.dataset.counted !== "true";
return true;
}
function applyFilters() {
const query = normalize(search ? search.value : "");
const status = statusFilter ? statusFilter.value : "";
const workstream = workstreamFilter ? workstreamFilter.value : "";
let visible = 0;
rows.forEach((row) => {
const textMatch = !query || normalize(row.dataset.search).includes(query);
const statusMatch = !status || row.dataset.status === status;
const workstreamMatch = !workstream || row.dataset.workstream === workstream;
const quickMatch = Array.from(activeQuick).every((key) => rowMatchesQuick(row, key));
const match = textMatch && statusMatch && workstreamMatch && quickMatch;
row.hidden = !match;
if (match) visible += 1;
});
if (countEl) countEl.textContent = String(visible);
}
function cellValue(row, key) {
const map = {
id: 0,
title: 1,
phase: 2,
status: 3,
owner: 4,
due: 5,
workstream: 6,
evidence: 7,
blockers: 8,
counted: 9,
};
if (key === "due") {
const due = row.dataset.due || "";
return due && due.toUpperCase() !== "TBD" ? due : "9999-12-31";
}
const index = map[key] ?? 0;
return normalize(row.cells[index] ? row.cells[index].innerText : "");
}
function sortRows(key) {
if (!tbody) return;
sortState.direction = sortState.key === key ? sortState.direction * -1 : 1;
sortState.key = key;
rows
.slice()
.sort((a, b) => {
const av = cellValue(a, key);
const bv = cellValue(b, key);
return av.localeCompare(bv, undefined, { numeric: true, sensitivity: "base" }) * sortState.direction;
})
.forEach((row) => tbody.appendChild(row));
}
function setWorkstreamFilter(value) {
if (workstreamFilter) workstreamFilter.value = value || "";
applyFilters();
const details = document.getElementById("task-details");
if (details) details.scrollIntoView({ behavior: "smooth", block: "start" });
}
function showRiskDetail(id) {
const risk = (payload.risks || []).find((item) => item.id === id);
const panel = document.getElementById("risk-detail");
if (!panel || !risk) return;
panel.hidden = false;
panel.querySelector("[data-risk-title]").textContent = `${risk.id} · ${risk.risk || "-"}`;
panel.querySelector("[data-risk-meta]").textContent = `P: ${risk.probability || "-"} / I: ${risk.impact || "-"} / ${risk.status || "-"}`;
panel.querySelector("[data-risk-owner]").textContent = risk.owner || "-";
panel.querySelector("[data-risk-mitigation]").textContent = risk.mitigation || "-";
panel.focus();
}
function exportVisibleTasks() {
if (!table) return;
const headers = Array.from(table.querySelectorAll("thead th")).map((th) => `"${th.innerText.replace(/"/g, '""')}"`);
const lines = [headers.join(",")];
rows.filter((row) => !row.hidden).forEach((row) => {
const cells = Array.from(row.cells).map((cell) => `"${cell.innerText.replace(/"/g, '""')}"`);
lines.push(cells.join(","));
});
const blob = new Blob(["\uFEFF" + lines.join("\n")], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `pdca-visible-tasks-${payload.date || "snapshot"}.csv`;
a.click();
URL.revokeObjectURL(url);
}
[search, statusFilter, workstreamFilter].forEach((control) => {
if (control) control.addEventListener("input", applyFilters);
});
quickButtons.forEach((button) => {
button.addEventListener("click", () => {
const key = button.dataset.quickFilter;
if (activeQuick.has(key)) activeQuick.delete(key);
else activeQuick.add(key);
button.classList.toggle("is-active", activeQuick.has(key));
button.setAttribute("aria-pressed", activeQuick.has(key) ? "true" : "false");
applyFilters();
});
});
document.querySelectorAll("[data-sort]").forEach((button) => {
button.addEventListener("click", () => sortRows(button.dataset.sort));
});
document.querySelectorAll("[data-workstream], .workstream-filter").forEach((item) => {
item.addEventListener("click", () => setWorkstreamFilter(item.dataset.workstream || item.dataset.workstreamFilter || ""));
item.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
setWorkstreamFilter(item.dataset.workstream || item.dataset.workstreamFilter || "");
}
});
});
document.querySelectorAll("[data-risk-id]").forEach((button) => {
button.addEventListener("click", () => showRiskDetail(button.dataset.riskId));
});
const reset = document.getElementById("reset-filters");
if (reset) reset.addEventListener("click", () => {
if (search) search.value = "";
if (statusFilter) statusFilter.value = "";
if (workstreamFilter) workstreamFilter.value = "";
activeQuick.clear();
quickButtons.forEach((button) => {
button.classList.remove("is-active");
button.setAttribute("aria-pressed", "false");
});
applyFilters();
});
const printButton = document.getElementById("print-dashboard");
if (printButton) printButton.addEventListener("click", () => window.print());
const exportButton = document.getElementById("export-visible");
if (exportButton) exportButton.addEventListener("click", exportVisibleTasks);
applyFilters();
})();
</script>
</body>
</html>