feat: simplify Bell alert handling for users (#33)
This commit is contained in:
+53
-32
@@ -1,33 +1,54 @@
|
||||
/* Bell-only components layered on the shared go-admin-ui prototype adapter. */
|
||||
.bell-go-admin .brand-mark { background: #1677ff; }
|
||||
.bell-go-admin .bell-workbench { grid-template-columns: minmax(340px,.95fr) minmax(460px,1.35fr); }
|
||||
.bell-go-admin .alert-list { display: grid; gap: 8px; }
|
||||
.bell-go-admin .alert-row { width: 100%; min-height: 72px; grid-template-columns: 4px minmax(0,1fr) auto; padding: 11px 12px; border-color: #e4e7ed; border-radius: 4px; text-align: left; color: #303133; }
|
||||
.bell-go-admin .alert-row:hover { border-color: #91caff; background: #f5f9ff; }
|
||||
.bell-go-admin .alert-row.selected { border-color: #69b1ff; background: #e6f4ff; box-shadow: inset 0 0 0 1px #91caff; }
|
||||
.bell-go-admin .severity-bar { background: #f56c6c; }
|
||||
.bell-go-admin .severity-bar.medium { background: #e6a23c; }
|
||||
.bell-go-admin .alert-title { display: block; font-size: 14px; font-weight: 500; }
|
||||
.bell-go-admin .alert-meta { margin-top: 5px; }
|
||||
.bell-go-admin .alert-actions { margin-top: 14px; display: flex; justify-content: flex-end; gap: 8px; }
|
||||
.bell-go-admin .el-tabs { margin-bottom: 14px; }
|
||||
.bell-go-admin .el-tabs button { height: 36px; padding: 0 16px; color: #606266; font-size: 13px; }
|
||||
.bell-go-admin .el-tabs button[aria-selected="true"] { color: #1677ff; border-bottom-color: #1677ff; }
|
||||
.bell-go-admin .timeline { padding-left: 2px; }
|
||||
.bell-go-admin .timeline-item::before { background: #1677ff; box-shadow: 0 0 0 4px #e6f4ff; }
|
||||
.bell-go-admin .dev-only-note { margin: 0 0 18px; border-left-color: #e6a23c; border-radius: 4px; }
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.bell-go-admin .bell-workbench { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.bell-go-admin .alert-row { min-height: 76px; }
|
||||
.bell-go-admin .alert-actions { flex-wrap: wrap; }
|
||||
.bell-go-admin .alert-actions .el-button { flex: 1 1 150px; }
|
||||
}
|
||||
|
||||
@media (max-width: 430px) {
|
||||
.bell-go-admin .alert-row { grid-template-columns: 4px minmax(0,1fr); }
|
||||
.bell-go-admin .alert-row .activity-time { grid-column: 2; }
|
||||
.bell-go-admin .sidebar { background: linear-gradient(180deg, #7c2d12 0%, #9a3412 44%, #431407 100%); }
|
||||
.bell-go-admin .brand-mark { background: #ffedd5; color: #9a3412; }
|
||||
.bell-go-admin .el-menu-item.active { background: rgba(255, 255, 255, 0.18); }
|
||||
.bell-go-admin .nav-count { margin-left: auto; min-width: 20px; padding: 1px 6px; border-radius: 10px; background: rgba(255,255,255,.2); font-size: 12px; text-align: center; }
|
||||
.bell-go-admin .compact-metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.bell-workbench { grid-template-columns: minmax(290px, .8fr) minmax(440px, 1.2fr); align-items: start; }
|
||||
.alert-workbench .card-body { padding: 0; }
|
||||
.alert-list { display: grid; }
|
||||
.alert-row { display: grid; grid-template-columns: 4px minmax(0, 1fr) auto; gap: 14px; width: 100%; min-height: 82px; padding: 15px 18px; border: 0; border-bottom: 1px solid var(--border); background: #fff; color: var(--text); font: inherit; text-align: left; cursor: pointer; }
|
||||
.alert-row:hover { background: #fff7ed; }
|
||||
.alert-row:focus-visible { outline: 2px solid #c2410c; outline-offset: -3px; }
|
||||
.alert-row.selected { background: #fff7ed; box-shadow: inset 3px 0 #c2410c; }
|
||||
.severity-bar { width: 4px; height: 48px; border-radius: 4px; background: var(--danger); }
|
||||
.severity-bar.medium { background: var(--warning); }
|
||||
.alert-title { display: block; margin-bottom: 7px; }
|
||||
.alert-meta { display: flex; flex-wrap: wrap; gap: 6px 12px; color: var(--text-muted); font-size: 13px; }
|
||||
.activity-time { color: var(--text-muted); font-size: 13px; }
|
||||
.situation-summary { display: grid; gap: 12px; }
|
||||
.situation-summary > div { display: grid; grid-template-columns: 92px 1fr; gap: 14px; }
|
||||
.situation-summary span { color: var(--text-muted); }
|
||||
.evidence-preview { display: flex; align-items: flex-end; justify-content: space-between; min-height: 170px; margin: 18px 0; padding: 18px; border-radius: 8px; background: linear-gradient(140deg, #172033, #506078); color: #fff; }
|
||||
.evidence-preview span { color: #e2e8f0; }
|
||||
.quick-actions { display: flex; flex-wrap: wrap; gap: 10px; }
|
||||
.primary-task { margin-top: 18px; padding: 15px; border-left: 4px solid #c2410c; border-radius: 4px; background: #fff7ed; }
|
||||
.primary-task p { margin: 0 0 12px; line-height: 1.6; }
|
||||
.technical-details { margin-top: 18px; border: 1px solid var(--border); border-radius: 6px; background: #f8fafc; }
|
||||
.technical-details summary { padding: 11px 14px; cursor: pointer; color: #475569; font-weight: 600; }
|
||||
.technical-details summary:focus-visible { outline: 2px solid #c2410c; outline-offset: 2px; }
|
||||
.technical-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; padding: 0 14px 14px; }
|
||||
.technical-grid > div { display: grid; gap: 4px; }
|
||||
.technical-grid span { color: var(--text-muted); font-size: 13px; }
|
||||
.technical-grid strong { overflow-wrap: anywhere; }
|
||||
.working-list { display: grid; gap: 10px; }
|
||||
.working-item { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 15px; border: 1px solid var(--border); border-radius: 8px; }
|
||||
.working-item p { margin: 5px 0 0; color: var(--text-muted); }
|
||||
.result-options { display: grid; gap: 10px; margin: 0; padding: 0; border: 0; }
|
||||
.result-options legend { margin-bottom: 4px; font-weight: 700; }
|
||||
.result-card { display: flex; gap: 12px; min-height: 64px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 8px; cursor: pointer; }
|
||||
.result-card:has(input:checked) { border-color: #c2410c; background: #fff7ed; box-shadow: 0 0 0 1px #c2410c; }
|
||||
.result-card input { margin-top: 4px; }
|
||||
.result-card span { display: grid; gap: 3px; }
|
||||
.result-card small { color: var(--text-muted); }
|
||||
.field-error { color: #b91c1c; font-size: 13px; }
|
||||
.dialog-subtitle { margin: 4px 0 0; color: var(--text-muted); font-size: 13px; }
|
||||
.admin-records { margin-bottom: 14px; }
|
||||
@media (max-width: 1100px) { .bell-workbench { grid-template-columns: 1fr; } }
|
||||
@media (max-width: 767px) {
|
||||
.bell-go-admin .compact-metrics { grid-template-columns: 1fr; }
|
||||
.situation-summary > div { grid-template-columns: 1fr; gap: 3px; }
|
||||
.technical-grid { grid-template-columns: 1fr; }
|
||||
.working-item { align-items: stretch; flex-direction: column; }
|
||||
.working-item .btn { width: 100%; }
|
||||
.evidence-preview { align-items: flex-start; flex-direction: column; }
|
||||
}
|
||||
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const qs = (selector, root = document) => root.querySelector(selector);
|
||||
const qsa = (selector, root = document) => [...root.querySelectorAll(selector)];
|
||||
const toastRegion = qs("[data-toast-region]");
|
||||
const toast = (message) => {
|
||||
if (!toastRegion) return;
|
||||
const item = document.createElement("div");
|
||||
item.className = "toast";
|
||||
item.setAttribute("role", "status");
|
||||
item.textContent = message;
|
||||
toastRegion.appendChild(item);
|
||||
window.setTimeout(() => item.remove(), 3600);
|
||||
};
|
||||
const number = (selector) => Number(qs(selector)?.textContent || 0);
|
||||
|
||||
qsa("[data-select-alert]").forEach((row) => row.addEventListener("click", () => {
|
||||
qs("[data-detail-title]").textContent = row.dataset.selectAlert;
|
||||
qs("[data-detail-what]").textContent = row.dataset.selectAlert;
|
||||
qs("[data-detail-location]").textContent = row.dataset.location;
|
||||
qs("[data-detail-meta]").textContent = `${row.dataset.location} · ${row.dataset.time}`;
|
||||
qs("[data-detail-suggestion]").textContent = row.dataset.suggestion;
|
||||
const state = qs("[data-alert-state]");
|
||||
state.textContent = row.dataset.risk;
|
||||
state.className = `status ${row.dataset.risk === "高风险" ? "status-danger" : "status-warning"} el-tag`;
|
||||
}));
|
||||
|
||||
let started = false;
|
||||
const startButton = qs("[data-action='start-alert']");
|
||||
startButton?.addEventListener("click", () => {
|
||||
if (started) return;
|
||||
started = true;
|
||||
startButton.disabled = true;
|
||||
startButton.textContent = "已由你开始处理";
|
||||
const state = qs("[data-alert-state]");
|
||||
state.textContent = "处理中";
|
||||
state.className = "status status-warning el-tag";
|
||||
qs("[data-pending-count]").textContent = String(Math.max(0, number("[data-pending-count]") - 1));
|
||||
qs("[data-working-count]").textContent = String(number("[data-working-count]") + 1);
|
||||
qs("[data-owned-alert]").hidden = false;
|
||||
qs("[data-working-total]").textContent = "3 条";
|
||||
toast("已开始处理;其他人会看到这条预警由王老师负责");
|
||||
});
|
||||
|
||||
qs("[data-simulate-conflict]")?.addEventListener("click", () => {
|
||||
toast("这条预警已由李老师开始处理,你可以查看进展但不能重复领取");
|
||||
});
|
||||
|
||||
qsa("input[name='result']").forEach((input) => input.addEventListener("change", () => {
|
||||
qs("[data-result-error]").hidden = true;
|
||||
}));
|
||||
|
||||
qs("[data-action='finish-alert']")?.addEventListener("click", () => {
|
||||
const selected = qs("input[name='result']:checked");
|
||||
const error = qs("[data-result-error]");
|
||||
if (!selected) {
|
||||
error.hidden = false;
|
||||
qs("input[name='result']")?.focus();
|
||||
return;
|
||||
}
|
||||
const note = qs("#result-note").value.trim();
|
||||
const tbody = qs("[data-history-body]");
|
||||
const row = document.createElement("tr");
|
||||
const statusClass = selected.value === "确认有危险" ? "status-danger" : selected.value === "现场正常" ? "status-success" : "status-neutral";
|
||||
row.innerHTML = `<td><span class="table-title">危险区域有人进入</span><span class="table-subtitle"></span></td><td>北围墙 01</td><td><span class="status ${statusClass} el-tag"></span></td><td>王老师</td><td>刚刚</td><td><button class="btn btn-quiet el-button-link">查看记录</button></td>`;
|
||||
qs(".table-subtitle", row).textContent = note || "未填写补充说明";
|
||||
qs(".status", row).textContent = selected.value;
|
||||
qs("button", row).addEventListener("click", () => toast("已打开完整处理记录"));
|
||||
tbody.prepend(row);
|
||||
qs("[data-owned-alert]").hidden = true;
|
||||
qs("[data-working-total]").textContent = "2 条";
|
||||
qs("[data-working-count]").textContent = String(Math.max(0, number("[data-working-count]") - 1));
|
||||
qs("[data-completed-count]").textContent = String(number("[data-completed-count]") + 1);
|
||||
qs("[data-close-modal]", qs("#finish-alert"))?.click();
|
||||
toast(`处理已完成,现场结果:${selected.value}`);
|
||||
});
|
||||
})();
|
||||
+57
-62
@@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="YoVision Bell 当前 MVP 交互原型,直接对齐 go-admin-ui 与 Element Plus">
|
||||
<title>YoVision Bell · go-admin-ui 对齐原型</title>
|
||||
<meta name="description" content="YoVision Bell 面向普通用户的预警处理 MVP 交互原型">
|
||||
<title>YoVision Bell · 预警处理</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="../assets/styles.css">
|
||||
<link rel="stylesheet" href="../assets/go-admin-ui.css">
|
||||
@@ -14,31 +14,26 @@
|
||||
<svg class="sr-only" aria-hidden="true">
|
||||
<symbol id="icon-menu" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16"/></symbol>
|
||||
<symbol id="icon-bell" viewBox="0 0 24 24"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9M10 21h4"/></symbol>
|
||||
<symbol id="icon-file" viewBox="0 0 24 24"><path d="M6 2h8l4 4v16H6zM14 2v5h5M9 13h6m-6 4h6"/></symbol>
|
||||
<symbol id="icon-rule" viewBox="0 0 24 24"><path d="M4 6h10m4 0h2M4 12h3m4 0h9M4 18h7m4 0h5"/><circle cx="16" cy="6" r="2"/><circle cx="9" cy="12" r="2"/><circle cx="13" cy="18" r="2"/></symbol>
|
||||
<symbol id="icon-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
|
||||
<symbol id="icon-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></symbol>
|
||||
<symbol id="icon-check" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M8 12l3 3 5-6"/></symbol>
|
||||
<symbol id="icon-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4"/></symbol>
|
||||
<symbol id="icon-refresh" viewBox="0 0 24 24"><path d="M20 6v5h-5M4 18v-5h5M18 9a7 7 0 0 0-12-2L4 11m16 2-2 4a7 7 0 0 1-12-2"/></symbol>
|
||||
<symbol id="icon-fullscreen" viewBox="0 0 24 24"><path d="M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5"/></symbol>
|
||||
<symbol id="icon-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></symbol>
|
||||
<symbol id="icon-lock" viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></symbol>
|
||||
<symbol id="icon-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6"/></symbol>
|
||||
</svg>
|
||||
|
||||
<a class="skip-link" href="#main-content">跳到主要内容</a>
|
||||
<div class="app-shell go-admin-shell" data-go-admin-shell>
|
||||
<aside class="sidebar sidebar-container" data-sidebar aria-label="Bell 主导航">
|
||||
<a class="brand sidebar-logo" href="../index.html">
|
||||
<span class="brand-mark">B</span>
|
||||
<span class="brand-copy"><span class="brand-name">YoVision Bell</span><span class="brand-subtitle">事件预警与处置</span></span>
|
||||
</a>
|
||||
<nav class="side-nav el-menu" aria-label="预警处置菜单">
|
||||
<div class="menu-group"><svg aria-hidden="true"><use href="#icon-bell"></use></svg><span>预警处置</span><svg class="menu-arrow" aria-hidden="true"><use href="#icon-arrow"></use></svg></div>
|
||||
<button class="active el-menu-item" data-view-target="duty" data-title="实时值班台"><svg aria-hidden="true"><use href="#icon-bell"></use></svg><span>实时值班台</span></button>
|
||||
<button class="el-menu-item" data-view-target="events" data-title="事件中心"><svg aria-hidden="true"><use href="#icon-file"></use></svg><span>事件中心</span></button>
|
||||
<button class="el-menu-item" data-view-target="rules" data-title="规则策略"><svg aria-hidden="true"><use href="#icon-rule"></use></svg><span>规则策略</span></button>
|
||||
<a class="brand sidebar-logo" href="../index.html"><span class="brand-mark">B</span><span class="brand-copy"><span class="brand-name">YoVision Bell</span><span class="brand-subtitle">安全预警处理</span></span></a>
|
||||
<nav class="side-nav el-menu" aria-label="预警处理菜单">
|
||||
<div class="menu-group"><svg aria-hidden="true"><use href="#icon-bell"></use></svg><span>我的预警</span><svg class="menu-arrow" aria-hidden="true"><use href="#icon-arrow"></use></svg></div>
|
||||
<button class="active el-menu-item" data-view-target="pending" data-title="待处理"><svg aria-hidden="true"><use href="#icon-bell"></use></svg><span>待处理</span><span class="nav-count">3</span></button>
|
||||
<button class="el-menu-item" data-view-target="working" data-title="处理中"><svg aria-hidden="true"><use href="#icon-clock"></use></svg><span>处理中</span><span class="nav-count">2</span></button>
|
||||
<button class="el-menu-item" data-view-target="history" data-title="处理记录"><svg aria-hidden="true"><use href="#icon-check"></use></svg><span>处理记录</span></button>
|
||||
</nav>
|
||||
<div class="side-note"><strong>独立产品边界</strong><span>Bell 使用独立账户、会话和数据库;当前以合成事件验收,不接受 Sense 用户 token。</span></div>
|
||||
<div class="side-note"><strong>处理顺序</strong><span>先查看发生了什么,再点击“开始处理”,确认现场结果后完成记录。</span></div>
|
||||
</aside>
|
||||
|
||||
<div class="main-column main-container">
|
||||
@@ -47,82 +42,82 @@
|
||||
<div class="topbar-left">
|
||||
<button class="navbar-action mobile-menu" type="button" data-mobile-menu aria-label="打开主导航" aria-expanded="false"><svg aria-hidden="true"><use href="#icon-menu"></use></svg></button>
|
||||
<button class="navbar-action desktop-collapse" type="button" data-collapse-sidebar aria-label="折叠主导航" aria-expanded="true"><svg aria-hidden="true"><use href="#icon-menu"></use></svg></button>
|
||||
<nav class="breadcrumb" aria-label="面包屑"><a href="../index.html">YoVision</a><span>/</span><span>Bell</span><span>/</span><strong data-page-name>实时值班台</strong></nav>
|
||||
<nav class="breadcrumb" aria-label="面包屑"><a href="../index.html">YoVision</a><span>/</span><span>Bell</span><span>/</span><strong data-page-name>待处理</strong></nav>
|
||||
</div>
|
||||
<div class="topbar-actions right-menu">
|
||||
<button class="navbar-action" type="button" data-toast="原型搜索仅演示入口,不发送请求" aria-label="全局搜索"><svg aria-hidden="true"><use href="#icon-search"></use></svg></button>
|
||||
<button class="navbar-action" type="button" data-toast="可按地点或预警内容搜索" aria-label="全局搜索"><svg aria-hidden="true"><use href="#icon-search"></use></svg></button>
|
||||
<button class="navbar-action desktop-only" type="button" data-toast="原型不切换浏览器全屏" aria-label="全屏"><svg aria-hidden="true"><use href="#icon-fullscreen"></use></svg></button>
|
||||
<span class="prototype-pill">交互原型 · 非生产数据</span>
|
||||
<button class="avatar-button" type="button" data-toast="当前原型用户:王值班员" aria-label="打开用户菜单"><span class="avatar"><svg aria-hidden="true"><use href="#icon-user"></use></svg></span><span class="desktop-only">王值班员</span></button>
|
||||
<button class="avatar-button" type="button" data-toast="当前用户:王老师" aria-label="打开用户菜单"><span class="avatar"><svg aria-hidden="true"><use href="#icon-user"></use></svg></span><span class="desktop-only">王老师</span></button>
|
||||
</div>
|
||||
</header>
|
||||
<nav class="tags-view-container" aria-label="已访问页面">
|
||||
<button class="tags-view-item active" data-view-target="duty" data-title="实时值班台" aria-current="page">实时值班台</button>
|
||||
<button class="tags-view-item" data-view-target="events" data-title="事件中心">事件中心 <span aria-hidden="true">×</span></button>
|
||||
<button class="tags-view-item" data-view-target="rules" data-title="规则策略">规则策略 <span aria-hidden="true">×</span></button>
|
||||
<button class="tags-view-item active" data-view-target="pending" data-title="待处理" aria-current="page">待处理</button>
|
||||
<button class="tags-view-item" data-view-target="working" data-title="处理中">处理中 <span aria-hidden="true">×</span></button>
|
||||
<button class="tags-view-item" data-view-target="history" data-title="处理记录">处理记录 <span aria-hidden="true">×</span></button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<main id="main-content" class="content app-main" tabindex="-1">
|
||||
<section class="view active" data-view="duty">
|
||||
<div class="page-header compact-header"><div><h1>实时值班台</h1><p>使用合成事件完成 Event → Rule → Alert → ack → close;原始 Event 始终保持不可变。</p></div><div class="page-actions"><button class="btn el-button el-button-primary" data-open-modal="inject-event" title="权限:bell:event:inject;仅开发测试"><svg aria-hidden="true"><use href="#icon-plus"></use></svg>注入合成事件</button></div></div>
|
||||
<div class="metric-grid">
|
||||
<article class="metric el-card"><div class="metric-label">待确认 Alert</div><div class="metric-value" data-alert-count>3</div><div class="metric-foot"><span class="status status-danger el-tag">1 高危</span></div></article>
|
||||
<article class="metric el-card"><div class="metric-label">处理中</div><div class="metric-value">2</div><div class="metric-foot"><span>已明确处置人</span></div></article>
|
||||
<article class="metric el-card"><div class="metric-label">今日关闭</div><div class="metric-value">18</div><div class="metric-foot"><strong>中位 3m 42s</strong></div></article>
|
||||
<article class="metric el-card"><div class="metric-label">重复收据</div><div class="metric-value">6</div><div class="metric-foot"><span>均未重复建 Alert</span></div></article>
|
||||
<section class="view active" data-view="pending">
|
||||
<div class="page-header compact-header"><div><h1>待处理</h1><p>请优先处理高风险预警。开始处理后,其他人会看到由你负责。</p></div></div>
|
||||
<div class="metric-grid compact-metrics">
|
||||
<article class="metric el-card"><div class="metric-label">待处理</div><div class="metric-value" data-pending-count>3</div><div class="metric-foot"><span class="status status-danger el-tag">1 条高风险</span></div></article>
|
||||
<article class="metric el-card"><div class="metric-label">由我处理</div><div class="metric-value" data-working-count>0</div><div class="metric-foot"><span>完成前会保留在处理中</span></div></article>
|
||||
<article class="metric el-card"><div class="metric-label">今日已完成</div><div class="metric-value" data-completed-count>18</div><div class="metric-foot"><strong>平均用时约 4 分钟</strong></div></article>
|
||||
</div>
|
||||
<div class="grid grid-2 bell-workbench">
|
||||
<article class="card el-card alert-workbench"><header class="card-header"><div><h2>待处置预警</h2><p class="card-subtitle">AlertWorkbench · Bell 业务专用组件</p></div><span class="status status-danger el-tag">3 待处理</span></header><div class="card-body alert-list" aria-label="待处置预警列表">
|
||||
<button class="alert-row selected" type="button" data-select-alert="危险区域闯入"><span class="severity-bar"></span><span><strong class="alert-title">危险区域闯入</strong><span class="alert-meta"><span>北围墙 01</span><span>2 个 Event</span><span>未确认</span></span></span><time class="activity-time">10:24</time></button>
|
||||
<button class="alert-row" type="button" data-select-alert="方向越线"><span class="severity-bar medium"></span><span><strong class="alert-title">反向越过警戒线</strong><span class="alert-meta"><span>宿舍东门 02</span><span>1 个 Event</span><span>处理中</span></span></span><time class="activity-time">10:19</time></button>
|
||||
<button class="alert-row" type="button" data-select-alert="区域停留"><span class="severity-bar medium"></span><span><strong class="alert-title">危险区域持续停留</strong><span class="alert-meta"><span>食堂后门 01</span><span>3 个 Event</span><span>未确认</span></span></span><time class="activity-time">10:11</time></button>
|
||||
<article class="card el-card alert-workbench"><header class="card-header"><div><h2>需要关注的情况</h2><p class="card-subtitle">选择一条查看地点、风险和建议</p></div><span class="status status-danger el-tag">3 条</span></header><div class="card-body alert-list" aria-label="待处理预警列表">
|
||||
<button class="alert-row selected" type="button" data-select-alert="危险区域有人进入" data-location="北围墙 01" data-time="今天 10:24" data-risk="高风险" data-suggestion="先查看现场;如无法确认,请联系校内保安巡查。"><span class="severity-bar"></span><span><strong class="alert-title">危险区域有人进入</strong><span class="alert-meta"><span>北围墙 01</span><span>高风险</span><span>刚刚</span></span></span><time class="activity-time">10:24</time></button>
|
||||
<button class="alert-row" type="button" data-select-alert="有人反向越过警戒线" data-location="宿舍东门 02" data-time="今天 10:19" data-risk="中风险" data-suggestion="查看现场是否有人逆向进入;确认安全后记录处理结果。"><span class="severity-bar medium"></span><span><strong class="alert-title">有人反向越过警戒线</strong><span class="alert-meta"><span>宿舍东门 02</span><span>中风险</span><span>5 分钟前</span></span></span><time class="activity-time">10:19</time></button>
|
||||
<button class="alert-row" type="button" data-select-alert="危险区域有人长时间停留" data-location="食堂后门 01" data-time="今天 10:11" data-risk="中风险" data-suggestion="查看现场并确认停留原因;无法判断时请安排人员巡查。"><span class="severity-bar medium"></span><span><strong class="alert-title">危险区域有人长时间停留</strong><span class="alert-meta"><span>食堂后门 01</span><span>中风险</span><span>13 分钟前</span></span></span><time class="activity-time">10:11</time></button>
|
||||
</div></article>
|
||||
<article class="card el-card alert-detail"><header class="card-header"><div><h2 data-detail-title>危险区域有人进入</h2><p class="card-subtitle" data-detail-meta>北围墙 01 · 今天 10:24</p></div><span class="status status-danger el-tag" data-alert-state>高风险</span></header><div class="card-body">
|
||||
<div class="situation-summary"><div><span>发生了什么</span><strong data-detail-what>危险区域有人进入</strong></div><div><span>发生地点</span><strong data-detail-location>北围墙 01</strong></div><div><span>建议怎么做</span><strong data-detail-suggestion>先查看现场;如无法确认,请联系校内保安巡查。</strong></div></div>
|
||||
<div class="evidence-preview" role="img" aria-label="北围墙预警现场的模拟画面"><span>北围墙 01 · 10:23:55</span><strong>查看现场画面</strong></div>
|
||||
<div class="quick-actions"><button class="btn el-button" data-toast="已打开现场画面(原型演示)">查看现场</button><button class="btn el-button" data-toast="联系人功能将在后续版本开放" disabled>联系负责人(后续开放)</button><button class="btn el-button" data-toast="转交功能将在后续版本开放" disabled>转交他人(后续开放)</button></div>
|
||||
<div class="primary-task"><p>准备处理这条预警时,请点击下面的按钮。此操作只表示“你已看到并开始处理”,不代表现场情况已确认。</p><button class="btn el-button el-button-primary" data-action="start-alert">我已看到,开始处理</button></div>
|
||||
<details class="technical-details"><summary>管理员技术详情</summary><div class="technical-grid"><div><span>预警编号</span><strong>BEL-A-20260812-0031</strong></div><div><span>原始事实</span><strong>2 条不可变事件</strong></div><div><span>匹配规则</span><strong>校园高危区域 · v4</strong></div><div><span>后台状态映射</span><strong>Alert new → ack → close</strong></div><div><span>来源标识</span><strong>producer-lab / evt-000042</strong></div></div></details>
|
||||
</div></article>
|
||||
<article class="card el-card alert-detail"><header class="card-header"><div><h2>危险区域闯入</h2><p class="card-subtitle">Alert BEL-A-20260812-0031</p></div><span class="status status-danger el-tag" data-alert-state>未确认</span></header><div class="card-body">
|
||||
<div class="tabs el-tabs" role="tablist" data-tabs aria-label="预警详情"><button role="tab" aria-selected="true" data-tab-target="alert-summary">处置摘要</button><button role="tab" aria-selected="false" tabindex="-1" data-tab-target="alert-events">关联 Event</button></div>
|
||||
<div id="alert-summary" class="tab-panel active"><div class="description-list"><div class="description-row"><span>规则</span><strong>校园高危区域 · v4</strong></div><div class="description-row"><span>处置建议</span><strong>确认现场情况;如无法确认,联系校内保安巡查。</strong></div></div><div class="alert-actions"><button class="btn el-button el-button-primary" data-action="ack-alert" title="权限:bell:alert:ack">确认预警</button><button class="btn el-button" data-action="close-alert" title="权限:bell:alert:close" disabled>完成并关闭</button></div></div>
|
||||
<div id="alert-events" class="tab-panel" hidden><div class="activity-list"><div class="activity-item"><div class="activity-icon"><svg aria-hidden="true"><use href="#icon-file"></use></svg></div><div><div class="activity-title">producer-lab / evt-000041</div><div class="activity-meta">不可变 Event · 10:23:48 · 证据引用可用</div></div></div><div class="activity-item"><div class="activity-icon"><svg aria-hidden="true"><use href="#icon-file"></use></svg></div><div><div class="activity-title">producer-lab / evt-000042</div><div class="activity-meta">不可变 Event · 10:23:55 · 同一 Alert 聚合</div></div></div></div></div>
|
||||
</div><header class="card-header"><div><h3>审计时间线</h3><p class="card-subtitle">AlertAuditTimeline · Bell 业务专用组件</p></div></header><div class="card-body timeline" data-alert-timeline><div class="timeline-item"><div class="timeline-title">规则命中并创建 Alert</div><div class="timeline-meta">10:23:56 · system · 规则 v4</div></div><div class="timeline-item"><div class="timeline-title">值班台已显示</div><div class="timeline-meta">10:23:57 · sent/seen 与 ack 分离</div></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="view" data-view="events" hidden>
|
||||
<div class="page-header compact-header"><div><h1>事件中心</h1><p>结构化 Event 校验后不可修改;误报和处置结果记录在 Alert/审计事实中。</p></div></div>
|
||||
<article class="card el-card box-card"><div class="card-body">
|
||||
<form class="search-form el-form el-form-inline" data-go-admin-query data-query-input="#event-filter">
|
||||
<div class="form-item"><label for="event-filter">事件关键字</label><input id="event-filter" class="input el-input" data-filter-input="#event-table" placeholder="Producer、Source ID 或规则"></div>
|
||||
<div class="form-item"><label for="receipt-status">收据状态</label><select id="receipt-status" class="input el-select"><option>全部状态</option><option>首次接收</option><option>重复收据</option></select></div>
|
||||
<div class="form-actions"><button class="btn el-button el-button-primary" type="submit"><svg aria-hidden="true"><use href="#icon-search"></use></svg>搜索</button><button class="btn el-button" type="reset"><svg aria-hidden="true"><use href="#icon-refresh"></use></svg>重置</button></div>
|
||||
</form>
|
||||
<div class="operation-bar"><div class="operation-actions"><button class="btn el-button el-button-primary" data-open-modal="inject-event" title="权限:bell:event:inject;仅开发测试"><svg aria-hidden="true"><use href="#icon-plus"></use></svg>注入合成事件</button><button class="btn el-button" disabled title="权限:bell:event:export;MVP 暂不开放"><svg aria-hidden="true"><use href="#icon-lock"></use></svg>导出</button></div><span class="permission-hint"><svg aria-hidden="true"><use href="#icon-lock"></use></svg>操作按钮受 RBAC 与环境开关双重控制</span></div>
|
||||
<div class="table-wrap"><table id="event-table" class="el-table"><thead><tr><th>Event</th><th>Producer / Source ID</th><th>接收时间</th><th>收据</th><th>Alert</th><th>操作</th></tr></thead><tbody>
|
||||
<tr><td><span class="table-title">危险区域进入</span><span class="table-subtitle">结构化事实已冻结</span></td><td>producer-lab / evt-000042</td><td>10:23:55</td><td><span class="status status-success el-tag">首次接收</span></td><td>BEL-A-0031</td><td><button class="btn btn-quiet el-button-link" data-toast="已打开 Event 与 Alert 导航链路">查看链路</button></td></tr>
|
||||
<tr><td><span class="table-title">危险区域进入</span><span class="table-subtitle">正文与首次一致</span></td><td>producer-lab / evt-000042</td><td>10:24:03</td><td><span class="status status-neutral el-tag">重复收据</span></td><td>未重复创建</td><td><button class="btn btn-quiet el-button-link" data-toast="已打开永久幂等收据">查看收据</button></td></tr>
|
||||
<tr><td><span class="table-title">方向越线</span><span class="table-subtitle">结构化事实已冻结</span></td><td>producer-lab / evt-000039</td><td>10:19:12</td><td><span class="status status-success el-tag">首次接收</span></td><td>BEL-A-0029</td><td><button class="btn btn-quiet el-button-link" data-toast="已打开 Event 与 Alert 导航链路">查看链路</button></td></tr>
|
||||
</tbody></table></div>
|
||||
<nav class="pagination-container el-pagination" aria-label="事件列表分页"><span class="pagination-total">共 3 条</span><button type="button" disabled aria-label="上一页">‹</button><button type="button" class="active" aria-current="page">1</button><button type="button" disabled aria-label="下一页">›</button><select class="pagination-size" aria-label="每页条数"><option>10 条/页</option><option>20 条/页</option></select></nav>
|
||||
<section class="view" data-view="working" hidden>
|
||||
<div class="page-header compact-header"><div><h1>处理中</h1><p>这里显示已由你或同事开始处理、但还没有记录现场结果的预警。</p></div></div>
|
||||
<article class="card el-card"><header class="card-header"><div><h2>正在处理的预警</h2><p class="card-subtitle">处理完成后必须记录现场结果</p></div><span class="status status-warning el-tag" data-working-total>2 条</span></header><div class="card-body working-list" data-working-list>
|
||||
<article class="working-item" data-owned-alert hidden><div><strong>危险区域有人进入</strong><p>北围墙 01 · 由王老师处理 · 刚刚开始</p></div><button class="btn el-button el-button-primary" data-open-modal="finish-alert">记录处理结果</button></article>
|
||||
<article class="working-item"><div><strong>有人反向越过警戒线</strong><p>宿舍东门 02 · 由李老师处理 · 5 分钟前开始</p></div><button class="btn el-button" data-simulate-conflict>查看处理人</button></article>
|
||||
<article class="working-item"><div><strong>夜间区域有人停留</strong><p>操场西侧 · 由保安室处理 · 8 分钟前开始</p></div><button class="btn el-button" data-toast="已打开处理进展(原型演示)">查看进展</button></article>
|
||||
</div></article>
|
||||
</section>
|
||||
|
||||
<section class="view" data-view="rules" hidden>
|
||||
<div class="page-header compact-header"><div><h1>规则策略</h1><p>MVP 仅展示事件到 Alert 的基本匹配;自动升级、通知和排班属于后续阶段。</p></div></div>
|
||||
<article class="card el-card box-card"><div class="card-body"><div class="operation-bar"><div class="operation-actions"><button class="btn el-button el-button-primary" data-toast="已创建规则草稿,不影响当前发布版本" title="权限:bell:rule:add"><svg aria-hidden="true"><use href="#icon-plus"></use></svg>新建规则草稿</button><button class="btn el-button" disabled title="请先选择规则">批量停用</button></div><span class="permission-hint"><svg aria-hidden="true"><use href="#icon-lock"></use></svg>发布版本不可直接修改</span></div>
|
||||
<div class="table-wrap"><table class="el-table"><thead><tr><th>规则名称</th><th>发布版本</th><th>匹配条件</th><th>Alert 严重度</th><th>状态</th><th>操作</th></tr></thead><tbody><tr><td><span class="table-title">校园高危区域</span></td><td>v4</td><td>event.type = zone_intrusion</td><td><span class="status status-danger el-tag">高危</span></td><td><span class="status status-success el-tag">生效</span></td><td><button class="btn btn-quiet el-button-link" data-toast="已进入校园高危区域规则草稿">编辑草稿</button></td></tr><tr><td><span class="table-title">方向越线</span></td><td>v2</td><td>event.type = line_crossing</td><td><span class="status status-warning el-tag">中危</span></td><td><span class="status status-success el-tag">生效</span></td><td><button class="btn btn-quiet el-button-link" data-toast="已进入方向越线规则草稿">编辑草稿</button></td></tr></tbody></table></div>
|
||||
<section class="view" data-view="history" hidden>
|
||||
<div class="page-header compact-header"><div><h1>处理记录</h1><p>查看已经完成的处理结果;普通用户不需要理解后台事件和收据。</p></div></div>
|
||||
<article class="card el-card box-card"><div class="card-body">
|
||||
<form class="search-form el-form el-form-inline" data-go-admin-query data-query-input="#history-filter"><div class="form-item"><label for="history-filter">搜索记录</label><input id="history-filter" class="input el-input" data-filter-input="#history-table" placeholder="输入地点、情况或处理人"></div><div class="form-item"><label for="result-filter">处理结果</label><select id="result-filter" class="input el-select"><option>全部结果</option><option>确认有危险</option><option>误报</option><option>现场正常</option><option>无法确认</option></select></div><div class="form-actions"><button class="btn el-button el-button-primary" type="submit"><svg aria-hidden="true"><use href="#icon-search"></use></svg>搜索</button><button class="btn el-button" type="reset"><svg aria-hidden="true"><use href="#icon-refresh"></use></svg>重置</button></div></form>
|
||||
<div class="table-wrap"><table id="history-table" class="el-table"><thead><tr><th>发生情况</th><th>地点</th><th>处理结果</th><th>处理人</th><th>完成时间</th><th>操作</th></tr></thead><tbody data-history-body>
|
||||
<tr><td><span class="table-title">有人进入危险区域</span></td><td>教学楼楼顶</td><td><span class="status status-danger el-tag">确认有危险</span></td><td>赵老师</td><td>09:42</td><td><button class="btn btn-quiet el-button-link" data-toast="已打开完整处理记录">查看记录</button></td></tr>
|
||||
<tr><td><span class="table-title">有人反向越过警戒线</span></td><td>宿舍西门</td><td><span class="status status-neutral el-tag">误报</span></td><td>王老师</td><td>09:18</td><td><button class="btn btn-quiet el-button-link" data-toast="已打开完整处理记录">查看记录</button></td></tr>
|
||||
<tr><td><span class="table-title">危险区域有人停留</span></td><td>食堂后门</td><td><span class="status status-success el-tag">现场正常</span></td><td>李老师</td><td>08:57</td><td><button class="btn btn-quiet el-button-link" data-toast="已打开完整处理记录">查看记录</button></td></tr>
|
||||
</tbody></table></div>
|
||||
<details class="technical-details admin-records"><summary>管理员:查看技术事件和审计链路</summary><div class="technical-grid"><div><span>不可变事件</span><strong>原始内容保持不变</strong></div><div><span>重复接收保护</span><strong>6 条重复收据,均未重复创建预警</strong></div><div><span>规则管理</span><strong>仅管理员可配置发布版本</strong></div><div><span>开发测试入口</span><strong>生产环境不显示合成事件注入</strong></div></div></details>
|
||||
<nav class="pagination-container el-pagination" aria-label="处理记录分页"><span class="pagination-total">共 18 条</span><button type="button" disabled aria-label="上一页">‹</button><button type="button" class="active" aria-current="page">1</button><button type="button" aria-label="下一页">›</button><select class="pagination-size" aria-label="每页条数"><option>10 条/页</option><option>20 条/页</option></select></nav>
|
||||
</div></article>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-backdrop el-overlay" id="inject-event" aria-hidden="true">
|
||||
<section class="modal el-dialog" role="dialog" aria-modal="true" aria-labelledby="inject-title">
|
||||
<header class="modal-header el-dialog-header"><h2 id="inject-title">注入合成事件</h2><button class="btn btn-icon btn-quiet dialog-close" data-close-modal aria-label="关闭合成事件窗口">×</button></header>
|
||||
<div class="modal-body el-dialog-body"><div class="boundary-note dev-only-note">该入口仅用于开发测试;生产构建必须通过环境开关移除。</div><form class="el-form vertical-form"><div class="form-item"><label for="synthetic-type">事件类型</label><select id="synthetic-type" class="input el-select"><option>危险区域进入</option><option>方向越线</option></select></div><div class="form-item"><label for="synthetic-producer">Producer identity</label><input id="synthetic-producer" class="input el-input" value="producer-lab" readonly></div><div class="form-item"><label for="synthetic-source">Source event ID</label><input id="synthetic-source" class="input el-input" value="evt-demo-0001"></div></form></div>
|
||||
<footer class="modal-footer el-dialog-footer"><button class="btn el-button" data-close-modal>取消</button><button class="btn el-button el-button-primary" data-action="inject-event">确定注入</button></footer>
|
||||
<div class="modal-backdrop el-overlay" id="finish-alert" aria-hidden="true">
|
||||
<section class="modal el-dialog" role="dialog" aria-modal="true" aria-labelledby="finish-title">
|
||||
<header class="modal-header el-dialog-header"><div><h2 id="finish-title">记录处理结果</h2><p class="dialog-subtitle">北围墙 01 · 危险区域有人进入</p></div><button class="btn btn-icon btn-quiet dialog-close" data-close-modal aria-label="关闭处理结果窗口">×</button></header>
|
||||
<div class="modal-body el-dialog-body"><form class="el-form vertical-form"><fieldset class="result-options"><legend>现场结果 <span aria-hidden="true">*</span></legend><label class="result-card"><input type="radio" name="result" value="确认有危险"><span><strong>确认有危险</strong><small>现场确有人员进入,需要保留为真实情况</small></span></label><label class="result-card"><input type="radio" name="result" value="误报"><span><strong>误报</strong><small>画面识别有误,现场没有相关情况</small></span></label><label class="result-card"><input type="radio" name="result" value="现场正常"><span><strong>现场正常</strong><small>情况真实发生,但已确认没有危险</small></span></label><label class="result-card"><input type="radio" name="result" value="无法确认"><span><strong>无法确认</strong><small>画面或现场信息不足,需要后续复核</small></span></label><span class="field-error" data-result-error role="alert" hidden>请选择现场结果后再完成。</span></fieldset><div class="form-item"><label for="result-note">处理说明(选填)</label><textarea id="result-note" class="input el-input" rows="3" placeholder="例如:已联系保安到现场确认"></textarea></div></form></div>
|
||||
<footer class="modal-footer el-dialog-footer"><button class="btn el-button" data-close-modal>取消</button><button class="btn el-button el-button-primary" type="button" data-action="finish-alert">确认结果并完成</button></footer>
|
||||
</section>
|
||||
</div>
|
||||
<div class="toast-region" data-toast-region aria-live="polite"></div>
|
||||
<script src="../assets/app.js"></script>
|
||||
<script src="../assets/go-admin-shell.js"></script>
|
||||
<script src="./bell.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user