lwx-wi-map
lwx-wi-map
基于 OpenLayers 的城市防洪排涝基础地图组件:底图切换、MVT/WMS 业务图层、图例分级渲染、属性面板、过程曲线、时间轴播放、要素高亮与 3D 透视。
核心设计目标:
- 逻辑与界面分离:所有地图逻辑在
MapController/useMapViewer中,内置 UI 只是可选的壳。 - 可定制主题:一套
--wi-map-*CSS 变量,传一个主题色即可派生全套;内置深/浅两套。 - 可按需取用 UI:每块内置 UI 可用
showXxx关闭,或用具名插槽整体替换;也可以完全不要 UI,只拿逻辑。
架构与设计说明见 ARCHITECTURE.md;可运行示例见 example/。
安装与初始化
pnpm add lwx-wi-map
无论用不用内置 UI,都必须先在应用启动时注入宿主运行时依赖:
import { configureMapApi } from 'lwx-wi-map'; // 纯逻辑用 'lwx-wi-map/core'
configureMapApi({
http, // 宿主 HTTP 客户端(需实现 get/post)
getWisession: () => token, // 用于给瓦片 URL 追加鉴权参数,可选
baseUrl: MAIN_URL, // 业务服务基地址,用于解析相对瓦片地址
// 以下可选,用于解耦宿主 iconfont
iconFontFamily: 'ywifont',
getIconGlyph: (name) => '\ue67e', // 图标名 → 字体字形字符
});
内置 UI 入口依赖 element-plus(peerDependency),且不打包 EP 样式,宿主需全局引入 Element Plus 样式。逻辑入口 lwx-wi-map/core 不依赖 Element Plus / ECharts。
业务图层
draw_style.logo用的ywifont字形已随包内置(src/styles/ywiconfont,woff2 内联进style.css),宿主无需自备该字体;只走lwx-wi-map/core时不含该 CSS,需自行引入,或用configureMapApi的iconFontFamily/getIconGlyph指向宿主自己的图标字体。
ol/ol-ext/echarts均为 peerDependency,需宿主自行安装。echarts标记为可选:仅在使用MapChartPanel/ChartDialog或横 / 纵断面分析面板时才需要;只用地图或useMapViewer时可不安装。
ol需 ≥ 10.7.0:MVT 图层默认使用WebGLVectorTileLayer(10.4.0 起提供),图例分档依赖 flat style 的else规则语义(10.7.0 起 WebGL 渲染器才按「前面规则都未命中」处理,低版本会把末档颜色覆盖到所有要素上)。
用法一:完整组件(内置 UI)
<script setup lang="ts">
import { MapView } from 'lwx-wi-map';
import 'lwx-wi-map/style.css';
function onReady(map) {
// 拿到原生 ol Map 实例,可自行叠加 overlay / interaction
}
</script>
<template>
<MapView
group="FLOOD"
:time="currentTime"
:time-layer-ids="['flood_contour_mvt']"
theme-color="#328fff"
theme="dark"
@map-ready="onReady"
@feature-click="(feature, layerName) => {}"
@time-rendered="() => {}"
/>
</template>
Props
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
layers |
LayerItem[] |
— | 外部直接传图层配置;传入后不再按 group 拉取 |
extent |
number[] |
— | 显示范围 [minx, miny, maxx, maxy] |
group |
string |
'' |
业务图层分组,自动调用 /busigis/getLayerListByGroup |
layerTheme |
string |
'' |
图层主题(全局配置 ui.layer_theme_config 中的主题名,如「实时仿真」),优先于 group:按主题图层 id 调 /busigis/getLayerListById;主题内按 group_type 形成两层图层树,最终显示图层以接口返回为准 |
time |
string |
'' |
外部时间 |
timeLayerIds |
string[] |
— | 播放优化:仅这些图层随 time 刷新,其余只加载一次 |
center |
[number, number] |
[104, 35.5] |
初始中心 [lng, lat] |
zoom |
number |
4.5 |
初始缩放 |
base |
BaseMapKey |
'vec' |
初始底图类型 |
stylePlugins |
LayerStylePlugin[] |
— | 图层样式插件,命中即接管该图层渲染 |
highlightUrl |
string |
— | 查询高亮数据接口(如 busigis/getBusiHighLightPdb?query_id=xxx),地图初始化后自动拉取(protobuf),按返回的 otype:oname 命中业务要素并修改其高亮样式 |
highlightQueryId |
string |
— | 查询高亮查询 id(highlightUrl 缺省时拼为 /busigis/getBusiHighLightPdb?query_id=) |
layerPanelWidth |
number |
300 |
图层控制面板宽度(px) |
propChartTarget |
'dialog' | 'bottom' |
'dialog' |
属性面板曲线打开方式:dialog 弹窗查看;bottom 抛出 prop-chart-open 由外部底部曲线面板显示(选中带默认曲线即属性配置 sections.chart 非空的要素时也会自动抛出) |
propPanelWidth |
number |
300 |
属性面板宽度(px) |
showControls |
boolean |
true |
图层控制面板 + 工具栏图层按钮 |
showGroupInput |
boolean |
false |
图层控制面板中的 group 切换输入框 |
showPropPanel |
boolean |
true |
属性面板 + 工具栏属性按钮 |
showHighlightButton |
boolean |
false |
工具栏高亮按钮 |
show3DButton |
boolean |
true |
工具栏 3D 按钮 |
showFullscreenButton |
boolean |
true |
工具栏全屏切换按钮(CSS 全屏,iframe 内可用,ESC 退出) |
showClearMarksButton |
boolean |
true |
工具栏清除临时标记按钮(清除查询高亮与业务高亮状态) |
showBaseSwitcher |
boolean |
true |
图层面板中的底图切换器 |
showTimeSlider |
boolean |
false |
底部时间条 |
autoOpenPropPanel |
boolean |
true |
选中要素后自动展开属性面板 |
themeColor |
string |
— | 主题色(推荐 #RRGGBB,自动派生 hover/active/soft/glow) |
theme |
'dark' | 'light' |
'dark' |
主题模式 |
Emits
| 事件 | 参数 | 说明 |
|---|---|---|
map-ready |
(map) |
OL 实例初始化完成 |
feature-click |
(feature, layerName?) |
点击要素(原始 OL Feature;取消选中时 feature 为 null) |
selection-change |
(selection) |
业务选中变化,{ otype, oname } 或 null(点击与程序化选中都会触发) |
update:time |
(time) |
时间变化(可用 v-model:time) |
time-rendered |
(time) |
随 time 刷新的图层本帧瓦片加载 + 渲染完成 |
fullscreen-change |
(fullscreen) |
全屏状态变化 |
right-panel-change |
(width) |
右侧面板占位宽度变化(供底部停靠区让位) |
prop-chart-open |
(payload: { url, title }) |
propChartTarget = 'bottom' 时抛出:点击属性面板曲线按钮,或选中带默认曲线(属性配置 sections.chart 非空)的要素,外部据此打开/切换底部曲线面板 |
插槽(选择性替换内置 UI)
所有插槽均作用域下发控制器状态;不传时使用内置 UI。
| 插槽 | 作用域 | 说明 |
|---|---|---|
default |
{ map, controller } |
地图覆盖层 |
toolbar |
{ is3D, toggle3D, isHighlighted, triggerHighlight, showLayerList, toggleLayerList, propPanelVisible, togglePropPanel } |
右侧工具条 |
layerPanel |
{ layers, visibleIds, setVisibleIds, group, changeGroup, loading, base, setBase, setLayerOpacity } |
图层面板 |
propPanel |
{ selection, feature, time, config, close } |
属性面板 |
timeSlider |
{ time, setTime } |
时间条 |
用
#layerPanel/#propPanel替换内置面板时,请把layerPanelWidth/propPanelWidth设为你自定义面板的实际宽度,right-panel-change才能给底部停靠区正确的让位宽度。插槽内容由父组件作用域编译,若在其中用 JS 读取主题(而非仅靠 CSS 变量级联),需从插槽作用域获取,或自行传入
themeColor/theme。
示例:只想要地图 + 自己的工具条,不要图层/属性面板:
<MapView
group="FLOOD"
:show-controls="false"
:show-prop-panel="false"
:show-3d-button="false"
>
<template #toolbar="{ toggle3D, is3D }">
<button @click="toggle3D">{{ is3D ? '2D' : '3D' }}</button>
</template>
</MapView>
暴露的方法
const mapRef = ref<InstanceType<typeof MapView>>();
mapRef.value?.getMap(); // 原生 ol Map
mapRef.value?.fitExtent([minx, miny, maxx, maxy], 600);
mapRef.value?.triggerHighLight();
mapRef.value?.reloadLayers('WDM');
mapRef.value?.toggleFullscreen(); // 切换全屏 / 退出全屏
mapRef.value?.clearMarks(); // 清除临时标记(查询高亮 + 业务高亮状态)
mapRef.value?.controller; // 完整控制器:状态 + 命令
用法二:只要逻辑(headless)
<script setup lang="ts">
import { useMapViewer } from 'lwx-wi-map/core';
const { targetRef, controller, map } = useMapViewer({
group: 'FLOOD',
time: () => timeline.value, // 支持 值 / ref / getter
timeLayerIds: ['flood_contour_mvt'],
center: [121.49, 31.24],
zoom: 10,
onEvent: (evt) => {
if (evt.type === 'feature-click') { /* ... */ }
if (evt.type === 'time-rendered') { /* 推进播放下一帧 */ }
},
});
// 命令
controller.setTime('2026-09-12 08:00:00');
controller.setVisibleIds(['flood_contour_mvt']);
controller.setBase('sat');
controller.toggle3D();
controller.fitExtent([...]);
await controller.triggerHighlight();
</script>
<template>
<div style="position: relative; height: 100%">
<div ref="targetRef" style="position: absolute; inset: 0" />
<!-- 你自己的任意 UI,绑定 controller 的状态 -->
<aside v-if="controller.selection.value">
{{ controller.selection.value.otype }} / {{ controller.selection.value.oname }}
</aside>
</div>
</template>
useMapViewer(options) 选项
| 选项 | 类型 | 说明 |
|---|---|---|
layers / extent / group / time / timeLayerIds / center / zoom / base / stylePlugins |
值 | Ref | () => 值 |
响应式来源,变化时自动同步到控制器 |
highlightDimOpacity |
值 | Ref | () => 值 |
高亮置暗倍数(默认 0.25,传 1 不置暗) |
onEvent |
(event: MapViewerEvent) => void |
同 MapView 的事件 |
返回 { targetRef, controller, map }。targetRef 绑定到容器后组件挂载时自动初始化并在地图卸载时销毁。
MapController 状态与命令
状态(均为 Vue ref):map、layers、layersLoading、visibleLayerIds、currentTime、is3D、currentBase、isHighlighted、selection、selectedFeature、selectedLayerName、propViewConfig、group、extent、queryHighlight、queryHighlightLoading、alarmed、alarmPhase。
命令:mount、destroy、getMap、setLayers、setExtent、setGroup、setTime、setTimeLayerIds、setStylePlugins、setVisibleIds、setLayerOpacity、setHighlightDimOpacity、setBase、toggle3D、set3D、reloadLayers、fitExtent、triggerHighlight、setHighlights、setQueryHighlight、setSelection、clearMarks、setAlarms、clearAlarms。
选中态样式:仅对点、线要素生效(面要素不应用)——按 otype:oname 匹配后放大 2 倍(圆点半径 ×2、字体图标字号 ×2、线描边宽度 ×2),统一青色 + 白描边,zIndex 高于高亮;点击空白取消。程序化设置选中用
controller.setSelection({ otype, oname })(直接改selection.value不会触发重绘)。悬停可拾取要素时光标自动变pointer。
查询高亮:
setQueryHighlight({ url })(或{ queryId })拉取/busigis/getBusiHighLightPdb返回的 protobuf otype:oname 命中列表,命中要素由高亮叠加层以高亮色重绘、未命中要素整体压暗(默认 0.25 不透明度,可用highlightDimOpacity调整);clearMarks()清除查询高亮与业务高亮状态。宿主自带高亮数据时(如连通性分析结果)用setHighlights(keys)直接设置命中键,键格式同报警('otype:oname'字符串或{ otype, oname }对象,全量替换,空数组即清除)。
MapController可脱离 Vue 组件单独new并手动mount(el)/destroy()。
图层样式插件
需要整层接管某类图层的渲染(例如把某类图层全部改成自定义样式):
import { Style, Stroke } from 'ol/style';
import type { LayerStylePlugin } from 'lwx-wi-map/core';
const plugins: LayerStylePlugin[] = [
(layer) =>
layer.type === 'TAIFENG'
? (feature) => new Style({ stroke: new Stroke({ color: '#f00', width: 4 }) })
: undefined,
];
// MapView 用 <MapView :style-plugins="plugins" />
useMapViewer({ group: '气象监测', stylePlugins: plugins });
插件优先于基础样式链(legend > draw_style > 默认),命中即接管该图层。
插件 / 类型钩子返回的是 Canvas
Style函数,WebGLVectorTileLayer只接受 flat style, 无法表达任意 JS 样式逻辑,因此被插件或钩子接管的图层会自动回退 Canvas 渲染, 其余 MVT 图层默认走 WebGL(GPU)。两种模式的高亮 / 选中 / 报警行为一致(见下一节)。
MVT 渲染:WebGL(GPU)+ 高亮叠加层
MVT(type 非 WMS)图层默认使用 OpenLayers 的 WebGLVectorTileLayer 渲染,
点 / 线 / 面 / 字体图标 / 图例分档都由 flat style 表达:
| 数据 | GPU 样式 |
|---|---|
| 点 | circle-*(draw_style.pcolor / psize) |
| 字体图标 | draw_style.logo 先在主线程栅格化成图片,再用 icon-src / icon-width / icon-height 绘制 |
| 线 | stroke-*(lcolor / lsize) |
| 面 | fill-*(pcolor)+ stroke-*(lcolor / lsize) |
| 图例 | 按 legend.column 用 Rule.filter 表达式分档(首命中取色,末档兜底) |
图标为什么不用
text-*:WebGL 矢量瓦片的文字由 Worker 里的 OffscreenCanvas 绘制,而 Worker 的self.fonts不包含页面@font-face注册的图标字体(实测self.fonts.size === 0), 走文字通道只会画出后备字形(方块)。因此改为「canvas 栅格化 →icon-src」, 图标字体加载完成后会清空栅格化缓存并按新字体重建 GPU 样式(字体未就绪时画的图不进缓存); 图标贴图解码完成后会通过onIconImageReady通知控制器补一次重绘(不再依赖固定延时)。
图例列必须是数值类型:GPU 路径在瓦片加载时把
legend.column归一化到内部属性__wi_legend_num__<column>(字符串型数值会转成数字,缺失 / 非数值写NaN→ 落到末档兜底色), 与 Canvas 路径Number()的口径一致。该内部属性不会出现在属性面板里。
临时态(选中 / 多选 / 分析高亮 / 业务高亮 / 报警闪烁)不再改写底图样式:每个 MVT 图层额外挂一个
Canvas 叠加层(共用同一个 VectorTileSource,瓦片只请求 / 解析一次),只把命中要素画在最上面。
- 叠加层不参与拾取:点击命中的始终是底层业务图层,高亮图形不会挡住点击; 没有临时态时叠加层直接隐藏,不产生逐帧开销。
- 叠加层按图层收敛显隐:
MvtTileIndex在瓦片加载时记录各图层出现过的 otype,只有可能包含 当前临时态要素的图层才显示叠加层(其余图层不参与样式求值)。要素可能在未加载区域、 或图层是 Canvas 回退时,保守地显示全部叠加层。 - 叠加层统一放在固定层级(业务图层之上、测量 / 绘制图层之下),不按图层顺序与业务图层交错: OL 会给 WebGL 图层按渲染顺序分配画布,一旦中间夹了 Canvas 叠加层,每个 MVT 图层就会各占一个 WebGL context(实测 20 层时有 4 个 context 被浏览器丢弃、图层变黑)。因此排在后面的业务图层 仍可能被高亮覆盖,需要严格控制叠放顺序时请用 WMS / 图层分组,而不是依赖高亮层级。
- 高亮置暗改为「压低底图图层不透明度」(默认 0.25,可用
highlightDimOpacity/setHighlightDimOpacity()运行时调整),叠加层保持原不透明度, 视觉上等价于原来的「命中要素高亮 + 其余置暗」,但不再逐要素改颜色。 - GPU 图层没有逐要素查询接口,拾取按几何自行计算(点顶点 / 点到线段 / 面包含 + 边界),
容差 6 CSS 像素,同级点击按「点 > 线 > 面」取,报警要素仍然优先;
瓦片层级与渲染器使用同一个
source.zDirection(否则小数缩放时拾取层级和屏幕层级会不一致), 瓦片按层级分桶、悬停时只遍历当前层级(及回退层级), 每个瓦片按要素包围盒建一次网格索引并缓存,悬停时只测少量候选要素;wrapX场景下点击坐标先折叠回规范世界,否则世界副本里的点击会全部落空。 - 图层重建 / 销毁时会显式
dispose()WebGL 图层,避免 WebGL context 泄漏。 - 环境不支持 WebGL 时自动整体回退 Canvas 渲染。
内置样式链会保留图层原有图形结构:高亮命中的要素在原图标/圆点基础上换成高亮色,保留字体图标。
属性面板:属性视图配置接口失败(未登录 / 无权限)或该 otype 未配置时,面板会退回展示要素自带的 原始属性(过滤
geometry/ 内部归一化属性等);要素本身没有业务属性时给出一条可读提示, 而不是只显示「未获取到属性配置」。
按图层类型注册样式钩子
地图包不内置任何业务类型样式,由宿主按需注册。适合「只覆盖某类图层的部分要素,其余回退内置样式链」的场景(如台风路径线):
import { registerLayerStyleHook } from 'lwx-wi-map';
import { Style, Stroke } from 'ol/style';
// 只对匹配要素返回样式,返回 undefined 继续走内置样式链
registerLayerStyleHook('MY_TYPE', (feature, layer) => {
if (feature.getGeometry()?.getType() === 'LineString') {
return new Style({ stroke: new Stroke({ color: '#ff0', width: 3 }) });
}
return undefined;
});
// 不再需要时移除
registerLayerStyleHook('MY_TYPE', null);
钩子优先级高于用户插件。stylePlugins 为整层接管,registerLayerStyleHook 适合按要素类型的局部覆盖。
报警闪烁
业务要素报警闪烁(点 / 线 / 面通用):硬闪两相位(亮 / 暗各 500ms),样式链最优先(报警 > 选中 > 高亮 > 基础),报警色默认 #FF3B30,可用 configureMapApi({ alarmStyle: { color } }) 定制。
const controller = mapRef.value?.controller;
controller.setAlarms(['Station:S001', { otype: 'Pipe', oname: 'P002' }]); // 开始闪烁('otype:oname' 或对象)
controller.clearAlarms(); // 停止
controller.alarmed.value; // 是否闪烁中(响应式,可联动外部红点 / 提示音)
行为细节:
- 数据来源由宿主决定(WebSocket 推送 / REST 轮询均可),包内只负责命中与闪烁渲染;命中按要素属性
otype+oname(大小写兼容),与高亮机制同键 - 报警要素所在图层自动置顶:即使该图层藏在图层列表底部、被其他图层盖住,报警时也会提升其 zIndex,清除报警后恢复原层级
- 报警优先拾取:重叠点击时哪怕报警要素被其他要素盖住,也优先点中报警要素;点击报警要素照常走选中流程(属性面板展开),但样式保持闪烁
- 相位由内部定时器驱动(仅在存在报警时运行,清空即停表),无报警时零开销
- 限制:报警要素须位于 MVT 图层;WMS 栅格图层无法逐要素闪烁
- 独立验证闪烁效果:
createAlarmBlinkTest(map)在地图中心放一组点(圆点 + 字体图标)/ 线 / 面测试要素,复用同一套报警样式与节奏,返回清理函数
import { createAlarmBlinkTest } from 'lwx-wi-map';
const stop = createAlarmBlinkTest(map, { interval: 500, size: 24 }); // 中心开始闪烁
stop(); // 清除
用法三:可选装配「功能菜单」
左侧功能菜单不内置在 MapView 中,由使用方按需装配:createFeatureMenu() 创建注册表 → 注册菜单项 → 渲染 MapFeatureMenu(菜单)与 MapFeaturePanel(面板外框)。不注册、不渲染就没有该功能。
<script setup lang="ts">
import { computed } from 'vue';
import { MapView, MapFeatureMenu, MapFeaturePanel, createFeatureMenu, type MapFeatureMenuItem } from 'lwx-wi-map';
const menu = createFeatureMenu();
const items: MapFeatureMenuItem[] = [
{ id: 'home', label: '首页', icon: 'home' },
{ id: 'stats', label: '自定义统计', icon: 'chart-bar', group: '对象统计',
panel: { mode: 'side', width: 380 } }, // 点击展开右侧停靠面板
{ id: 'pipe', label: '管线专题图', icon: 'chart-line', group: '管线专题',
panel: { mode: 'overlay', component: MyOverlay } }, // 覆盖整个地图区域
{ id: 'analysis', label: '数据分析', icon: 'chart-box-outline',
onSelect: (item) => console.log(item.id) }, // 纯动作按钮(无 panel)
];
const unregister = menu.register(items); // 注册;返回反注册函数,可在 onUnmounted 清理
const activePanel = computed(() => menu.activeItem.value?.panel ?? null);
// side:贴菜单右侧停靠;overlay:从菜单右侧铺满地图区域
const dockStyle = computed(() =>
activePanel.value?.mode === 'overlay'
? { left: '156px', right: '0' }
: { left: '156px', width: `${activePanel.value?.width ?? 360}px` },
);
</script>
<template>
<div class="relative h-full">
<MapView group="FLOOD" />
<MapFeatureMenu :menu="menu" :width="148" />
<!-- 面板外框定位由使用方决定;仅在存在激活面板时渲染,避免遮挡地图交互 -->
<div v-if="activePanel" class="absolute top-0 bottom-0" :style="dockStyle">
<MapFeaturePanel :menu="menu" position="static" />
</div>
</div>
</template>
菜单项字段(MapFeatureMenuItem)
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string |
唯一标识(重复注册即覆盖更新) |
label |
string |
显示名 |
icon |
string |
mdi 图标名(不含 mdi: 前缀) |
group |
string |
分组名(同名归入可折叠分组;缺省排在无分组散排区) |
order |
number |
排序权重(小在前;同权重按注册顺序) |
disabled |
boolean |
禁用 |
panel |
MapFeaturePanelConfig |
点击后打开的面板:mode('side' 停靠 / 'overlay' 覆盖地图)、width、title、component、props;缺省时作为纯动作按钮(点击仅高亮选中并收起面板,不打开内容) |
onSelect |
(item) => void |
点击回调(与 panel 可同时使用,先切换面板再回调) |
说明
- 面板外框(标题栏 + 关闭按钮 + 内容区)由
MapFeaturePanel提供;panel.component缺省时只渲染外框占位,具体内容由使用方接入。 MapFeaturePanel传keep-mounted时,面板关闭(最小化)仅v-show隐藏、内容组件不卸载,重开同一面板还原内部状态(查询条件 / 结果等);缺省关闭即卸载。- 注册表命令:
register/unregister/select(id | null)/toggle(id)/close();状态:items/activeId/activeItem。 - 后代组件可用
provideFeatureMenu(menu)/useFeatureMenu()读写注册表(均为纯逻辑,出自lwx-wi-map/core)。 - 菜单项
icon由数据传入,UnoCSS 静态提取不到,宿主需把用到的icon-mdi:xxx加入图标 safelist(参见example/vite.config.ts)。
用法三·一:对象统计一键装配(useObjectStatsMenu)
「自定义统计 + 各 otype 统计」是包内置面板(ObjectStatsPanel,走 /busigis/getSearchOtypeList、searchOtypeValues、searchOtypeValuesPDB、searchOtypeValuesSummary),useObjectStatsMenu 负责注册整组菜单并在分组变化时重拉:
import { useObjectStatsMenu, type MapController } from 'lwx-wi-map';
const analysisProps = shallowReactive<{ controller: MapController | null; layerGroup: string }>({
controller: null, // 地图就绪后由宿主填充
layerGroup: '智慧管网',
});
const unregisterStats = useObjectStatsMenu(menu, {
props: analysisProps, // 面板共享 props(shallowReactive,controller/layerGroup 变化自动跟随)
layerGroup: '智慧管网', // string / Ref<string> / () => string
group: '对象统计', // 菜单分组名,缺省「对象统计」
order: 10, // 排序基准:自定义统计 = order,各 otype 项依次 +1
width: 470, // side 面板宽度,缺省 470
});
onUnmounted(() => unregisterStats());
- 菜单结构:最前为「自定义统计」(面板内选择 otype、自组条件),其后为 otype 列表展开的各「XX统计」(固定条件区)。
- 面板能力:空间范围(全局 / 当前视图 / 矩形框选,矩形走
controller.startRectDraw)、固定过滤 / 自组条件、查询明细表格 + 分页(searchOtypeValues)、汇总柱状图(searchOtypeValuesSummary)、高亮(searchOtypeValuesPDB→controller.setHighlights,改命中要素自身颜色)与行定位(controller.showLocationMarker统一定位)。 - otype 列表按
layerGroup模块级缓存(fetchOtypeStatList),菜单与面板共用一次请求;clearOtypeStatListCache()可强制失效。
主题定制
组件方式
<!-- 内置深色,主色改为绿色 -->
<MapView group="FLOOD" theme-color="#00b96b" />
<!-- 浅色 -->
<MapView group="FLOOD" theme="light" />
<!-- 浅色 + 绿色 -->
<MapView group="FLOOD" theme="light" theme-color="#00b96b" />
独立使用某个组件(如 MapChartPanel)时也可传 themeColor / theme:
<MapChartPanel otype="Station" :oname="id" theme="light" theme-color="#00b96b" />
CSS 变量方式
内置 UI 只使用下列变量,可在任意容器覆盖:
.my-map-container {
--wi-map-primary: #00b96b;
--wi-map-primary-hover: #1fd18a;
--wi-map-primary-active: #009957;
--wi-map-primary-soft: rgba(0, 185, 107, 0.16);
--wi-map-primary-glow: rgba(0, 185, 107, 0.35);
}
theme-color 会在 JS 侧由单个色值自动派生 hover / active / soft / glow,无需手写。
其余语义变量(--wi-map-panel-bg、--wi-map-panel-text、--wi-map-chart-border、--wi-map-btn-bg …)定义在 src/styles/theme.css,默认深色;根元素加 wi-map-light class 切换浅色。自定义组件主题时:
import { useMapThemeBindings } from 'lwx-wi-map';
const { theme, themeClass, themeStyle } = useMapThemeBindings({
mode: () => 'light', // 或继承父级
color: () => '#00b96b',
});
teleport 到
body的弹层(el-dialog/el-popover)无法继承容器 CSS 变量,需把themeClass/themeStyle同样绑到弹层元素上(内置组件已处理)。
内置组件
均从 lwx-wi-map 导出,可单独使用:
| 组件 | 说明 |
|---|---|
MapView |
完整地图(逻辑 + 内置 UI 壳) |
LayerList |
图层树 + 图例 + 透明度 + 底图切换 |
PropPanel |
属性面板(含曲线弹窗入口) |
ChartDialog |
曲线弹窗(teleport) |
MapChartPanel |
过程曲线面板(图表 / 列表、日期区间、多日对比) |
TimeSlider |
时间条 |
MapToolBar / MapToolButton |
工具条容器 / 按钮 |
MapIcon |
内置 mdi 图标 |
MapFeatureMenu |
功能菜单(可选装配,配合 createFeatureMenu 使用) |
MapFeaturePanel |
功能菜单面板外框(side 停靠 / overlay 覆盖地图) |
HorizontalSectionPanel / VerticalSectionPanel |
横 / 纵断面分析面板(依赖 echarts) |
ValveClosingPanel |
爆管关阀分析面板 |
示例
example/ 内是四个可直接运行的示例:
full-ui.vue:全部使用内置 UI(<MapView>),mock 后端custom-ui.vue:完全自定义 UI,只复用useMapViewer()与业务 API,mock 后端bottom-chart.vue:底部曲线模式,mock 后端feature-menu.vue:可选装配功能菜单与分析面板,接真实后端并需配置wisession
pnpm example # http://localhost:5180
底图
import { BASE_MAPS, DEFAULT_BASE_KEY, type BaseMapKey } from 'lwx-wi-map/core';
// BaseMapKey: 'vec' | 'cva' | 'sat' | 'satc' | 'ter'
开发
pnpm build # vite 构建 + vue-tsc 生成 d.ts
pnpm build:js # 仅 JS
pnpm build:types # 仅类型
pnpm dev # watch 构建
pnpm type-check # 源码类型检查
pnpm example # 启动示例(http://localhost:5180)
pnpm example:type-check # 示例类型检查
产物:
dist/index.js+dist/style.css:完整组件入口dist/core.js:纯逻辑入口(不含 Element Plus / ECharts / CSS)
目录结构
src/
├── core/ # 纯逻辑:无 UI、无 Element Plus、无 echarts、无 CSS
│ ├── controller.ts # MapController:图层/底图/时间/拾取/高亮/3D
│ ├── useMapViewer.ts # headless composable
│ ├── featureMenu.ts # 功能菜单注册表(可选装配)
│ ├── baseTiles.ts # 底图配置(唯一事实来源)
│ ├── styles.ts # 图层样式函数 + 插件机制
│ ├── picking.ts # 要素拾取(含 3D 矩阵补偿)
│ ├── tiles.ts # 瓦片加载等待
│ └── types.ts
├── components/ # UI 层:只依赖 core + CSS 变量
│ ├── MapFeatureMenu.vue # 左侧功能菜单(可选装配)
│ └── MapFeaturePanel.vue # 功能菜单面板外框(可选装配)
├── api/ # adapter(宿主注入)+ busigis 业务接口
├── styles/theme.css # --wi-map-* 主题变量
├── styles/ywiconfont/ # 内置 ywifont 字体图标(@font-face + 图标名→码点,随 style.css 发布)
└── theme.ts # 主题上下文 / 变量派生
Related Packages
Version History
| Version | Released | License |
|---|---|---|
| 0.10.0 | MIT | |
| 0.9.2 | MIT | |
| 0.9.1 | MIT | |
| 0.9.0 | MIT | |
| 0.8.0 | MIT | |
| 0.7.0 | MIT | |
| 0.6.0 | MIT | |
| 0.5.0 | MIT | |
| 0.4.0 | MIT | |
| 0.3.0 | MIT | |
| 0.2.0 | MIT | |
| 0.1.0 | MIT |