diff --git a/config/index.js b/config/index.js index 02354df..38f603c 100644 --- a/config/index.js +++ b/config/index.js @@ -33,6 +33,15 @@ module.exports = { '^/api': '/ds1' } }, + { + context: '/api/report/ship/total.htm', + target: 'https://weixin.bdsmart.cn', + changeOrigin: true, + secure: true, + pathRewrite: { + '^/api': '/ds1' + } + }, { context: '/api', target: 'http://test.gopmas.com/ds', // 测试环境 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'; 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 b149f65..03c4fb3 100644 --- a/src/utils/vector-layer-utils.js +++ b/src/utils/vector-layer-utils.js @@ -3,10 +3,45 @@ import { regInfo } from '@/api/dataSearch' let sourceLayer = undefined; let clickedLineIds = []; -// 单例 Popup,用于确保地图上只有一个弹框实例 -let sharedPopup = null; -export function loadVectorLayer(layerId, features = ["line", "symbol"], colors = ["#FEFEFE", "#FF0000"], visibility = "visible", mapId = "homeMap", workspace = 'dsds', textField = "name") { +// 存储可复用的每个地图对象的唯一 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") { + // 设置所有图层的显示/隐藏状态(根据图层类型或名称过滤底图图层,避免误操作) + 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 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); @@ -40,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 样式规范中的一个概念,它定义了图层在渲染过程中的位置和顺序。 @@ -127,15 +162,23 @@ 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); 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}`); + } else await showTrackInfo(`${sourceId}-${feature}`, mapId); @@ -145,8 +188,10 @@ 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) => { - if (!e.features || e.features.length <= 0) + Vue.config.maps[mapId].on('click', layerId, async (e) => { + sourceLayer = e.features[0].sourceLayer; + // 排除没有 sourceLayer 的情况(如点击事件中 features 可能来自多个图层,某些图层缺乏 sourceLayer 属性会导致后续 setFeatureState 报错) + if (!sourceLayer) return; // // 查询点击点周围的所有要素(使用边界框) @@ -161,9 +206,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]; @@ -188,24 +230,21 @@ 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); - 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 (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 }, @@ -231,6 +270,41 @@ export function setHighlight(layerId, sourceId, mapId = "homeMap") { }); } +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) { + alert("未查询到相关单位的统计信息"); + return; + } + + const description = ` +
| 单位参航信息 | +|
| 单位名称: ${unitReports[0].unit_name} | +|
| 参航次数: ${unitReports[0].voyage_total || "/"} | +参航天数: ${unitReports[0].voyage_days || "/"} | +
| 参航人数: ${unitReports[0].member_total || "/"} | +下潜次数: ${unitReports[0].drving_total || "/"} | +
| 首潜人数: ${unitReports[0].f_drving_total || "/"} | +首次参航人数: ${unitReports[0].f_voyage_total || "/"} | +
| 测线信息 | +|
| 数据集名称: ${sample.dataset_name} | +数据样品编号: ${sample.sample_name} | +
| 赞助机构: ${sample.funding_org} | +公开期限: ${sample.publish_rule} | +
| 开始经度: ${sample.start_lon}° | +结束经度: ${sample.end_lon}° | +
| 开始纬度: ${sample.start_lat}° | +结束纬度: ${sample.end_lat}° | +
| 开始时间: ${sample.start_time} | +结束时间: ${sample.end_time} | +
| 测线深度: ${sample.line_deep}m | +数据状态: ${sample.sample_status} | +
| 备注信息: ${sample.sample_remark} | +|
| 站位信息 | +|
| 数据集名称: ${sample.dataset_name} | +样品编号: ${sample.sample_name} | +
| 赞助机构: ${sample.funding_org} | +公开期限: ${sample.publish_rule} | +
| 站位经度: ${sample.sampling_lon}° | +站位纬度: ${sample.sampling_lat}° | +
| 采样时间: ${sample.sampling_time} | +采样深度: ${sample.sampling_deep}m | +
| 备注信息: ${sample.sample_remark} | +|
| 潜次信息 | +|
| 潜次编号: ${feature.properties.name} | +|
| created_at: ${feature.properties.created_at} | +
编号规则:航次编号_平台类型_设备类型_数据样品类型
{{ item.dataset_name }} {{ item.file_total }}
+{{ item.dataset_name }} {{ item.file_total }}
{{ item.create_time }}航次:
- {{ item.voyage_name }} + {{ item.voyage_name + }}平台类型:
@@ -226,13 +230,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 { setMapLayoutProperty, loadVectorLayer, showHighlightSampleInfo, lineWidthExpression } 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', @@ -354,8 +355,8 @@ export default { center: [122, 15], minZoom: 2, maxZoom: 29, - coastlineOn:false, - contourOn:false + coastlineOn: false, + contourOn: false }); }, methods: { @@ -443,6 +444,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) { @@ -451,9 +455,8 @@ 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"); + loadVectorLayer({ layerId: item.voyage_name, features: ["line", "symbol"], colors: [colors[i], colors[i]], visibility: "visible", mapId: "dataSearchMap", workspace: "dsds" }); i++; }) } @@ -476,14 +479,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; }, @@ -529,11 +524,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++; }) @@ -559,6 +556,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 => { @@ -636,9 +640,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' }) @@ -748,7 +757,7 @@ export default { display: flex; flex-direction: column; - > .rule-text { + >.rule-text { flex-shrink: 0; margin-top: 12px; margin-bottom: 10px; @@ -758,7 +767,7 @@ export default { overflow-x: auto; } - > .left4 { + >.left4 { flex: 1; min-height: 0; } @@ -999,5 +1008,4 @@ export default { color: #fff; background: none !important; } - diff --git a/src/views/home.vue b/src/views/home.vue index a270da2..8ee9da2 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 1ca79c3..35b297b 100644 --- a/src/views/statistics/AuvStatistics.vue +++ b/src/views/statistics/AuvStatistics.vue @@ -63,15 +63,16 @@