共计 9570 个字符,预计需要花费 24 分钟才能阅读完成。
🌈个人主页:前端青山
🔥系列专栏:Vue 篇
🔖 人终将被年少不可得之物困其一生
依旧 青山, 本期给大家带来 Vue 篇专栏内容:Vue- 依赖注入 - 中央事件总线
目录
中央事件总线使用
依赖注入使用
总结
中央事件总线
依赖注入
结言
大家好,依旧青山,
最近呢也随着需求的变更调优,加载数字孪生地图的缓慢,要将原有 vue3+Ts 数据大屏子菜单整合到一个地图环境下(注:无需加载其余地图场景, 同一地图环境下切换不同菜单),也就是主页面及子菜单调用一次地图环境即可,页面很好集合前嵌套,但是不同页面对地图的操作该如何呢?
那么我首先做的就是封装一个公共的地图调用方法,以组件形式引入所有子菜单实现跨组件通信!
以数字孪生地图为例
import {ElLoading} from 'element-plus'
import mapJson from '@/utils/tjbhJson';
import textArr from '@/utils/textJson';
import cloudRenderer from "51superapi"
import {ref, onMounted, onBeforeUnmount,reactive,watchEffect} from "vue"
// 引入前缀路径
// 封装 51 地图函数
const app = new cloudRenderer("mapDiv");
export default function (){
let loadingInstance: any; // 在更宽泛的作用域定
const prefixUrl = import.meta.env.VITE_APP_BASE_API || '';
// 配置 51 地图参数
const startRenderConfig = reactive({"url": "http://192.168.1.20:8080", //[必须] 云渲染服务地址; 8889: 固定端口
"order": "123456", //[必须] 渲染口令; 在云渲染客户端上获得
"resolution": [window.innerWidth> 1920? 4096 : window.innerWidth,window.innerHeight >1080? 1209 : window.innerHeight], //[可选] 设置渲染场景像素分辨率
"nodestyle": `width:${window.innerWidth> 1920? 4096 : window.innerWidth};height:${window.innerHeight>1080? 1209 : window.innerHeight};position:absolute;top:0px;left:0px;bottom:0px;right:0px;margin:auto;`, //[可选] 设置渲染场景容器 DOM 节点样式, 与设置渲染场景像素分辨率配对使用
"keyboard": "keyboardnofn", //[可选] 初始建盘事件, 开启 wasd 方向键 [选项: keyboard/keyboardnofn; 详见注册键盘事件]
"setlogmode": true, //[可选] 开启 / 关闭 SuperAPI 调用日志, 默认 false
})
// 设置初始分辨率
startRenderConfig.resolution = [window.innerWidth> 1920 ? 4096 : window.innerWidth,
window.innerHeight > 1080 ? 1209 : window.innerHeight,
];
// 围绕中心旋转
let jsonData = {"time": 50, // 相机旋转一周所需要的时间, (单位: 秒)
"direction": "stop" //clockwise: 顺时针; anticlockwise: 逆时针; stop: 停止旋转
}
// 添加区域轮廓
let jsondata2 = {
"id": "range_id",
"coord_type": 0, // 坐标类型(0: 经纬度坐标, 1:cad 坐标)
"cad_mapkey": "", //CAD 基准点 Key 值, 项目中约定"coord_z": 0, // 高度(单位: 米)"coord_z_type": 0, // 坐标高度类型(0: 相对 3D 世界表面;1: 相对 3D 世界地面;2: 相对 3D 世界海拔; 注:cad 坐标无效)"type":"loop_line", // 样式类型; 注①"color":"ffffff", // 轮廓颜色(HEXA 颜色值)"range_height": 60, // 围栏高度(单位: 米)"stroke_weight": 10, // 底部轮廓线宽度(单位: 米; 注: 区域中含有内环"inner_points"时无效)"fill_area":"none", // 底部区域填充类型; 注②"geojson": mapJson, //geojson 数据; 注③
}
// 添加 3d 文字信息与区域轮廓
const pushAllCovering = () => {app.SuperAPI("Add3DText", textArr, (status: any) => {console.log(status); // 成功、失败回调
});
// 添加区域轮廓
app.SuperAPI('AddGeoRange', jsondata2).then((_back: any) => {})
}
// 初始地图视角
const Camejsondata = {"coord_type": 0, // 坐标类型(0: 经纬度坐标, 1:cad 坐标)
"cad_mapkey": "", //CAD 基准点 Key 值, 项目中约定"coord_z":"2.06", // 海拔高度(单位: 米)"center_coord":"117.689178,39.01527", // 中心点的坐标 lng,lat"arm_distance": 3000, // 镜头距中心点距离(单位: 米)"pitch": 30, // 镜头俯仰角(5~89)"yaw": 70, // 镜头偏航角(0 正北, 0~359)"fly": true //true: 飞行动画(有一个短暂飞行动画, 并按照 arm_distance,pitch,yaw 设置镜头);
//false: 立刻跳转过去(瞬移)
}
// 设置渲染质量
let jsonDate ={"quality": "epic" //low: 低; medium: 中; high: 高; epic: 超高;}
// 地图事件注册函数
const myHandleResponseFunction = (data: string) => {const jsonObject = typeof data === "object" ? JSON.parse(JSON.stringify(data)) : JSON.parse(data);
switch (jsonObject.func_name) {
case "APIAlready":
app.SuperAPI("RemoveAllCovering", {covering_type: "all", // 覆盖物类型, 详见下表})
.then((_back: any) => {console.log(_back);
});
pushAllCovering(); // 添加区域轮廓
// 设置镜头绕场景中心点旋转
app.SuperAPI("SetCameraRotate", jsonData, (e: any) => {})
// 设置当前场景镜头视界
app.SuperAPI("SetCameraInfo", Camejsondata, (status: any) => {})
app.SuperAPI("SetRenderQuality", jsonDate, (status:any) => {console.log(status,'设置渲染质量'); // 成功、失败回调
})
loadingInstance.close();
break;
case 'OnPOIClick':
const coord = jsonObject.args.coord;
const poiId = jsonObject.args.id;
console.log(poiId,"poiId")
break;
}
return data;
}
const myStartRender = async () => {
try {
// 设置初始分辨率
startRenderConfig.resolution = [window.innerWidth> 1920 ? 4096 : window.innerWidth,
window.innerHeight > 1080 ? 1209 : window.innerHeight,
];
await app.startRender(startRenderConfig).then((el: any) => {
loadingInstance = ElLoading.service({ // 赋值给外部变量
lock: true,
text: '地图加载中',
background: 'rgba(0, 0, 0, 0.7)',
});
// 事件注册;事件监听处理器函数, 接收所有从云渲染返回的事件, 数据等信息
app.RegisterCloudResponse(myHandleResponseFunction);
})
} catch (error) {console.error("error:", error)
}
}
const SuperAPI = () => {
// 先删除全部覆盖物 覆盖物类型, 详见下表
app.SuperAPI("RemoveAllCovering", { "covering_type": "poi"}, (status: any) => {console.log(status); // 成功、失败回调
})
}
// 监听窗口大小变化
watchEffect(() => {
startRenderConfig.resolution = [window.innerWidth> 1920 ? 4096 : window.innerWidth,
window.innerHeight > 1080 ? 1209 : window.innerHeight,
];
});
return {app, startRenderConfig, myStartRender, myHandleResponseFunction,prefixUrl,loadingInstance, SuperAPI}
}
把公共地图渲染部分封装为一个 ts 文件,并暴露出 myStartRender
函数方便在主页面 onMounted
函数中调用并渲染地图,依次执行即可,那么大家可以看到还暴露出一个 app
进行全局调用,是因为这个数字孪生地图的操作都要以 app.(地图操作 Api)
的形式调用
最终我们在页面中删除公共部分, 只需引入公共函数即可!
那么随之而来问题也就来了,当地图出现 poi 点的时候,我们点击对应的 poi 点肯定要实现不同的事件,我们现在所封装的 app 暴露出来可以进行打点操作, 点击 poi 点的操作是由地图函数内部执行
// 地图事件注册函数
const myHandleResponseFunction = (data: string) => {const jsonObject = typeof data === "object" ? JSON.parse(JSON.stringify(data)) : JSON.parse(data);
switch (jsonObject.func_name) {
case 'OnPOIClick':
const coord = jsonObject.args.coord;
const poiId = jsonObject.args.id;
console.log(poiId,"poiId" 点击 poi 点所获得的 id 及经纬度)
break;
}
return data;
}
在没有整合之前调用的时候是在当前页面的地图函数下执行, 请看下方
// 地图事件注册函数
const myHandleResponseFunction = (data: string) => {const jsonObject = typeof data === "object" ? JSON.parse(JSON.stringify(data)) : JSON.parse(data);
switch (jsonObject.func_name) {
case 'OnPOIClick':
const coord = jsonObject.args.coord;
const poiId = jsonObject.args.id;
handlePOIClick(poiId, coord);
break;
}
return data;
}
// 处理自定义 POI Label 点击事件的函数
const handlePOIClick = (poiId: string, coord: string) => {const [type, id] = poiId.split('_'); // 分割前缀和 ID
switch (type) { // 假设 id 格式为 "type_ID",通过前缀区分类型
case 'ggwhcs':
// 公共文化场所
handelGgwhcs(id);
break;
case 'lyjq':
// 旅游景区
handelLyjq(id);
break;
default:
console.log(` 未识别的 POI 类型: ${poiId}`);
break;
}
}
那么现在我们封装成一个公共函数, 且渲染地图只在主页面调用, 就要想办法将函数内部的 poiId 和 coord
作为参数暴露出去,方便我们每个子页面调用执行不同的操作, 这里我就想到了 vue 的中央事件总线和依赖注入!
Vue3
提供了多种机制来支持组件间的通信,包括中央事件总线和依赖注入。选择哪种方式取决于具体的应用场景和需求
中央事件总线使用
在处理地图的 poi 点点击事件时, 我们可以先使用中央事件总线来执行我们组件不同页面点击 poi 点的处理逻辑,
首先, 在 utils
文件夹下创建一个 EventBus.ts
文件
// 封装中央事件总线
class EventBus {private events: Record = {};
on(event: string, callback: Function) {if (!this.events[event]) {this.events[event] = [];}
this.events[event].push(callback);
}
off(event: string, callback: Function) {if (!this.events[event]) return;
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
emit(event: string, ...args: any[]) {if (!this.events[event]) return;
this.events[event].forEach(callback => callback(...args));
}
}
const eventBus = new EventBus();
export default eventBus;
在 main.ts
中创建一个全局的事件总线
app.config.globalProperties.$bus = {}; // 直接在全局属性中创建事件总线
然后再封装的内部地图函数 poi 点击事件时进行发送事件
case 'OnPOIClick':
const coord = jsonObject.args.coord;
const poiId = jsonObject.args.id;
console.log(poiId,"poiId")
eventBus.emit('poi-click', poiId, coord); // 发送事件
break;
然后再主页面和各个子页面引入 eventBus
, 在onMounted
和onBeforeUnmount
监听和移出事件总线
onMounted(() => {nextTick(() => {eventBus.on('poi-click', handlePOIClick);
//handlePOIClick 为 poi 点击事件
init();// 初始化函数})
});
onBeforeUnmount(() => {
// 移出监听
eventBus.off('poi-click', handlePOIClick);
})
这时, 不管是我们的主页面, 还是子菜单, 都可以在切换的时候对应页面的 poi 点进行不同的处理逻辑了
依赖注入使用
单个菜单调用地图不同服务的事情解决了,那子菜单和主页面或子菜单和子菜单之间还有通信的复杂操作呢
比如在主页面的 Echarts 图表中,柱状图列出了 A
页面和 B
页面的统计数据,当我点击不同的柱状图时要切换到当前菜单,并直接选中状态及地图出现对应的操作,这时,基于这种复杂的操作我们可以使用 依赖注入
在主页面先通过 ref
绑定对应组件, 并引入 provide
提供依赖
然后在我们的图表组件页面中注入依赖
let setNames: any = inject("setNames")
当我们点击对应的 echarts 图表时
zgrwczqktance.value.on('click', (params: any) => {nextTick(() => {setNames(params.name)// 传入对应 name
})
});
那么我们子菜单页面肯定是要通过传入的 name 来执行不同的地图操作或展示详情等逻辑 …
const setName = (name: any) => {
const mappings:any = {"菜单一": [1, '菜单一'],
"菜单二": [3, '菜单二'],
"菜单三": [6, '菜单三'],
"菜单四": [11, '菜单四'],
"菜单五": [7, '菜单五'],
};
const [id, description] = mappings[name] || [];
if (id !== undefined) {(name === "菜单一" || name === "菜单二" || name === "菜单四" || name === "菜单五" || name === "菜单三")
? abreastClicks(id, description)
: abreastClick(id, description);
}
};
然后我们把这个方法通过 defineExpose
给暴露出去
defineExpose({setName})
最后在我们的主页面通过 ref
所绑定实例再取到依赖注入传入的参数和暴露的内部方法来进行通信啦
let disasterGeneralData = ref()//ref 绑定实例
const setNames = (name: any) => {disasterGeneralData.value.setName(name)// 子菜单内部的 setName 方法(已暴露)
}
总结
中央事件总线
依赖注入
-
优点:
-
更好的组织性和可维护性,因为依赖关系是显式的。
-
适用于需要在多个组件间共享数据和服务的情况。
-
支持树状结构中的组件通信,无需直接父子关系。
-
-
缺点:
结言
原文地址: 依赖注入 中央事件总线:Vue 3 组件通信新玩法