提交 308e8d55 作者: 方治民

feat: 采用 Hook 方式重构 Mapbox 组件,测试通信方式

上级 9aeb4a39
/** 地图地址路径 */
const MapUrl = 'https://hntq.zhijietianqi.com'
/** 地图基础样式配置 */
export const countyLayers: mapboxgl.AnyLayer[] = [
{
id: 'background',
type: 'background',
// layout: { visibility: 'none' },
// paint: { 'background-color': 'hsla(0, 0%, 0%, 0)' },
paint: { 'background-color': '#ffffff' },
},
{
id: 'xian-fill',
type: 'fill',
source: 'compositeConty',
'source-layer': '430000.area',
layout: {},
minzoom: 0,
// paint: { 'line-color': 'hsl(0, 0%, 79%)' },
paint: { 'fill-color': 'rgba(255, 255, 255, 0)' },
},
{
id: 'xian-boundary',
type: 'line',
source: 'compositeConty',
'source-layer': '430000.area',
layout: {},
minzoom: 0,
paint: { 'line-color': 'hsl(0, 0%, 65%)' },
},
{
id: 'shi-boundary',
type: 'line',
source: 'compositeCity',
'source-layer': '430000.city',
layout: {},
minzoom: 0,
paint: { 'line-color': '#666' },
},
{
id: 'secondary-river',
type: 'fill',
source: 'riskCombine',
'source-layer': 'riversecondary',
layout: { visibility: 'none' },
// paint: { 'fill-color': 'hsl(157, 82%, 42%)' },
// paint: { 'fill-color': '#027EFE' },//7DF2FF
paint: { 'fill-color': '#7DF2FF' },
},
{
id: 'hunansheng-mask',
type: 'fill',
source: 'compositeProvinceMask',
'source-layer': '430000.mask',
layout: {
visibility: 'visible',
},
paint: { 'fill-color': '#fff' },
},
// {
// id: 'sheng-boundary',
// type: 'line',
// source: 'compositeProvince',
// 'source-layer': '430000',
// layout: {
// 'line-join': 'round',
// 'line-cap': 'round',
// },
// paint: {
// 'line-color': '#000',
// 'line-width': 8,
// 'line-blur': 70.7,
// 'line-offset': -4,
// },
// },
{
id: 'sheng-boundary-line',
type: 'line',
source: 'compositeProvince',
'source-layer': '430000',
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': '#000',
'line-width': 8,
'line-blur': 70.7,
'line-offset': -4,
},
},
{
id: 'sheng-boundary',
type: 'line',
source: 'compositeProvince',
'source-layer': '430000',
paint: {
'line-width': 1,
'line-color': '#000',
},
},
]
export const provinceLayers: mapboxgl.AnyLayer[] = [
{
id: 'background',
type: 'background',
// layout: { visibility: 'none' },
paint: { 'background-color': '#ffffff' },
},
{
id: 'hunansheng-mask',
type: 'fill',
source: 'composite',
'source-layer': 'hunansheng-mask',
layout: {
visibility: 'visible',
},
paint: { 'fill-color': '#fff' },
},
{
id: 'xian-boundary',
type: 'line',
source: 'composite',
'source-layer': 'xian-boundary',
layout: {},
minzoom: 7.7,
// paint: { 'line-color': 'hsl(0, 0%, 79%)' },
paint: { 'line-color': '#9e9e9e' },
},
{
id: 'shi-boundary',
type: 'line',
source: 'composite',
'source-layer': 'shi-boundary',
layout: {},
maxzoom: 7.7,
paint: { 'line-color': '#9e9e9e' },
},
// 湖南省周边
// {
// id: 'hunanshengzhoubian',
// type: 'line',
// source: 'composite',
// 'source-layer': 'hunanshengzhoubian',
// layout: {
// // visibility: 'none',
// },
// paint: { 'line-color': '#9e9e9e' },
// },
// {
// id: 'china-boundary',
// type: 'line',
// source: 'composite',
// 'source-layer': 'china-1zg0yz',
// layout: {
// visibility: 'none',
// },
// paint: { 'line-color': '#9e9e9e', 'line-width': 2 },
// },
{
id: 'sheng-boundary',
type: 'line',
source: 'composite',
'source-layer': 'sheng-boundary',
layout: {},
paint: { 'line-width': 2 },
},
{
id: 'sheng-boundary copy',
type: 'line',
source: 'composite',
'source-layer': 'sheng-boundary',
layout: { 'line-cap': 'round', 'line-join': 'round' },
paint: {
'line-width': 8,
'line-blur': 92.7,
'line-translate': [0, 0],
'line-offset': -4,
'line-color': 'hsl(0, 0%, 0%)',
},
},
]
export const surfacerainLayers: mapboxgl.AnyLayer[] = [
/** ************************************* 全流域 **************************************/
/** 大流域 */
{
id: 'water-big-fill',
type: 'fill',
source: 'compositeBig',
'source-layer': 'watershed-big',
layout: { visibility: 'none' },
paint: { 'fill-color': '#fff' },
},
{
id: 'water-mediumweb-fill',
type: 'fill',
source: 'compositeMediumweb',
'source-layer': 'watershedmediumweb',
layout: { visibility: 'none' },
paint: { 'fill-color': '#fff' },
},
{
id: 'water-big-name',
type: 'symbol',
source: 'compositeBigLabel',
'source-layer': 'watershed-big-label',
layout: {
'text-size': 13,
'text-field': '{name}',
visibility: 'none',
'text-allow-overlap': true,
},
paint: {
'text-halo-width': 1,
'text-halo-color': '#fff',
'text-color': '#444',
},
},
/**
* 大流域 上中下名称
*/
// {
// id: 'medium-watershed-name',
// type: 'symbol',
// source: 'watershedDashmedium',
// 'source-layer': 'watershed-big-medium',
// layout: {
// 'text-size': 13,
// 'text-field': '{name}',
// 'text-allow-overlap': true,
// },
// paint: {
// 'text-halo-width': 1,
// 'text-halo-color': '#fff',
// 'text-color': '#444',
// },
// },
{
id: 'water-big-line',
type: 'line',
source: 'compositeBig',
'source-layer': 'watershed-big',
paint: { 'line-color': '#666' },
layout: { visibility: 'none' },
},
/** ************************************** 中小河流域 ***************************************/
{
id: 'water-mediumweb-line',
type: 'line',
source: 'compositeMediumweb',
'source-layer': 'watershedmediumweb',
paint: { 'line-color': '#666' },
layout: { visibility: 'none' },
},
{
id: 'water-mediumweb-name',
type: 'symbol',
source: 'compositeMediumwebName',
'source-layer': 'watershedmediumweblabel',
layout: {
'text-size': 12,
'text-field': '{name}',
'text-allow-overlap': false,
'text-ignore-placement': false,
'text-font': ['Microsoft YaHei'],
visibility: 'none',
},
paint: {
'text-halo-width': 1,
'text-halo-color': '#fff',
'text-color': '#444',
},
},
/**
* 二级河流
*/
{
id: 'secondary-river',
type: 'fill',
source: 'riskCombine',
'source-layer': 'riversecondary',
layout: { visibility: 'none' },
// paint: { 'fill-color': 'hsl(157, 82%, 42%)' },
paint: { 'fill-color': '#3DA5FF' },
},
/**
* 三级河流
*/
{
id: 'thirdlevel-river',
type: 'line',
source: 'riskCombine',
'source-layer': 'rivertertiary',
layout: { visibility: 'none' },
paint: { 'line-color': '#69C0FF' },
},
/**
* 大流域上中下线
*/
{
id: 'medium-watershed',
type: 'line',
source: 'watershedDashmedium',
'source-layer': 'watershed-big-medium',
layout: { visibility: 'none' },
paint: { 'line-width': 2, 'line-color': '#1974ff' },
},
{
id: 'water-quan-mask',
type: 'fill',
source: 'compositeAll',
'source-layer': 'watershedquanliuyumask',
// layout: { visibility: 'none' },
paint: { 'fill-color': '#fff' },
},
{
id: 'water-quan-line',
type: 'line',
source: 'compositeAll',
'source-layer': 'watershedquanliuyu',
layout: { visibility: 'visible' },
paint: { 'line-color': '#666' },
},
]
export const mapOpts = {
// center: [111.53897, 27.7573],
// zoom: 6.3,
minzoom: 5,
maxzoom: 18,
/** 湖南区域 */
maxBounds: [
[99.94, 20.89],
[125.03, 33.52],
],
zoom: 5.5, // starting zoom
center: [111.6, 26.168], // starting position [lng, lat]
projection: 'equirectangular',
}
export const mapStyles: mapboxgl.Style = {
version: 8,
name: 'HUNAN_BOUNDARY__environment_v2',
metadata: {
'mapbox:autocomposite': true,
'mapbox:type': 'template',
'mapbox:sdk-support': {
js: 'latest',
android: '7.4.0',
ios: '4.11.0',
},
'mapbox:groups': {},
'mapbox:uiParadigm': 'layers',
},
bearing: 0,
pitch: 0,
sources: {
composite: {
type: 'vector',
tiles: [
`${MapUrl}/proxy/ifzm.2dcp8xnf,ifzm.ck03ktoon0kjs2nnwex2obobl-246jp,ifzm.cjyz8y6hi0pfr2no71j4yzhny-80vkn,ifzm.cjyzh5amz0r4l2up6ibnrpd0j-1r2sr,ifzm.ck096fe9l0ycc2nln9xryn2zk-0rswm,ifzm.cjyz95qf203yf2qo9yn4fy3u7-99efd/{z}/{x}/{y}`,
],
},
/** 新版省级mask */
compositeProvinceMask: {
type: 'vector',
tiles: ['https://foxgis.server.yiring.com/api/tilesets/430000.mask/{z}/{x}/{y}.pbf'],
},
/** 新版省级 */
compositeProvince: {
type: 'vector',
tiles: ['https://foxgis.server.yiring.com/api/tilesets/430000/{z}/{x}/{y}.pbf'],
},
/** 新版市级 */
compositeCity: {
type: 'vector',
tiles: ['https://foxgis.server.yiring.com/api/tilesets/430000.city/{z}/{x}/{y}.pbf'],
},
/** 新版县区 */
compositeConty: {
type: 'vector',
tiles: ['https://foxgis.server.yiring.com/api/tilesets/430000.area/{z}/{x}/{y}.pbf'],
},
/** 新版 洞庭湖 */
riskCombine: {
type: 'vector',
scheme: 'xyz',
minzoom: 0,
maxzoom: 9,
bounds: [87.638275, 8.296956, 136.638275, 44.385598],
tiles: ['https://foxgis.server.yiring.com/api/tilesets/river-secondary/{z}/{x}/{y}.pbf'],
},
/** 新版 全流域及遮罩 */
compositeAll: {
type: 'vector',
// format: 'pbf',
scheme: 'xyz',
maxzoom: 11,
minzoom: 0,
bounds: [87.638275, 8.296956, 136.638275, 44.385598],
tiles: ['https://foxgis.server.yiring.com/api/tilesets/watershed-quan-liu-yu/{z}/{x}/{y}.pbf'],
},
/** 新版 大河流域 */
compositeBig: {
type: 'vector',
scheme: 'xyz',
tiles: ['https://foxgis.server.yiring.com/api/tilesets/watershed-big/{z}/{x}/{y}.pbf'],
maxzoom: 13,
minzoom: 1,
bounds: [107.264454, 23.925618, 114.296651, 30.548506],
},
/** 新版 大河流域名称 */
compositeBigLabel: {
type: 'vector',
scheme: 'xyz',
tiles: ['https://foxgis.server.yiring.com/api/tilesets/watershed-big-label/{z}/{x}/{y}.pbf'],
maxzoom: 0,
minzoom: 0,
bounds: [109.539, 26.6586, 112.632, 29.6013],
},
/** 新版 中小河流域及名称 */
compositeMediumweb: {
type: 'vector',
scheme: 'xyz',
tiles: ['https://foxgis.server.yiring.com/api/tilesets/watershed-mediumweb/{z}/{x}/{y}.pbf'],
maxzoom: 11,
minzoom: 0,
bounds: [107.264454, 23.925618, 114.296644, 30.548506],
},
compositeMediumwebName: {
type: 'vector',
scheme: 'xyz',
tiles: ['https://foxgis.server.yiring.com/api/tilesets/watershed-mediumweb-label/{z}/{x}/{y}.pbf'],
minzoom: 0,
maxzoom: 1,
bounds: [108.3062, 24.5421, 114.074, 30.0494],
},
/** 上中下游边界 */
watershedDashmedium: {
type: 'vector',
scheme: 'xyz',
minzoom: 0,
maxzoom: 12,
bounds: [107.264454, 23.925618, 114.296651, 30.548506],
tiles: ['https://foxgis.server.yiring.com/api/tilesets/watershed-big-medium/{z}/{x}/{y}.pbf'],
},
},
sprite: 'https://hntq.zhijietianqi.com/zj/map/sprite/sprite?',
glyphs: 'https://foxgis.server.yiring.com/api/fonts/{fontstack}/{range}.pbf',
layers: provinceLayers,
}
import type { MapboxConfig, MapboxInstance } from './index'
import { isProdMode } from '@/utils/env'
// 组件名称
export const name = 'Mapbox'
/**
* 注册 Mapbox 实例
* @param config Mapbox 配置项
*/
export function useMapbox<T extends MapboxInstance, P extends MapboxConfig>(
config: P,
): [(instance: T) => void, MapboxInstance] {
const instanceRef = ref<T>()
function register(instance: T) {
if (isProdMode()) {
if (instance === unref(instanceRef)) {
return
}
onUnmounted(() => {
instanceRef.value = null
})
}
instanceRef.value = instance
config && instance?.setConfig(config)
}
function getInstance() {
const instance = unref(instanceRef)
if (!instance) {
console.warn('Mapbox instance is undefined!')
}
return instance as T
}
return [
register,
{
setConfig: (config: Partial<P>) => getInstance()?.setConfig(config),
isReady: computed(() => instanceRef?.value?.isReady) as unknown as ComputedRef<boolean>,
},
]
}
...@@ -810,4 +810,27 @@ export interface MapboxConfig { ...@@ -810,4 +810,27 @@ export interface MapboxConfig {
*/ */
align?: 'bottom-left' | 'bottom-right' align?: 'bottom-left' | 'bottom-right'
} }
// =============== 自定义事件 =================
/**
* 注意: 此处暴露的 map 对象的类型定义只是为了方便 IDE 提示, 实际上 map 对象上的方法和属性并不完整,依赖于 Mapbox 自定义组件的内部实现
* @param map 组件实例
* @param data 事件数据, 由视图层在 Map 加载完成后传入的可序列化 JSON 数据
*/
onLoaded?: (map: MapboxInstance, data: Recordable) => void
}
/**
* Mapbox 组件实例类型定义
*/
export interface MapboxInstance {
/**
* 设置地图组件配置
* @param config 地图配置
*/
setConfig: (config: Partial<MapboxConfig>) => void
/**
* 地图组件是否准备好了
*/
isReady: ComputedRef<boolean>
} }
...@@ -11,34 +11,48 @@ ...@@ -11,34 +11,48 @@
// 1. 通过在逻辑层 data 中设置的属性向视图层传递数据 // 1. 通过在逻辑层 data 中设置的属性向视图层传递数据
// 2. 通过在逻辑层 methods 定义方法,在视图层调用传递参数(注意:只能传递可进行 JSON 序列化的数据) // 2. 通过在逻辑层 methods 定义方法,在视图层调用传递参数(注意:只能传递可进行 JSON 序列化的数据)
function nail() {}
export default { export default {
props: { emits: ['register'],
config: {
type: Object,
default: () => ({}),
},
},
data() { data() {
return { return {
id: nanoid(), id: nanoid(),
onLoaded: nail,
options: {}, options: {},
// FIXED: 定义为 undefined,避免在 Hook 中通过计算属性取值是出现两次变化的问题
loaded: undefined,
} }
}, },
computed: {
isReady() {
return this.loaded
},
},
created() { created() {
Message.loading() Message.loading()
}, },
mounted() { mounted() {
this.options = { this.$emit('register', this)
container: this.id,
style: this.config?.style,
options: this.config?.options,
attribution: this.config?.attribution,
}
}, },
methods: { methods: {
onMapLoad(data) { onMapLoad(data) {
this.loaded = true
Message.hideLoading() Message.hideLoading()
console.log('✨ Map Loaded', data) console.log('✨ Map Loaded', data)
// 触发 onLoaded 事件
this.onLoaded?.(this, data)
},
setConfig(config) {
this.options = {
container: this.id,
style: config?.style,
options: config?.options,
attribution: config?.attribution,
}
this.onLoaded = config?.onLoaded || nail
}, },
}, },
} }
......
...@@ -7,12 +7,13 @@ ...@@ -7,12 +7,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { defaultLegendConfig } from './config' import { defaultLegendConfig } from './config'
import { useShare } from '@/hooks/page/useShare' import { useShare } from '@/hooks/page/useShare'
import type { MapboxConfig } from '@/components/Map/Mapbox' import { useMapbox } from '@/components/Map/Mapbox/hook'
import { LegendWidget, useLegendWidget } from '@/components/Map/Widgets/Legend' import { LegendWidget, useLegendWidget } from '@/components/Map/Widgets/Legend'
import { SwitchWidget, useSwitchWidget } from '@/components/Map/Widgets/Switch' import { SwitchWidget, useSwitchWidget } from '@/components/Map/Widgets/Switch'
import { TimeBarWidget, formatTime, useTimeBarWidget } from '@/components/Map/Widgets/TimeBar' import { TimeBarWidget, formatTime, useTimeBarWidget } from '@/components/Map/Widgets/TimeBar'
import { ToolBoxWidget, useToolBoxWidget } from '@/components/Map/Widgets/ToolBox' import { ToolBoxWidget, useToolBoxWidget } from '@/components/Map/Widgets/ToolBox'
import { BottomBarWidget, useBottomBarWidget } from '@/components/Map/Widgets/BottomBar' import { BottomBarWidget, useBottomBarWidget } from '@/components/Map/Widgets/BottomBar'
import type { MapboxInstance } from '@/components/Map/Mapbox'
useShare() useShare()
...@@ -23,7 +24,7 @@ ...@@ -23,7 +24,7 @@
// }) // })
// 地图配置 // 地图配置
const config: MapboxConfig = { const [registerMap, { isReady }] = useMapbox({
// 说明: 地图数据来源标注展示 // 说明: 地图数据来源标注展示
attribution: { attribution: {
text: '湖南省气象台', text: '湖南省气象台',
...@@ -33,7 +34,18 @@ ...@@ -33,7 +34,18 @@
// 说明: 根据每个页面的 widget 布局情况,可能需要适当调整地图的中心位置,让界面显示效果更好 // 说明: 根据每个页面的 widget 布局情况,可能需要适当调整地图的中心位置,让界面显示效果更好
center: [111.6, 26.170844], center: [111.6, 26.170844],
}, },
} onLoaded: (map: MapboxInstance, data: Recordable) => {
console.log('✨✨✨ Map Loaded', map, data)
Message.alert(JSON.stringify(data), '地图加载完成')
},
})
watch(
() => isReady.value,
(ready) => {
console.log('[useMapbox] isReady', ready)
},
)
// 顶部时间轴小部件 // 顶部时间轴小部件
const [registerTimeBarWidget, { setTime, getTimeBarValue }] = useTimeBarWidget({ const [registerTimeBarWidget, { setTime, getTimeBarValue }] = useTimeBarWidget({
...@@ -251,7 +263,7 @@ ...@@ -251,7 +263,7 @@
<template> <template>
<view class="page h-100vh bg-white"> <view class="page h-100vh bg-white">
<!-- 地图组件 --> <!-- 地图组件 -->
<Mapbox :config="config" /> <Mapbox @register="registerMap" />
<!-- 地图上方所有小部件 --> <!-- 地图上方所有小部件 -->
<view class="widgets"> <view class="widgets">
......
...@@ -144,7 +144,6 @@ declare module 'vue' { ...@@ -144,7 +144,6 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
Switch: typeof import('./../src/components/Map/Widgets/Switch/src/Switch.vue')['default'] Switch: typeof import('./../src/components/Map/Widgets/Switch/src/Switch.vue')['default']
SwitchControl: typeof import('./../src/components/Map/Widgets/SwitchControl/src/SwitchControl.vue')['default']
ThumbnailPreview: typeof import('./../src/components/ThumbnailPreview/index.vue')['default'] ThumbnailPreview: typeof import('./../src/components/ThumbnailPreview/index.vue')['default']
TimeBar: typeof import('./../src/components/Map/Widgets/TimeBar/src/TimeBar.vue')['default'] TimeBar: typeof import('./../src/components/Map/Widgets/TimeBar/src/TimeBar.vue')['default']
ToolBox: typeof import('./../src/components/Map/Widgets/ToolBox/src/ToolBox.vue')['default'] ToolBox: typeof import('./../src/components/Map/Widgets/ToolBox/src/ToolBox.vue')['default']
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论