xsh_1997 1 miesiąc temu
rodzic
commit
eb7aa83db5

+ 2 - 2
ruoyi-screen/.env.development

@@ -24,8 +24,8 @@ VITE_GIS_WMTS_LAYER2=baqing_wp2
24 24
 # VITE_GIS_WMTS_LAYER_SWITCH_ZOOM=16
25 25
 # 瓦片边长:256 
26 26
 VITE_GIS_TILE_SIZE=256
27
-VITE_GIS_DEFAULT_CENTER=94.054094,31.920244
28
-VITE_GIS_DEFAULT_ZOOM=14
27
+VITE_GIS_DEFAULT_CENTER=94.043107,32.241973
28
+VITE_GIS_DEFAULT_ZOOM=9
29 29
 VITE_GIS_MIN_ZOOM=7
30 30
 # 地图最大缩放;建议与瓦片最高级别一致,避免放到顶过采样缺图
31 31
 VITE_GIS_MAX_ZOOM=20

+ 2 - 2
ruoyi-screen/.env.production

@@ -22,8 +22,8 @@ VITE_GIS_WMTS_LAYER=baqing_wp
22 22
 VITE_GIS_WMTS_LAYER2=baqing_wp2
23 23
 # VITE_GIS_WMTS_LAYER_SWITCH_ZOOM=16
24 24
 VITE_GIS_TILE_SIZE=256
25
-VITE_GIS_DEFAULT_CENTER=94.054094,31.920244
26
-VITE_GIS_DEFAULT_ZOOM=14
25
+VITE_GIS_DEFAULT_CENTER=94.043107,32.241973
26
+VITE_GIS_DEFAULT_ZOOM=9
27 27
 VITE_GIS_MIN_ZOOM=7
28 28
 VITE_GIS_MAX_ZOOM=20
29 29
 VITE_GIS_TILE_MAX_ZOOM=19

Plik diff jest za duży
+ 6016 - 0
ruoyi-screen/src/assets/mapJson/map.json


+ 183 - 1
ruoyi-screen/src/components/GisMap.vue

@@ -18,6 +18,15 @@ import {
18 18
   parsePoiLngLat,
19 19
   searchPois
20 20
 } from '@/utils/gisPoiSearch'
21
+import {
22
+  addTownshipBoundaryLayers,
23
+  bindTownshipBoundaryClick,
24
+  buildTownshipInfo,
25
+  flyToTownship,
26
+  getTownshipLabelPoints,
27
+  removeTownshipBoundaryLayers
28
+} from '@/utils/gisTownshipBoundary'
29
+import { getTownshipLivestockStats } from '@/constants/townshipLivestock'
21 30
 
