| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>总体概览 / 基础设施</title>
- <style>
- :root {
- --ink: #172033;
- --muted: #667085;
- --line: #d9dee7;
- --panel: #ffffff;
- --page: #f3f5f8;
- --gas: #d92d20;
- --water: #1570ef;
- --drainage: #067647;
- }
- * { box-sizing: border-box; }
- body {
- margin: 0;
- min-height: 100vh;
- color: var(--ink);
- font-family: "Microsoft YaHei", "Segoe UI", Arial, sans-serif;
- background: var(--page);
- }
- header {
- display: flex;
- flex-wrap: wrap;
- gap: 16px;
- align-items: center;
- justify-content: space-between;
- padding: 18px 28px;
- background: var(--panel);
- border-bottom: 1px solid var(--line);
- }
- h1 { margin: 0; font-size: 20px; font-weight: 600; }
- .status { color: var(--muted); font-size: 13px; }
- .status strong { color: #067647; font-weight: 600; }
- .summary {
- display: grid;
- grid-template-columns: repeat(4, minmax(180px, 1fr));
- gap: 1px;
- background: var(--line);
- border-bottom: 1px solid var(--line);
- }
- .summary div { background: var(--panel); padding: 18px 28px; }
- .summary span { display: block; color: var(--muted); font-size: 13px; }
- .summary strong { display: block; margin-top: 5px; font-size: 26px; font-weight: 600; }
- .layout {
- display: grid;
- grid-template-columns: minmax(330px, 420px) minmax(0, 1fr);
- min-height: calc(100vh - 173px);
- }
- aside {
- border-right: 1px solid var(--line);
- background: var(--panel);
- }
- .filter {
- display: flex;
- gap: 8px;
- padding: 16px 20px;
- border-bottom: 1px solid var(--line);
- }
- button {
- min-width: 62px;
- height: 32px;
- padding: 0 12px;
- border: 1px solid var(--line);
- border-radius: 4px;
- background: #fff;
- color: var(--ink);
- font-size: 13px;
- cursor: pointer;
- }
- button.active { border-color: #1570ef; background: #eff6ff; color: #175cd3; font-weight: 600; }
- .industries { padding: 16px 20px; }
- .industry + .industry { margin-top: 12px; border-top: 1px solid var(--line); }
- .industry { padding-top: 12px; }
- .industry-head {
- display: flex;
- justify-content: space-between;
- align-items: baseline;
- font-size: 15px;
- font-weight: 600;
- }
- .industry-head small { color: var(--muted); font-size: 12px; }
- .bar {
- display: grid;
- grid-template-columns: 70px 1fr 36px;
- gap: 8px;
- align-items: center;
- margin-top: 10px;
- font-size: 12px;
- color: var(--muted);
- }
- .track { height: 8px; background: #eef1f5; overflow: hidden; }
- .fill { height: 100%; width: 0; }
- .facility-gas { background: var(--gas); }
- .facility-water { background: var(--water); }
- .facility-drainage { background: var(--drainage); }
- .types {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- margin-top: 10px;
- font-size: 12px;
- color: #475467;
- }
- .types span { padding: 3px 6px; background: #f7f8fa; border-radius: 3px; }
- .map-panel {
- display: grid;
- grid-template-rows: auto minmax(0, 1fr);
- background: #dfe5ec;
- }
- .map-tools {
- display: flex;
- justify-content: space-between;
- gap: 12px;
- padding: 14px 20px;
- background: var(--panel);
- border-bottom: 1px solid var(--line);
- font-size: 13px;
- }
- .legend { display: flex; gap: 14px; color: var(--muted); align-items: center; }
- .dot-label { display: inline-flex; align-items: center; gap: 5px; }
- .shape { width: 9px; height: 9px; border-radius: 50%; }
- .square { border-radius: 2px; }
- .map {
- position: relative;
- overflow: hidden;
- background:
- linear-gradient(0deg, rgba(255, 255, 255, .48) 1px, transparent 1px),
- linear-gradient(90deg, rgba(255, 255, 255, .48) 1px, transparent 1px),
- #dfe5ec;
- background-size: 80px 80px, 80px 80px;
- }
- .point { position: absolute; width: 13px; height: 13px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 1px 4px rgba(16, 24, 40, .24); }
- .point.facility { border-radius: 2px; transform: rotate(45deg); }
- .point.gas { background: var(--gas); }
- .point.water { background: var(--water); }
- .point.drainage { background: var(--drainage); }
- .point-label {
- position: absolute;
- transform: translate(14px, -4px);
- padding: 2px 5px;
- background: rgba(255, 255, 255, .88);
- font-size: 11px;
- color: #344054;
- border-radius: 3px;
- white-space: nowrap;
- }
- @media (max-width: 900px) {
- .summary { grid-template-columns: repeat(2, 1fr); }
- .layout { grid-template-columns: 1fr; min-height: auto; }
- aside { border-right: 0; border-bottom: 1px solid var(--line); }
- .map { height: 460px; }
- }
- </style>
- </head>
- <body>
- <header>
- <h1>总体概览 / 基础设施</h1>
- <div class="status">更新时间 <strong id="updated-at">2026-09-09 10:00:00 +08:00</strong> · 状态 <strong>CURRENT</strong></div>
- </header>
- <section class="summary">
- <div><span>设施总数</span><strong id="facility-total">418</strong></div>
- <div><span>智能设备总数</span><strong id="device-total">1126</strong></div>
- <div><span>未知类型</span><strong>0</strong></div>
- <div><span>类型错挂</span><strong>0</strong></div>
- </section>
- <main class="layout">
- <aside>
- <div class="filter" aria-label="行业筛选">
- <button data-industry="ALL" class="active">全部</button>
- <button data-industry="GAS">燃气</button>
- <button data-industry="WATER_SUPPLY">供水</button>
- <button data-industry="DRAINAGE">排水</button>
- </div>
- <div class="industries" id="industries"></div>
- </aside>
- <section class="map-panel">
- <div class="map-tools">
- <strong id="map-title">三行业基础设施一张图</strong>
- <div class="legend">
- <span class="dot-label"><i class="shape facility gas"></i>设施</span>
- <span class="dot-label"><i class="shape gas"></i>智能设备</span>
- </div>
- </div>
- <div class="map" id="map"></div>
- </section>
- </main>
- <script>
- const summary = {
- updatedAt: "2026-09-09T10:00:00+08:00",
- facilityCount: 418,
- smartDeviceCount: 1126,
- industries: [
- { code: "GAS", label: "燃气", facilityCount: 126, smartDeviceCount: 402,
- facilityTypes: [{ code: "PIPELINE", name: "燃气管线", count: 86 }, { code: "PUMP_STATION", name: "泵站", count: 40 }],
- deviceTypes: [{ code: "GAS_SENSOR", name: "燃气浓度计", count: 402 }] },
- { code: "WATER_SUPPLY", label: "供水", facilityCount: 158, smartDeviceCount: 368,
- facilityTypes: [{ code: "WATER_PLANT", name: "水厂", count: 38 }, { code: "PIPELINE", name: "供水管线", count: 82 }, { code: "PUMP_STATION", name: "泵站", count: 38 }],
- deviceTypes: [{ code: "WATER_SUPPLY_SENSOR", name: "供水压力计", count: 368 }] },
- { code: "DRAINAGE", label: "排水", facilityCount: 134, smartDeviceCount: 356,
- facilityTypes: [{ code: "PIPELINE", name: "排水管线", count: 96 }, { code: "PUMP_STATION", name: "泵站", count: 38 }],
- deviceTypes: [{ code: "RAINWATER_SENSOR", name: "雨水流量计", count: 186 }, { code: "SEWAGE_ENVIRONMENT_SENSOR", name: "污水环境监测仪", count: 170 }] }
- ]
- };
- const points = [
- { industry: "GAS", kind: "FACILITY", label: "燃气管线 A", x: 14, y: 32 },
- { industry: "GAS", kind: "SMART_DEVICE", label: "燃气浓度计-011", x: 26, y: 54 },
- { industry: "GAS", kind: "SMART_DEVICE", label: "燃气浓度计-024", x: 42, y: 26 },
- { industry: "WATER_SUPPLY", kind: "FACILITY", label: "第一水厂", x: 56, y: 40 },
- { industry: "WATER_SUPPLY", kind: "FACILITY", label: "供水管线 B", x: 68, y: 61 },
- { industry: "WATER_SUPPLY", kind: "SMART_DEVICE", label: "供水压力计-008", x: 47, y: 70 },
- { industry: "DRAINAGE", kind: "FACILITY", label: "排水管线 C", x: 78, y: 28 },
- { industry: "DRAINAGE", kind: "SMART_DEVICE", label: "雨水流量计-015", x: 84, y: 48 },
- { industry: "DRAINAGE", kind: "SMART_DEVICE", label: "污水环境监测仪-006", x: 31, y: 80 }
- ];
- const colorKey = code => code === "WATER_SUPPLY" ? "water" : code.toLowerCase();
- const maxCount = Math.max(...summary.industries.flatMap(item => [item.facilityCount, item.smartDeviceCount]));
- document.getElementById("updated-at").textContent = summary.updatedAt.replace("T", " ");
- document.getElementById("facility-total").textContent = summary.facilityCount.toLocaleString();
- document.getElementById("device-total").textContent = summary.smartDeviceCount.toLocaleString();
- document.getElementById("industries").innerHTML = summary.industries.map(industry => {
- const key = colorKey(industry.code);
- const types = [...industry.facilityTypes, ...industry.deviceTypes]
- .map(item => `<span>${item.name} ${item.count}</span>`).join("");
- return `
- <section class="industry">
- <div class="industry-head">${industry.label}<small>${industry.code}</small></div>
- <div class="bar"><span>设施</span><div class="track"><div class="fill facility-${key}" style="width:${industry.facilityCount / maxCount * 100}%"></div></div><span>${industry.facilityCount}</span></div>
- <div class="bar"><span>设备</span><div class="track"><div class="fill facility-${key}" style="width:${industry.smartDeviceCount / maxCount * 100}%"></div></div><span>${industry.smartDeviceCount}</span></div>
- <div class="types">${types}</div>
- </section>`;
- }).join("");
- function renderMap(industry) {
- const selected = points.filter(item => industry === "ALL" || item.industry === industry);
- document.getElementById("map-title").textContent = industry === "ALL"
- ? "三行业基础设施一张图"
- : `${summary.industries.find(item => item.code === industry).label}基础设施一张图`;
- document.getElementById("map").innerHTML = selected.map(point => `
- <div class="point ${point.kind === "FACILITY" ? "facility " : ""}${colorKey(point.industry)}" style="left:${point.x}%;top:${point.y}%"></div>
- <div class="point-label" style="left:${point.x}%;top:${point.y}%">${point.label}</div>`).join("");
- }
- document.querySelectorAll("[data-industry]").forEach(button => {
- button.addEventListener("click", () => {
- document.querySelectorAll("[data-industry]").forEach(item => item.classList.remove("active"));
- button.classList.add("active");
- renderMap(button.dataset.industry);
- });
- });
- renderMap("ALL");
- </script>
- </body>
- </html>
|