1266 lines
36 KiB
HTML
1266 lines
36 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}审核指挥台原型{% endblock %}
|
||
|
||
{% block content %}
|
||
<style>
|
||
:root {
|
||
--cmd-navy: #06345d;
|
||
--cmd-navy-deep: #042848;
|
||
--cmd-blue: #176fe8;
|
||
--cmd-line: #dce5ef;
|
||
--cmd-page: #f3f6fa;
|
||
--cmd-ink: #172033;
|
||
--cmd-muted: #657386;
|
||
--cmd-danger: #d92d20;
|
||
--cmd-warning: #f08a18;
|
||
--cmd-success: #1f9d62;
|
||
}
|
||
|
||
body { background: var(--cmd-page); }
|
||
.topbar { display: none; }
|
||
.page {
|
||
width: 100%;
|
||
margin: 0;
|
||
min-height: 100vh;
|
||
display: block;
|
||
}
|
||
|
||
.command-shell {
|
||
min-height: 100vh;
|
||
display: grid;
|
||
grid-template-columns: 156px minmax(0, 1fr);
|
||
background: var(--cmd-page);
|
||
color: var(--cmd-ink);
|
||
}
|
||
|
||
.command-side {
|
||
background: linear-gradient(180deg, var(--cmd-navy) 0%, var(--cmd-navy-deep) 100%);
|
||
color: #dcecff;
|
||
display: grid;
|
||
grid-template-rows: auto 1fr auto;
|
||
min-height: 100vh;
|
||
box-shadow: 6px 0 22px rgba(10, 31, 68, 0.18);
|
||
}
|
||
|
||
.side-menu-button {
|
||
width: 44px;
|
||
height: 44px;
|
||
margin: 12px 0 8px 12px;
|
||
border: 0;
|
||
background: transparent;
|
||
color: #fff;
|
||
font-size: 1.3rem;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.side-nav {
|
||
display: grid;
|
||
align-content: start;
|
||
gap: 4px;
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.side-link {
|
||
display: grid;
|
||
grid-template-columns: 28px minmax(0, 1fr) auto;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-height: 48px;
|
||
padding: 0 12px;
|
||
color: #dcecff;
|
||
border-left: 3px solid transparent;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.side-link:hover,
|
||
.side-link.active {
|
||
background: rgba(23, 111, 232, 0.26);
|
||
border-left-color: #5ea8ff;
|
||
color: #fff;
|
||
}
|
||
|
||
.side-icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
border: 1px solid rgba(255, 255, 255, 0.36);
|
||
border-radius: 6px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.nav-badge {
|
||
min-width: 20px;
|
||
height: 20px;
|
||
border-radius: 999px;
|
||
background: #f04438;
|
||
color: #fff;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.76rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.collapse-link {
|
||
padding: 18px 14px 24px;
|
||
color: #dcecff;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
||
font-size: 0.92rem;
|
||
}
|
||
|
||
.command-main {
|
||
min-width: 0;
|
||
display: grid;
|
||
grid-template-rows: auto auto minmax(0, 1fr);
|
||
}
|
||
|
||
.command-top {
|
||
height: 74px;
|
||
background: #fff;
|
||
border-bottom: 1px solid var(--cmd-line);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 20px;
|
||
padding: 0 28px;
|
||
}
|
||
|
||
.brand-lockup {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
min-width: 360px;
|
||
}
|
||
|
||
.shield-mark {
|
||
width: 38px;
|
||
height: 38px;
|
||
border-radius: 10px;
|
||
background: #eaf3ff;
|
||
color: var(--cmd-blue);
|
||
border: 1px solid #cfe3ff;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.brand-title {
|
||
margin: 0;
|
||
font-size: 1.12rem;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.brand-subtitle {
|
||
margin: 3px 0 0;
|
||
color: var(--cmd-muted);
|
||
font-size: 0.82rem;
|
||
}
|
||
|
||
.top-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 18px;
|
||
color: #29364a;
|
||
min-width: 0;
|
||
}
|
||
|
||
.top-action {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
color: #29364a;
|
||
white-space: nowrap;
|
||
border: 0;
|
||
background: transparent;
|
||
min-height: auto;
|
||
padding: 0;
|
||
}
|
||
|
||
.notice-dot {
|
||
position: absolute;
|
||
top: -10px;
|
||
left: 12px;
|
||
min-width: 18px;
|
||
height: 18px;
|
||
padding: 0 5px;
|
||
border-radius: 999px;
|
||
background: #f04438;
|
||
color: #fff;
|
||
display: inline-flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
font-size: 0.7rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.user-chip {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding-left: 10px;
|
||
border-left: 1px solid var(--cmd-line);
|
||
}
|
||
|
||
.avatar {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 999px;
|
||
background: #e8edf4;
|
||
color: #56657a;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.batch-strip {
|
||
min-height: 52px;
|
||
background: #fff;
|
||
border-bottom: 1px solid var(--cmd-line);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 26px;
|
||
padding: 0 28px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.batch-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: var(--cmd-muted);
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.batch-item strong { color: var(--cmd-ink); }
|
||
.select-lite {
|
||
border: 1px solid var(--cmd-line);
|
||
background: #f8fafc;
|
||
border-radius: 6px;
|
||
min-height: 30px;
|
||
padding: 0 26px 0 10px;
|
||
color: #344054;
|
||
width: auto;
|
||
}
|
||
|
||
.status-live {
|
||
color: var(--cmd-success);
|
||
background: #eaf8f0;
|
||
border: 1px solid #c8ecd7;
|
||
border-radius: 5px;
|
||
padding: 4px 8px;
|
||
font-size: 0.82rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.command-content {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 304px;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
align-items: start;
|
||
}
|
||
|
||
.content-left,
|
||
.content-right {
|
||
display: grid;
|
||
gap: 12px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.cmd-section {
|
||
min-width: 0;
|
||
background: #fff;
|
||
border: 1px solid var(--cmd-line);
|
||
border-radius: 6px;
|
||
padding: 14px;
|
||
}
|
||
|
||
.section-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.section-name {
|
||
margin: 0;
|
||
font-size: 1rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.cmd-button-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.cmd-btn {
|
||
min-height: 34px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--cmd-line);
|
||
background: #fff;
|
||
color: #2c3a4e;
|
||
padding: 0 12px;
|
||
font-size: 0.88rem;
|
||
}
|
||
|
||
.cmd-btn.primary {
|
||
background: var(--cmd-blue);
|
||
border-color: var(--cmd-blue);
|
||
color: #fff;
|
||
}
|
||
|
||
.metric-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.cmd-metric {
|
||
border: 1px solid var(--cmd-line);
|
||
border-radius: 6px;
|
||
padding: 16px;
|
||
min-height: 132px;
|
||
background: #fff;
|
||
}
|
||
|
||
.metric-heading {
|
||
color: #344054;
|
||
font-weight: 700;
|
||
font-size: 0.92rem;
|
||
}
|
||
|
||
.score-line {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 4px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.score-main {
|
||
font-size: 2.15rem;
|
||
line-height: 1;
|
||
font-weight: 800;
|
||
color: var(--cmd-danger);
|
||
}
|
||
|
||
.score-main.dark { color: #182235; }
|
||
.score-main.blue { color: var(--cmd-blue); }
|
||
|
||
.score-suffix {
|
||
color: var(--cmd-muted);
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.level-tag,
|
||
.cmd-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 4px;
|
||
min-height: 24px;
|
||
padding: 0 8px;
|
||
font-size: 0.8rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.level-tag { background: #fff0ed; color: var(--cmd-danger); }
|
||
.metric-note { color: var(--cmd-muted); margin-top: 14px; font-size: 0.84rem; }
|
||
|
||
.donut-row {
|
||
display: grid;
|
||
grid-template-columns: 72px minmax(0, 1fr);
|
||
gap: 14px;
|
||
align-items: center;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.donut {
|
||
width: 70px;
|
||
height: 70px;
|
||
border-radius: 999px;
|
||
background: conic-gradient(var(--cmd-success) 0 45%, var(--cmd-warning) 45% 74%, var(--cmd-danger) 74% 100%);
|
||
position: relative;
|
||
}
|
||
|
||
.donut::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 14px;
|
||
border-radius: 999px;
|
||
background: #fff;
|
||
}
|
||
|
||
.mini-legend {
|
||
display: grid;
|
||
gap: 7px;
|
||
color: #344054;
|
||
font-size: 0.82rem;
|
||
}
|
||
|
||
.legend-row {
|
||
display: grid;
|
||
grid-template-columns: 12px minmax(0, 1fr) auto;
|
||
gap: 7px;
|
||
align-items: center;
|
||
}
|
||
|
||
.legend-dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 999px;
|
||
background: var(--cmd-success);
|
||
}
|
||
|
||
.legend-dot.warning { background: var(--cmd-warning); }
|
||
.legend-dot.danger { background: var(--cmd-danger); }
|
||
|
||
.risk-counts {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 8px;
|
||
margin-top: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.risk-num {
|
||
display: block;
|
||
font-size: 1.65rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.danger-text { color: var(--cmd-danger); }
|
||
.warning-text { color: var(--cmd-warning); }
|
||
.success-text { color: var(--cmd-success); }
|
||
|
||
.progress-track {
|
||
width: 100%;
|
||
height: 9px;
|
||
background: #e8eef6;
|
||
border-radius: 999px;
|
||
overflow: hidden;
|
||
margin-top: 18px;
|
||
}
|
||
|
||
.progress-fill {
|
||
width: 46%;
|
||
height: 100%;
|
||
background: var(--cmd-blue);
|
||
}
|
||
|
||
.flow-line {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||
gap: 8px;
|
||
padding: 4px 0 2px;
|
||
}
|
||
|
||
.flow-step {
|
||
position: relative;
|
||
display: grid;
|
||
grid-template-columns: 32px minmax(0, 1fr);
|
||
gap: 8px;
|
||
align-items: start;
|
||
}
|
||
|
||
.flow-step::after {
|
||
content: "";
|
||
position: absolute;
|
||
top: 15px;
|
||
left: 32px;
|
||
right: -8px;
|
||
height: 2px;
|
||
background: #cfe0f5;
|
||
}
|
||
|
||
.flow-step:last-child::after { display: none; }
|
||
|
||
.flow-index {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 999px;
|
||
border: 1px solid #c8d5e5;
|
||
background: #fff;
|
||
color: #7b8797;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 800;
|
||
font-size: 0.82rem;
|
||
}
|
||
|
||
.flow-step.done .flow-index {
|
||
background: #f0f7ff;
|
||
border-color: #7fb4f5;
|
||
color: var(--cmd-blue);
|
||
}
|
||
|
||
.flow-step.active .flow-index {
|
||
background: var(--cmd-blue);
|
||
color: #fff;
|
||
border-color: var(--cmd-blue);
|
||
}
|
||
|
||
.flow-title {
|
||
font-weight: 800;
|
||
font-size: 0.88rem;
|
||
color: #344054;
|
||
}
|
||
|
||
.flow-date {
|
||
margin-top: 5px;
|
||
color: var(--cmd-muted);
|
||
font-size: 0.78rem;
|
||
}
|
||
|
||
.tabs {
|
||
display: flex;
|
||
gap: 28px;
|
||
border-bottom: 1px solid var(--cmd-line);
|
||
padding: 0 14px;
|
||
background: #fff;
|
||
}
|
||
|
||
.tab-btn {
|
||
min-height: 44px;
|
||
padding: 0;
|
||
border: 0;
|
||
border-bottom: 2px solid transparent;
|
||
border-radius: 0;
|
||
background: transparent;
|
||
color: #344054;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.tab-btn.active {
|
||
color: var(--cmd-blue);
|
||
border-bottom-color: var(--cmd-blue);
|
||
}
|
||
|
||
.filter-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
color: var(--cmd-muted);
|
||
font-size: 0.86rem;
|
||
}
|
||
|
||
.filter-control {
|
||
width: 134px;
|
||
min-height: 34px;
|
||
padding: 0 10px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--cmd-line);
|
||
background: #fff;
|
||
color: #344054;
|
||
}
|
||
|
||
.search-field {
|
||
width: 260px;
|
||
min-height: 34px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--cmd-line);
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.audit-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.86rem;
|
||
}
|
||
|
||
.audit-table th,
|
||
.audit-table td {
|
||
padding: 10px 9px;
|
||
border-bottom: 1px solid #edf1f6;
|
||
text-align: left;
|
||
vertical-align: middle;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.audit-table th {
|
||
background: #f6f8fb;
|
||
color: #5f6d80;
|
||
font-size: 0.8rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.cmd-tag.success { color: var(--cmd-success); background: #eaf8f0; }
|
||
.cmd-tag.warning { color: var(--cmd-warning); background: #fff4e5; }
|
||
.cmd-tag.danger { color: var(--cmd-danger); background: #fff0ed; }
|
||
.link-blue { color: var(--cmd-blue); font-weight: 700; }
|
||
|
||
.pager {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
padding-top: 14px;
|
||
color: var(--cmd-muted);
|
||
font-size: 0.84rem;
|
||
}
|
||
|
||
.page-btn,
|
||
.page-box {
|
||
min-width: 30px;
|
||
height: 30px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--cmd-line);
|
||
background: #fff;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.page-btn.active {
|
||
color: var(--cmd-blue);
|
||
border-color: var(--cmd-blue);
|
||
}
|
||
|
||
.risk-summary {
|
||
border: 1px solid #f2b8b5;
|
||
background: #fff3f1;
|
||
border-radius: 6px;
|
||
padding: 18px;
|
||
}
|
||
|
||
.risk-alert-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin: 0 0 12px;
|
||
color: var(--cmd-danger);
|
||
font-size: 1.1rem;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.alert-mark {
|
||
width: 26px;
|
||
height: 26px;
|
||
border-radius: 999px;
|
||
background: var(--cmd-danger);
|
||
color: #fff;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.risk-summary p {
|
||
margin: 0 0 10px;
|
||
color: #455166;
|
||
font-size: 0.9rem;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.side-panel {
|
||
background: #fff;
|
||
border: 1px solid var(--cmd-line);
|
||
border-radius: 6px;
|
||
padding: 14px;
|
||
}
|
||
|
||
.side-panel-title {
|
||
margin: 0 0 12px;
|
||
font-size: 0.98rem;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.issue-list,
|
||
.action-list,
|
||
.owner-list,
|
||
.op-list {
|
||
display: grid;
|
||
gap: 10px;
|
||
margin: 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
}
|
||
|
||
.issue-item {
|
||
display: grid;
|
||
grid-template-columns: 10px minmax(0, 1fr) auto;
|
||
gap: 8px;
|
||
align-items: start;
|
||
font-size: 0.86rem;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.issue-dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 999px;
|
||
background: var(--cmd-danger);
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.action-item {
|
||
display: grid;
|
||
grid-template-columns: 18px minmax(0, 1fr) auto;
|
||
gap: 8px;
|
||
align-items: start;
|
||
font-size: 0.86rem;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.action-signal {
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 999px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #fff;
|
||
background: var(--cmd-warning);
|
||
font-size: 0.72rem;
|
||
font-weight: 800;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.action-signal.danger { background: var(--cmd-danger); }
|
||
.action-signal.success { background: var(--cmd-success); }
|
||
.item-state { color: #667085; white-space: nowrap; }
|
||
|
||
.owner-item {
|
||
display: grid;
|
||
grid-template-columns: 72px minmax(0, 1fr) auto;
|
||
gap: 8px;
|
||
align-items: center;
|
||
font-size: 0.86rem;
|
||
}
|
||
|
||
.owner-status {
|
||
padding: 3px 7px;
|
||
border-radius: 4px;
|
||
background: #eef4ff;
|
||
color: #386bd8;
|
||
font-size: 0.76rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.op-item {
|
||
display: grid;
|
||
grid-template-columns: 9px minmax(0, 1fr);
|
||
gap: 8px;
|
||
color: #344054;
|
||
font-size: 0.82rem;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.op-dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 999px;
|
||
background: var(--cmd-success);
|
||
margin-top: 7px;
|
||
}
|
||
|
||
.toast {
|
||
position: fixed;
|
||
right: 24px;
|
||
bottom: 24px;
|
||
min-width: 260px;
|
||
max-width: 360px;
|
||
padding: 13px 14px;
|
||
border-radius: 6px;
|
||
background: #101828;
|
||
color: #fff;
|
||
box-shadow: 0 14px 34px rgba(16, 24, 40, 0.24);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transform: translateY(8px);
|
||
transition: opacity 160ms ease, transform 160ms ease;
|
||
z-index: 30;
|
||
}
|
||
|
||
.toast.show {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.drawer-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(16, 24, 40, 0.36);
|
||
display: none;
|
||
z-index: 40;
|
||
}
|
||
|
||
.drawer-mask.open { display: block; }
|
||
|
||
.review-drawer {
|
||
position: fixed;
|
||
top: 0;
|
||
right: 0;
|
||
width: min(520px, 100vw);
|
||
height: 100vh;
|
||
background: #fff;
|
||
box-shadow: -16px 0 40px rgba(16, 24, 40, 0.2);
|
||
padding: 20px;
|
||
display: grid;
|
||
grid-template-rows: auto 1fr auto;
|
||
gap: 16px;
|
||
}
|
||
|
||
.drawer-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
align-items: start;
|
||
border-bottom: 1px solid var(--cmd-line);
|
||
padding-bottom: 14px;
|
||
}
|
||
|
||
.drawer-title {
|
||
margin: 0;
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.drawer-close {
|
||
border: 1px solid var(--cmd-line);
|
||
border-radius: 4px;
|
||
min-height: 32px;
|
||
padding: 0 10px;
|
||
background: #fff;
|
||
color: #344054;
|
||
}
|
||
|
||
.drawer-body {
|
||
display: grid;
|
||
align-content: start;
|
||
gap: 12px;
|
||
overflow: auto;
|
||
}
|
||
|
||
.drawer-kv {
|
||
border: 1px solid var(--cmd-line);
|
||
border-radius: 6px;
|
||
padding: 12px;
|
||
display: grid;
|
||
gap: 6px;
|
||
}
|
||
|
||
.hidden { display: none !important; }
|
||
|
||
@media (max-width: 1180px) {
|
||
.command-shell { grid-template-columns: 76px minmax(0, 1fr); }
|
||
.side-link { grid-template-columns: 28px; justify-content: center; }
|
||
.side-link span:not(.side-icon), .nav-badge, .collapse-link { display: none; }
|
||
.brand-lockup { min-width: 260px; }
|
||
.command-content { grid-template-columns: 1fr; }
|
||
.content-right { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
}
|
||
|
||
@media (max-width: 820px) {
|
||
html, body { overflow-x: hidden; }
|
||
.command-shell { grid-template-columns: 1fr; }
|
||
.command-side { display: none; }
|
||
.command-top { height: auto; padding: 14px; align-items: flex-start; flex-direction: column; }
|
||
.brand-lockup { min-width: 0; width: 100%; align-items: flex-start; }
|
||
.brand-title { font-size: 1rem; overflow-wrap: anywhere; }
|
||
.brand-subtitle { overflow-wrap: anywhere; }
|
||
.top-actions { width: 100%; gap: 12px; }
|
||
.user-chip { padding-left: 0; border-left: 0; }
|
||
.batch-strip { padding: 10px 14px; gap: 10px; align-items: flex-start; flex-direction: column; }
|
||
.batch-item { width: 100%; max-width: 100%; flex-wrap: wrap; }
|
||
.batch-item strong { overflow-wrap: anywhere; }
|
||
.metric-row, .flow-line, .content-right { grid-template-columns: 1fr; }
|
||
.flow-step::after { display: none; }
|
||
.tabs { overflow-x: auto; gap: 18px; }
|
||
.command-content { padding: 8px; }
|
||
.section-bar { align-items: flex-start; flex-direction: column; }
|
||
.cmd-button-row { width: 100%; display: grid; grid-template-columns: 1fr; }
|
||
.cmd-btn { width: 100%; min-width: 0; white-space: nowrap; }
|
||
.donut-row { grid-template-columns: 1fr; }
|
||
.mini-legend { width: 100%; }
|
||
.legend-row { grid-template-columns: 12px minmax(0, 1fr); }
|
||
.legend-row strong { grid-column: 2; }
|
||
.filter-control, .search-field { width: 100%; }
|
||
.audit-table { min-width: 900px; }
|
||
}
|
||
</style>
|
||
|
||
<div class="command-shell">
|
||
<aside class="command-side" aria-label="主导航">
|
||
<div>
|
||
<button class="side-menu-button" type="button" data-action="toast" data-message="菜单已收起为当前原型导航。">=</button>
|
||
<nav class="side-nav">
|
||
<a class="side-link active" href="{% url 'platform_ui:command-center-v2' %}"><span class="side-icon">W</span><span>工作台</span></a>
|
||
<a class="side-link" href="{% url 'scenarios:index' %}"><span class="side-icon">T</span><span>任务中心</span><span class="nav-badge">8</span></a>
|
||
<a class="side-link" href="{% url 'documents:list' %}"><span class="side-icon">D</span><span>资料包管理</span></a>
|
||
<a class="side-link" href="{% url 'platform_ui:knowledge-base' %}"><span class="side-icon">R</span><span>法规库</span></a>
|
||
<a class="side-link" href="{% url 'chat:index' 'document_review' %}"><span class="side-icon">A</span><span>智能审核</span></a>
|
||
<a class="side-link" href="#"><span class="side-icon">!</span><span>风险管理</span></a>
|
||
<a class="side-link" href="#"><span class="side-icon">Q</span><span>问题清单</span></a>
|
||
<a class="side-link" href="{% url 'platform_ui:mcp-center' %}"><span class="side-icon">F</span><span>飞书通知</span></a>
|
||
<a class="side-link" href="{% url 'audit:list' %}"><span class="side-icon">S</span><span>统计分析</span></a>
|
||
<a class="side-link" href="#"><span class="side-icon">G</span><span>系统设置</span></a>
|
||
</nav>
|
||
</div>
|
||
<a class="collapse-link" href="#" data-action="toast" data-message="已模拟收起菜单。">< 收起菜单</a>
|
||
</aside>
|
||
|
||
<section class="command-main">
|
||
<header class="command-top">
|
||
<div class="brand-lockup">
|
||
<div class="shield-mark">RA</div>
|
||
<div>
|
||
<h1 class="brand-title">试剂盒临床注册文件准备与审核智能体平台</h1>
|
||
<p class="brand-subtitle">NMPA IVD 注册审评辅助平台</p>
|
||
</div>
|
||
</div>
|
||
<div class="top-actions">
|
||
<button class="top-action" type="button" data-action="toast" data-message="已打开告警中心,共 12 条提醒。"><span>铃</span><span class="notice-dot">12</span></button>
|
||
<button class="top-action" type="button" data-action="toast" data-message="飞书通知队列正常,最近一次回传成功。">飞书通知</button>
|
||
<button class="top-action" type="button" data-action="toast" data-message="帮助中心会解释规则、证据和审计字段。">帮助</button>
|
||
<div class="user-chip">
|
||
<span class="avatar">张</span>
|
||
<div>
|
||
<strong>{{ command_batch.reviewer }}</strong>
|
||
<div class="brand-subtitle">{{ command_batch.role }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="batch-strip">
|
||
<span class="batch-item">当前批次:<strong>{{ command_batch.id }}</strong></span>
|
||
<span class="status-live">{{ command_batch.status }}</span>
|
||
<span class="batch-item">申报类型:<select class="select-lite" aria-label="申报类型"><option>{{ command_batch.workflow }}</option><option>变更注册</option><option>延续注册</option></select></span>
|
||
<span class="batch-item">注册分类:<strong>{{ command_batch.class }}</strong></span>
|
||
<span class="batch-item">创建时间:<strong>{{ command_batch.created_at }}</strong></span>
|
||
<span class="batch-item">申报主体:<strong>{{ command_batch.applicant }}</strong></span>
|
||
</div>
|
||
|
||
<div class="command-content">
|
||
<div class="content-left">
|
||
<section class="cmd-section">
|
||
<div class="section-bar">
|
||
<h2 class="section-name">资料包总览</h2>
|
||
<div class="cmd-button-row">
|
||
<button class="cmd-btn primary" type="button" data-action="toast" data-message="已发起完整性核查,Agent Core 将重新装载规则版本 {{ command_batch.version }}。">发起完整性核查</button>
|
||
<button class="cmd-btn" type="button" data-action="toast" data-message="已生成风险报告预览,当前结论为不通过。">生成风险报告</button>
|
||
<button class="cmd-btn" type="button" data-action="toast" data-message="已模拟导出 Word 报告,路径写入审计记录。">导出 Word</button>
|
||
<button class="cmd-btn" type="button" data-action="toast" data-message="已向张审评员、李组长、王法规发送飞书卡片。">通知责任人</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="metric-row">
|
||
<article class="cmd-metric">
|
||
<div class="metric-heading">{{ command_metrics.0.label }}</div>
|
||
<div class="score-line"><span class="score-main">{{ command_metrics.0.value }}</span><span class="score-suffix">{{ command_metrics.0.suffix }}</span></div>
|
||
<div class="metric-note">完整性评级:<span class="level-tag">{{ command_metrics.0.level }}</span></div>
|
||
<div class="metric-note">{{ command_metrics.0.detail }}</div>
|
||
</article>
|
||
|
||
<article class="cmd-metric">
|
||
<div class="metric-heading">{{ command_metrics.1.label }}</div>
|
||
<div class="donut-row">
|
||
<div class="donut" aria-hidden="true"></div>
|
||
<div class="mini-legend">
|
||
{% for segment in command_metrics.1.segments %}
|
||
<div class="legend-row">
|
||
<span class="legend-dot{% if forloop.counter == 2 %} warning{% elif forloop.counter == 3 %} danger{% endif %}"></span>
|
||
<span>{{ segment.label }}</span>
|
||
<strong>{{ segment.value }} ({{ segment.hint }})</strong>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="metric-note">共 {{ command_metrics.1.value }} {{ command_metrics.1.suffix }}</div>
|
||
</article>
|
||
|
||
<article class="cmd-metric">
|
||
<div class="metric-heading">{{ command_metrics.2.label }}</div>
|
||
<div class="risk-counts">
|
||
<div><span class="risk-num danger-text">3</span><span>高风险</span></div>
|
||
<div><span class="risk-num warning-text">7</span><span>中风险</span></div>
|
||
<div><span class="risk-num success-text">12</span><span>低风险</span></div>
|
||
</div>
|
||
<div class="metric-note">共 {{ command_metrics.2.value }} {{ command_metrics.2.suffix }}</div>
|
||
</article>
|
||
|
||
<article class="cmd-metric">
|
||
<div class="metric-heading">{{ command_metrics.3.label }}</div>
|
||
<div class="score-line"><span class="score-main dark">{{ command_metrics.3.value }}</span><span class="score-suffix">{{ command_metrics.3.suffix }}</span></div>
|
||
<div class="progress-track"><div class="progress-fill"></div></div>
|
||
<div class="metric-note">{{ command_metrics.3.detail }}</div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="cmd-section">
|
||
<h2 class="section-name">注册申报流程</h2>
|
||
<div class="flow-line" style="margin-top: 14px;">
|
||
{% for step in command_flow %}
|
||
<div class="flow-step {{ step.state }}">
|
||
<div class="flow-index">{{ step.step }}</div>
|
||
<div>
|
||
<div class="flow-title">{{ step.title }}</div>
|
||
<div class="flow-date">{{ step.date }}</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
|
||
<section class="cmd-section" style="padding: 0;">
|
||
<div class="tabs" role="tablist">
|
||
{% for tab in command_tabs %}
|
||
<button class="tab-btn{% if forloop.first %} active{% endif %}" type="button" data-tab="{{ tab.id }}">{{ tab.label }}</button>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<div style="padding: 14px;">
|
||
<div class="filter-row">
|
||
<span>检查标准:{{ command_batch.standard }}</span>
|
||
<span style="margin-left: auto;">检查版本:{{ command_batch.version }}</span>
|
||
<button class="cmd-btn" type="button" data-action="toast" data-message="已切换到可选规则版本列表。">切换版本</button>
|
||
</div>
|
||
<div class="filter-row" style="margin-top: 12px;">
|
||
<select class="filter-control" data-filter="chapter"><option>全部章节</option><option>CH1 监管信息</option><option>CH4 临床评价资料</option></select>
|
||
<select class="filter-control" data-filter="risk"><option>全部状态</option><option>高风险</option><option>中风险</option><option>低风险</option></select>
|
||
<input class="search-field" type="text" placeholder="请输入检查项或依据条款" data-filter="search">
|
||
</div>
|
||
|
||
<div class="table-wrap" style="margin-top: 12px;">
|
||
<table class="audit-table" id="command-check-table">
|
||
<thead>
|
||
<tr>
|
||
<th>章节</th>
|
||
<th>检查项</th>
|
||
<th>依据条款</th>
|
||
<th>状态</th>
|
||
<th>风险等级</th>
|
||
<th>问题描述</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in command_checks %}
|
||
<tr data-risk="{{ row.risk }}" data-text="{{ row.chapter }} {{ row.item }} {{ row.rule }} {{ row.problem }}">
|
||
<td>{{ row.chapter }}</td>
|
||
<td>{{ row.item }}</td>
|
||
<td>{{ row.rule }}</td>
|
||
<td><span class="cmd-tag {% if row.status == '完整' %}success{% elif row.status == '部分缺失' %}warning{% else %}danger{% endif %}">{{ row.status }}</span></td>
|
||
<td><span class="cmd-tag {% if row.risk == '低风险' %}success{% elif row.risk == '中风险' %}warning{% else %}danger{% endif %}">{{ row.risk }}</span></td>
|
||
<td>{{ row.problem }}</td>
|
||
<td><button class="top-action link-blue" type="button" data-action="drawer" data-title="{{ row.item }}" data-risk="{{ row.risk }}" data-problem="{{ row.problem }}" data-rule="{{ row.rule }}">查看</button></td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="pager">
|
||
<span>共 62 条</span>
|
||
<span class="page-box"><</span>
|
||
<span class="page-btn active">1</span>
|
||
<span class="page-btn">2</span>
|
||
<span class="page-btn">3</span>
|
||
<span class="page-btn">4</span>
|
||
<span class="page-box">></span>
|
||
<span class="page-box">10 条/页</span>
|
||
<span>跳至</span>
|
||
<span class="page-box">1</span>
|
||
<span>页</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<aside class="content-right">
|
||
<section class="side-panel">
|
||
<h2 class="side-panel-title">风险准入结论(审评结论)</h2>
|
||
<div class="risk-summary">
|
||
<h3 class="risk-alert-title"><span class="alert-mark">!</span>不通过:存在 3 项高风险</h3>
|
||
<p>存在高风险问题,建议退回企业补充完善后重新提交。</p>
|
||
<p>高风险项:3</p>
|
||
<p>中风险项:7</p>
|
||
<p>低风险项:12</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="side-panel">
|
||
<h2 class="side-panel-title">关键问题概览</h2>
|
||
<ul class="issue-list">
|
||
{% for risk in key_risks %}
|
||
<li class="issue-item">
|
||
<span class="issue-dot"></span>
|
||
<span>{{ risk.title }}</span>
|
||
<span class="cmd-tag {% if risk.level == '高风险' %}danger{% else %}warning{% endif %}">{{ risk.level }}</span>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
<button class="top-action link-blue" type="button" style="margin-top: 12px;" data-action="toast" data-message="已筛选全部 22 项风险。">查看全部(22)</button>
|
||
</section>
|
||
|
||
<section class="side-panel">
|
||
<h2 class="side-panel-title">下一步行动</h2>
|
||
<ul class="action-list">
|
||
{% for action in next_actions %}
|
||
<li class="action-item">
|
||
<span class="action-signal {{ action.tone }}">{% if action.tone == 'success' %}✓{% else %}!{% endif %}</span>
|
||
<span><strong>{{ action.title }}</strong><br><span class="brand-subtitle">{{ action.detail }}</span></span>
|
||
<span class="item-state">{{ action.state }}</span>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</section>
|
||
|
||
<section class="side-panel">
|
||
<h2 class="side-panel-title">责任人</h2>
|
||
<ul class="owner-list">
|
||
{% for owner in owners %}
|
||
<li class="owner-item">
|
||
<span>{{ owner.role }}</span>
|
||
<strong>{{ owner.name }}</strong>
|
||
<span class="owner-status">{{ owner.status }}</span>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</section>
|
||
|
||
<section class="side-panel">
|
||
<h2 class="side-panel-title">操作记录 <button class="top-action link-blue" type="button" data-action="toast" data-message="已跳转到审计列表入口。">查看全部 ></button></h2>
|
||
<ul class="op-list">
|
||
{% for log in operation_logs %}
|
||
<li class="op-item">
|
||
<span class="op-dot"></span>
|
||
<span>{{ log.time }} {{ log.actor }} {{ log.action }}</span>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</section>
|
||
</aside>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<div class="toast" id="cmd-toast" role="status" aria-live="polite"></div>
|
||
|
||
<div class="drawer-mask" id="review-drawer-mask" aria-hidden="true">
|
||
<aside class="review-drawer" aria-label="检查项详情">
|
||
<div class="drawer-head">
|
||
<div>
|
||
<h2 class="drawer-title" id="drawer-title">检查项详情</h2>
|
||
<p class="brand-subtitle" id="drawer-subtitle">规则依据与问题说明</p>
|
||
</div>
|
||
<button class="drawer-close" type="button" data-action="close-drawer">关闭</button>
|
||
</div>
|
||
<div class="drawer-body">
|
||
<div class="drawer-kv">
|
||
<strong>风险等级</strong>
|
||
<span id="drawer-risk">-</span>
|
||
</div>
|
||
<div class="drawer-kv">
|
||
<strong>依据条款</strong>
|
||
<span id="drawer-rule">-</span>
|
||
</div>
|
||
<div class="drawer-kv">
|
||
<strong>问题描述</strong>
|
||
<span id="drawer-problem">-</span>
|
||
</div>
|
||
<div class="drawer-kv">
|
||
<strong>证据引用</strong>
|
||
<span>命中法规规则版本 {{ command_batch.version }},引用结果将写入本次审计记录。</span>
|
||
</div>
|
||
</div>
|
||
<div class="cmd-button-row">
|
||
<button class="cmd-btn primary" type="button" data-action="toast" data-message="已标记为人工复核。">标记复核</button>
|
||
<button class="cmd-btn" type="button" data-action="toast" data-message="已创建飞书通知草稿。">通知责任人</button>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
const toast = document.getElementById("cmd-toast");
|
||
const mask = document.getElementById("review-drawer-mask");
|
||
const table = document.getElementById("command-check-table");
|
||
let toastTimer;
|
||
|
||
function showToast(message) {
|
||
toast.textContent = message;
|
||
toast.classList.add("show");
|
||
window.clearTimeout(toastTimer);
|
||
toastTimer = window.setTimeout(() => toast.classList.remove("show"), 2400);
|
||
}
|
||
|
||
function openDrawer(button) {
|
||
document.getElementById("drawer-title").textContent = button.dataset.title || "检查项详情";
|
||
document.getElementById("drawer-risk").textContent = button.dataset.risk || "-";
|
||
document.getElementById("drawer-rule").textContent = button.dataset.rule || "-";
|
||
document.getElementById("drawer-problem").textContent = button.dataset.problem || "-";
|
||
mask.classList.add("open");
|
||
mask.setAttribute("aria-hidden", "false");
|
||
}
|
||
|
||
function closeDrawer() {
|
||
mask.classList.remove("open");
|
||
mask.setAttribute("aria-hidden", "true");
|
||
}
|
||
|
||
function applyFilters() {
|
||
const risk = document.querySelector("[data-filter='risk']").value;
|
||
const chapter = document.querySelector("[data-filter='chapter']").value;
|
||
const search = document.querySelector("[data-filter='search']").value.trim().toLowerCase();
|
||
table.querySelectorAll("tbody tr").forEach((row) => {
|
||
const rowRisk = row.dataset.risk || "";
|
||
const text = (row.dataset.text || "").toLowerCase();
|
||
const chapterOk = chapter === "全部章节" || text.includes(chapter.toLowerCase().replace(" ", ""));
|
||
const riskOk = risk === "全部状态" || rowRisk === risk;
|
||
const searchOk = !search || text.includes(search);
|
||
row.classList.toggle("hidden", !(chapterOk && riskOk && searchOk));
|
||
});
|
||
}
|
||
|
||
document.addEventListener("click", (event) => {
|
||
const target = event.target.closest("[data-action], [data-tab]");
|
||
if (!target) return;
|
||
|
||
if (target.dataset.tab) {
|
||
document.querySelectorAll(".tab-btn").forEach((tab) => tab.classList.remove("active"));
|
||
target.classList.add("active");
|
||
const label = target.textContent.trim();
|
||
showToast("已切换到:" + label);
|
||
}
|
||
|
||
if (target.dataset.action === "toast") {
|
||
event.preventDefault();
|
||
showToast(target.dataset.message || "操作已完成。");
|
||
}
|
||
|
||
if (target.dataset.action === "drawer") {
|
||
openDrawer(target);
|
||
}
|
||
|
||
if (target.dataset.action === "close-drawer") {
|
||
closeDrawer();
|
||
}
|
||
});
|
||
|
||
mask.addEventListener("click", (event) => {
|
||
if (event.target === mask) closeDrawer();
|
||
});
|
||
|
||
document.querySelectorAll("[data-filter]").forEach((control) => {
|
||
control.addEventListener("input", applyFilters);
|
||
control.addEventListener("change", applyFilters);
|
||
});
|
||
}());
|
||
</script>
|
||
{% endblock %}
|