Explorar el Código

style(gis): align map pages with alarm map chrome

Kazerin hace 1 mes
padre
commit
479e413b95

+ 8 - 0
memory/sessions/2026-09-16.md

@@ -47,3 +47,11 @@
 - 新增契约测试:指标卡定位回归、三图卡片与抽屉结构一致性。
 - 验证:前端全量测试 114/114 通过;生产构建通过。
 - 仍需人工验收:三图指标卡与抽屉的实际渲染效果、点位数量与颜色、左侧面板滚动。
+
+## 补充:GIS 页面骨架对齐监测报警一张图
+
+- 目标结构以「监测报警一张图」为准:页面标题区(图标 + 标题 + 副标题 + 刷新)→ 独立筛选卡片(卡片标题 + 关键字/类型 + 查询/重置)→ 地图卡片(内嵌统一页壳)。
+- 设施图与设备图补齐页面标题区与筛选卡片;筛选控件与页壳内搜索框绑定同一组状态,保持行为一致。
+- 新增共享样式 `src/views/subSystem/waterSupply/components/waterGisPageTheme.scss`,承载 `.gis-page` 页面骨架、标题区、筛选卡片、卡片标题与地图卡片样式;设施图与设备图通过非 scoped `@use` 引入,避免逐页复制。
+- 报警图保持原样作为参照基准,其本地样式与共享骨架取值一致。
+- 验证:前端全量测试 116/116 通过;生产构建通过。

+ 47 - 7
src/views/subSystem/waterSupply/components/WaterDeviceGisMap.vue

@@ -1,4 +1,38 @@
 <template>
+  <div class="water-page gis-page">
+    <div class="page-heading">
+      <div class="page-title">
+        <span class="page-title-icon"><el-icon><MapLocation/></el-icon></span>
+        <div><h2>监测设备 GIS 一张图</h2><span class="muted">设备点位分布与状态查询</span></div>
+      </div>
+      <div class="heading-actions">
+        <el-button :icon="Refresh" :loading="loading" @click="loadData">刷新</el-button>
+      </div>
+    </div>
+
+    <el-card shadow="never" class="water-panel filter-card">
+      <template #header>
+        <div class="card-title">
+          <span class="card-heading"><el-icon><Search/></el-icon><span>设备筛选</span></span>
+        </div>
+      </template>
+      <el-form :inline="true" class="filter-form">
+        <el-form-item label="关键字">
+          <el-input v-model="searchKeyword" class="keyword-input" clearable placeholder="设备编号 / 名称 / 位置"
+                    :prefix-icon="Search" @keyup.enter="loadData"/>
+        </el-form-item>
+        <el-form-item label="设备类型">
+          <el-select v-model="selectedTypeId" class="status-select" clearable placeholder="全部设备类型">
+            <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="loadData">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
   <el-card shadow="never" class="water-panel map-card">
   <WaterGisPageShell
     embedded
@@ -190,11 +224,12 @@
     </template>
   </WaterGisPageShell>
   </el-card>
+  </div>
 </template>
 
 <script setup>
 import { computed, nextTick, onMounted, ref } from 'vue'
-import { Aim, CircleCheck, CircleClose, Odometer, Refresh, Search, Warning } from '@element-plus/icons-vue'
+import { Aim, CircleCheck, CircleClose, MapLocation, Odometer, Refresh, Search, Warning } from '@element-plus/icons-vue'
 import GisMapCanvas from '@/components/GisMapCanvas.vue'
 import WaterGisPageShell from '@/components/WaterGisPageShell.vue'
 import { getMonitoringPointColor, getMonitoringPointIcon, getMonitoringPointLabel, getMonitoringPointStatus, getMonitoringPointTagType } from '@/utils/gisMapColors'
@@ -331,6 +366,13 @@ async function loadData() {
   }
 }
 
+function resetFilters() {
+  searchKeyword.value = ''
+  selectedTypeId.value = ''
+  statusFilters.value = ['normal', 'alarm', 'warning', 'workOrder', 'offline']
+  loadData()
+}
+
 function fitBounds() {
   gisMapRef.value?.fitBounds()
 }