22 31
 const props = defineProps({
23 32
   center: {
@@ -44,10 +53,30 @@ const props = defineProps({
44 53
   showPoiMarkers: {
45 54
     type: Boolean,
46 55
     default: true
56
+  },
57
+  /** 是否展示巴青县乡镇边界(map.json)及名称标注 */
58
+  showTownshipBoundary: {
59
+    type: Boolean,
60
+    default: true
61
+  },
62
+  /** 乡镇存栏/出栏覆盖,如 { 岗切乡: { inventory: 1, outbound: 2 } } */
63
+  townshipLabels: {
64
+    type: Object,
65
+    default: () => ({})
66
+  },
67
+  /** 点击乡镇是否下钻到该乡镇范围 */
68
+  townshipDrillDown: {
69
+    type: Boolean,
70
+    default: true
71
+  },
72
+  /** 点击乡镇下钻 zoom 级别 */
73
+  townshipDrillMaxZoom: {
74
+    type: Number,
75
+    default: 16
47 76
   }
48 77
 })
49 78
 
50
-const emit = defineEmits(['ready', 'click'])
79
+const emit = defineEmits(['ready', 'click', 'township-click'])
51 80
 
52 81
 const containerRef = ref(null)
53 82
 
@@ -64,6 +93,8 @@ let wheelDeltaAccum = 0
64 93
 let wheelDebounceTimer = null
65 94
 let lastWheelAround = null
66 95
 let poiMarkers = []
96
+let townshipMarkers = []
97
+let unbindTownshipClick = null
67 98
 
68 99
 function logClickCoordinate(lngLat) {
69 100
   if (!lngLat) {
@@ -107,6 +138,106 @@ function clearPoiMarkers() {
107 138
   poiMarkers = []
108 139
 }
109 140
 
141
+function clearTownshipMarkers() {
142
+  townshipMarkers.forEach((marker) => {
143
+    try {
144
+      marker.remove()
145
+    } catch {
146
+      /* ignore */
147
+    }
148
+  })
149
+  townshipMarkers = []
150
+}
151
+
152
+function teardownTownshipBoundary() {
153
+  if (typeof unbindTownshipClick === 'function') {
154
+    unbindTownshipClick()
155
+    unbindTownshipClick = null
156
+  }
157
+  clearTownshipMarkers()
158
+  if (map) {
159
+    removeTownshipBoundaryLayers(map)
160
+  }
161
+}
162
+
163
+function bindTownshipDrillDown() {
164
+  if (!props.townshipDrillDown || !map) {
165
+    return
166
+  }
167
+
168
+  unbindTownshipClick = bindTownshipBoundaryClick(map, (feature) => {
169
+    const info = buildTownshipInfo(feature)
170
+    if (!info) {
171
+      return
172
+    }
173
+
174
+    flyToTownship(map, feature, {
175
+      zoom: props.townshipDrillMaxZoom
176
+    })
177
+    emit('township-click', info)
178
+
179
+    if (props.showPoiMarkers && getGisPoiSearchMode() === GIS_POI_SEARCH_MODE.NEARBY) {
180
+      map.once('moveend', () => {
181
+        renderPoiMarkers()
182
+      })
183
+    }
184
+  })
185
+}
186
+
187
+function createTownshipMarkerElement(name, stats) {
188
+  const wrap = document.createElement('div')
189
+  wrap.className = 'gis-township-marker'
190
+
191
+  const nameEl = document.createElement('div')
192
+  nameEl.className = 'gis-township-marker__name'
193
+  nameEl.textContent = String(name || '')
194
+  wrap.appendChild(nameEl)
195
+
196
+  if (stats && stats.inventory != null) {
197
+    const inventoryEl = document.createElement('div')
198
+    inventoryEl.className = 'gis-township-marker__stat'
199
+    inventoryEl.textContent = `存栏:${stats.inventory}`
200
+    wrap.appendChild(inventoryEl)
201
+  }
202
+
203
+  if (stats && stats.outbound != null) {
204
+    const outboundEl = document.createElement('div')
205
+    outboundEl.className = 'gis-township-marker__stat gis-township-marker__stat--out'
206
+    outboundEl.textContent = `出栏:${stats.outbound}`
207
+    wrap.appendChild(outboundEl)
208
+  }
209
+
210
+  return wrap
211
+}
212
+
213
+function renderTownshipBoundary() {
214
+  if (!props.showTownshipBoundary || !map || !cmmapglRef) {
215
+    return
216
+  }
217
+
218
+  teardownTownshipBoundary()
219
+  if (!addTownshipBoundaryLayers(map)) {
220
+    return
221
+  }
222
+  bindTownshipDrillDown()
223
+
224
+  const Marker = cmmapglRef.Marker
225
+  if (typeof Marker !== 'function') {
226
+    return
227
+  }
228
+
229
+  getTownshipLabelPoints().forEach((item) => {
230
+    const stats = getTownshipLivestockStats(item.name, props.townshipLabels)
231
+    const marker = new Marker({
232
+      element: createTownshipMarkerElement(item.name, stats),
233
+      anchor: 'center'
234
+    })
235
+      .setLngLat(item.lngLat)
236
+      .addTo(map)
237
+    townshipMarkers.push(marker)
238
+  })
239
+}
240
+
110 241
 function escapeHtml(text) {
111 242
   return String(text || '')
112 243
     .replace(/&/g, '&')
@@ -200,6 +331,7 @@ function destroyMap() {
200 331
   lastWheelAround = null
201 332
   clampingZoom = false
202 333
   clearPoiMarkers()
334
+  teardownTownshipBoundary()
203 335
   if (map) {
204 336
     map.remove()
205 337
     map = null
@@ -438,6 +570,7 @@ async function initMap() {
438 570
       snapZoomToLimits()
439 571
       syncWmtsLayerVisibility(map)
440 572
       resizeMap()
573
+      renderTownshipBoundary()
441 574
       renderPoiMarkers()
442 575
       emit('ready', map)
443 576
     })
@@ -475,6 +608,19 @@ watch(
475 608
   }
476 609
 )
477 610
 
611
+watch(
612
+  () => [props.showTownshipBoundary, props.townshipLabels, props.townshipDrillDown],
613
+  () => {
614
+    if (!map || !cmmapglRef) return
615
+    if (props.showTownshipBoundary) {
616
+      renderTownshipBoundary()
617
+    } else {
618
+      teardownTownshipBoundary()
619
+    }
620
+  },
621
+  { deep: true }
622
+)
623
+
478 624
 onMounted(() => {
479 625
   initMap()
480 626
 })
@@ -544,4 +690,40 @@ defineExpose({ resizeMap, getMap: () => map })
544 690
   box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
545 691
   flex-shrink: 0;
546 692
 }
693
+
694
+.gis-township-marker {
695
+  display: flex;
696
+  flex-direction: column;
697
+  align-items: center;
698
+  gap: 2px;
699
+  pointer-events: none;
700
+  text-align: center;
701
+  padding: 5px;
702
+  border-radius: 6px;
703
+  background: rgba(8, 24, 22, 0.88);
704
+  border: 1px solid rgba(0, 220, 190, 0.35);
705
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
706
+  transform: translateY(-4px);
707
+}
708
+
709
+.gis-township-marker__name {
710
+  color: #e8fff9;
711
+  font-size: 13px;
712
+  font-weight: 600;
713
+  line-height: 1.3;
714
+  letter-spacing: 0.02em;
715
+  white-space: nowrap;
716
+}
717
+
718
+.gis-township-marker__stat {
719
+  color: #7dffe8;
720
+  font-size: 11px;
721
+  font-weight: 500;
722
+  line-height: 1.3;
723
+  white-space: nowrap;
724
+}
725
+
726
+.gis-township-marker__stat--out {
727
+  color: #f0b429;
728
+}
547 729
 </style>

+ 25 - 0
ruoyi-screen/src/constants/townshipLivestock.js

@@ -0,0 +1,25 @@
1
+/** 巴青县各乡镇牦牛存栏 / 出栏(大屏地图标注) */
2
+export const TOWNSHIP_LIVESTOCK_STATS = {
3
+  拉西镇: { inventory: 21587, outbound: 5670 },
4
+  雅安镇: { inventory: 12356, outbound: 3432 },
5
+  杂色镇: { inventory: 40489, outbound: 7028 },
6
+  本塔乡: { inventory: 11854, outbound: 2786 },
7
+  巴青乡: { inventory: 14158, outbound: 1523 },
8
+  岗切乡: { inventory: 32889, outbound: 3984 },
9
+  玛如乡: { inventory: 41186, outbound: 5632 },
10
+  贡日乡: { inventory: 7364, outbound: 1389 },
11
+  阿秀乡: { inventory: 14609, outbound: 1849 },
12
+  江绵乡: { inventory: 41575, outbound: 4156 }
13
+}
14
+
15
+export function getTownshipLivestockStats(name, overrides = {}) {
16
+  const base = TOWNSHIP_LIVESTOCK_STATS[name]
17
+  const custom = overrides?.[name]
18
+  if (!base && !custom) {
19
+    return null
20
+  }
21
+  if (custom && typeof custom === 'object') {
22
+    return { ...base, ...custom }
23
+  }
24
+  return base || null
25
+}

+ 392 - 0
ruoyi-screen/src/utils/gisTownshipBoundary.js

@@ -0,0 +1,392 @@
1
+import townshipGeoJson from '@/assets/mapJson/map.json'
2
+
3
+export const TOWNSHIP_SOURCE_ID = 'baqing-township'
4
+export const TOWNSHIP_FILL_LAYER_ID = 'baqing-township-fill'
5
+export const TOWNSHIP_LINE_GLOW_LAYER_ID = 'baqing-township-line-glow'
6
+export const TOWNSHIP_LINE_LAYER_ID = 'baqing-township-line'
7
+
8
+/** 乡镇名称标注手动微调(经度, 纬度) */
9
+const TOWNSHIP_LABEL_OVERRIDES = {
10
+  拉西镇: [94.003062, 32.041406],
11
+  玛如乡: [93.862433, 32.341736]
12
+}
13
+
14
+/** 乡镇点击下钻中心手动微调(经度, 纬度) */
15
+const TOWNSHIP_DRILL_CENTER_OVERRIDES = {
16
+  拉西镇: [94.054094, 31.920244]
17
+}
18
+
19
+function getTownshipDrillCenter(feature) {
20
+  if (!feature) {
21
+    return null
22
+  }
23
+  const name = String(feature.properties?.name || '')
24
+  const override = TOWNSHIP_DRILL_CENTER_OVERRIDES[name]
25
+  if (Array.isArray(override) && override.length >= 2) {
26
+    const lng = Number(override[0])
27
+    const lat = Number(override[1])
28
+    if (Number.isFinite(lng) && Number.isFinite(lat)) {
29
+      return [lng, lat]
30
+    }
31
+  }
32
+
33
+  const labelPoint = getTownshipLabelPoints().find((item) => item.name === name)?.lngLat
34
+  if (Array.isArray(labelPoint) && labelPoint.length >= 2) {
35
+    return [...labelPoint]
36
+  }
37
+
38
+  const center = feature.properties?.center
39
+  if (Array.isArray(center) && center.length >= 2) {
40
+    return [Number(center[0]), Number(center[1])]
41
+  }
42
+
43
+  return null
44
+}
45
+
46
+export function getTownshipGeoJson() {
47
+  return townshipGeoJson
48
+}
49
+
50
+export function getTownshipFeatures(geojson = townshipGeoJson) {
51
+  return (geojson?.features || []).filter((feature) => feature?.geometry?.type === 'Polygon')
52
+}
53
+
54
+function getPolygonOuterRing(feature) {
55
+  return feature?.geometry?.coordinates?.[0] || null
56
+}
57
+
58
+function pointInPolygon(point, ring) {
59
+  const x = point[0]
60
+  const y = point[1]
61
+  let inside = false
62
+  for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {
63
+    const xi = ring[i][0]
64
+    const yi = ring[i][1]
65
+    const xj = ring[j][0]
66
+    const yj = ring[j][1]
67
+    const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
68
+    if (intersect) {
69
+      inside = !inside
70
+    }
71
+  }
72
+  return inside
73
+}
74
+
75
+/** 在多边形内部求视觉中心(拉西镇等 concave 面 properties.centroid 可能在面外) */
76
+function computePolygonLabelPoint(ring, precision = 0.001) {
77
+  if (!Array.isArray(ring) || ring.length < 4) {
78
+    return null
79
+  }
80
+
81
+  let minLng = Infinity
82
+  let maxLng = -Infinity
83
+  let minLat = Infinity
84
+  let maxLat = -Infinity
85
+  ring.forEach(([lng, lat]) => {
86
+    minLng = Math.min(minLng, lng)
87
+    maxLng = Math.max(maxLng, lng)
88
+    minLat = Math.min(minLat, lat)
89
+    maxLat = Math.max(maxLat, lat)
90
+  })
91
+
92
+  const width = maxLng - minLng
93
+  const height = maxLat - minLat
94
+  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
95
+    return null
96
+  }
97
+
98
+  const cellSize = Math.min(width, height)
99
+  const half = cellSize / 2
100
+  const cells = []
101
+
102
+  const cellMaxDistance = (lng, lat, inside) => {
103
+    if (!inside) {
104
+      return -1
105
+    }
106
+    return Math.min(lng - minLng, maxLng - lng, lat - minLat, maxLat - lat)
107
+  }
108
+
109
+  for (let lng = minLng; lng < maxLng; lng += cellSize) {
110
+    for (let lat = minLat; lat < maxLat; lat += cellSize) {
111
+      const cx = lng + half
112
+      const cy = lat + half
113
+      const inside = pointInPolygon([cx, cy], ring)
114
+      cells.push({
115
+        x: cx,
116
+        y: cy,
117
+        h: half,
118
+        max: cellMaxDistance(cx, cy, inside) + half * Math.SQRT2
119
+      })
120
+    }
121
+  }
122
+
123
+  cells.sort((a, b) => b.max - a.max)
124
+  let best = cells[0]
125
+  if (!best) {
126
+    return null
127
+  }
128
+
129
+  while (best.h > precision) {
130
+    const nextHalf = best.h / 2
131
+    let nextBest = null
132
+    ;[
133
+      [-nextHalf, -nextHalf],
134
+      [nextHalf, -nextHalf],
135
+      [-nextHalf, nextHalf],
136
+      [nextHalf, nextHalf]
137
+    ].forEach(([dx, dy]) => {
138
+      const cx = best.x + dx
139
+      const cy = best.y + dy
140
+      const inside = pointInPolygon([cx, cy], ring)
141
+      const cell = {
142
+        x: cx,
143
+        y: cy,
144
+        h: nextHalf,
145
+        max: cellMaxDistance(cx, cy, inside) + nextHalf * Math.SQRT2
146
+      }
147
+      if (!nextBest || cell.max > nextBest.max) {
148
+        nextBest = cell
149
+      }
150
+    })
151
+    best = nextBest || best
152
+  }
153
+
154
+  return [best.x, best.y]
155
+}
156
+
157
+/** 从多边形几何计算乡镇标注点(手动微调 > 面内视觉中心 > properties.centroid) */
158
+export function getTownshipLabelPoints(geojson = townshipGeoJson) {
159
+  return getTownshipFeatures(geojson)
160
+    .map((feature) => {
161
+      const props = feature.properties || {}
162
+      const name = String(props.name || '')
163
+      const override = TOWNSHIP_LABEL_OVERRIDES[name]
164
+      let lngLat = null
165
+
166
+      if (Array.isArray(override) && override.length >= 2) {
167
+        const lng = Number(override[0])
168
+        const lat = Number(override[1])
169
+        if (Number.isFinite(lng) && Number.isFinite(lat)) {
170
+          lngLat = [lng, lat]
171
+        }
172
+      }
173
+
174
+      if (!lngLat) {
175
+        const ring = getPolygonOuterRing(feature)
176
+        lngLat = computePolygonLabelPoint(ring)
177
+      }
178
+
179
+      if (!lngLat) {
180
+        const center = props.centroid || props.center
181
+        if (!Array.isArray(center) || center.length < 2) {
182
+          return null
183
+        }
184
+        const lng = Number(center[0])
185
+        const lat = Number(center[1])
186
+        if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
187
+          return null
188
+        }
189
+        lngLat = [lng, lat]
190
+      }
191
+
192
+      return {
193
+        name,
194
+        adcode: props.adcode,
195
+        lngLat
196
+      }
197
+    })
198
+    .filter(Boolean)
199
+}
200
+
201
+export function getTownshipBounds(feature) {
202
+  const ring = getPolygonOuterRing(feature)
203
+  if (!ring?.length) {
204
+    return null
205
+  }
206
+
207
+  let minLng = Infinity
208
+  let maxLng = -Infinity
209
+  let minLat = Infinity
210
+  let maxLat = -Infinity
211
+  ring.forEach(([lng, lat]) => {
212
+    minLng = Math.min(minLng, lng)
213
+    maxLng = Math.max(maxLng, lng)
214
+    minLat = Math.min(minLat, lat)
215
+    maxLat = Math.max(maxLat, lat)
216
+  })
217
+
218
+  if (!Number.isFinite(minLng) || !Number.isFinite(maxLng) || !Number.isFinite(minLat) || !Number.isFinite(maxLat)) {
219
+    return null
220
+  }
221
+
222
+  return [
223
+    [minLng, minLat],
224
+    [maxLng, maxLat]
225
+  ]
226
+}
227
+
228
+/** 根据点击坐标命中乡镇面 */
229
+export function findTownshipFeatureByLngLat(lngLat, geojson = townshipGeoJson) {
230
+  const lng = Number(lngLat?.lng ?? lngLat?.[0])
231
+  const lat = Number(lngLat?.lat ?? lngLat?.[1])
232
+  if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
233
+    return null
234
+  }
235
+
236
+  return (
237
+    getTownshipFeatures(geojson).find((feature) => {
238
+      const ring = getPolygonOuterRing(feature)
239
+      return ring && pointInPolygon([lng, lat], ring)
240
+    }) || null
241
+  )
242
+}
243
+
244
+export function buildTownshipInfo(feature) {
245
+  if (!feature) {
246
+    return null
247
+  }
248
+  const props = feature.properties || {}
249
+  const name = String(props.name || '')
250
+  const labelPoint = getTownshipLabelPoints().find((item) => item.name === name)?.lngLat || null
251
+  const drillCenter = getTownshipDrillCenter(feature)
252
+
253
+  return {
254
+    name,
255
+    adcode: props.adcode,
256
+    center: Array.isArray(props.center) ? [...props.center] : null,
257
+    centroid: Array.isArray(props.centroid) ? [...props.centroid] : null,
258
+    labelPoint: labelPoint ? [...labelPoint] : null,
259
+    drillCenter: drillCenter ? [...drillCenter] : null,
260
+    bounds: getTownshipBounds(feature)
261
+  }
262
+}
263
+
264
+/** 点击乡镇后缩放到指定级别(默认 zoom 19,中心取标注点/中心点) */
265
+export function flyToTownship(map, feature, options = {}) {
266
+  if (!map || !feature) {
267
+    return false
268
+  }
269
+
270
+  const duration = Number(options.duration) || 900
271
+  const zoom = Number(options.zoom ?? options.maxZoom) || 19
272
+  const center = getTownshipDrillCenter(feature)
273
+
274
+  if (Array.isArray(center) && center.length >= 2 && typeof map.easeTo === 'function') {
275
+    map.easeTo({
276
+      center,
277
+      zoom,
278
+      duration,
279
+      essential: true
280
+    })
281
+    return true
282
+  }
283
+
284
+  return false
285
+}
286
+
287
+export function addTownshipBoundaryLayers(map, geojson = townshipGeoJson) {
288
+  if (!map || typeof map.addSource !== 'function') {
289
+    return false
290
+  }
291
+  if (map.getSource(TOWNSHIP_SOURCE_ID)) {
292
+    return true
293
+  }
294
+
295
+  map.addSource(TOWNSHIP_SOURCE_ID, {
296
+    type: 'geojson',
297
+    data: geojson
298
+  })
299
+
300
+  map.addLayer({
301
+    id: TOWNSHIP_FILL_LAYER_ID,
302
+    type: 'fill',
303
+    source: TOWNSHIP_SOURCE_ID,
304
+    paint: {
305
+      'fill-color': 'rgba(0, 0, 0, 0)',
306
+      'fill-outline-color': 'transparent'
307
+    }
308
+  })
309
+
310
+  map.addLayer({
311
+    id: TOWNSHIP_LINE_GLOW_LAYER_ID,
312
+    type: 'line',
313
+    source: TOWNSHIP_SOURCE_ID,
314
+    paint: {
315
+      'line-color': 'rgba(0, 229, 204, 0.5)',
316
+      'line-width': 3.5,
317
+      'line-blur': 2
318
+    }
319
+  })
320
+
321
+  map.addLayer({
322
+    id: TOWNSHIP_LINE_LAYER_ID,
323
+    type: 'line',
324
+    source: TOWNSHIP_SOURCE_ID,
325
+    paint: {
326
+      'line-color': '#b8fff2',
327
+      'line-width': 1.2
328
+    }
329
+  })
330
+
331
+  return true
332
+}
333
+
334
+export function removeTownshipBoundaryLayers(map) {
335
+  if (!map) {
336
+    return
337
+  }
338
+  ;[TOWNSHIP_LINE_LAYER_ID, TOWNSHIP_LINE_GLOW_LAYER_ID, TOWNSHIP_FILL_LAYER_ID].forEach((id) => {
339
+    if (typeof map.getLayer === 'function' && map.getLayer(id)) {
340
+      map.removeLayer(id)
341
+    }
342
+  })
343
+  if (typeof map.getSource === 'function' && map.getSource(TOWNSHIP_SOURCE_ID)) {
344
+    map.removeSource(TOWNSHIP_SOURCE_ID)
345
+  }
346
+}
347
+
348
+/** 点击乡镇面触发下钻 */
349
+export function bindTownshipBoundaryClick(map, onSelect) {
350
+  if (!map || typeof map.on !== 'function' || typeof onSelect !== 'function') {
351
+    return () => {}
352
+  }
353
+
354
+  const resolveFeature = (eventFeature) => {
355
+    const adcode = eventFeature?.properties?.adcode
356
+    if (adcode == null) {
357
+      return eventFeature
358
+    }
359
+    return getTownshipFeatures().find((feature) => feature.properties?.adcode === adcode) || eventFeature
360
+  }
361
+
362
+  const onClick = (e) => {
363
+    if (!e.features?.length) {
364
+      return
365
+    }
366
+    const feature = resolveFeature(e.features[0])
367
+    onSelect(feature, e)
368
+  }
369
+
370
+  const onEnter = () => {
371
+    if (typeof map.getCanvas === 'function') {
372
+      map.getCanvas().style.cursor = 'pointer'
373
+    }
374
+  }
375
+
376
+  const onLeave = () => {
377
+    if (typeof map.getCanvas === 'function') {
378
+      map.getCanvas().style.cursor = ''
379
+    }
380
+  }
381
+
382
+  map.on('click', TOWNSHIP_FILL_LAYER_ID, onClick)
383
+  map.on('mouseenter', TOWNSHIP_FILL_LAYER_ID, onEnter)
384
+  map.on('mouseleave', TOWNSHIP_FILL_LAYER_ID, onLeave)
385
+
386
+  return () => {
387
+    map.off('click', TOWNSHIP_FILL_LAYER_ID, onClick)
388
+    map.off('mouseenter', TOWNSHIP_FILL_LAYER_ID, onEnter)
389
+    map.off('mouseleave', TOWNSHIP_FILL_LAYER_ID, onLeave)
390
+    onLeave()
391
+  }
392
+}

+ 11 - 5
ruoyi-screen/src/views/home/index.vue

@@ -32,9 +32,11 @@
32 32
           </div>
33 33
           <div class="content_3">
34 34
             <div class="content_num">
35
-              <ScreenScrollNumber :value="overview?.pastureCount" /> 个
35
+              <!-- <ScreenScrollNumber :value="overview?.pastureCount" /> 个 -->
36
+              <ScreenScrollNumber :value="FARMER_COUNT" /> 户
36 37
             </div>
37
-            <div class="content_title"><div>牧场数量</div></div>
38
+            <!-- <div class="content_title"><div>牧场数量</div></div> -->
39
+            <div class="content_title"><div>农牧户</div></div>
38 40
           </div>
39 41
           <div class="content_4">
40 42
             <div class="content_num">
@@ -122,19 +124,22 @@
122 124
             <div class="yu">
123 125
               <div class="yu_title">兽医人员</div>
124 126
               <div class="yu_num">
125
-                <strong>{{ display(appointment?.annualVetCount) }}</strong> 人
127
+                <!-- <strong>{{ display(appointment?.annualVetCount) }}</strong> 人 -->
128
+                <strong>150</strong> 人
126 129
               </div>
127 130
             </div>
128 131
             <div class="yu">
129 132
               <div class="yu_title">专家人员</div>
130 133
               <div class="yu_num">
131
-                <strong>{{ display(appointment?.annualExpertCount) }}</strong> 人
134
+                <!-- <strong>{{ display(appointment?.annualExpertCount) }}</strong> 人 -->
135
+                <strong>1</strong> 人
132 136
               </div>
133 137
             </div>
134 138
             <div class="yu">
135 139
               <div class="yu_title">诊疗机构</div>
136 140
               <div class="yu_num">
137
-                <strong>{{ display(appointment?.annualOrgCount) }}</strong> 人
141
+                <!-- <strong>{{ display(appointment?.annualOrgCount) }}</strong> 个 -->
142
+                <strong>12</strong> 个
138 143
               </div>
139 144
             </div>
140 145
           </div>
@@ -236,6 +241,7 @@ const STANDARD_FETCH_PAGE_SIZE = 50
236 241
 /** 产值总览:牦牛存栏/出栏固定展示值(不走接口) */
237 242
 const YAK_INVENTORY_TOTAL = 238067
238 243
 const YAK_ANNUAL_OUTBOUND = 37449
244
+const FARMER_COUNT = 8988
239 245
 
240 246
 const loading = ref(false)
241 247
 const loadError = ref('')