diff --git a/prototypes/bell/bell.css b/prototypes/bell/bell.css index 30f5062..dfcad35 100644 --- a/prototypes/bell/bell.css +++ b/prototypes/bell/bell.css @@ -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; } } diff --git a/prototypes/bell/bell.js b/prototypes/bell/bell.js new file mode 100644 index 0000000..7c6a09d --- /dev/null +++ b/prototypes/bell/bell.js @@ -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 = `危险区域有人进入北围墙 01王老师刚刚`; + 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}`); + }); +})(); diff --git a/prototypes/bell/index.html b/prototypes/bell/index.html index 6994def..dbbae21 100644 --- a/prototypes/bell/index.html +++ b/prototypes/bell/index.html @@ -3,8 +3,8 @@ - - YoVision Bell · go-admin-ui 对齐原型 + + YoVision Bell · 预警处理 @@ -14,31 +14,26 @@
@@ -47,82 +42,82 @@
- +
- + 交互原型 · 非生产数据 - +
-
- -
-
待确认 Alert
3
1 高危
-
处理中
2
已明确处置人
-
今日关闭
18
中位 3m 42s
-
重复收据
6
均未重复建 Alert
+
+ +
+
待处理
3
1 条高风险
+
由我处理
0
完成前会保留在处理中
+
今日已完成
18
平均用时约 4 分钟
-

待处置预警

AlertWorkbench · Bell 业务专用组件

3 待处理
- - - +

需要关注的情况

选择一条查看地点、风险和建议

3 条
+ + + +
+

危险区域有人进入

北围墙 01 · 今天 10:24

高风险
+
发生了什么危险区域有人进入
发生地点北围墙 01
建议怎么做先查看现场;如无法确认,请联系校内保安巡查。
+ +
+

准备处理这条预警时,请点击下面的按钮。此操作只表示“你已看到并开始处理”,不代表现场情况已确认。

+
管理员技术详情
预警编号BEL-A-20260812-0031
原始事实2 条不可变事件
匹配规则校园高危区域 · v4
后台状态映射Alert new → ack → close
来源标识producer-lab / evt-000042
-

危险区域闯入

Alert BEL-A-20260812-0031

未确认
-
-
规则校园高危区域 · v4
处置建议确认现场情况;如无法确认,联系校内保安巡查。
- -

审计时间线

AlertAuditTimeline · Bell 业务专用组件

规则命中并创建 Alert
10:23:56 · system · 规则 v4
值班台已显示
10:23:57 · sent/seen 与 ack 分离
-
-