@@ -361,12 +403,6 @@ onMounted(loadData)
 </script>
 
 <style scoped>
-.map-card {
-  min-height: 560px;
-  border: 1px solid #e1ebf3;
-  border-radius: 8px;
-  box-shadow: 0 5px 16px rgba(28, 75, 110, .05);
-}
 .header-actions {
   display: flex;
   align-items: center;
@@ -518,3 +554,7 @@ onMounted(loadData)
   }
 }
 </style>
+
+<style lang="scss">
+@use './waterGisPageTheme.scss';
+</style>

+ 95 - 0
src/views/subSystem/waterSupply/components/waterGisPageTheme.scss

@@ -0,0 +1,95 @@
+/* 供水 GIS 一张图页面骨架:与「监测报警一张图」保持同一结构与视觉 */
+
+.gis-page {
+  min-height: calc(100vh - 84px);
+  padding: 20px;
+  color: #1e293b;
+  background: linear-gradient(135deg, #eff6ff 0%, #f8fafc 45%, #eef2f7 100%);
+}
+
+.gis-page .page-heading {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.gis-page .page-title {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.gis-page .page-title-icon {
+  display: grid;
+  place-items: center;
+  width: 42px;
+  height: 42px;
+  color: #fff;
+  background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
+  border: 0;
+  border-radius: 8px;
+  box-shadow: 0 8px 16px rgba(37, 99, 235, .2);
+  font-size: 22px;
+}
+
+.gis-page .page-heading h2 {
+  margin: 0 0 5px;
+  font-size: 20px;
+}
+
+.gis-page .muted {
+  font-size: 13px;
+  color: #909399;
+}
+
+.gis-page .filter-card,
+.gis-page .map-card {
+  margin-bottom: 16px;
+}
+
+.gis-page .filter-form .el-form-item {
+  margin-top: 8px;
+  margin-right: 18px;
+  margin-bottom: 8px;
+}
+
+.gis-page .keyword-input {
+  width: 250px;
+}
+
+.gis-page .type-select,
+.gis-page .status-select {
+  width: 150px;
+}
+
+.gis-page .card-heading {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  color: #1e293b;
+  font-weight: 600;
+}
+
+.gis-page .card-heading .el-icon {
+  color: #2563eb;
+  font-size: 18px;
+}
+
+.gis-page .map-card {
+  min-height: 560px;
+  border: 1px solid #e1ebf3;
+  border-radius: 8px;
+  box-shadow: 0 5px 16px rgba(28, 75, 110, .05);
+}
+
+@media (max-width: 620px) {
+  .gis-page {
+    padding: 12px;
+  }
+
+  .gis-page .page-heading {
+    align-items: flex-start;
+    flex-direction: column;
+  }
+}

+ 55 - 7
src/views/subSystem/waterSupply/facility/gis.vue

@@ -1,4 +1,38 @@
 <template>
+  <div class="water-page gis-page">
+    <div class="page-heading">
+      <div class="page-title">
+        <span class="page-title-icon"><el-icon><MapLocation/></el-icon></span>
+        <div><h2>供水设施 GIS 一张图</h2><span class="muted">设施点位分布与查询定位</span></div>
+      </div>
+      <div class="heading-actions">
+        <el-button :icon="Refresh" :loading="loading" @click="refreshScene">刷新</el-button>
+      </div>
+    </div>
+
+    <el-card shadow="never" class="water-panel filter-card">
+      <template #header>
+        <div class="card-title">
+          <span class="card-heading"><el-icon><Search/></el-icon><span>设施筛选</span></span>
+        </div>
+      </template>
+      <el-form :inline="true" class="filter-form">
+        <el-form-item label="关键字">
+          <el-input v-model="searchKeyword" class="keyword-input" clearable placeholder="设施名称 / 编号 / 地址"
+                    :prefix-icon="Search" @keyup.enter="applyFilters"/>
+        </el-form-item>
+        <el-form-item label="设施类型">
+          <el-select v-model="selectedFacilityType" class="status-select" clearable placeholder="全部设施类型">
+            <el-option v-for="item in facilityTypes" :key="item.value" :label="item.label" :value="item.value"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="applyFilters">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
   <el-card shadow="never" class="water-panel map-card">
   <WaterGisPageShell
     embedded
@@ -149,11 +183,12 @@
     </template>
   </WaterGisPageShell>
   </el-card>
+  </div>
 </template>
 
 <script setup>
 import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
-import { Aim, Connection, Odometer, OfficeBuilding, Refresh, Search, SetUp } from '@element-plus/icons-vue'
+import { Aim, Connection, MapLocation, Odometer, OfficeBuilding, Refresh, Search, SetUp } from '@element-plus/icons-vue'
 import { ElMessage } from 'element-plus'
 import GisMapCanvas from '@/components/GisMapCanvas.vue'
 import WaterGisPageShell from '@/components/WaterGisPageShell.vue'
@@ -370,6 +405,21 @@ async function loadFacilityDetail() {
   }
 }
 
+function applyFilters() {
+  if (searchKeyword.value.trim()) {
+    handleSearch()
+    return
+  }
+  loadGisData()
+}
+
+function resetFilters() {
+  searchKeyword.value = ''
+  selectedFacilityType.value = ''
+  detailOpen.value = false
+  loadGisData()
+}
+
 async function refreshScene() {
   layerState.source = true
   layerState.plant = true
@@ -385,12 +435,6 @@ async function refreshScene() {
 </script>
 
 <style scoped>
-.map-card {
-  min-height: 560px;
-  border: 1px solid #e1ebf3;
-  border-radius: 8px;
-  box-shadow: 0 5px 16px rgba(28, 75, 110, .05);
-}
 .header-actions {
   display: flex;
   align-items: center;
@@ -520,3 +564,7 @@ async function refreshScene() {
   }
 }
 </style>
+
+<style lang="scss">
+@use '../components/waterGisPageTheme.scss';
+</style>

+ 25 - 0
tests/waterGisConsistency.test.mjs

@@ -93,4 +93,29 @@ test('all three GIS maps render the shared shell inside the same map card and dr
     assert.match(source, /<section class="detail-hero">/u, `${name} should use the shared drawer hero`)
     assert.match(source, /<section class="detail-section">/u, `${name} should use the shared drawer section`)
   }
+})
+test('all three GIS maps share the page heading and filter card chrome', async () => {
+  for (const [name, path] of Object.entries(pages)) {
+    const source = await readFile(path, 'utf8')
+
+    assert.match(source, /class="page-heading"/u, `${name} should render the page heading`)
+    assert.match(source, /class="page-title-icon"/u, `${name} should render the page title icon`)
+    assert.match(source, /class="water-panel filter-card"/u, `${name} should render the filter card`)
+    assert.match(source, /class="filter-form"/u, `${name} should render the inline filter form`)
+    assert.match(source, />查询<\/el-button>/u, `${name} should expose a query action`)
+    assert.match(source, />重置<\/el-button>/u, `${name} should expose a reset action`)
+  }
+})
+
+test('the GIS page chrome theme is shared by the facility and device maps', async () => {
+  const theme = await readFile('src/views/subSystem/waterSupply/components/waterGisPageTheme.scss', 'utf8')
+  const facility = await readFile(pages.facility, 'utf8')
+  const device = await readFile(pages.device, 'utf8')
+
+  assert.match(theme, /\.gis-page \.page-heading/u)
+  assert.match(theme, /\.gis-page \.page-title-icon/u)
+  assert.match(theme, /\.gis-page \.filter-form \.el-form-item/u)
+  assert.match(theme, /\.gis-page \.card-heading/u)
+  assert.match(facility, /@use '\.\.\/components\/waterGisPageTheme\.scss'/u)
+  assert.match(device, /@use '\.\/waterGisPageTheme\.scss'/u)
 })