Files
DEMO-AGENT/templates/platform_ui/command_center_v2.html

1266 lines
36 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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' %}"><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="已模拟收起菜单。">&lt; 收起菜单</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">&lt;</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">&gt;</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="已跳转到审计列表入口。">查看全部 &gt;</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 %}