1596 lines
65 KiB
HTML
1596 lines
65 KiB
HTML
<!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) 78%, #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-10</div>
|
||
<div>项目: E:\MyWork\深圳国际艺术馆\museum-guide\museum-guide-v4.0\frontend-miniapp</div>
|
||
<div>Git: master@a90f63c · 变更文件数:22</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">9</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">2 个任务有阻塞/依赖说明(不等同硬阻塞);2 个高影响风险仍开放;1 个决策待确认</p>
|
||
</section>
|
||
|
||
<section class="panel span-4">
|
||
<h2>任务完成率</h2>
|
||
<div class="ring-wrap">
|
||
<div class="ring" aria-label="78%"><span>78%</span></div>
|
||
<div>
|
||
<p>已完成: 7/9</p>
|
||
<p class="subtitle">进行中/待复核: 0 · 硬阻塞: 0</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel span-12">
|
||
<h2>任务总览</h2>
|
||
<div class="metrics">
|
||
|
||
<div class="metric metric-neutral">
|
||
<span>任务总览</span>
|
||
<strong>9</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-good">
|
||
<span>已完成(有证据)</span>
|
||
<strong>7</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-good">
|
||
<span>完成但缺证据</span>
|
||
<strong>0</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-info">
|
||
<span>进行中/待复核</span>
|
||
<strong>0</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-good">
|
||
<span>硬阻塞</span>
|
||
<strong>0</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-warn">
|
||
<span>阻塞/依赖说明</span>
|
||
<strong>2</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-warn">
|
||
<span>风险</span>
|
||
<strong>4</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-neutral">
|
||
<span>检查</span>
|
||
<strong>通过 8 / 未通过 1</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-info">
|
||
<span>行动</span>
|
||
<strong>5</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-info">
|
||
<span>里程碑</span>
|
||
<strong>5</strong>
|
||
</div>
|
||
|
||
|
||
<div class="metric metric-warn">
|
||
<span>决策待确认</span>
|
||
<strong>1</strong>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel span-12">
|
||
<h2>专业状态维度</h2>
|
||
<div class="dimension-grid">
|
||
|
||
<article class="dimension-card status-yellow">
|
||
<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-good" 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>已验证完成 6/6</span>
|
||
<span>阻塞 0</span>
|
||
<span>关注项 0</span>
|
||
<span>下一到期 待确认</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 JSON;L1.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-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:78%;" title="已完成: 7"></span><span class="segment segment-todo" style="width:22%;" title="待办: 2"></span></div>
|
||
<div class="legend">
|
||
<span class="done">已完成 7</span>
|
||
<span class="doing">进行中 0</span>
|
||
<span class="review">待复核 0</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></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>6</strong>
|
||
<small>已完成 5/6</small>
|
||
</div>
|
||
|
||
|
||
<div class="phase phase-check">
|
||
<span>检查</span>
|
||
<strong>1</strong>
|
||
<small>已完成 0/1</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>1 个检查待完成</li><li>2 个高影响风险仍开放</li><li>1 个决策待确认</li>
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
<h3>最近检查</h3>
|
||
|
||
<article class="quality-row result-pass">
|
||
<span class="quality-result">通过</span>
|
||
<strong>根目录 TXT 校验文件公网访问</strong>
|
||
<small>2026-06-10 · C-010</small>
|
||
</article>
|
||
|
||
<article class="quality-row result-pass">
|
||
<span class="quality-result">通过</span>
|
||
<strong>PDCA 报告与面板生成</strong>
|
||
<small>2026-06-10 · C-009</small>
|
||
</article>
|
||
|
||
<article class="quality-row result-pending">
|
||
<span class="quality-result">待检查</span>
|
||
<strong>in-app browser fresh 3D 复测</strong>
|
||
<small>2026-06-10 · C-008</small>
|
||
</article>
|
||
|
||
<article class="quality-row result-fail">
|
||
<span class="quality-result">未通过</span>
|
||
<strong>H5 用户流程闭环审计</strong>
|
||
<small>2026-06-10 · C-007</small>
|
||
</article>
|
||
|
||
<article class="quality-row result-pass">
|
||
<span class="quality-result">通过</span>
|
||
<strong>缺失模型资源处理</strong>
|
||
<small>2026-06-10 · C-006</small>
|
||
</article>
|
||
|
||
<article class="quality-row result-pass">
|
||
<span class="quality-result">通过</span>
|
||
<strong>楼层 GLB 公网响应</strong>
|
||
<small>2026-06-10 · C-005</small>
|
||
</article>
|
||
|
||
</div>
|
||
|
||
</section>
|
||
|
||
<section class="panel span-6">
|
||
<h2>阻塞与逾期</h2>
|
||
<div class="item-list">
|
||
<p class="empty">暂无阻塞或逾期任务。</p>
|
||
</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="floor"` 和当前楼层 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="floor"` 和当前楼层 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"
|
||
data-status="todo"
|
||
data-owner="TBD"
|
||
data-owner-missing="true"
|
||
data-due="2026-06-11"
|
||
data-due-state="future"
|
||
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>
|
||
|
||
</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>
|
||
|
||
</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>监控模型资源缓存与 404 行为,确认无 `<!DOCTYPE` JSON 解析报错复发</strong>
|
||
<div><span>编号: A-004</span><span>负责人: 待确认</span><span>截止日期: 2026-06-11</span><span>来源: R-003</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>
|
||
|
||
</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>
|
||
|
||
</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-10", "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": "发布前整理变更清单,确认哪些进入本轮交付,哪些留作后续", "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>
|