From f4f411f3b90d6f557248ff5b2d48975f6a707797 Mon Sep 17 00:00:00 2001 From: hym Date: Sat, 4 Apr 2026 16:42:48 +0800 Subject: [PATCH 01/17] =?UTF-8?q?=E5=8D=95=E4=BD=8D=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E6=94=BE=E5=9C=A8=E5=9C=B0=E5=9B=BE=E4=B8=8B=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/statistics/UuvUnitStatistics.vue | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/views/statistics/UuvUnitStatistics.vue b/src/views/statistics/UuvUnitStatistics.vue index 38df067..96b2987 100644 --- a/src/views/statistics/UuvUnitStatistics.vue +++ b/src/views/statistics/UuvUnitStatistics.vue @@ -9,18 +9,6 @@ -
-
-
- - -
-
-
@@ -37,6 +25,18 @@
-->
+
+
+
+ + +
+
+
@@ -560,7 +560,7 @@ export default { } .map-module { - padding: 50px 50px; + padding: 10px 50px; display: flex; justify-content: space-between; gap: 50px; From 8403c26edf326851e4c9b50854a474dbdd35d3cb Mon Sep 17 00:00:00 2001 From: hym Date: Sat, 4 Apr 2026 17:03:09 +0800 Subject: [PATCH 02/17] =?UTF-8?q?=E5=8D=95=E4=BD=8D=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=8E=BB=E6=8E=89=E8=88=AA=E6=AC=A1=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/statistics/UuvUnitStatistics.vue | 54 +--------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/src/views/statistics/UuvUnitStatistics.vue b/src/views/statistics/UuvUnitStatistics.vue index 96b2987..fd5858f 100644 --- a/src/views/statistics/UuvUnitStatistics.vue +++ b/src/views/statistics/UuvUnitStatistics.vue @@ -9,21 +9,12 @@
- +
参航单位
-
@@ -149,7 +140,6 @@ export default { this.loadUnitTotalFromApi() this.getHovUnit() this.getShipUnit() - this.gitShipVoyage() }, mounted() { initMapbox('workUnitMap_uuv', { @@ -159,13 +149,6 @@ export default { minZoom: 2, maxZoom: 29 }) - // initMapbox('voyageMap_uuv', { - // tileKey: 'GEBCO_basemap_NCEI', - // zoom: 3, - // center: [113, 15], - // minZoom: 2, - // maxZoom: 29 - // }) }, beforeDestroy() { const unitMap = Vue.config.maps && Vue.config.maps['workUnitMap_uuv'] @@ -173,11 +156,6 @@ export default { unitMap.remove() delete Vue.config.maps['workUnitMap_uuv'] } - const voyageMap = Vue.config.maps && Vue.config.maps['voyageMap_uuv'] - if (voyageMap) { - voyageMap.remove() - delete Vue.config.maps['voyageMap_uuv'] - } }, methods: { /** @@ -356,36 +334,6 @@ export default { loadJsonPointFeature('workUnit', 'blue', geojson, 'unit_name', 'workUnitMap_uuv') }) }, - - /** - * 科考船航次轨迹(与 ships 页地图同源接口),通过 Mapbox 矢量图层渲染。 - * @param {Object} [params] 查询参数 - * @returns {void} - */ - gitShipVoyage(params = {}) { - shipVoyage(params).then(res => { - let i = 0 - const colors = ColorGenerator.generateDivergingColors((res.array || []).length) - ; (res.array || []).forEach(item => { - loadVectorLayer(item.voyage_name, ['line', 'symbol'], [colors[i], '#FFFFFF'], 'visible', 'voyageMap_uuv', 'dsds') - i++ - }) - }) - }, - - /** - * 选择航次地图年份。 - * @param {*} val 年份值 - * @returns {void} - */ - selectVoyageYear(val) { - if (val) { - const params = getYearRange(val) - this.gitShipVoyage({ ...params }) - } else { - this.gitShipVoyage({}) - } - } } } From f3ecdf8c0cbf60f46f7d32d2bd8408c55f71ea12 Mon Sep 17 00:00:00 2001 From: hym Date: Sat, 4 Apr 2026 23:09:57 +0800 Subject: [PATCH 03/17] =?UTF-8?q?=E5=8D=95=E4=BD=8D=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=A2=9E=E5=8A=A0=E5=9C=B0=E5=9B=BE=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E4=BF=A1=E6=81=AF=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vector-layer-utils.js | 44 +++++++++++++++++++++- src/views/statistics/UuvUnitStatistics.vue | 5 ++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/utils/vector-layer-utils.js b/src/utils/vector-layer-utils.js index b149f65..9c801b5 100644 --- a/src/utils/vector-layer-utils.js +++ b/src/utils/vector-layer-utils.js @@ -145,10 +145,15 @@ export async function loadVectorFeature(layerId, feature, color, visibility, map export function setHighlight(layerId, sourceId, mapId = "homeMap") { let hoveredLineId = null; let clickedLineId = null; - Vue.config.maps[mapId].on('click', layerId, (e) => { + Vue.config.maps[mapId].on('click', layerId, async (e) => { if (!e.features || e.features.length <= 0) return; + if (mapId == "workUnitMap_uuv") { + showUnitReport(mapId, e.features[0].properties.unit_name, e.features[0].geometry.coordinates); + return; + } + // // 查询点击点周围的所有要素(使用边界框) // const bbox = [ // [e.point.x - 10, e.point.y - 10], // 左上 @@ -203,6 +208,9 @@ export function setHighlight(layerId, sourceId, mapId = "homeMap") { // When the user moves their mouse over the state-fill layer, we'll update the feature state for the feature under the mouse. Vue.config.maps[mapId].on('mousemove', layerId, (e) => { + if (mapId == "workUnitMap_uuv") + return; + if (e.features.length <= 0) return; sourceLayer = e.features[0].sourceLayer; @@ -231,6 +239,40 @@ export function setHighlight(layerId, sourceId, mapId = "homeMap") { }); } +export async function showUnitReport(mapId, unit, coordinates) { + const result = await (await fetch(`${Vue.config.baseUrl}/ds/report/unit/total.htm?unit=${unit}`)).json(); + const unitReports = result.array || []; + if (!unitReports.length) { + alert("未查询到相关单位的统计信息"); + return; + } + const description = ` + + + + + + + + + + + + + + + + + + + + + + `; + + showPopupDetails(mapId, coordinates, description, "350px"); +} + export function changeCursor(layerId, mapId = "homeMap", cursor = "pointer") { Vue.config.maps[mapId].on("mouseenter", layerId, () => { Vue.config.maps[mapId].getCanvas().style.cursor = cursor; diff --git a/src/views/statistics/UuvUnitStatistics.vue b/src/views/statistics/UuvUnitStatistics.vue index fd5858f..9e7d7df 100644 --- a/src/views/statistics/UuvUnitStatistics.vue +++ b/src/views/statistics/UuvUnitStatistics.vue @@ -60,7 +60,7 @@ import BarChart from './components/BarChart.vue' import { unitTotal, hovUnit, shipUnit, shipVoyage } from '../../api/statistics' import { getYearRange } from '../../utils/index' import { initMapbox, handleWheel } from '@/utils/mapbox-utils' -import { loadVectorLayer, loadJsonPointFeature } from '@/utils/vector-layer-utils' +import { loadVectorLayer, loadJsonPointFeature, showPopupDetails,showUnitReport } from '@/utils/vector-layer-utils' import { ColorGenerator } from '@/utils/color-generator' /** @@ -184,6 +184,9 @@ export default { const nameSet = new Set(sel.map(u => u && u.unit_name).filter(Boolean)) const filtered = raw.filter(row => nameSet.has(row.unit_name)) this.applyUnitTotalRows(filtered) + + // 地图显示最后一个选中单位的相关信息 + showUnitReport('workUnitMap_uuv', this.selectedUnits.at(-1).unit_name, [this.selectedUnits.at(-1).unit_lon, this.selectedUnits.at(-1).unit_lat]) }, /** From e783375fbfd9b10b234b12146a08ed0bc7f58aae Mon Sep 17 00:00:00 2001 From: hym Date: Sat, 4 Apr 2026 23:10:36 +0800 Subject: [PATCH 04/17] =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E5=A2=9E=E5=8A=A0=E9=80=8F=E6=98=8E=E5=BA=A6?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/style/style.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/style/style.scss b/src/style/style.scss index a4eaa71..b8dbc0c 100644 --- a/src/style/style.scss +++ b/src/style/style.scss @@ -1783,6 +1783,12 @@ header{ display: none !important; } +.mapboxgl-popup-content { + background-color: rgba(255, 255, 255, 0.9) !important; + padding-bottom: 5px !important; + border-radius: 5px !important; +} + .popup-table { border-spacing: 0; font-family: 'Microsoft YaHei'; From 399ad08a129b89eaa78d450202accff6ce4ee2f2 Mon Sep 17 00:00:00 2001 From: hym Date: Sun, 5 Apr 2026 09:12:20 +0800 Subject: [PATCH 05/17] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8D=95=E4=BD=8D?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E9=A1=B5=E9=9D=A2=E5=9C=B0=E5=9B=BE=E7=82=B9?= =?UTF-8?q?=E9=80=89=E4=BA=A4=E4=BA=92=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vector-layer-utils.js | 29 ++++++--------- src/views/statistics/UuvUnitStatistics.vue | 43 +++++++++++++--------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/utils/vector-layer-utils.js b/src/utils/vector-layer-utils.js index 9c801b5..c4f97de 100644 --- a/src/utils/vector-layer-utils.js +++ b/src/utils/vector-layer-utils.js @@ -150,7 +150,8 @@ export function setHighlight(layerId, sourceId, mapId = "homeMap") { return; if (mapId == "workUnitMap_uuv") { - showUnitReport(mapId, e.features[0].properties.unit_name, e.features[0].geometry.coordinates); + // 相关逻辑位于单位统计页面 + // showUnitReport(mapId, e.features[0].properties.unit_name, e.features[0].geometry.coordinates); return; } @@ -166,9 +167,6 @@ export function setHighlight(layerId, sourceId, mapId = "homeMap") { // // const featuresByLayer = groupFeaturesByLayer(allFeatures); // // console.log('点击位置附近的所有要素:', featuresByLayer); - // console.log(e.features.length, e.features); - // console.log("1 e: ", e); - let feature = e.features[0]; if (e.features.length > 1 && e.options) feature = e.features.filter(f => f.properties.sample_name == e.options.sample_name)[0]; @@ -193,9 +191,6 @@ export function setHighlight(layerId, sourceId, mapId = "homeMap") { clickedLineIds = clickedLineIds.filter((item) => item !== clickedLineId); } else { Vue.config.maps[mapId].setFeatureState({ source: sourceId, sourceLayer: sourceLayer, id: clickedLineId }, { click: true }); - // 显示信息框 - // if (sourceId === 'GMSL Cable Data') - // document.getElementById("cable-overlay")!.style.display = 'block'; // 单击时加入选择列表(用于单击 ESC 键时取消选择状态) clickedLineIds.push(clickedLineId); // document.removeEventListener("keydown", removeHighlight, true); @@ -246,27 +241,28 @@ export async function showUnitReport(mapId, unit, coordinates) { alert("未查询到相关单位的统计信息"); return; } + const description = ` - + - + - - + + - - + + - - + + `; @@ -341,9 +337,6 @@ export function showSampleStationInfo(layerId, mapId = "homeMap") { // if (typeof e.preventDefault === "function") // e.preventDefault(); - // console.log(e.features.length, e.features); - // console.log("2 e: ", e); - let feature = e.features[0]; if (e.features.length > 1) feature = e.features.filter(f => f.properties.sample_name == e.options.sample_name)[0]; diff --git a/src/views/statistics/UuvUnitStatistics.vue b/src/views/statistics/UuvUnitStatistics.vue index 9e7d7df..362d9d9 100644 --- a/src/views/statistics/UuvUnitStatistics.vue +++ b/src/views/statistics/UuvUnitStatistics.vue @@ -60,7 +60,7 @@ import BarChart from './components/BarChart.vue' import { unitTotal, hovUnit, shipUnit, shipVoyage } from '../../api/statistics' import { getYearRange } from '../../utils/index' import { initMapbox, handleWheel } from '@/utils/mapbox-utils' -import { loadVectorLayer, loadJsonPointFeature, showPopupDetails,showUnitReport } from '@/utils/vector-layer-utils' +import { loadVectorLayer, loadJsonPointFeature, showPopupDetails, showUnitReport } from '@/utils/vector-layer-utils' import { ColorGenerator } from '@/utils/color-generator' /** @@ -318,23 +318,32 @@ export default { const geojson = { type: 'FeatureCollection', features: [] - } - ; (res.array || []).forEach(element => { - geojson.features.push({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [element.unit_lon, element.unit_lat] - }, - properties: { - unit_name: element.unit_name, - unit_type: element.unit_type, - create_time: element.create_time, - tsy_id: element.tsy_id - } - }) + }; + (res.array || []).forEach(element => { + geojson.features.push({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [element.unit_lon, element.unit_lat] + }, + properties: { + unit_name: element.unit_name, + unit_type: element.unit_type, + create_time: element.create_time, + tsy_id: element.tsy_id + } }) - loadJsonPointFeature('workUnit', 'blue', geojson, 'unit_name', 'workUnitMap_uuv') + }); + loadJsonPointFeature('workUnit', 'blue', geojson, 'unit_name', 'workUnitMap_uuv'); + + Vue.config.maps['workUnitMap_uuv'].on('click', 'workUnit', async (e) => { + if (!e.features || e.features.length <= 0) + return; + // 地图点选弹窗显示单位信息 + showUnitReport('workUnitMap_uuv', e.features[0].properties.unit_name, e.features[0].geometry.coordinates); + // 同步更新单位统计图 + this.applyUnitTotalRows((this.unitTotalRawList || []).filter(row => row.unit_name === e.features[0].properties.unit_name)); + }); }) }, } From 4fc5d6edeac0bfd40dea241c50d4e3826f8cac42 Mon Sep 17 00:00:00 2001 From: hym Date: Sun, 5 Apr 2026 16:54:15 +0800 Subject: [PATCH 06/17] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8F=82=E8=88=AA?= =?UTF-8?q?=E5=8D=95=E4=BD=8D=E5=9C=B0=E5=9B=BE=E4=BF=A1=E6=81=AF=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E4=BA=A4=E4=BA=92=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vector-layer-utils.js | 57 +++++++++++----------- src/views/statistics/AuvStatistics.vue | 12 ++++- src/views/statistics/HovStatistics.vue | 13 +++-- src/views/statistics/RovStatistics.vue | 12 ++++- src/views/statistics/ShipStatistics.vue | 10 +++- src/views/statistics/UuvUnitStatistics.vue | 19 ++++++-- 6 files changed, 83 insertions(+), 40 deletions(-) diff --git a/src/utils/vector-layer-utils.js b/src/utils/vector-layer-utils.js index c4f97de..65efad8 100644 --- a/src/utils/vector-layer-utils.js +++ b/src/utils/vector-layer-utils.js @@ -146,15 +146,11 @@ export function setHighlight(layerId, sourceId, mapId = "homeMap") { let hoveredLineId = null; let clickedLineId = null; Vue.config.maps[mapId].on('click', layerId, async (e) => { - if (!e.features || e.features.length <= 0) + sourceLayer = e.features[0].sourceLayer; + // 排除没有 sourceLayer 的情况(如点击事件中 features 可能来自多个图层,某些图层缺乏 sourceLayer 属性会导致后续 setFeatureState 报错) + if (!sourceLayer) return; - if (mapId == "workUnitMap_uuv") { - // 相关逻辑位于单位统计页面 - // showUnitReport(mapId, e.features[0].properties.unit_name, e.features[0].geometry.coordinates); - return; - } - // // 查询点击点周围的所有要素(使用边界框) // const bbox = [ // [e.point.x - 10, e.point.y - 10], // 左上 @@ -194,21 +190,18 @@ export function setHighlight(layerId, sourceId, mapId = "homeMap") { // 单击时加入选择列表(用于单击 ESC 键时取消选择状态) clickedLineIds.push(clickedLineId); // document.removeEventListener("keydown", removeHighlight, true); - document.addEventListener("keydown", (e) => { - removeHighlight(e, sourceId); - }, { once: true }); // once:一个布尔值,表示 listener 在添加之后最多只调用一次。如果为 true,listener 会在其被调用之后自动移除。 - // }, true); + // document.addEventListener("keydown", (e) => { + // removeHighlight(e, sourceId); + // }, { once: true }); // once:一个布尔值,表示 listener 在添加之后最多只调用一次。如果为 true,listener 会在其被调用之后自动移除。 } }); // When the user moves their mouse over the state-fill layer, we'll update the feature state for the feature under the mouse. Vue.config.maps[mapId].on('mousemove', layerId, (e) => { - if (mapId == "workUnitMap_uuv") - return; - - if (e.features.length <= 0) - return; sourceLayer = e.features[0].sourceLayer; + // 排除没有 sourceLayer 的情况(如点击事件中 features 可能来自多个图层,某些图层缺乏 sourceLayer 属性会导致后续 setFeatureState 报错) + if (!sourceLayer) + return; if (hoveredLineId) { Vue.config.maps[mapId].setFeatureState( { source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId }, @@ -234,7 +227,7 @@ export function setHighlight(layerId, sourceId, mapId = "homeMap") { }); } -export async function showUnitReport(mapId, unit, coordinates) { +export async function showUnitReport(mapId, unit, coordinate, callbackOnClose = null) { const result = await (await fetch(`${Vue.config.baseUrl}/ds/report/unit/total.htm?unit=${unit}`)).json(); const unitReports = result.array || []; if (!unitReports.length) { @@ -266,7 +259,7 @@ export async function showUnitReport(mapId, unit, coordinates) { `; - showPopupDetails(mapId, coordinates, description, "350px"); + showPopupDetails({ mapId, coordinate, description, maxWidth: "350px", callbackOnClose: callbackOnClose }); } export function changeCursor(layerId, mapId = "homeMap", cursor = "pointer") { @@ -324,7 +317,7 @@ export function showSampleLineInfo(layerId, mapId = "homeMap") { `; - showPopupDetails(mapId, e.lngLat, description, "450px"); + showPopupDetails({ mapId, coordinate: e.lngLat, description, maxWidth: "450px" }); }); } @@ -369,7 +362,7 @@ export function showSampleStationInfo(layerId, mapId = "homeMap") { `; - showPopupDetails(mapId, feature.geometry.coordinates, description, "450px"); + showPopupDetails({ mapId, coordinate: feature.geometry.coordinates, description, maxWidth: "450px" }); }); } @@ -413,12 +406,12 @@ export function showTrackInfo(layerId, mapId = "homeMap") { `; - showPopupDetails(mapId, coordinate, description, "420px"); + showPopupDetails({ coordinate, mapId, description, maxWidth: "420px" }); }) }); } -export function showPopupDetails(mapId, coordinate, description, maxWidth = "300px", closeButton = true, closeOnClick = true, options = {}) { +export function showPopupDetails({ mapId, coordinate, description, maxWidth = "300px", closeButton = true, closeOnClick = true, options = {}, callbackOnClose }) { const map = Vue.config.maps[mapId]; if (!map) return; @@ -446,7 +439,13 @@ export function showPopupDetails(mapId, coordinate, description, maxWidth = "300 .setOffset(10) .setMaxWidth(maxWidth) .addTo(map); - sharedPopup.on('close', () => { sharedPopup = null; }); + sharedPopup.on('close', () => { + sharedPopup = null; + // 关闭弹窗时执行回调(如有) + if (callbackOnClose && typeof callbackOnClose === "function") { + callbackOnClose(); + } + }); } } else { sharedPopup = new window.mapboxgl.Popup(popupOptions) @@ -455,13 +454,17 @@ export function showPopupDetails(mapId, coordinate, description, maxWidth = "300 .setOffset(10) .setMaxWidth(maxWidth) .addTo(map); - sharedPopup.on('close', () => { sharedPopup = null; }); + sharedPopup.on('close', () => { + sharedPopup = null; + // 关闭弹窗时执行回调(如有) + if (callbackOnClose && typeof callbackOnClose === "function") { + callbackOnClose(); + } + }); } } export async function loadJsonLineFeature(layerId, color, geojson, mapId) { - // console.log("layerId: ",layerId); - let source = Vue.config.maps[mapId].getSource(layerId); if (!source) { Vue.config.maps[mapId].addSource(layerId, { @@ -551,13 +554,11 @@ export async function loadJsonPointFeature(layerId, color, geojson, text_field, export async function highlightFeaturesByProperty(mapId, sourceId, layerId, propertyName = "sample_name", targetValue = "TARGET_VALUE") { // 清空所有高亮状态(可选) Vue.config.maps[mapId].querySourceFeatures(sourceId).forEach(f => { - console.log("AAAAA: ", f); Vue.config.maps[mapId].setFeatureState({ source: sourceId, id: f.id }, { highlight: false }); }); // 标记匹配要素 Vue.config.maps[mapId].querySourceFeatures(sourceId).forEach(f => { - console.log("BBBBB: ", f); if (f.properties && f.properties[propertyName] === targetValue) { Vue.config.maps[mapId].setFeatureState({ source: sourceId, id: f.id }, { highlight: true }); } diff --git a/src/views/statistics/AuvStatistics.vue b/src/views/statistics/AuvStatistics.vue index 6b31a28..9889f2a 100644 --- a/src/views/statistics/AuvStatistics.vue +++ b/src/views/statistics/AuvStatistics.vue @@ -64,6 +64,7 @@ From 378d1b877854249f2b553b1310f176dd6e282f4b Mon Sep 17 00:00:00 2001 From: hym Date: Sun, 5 Apr 2026 16:54:44 +0800 Subject: [PATCH 07/17] =?UTF-8?q?=E6=B8=85=E7=90=86=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/dataSearch.vue | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/views/dataSearch.vue b/src/views/dataSearch.vue index e29d5e4..d44c572 100644 --- a/src/views/dataSearch.vue +++ b/src/views/dataSearch.vue @@ -231,13 +231,10 @@ import { import { getParamsList, getVoyageLine } from '../api/home' import { regInfo } from '@/api/dataSearch' import Cookies from 'js-cookie' -import { - setImageryViewModels -} from '@/utils/common' import Vue from 'vue' -import { loadVectorLayer, loadJsonLineFeature, loadJsonPointFeature, highlightFeaturesByProperty } from '@/utils/vector-layer-utils' +import { loadVectorLayer } from '@/utils/vector-layer-utils' import { ColorGenerator } from '@/utils/color-generator'; -import { initMapbox, handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils"; +import { initMapbox, triggerLayerClick } from "@/utils/mapbox-utils"; export default { name: 'Shuju', From 621b368d3894328b55b95e638f6fd0e036bea7c6 Mon Sep 17 00:00:00 2001 From: hym Date: Sun, 5 Apr 2026 20:15:42 +0800 Subject: [PATCH 08/17] =?UTF-8?q?=E5=8F=82=E8=88=AA=E5=8D=95=E4=BD=8D?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=88=B9=E4=BE=A7=E6=95=B0=E6=8D=AE=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9C=B0=E5=9B=BE=E5=BC=B9=E7=AA=97=E4=B8=8E?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E6=95=B0=E6=8D=AE=E4=BA=A4=E4=BA=92=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vector-layer-utils.js | 4 +- src/views/statistics/UuvUnitStatistics.vue | 49 +++++++++++++--------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/utils/vector-layer-utils.js b/src/utils/vector-layer-utils.js index 65efad8..dea83a8 100644 --- a/src/utils/vector-layer-utils.js +++ b/src/utils/vector-layer-utils.js @@ -236,7 +236,7 @@ export async function showUnitReport(mapId, unit, coordinate, callbackOnClose = } const description = ` - + @@ -259,7 +259,7 @@ export async function showUnitReport(mapId, unit, coordinate, callbackOnClose = `; - showPopupDetails({ mapId, coordinate, description, maxWidth: "350px", callbackOnClose: callbackOnClose }); + showPopupDetails({ mapId, coordinate, description, maxWidth: "300px", callbackOnClose: callbackOnClose }); } export function changeCursor(layerId, mapId = "homeMap", cursor = "pointer") { diff --git a/src/views/statistics/UuvUnitStatistics.vue b/src/views/statistics/UuvUnitStatistics.vue index 1a4c12b..3c8eba6 100644 --- a/src/views/statistics/UuvUnitStatistics.vue +++ b/src/views/statistics/UuvUnitStatistics.vue @@ -85,6 +85,8 @@ export default { selectedUnits: [], /** unitTotal 全量缓存,便于按 Banner 选择做前端过滤 */ unitTotalRawList: [], + // 全量单位列表,用于地图点选时绑定 Banner + totalUnits: [], numbers: [ { label: '参航单位数量', @@ -143,8 +145,8 @@ export default { mounted() { initMapbox('workUnitMap_uuv', { tileKey: 'gaode', - zoom: 3, - center: [110, 33], + zoom: 2.8, + center: [120, 31], minZoom: 2, maxZoom: 29 }) @@ -184,6 +186,7 @@ export default { const filtered = raw.filter(row => nameSet.has(row.unit_name)) this.applyUnitTotalRows(filtered) + console.log('applyFilteredUnitTotal', this.selectedUnits); // 地图显示最后一个选中单位的相关信息 showUnitReport('workUnitMap_uuv', this.selectedUnits.at(-1).unit_name, [this.selectedUnits.at(-1).unit_lon, this.selectedUnits.at(-1).unit_lat]) }, @@ -299,12 +302,13 @@ export default { */ getHovUnit() { hovUnit().then(res => { - const map = new Map() - ; (res.array || []).forEach(unit => { - const k = unit.unit_id || unit.unitId || unit.unit_name - if (!map.has(k)) map.set(k, unit) - }) - this.unitOptions = Array.from(map.values()) + // 参航单位不一定下潜 + // const map = new Map(); + // (res.array || []).forEach(unit => { + // const k = unit.unit_id || unit.unitId || unit.unit_name + // if (!map.has(k)) map.set(k, unit) + // }) + // this.unitOptions = Array.from(map.values()); }) }, @@ -314,11 +318,19 @@ export default { */ getShipUnit() { shipUnit().then(res => { + this.totalUnits = res.array || []; + const map = new Map(); + this.totalUnits.forEach(unit => { + const k = unit.unit_id || unit.unitId || unit.unit_name + if (!map.has(k)) map.set(k, unit) + }) + this.unitOptions = Array.from(map.values()) + const geojson = { type: 'FeatureCollection', features: [] }; - (res.array || []).forEach(element => { + this.totalUnits.forEach(element => { geojson.features.push({ type: 'Feature', geometry: { @@ -338,24 +350,21 @@ export default { Vue.config.maps['workUnitMap_uuv'].on('click', 'workUnit', async (e) => { if (!e.features || e.features.length <= 0) return; + + // 绑定地图单击的单位与 Banner 选项,保持地图点选与 Banner 选项同步 + this.selectedUnits = this.totalUnits.filter(u => u.unit_name === e.features[0].properties.unit_name); // 地图点选弹窗显示单位信息 - showUnitReport('workUnitMap_uuv', e.features[0].properties.unit_name, e.features[0].geometry.coordinates,this.resetFilter); + showUnitReport('workUnitMap_uuv', e.features[0].properties.unit_name, e.features[0].geometry.coordinates, this.resetFilter); // 同步更新单位统计图 this.applyUnitTotalRows((this.unitTotalRawList || []).filter(row => row.unit_name === e.features[0].properties.unit_name)); }); }) }, - resetFilter(){ - const raw = this.unitTotalRawList || [] - const sel = this.selectedUnits || [] - if (!sel.length) { - this.applyUnitTotalRows(raw) - return - } - const nameSet = new Set(sel.map(u => u && u.unit_name).filter(Boolean)) - const filtered = raw.filter(row => nameSet.has(row.unit_name)) - this.applyUnitTotalRows(filtered) + resetFilter() { + // 关闭地图弹窗后恢复全部单位统计显示 + this.selectedUnits = []; + this.applyUnitTotalRows(this.unitTotalRawList || []); } } } From 9085ad60aa331acfe80150f165fa9edb88019302 Mon Sep 17 00:00:00 2001 From: hym Date: Sun, 5 Apr 2026 22:36:41 +0800 Subject: [PATCH 09/17] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9B=BE=E5=B1=82?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=80=BB=E8=BE=91=EF=BC=8C=E5=9B=BE=E5=B1=82?= =?UTF-8?q?=E5=B7=B2=E5=AD=98=E5=9C=A8=E5=88=99=E8=AE=BE=E7=BD=AE=E4=B8=BA?= =?UTF-8?q?=E5=8F=AF=E8=A7=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vector-layer-utils.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/vector-layer-utils.js b/src/utils/vector-layer-utils.js index dea83a8..acebfe6 100644 --- a/src/utils/vector-layer-utils.js +++ b/src/utils/vector-layer-utils.js @@ -127,6 +127,10 @@ export async function loadVectorFeature(layerId, feature, color, visibility, map // 'slot': 'foreground' }); } + else { + // 图层已存在则设置为可见 + Vue.config.maps[mapId].setLayoutProperty(`${sourceId}-${feature}`, "visibility", "visible"); + } // 点击可交互的图层,显示相关信息 await setHighlight(`${sourceId}-${feature}`, sourceId, mapId); From 21949e6057baab4cfc0a711c592e9099a1a83e66 Mon Sep 17 00:00:00 2001 From: hym Date: Sun, 5 Apr 2026 22:39:32 +0800 Subject: [PATCH 10/17] =?UTF-8?q?=E8=88=AA=E6=AC=A1=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E5=A2=9E=E5=8A=A0=E6=8C=89=E5=B9=B4=E4=BB=BD?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E6=98=BE=E7=A4=BA=E7=9B=B8=E5=BA=94=E8=BD=A8?= =?UTF-8?q?=E8=BF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/statistics/ShipStatistics.vue | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/views/statistics/ShipStatistics.vue b/src/views/statistics/ShipStatistics.vue index 1f3e45f..d2e3b55 100644 --- a/src/views/statistics/ShipStatistics.vue +++ b/src/views/statistics/ShipStatistics.vue @@ -241,11 +241,22 @@ export default { // 航次统计 gitShipVoyage(params = {}) { shipVoyage(params).then(res => { + if (params.start || params.end) { + // 按年份筛选数据,先隐藏所有图层,再根据筛选结果显示对应图层 + const layers = Vue.config.maps['voyageMap_ship'].getStyle().layers; + layers.forEach(layer => { + // 跳过底图 + if (layer.id !== '影像地图' && layer.type !== 'raster') { + Vue.config.maps['voyageMap_ship'].setLayoutProperty(layer.id, 'visibility', 'none'); + } + }); + } + let i = 0 const colors = ColorGenerator.generateDivergingColors(res.array.length) // 添加轨迹图层 res.array.forEach(item => { - // 加载轨迹图层 + // 加载轨迹图层(已存在则设置为可见) loadVectorLayer(item.voyage_name, ['line', 'symbol'], [colors[i], '#FFFFFF'], 'visible', 'voyageMap_ship', 'dsds') i++ }) From 8ee0db5f3aba8ac56d91ec1817c7f4df6828fb7c Mon Sep 17 00:00:00 2001 From: hym Date: Mon, 6 Apr 2026 10:58:58 +0800 Subject: [PATCH 11/17] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9C=B0=E5=9B=BE=20Po?= =?UTF-8?q?pup=20=E5=AF=B9=E8=B1=A1=E5=A4=8D=E7=94=A8=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/vector-layer-utils.js | 70 +++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/src/utils/vector-layer-utils.js b/src/utils/vector-layer-utils.js index acebfe6..91783b6 100644 --- a/src/utils/vector-layer-utils.js +++ b/src/utils/vector-layer-utils.js @@ -3,9 +3,19 @@ import { regInfo } from '@/api/dataSearch' let sourceLayer = undefined; let clickedLineIds = []; -// 单例 Popup,用于确保地图上只有一个弹框实例 -let sharedPopup = null; +// 存储可复用的每个地图对象的唯一 Popup 实例(key 为 mapId),以避免频繁创建/销毁 Popup 导致的性能问题和潜在内存泄漏 +let sharedPopup = {}; +export function setMapLayoutProperty(mapId, visibility = "none") { + // 设置所有图层的显示/隐藏状态(根据图层类型或名称过滤底图图层,避免误操作) + const layers = Vue.config.maps[mapId].getStyle().layers; + layers.forEach(layer => { + // 跳过底图 + if (layer.id !== '影像地图' && layer.type !== 'raster') { + Vue.config.maps[mapId].setLayoutProperty(layer.id, 'visibility', visibility); + } + }); +} export function loadVectorLayer(layerId, features = ["line", "symbol"], colors = ["#FEFEFE", "#FF0000"], visibility = "visible", mapId = "homeMap", workspace = 'dsds', textField = "name") { // 添加数据源 const sourceId = `vector-${layerId}`; @@ -140,6 +150,10 @@ export async function loadVectorFeature(layerId, feature, color, visibility, map await showSampleLineInfo(`${sourceId}-${feature}`, mapId); if (layerId.includes("sample_station")) await showSampleStationInfo(`${sourceId}-${feature}`, mapId); + else if (sourceId.includes("-SY") || sourceId.includes("-FDZ")) { + await showDiveInfo(`${sourceId}-${feature}`, mapId); + console.log(mapId, `${sourceId}-${feature}`); + } else await showTrackInfo(`${sourceId}-${feature}`, mapId); @@ -415,6 +429,36 @@ export function showTrackInfo(layerId, mapId = "homeMap") { }); } +export function showDiveInfo(layerId, mapId = "homeMap") { + Vue.config.maps[mapId].on("click", layerId, async (e) => { + // 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现) + if (e.defaultPrevented) + return; + // 标记该 event 已触发,禁止点击事件穿透 + if (typeof e.preventDefault === "function") + e.preventDefault(); + + const feature = e.features[0]; + let coordinate = e.lngLat; + const description = ` + + + + + + + + + + + + + `; + + showPopupDetails({ mapId, coordinate, description, maxWidth: "300px" }); + }) +} + export function showPopupDetails({ mapId, coordinate, description, maxWidth = "300px", closeButton = true, closeOnClick = true, options = {}, callbackOnClose }) { const map = Vue.config.maps[mapId]; if (!map) @@ -423,28 +467,29 @@ export function showPopupDetails({ mapId, coordinate, description, maxWidth = "3 const popupOptions = Object.assign({ offset: {}, className: "my-class", closeButton: closeButton, closeOnClick: closeOnClick }, options); // 如果已有共享 popup,复用并更新位置/内容;否则创建新的并绑定 close 事件以便释放引用 - if (sharedPopup) { + if (sharedPopup[mapId]) { try { - sharedPopup.setLngLat(coordinate) + sharedPopup[mapId].setLngLat(coordinate) .setHTML(description) .setOffset(10) - .setMaxWidth(maxWidth); + .setMaxWidth(maxWidth) + .addTo(map); } catch (err) { + console.error("设置 popup 失败:", err); try { // 万一现有 popup 状态异常,移除并重新创建 - sharedPopup.remove(); - sharedPopup = null; + sharedPopup[mapId].remove(); + sharedPopup[mapId] = null; } catch (e) { console.error("移除异常 popup 失败:", e); } - sharedPopup = new window.mapboxgl.Popup(popupOptions) + sharedPopup[mapId] = new window.mapboxgl.Popup(popupOptions) .setLngLat(coordinate) .setHTML(description) .setOffset(10) .setMaxWidth(maxWidth) .addTo(map); - sharedPopup.on('close', () => { - sharedPopup = null; + sharedPopup[mapId].on('close', () => { // 关闭弹窗时执行回调(如有) if (callbackOnClose && typeof callbackOnClose === "function") { callbackOnClose(); @@ -452,14 +497,13 @@ export function showPopupDetails({ mapId, coordinate, description, maxWidth = "3 }); } } else { - sharedPopup = new window.mapboxgl.Popup(popupOptions) + sharedPopup[mapId] = new window.mapboxgl.Popup(popupOptions) .setLngLat(coordinate) .setHTML(description) .setOffset(10) .setMaxWidth(maxWidth) .addTo(map); - sharedPopup.on('close', () => { - sharedPopup = null; + sharedPopup[mapId].on('close', () => { // 关闭弹窗时执行回调(如有) if (callbackOnClose && typeof callbackOnClose === "function") { callbackOnClose(); From 1dadfc0b6b372dd16874d18bed4ab76cd09a190c Mon Sep 17 00:00:00 2001 From: hym Date: Mon, 6 Apr 2026 11:01:12 +0800 Subject: [PATCH 12/17] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=88=AA=E6=AC=A1?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E8=88=AA=E8=BF=B9=E6=98=BE=E7=A4=BA=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/statistics/ShipStatistics.vue | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/views/statistics/ShipStatistics.vue b/src/views/statistics/ShipStatistics.vue index d2e3b55..e7257a2 100644 --- a/src/views/statistics/ShipStatistics.vue +++ b/src/views/statistics/ShipStatistics.vue @@ -76,7 +76,7 @@ import PieChart from './components/PieChart.vue' import { reportList, shipTotal, shipUnit, shipUnitYear, shipVoyage } from '../../api/statistics' import { getYearRange, filterDictItems } from '../../utils/index' import { initMapbox } from '@/utils/mapbox-utils' -import { loadVectorLayer, loadJsonPointFeature, showUnitReport } from '@/utils/vector-layer-utils' +import { setMapLayoutProperty, loadVectorLayer, loadJsonPointFeature, showUnitReport } from '@/utils/vector-layer-utils' import { ColorGenerator } from '@/utils/color-generator' export default { @@ -171,7 +171,6 @@ export default { // 选择科考船 handleCommand(command) { this.currentTitle = command - // this.removeSingleLineImg() if (command === '全部科考船') { this.getShipList() this.gitShipVoyage() @@ -243,13 +242,7 @@ export default { shipVoyage(params).then(res => { if (params.start || params.end) { // 按年份筛选数据,先隐藏所有图层,再根据筛选结果显示对应图层 - const layers = Vue.config.maps['voyageMap_ship'].getStyle().layers; - layers.forEach(layer => { - // 跳过底图 - if (layer.id !== '影像地图' && layer.type !== 'raster') { - Vue.config.maps['voyageMap_ship'].setLayoutProperty(layer.id, 'visibility', 'none'); - } - }); + setMapLayoutProperty('voyageMap_ship', 'none') } let i = 0 @@ -267,7 +260,6 @@ export default { selectVoyageYear(val) { const shipName = this.currentTitle === '全部科考船' ? '' : this.currentTitle if (val) { - // this.removeSingleLineImg() const params = getYearRange(val) this.gitShipVoyage({ ...params, ship_name: shipName }) } else { From efe6cd18a3092f9cf9674ee9dbffe05f1bbc885b Mon Sep 17 00:00:00 2001 From: hym Date: Mon, 6 Apr 2026 11:01:46 +0800 Subject: [PATCH 13/17] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8B=E6=BD=9C?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E4=B8=8B=E6=BD=9C=E8=BD=A8=E8=BF=B9=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/statistics/HovStatistics.vue | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/views/statistics/HovStatistics.vue b/src/views/statistics/HovStatistics.vue index c590b9b..a4d82f2 100644 --- a/src/views/statistics/HovStatistics.vue +++ b/src/views/statistics/HovStatistics.vue @@ -70,7 +70,7 @@ import PieChart from './components/PieChart.vue' import { reportList, platformHov, hovUnit, hovUnitYear, hovDiving } from '../../api/statistics' import { filterDictItems } from '../../utils/index' import { initMapbox } from "@/utils/mapbox-utils"; -import { loadVectorLayer, loadJsonPointFeature,showUnitReport } from '@/utils/vector-layer-utils' +import { setMapLayoutProperty, loadVectorLayer, loadJsonPointFeature, showUnitReport } from '@/utils/vector-layer-utils' import { ColorGenerator } from '@/utils/color-generator'; export default { @@ -158,7 +158,6 @@ export default { // 选择载人潜水器 handleCommand(command) { this.currentTitle = command - this.removeSingleLineImg() if (command === '全部载人潜水器') { this.getHovList() this.gitHovDiving() @@ -227,15 +226,17 @@ export default { // 潜次统计 gitHovDiving(params = {}) { hovDiving(params).then(res => { + if (params.platform_name) { + // 按潜水器筛选数据,先隐藏所有图层,再根据筛选结果显示对应图层 + setMapLayoutProperty('voyageMap_hov', 'none') + } + let i = 0; const colors = ColorGenerator.generateDivergingColors(res.array.length); - // 添加轨迹图层 res.array.forEach(item => { - // 加载轨迹图层 + // 加载下潜轨迹 loadVectorLayer(item.diving_sn, ["line", "symbol"], [colors[i], "#FFFFFF"], "visible", "voyageMap_hov", "dsds"); i++; - - // this.addSingleLineImg(item) }) }) }, From 5207a0662f5b535706146451d60c02d62428afdc Mon Sep 17 00:00:00 2001 From: hym Date: Mon, 6 Apr 2026 11:22:09 +0800 Subject: [PATCH 14/17] =?UTF-8?q?=E8=88=AA=E6=AC=A1=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?=E8=BD=A8=E8=BF=B9=E5=A2=9E=E5=8A=A0=E6=8C=89=E8=88=B9=E8=88=B6?= =?UTF-8?q?=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/statistics/ShipStatistics.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/statistics/ShipStatistics.vue b/src/views/statistics/ShipStatistics.vue index e7257a2..268d536 100644 --- a/src/views/statistics/ShipStatistics.vue +++ b/src/views/statistics/ShipStatistics.vue @@ -240,7 +240,7 @@ export default { // 航次统计 gitShipVoyage(params = {}) { shipVoyage(params).then(res => { - if (params.start || params.end) { + if (params.start || params.end || params.ship_name) { // 按年份筛选数据,先隐藏所有图层,再根据筛选结果显示对应图层 setMapLayoutProperty('voyageMap_ship', 'none') } From e472fb961e2b4a7762dd764f3a0ccb309cd45af6 Mon Sep 17 00:00:00 2001 From: hym Date: Mon, 6 Apr 2026 11:50:54 +0800 Subject: [PATCH 15/17] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9B=BE=E5=B1=82?= =?UTF-8?q?=E6=8C=89=E6=9F=A5=E8=AF=A2=E6=9D=A1=E4=BB=B6=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/dataSearch.vue | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/views/dataSearch.vue b/src/views/dataSearch.vue index d44c572..5f6a486 100644 --- a/src/views/dataSearch.vue +++ b/src/views/dataSearch.vue @@ -28,20 +28,24 @@

编号规则:航次编号_平台类型_设备类型_数据样品类型

-
+
-

{{ item.dataset_name }} {{ item.file_total }}

+

{{ item.dataset_name }} {{ item.file_total }}

{{ item.create_time }}
  • {{ sample.sample_name }} + v-for="(sample, i) in item.sampleArray" :key="i" @click.stop="onSample(sample, item.job_type)">{{ + sample.sample_name }}

航次:

- {{ item.voyage_name }} + {{ item.voyage_name + }}

平台类型:

@@ -232,7 +236,7 @@ import { getParamsList, getVoyageLine } from '../api/home' import { regInfo } from '@/api/dataSearch' import Cookies from 'js-cookie' import Vue from 'vue' -import { loadVectorLayer } from '@/utils/vector-layer-utils' +import { setMapLayoutProperty, loadVectorLayer } from '@/utils/vector-layer-utils' import { ColorGenerator } from '@/utils/color-generator'; import { initMapbox, triggerLayerClick } from "@/utils/mapbox-utils"; @@ -359,8 +363,8 @@ export default { center: [122, 15], minZoom: 2, maxZoom: 29, - coastlineOn:false, - contourOn:false + coastlineOn: false, + contourOn: false }); }, methods: { @@ -448,6 +452,9 @@ export default { FrameSessionId: Cookies.get('JSESSIONID') } getDataList(params).then(res => { + // 先隐藏所有图层,再根据筛选结果显示对应图层 + setMapLayoutProperty('dataSearchMap', 'none') + const arr = res.page.records if (this.radio === '航次') { if (arr.length) { @@ -456,7 +463,6 @@ export default { arr.forEach(item => { item.highlight = false item.show = true - // this.addSingleLineImg(item.voyage_name, null) // 加载轨迹图层 loadVectorLayer(item.voyage_name, ["line", "symbol"], [colors[i], colors[i]], "visible", "dataSearchMap", "dsds"); i++; @@ -753,7 +759,7 @@ export default { display: flex; flex-direction: column; - > .rule-text { + >.rule-text { flex-shrink: 0; margin-top: 12px; margin-bottom: 10px; @@ -763,7 +769,7 @@ export default { overflow-x: auto; } - > .left4 { + >.left4 { flex: 1; min-height: 0; } @@ -998,5 +1004,4 @@ export default { color: #fff; background: none !important; } - From cb78e3b6d49daf441b42678c021d92f3373daf0d Mon Sep 17 00:00:00 2001 From: hym Date: Mon, 6 Apr 2026 23:06:43 +0800 Subject: [PATCH 16/17] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=9F=A2=E9=87=8F?= =?UTF-8?q?=E5=9B=BE=E5=B1=82=E5=8A=A0=E8=BD=BD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/dataSearch.vue | 32 +++++++++++++++---------- src/views/home.vue | 6 ++--- src/views/statistics/AuvStatistics.vue | 4 ++-- src/views/statistics/HovStatistics.vue | 2 +- src/views/statistics/RovStatistics.vue | 4 ++-- src/views/statistics/ShipStatistics.vue | 2 +- 6 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/views/dataSearch.vue b/src/views/dataSearch.vue index 5f6a486..f13774e 100644 --- a/src/views/dataSearch.vue +++ b/src/views/dataSearch.vue @@ -236,7 +236,7 @@ import { getParamsList, getVoyageLine } from '../api/home' import { regInfo } from '@/api/dataSearch' import Cookies from 'js-cookie' import Vue from 'vue' -import { setMapLayoutProperty, loadVectorLayer } from '@/utils/vector-layer-utils' +import { setMapLayoutProperty, loadVectorLayer, showHighlightSampleInfo, lineWidthExpression } from '@/utils/vector-layer-utils' import { ColorGenerator } from '@/utils/color-generator'; import { initMapbox, triggerLayerClick } from "@/utils/mapbox-utils"; @@ -464,7 +464,7 @@ export default { item.highlight = false item.show = true // 加载轨迹图层 - loadVectorLayer(item.voyage_name, ["line", "symbol"], [colors[i], colors[i]], "visible", "dataSearchMap", "dsds"); + loadVectorLayer({ layerId: item.voyage_name, features: ["line", "symbol"], colors: [colors[i], colors[i]], visibility: "visible", mapId: "dataSearchMap", workspace: "dsds" }); i++; }) } @@ -487,14 +487,6 @@ export default { }, // 清除高亮 clearHighlight(voyageName, i) { - let lineWidthExpression = [ - "case", - ["boolean", ["feature-state", "hover"], false], - 4, - ["boolean", ["feature-state", "click"], false], - 2, - 1.8 - ]; Vue.config.maps["dataSearchMap"].setPaintProperty(`vector-${voyageName}-line`, "line-width", lineWidthExpression); this.dataList[i].highlight = false; }, @@ -540,11 +532,13 @@ export default { let i = 0; const colors = ColorGenerator.generateDivergingColors(arr.length); arr.forEach(item => { + // 加载测线/站位所在航次的轨迹 + // loadVectorLayer({layerId:item.voyage_name, features:["line", "symbol"], colors:[colors[i], colors[i]], visibility:"visible", mapId:"dataSearchMap", workspace:"dsds"}); // 添加测线/站位图层 if (item.job_type == "测线作业") { - loadVectorLayer(`sample_line_${item.dataset_name}`, ["line"], ["#FFFFFF", "#FFFFFF"], "visible", "dataSearchMap", "dsds", "sample_name"); + loadVectorLayer({ layerId: `sample_line_${item.dataset_name}`, features: ["line"], colors: ["#FFFFFF", "#FFFFFF"], visibility: "visible", mapId: "dataSearchMap", workspace: "dsds", textField: "sample_name", callbackOnClick: this.setHightItem }); } else { - loadVectorLayer(`sample_station_${item.dataset_name}`, ["symbol"], ["#FFFFFF", "#FFFFFF"], "visible", "dataSearchMap", "dsds", "sample_name"); + loadVectorLayer({ layerId: `sample_station_${item.dataset_name}`, features: ["symbol"], colors: ["#FFFFFF", "#FFFFFF"], visibility: "visible", mapId: "dataSearchMap", workspace: "dsds", textField: "sample_name", callbackOnClick: this.setHightItem }); } i++; }) @@ -570,6 +564,13 @@ export default { this.dataList = voyageList }, + setHightItem(item) { + // 单击地图上的样品后,在左侧列表中高亮对应项 + this.currenSample = item.properties.tsy_id; + // this.currenId = item.properties.tsy_id; + console.log("clicked item:", item); + }, + turnDeatilPage(item) { this.dialog5 = true regInfo({ voyage_name: item.voyage_name }).then(res => { @@ -647,9 +648,14 @@ export default { }); let layerId = `vector-${job_type == "测线作业" ? "sample_line" : "sample_station"}_${sample.dataset_name}-${job_type == "测线作业" ? "line" : "symbol"}`; let sourceLayerId = `${job_type == "测线作业" ? "sample_line" : "sample_station"}_${sample.dataset_name}`; + // triggerLayerClick("dataSearchMap", sourceLayerId, layerId, [lon, lat], { "sample_name": sample.sample_name }); + // 单击样品时,同步在地图上显示样品信息弹窗 + showHighlightSampleInfo({ mapId: "dataSearchMap", job_type, sample, layerId, sourceLayerId }); + // 将选中的图层移动到最顶层,传入 undefined 作为第二个参数即可 + Vue.config.maps["dataSearchMap"].moveLayer(layerId, undefined); + // vector-sample_station_TS-46-1_FDZ377_Slurp_SWYP-symbol、vector-sample_line_TS2-29-1_SY612_GQXJ_PJZP-line // highlightFeaturesByProperty("dataSearchMap", `${job_type == "测线作业" ? "sample_line" : "sample_station"}_${sample.dataset_name}`, layerId, "sample_name", sample.sample_name); - triggerLayerClick("dataSearchMap", sourceLayerId, layerId, [lon, lat], { "sample_name": sample.sample_name }); }, goHome() { this.$router.push({ name: 'home' }) diff --git a/src/views/home.vue b/src/views/home.vue index 023c605..d441c86 100644 --- a/src/views/home.vue +++ b/src/views/home.vue @@ -328,8 +328,8 @@ export default { // 加载海岸线 // loadVectorLayer("GSHHS_f_L1", ["line"], ["#FEFEFE"], "homeMap", 'ougp'); // 加载等深线(默认不显示) - loadVectorLayer("contour-4500m", ["line"], ["#FEFEFE"], "none", "homeMap", 'ougp'); - loadVectorLayer("contour-6000m", ["line"], ["#FEFEFE"], "none", "homeMap", 'ougp'); + loadVectorLayer({ layerId: "contour-4500m", features: ["line"], colors: ["#FEFEFE"], visibility: "none", mapId: "homeMap", workspace: 'ougp' }); + loadVectorLayer({ layerId: "contour-6000m", features: ["line"], colors: ["#FEFEFE"], visibility: "none", mapId: "homeMap", workspace: 'ougp' }); }); }, methods: { @@ -345,7 +345,7 @@ export default { const colors = ColorGenerator.generateDivergingColors(this.voyageList.length); this.voyageList.forEach(element => { // 加载轨迹图层 - loadVectorLayer(element.voyage_name, ["line", "symbol"], [colors[i], colors[i]], "visible", "homeMap", "dsds"); + loadVectorLayer({ layerId: element.voyage_name, features: ["line", "symbol"], colors: [colors[i], colors[i]], visibility: "visible", mapId: "homeMap", workspace: "dsds" }); i++; }); diff --git a/src/views/statistics/AuvStatistics.vue b/src/views/statistics/AuvStatistics.vue index 9889f2a..070623f 100644 --- a/src/views/statistics/AuvStatistics.vue +++ b/src/views/statistics/AuvStatistics.vue @@ -73,7 +73,7 @@ import PieChart from './components/PieChart.vue' import { reportList, platformAuv, auvUnit, auvUnitYear, auvDiving } from '../../api/statistics' import { filterDictItems } from '../../utils/index' import { initMapbox } from "@/utils/mapbox-utils"; -import { loadVectorLayer, loadJsonPointFeature,showUnitReport } from '@/utils/vector-layer-utils' +import { loadVectorLayer, loadJsonPointFeature, showUnitReport } from '@/utils/vector-layer-utils' import { ColorGenerator } from '@/utils/color-generator'; export default { @@ -235,7 +235,7 @@ export default { // 添加轨迹图层 res.array.forEach(item => { // 加载轨迹图层 - loadVectorLayer(item.diving_sn, ["line", "symbol"], [colors[i], "#FFFFFF"], "visible", "voyageMap_auv", "dsds"); + loadVectorLayer({ layerId: item.diving_sn, features: ["line", "symbol"], colors: [colors[i], "#FFFFFF"], visibility: "visible", mapId: "voyageMap_auv", worksapce: "dsds" }); i++; }) }) diff --git a/src/views/statistics/HovStatistics.vue b/src/views/statistics/HovStatistics.vue index a4d82f2..6f8df35 100644 --- a/src/views/statistics/HovStatistics.vue +++ b/src/views/statistics/HovStatistics.vue @@ -235,7 +235,7 @@ export default { const colors = ColorGenerator.generateDivergingColors(res.array.length); res.array.forEach(item => { // 加载下潜轨迹 - loadVectorLayer(item.diving_sn, ["line", "symbol"], [colors[i], "#FFFFFF"], "visible", "voyageMap_hov", "dsds"); + loadVectorLayer({ layerId: item.diving_sn, features: ["line", "symbol"], colors: [colors[i], "#FFFFFF"], visibility: "visible", mapId: "voyageMap_hov", workspace: "dsds" }); i++; }) }) diff --git a/src/views/statistics/RovStatistics.vue b/src/views/statistics/RovStatistics.vue index 93c8109..ce684eb 100644 --- a/src/views/statistics/RovStatistics.vue +++ b/src/views/statistics/RovStatistics.vue @@ -73,7 +73,7 @@ import PieChart from './components/PieChart.vue' import { reportList, platformRov, rovUnit, rovUnitYear, rovDiving } from '../../api/statistics' import { filterDictItems } from '../../utils/index' import { initMapbox } from "@/utils/mapbox-utils"; -import { loadVectorLayer, loadJsonPointFeature,showUnitReport } from '@/utils/vector-layer-utils' +import { loadVectorLayer, loadJsonPointFeature, showUnitReport } from '@/utils/vector-layer-utils' import { ColorGenerator } from '@/utils/color-generator'; export default { @@ -235,7 +235,7 @@ export default { // 添加轨迹图层 res.array.forEach(item => { // 加载轨迹图层 - loadVectorLayer(item.diving_sn, ["line", "symbol"], [colors[i], "#FFFFFF"], "visible", "voyageMap_rov", "dsds"); + loadVectorLayer({ layerId: item.diving_sn, features: ["line", "symbol"], colors: [colors[i], "#FFFFFF"], visibility: "visible", mapId: "voyageMap_rov", workspace: "dsds" }); i++; }) }) diff --git a/src/views/statistics/ShipStatistics.vue b/src/views/statistics/ShipStatistics.vue index 268d536..68e9ed8 100644 --- a/src/views/statistics/ShipStatistics.vue +++ b/src/views/statistics/ShipStatistics.vue @@ -250,7 +250,7 @@ export default { // 添加轨迹图层 res.array.forEach(item => { // 加载轨迹图层(已存在则设置为可见) - loadVectorLayer(item.voyage_name, ['line', 'symbol'], [colors[i], '#FFFFFF'], 'visible', 'voyageMap_ship', 'dsds') + loadVectorLayer({ layerId: item.voyage_name, features: ['line', 'symbol'], colors: [colors[i], '#FFFFFF'], visibility: 'visible', mapId: 'voyageMap_ship', workspace: 'dsds' }) i++ }) }) From 84270bd4eb9a776b7af3756743d5519b776855aa Mon Sep 17 00:00:00 2001 From: hym Date: Mon, 6 Apr 2026 23:07:42 +0800 Subject: [PATCH 17/17] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=9F=A2=E9=87=8F?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/color-generator.js | 2 +- src/utils/mapbox-utils.js | 6 +- src/utils/vector-layer-utils.js | 148 ++++++++++++++++++++++++++++++-- 3 files changed, 144 insertions(+), 12 deletions(-) diff --git a/src/utils/color-generator.js b/src/utils/color-generator.js index 8d7c566..547e25b 100644 --- a/src/utils/color-generator.js +++ b/src/utils/color-generator.js @@ -66,7 +66,7 @@ export class ColorGenerator { positiveColor = '#1a9850' ) { const colors = []; - const midPoint = Math.floor(count / 2); + const midPoint = Math.floor(count + 1 / 2); // 负值部分 const negRGB = this.parseHexColor(negativeColor); diff --git a/src/utils/mapbox-utils.js b/src/utils/mapbox-utils.js index 7670946..8e2a987 100644 --- a/src/utils/mapbox-utils.js +++ b/src/utils/mapbox-utils.js @@ -86,11 +86,11 @@ export function initMapbox(mapId, options = {}) { map.on('load', () => { // 加载海岸线 if (options.coastlineOn || false) - loadVectorLayer("GSHHS_f_L1", ["line"], ["#5c71c3"], "visible", mapId, 'ougp'); + loadVectorLayer({ layerId: "GSHHS_f_L1", features: ["line"], colors: ["#5c71c3"], visibility: "visible", mapId, workspace: 'ougp' }); // 加载等深线 if (options.contourOn || false) { - loadVectorLayer("contour-4500m", ["line"], ["#FEFEFE"], "visible", mapId, 'ougp'); - loadVectorLayer("contour-6000m", ["line"], ["#FEFEFE"], "visible", mapId, 'ougp'); + loadVectorLayer({ layerId: "contour-4500m", features: ["line"], colors: ["#FEFEFE"], visibility: "visible", mapId, workspace: 'ougp' }); + loadVectorLayer({ layerId: "contour-6000m", features: ["line"], colors: ["#FEFEFE"], visibility: "visible", mapId, workspace: 'ougp' }); } }); } diff --git a/src/utils/vector-layer-utils.js b/src/utils/vector-layer-utils.js index 91783b6..03c4fb3 100644 --- a/src/utils/vector-layer-utils.js +++ b/src/utils/vector-layer-utils.js @@ -3,8 +3,21 @@ import { regInfo } from '@/api/dataSearch' let sourceLayer = undefined; let clickedLineIds = []; + // 存储可复用的每个地图对象的唯一 Popup 实例(key 为 mapId),以避免频繁创建/销毁 Popup 导致的性能问题和潜在内存泄漏 let sharedPopup = {}; +// 存储上一条点击的线 +let lastLineId; + +// 默认的线宽表达式 +export let lineWidthExpression = [ + "case", + ["boolean", ["feature-state", "hover"], false], + 4, + ["boolean", ["feature-state", "click"], false], + 2, + 1.8 +]; export function setMapLayoutProperty(mapId, visibility = "none") { // 设置所有图层的显示/隐藏状态(根据图层类型或名称过滤底图图层,避免误操作) @@ -16,7 +29,19 @@ export function setMapLayoutProperty(mapId, visibility = "none") { } }); } -export function loadVectorLayer(layerId, features = ["line", "symbol"], colors = ["#FEFEFE", "#FF0000"], visibility = "visible", mapId = "homeMap", workspace = 'dsds', textField = "name") { + +export function setMapPaintProperty({ mapId, property, value }) { + const layers = Vue.config.maps[mapId].getStyle().layers; + layers.forEach(layer => { + // 跳过底图和非测线图层 + if (layer.id !== '影像地图' && layer.type !== 'raster' && layer.id.endsWith("-line")) { + Vue.config.maps[mapId].setPaintProperty(layer.id, property, lineWidthExpression); + } + }); + // Vue.config.maps[mapId].setPaintProperty(layerId, "line-width", 14); +} + +export function loadVectorLayer({ layerId, features = ["line", "symbol"], colors = ["#FEFEFE", "#FF0000"], visibility = "visible", mapId = "homeMap", workspace = 'dsds', textField = "name", callbackOnClick }) { // 添加数据源 const sourceId = `vector-${layerId}`; let source = Vue.config.maps[mapId].getSource(sourceId); @@ -50,11 +75,11 @@ export function loadVectorLayer(layerId, features = ["line", "symbol"], colors = let i = 0; features.forEach((feature) => { - loadVectorFeature(layerId, feature, colors[i++], visibility, mapId, textField); + loadVectorFeature({ layerId, feature, color: colors[i++], visibility, mapId, textField, callbackOnClick }); }); } -export async function loadVectorFeature(layerId, feature, color, visibility, mapId, textField = "name") { +export async function loadVectorFeature({ layerId, feature, color, visibility, mapId, textField = "name", callbackOnClick }) { let sourceId = `vector-${layerId}`; // symbol // Slot 是 Mapbox 样式规范中的一个概念,它定义了图层在渲染过程中的位置和顺序。 @@ -147,9 +172,9 @@ export async function loadVectorFeature(layerId, feature, color, visibility, map changeCursor(`${sourceId}-${feature}`, mapId); if (layerId.includes("sample_line")) - await showSampleLineInfo(`${sourceId}-${feature}`, mapId); + await showSampleLineInfo(`${sourceId}-${feature}`, mapId, callbackOnClick); if (layerId.includes("sample_station")) - await showSampleStationInfo(`${sourceId}-${feature}`, mapId); + await showSampleStationInfo(`${sourceId}-${feature}`, mapId, callbackOnClick); else if (sourceId.includes("-SY") || sourceId.includes("-FDZ")) { await showDiveInfo(`${sourceId}-${feature}`, mapId); console.log(mapId, `${sourceId}-${feature}`); @@ -290,14 +315,20 @@ export function changeCursor(layerId, mapId = "homeMap", cursor = "pointer") { }); } -export function showSampleLineInfo(layerId, mapId = "homeMap") { +export function showSampleLineInfo(layerId, mapId = "homeMap", callbackOnClick = null) { Vue.config.maps[mapId].on("click", layerId, async (e) => { + // 在左侧列表中高亮对应项 + if (callbackOnClick && typeof callbackOnClick === "function") { + callbackOnClick(e.features[0]); + } + // 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现) if (e.defaultPrevented) return; // 标记该 event 已触发,禁止点击事件穿透 if (typeof e.preventDefault === "function") e.preventDefault(); + // console.log("showSampleLineInfo: ", layerId, e.features[0]); const description = ` @@ -339,8 +370,13 @@ export function showSampleLineInfo(layerId, mapId = "homeMap") { }); } -export function showSampleStationInfo(layerId, mapId = "homeMap") { +export function showSampleStationInfo(layerId, mapId = "homeMap", callbackOnClick = null) { Vue.config.maps[mapId].on("click", layerId, async (e) => { + // 在左侧列表中高亮对应项 + if (callbackOnClick && typeof callbackOnClick === "function") { + callbackOnClick(e.features[0]); + } + // 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现) if (e.defaultPrevented) return; @@ -361,7 +397,7 @@ export function showSampleStationInfo(layerId, mapId = "homeMap") { - + @@ -384,6 +420,102 @@ export function showSampleStationInfo(layerId, mapId = "homeMap") { }); } +export function showHighlightSampleInfo({ mapId, job_type, sample, layerId, sourceLayerId, callbackOnClick }) { + let description = ""; + let coordinate; + if (job_type == "测线作业") { + // // 恢复其它测线的线宽 + // // setMapPaintProperty({ mapId, property: "line-width", value: lineWidthExpression }); + // // 恢复上一条高亮测线的宽度 + // Vue.config.maps[mapId].setPaintProperty(lastLineId, "line-width", lineWidthExpression); + // // 高亮当前选择的测线 + // Vue.config.maps[mapId].setPaintProperty(layerId, "line-width", 5); + + // if (lastLineId) + // Vue.config.maps[mapId].setFeatureState( + // { sourceId: sourceLayerId, id: lastLineId }, + // { hover: false } + // ); + // Vue.config.maps[mapId].setFeatureState( + // { sourceId: sourceLayerId, id: layerId }, + // { hover: true } + // ); + + // if (lastLineId) + // Vue.config.maps[mapId].setPaintProperty(lastLineId, 'line-color', '#FFFFFF') + // Vue.config.maps[mapId].setPaintProperty(layerId, 'line-color', '#FFFF00') + // lastLineId = layerId; + + coordinate = [(Number(sample.start_lon) + Number(sample.end_lon)) / 2, (Number(sample.start_lat) + Number(sample.end_lat)) / 2]; + description = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `; + } + else if (job_type == "站位作业") { + coordinate = [sample.sampling_lon, sample.sampling_lat]; + description = ` + + + + + + + + + + + + + + + + + + + + + + + + + + `; + } + showPopupDetails({ mapId, coordinate, description, maxWidth: "450px", callbackOnClose: callbackOnClick }); +} + export function showTrackInfo(layerId, mapId = "homeMap") { Vue.config.maps[mapId].on("click", layerId, async (e) => { // 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)