infrastructure-base-archives-demo.html 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <title>基础设施 / 基础档案</title>
  7. <style>
  8. :root { --ink:#172033; --muted:#667085; --line:#d9dee7; --panel:#fff; --page:#f3f5f8; }
  9. * { box-sizing:border-box; }
  10. body { margin:0; min-height:100vh; color:var(--ink); font-family:"Microsoft YaHei",Arial,sans-serif; background:var(--page); }
  11. header { display:flex; flex-wrap:wrap; gap:12px; justify-content:space-between; align-items:center; padding:18px 24px; background:var(--panel); border-bottom:1px solid var(--line); }
  12. h1 { margin:0; font-size:20px; }
  13. .state { color:var(--muted); font-size:13px; }
  14. .state strong { color:#067647; }
  15. main { display:grid; grid-template-columns:minmax(330px,430px) minmax(0,1fr); min-height:calc(100vh - 61px); }
  16. aside { padding:18px; background:var(--panel); border-right:1px solid var(--line); }
  17. section { display:grid; grid-template-rows:auto minmax(0,1fr); }
  18. .toolbar { display:flex; justify-content:space-between; gap:12px; padding:14px 20px; background:var(--panel); border-bottom:1px solid var(--line); }
  19. button { min-width:74px; height:32px; border:1px solid #d0d5dd; border-radius:4px; background:#fff; color:var(--ink); cursor:pointer; }
  20. button:hover { border-color:#1570ef; color:#175cd3; }
  21. h2 { margin:16px 0 8px; font-size:15px; }
  22. .chart { display:grid; gap:6px; }
  23. .bar { display:grid; grid-template-columns:96px minmax(0,1fr) 34px; gap:8px; align-items:center; font-size:12px; }
  24. .track { height:8px; background:#eef1f5; }
  25. .fill { height:100%; width:0; background:#1570ef; }
  26. .item { cursor:pointer; }
  27. .item.active .fill { background:#067647; }
  28. .map { position:relative; overflow:hidden; background:linear-gradient(90deg,rgba(255,255,255,.4) 1px,transparent 1px),linear-gradient(0deg,rgba(255,255,255,.4) 1px,transparent 1px),#dfe5ec; background-size:70px 70px; }
  29. .point { position:absolute; width:13px; height:13px; border-radius:50%; background:#1570ef; border:2px solid #fff; box-shadow:0 1px 4px rgba(16,24,40,.25); }
  30. .point.pipeline { border-radius:2px; transform:rotate(45deg); }
  31. .point.hidden { display:none; }
  32. .label { position:absolute; font-size:11px; color:#344054; background:rgba(255,255,255,.88); padding:1px 4px; border-radius:3px; white-space:nowrap; }
  33. .legend { position:absolute; left:14px; bottom:14px; padding:7px 10px; border-radius:4px; background:rgba(255,255,255,.9); font-size:12px; color:var(--muted); }
  34. @media (max-width:900px) { main { grid-template-columns:1fr; } .map { min-height:420px; } aside { border-right:0; border-bottom:1px solid var(--line); } }
  35. </style>
  36. </head>
  37. <body>
  38. <header>
  39. <h1>基础设施 / 基础档案</h1>
  40. <div class="state">更新时间 <strong id="updatedAt">--</strong> · 新鲜度 <strong id="freshness">CURRENT</strong> · 设施 <strong id="total">0</strong></div>
  41. </header>
  42. <main>
  43. <aside>
  44. <h2>规模</h2>
  45. <div class="chart" id="scaleChart"></div>
  46. <h2>年龄</h2>
  47. <div class="chart" id="ageChart"></div>
  48. <h2>类型</h2>
  49. <div class="chart" id="typeChart"></div>
  50. <h2>行业</h2>
  51. <div class="chart" id="industryChart"></div>
  52. <h2>区域</h2>
  53. <div class="chart" id="regionChart"></div>
  54. </aside>
  55. <section>
  56. <div class="toolbar">
  57. <strong id="mapTitle">全部基础档案</strong>
  58. <button id="refresh" type="button">手动刷新</button>
  59. </div>
  60. <div class="map" id="map">
  61. <div class="legend">点击左侧分布项联动筛选</div>
  62. </div>
  63. </section>
  64. </main>
  65. <script>
  66. const records = [
  67. { name:"第一水厂", industry:"GAS", type:"WATER_PLANT", scale:"medium", age:"UNDER_5_YEARS", region:"CN", x:52, y:42 },
  68. { name:"2号泵站", industry:"WATER_SUPPLY", type:"PUMP_STATION", scale:"small", age:"5_TO_10_YEARS", region:"CN", x:65, y:57 },
  69. { name:"燃气管线 A", industry:"GAS", type:"GAS_PIPE", scale:null, age:"20_YEARS_OR_OVER", region:"CB", x:22, y:34 },
  70. { name:"供水管线 B", industry:"WATER_SUPPLY", type:"WATER_PIPE", scale:null, age:"10_TO_20_YEARS", region:"CN", x:75, y:35 },
  71. { name:"排水管线 C", industry:"DRAINAGE", type:"DRAINAGE_PIPE", scale:null, age:"20_YEARS_OR_OVER", region:"UNKNOWN", x:36, y:72 }
  72. ];
  73. const dimensions = {
  74. scale: ["medium","small"],
  75. age: ["UNDER_5_YEARS","5_TO_10_YEARS","10_TO_20_YEARS","20_YEARS_OR_OVER"],
  76. type: ["WATER_PLANT","PUMP_STATION","GAS_PIPE","WATER_PIPE","DRAINAGE_PIPE"],
  77. industry: ["GAS","WATER_SUPPLY","DRAINAGE"],
  78. region: ["CN","CB","UNKNOWN"]
  79. };
  80. let selected = {};
  81. function count(dimension, value) {
  82. return records.filter(item => String(item[dimension]) === value).length;
  83. }
  84. function matches(item) {
  85. return Object.entries(selected).every(([key, value]) => !value || String(item[key]) === value);
  86. }
  87. function renderCharts() {
  88. Object.entries(dimensions).forEach(([dimension, values]) => {
  89. const max = Math.max(...values.map(value => count(dimension, value)), 1);
  90. document.getElementById(dimension + "Chart").innerHTML = values.map(value => {
  91. const selectedCount = records.filter(item => matches(item) && String(item[dimension]) === value).length;
  92. const active = selected[dimension] === value ? " active" : "";
  93. return `<div class="bar item${active}" data-dimension="${dimension}" data-value="${value}">
  94. <span>${value}</span><div class="track"><div class="fill" style="width:${selectedCount / max * 100}%"></div></div><span>${selectedCount}</span>
  95. </div>`;
  96. }).join("");
  97. });
  98. }
  99. function renderMap() {
  100. const visible = records.filter(matches);
  101. const map = document.getElementById("map");
  102. map.querySelectorAll(".point,.label").forEach(node => node.remove());
  103. const title = Object.values(selected).filter(Boolean).join(" / ") || "全部基础档案";
  104. document.getElementById("mapTitle").textContent = title;
  105. visible.forEach(item => {
  106. const point = document.createElement("div");
  107. point.className = "point" + (item.type.includes("PIPE") ? " pipeline" : "");
  108. point.style.left = item.x + "%";
  109. point.style.top = item.y + "%";
  110. map.appendChild(point);
  111. const label = document.createElement("div");
  112. label.className = "label";
  113. label.style.left = `calc(${item.x}% + 10px)`;
  114. label.style.top = `calc(${item.y}% - 7px)`;
  115. label.textContent = item.name;
  116. map.appendChild(label);
  117. });
  118. }
  119. function render() {
  120. const visible = records.filter(matches);
  121. document.getElementById("total").textContent = visible.length;
  122. document.getElementById("updatedAt").textContent = "2026-09-02T10:00:00+08:00";
  123. renderCharts();
  124. renderMap();
  125. }
  126. document.addEventListener("click", event => {
  127. const item = event.target.closest(".item");
  128. if (!item) return;
  129. const {dimension, value} = item.dataset;
  130. selected[dimension] = selected[dimension] === value ? null : value;
  131. render();
  132. });
  133. document.getElementById("refresh").addEventListener("click", () => {
  134. selected = {};
  135. render();
  136. });
  137. render();
  138. </script>
  139. </body>
  140. </html>