ToolJet Map 组件深度解析:属性、事件、组件特定动作与源码级实现原理
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
本文以 ToolJet 的 Map(地图)组件为主线,完整覆盖其属性配置、事件体系、组件特定动作(CSA)、暴露变量与样式控制,并结合frontend/src/AppBuilder/Widgets/Map/下的实际源码,剖析其底层基于@react-google-maps/api的实现机制、动态值(fx)解析流程以及自托管部署时GOOGLE_MAPS_API_KEY环境变量的配置方式。读完本文,你将能够独立配置并编程化控制 Map 组件,在业务应用中展示商家、门店或用户位置,并通过事件与变量实现地图交互闭环。
一、组件定位与典型场景
Map 组件用于在应用中显示一张地图,支持展示或选择单个/多个地理位置。文档中给出的典型场景包括:展示企业、门店或餐厅的位置,展示用户在地图上的位置,以及允许终端用户与地图界面交互、点击选取兴趣点。
从源码结构看,Map 是 ToolJet 前端 WidgetManager 中注册的一个标准组件。其注册配置文件 map.js 声明了组件元数据,其中默认尺寸为宽 16 列、高 420 像素(defaultSize: { width: 16, height: 420 }),组件标识为Map,描述为 "Display map locations"。组件的实际渲染逻辑位于 Map.jsx。
二、自托管部署前提:配置 Google Maps API Key
原文档明确提示:若使用 ToolJet 自托管(self-hosted)版本,必须将 Google Maps API key 配置为环境变量,否则地图无法正常加载。
这一点在源码中得到印证:Map.jsx 中地图脚本通过如下方式加载:
<LoadScript googleMapsApiKey={window.public_config.GOOGLE_MAPS_API_KEY} libraries={['places'}}> <GoogleMap ... /> </LoadScript>即组件依赖window.public_config.GOOGLE_MAPS_API_KEY这一前端全局配置项来初始化 Google Maps JS SDK,并同时加载places库(Places 库是"地点搜索"能力的前提,详见第四节)。环境变量GOOGLE_MAPS_API_KEY的官方说明见 env-vars.md 中的 "Google maps configuration (optional)" 章节:
| variable | description |
|---|---|
| GOOGLE_MAPS_API_KEY | Google maps API key |
三、Properties 属性详解
Map 组件提供 5 个专属属性。以下在继承原文档说明的基础上,结合 map.js 中的配置补充了默认值与校验规则。
| 属性 | 说明 | 期望取值 |
|---|---|---|
| Initial location | 应用初始加载时的默认位置。 | 包含latitude和longitude键值对的对象。例:{{ {"lat": 40.7128, "lng": -73.935242} }}。 |
| Default markers | 地图上应显示的标记点数量(即初始标记点集合)。 | 包含坐标的对象数组。例:{{ [{"lat": 40.7128, "lng": -73.935242}, {"lat": 40.7128, "lng": -73.935242}] }}。 |
| Polygon points | 使用给定坐标在地图上创建多边形。 | 包含坐标的对象数组。例:{{ [{"lat": 40.7128, "lng": -73.935242}, {"lat": 40.7128, "lng": -73.935242}] }}。 |
| Add new markers | 点击地图时在对应位置添加新标记。 | 默认On。切换为off可禁用"点击加标记"行为。点击fx可动态设置{{true}}/{{false}}。 |
| Search for places | 启用后在地图左上角显示地点搜索框。 | 默认On。切换为off可禁用搜索框。点击fx可动态设置{{true}}/{{false}}。 |
源码层面有三个值得注意的细节:
- 前三个属性均为
code类型编辑器。map.js 中initialLocation、defaultMarkers、polygonPoints均配置为type: 'code'、mode: 'javascript',校验 schema 为union,即"对象或对象数组"(schemas: [{ type: 'array', element: { type: 'object' } }, { type: 'object' }])。这意味着这三个字段不仅接受静态坐标对象,还支持 fx 动态表达式,例如{{ JSON.parse(query1.data) }}之类的查询结果。 - 两个开关属性的默认值均为
true(addNewMarkers与canSearch的defaultValue均为true),与文档"默认 On"的说明一致。 - 渲染时的兜底逻辑:Map.jsx 中,
initialLocation缺失时回退到{ lat: 0, lng: 0 },addNewMarkers与canSearch缺失时回退到false,polygonPoints与defaultMarkers缺失时回退到空数组。
多边形渲染的附加条件:Map.jsx 中,仅当polygonPoints.length > 1时才会渲染<Polygon>,且样式是硬编码的——描边色#4d72fa、线宽 2、填充色#4d72fa、填充不透明度 0.5,因此多边形外观不支持属性级自定义。
四、Events 事件体系
| 事件名 | 触发时机 |
|---|---|
| On bounds change | 地图可视边界(bounding area)发生变化后触发,此时bounds暴露变量已更新。 |
| On create marker | 向地图添加新标记点时触发。 |
| On marker click | 用户点击地图上任意标记点时触发。 |
| On polygon click | 用户点击地图上的多边形时触发。 |
这四个事件在 map.js 的events段注册,并与 Map.jsx 中的四处fireEvent调用一一对应:
onBoundsChange:由 handleBoundsChange 触发,绑定在地图的onDragEnd上(即用户拖拽地图结束)。该函数通过gmap.getBounds()取出northEast/southWest两个角点,连同新的center一并写入暴露变量,最后才fireEvent('onBoundsChange')——这保证了事件处理函数内读取bounds、center时拿到的一定是新值。onCreateMarker:由 handleMapClick 触发。若canAddNewMarkers为假则直接返回;否则从点击事件的e.latLng中取出经纬度,追加进markers状态、更新暴露变量,再触发事件。onMarkerClick:由 handleMarkerClick 触发,先将被点击的标记写入selectedMarker暴露变量再触发事件。onPolygonClick:直接绑定在<Polygon>的onClick上。
关于 ToolJet 全部可用 Actions 的更多信息,可参考 Actions 参考文档,以及在 RunJS 查询中执行动作的用法见 run-action-from-runjs.md。
五、Component Specific Actions(CSA)
Map 组件当前对外暴露一个组件特定动作,可在任意事件处理函数中通过 RunJS 查询编程化调用:
| 动作 | 说明 | 调用方式 |
|---|---|---|
| setLocation | 通过经度、纬度参数在地图上设置标记点位置。 | 在 RunJS 查询中执行:component.map1.setLocation(40.7128, -73.935242)。 |
动作定义见 map.js:handle为setLocation,接收lat(Latitude)与lng(Longitude)两个参数。
其实现机制在 Map.jsx 的初始化useEffect中:组件挂载时通过setExposedVariables将一个闭包函数注入到组件的暴露变量对象上:
const exposedVariables = { setLocation: async function (lat, lng) { if (lat && lng) setMapCenter(resolveWidgetFieldValue({ lat, lng })); }, center: addMapUrlToJson(resolvedCenter), markers: defaultMarkers, };也就是说,setLocation本质上是一个被挂到components.<id>命名空间下的异步函数:它经resolveWidgetFieldValue解析参数(因此参数位置同样支持 fx 表达式),再调用setMapCenter把地图中心平移到目标坐标。
六、Exposed Variables 暴露变量
暴露变量用于从组件中读取运行时数据,完整清单如下:
| 变量 | 说明 | 访问方式 |
|---|---|---|
| center | 持有纬度、经度及 Google Maps URL 三个值。 | |
center.lat | Map 组件上标记点的纬度值。 | {{components.map1.center.lat}} |
center.lng | Map 组件上标记点的经度值。 | {{components.map1.center.lng}} |
center.googleMapUrl | 中心标记点位置的 Google Maps URL。 | {{components.map1.center.googleMapUrl}} |
| markers | 仅在启用add new markers属性后持有值;每个标记为含lat、lng键的对象。 | {{components.map1.markers[1].lat}} |
| selectedMarker | 用户所选标记点构成的对象。 | |
| bounds | 由西南角与东北角两点构成的矩形(地图可视范围)。 | |
| bounds.northEast | 矩形东北角的经纬度。 | {{components.map1.bounds.northEast.lat}}或{{components.map1.bounds.northEast.lng}} |
| bounds.southWest | 矩形西南角的经纬度。 | {{components.map1.bounds.southWest.lat}}或{{components.map1.bounds.southWest.lng}} |
源码印证了各变量的写入时机与结构:
- center / googleMapUrl:addMapUrlToJson 在中心坐标对象上追加一个可直接打开的 URL 字段:
https://www.google.com/maps/@?api=1&map_action=map¢er=<lat>,<lng>。center在地图onLoad、拖拽结束(handleBoundsChange)以及initialLocation变化时都会刷新(见 Map.jsx)。 - markers:初始值来自
defaultMarkers属性(Map.jsx 中监听defaultMarkers变化同步状态);用户点击地图新增标记后,handleMapClick会即时将最新数组写入markers暴露变量。 - selectedMarker:
handleMarkerClick中执行setExposedVariable('selectedMarker', markers[index]),即当前点击索引对应的完整标记对象。 - bounds:
handleBoundsChange中由getBounds().getNorthEast().toJSON()与getSouthWest().toJSON()构造,故northEast/southWest各含lat、lng两个子字段。
一个实用的组合用法:在onBoundsChange事件处理中读取components.map1.bounds,即可在用户平移/缩放地图后,用边界坐标作为参数去执行一条查询(例如只加载当前可视范围内的门店数据)。
七、动态值解析与交互实现原理
7.1 fx 动态值的统一解析入口
Map 属性中所有{{ ... }}表达式最终都经过 resolveWidgetFieldValue 解析:
export function resolveWidgetFieldValue(prop, _default = [], customResolveObjects = {}) { const widgetFieldValue = prop; try { const state = {}; // getCurrentState(); return resolveReferences(widgetFieldValue, state, _default, customResolveObjects); } catch (err) { console.log(err); } return widgetFieldValue; }在 Map.jsx 中可以看到该函数的三类典型用法:解析initialLocation得到地图初始中心(第 48 行useState(() => resolveWidgetFieldValue(center)));解析styles.visibility得到可见性布尔值(第 41 行);解析setLocation的入参坐标(第 150 行)。解析失败时回退原值并打印日志,保证组件不会因表达式异常而白屏。
7.2 地图交互细节
从 Map.jsx 的渲染结构看:
- 地图实例:
<GoogleMap>固定zoom={12},关闭街景与地图类型控件(streetViewControl: false、mapTypeControl: false),允许拖拽(draggable: true)。 - 地点搜索:仅当
canSearch为真时渲染<Autocomplete>(第 196-204 行),用户选定搜索结果后onPlaceChanged会把地图中心移动到所选地点的geometry.location,并顺带触发一次handleBoundsChange(刷新bounds/center并触发onBoundsChange事件)。搜索框的占位文本走 i18n 翻译键globals.search。 - 标记点:
markers数组逐项渲染为<Marker>,支持可选的label字段,点击回调携带索引以便写入selectedMarker。 - 深色模式:当
darkMode为真时应用 styles.js 中的darkModeStyles(Google Maps 主题化样式集);styles.scss 则隐藏了 Places 自动补全的默认面板(.pac-container { display: none !important; }),因为组件使用的是onPlaceChanged回调而非下拉面板交互。 - 编辑态装饰:编辑器画布中地图上叠加了一个中心大头针图标(
assets/images/icons/marker.svg)作为组件可视化标记,仅用于设计态辨识。
八、通用能力:Devices 与 Styles
Devices(设备可见性)
| 属性 | 说明 | 期望取值 |
|---|---|---|
| Show on desktop | 组件在桌面视图中可见。 | 可用开关按钮设置,或点击fx输入逻辑表达式动态控制。 |
| Show on mobile | 组件在移动视图中可见。 | 可用开关按钮设置,或点击fx输入逻辑表达式动态控制。 |
源码中对应mapConfig.others段的两个 toggle 属性 showOnDesktop / showOnMobile;注意组件定义里二者的出厂默认值分别为{{true}}与{{false}}(map.js),即新建的 Map 组件默认只在桌面端显示。
General — Tooltip
Tooltip 用于在用户鼠标悬停组件时展示附加说明信息:一旦设置了 Tooltip 值,悬停时即显示指定字符串。
Styles(样式)
| 属性 | 说明 | 期望取值 |
|---|---|---|
| Visibility | 开关组件的可见性。 | 点击旁边的fx按钮可编程修改。设为{{false}}时,应用发布后组件不可见。默认{{true}}。 |
| Disable | 默认off;切换为on时锁定组件、使其不可交互。 | 也可通过fx按钮编程设置。设为{{true}}后组件被锁定不可用。默认{{false}}。 |
| Box shadow | 提供 X、Y、Blur、Spread 与 Color 值,为组件添加阴影效果。 | 也可通过fx按钮编程设置。例:{{"x": 0, "y": 0, "blur": 0, "spread": 0, "color": "#000000"}}。 |
源码中visibility直接决定外层容器display: none(Map.jsx 中style={{ height, display: parsedWidgetVisibility ? '' : 'none', boxShadow: styles.boxShadow }});disabledState则写入data-disabled属性供编辑器渲染锁定态;boxShadow原样作用于容器内联样式。
九、小结与延伸阅读
Map 组件通过"属性(位置/标记/多边形/开关)+ 事件(边界/标记/多边形交互)+ CSA(setLocation)+ 暴露变量(center/markers/selectedMarker/bounds)"四层机制构成完整的地图交互闭环,其底层由@react-google-maps/api驱动,所有动态值统一经resolveWidgetFieldValue解析。自托管环境下务必先配置GOOGLE_MAPS_API_KEY环境变量。相关路径一览:
- 组件文档源文件:docs/docs/widgets/map.md
- 组件注册与默认值:frontend/src/AppBuilder/WidgetManager/widgets/map.js
- 组件渲染实现:frontend/src/AppBuilder/Widgets/Map/Map.jsx
- 深色模式样式:frontend/src/AppBuilder/Widgets/Map/styles.js
- 环境变量说明:docs/docs/setup/env-vars.md
- Actions 参考目录:docs/docs/actions/
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考