Files
smart_grid_iot/系统设计/xintong-architecture-full.html
andyesfly f61ade107b docs: 更新鑫潼架构文档,补充运营管理APP相关内容
1. 新增青色色系变量与对应的样式类
2. 调整图例顺序与文案,拆分运营相关端
3. 重构客户端层描述,从四端改为五端入口
4. 更新API路由分组,新增/ops路由
5. 补充对象存储为RustFS并更新说明
6. 新增运营管理APP模块与权限说明
7. 更新权限设计、权限流转与角色矩阵内容
8. 修正多处文案描述与功能细节
2026-08-26 10:55:17 +08:00

890 lines
61 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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.
<!-- Generated by Trae Work -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>鑫潼智电系统 · 完整架构设计</title>
<style>
@font-face { font-family:'InstrumentSans'; src:url('./_shared/fonts/InstrumentSans-Regular.ttf') format('truetype'); font-weight:400; font-display:swap; }
@font-face { font-family:'InstrumentSans'; src:url('./_shared/fonts/InstrumentSans-Bold.ttf') format('truetype'); font-weight:700; font-display:swap; }
@font-face { font-family:'JetBrainsMono'; src:url('./_shared/fonts/JetBrainsMono-Regular.ttf') format('truetype'); font-weight:400; font-display:swap; }
@font-face { font-family:'JetBrainsMono'; src:url('./_shared/fonts/JetBrainsMono-Bold.ttf') format('truetype'); font-weight:700; font-display:swap; }
:root{
--bg:#eef3f8; --bg2:#fff; --ink:#0f1e2e; --muted:#5b6b7e; --rule:#dbe4ee;
--accent:#1d6fff; --accent2:#0d9488; --accent3:#7c3aed; --warn:#e0852b; --red:#dc2626;
--shadow:0 4px 14px rgba(0,0,0,.07); --shadow-lg:0 8px 28px rgba(0,0,0,.1); --radius:12px;
/* block diagram palette */
--c-blue:#1d6fff; --c-blue-bg:#e8f1ff; --c-blue-d:#0c4dd1;
--c-green:#0d9488; --c-green-bg:#e6f7f5; --c-green-d:#0a7068;
--c-purple:#7c3aed; --c-purple-bg:#f0eaff; --c-purple-d:#5b21b6;
--c-teal:#0f766e; --c-teal-bg:#ccfbf1; --c-teal-d:#0d9488;
--c-amber:#e0852b; --c-amber-bg:#fef3e2; --c-amber-d:#b5681a;
--c-slate:#475569; --c-slate-bg:#f1f5f9; --c-slate-d:#334155;
--c-red:#dc2626; --c-red-bg:#fee2e2;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
font-family:'InstrumentSans','PingFang SC','Microsoft YaHei','Hiragino Sans GB',sans-serif;
background:var(--bg);color:var(--ink);line-height:1.7;font-size:15px;-webkit-font-smoothing:antialiased;
}
body::before{
content:"";position:fixed;inset:0;z-index:-1;
background:radial-gradient(900px 600px at 8% 0%,rgba(29,111,255,.07),transparent 60%),
radial-gradient(800px 600px at 92% 6%,rgba(13,148,136,.07),transparent 60%),
radial-gradient(700px 500px at 50% 100%,rgba(124,58,237,.05),transparent 60%);
}
.wrap{max-width:1200px;margin:0 auto;padding:0 24px}
/* ===== HERO ===== */
.hero{padding:56px 0 32px}
.hero .tag{display:inline-flex;align-items:center;gap:8px;font-size:12px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--accent);background:rgba(29,111,255,.08);padding:6px 14px;border-radius:999px;margin-bottom:16px}
.hero h1{font-size:36px;font-weight:700;letter-spacing:-.01em;line-height:1.2;margin-bottom:12px;background:linear-gradient(135deg,var(--ink),var(--accent));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.hero .sub{font-size:16px;color:var(--muted);max-width:740px}
.hero .meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:22px}
.hero .meta span{font-size:12.5px;color:var(--muted);background:var(--bg2);border:1px solid var(--rule);padding:5px 11px;border-radius:8px}
.hero .meta b{color:var(--ink);font-weight:600}
/* ===== SECTION ===== */
section{padding:40px 0}
.section-head{display:flex;align-items:baseline;gap:14px;margin-bottom:24px}
.section-num{font-family:'JetBrainsMono',monospace;font-size:13px;font-weight:700;color:var(--accent);background:rgba(29,111,255,.08);padding:4px 10px;border-radius:6px;flex-shrink:0}
.section-head h2{font-size:23px;font-weight:700;letter-spacing:-.01em}
.section-head .desc{font-size:13px;color:var(--muted);margin-left:auto}
/* ===== DG: DIAGRAM BLOCK OVERVIEW ===== */
.dg-legend{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-bottom:20px}
.dg-legend .li{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);background:var(--bg2);border:1px solid var(--rule);padding:4px 11px;border-radius:8px}
.dg-legend .lc{width:11px;height:11px;border-radius:3px}
.dg-diagram{background:var(--bg2);border:1px solid var(--rule);border-radius:var(--radius);padding:24px;box-shadow:var(--shadow-lg);overflow-x:auto}
.dg-layer{margin-bottom:10px}
.dg-layer-label{display:flex;align-items:center;gap:8px;margin-bottom:8px}
.dg-ll-tag{font-size:11px;font-weight:700;color:#fff;padding:2px 9px;border-radius:5px;letter-spacing:.05em;flex-shrink:0}
.dg-ll-name{font-size:14px;font-weight:700}
.dg-ll-desc{font-size:12px;color:var(--muted);margin-left:auto}
.dg-conn{text-align:center;color:var(--rule);height:24px;display:flex;justify-content:center;align-items:center}
.dg-blocks{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
.dg-block{flex:1;min-width:145px;max-width:225px;border-radius:10px;padding:11px 13px;border:1.5px solid;transition:transform .12s}
.dg-block:hover{transform:translateY(-2px);box-shadow:var(--shadow-lg)}
.dg-block .dh{display:flex;align-items:center;gap:7px;margin-bottom:5px}
.dg-block .di{width:24px;height:24px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#fff;flex-shrink:0}
.dg-block .dt{font-size:13.5px;font-weight:700;line-height:1.2}
.dg-block .dx{font-size:11px;font-weight:600;margin-bottom:3px;opacity:.85}
.dg-block ul{list-style:none;font-size:11.5px;color:var(--muted);line-height:1.55}
.dg-block ul li::before{content:"·";font-weight:700;margin-right:4px}
.dg-blue{border-color:var(--c-blue);background:var(--c-blue-bg)} .dg-blue .di{background:var(--c-blue)} .dg-blue .dx{color:var(--c-blue-d)} .dg-blue .dt{color:var(--c-blue-d)}
.dg-purple{border-color:var(--c-purple);background:var(--c-purple-bg)} .dg-purple .di{background:var(--c-purple)} .dg-purple .dx{color:var(--c-purple-d)} .dg-purple .dt{color:var(--c-purple-d)}
.dg-teal{border-color:var(--c-teal);background:var(--c-teal-bg)} .dg-teal .di{background:var(--c-teal)} .dg-teal .dx{color:var(--c-teal-d)} .dg-teal .dt{color:var(--c-teal-d)}
.dg-green{border-color:var(--c-green);background:var(--c-green-bg)} .dg-green .di{background:var(--c-green)} .dg-green .dx{color:var(--c-green-d)} .dg-green .dt{color:var(--c-green-d)}
.dg-amber{border-color:var(--c-amber);background:var(--c-amber-bg)} .dg-amber .di{background:var(--c-amber)} .dg-amber .dx{color:var(--c-amber-d)} .dg-amber .dt{color:var(--c-amber-d)}
.dg-slate{border-color:var(--c-slate);background:var(--c-slate-bg)} .dg-slate .di{background:var(--c-slate)} .dg-slate .dx{color:var(--c-slate-d)} .dg-slate .dt{color:var(--c-slate-d)}
.dg-ext{border-style:dashed;border-width:2px}
.dg-gateway{display:flex;align-items:stretch;border-radius:10px;overflow:hidden;border:1.5px solid var(--c-purple)}
.dg-gateway .gi{flex:1;padding:9px 10px;text-align:center;background:var(--c-purple-bg);border-right:1px solid rgba(124,58,237,.15)}
.dg-gateway .gi:last-child{border:none}
.dg-gateway .gi h4{font-size:12.5px;font-weight:700;color:var(--c-purple-d);margin-bottom:1px}
.dg-gateway .gi p{font-size:10.5px;color:var(--muted);line-height:1.4}
.dg-backend{display:grid;grid-template-columns:repeat(5,1fr);gap:7px}
.dg-be{border-radius:8px;padding:9px 7px;text-align:center;border:1.5px solid var(--c-green);background:var(--c-green-bg);transition:transform .12s}
.dg-be:hover{transform:translateY(-2px)}
.dg-be .bi{font-size:17px;margin-bottom:3px}
.dg-be h4{font-size:11.5px;font-weight:700;line-height:1.2;margin-bottom:2px;color:var(--c-green-d)}
.dg-be p{font-size:10px;color:var(--muted);line-height:1.3}
.dg-data{display:grid;grid-template-columns:repeat(4,1fr);gap:9px}
.dg-db{border-radius:10px;padding:11px;border:1.5px solid;text-align:center}
.dg-db .dmi{font-size:20px;margin-bottom:3px}
.dg-db h4{font-size:12.5px;font-weight:700;margin-bottom:2px}
.dg-db p{font-size:10.5px;color:var(--muted);line-height:1.4}
.dg-db.d-amber{border-color:var(--c-amber);background:var(--c-amber-bg)} .dg-db.d-amber h4{color:var(--c-amber-d)}
.dg-db.d-red{border-color:var(--c-red);background:var(--c-red-bg)} .dg-db.d-red h4{color:var(--c-red)}
.dg-db.d-blue{border-color:var(--c-blue);background:var(--c-blue-bg)} .dg-db.d-blue h4{color:var(--c-blue-d)}
.dg-db.d-green{border-color:var(--c-green);background:var(--c-green-bg)} .dg-db.d-green h4{color:var(--c-green-d)}
.dg-perm{margin-top:18px;background:var(--c-red-bg);border:1.5px solid var(--c-red);border-radius:10px;padding:13px 16px}
.dg-perm h4{font-size:14px;font-weight:700;color:var(--c-red);margin-bottom:8px}
.dg-perm .pr{display:flex;gap:12px;flex-wrap:wrap}
.dg-perm .pi{flex:1;min-width:180px;background:var(--bg2);border-radius:8px;padding:9px 11px;border:1px solid rgba(220,38,38,.15)}
.dg-perm .pi .pt{font-size:12px;font-weight:700;color:var(--ink);margin-bottom:2px}
.dg-perm .pi .pd{font-size:11px;color:var(--muted)}
/* ===== DETAILED: LAYERED ARCH ===== */
.arch-stack{display:flex;flex-direction:column;gap:12px}
.alayer{position:relative;background:var(--bg2);border:1px solid var(--rule);border-radius:var(--radius);padding:16px 18px;box-shadow:var(--shadow)}
.alayer-head{display:flex;align-items:center;gap:12px;margin-bottom:12px}
.alayer-tag{font-family:'JetBrainsMono',monospace;font-size:11px;font-weight:700;color:#fff;padding:3px 9px;border-radius:5px;letter-spacing:.05em}
.alayer-name{font-size:15px;font-weight:700}
.alayer-note{font-size:12px;color:var(--muted);margin-left:auto}
.alayer-body{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.anode{border:1px solid var(--rule);border-radius:10px;padding:11px 13px;background:linear-gradient(180deg,#fff,rgba(255,255,255,.6));transition:transform .15s,box-shadow .15s}
.anode:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.06)}
.anode h4{font-size:13.5px;font-weight:700;margin-bottom:3px;display:flex;align-items:center;gap:6px}
.anode h4 .dot{width:7px;height:7px;border-radius:50%;display:inline-block}
.anode p{font-size:12px;color:var(--muted);line-height:1.5}
.anode .tech{font-family:'JetBrainsMono',monospace;font-size:11px;color:var(--accent);margin-top:5px;display:block}
.alayer.client .alayer-tag{background:linear-gradient(135deg,#1d6fff,#3b82f6)} .alayer.client .anode h4 .dot{background:#1d6fff}
.alayer.gw .alayer-tag{background:linear-gradient(135deg,#7c3aed,#8b5cf6)} .alayer.gw .anode h4 .dot{background:#7c3aed}
.alayer.svc .alayer-tag{background:linear-gradient(135deg,#0d9488,#14b8a6)} .alayer.svc .anode h4 .dot{background:#0d9488}
.alayer.data .alayer-tag{background:linear-gradient(135deg,#e0852b,#f59e0b)} .alayer.data .anode h4 .dot{background:#e0852b}
.alayer.ext .alayer-tag{background:linear-gradient(135deg,#475569,#64748b)} .alayer.ext .anode h4 .dot{background:#64748b}
.flow-arrow{text-align:center;color:var(--muted);font-size:16px;line-height:1;margin:-3px 0}
/* ===== MODULE CARDS ===== */
.client-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.client-card{background:var(--bg2);border:1px solid var(--rule);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow)}
.client-card.full{grid-column:1/-1}
.cc-head{padding:14px 18px;color:#fff;display:flex;align-items:center;gap:12px}
.cc-head .icon{width:32px;height:32px;border-radius:8px;background:rgba(255,255,255,.18);display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700}
.cc-head h3{font-size:16px;font-weight:700}
.cc-head .role{font-size:11.5px;opacity:.85;margin-left:auto;background:rgba(255,255,255,.15);padding:3px 10px;border-radius:999px}
.cc-blue .cc-head{background:linear-gradient(135deg,#1d6fff,#2563eb)}
.cc-green .cc-head{background:linear-gradient(135deg,#0d9488,#14b8a6)}
.cc-purple .cc-head{background:linear-gradient(135deg,#7c3aed,#8b5cf6)}
.cc-amber .cc-head{background:linear-gradient(135deg,#e0852b,#f59e0b)}
.cc-slate .cc-head{background:linear-gradient(135deg,#475569,#64748b)}
.cc-body{padding:7px 7px 11px}
.mod-list{display:grid;grid-template-columns:1fr 1fr;gap:7px;padding:5px}
.mod{background:rgba(0,0,0,.02);border:1px solid var(--rule);border-radius:8px;padding:9px 11px}
.mod h4{font-size:12.5px;font-weight:700;margin-bottom:2px}
.mod p{font-size:11px;color:var(--muted);line-height:1.45}
/* ===== TABLE ===== */
.table-wrap{overflow-x:auto;max-height:560px;overflow-y:auto;border:1px solid var(--rule);border-radius:var(--radius);background:var(--bg2)}
table{width:100%;border-collapse:collapse;font-size:13px}
thead th{position:sticky;top:0;background:#f1f5fb;color:var(--ink);font-weight:700;text-align:left;padding:9px 13px;border-bottom:2px solid var(--rule);z-index:1}
tbody td{padding:8px 13px;border-bottom:1px solid var(--rule);vertical-align:top}
tbody tr:hover{background:rgba(29,111,255,.03)}
tbody td code{font-family:'JetBrainsMono',monospace;font-size:12px;color:var(--accent);background:rgba(29,111,255,.06);padding:1px 5px;border-radius:4px}
.pill{display:inline-block;font-size:11px;font-weight:700;padding:2px 8px;border-radius:999px}
.pill-yes{background:rgba(13,148,136,.12);color:#0d9488}
.pill-no{background:rgba(239,68,68,.1);color:#dc2626}
/* ===== CALLOUT ===== */
.callout{background:var(--bg2);border:1px solid var(--rule);border-left:4px solid var(--accent);border-radius:0 var(--radius) var(--radius) 0;padding:15px 18px;margin:16px 0;box-shadow:var(--shadow)}
.callout.warn{border-left-color:var(--warn)}
.callout h4{font-size:14px;font-weight:700;margin-bottom:5px}
.callout p{font-size:13.5px;color:var(--muted)}
.callout p strong{color:var(--ink)}
/* ===== DB COMPARISON ===== */
.db-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:16px 0}
.db-card{background:var(--bg2);border:2px solid var(--rule);border-radius:var(--radius);padding:18px;box-shadow:var(--shadow);position:relative}
.db-card.rec{border-color:var(--accent2);background:linear-gradient(180deg,rgba(13,148,136,.04),#fff)}
.db-card .badge{position:absolute;top:-11px;right:14px;font-size:11px;font-weight:700;background:var(--accent2);color:#fff;padding:3px 10px;border-radius:999px}
.db-card h3{font-size:17px;font-weight:700;margin-bottom:3px}
.db-card .ver{font-family:'JetBrainsMono',monospace;font-size:12px;color:var(--muted);margin-bottom:10px}
.db-card ul{list-style:none;padding:0}
.db-card li{font-size:12.5px;padding:4px 0;border-bottom:1px dashed var(--rule);display:flex;gap:8px}
.db-card li:last-child{border:none}
.db-card li .ico{flex-shrink:0;font-weight:700}
.db-card.rec li .ico{color:var(--accent2)} .db-card.alt li .ico{color:var(--muted)}
/* ===== MERMAID ===== */
.mermaid{background:var(--bg2);border:1px solid var(--rule);border-radius:var(--radius);padding:22px;text-align:center;margin:12px 0;box-shadow:var(--shadow)}
/* ===== PERMISSION FLOW ===== */
.perm-flow{display:flex;align-items:stretch;gap:0;margin:16px 0;flex-wrap:wrap}
.perm-step{flex:1;min-width:170px;background:var(--bg2);border:1px solid var(--rule);border-radius:var(--radius);padding:13px;margin:0 5px;box-shadow:var(--shadow)}
.perm-step .num{font-family:'JetBrainsMono',monospace;font-size:11px;font-weight:700;color:var(--accent);background:rgba(29,111,255,.08);padding:2px 7px;border-radius:5px;display:inline-block;margin-bottom:7px}
.perm-step h4{font-size:13.5px;font-weight:700;margin-bottom:3px}
.perm-step p{font-size:12px;color:var(--muted)}
.perm-arrow{display:flex;align-items:center;color:var(--accent);font-size:18px;font-weight:700;padding:0 2px}
/* ===== ISOLATION ===== */
.iso-vis{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:16px 0}
.iso-box{background:var(--bg2);border:1px solid var(--rule);border-radius:var(--radius);padding:16px;box-shadow:var(--shadow)}
.iso-box h4{font-size:14.5px;font-weight:700;margin-bottom:8px;display:flex;align-items:center;gap:8px}
.iso-box h4 .tg{font-size:11px;font-weight:700;padding:2px 7px;border-radius:5px}
.iso-box .rows{display:flex;flex-direction:column;gap:5px}
.iso-row{display:flex;align-items:center;gap:8px;font-family:'JetBrainsMono',monospace;font-size:11.5px;padding:6px 10px;border-radius:6px;background:rgba(0,0,0,.02);border:1px solid var(--rule)}
.iso-row .cid{font-weight:700;color:var(--accent)}
.iso-box.business .tg{background:rgba(29,111,255,.12);color:var(--accent)}
.iso-box.timeseries .tg{background:rgba(13,148,136,.12);color:var(--accent2)}
/* ===== FOOTER ===== */
footer{margin-top:36px;padding:24px 0 44px;border-top:1px solid var(--rule)}
footer .ft{font-size:13px;color:var(--muted);text-align:center}
/* ===== RESPONSIVE ===== */
@media(max-width:860px){
.alayer-body{grid-template-columns:1fr 1fr}
.client-grid,.db-grid,.iso-vis{grid-template-columns:1fr}
.perm-flow{flex-direction:column} .perm-arrow{transform:rotate(90deg);justify-content:center;padding:4px 0}
.dg-backend{grid-template-columns:repeat(3,1fr)} .dg-data{grid-template-columns:1fr 1fr}
.hero h1{font-size:27px}
}
@media(max-width:520px){
.alayer-body,.mod-list,.dg-backend,.dg-data{grid-template-columns:1fr}
.dg-block{max-width:none;flex:1 1 100%}
.dg-gateway{flex-direction:column}
.wrap{padding:0 14px}
}
</style>
</head>
<body>
<div class="wrap">
<!-- ===== HERO ===== -->
<header class="hero">
<span class="tag">SYSTEM ARCHITECTURE · V1.0</span>
<h1>鑫潼智电系统 · 实现架构设计</h1>
<p class="sub">基于《鑫潼智电系统需求分析报告 V1.0》,按功能模块拆解为后端、前端、小程序、APP 的完整系统实现架构。覆盖 ICC 平台 API 对接、数据隔离、权限控制等关键设计决策。</p>
<div class="meta">
<span>运营主体:<b>鑫潼科技(单一运营方)</b></span>
<span>后端:<b>Midway.js + TypeORM</b></span>
<span>前端:<b>Vue3 + Ant Design Vue</b></span>
<span>APP/小程序:<b>uni-app</b></span>
<span>数据库:<b>PostgreSQL</b></span>
</div>
</header>
<!-- ===== 01. 架构总览框图 ===== -->
<section id="overview-diagram">
<div class="section-head">
<span class="section-num">01</span>
<h2>架构总览框图</h2>
<span class="desc">五层架构 · 一图全局</span>
</div>
<div class="dg-legend">
<div class="li"><span class="lc" style="background:var(--c-blue)"></span>运营管理平台 Web</div>
<div class="li"><span class="lc" style="background:var(--c-purple)"></span>运营管理 APP(业务员)</div>
<div class="li"><span class="lc" style="background:var(--c-green)"></span>共享电工 APP</div>
<div class="li"><span class="lc" style="background:var(--c-amber)"></span>客户微信小程序</div>
<div class="li"><span class="lc" style="background:var(--c-slate)"></span>大华 ICC 外部</div>
<div class="li"><span class="lc" style="background:var(--c-teal, #0f766e)"></span>后端服务 Midway</div>
<div class="li"><span class="lc" style="background:var(--c-red)"></span>权限控制</div>
</div>
<div class="dg-diagram">
<!-- L1 Client -->
<div class="dg-layer">
<div class="dg-layer-label">
<span class="dg-ll-tag" style="background:var(--c-blue)">L1</span>
<span class="dg-ll-name">客户端层 — 五端入口</span>
<span class="dg-ll-desc">不同角色访问不同端,运营方双端(Web + APP)</span>
</div>
<div class="dg-blocks">
<div class="dg-block dg-blue">
<div class="dh"><div class="di">PC</div><div><div class="dt">运营管理平台</div><div class="dx">Vue3 + Ant Design Vue</div></div></div>
<ul><li>用户:运营管理员</li><li>审批/配置/报表/系统设置</li><li>全量数据查看与管理</li><li>RBAC 菜单级权限</li></ul>
</div>
<div class="dg-block dg-purple">
<div class="dh"><div class="di">APP</div><div><div class="dt">运营管理 APP</div><div class="dx">uni-app → iOS / Android</div></div></div>
<ul><li>用户:业务员(运营方)</li><li>客户管理 / 项目跟进</li><li>勘察/回访/现场巡查</li><li>共享 sys_user 表 + 角色区分</li></ul>
</div>
<div class="dg-block dg-teal">
<div class="dh"><div class="di">APP</div><div><div class="dt">共享电工 APP</div><div class="dx">uni-app → iOS / Android</div></div></div>
<ul><li>用户:共享电工</li><li>五类派单接单处理</li><li>现场水印拍照取证上传</li><li>领料管理 / 报酬查询</li></ul>
</div>
<div class="dg-block dg-amber">
<div class="dh"><div class="di">WX</div><div><div class="dt">客户微信小程序</div><div class="dx">uni-app → 微信小程序</div></div></div>
<ul><li>用户:客户(学校/医院)</li><li>设备状态 / 用电统计查看</li><li>报警信息 / 月度报告下载</li><li>水电报修 / 服务评分</li></ul>
</div>
<div class="dg-block dg-slate dg-ext">
<div class="dh"><div class="di">ICC</div><div><div class="dt">大华 ICC 平台</div><div class="dx">外部系统 · API 对接</div></div></div>
<ul><li>设备数据采集平台(厂家)</li><li>网关/探测器设备管理</li><li>设备运行状态 / 报警</li><li>用电采集数据</li></ul>
</div>
</div>
</div>
<div class="dg-conn">▼</div>
<!-- L2 Gateway -->
<div class="dg-layer">
<div class="dg-layer-label">
<span class="dg-ll-tag" style="background:var(--c-purple)">L2</span>
<span class="dg-ll-name">接入网关层 — 统一入口</span>
<span class="dg-ll-desc">认证 · 路由 · 限流 · 数据权限拦截</span>
</div>
<div class="dg-gateway">
<div class="gi"><h4>统一鉴权</h4><p>JWT 双 Token<br/>多端统一登录态</p></div>
<div class="gi"><h4>API 路由分发</h4><p>/admin · /ops · /worker<br/>/customer 分组路由</p></div>
<div class="gi"><h4>接口数据权限</h4><p>Interceptor 注入过滤<br/>给什么数据看什么</p></div>
<div class="gi"><h4>限流与日志</h4><p>rate-limiter<br/>操作审计 winston</p></div>
</div>
</div>
<div class="dg-conn">▼</div>
<!-- L3 Backend -->
<div class="dg-layer">
<div class="dg-layer-label">
<span class="dg-ll-tag" style="background:var(--c-green)">L3</span>
<span class="dg-ll-name">业务服务层 — Midway.js(Node.js + TypeScript + TypeORM)</span>
<span class="dg-ll-desc">10 个核心服务模块</span>
</div>
<div class="dg-backend">
<div class="dg-be"><div class="bi">👥</div><h4>客户管理</h4><p>客户/部门<br/>建筑/房间<br/>账号分配</p></div>
<div class="dg-be"><div class="bi">📋</div><h4>项目管理</h4><p>勘察/方案<br/>合同/安装<br/>验收/回访</p></div>
<div class="dg-be"><div class="bi">📡</div><h4>设备管理</h4><p>ICC 设备同步<br/>线路分配<br/>部门/房间映射</p></div>
<div class="dg-be"><div class="bi">📮</div><h4>派单管理</h4><p>勘察/安装<br/>报警/报修/维保<br/>全生命周期</p></div>
<div class="dg-be"><div class="bi">🔄</div><h4>ICC 数据采集</h4><p>定时拉取<br/>队列缓冲<br/>清洗映射</p></div>
<div class="dg-be"><div class="bi">📊</div><h4>统计报表</h4><p>多维用电统计<br/>月度报告生成<br/>报酬计算</p></div>
<div class="dg-be"><div class="bi">📸</div><h4>取证文件</h4><p>水印拍照取证<br/>合同/验收存档<br/>月报文件</p></div>
<div class="dg-be"><div class="bi">🔔</div><h4>消息通知</h4><p>APP 推送<br/>微信订阅消息<br/>短信(按需)</p></div>
<div class="dg-be"><div class="bi">⚙️</div><h4>系统管理</h4><p>用户/角色/菜单<br/>数据字典<br/>日志审计</p></div>
<div class="dg-be"><div class="bi">🔐</div><h4>认证授权</h4><p>JWT 签发校验<br/>微信 openid 登录<br/>RBAC 权限</p></div>
</div>
</div>
<div class="dg-conn">▼</div>
<!-- L4 Data -->
<div class="dg-layer">
<div class="dg-layer-label">
<span class="dg-ll-tag" style="background:var(--c-amber)">L4</span>
<span class="dg-ll-name">数据层 — 存储与缓存</span>
<span class="dg-ll-desc">逻辑隔离 + 时序分区 · 不做物理分库</span>
</div>
<div class="dg-data">
<div class="dg-db d-amber"><div class="dmi">🐘</div><h4>PostgreSQL 主库</h4><p>业务数据(customer_id 逻辑隔离)<br/>用电采集数据(按月分区)<br/>TypeORM + 声明式分区</p></div>
<div class="dg-db d-red"><div class="dmi">⚡</div><h4>Redis 缓存</h4><p>JWT 会话 Token<br/>数据字典/热点配置<br/>BullMQ 队列底层</p></div>
<div class="dg-db d-blue"><div class="dmi">📁</div><h4>对象存储 RustFS</h4><p>取证照片(水印)<br/>合同/验收/方案文件<br/>月度报告文档<br/>国产 · S3 兼容 · Apache 2.0</p></div>
<div class="dg-db d-green"><div class="dmi">📬</div><h4>BullMQ 消息队列</h4><p>ICC 拉取数据缓冲<br/>报警事件异步处理<br/>基于 Redis 轻量级</p></div>
</div>
</div>
<div class="dg-conn">▼</div>
<!-- L5 External -->
<div class="dg-layer">
<div class="dg-layer-label">
<span class="dg-ll-tag" style="background:var(--c-slate)">L5</span>
<span class="dg-ll-name">外部系统对接层</span>
<span class="dg-ll-desc">仅 API 对接 · 不直接操作硬件</span>
</div>
<div class="dg-blocks">
<div class="dg-block dg-slate dg-ext" style="max-width:380px;flex:2">
<div class="dh"><div class="di">ICC</div><div><div class="dt">大华 ICC OpenAPI</div><div class="dx">HTTP REST · 主动拉取</div></div></div>
<ul><li>客户信息接口 → 每小时/事件触发</li><li>设备信息接口(网关/探测器)→ 每小时</li><li>用电采集数据接口 → 5~15 分钟</li><li>设备报警数据接口 → 1 分钟</li></ul>
</div>
<div class="dg-block dg-green dg-ext">
<div class="dh"><div class="di">WX</div><div><div class="dt">微信开放平台</div><div class="dx">小程序登录 + 订阅消息</div></div></div>
<ul><li>openid 登录授权</li><li>订阅消息推送</li><li>客户账号绑定</li></ul>
</div>
<div class="dg-block dg-amber dg-ext">
<div class="dh"><div class="di">SMS</div><div><div class="dt">短信服务</div><div class="dx">阿里云短信(可选)</div></div></div>
<ul><li>客户初始密码通知</li><li>报警短信提醒</li></ul>
</div>
</div>
</div>
</div>
<!-- Permission overlay -->
<div class="dg-perm">
<h4>🔒 权限控制方案(贯穿 L1 → L3)</h4>
<div class="pr">
<div class="pi"><div class="pt">运营管理平台(Web)— RBAC 菜单级</div><div class="pd">运营管理员:审批/配置/报表/系统设置,全量数据,全部菜单</div></div>
<div class="pi"><div class="pt">运营管理 APP — 角色 + 数据权限</div><div class="pd">业务员:客户管理/项目跟进/勘察回访,按 salesman_id 过滤自己负责的数据</div></div>
<div class="pi"><div class="pt">共享电工 APP — 接口级数据权限</div><div class="pd">接口按 worker_id 过滤:仅自己接的派单、个人报酬明细,给什么看什么</div></div>
<div class="pi"><div class="pt">客户小程序 — 接口级数据权限</div><div class="pd">接口按 customer_id 过滤:仅本客户设备/用电/报警/月报/报修记录</div></div>
</div>
</div>
</section>
<!-- ===== 02. 总体分层架构(详细) ===== -->
<section id="overview">
<div class="section-head">
<span class="section-num">02</span>
<h2>总体分层架构(详细)</h2>
<span class="desc">五层架构 · 自上而下数据流转</span>
</div>
<div class="arch-stack">
<div class="alayer client">
<div class="alayer-head"><span class="alayer-tag">CLIENT</span><span class="alayer-name">客户端层 · 五端入口</span><span class="alayer-note">运营方双端(Web+APP),统一鉴权</span></div>
<div class="alayer-body">
<div class="anode"><h4><span class="dot"></span>运营管理平台(Web)</h4><p>运营管理员使用:审批、配置、报表、系统设置</p><span class="tech">Vue3 + Ant Design Vue</span></div>
<div class="anode"><h4><span class="dot"></span>运营管理 APP</h4><p>业务员移动办公:客户管理、项目跟进、现场勘察回访</p><span class="tech">uni-app (iOS / Android)</span></div>
<div class="anode"><h4><span class="dot"></span>共享电工 APP</h4><p>共享电工接单、现场拍照取证、报酬查询</p><span class="tech">uni-app (iOS / Android)</span></div>
<div class="anode"><h4><span class="dot"></span>客户微信小程序</h4><p>客户查看用电/报警/月报、报修申请、评分</p><span class="tech">uni-app → 微信小程序</span></div>
</div>
<div class="alayer-body" style="margin-top:10px;grid-template-columns:1fr">
<div class="anode" style="border-style:dashed"><h4><span class="dot" style="background:#64748b"></span>大华 ICC 管理平台</h4><p>设备数据采集平台(厂家后台,非自研)· API 对接</p><span class="tech">外部系统 · HTTP REST</span></div>
</div>
</div>
<div class="flow-arrow">▼</div>
<div class="alayer gw">
<div class="alayer-head"><span class="alayer-tag">GATEWAY</span><span class="alayer-name">接入网关层 · 统一入口</span><span class="alayer-note">认证、路由、限流、日志</span></div>
<div class="alayer-body">
<div class="anode"><h4><span class="dot"></span>统一鉴权服务</h4><p>JWT Token 签发与校验,多端统一登录态</p><span class="tech">Midway Guard + JWT</span></div>
<div class="anode"><h4><span class="dot"></span>API 路由分发</h4><p>按端区分路由前缀:/admin · /ops · /worker · /customer</p><span class="tech">Midway Controller 分组</span></div>
<div class="anode"><h4><span class="dot"></span>接口级数据权限</h4><p>接口决定可见数据范围,APP/小程序给什么看什么</p><span class="tech">Guard + Interceptor</span></div>
<div class="anode"><h4><span class="dot"></span>限流与日志</h4><p>常规 ERP 并发量,请求日志与操作审计</p><span class="tech">rate-limiter + winston</span></div>
</div>
</div>
<div class="flow-arrow">▼</div>
<div class="alayer svc">
<div class="alayer-head"><span class="alayer-tag">SERVICE</span><span class="alayer-name">业务服务层 · Midway.js 核心模块</span><span class="alayer-note">按业务域划分,模块化服务</span></div>
<div class="alayer-body">
<div class="anode"><h4><span class="dot"></span>客户与项目模块</h4><p>客户/部门/建筑/房间、勘察/方案/合同/验收/回访</p><span class="tech">Customer · Project Service</span></div>
<div class="anode"><h4><span class="dot"></span>运营派单模块</h4><p>报警派单、报修派单、维保派单、安装派单</p><span class="tech">Dispatch Service</span></div>
<div class="anode"><h4><span class="dot"></span>设备与线路模块</h4><p>设备信息同步、线路分配、设备状态管理</p><span class="tech">Device · Line Service</span></div>
<div class="anode"><h4><span class="dot"></span>统计与报表模块</h4><p>多维度用电统计、月度报告、共享电工报酬</p><span class="tech">Report Service</span></div>
</div>
<div class="alayer-body" style="margin-top:10px">
<div class="anode"><h4><span class="dot"></span>ICC 数据采集模块</h4><p>定时拉取设备/报警/用电数据,缓冲写入</p><span class="tech">ICC Sync Service + 定时任务</span></div>
<div class="anode"><h4><span class="dot"></span>取证与文件模块</h4><p>现场拍照取证、合同/验收/方案文件存档</p><span class="tech">File Service + 对象存储</span></div>
<div class="anode"><h4><span class="dot"></span>系统管理模块</h4><p>用户、角色、菜单权限(RBAC)、字典、日志</p><span class="tech">System Service</span></div>
<div class="anode"><h4><span class="dot"></span>消息通知模块</h4><p>报警推送到 APP、派单提醒、微信消息</p><span class="tech">Notify Service</span></div>
</div>
</div>
<div class="flow-arrow">▼</div>
<div class="alayer data">
<div class="alayer-head"><span class="alayer-tag">DATA</span><span class="alayer-name">数据层 · 存储与缓存</span><span class="alayer-note">逻辑隔离 + 时序分区,不做物理分库</span></div>
<div class="alayer-body">
<div class="anode"><h4><span class="dot"></span>PostgreSQL 主库</h4><p>业务数据(逻辑隔离)+ 用电采集数据(按月分区)</p><span class="tech">TypeORM + 声明式分区</span></div>
<div class="anode"><h4><span class="dot"></span>Redis 缓存</h4><p>会话 Token、数据字典、热点配置、限流计数</p><span class="tech">ioredis</span></div>
<div class="anode"><h4><span class="dot"></span>对象存储</h4><p>取证照片、合同/验收/方案电子文件、月度报告</p><span class="tech">RustFS(国产 · S3 兼容)</span></div>
<div class="anode"><h4><span class="dot"></span>消息队列</h4><p>ICC 拉取数据缓冲、报警事件异步处理</p><span class="tech">BullMQ (Redis 队列)</span></div>
</div>
</div>
<div class="flow-arrow">▼</div>
<div class="alayer ext">
<div class="alayer-head"><span class="alayer-tag">EXTERNAL</span><span class="alayer-name">外部系统对接层</span><span class="alayer-note">仅 API 对接,不直接操作硬件</span></div>
<div class="alayer-body">
<div class="anode"><h4><span class="dot"></span>大华 ICC OpenAPI</h4><p>设备信息、设备状态、报警数据、用电采集数据</p><span class="tech">HTTP REST · 主动拉取</span></div>
<div class="anode"><h4><span class="dot"></span>微信开放平台</h4><p>小程序登录授权(openid)、消息订阅推送</p><span class="tech">微信登录 + 订阅消息</span></div>
<div class="anode"><h4><span class="dot"></span>短信服务(可选)</h4><p>客户账号初始密码、报警短信通知(按需)</p><span class="tech">阿里云短信</span></div>
<div class="anode"><h4><span class="dot"></span>智电终端设备</h4><p>网关/探测器 → ICC 平台采集 → 鑫潼系统拉取</p><span class="tech">不直接对接</span></div>
</div>
</div>
</div>
<div class="callout">
<h4>架构设计要点</h4>
<p>① <strong>不直接对接硬件</strong>:所有设备数据通过大华 ICC 平台 API 拉取,鑫潼系统只做数据消费方。② <strong>五端共享一套后端</strong>:Midway.js 统一提供 /admin(Web管理台)、/ops(运营APP)、/worker(电工APP)、/customer(小程序)四组路由,避免重复开发。③ <strong>运营方双端同一套账号</strong>:运营管理平台(Web)和运营管理 APP(业务员)共用 sys_user 表,通过角色区分权限,共用 RBAC 体系。④ <strong>ICC 内部虽实时推送,但鑫潼侧采用主动拉取</strong>,通过定时任务按采集频次拉取,进入 BullMQ 队列缓冲后写入分区表。</p>
</div>
</section>
<!-- ===== 03. 技术栈选型 ===== -->
<section id="tech">
<div class="section-head">
<span class="section-num">03</span>
<h2>技术栈选型</h2>
<span class="desc">含数据库选型建议</span>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>层级</th><th>技术选型</th><th>版本/说明</th><th>选型理由</th></tr></thead>
<tbody>
<tr><td>后端框架</td><td><code>Midway.js</code></td><td>Node.js · IoC + TypeORM</td><td>阿里出品,TypeScript 原生支持,装饰器风格,模块化清晰</td></tr>
<tr><td>前端框架</td><td><code>Vue 3 + Ant Design Vue</code></td><td>Composition API</td><td>AntDV 组件丰富,适合中后台 ERP 型管理系统</td></tr>
<tr><td>共享电工 APP</td><td><code>uni-app</code></td><td>跨 iOS / Android</td><td>一套代码多端发布,共享电工端仅需移动 App</td></tr>
<tr><td>运营管理 APP</td><td><code>uni-app</code></td><td>跨 iOS / Android</td><td>业务员移动办公,共用 sys_user 表,复用 uni-app 技术栈</td></tr>
<tr><td>小程序</td><td><code>uni-app → 微信小程序</code></td><td>客户端使用</td><td>复用 uni-app 技术栈,降低学习成本</td></tr>
<tr><td>数据库</td><td><code>PostgreSQL</code></td><td>15+</td><td>时序数据分区成熟、多维聚合分析强、JSONB 灵活(详见下方对比)</td></tr>
<tr><td>ORM</td><td><code>TypeORM</code></td><td>Midway 内置集成</td><td>与 Midway 深度集成,支持 PostgreSQL 分区表</td></tr>
<tr><td>缓存</td><td><code>Redis</code></td><td>7.x</td><td>会话、字典缓存、限流、BullMQ 队列底层</td></tr>
<tr><td>消息队列</td><td><code>BullMQ</code></td><td>基于 Redis</td><td>轻量级,无需额外引入 Kafka,满足常规 ERP 并发</td></tr>
<tr><td>对象存储</td><td><code>RustFS(国产)</code></td><td>S3 兼容 · Apache 2.0</td><td>取证照片、合同/验收文件、月度报告存档,替代 MinIO</td></tr>
<tr><td>鉴权</td><td><code>JWT</code></td><td>双 Token 刷新</td><td>多端统一,无状态易扩展</td></tr>
</tbody>
</table>
</div>
<h3 style="margin:24px 0 6px;font-size:17px">数据库选型对比:PostgreSQL vs MySQL</h3>
<p style="color:var(--muted);font-size:13px;margin-bottom:12px">基于本系统的数据特征(时序用电数据 + 多维统计报表 + ICC 灵活设备数据)进行对比。</p>
<div class="db-grid">
<div class="db-card rec">
<span class="badge">推荐</span>
<h3>PostgreSQL</h3>
<div class="ver">v15+ · 开源 · 对象关系型</div>
<ul>
<li><span class="ico">✓</span>声明式分区成熟,用电采集数据按月分区查询性能优</li>
<li><span class="ico">✓</span>窗口函数/CTE/物化视图,多维统计(部门/楼栋/楼层/房间)能力强</li>
<li><span class="ico">✓</span>JSONB 类型存储 ICC 灵活设备数据,支持索引查询</li>
<li><span class="ico">✓</span>Midway + TypeORM 完整支持,迁移成本低</li>
<li><span class="ico">✓</span>行级安全(RLS)可选,未来可做更细数据隔离</li>
</ul>
</div>
<div class="db-card alt">
<h3>MySQL</h3>
<div class="ver">v8.x · 开源 · 关系型</div>
<ul>
<li><span class="ico">△</span>分区功能有但不如 PG 灵活,大表管理偏弱</li>
<li><span class="ico">△</span>聚合分析能力可用,但复杂多维统计不如 PG</li>
<li><span class="ico">△</span>JSON 类型有但查询和索引能力弱于 JSONB</li>
<li><span class="ico">✓</span>国内团队更熟悉,运维资源好找</li>
<li><span class="ico">△</span>月度报告按部门/楼栋统计时性能不如 PG</li>
</ul>
</div>
</div>
<div class="callout">
<h4>结论:推荐 PostgreSQL</h4>
<p>本系统最核心的数据特征是<strong>用电采集数据持续增长(时序)</strong>和<strong>月度报告多维统计</strong>。PostgreSQL 的声明式分区能直接解决您担心的"数据量暴涨"问题——按月分区后,单表数据被切分为多个物理子表,查询只扫描相关分区,性能稳定。配合物化视图预聚合月度统计,即使客户和设备数量增长,报表查询依然高效。</p>
</div>
</section>
<!-- ===== 04. 四端功能模块拆解 ===== -->
<section id="modules">
<div class="section-head">
<span class="section-num">04</span>
<h2>五端功能模块拆解</h2>
<span class="desc">后端 · Web前端 · 运营APP · 电工APP · 小程序</span>
</div>
<div class="client-grid" style="margin-bottom:16px">
<div class="client-card full cc-green">
<div class="cc-head"><div class="icon">BE</div><h3>后端服务(Midway.js)</h3><span class="role">五端共享 · 统一 API</span></div>
<div class="cc-body"><div class="mod-list">
<div class="mod"><h4>客户管理服务</h4><p>客户信息(省市县分类)、客户部门、客户访问账号分配</p></div>
<div class="mod"><h4>项目管理服务</h4><p>建筑/房间、勘察派单、智电方案、合同、安装、验收、回访</p></div>
<div class="mod"><h4>设备管理服务</h4><p>ICC 设备信息同步、智电设备/网关/探测器、线路分配(部门/房间)</p></div>
<div class="mod"><h4>派单管理服务</h4><p>勘察/安装/报警/报修/维保五类派单全生命周期管理</p></div>
<div class="mod"><h4>ICC 数据采集服务</h4><p>定时拉取设备/报警/用电数据,队列缓冲,写入分区表</p></div>
<div class="mod"><h4>统计报表服务</h4><p>按部门/楼栋/楼层/房间维度用电统计,月度报告生成,报酬计算</p></div>
<div class="mod"><h4>取证文件服务</h4><p>现场拍照取证(水印)、合同/验收/方案/月报文件存档</p></div>
<div class="mod"><h4>消息通知服务</h4><p>APP 推送(报警/派单)、微信订阅消息、短信(按需)</p></div>
<div class="mod"><h4>系统管理服务</h4><p>用户/角色/菜单(RBAC)、数据字典、操作日志、定时任务</p></div>
<div class="mod"><h4>认证授权服务</h4><p>JWT 签发校验、微信 openid 登录、接口级数据权限拦截</p></div>
</div></div>
</div>
</div>
<div class="client-grid">
<div class="client-card cc-blue">
<div class="cc-head"><div class="icon">PC</div><h3>运营管理平台(Web 前端)</h3><span class="role">运营管理员</span></div>
<div class="cc-body"><div class="mod-list">
<div class="mod"><h4>运营工作台</h4><p>全局数据概览、报警总览、派单总览、待办事项</p></div>
<div class="mod"><h4>审批与配置</h4><p>方案审批、合同审批、派单规则配置、数据字典</p></div>
<div class="mod"><h4>运营派单</h4><p>报警派单(从 ICC 拉取报警)、报修派单、维保派单、评分管理</p></div>
<div class="mod"><h4>设备监控</h4><p>设备列表、线路分配(部门/房间)、设备状态、报警查询</p></div>
<div class="mod"><h4>统计报表</h4><p>多维度用电统计、月度报告生成下载、共享电工报酬统计</p></div>
<div class="mod"><h4>系统管理</h4><p>用户/角色/菜单权限配置、员工管理、日志审计</p></div>
</div></div>
</div>
<div class="client-card cc-purple">
<div class="cc-head"><div class="icon">APP</div><h3>运营管理 APP(uni-app)</h3><span class="role">业务员(运营方)</span></div>
<div class="cc-body"><div class="mod-list">
<div class="mod"><h4>客户管理</h4><p>客户档案查看、联系人管理、客户项目列表(和后台功能一致,移动端适配)</p></div>
<div class="mod"><h4>项目管理</h4><p>项目跟进、勘察任务、方案查看、合同查看、验收与回访</p></div>
<div class="mod"><h4>派单查看</h4><p>负责客户的派单进度跟踪、报警处理进度、报修处理进度</p></div>
<div class="mod"><h4>现场巡查</h4><p>现场拍照记录、巡查日志提交、问题上报</p></div>
<div class="mod"><h4>消息通知</h4><p>派单提醒、报警推送、客户消息、系统通知</p></div>
<div class="mod"><h4>我的</h4><p>个人信息、修改密码、负责客户统计、业绩查看</p></div>
</div></div>
</div>
<div class="client-card cc-purple">
<div class="cc-head"><div class="icon">APP</div><h3>共享电工 APP(uni-app)</h3><span class="role">共享电工</span></div>
<div class="cc-body"><div class="mod-list">
<div class="mod"><h4>派单接收</h4><p>勘察/安装/报警/报修/维保五类派单推送与接单确认</p></div>
<div class="mod"><h4>领料管理</h4><p>安装派单自动生成设备/耗材领用单,领料确认</p></div>
<div class="mod"><h4>现场处理</h4><p>配电井/箱位置记录、按标准流程处置、水印拍照取证上传</p></div>
<div class="mod"><h4>任务提交</h4><p>处理完成后照片上传、提交派单、查看历史任务</p></div>
<div class="mod"><h4>报酬查询</h4><p>月度派单完成统计、单笔/月度收入明细查询</p></div>
<div class="mod"><h4>客户回访</h4><p>业务员回访记录上传(拍照+意见)</p></div>
</div></div>
</div>
<div class="client-card cc-amber">
<div class="cc-head"><div class="icon">WX</div><h3>客户微信小程序(uni-app)</h3><span class="role">客户(学校/医院等)</span></div>
<div class="cc-body"><div class="mod-list">
<div class="mod"><h4>设备状态查看</h4><p>所属设备在线状态、线路用电实时数据</p></div>
<div class="mod"><h4>报警信息</h4><p>设备报警记录查询、处理进度跟踪</p></div>
<div class="mod"><h4>用电统计</h4><p>按部门/楼栋/楼层/房间多维度用电查询、节电情况</p></div>
<div class="mod"><h4>月度报告</h4><p>当月用电统计查看、用电及改善报告文件下载</p></div>
<div class="mod"><h4>报修申请</h4><p>水电问题报修提交(描述+拍照)、报修进度查询</p></div>
<div class="mod"><h4>服务评分</h4><p>报修/维保派单完成后对共享电工评分</p></div>
</div></div>
</div>
<div class="client-card cc-slate" style="border-style:dashed">
<div class="cc-head"><div class="icon">ICC</div><h3>大华 ICC 平台(外部)</h3><span class="role">API 对接 · 非自研</span></div>
<div class="cc-body" style="padding:12px"><div class="mod-list">
<div class="mod"><h4>客户信息接口</h4><p>ICC 录入意向客户 → 推送到运营管理平台</p></div>
<div class="mod"><h4>设备信息接口</h4><p>网关/探测器设备数据同步到运营管理平台</p></div>
<div class="mod"><h4>设备数据采集接口</h4><p>用电实时数据按采集频次同步拉取</p></div>
<div class="mod"><h4>设备报警接口</h4><p>设备运行状态、预警类型、报警数值拉取</p></div>
</div></div>
</div>
</div>
</section>
<!-- ===== 05. ICC 对接架构 ===== -->
<section id="icc">
<div class="section-head">
<span class="section-num">05</span>
<h2>ICC 平台对接架构</h2>
<span class="desc">主动拉取 · 队列缓冲 · 分区写入</span>
</div>
<div class="callout warn">
<h4>对接方式说明</h4>
<p>ICC 平台内部是实时推送机制,但<strong>鑫潼系统侧采用主动拉取</strong>(定时任务调 ICC OpenAPI)。ICC 接口文档待研究,当前按通用 REST API 模式设计,后续接入时按实际文档调整鉴权方式(AppKey/Token)和接口字段。</p>
</div>
<figure class="mermaid">
flowchart LR
subgraph ICC ["大华 ICC 平台(外部)"]
A1["客户信息"]
A2["设备信息<br/>网关/探测器"]
A3["用电采集数据<br/>(按频次)"]
A4["设备报警数据"]
end
subgraph SYNC ["鑫潼 ICC 采集服务"]
T1["定时任务调度<br/>不同频次拉取"]
Q1[("BullMQ 队列<br/>数据缓冲")]
P1["数据清洗与映射<br/>设备码 → 线路 → 客户"]
end
subgraph DB ["数据层"]
D1[("业务表<br/>设备/线路/客户")]
D2[("用电采集分区表<br/>按月分区")]
D3[("报警表")]
end
subgraph BIZ ["业务消费"]
B1["报警派单服务<br/>实时推送 APP"]
B2["统计报表服务<br/>多维聚合"]
B3["月度报告生成"]
end
A1 -->|"HTTP API 拉取"| T1
A2 -->|"HTTP API 拉取"| T1
A3 -->|"HTTP API 拉取<br/>高频(如5分钟)"| T1
A4 -->|"HTTP API 拉取<br/>中频(如1分钟)"| T1
T1 --> Q1
Q1 --> P1
P1 --> D1
P1 --> D2
P1 --> D3
D3 --> B1
D2 --> B2
D2 --> B3
style ICC fill:#f1f5f9,stroke:#475569,stroke-width:2px
style SYNC fill:#ecfdf5,stroke:#0d9488,stroke-width:2px
style DB fill:#fffbeb,stroke:#e0852b,stroke-width:2px
style BIZ fill:#eff6ff,stroke:#1d6fff,stroke-width:2px
</figure>
<div class="table-wrap" style="margin-top:16px">
<table>
<thead><tr><th>拉取数据</th><th>拉取频次(建议)</th><th>写入目标</th><th>消费方</th></tr></thead>
<tbody>
<tr><td>客户信息</td><td>事件触发 / 每小时</td><td>业务表</td><td>客户管理模块</td></tr>
<tr><td>设备信息(网关/探测器)</td><td>每小时</td><td>设备表 + 线路表</td><td>设备管理模块</td></tr>
<tr><td>用电采集数据</td><td><code>5~15 分钟</code>(按 ICC 采集频次)</td><td>用电采集分区表(按月)</td><td>统计报表 / 月度报告</td></tr>
<tr><td>设备报警数据</td><td><code>1 分钟</code></td><td>报警表</td><td>报警派单服务 → APP 推送</td></tr>
</tbody>
</table>
</div>
</section>
<!-- ===== 06. 数据隔离方案 ===== -->
<section id="isolation">
<div class="section-head">
<span class="section-num">06</span>
<h2>数据隔离方案</h2>
<span class="desc">逻辑隔离 + 时序分区 · 不做物理分库</span>
</div>
<div class="callout">
<h4>核心决策:是否需要多租户 / 分库?</h4>
<p>鑫潼是<strong>唯一运营主体</strong>,客户(学校/医院)是平台上的数据主体而非独立租户,因此<strong>不需要严格多租户架构</strong>。采用<strong>逻辑隔离(customer_id 行级隔离)+ 时序数据按月分区</strong>的混合方案,兼顾性能与运维成本。</p>
</div>
<div class="iso-vis">
<div class="iso-box business">
<h4>业务数据 · 逻辑隔离 <span class="tg">customer_id</span></h4>
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">客户、合同、派单、设备等业务表数据量有限,通过 customer_id 字段 + 索引隔离。</p>
<div class="rows">
<div class="iso-row"><span class="cid">C001</span> 客户A · 合同 · 派单 · 设备</div>
<div class="iso-row"><span class="cid">C002</span> 客户B · 合同 · 派单 · 设备</div>
<div class="iso-row"><span class="cid">C003</span> 客户C · 合同 · 派单 · 设备</div>
<div class="iso-row" style="opacity:.6"><span class="cid">...</span> 共享同一张表,行级隔离</div>
</div>
</div>
<div class="iso-box timeseries">
<h4>用电采集数据 · 按月分区 <span class="tg">RANGE BY 采集时间</span></h4>
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">数据量增量大头。按采集时间分区,查询只扫描相关月份分区,性能稳定。</p>
<div class="rows">
<div class="iso-row">electricity_2026_05 · 分区子表</div>
<div class="iso-row">electricity_2026_06 · 分区子表</div>
<div class="iso-row">electricity_2026_07 · 分区子表</div>
<div class="iso-row">electricity_2026_08 · 分区子表(当前)</div>
<div class="iso-row" style="opacity:.6">历史分区可归档,不影响热数据查询</div>
</div>
</div>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>方案</th><th>是否采用</th><th>适用数据</th><th>理由</th></tr></thead>
<tbody>
<tr><td>逻辑隔离(customer_id 行级)</td><td><span class="pill pill-yes">采用</span></td><td>业务数据(客户/合同/派单/设备)</td><td>数据量有限,索引隔离足够,支持跨客户运营统计</td></tr>
<tr><td>时序数据按月分区</td><td><span class="pill pill-yes">采用</span></td><td>用电采集数据</td><td>解决数据量暴涨问题,查询只扫相关分区</td></tr>
<tr><td>物理分库(每客户独立库)</td><td><span class="pill pill-no">不采用</span></td><td>—</td><td>运维成本高,跨客户统计困难,无合规强制要求</td></tr>
<tr><td>独立 Schema 隔离</td><td><span class="pill pill-no">不采用</span></td><td>—</td><td>增加复杂度但收益有限,逻辑隔离已满足</td></tr>
<tr><td>严格多租户(Tenant 模型)</td><td><span class="pill pill-no">不采用</span></td><td>—</td><td>鑫潼是单一运营方,客户非独立租户</td></tr>
</tbody>
</table>
</div>
<div class="callout warn">
<h4>关于"数据量暴涨影响性能"的回应</h4>
<p>数据增长的大头是<strong>用电采集数据</strong>(每条线路按采集频次持续写入),而非业务数据。业务数据(客户/合同/派单)即使客户增长到上千家,数据量仍在百万级以内,逻辑隔离 + 索引完全够用。用电采集数据通过<strong>按月分区 + 物化视图预聚合月度统计</strong>,即使积累数年数据,查询也只扫描目标月份分区,性能可控。<strong>不需要物理分库。</strong></p>
</div>
</section>
<!-- ===== 07. 权限控制方案 ===== -->
<section id="permission">
<div class="section-head">
<span class="section-num">07</span>
<h2>权限控制方案</h2>
<span class="desc">后台 RBAC 菜单级 · APP/小程序接口级</span>
</div>
<div class="callout">
<h4>权限设计原则</h4>
<p>现阶段权限粗放管理:<strong>运营方双端(Web + APP)共用 sys_user 表</strong>,采用 RBAC 菜单级权限(角色绑定菜单/按钮);<strong>共享电工 APP 和客户小程序的数据权限由接口定义</strong>——接口返回什么数据,端上就只能看到什么数据,不在端上做复杂权限判断。</p>
</div>
<h3 style="margin:18px 0 10px;font-size:16px">权限流转链路</h3>
<div class="perm-flow">
<div class="perm-step"><span class="num">STEP 1</span><h4>用户登录</h4><p>运营方双端:账号密码(sys_user)<br/>电工APP:账号密码(worker)<br/>小程序:微信 openid</p></div>
<div class="perm-arrow">→</div>
<div class="perm-step"><span class="num">STEP 2</span><h4>JWT 签发</h4><p>Token 携带:userId、user_type、role、customerId<br/>双 Token 刷新</p></div>
<div class="perm-arrow">→</div>
<div class="perm-step"><span class="num">STEP 3</span><h4>菜单/路由权限</h4><p>运营方双端:RBAC 角色 → 菜单<br/>电工APP/小程序:固定功能页</p></div>
<div class="perm-arrow">→</div>
<div class="perm-step"><span class="num">STEP 4</span><h4>接口数据权限</h4><p>接口根据 user_type + role 过滤数据<br/>给什么看什么</p></div>
</div>
<h3 style="margin:24px 0 10px;font-size:16px">角色与权限矩阵</h3>
<div class="table-wrap">
<table>
<thead><tr><th>角色</th><th>使用端</th><th>账号表</th><th>菜单权限(RBAC)</th><th>接口数据权限</th></tr></thead>
<tbody>
<tr><td>运营管理员</td><td>运营管理平台(Web)</td><td>sys_user</td><td>全部菜单:工作台/审批/派单/设备/报表/系统管理</td><td>全量数据,所有客户和派单</td></tr>
<tr><td>业务员</td><td>运营管理平台(Web)+ 运营管理 APP</td><td>sys_user</td><td>客户/项目/派单查看/报表查看(按需配置)</td><td>仅自己负责的客户(salesman_id 过滤)</td></tr>
<tr><td>共享电工</td><td>共享电工 APP</td><td>worker</td><td>—(APP 固定功能页)</td><td>仅分配给自己的派单、个人报酬明细</td></tr>
<tr><td>客户</td><td>微信小程序</td><td>customer_account</td><td>—(小程序固定功能页)</td><td>仅本客户设备/用电/报警/月报/报修记录</td></tr>
</tbody>
</table>
</div>
<div class="callout">
<h4>接口级数据权限实现要点</h4>
<p>① <strong>共享电工</strong>:查询派单接口自动按 <code>worker_id = 当前用户</code> 过滤;报酬接口只返回本人数据。② <strong>客户</strong>:查询设备/用电/报警接口自动按 <code>customer_id = 当前客户</code> 过滤;报修只能提交本客户的。③ <strong>业务员(运营管理 APP + Web)</strong>:查询客户/项目/派单接口按 <code>salesman_id = 当前用户</code> 过滤,只能看自己负责的客户数据(运营管理员不受限)。④ <strong>运营方双端共用一套 RBAC</strong>:Web 管理台和运营管理 APP 共用 sys_user 表 + 角色体系,菜单权限由后端统一返回,前端/APP 根据返回动态渲染。通过 Midway Interceptor 统一注入过滤条件,避免每个接口手动处理。</p>
</div>
</section>
<!-- ===== 08. 关键业务数据流 ===== -->
<section id="dataflow">
<div class="section-head">
<span class="section-num">08</span>
<h2>关键业务数据流</h2>
<span class="desc">端到端数据流转</span>
</div>
<figure class="mermaid">
flowchart TB
subgraph PHASE1 ["项目阶段"]
P1["ICC 录入客户"] --> P2["运营平台配置建筑/房间"]
P2 --> P3["APP 勘察派单<br/>现场拍照"]
P3 --> P4["运营平台制定方案"]
P4 --> P5["签订合同"]
P5 --> P6["APP 安装派单<br/>设备配置指向 ICC"]
P6 --> P7["ICC 同步设备信息<br/>到运营平台"]
P7 --> P8["线路分配部门/房间"]
P8 --> P9["分配客户访问账号"]
P9 --> P10["项目验收"]
end
subgraph PHASE2 ["运营阶段"]
O1["ICC 报警数据"] -->|"定时拉取"| O2["运营平台生成报警派单"]
O2 --> O3["APP 推送接单"]
O3 --> O4["现场处置拍照"]
O4 --> O5["运营管理员评分"]
M1["客户小程序报修"] --> M2["运营平台生成报修派单"]
M2 --> M3["APP 接单处理"]
M3 --> M4["客户小程序评分"]
R1["ICC 用电数据"] -->|"定时拉取"| R2["分区表存储"]
R2 --> R3["多维统计聚合"]
R3 --> R4["月度报告生成"]
R4 --> R5["小程序下载查看"]
end
P10 --> O1
P10 --> M1
P10 --> R1
style PHASE1 fill:#eff6ff,stroke:#1d6fff,stroke-width:2px
style PHASE2 fill:#ecfdf5,stroke:#0d9488,stroke-width:2px
</figure>
</section>
<!-- ===== 09. 设计假设清单 ===== -->
<section id="assumptions">
<div class="section-head">
<span class="section-num">09</span>
<h2>设计假设清单</h2>
<span class="desc">已采用的假设 · 如需调整请告知</span>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>假设项</th><th>假设内容</th><th>来源</th></tr></thead>
<tbody>
<tr><td>运营主体</td><td>鑫潼为唯一运营方,不做白标输出</td><td>用户确认</td></tr>
<tr><td>多租户</td><td>不需要严格多租户,采用逻辑隔离</td><td>用户确认 + 架构建议</td></tr>
<tr><td>分库</td><td>不做物理分库,用电数据按月分区</td><td>架构建议(解决数据量问题)</td></tr>
<tr><td>ICC 对接</td><td>主动拉取(定时任务),非 ICC 推送</td><td>用户确认</td></tr>
<tr><td>ICC 接口文档</td><td>待研究,按通用 REST API 模式预留</td><td>用户确认</td></tr>
<tr><td>后台权限</td><td>RBAC 菜单级,现阶段粗放管理</td><td>用户确认</td></tr>
<tr><td>APP/小程序数据权限</td><td>接口定义数据范围,给什么看什么</td><td>用户确认</td></tr>
<tr><td>共享电工 APP 技术栈</td><td>uni-app 跨 iOS/Android</td><td>用户确认</td></tr>
<tr><td>运营管理 APP</td><td>uni-app,业务员使用,功能与后台一致,共用 sys_user 表</td><td>用户确认</td></tr>
<tr><td>客户小程序</td><td>微信小程序,运营后台分配账号</td><td>用户确认</td></tr>
<tr><td>子账号</td><td>待定,当前按单账号预留扩展</td><td>用户确认(待定)</td></tr>
<tr><td>电子签章</td><td>不需要,但需取证拍照(水印)</td><td>用户确认</td></tr>
<tr><td>并发量</td><td>常规 ERP 级,无需特殊高并发架构</td><td>用户确认</td></tr>
<tr><td>数据库</td><td>PostgreSQL(架构建议,用户待定)</td><td>架构建议</td></tr>
<tr><td>消息队列</td><td>BullMQ(Redis 基础),不引入 Kafka</td><td>架构建议(匹配并发量)</td></tr>
<tr><td>后端框架</td><td>Midway.js + TypeORM</td><td>用户确认</td></tr>
<tr><td>前端框架</td><td>Vue3 + Ant Design Vue</td><td>用户确认</td></tr>
</tbody>
</table>
</div>
</section>
<footer>
<p class="ft">鑫潼智电系统实现架构设计 · 基于《鑫潼智电系统需求分析报告 V1.0》 · 2026-08</p>
</footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'default',
securityLevel: 'loose',
flowchart: { htmlLabels: true, curve: 'basis', useMaxWidth: true },
themeVariables: {
primaryColor: '#ffffff', primaryTextColor: '#0f1e2e', primaryBorderColor: '#1d6fff',
lineColor: '#94a3b8', fontSize: '14px',
fontFamily: 'PingFang SC, Microsoft YaHei, sans-serif',
clusterBkg: '#f8fafc', clusterBorder: '#1d6fff'
}
});
</script>
</body>
</html>