news 2026/10/15 15:21:08

uniapp全局事件机制详解:跨页面通信与事件总线的正确姿势

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
uniapp全局事件机制详解:跨页面通信与事件总线的正确姿势

做了几年 uniapp 开发,要是有人问我最容易被忽略又最好用的能力是什么,我大概率会说是全局事件。页面之间要传参、组件要联动、跨页面刷新数据,很多场景大家第一反应是 Vuex 或者 url 带参,但用多了你会发现,全局事件这套东西用好了,代码能轻一大截。当然用不好也能让你 debug 到怀疑人生。这篇就专门把 uniapp 的全局事件机制从头到尾捋一遍,从 API 细节到实际应用场景,再到我踩过的那些坑,一次性讲透。

开门见山,uniapp 全局事件的核心是一组挂载在 uni 对象上的方法:uni.$emit、uni.$on、uni.$once、uni.$off。这四个方法构成了一个轻量级的事件发布订阅机制,让你可以在任意页面、任意组件里发起一个事件,然后让所有关心这件事的地方都能收到通知。它跟 Vue 组件里的 $emit 最大的区别在于作用域:组件 $emit 只能让父组件听到,全局事件是让整个应用都能听到。适合谁用?写过一段时间 uniapp、开始被跨页面通信折磨的人,或者想从 Vuex 大而全的状态管理里解放出来、追求轻量解耦的人,这篇都会对你有用。

1. 全局事件到底解决了什么问题

1.1 跨页面通信的三种姿势与选型逻辑

先说场景。你做一个电商应用,用户在商品详情页收藏了一个商品,返回列表页,列表页的收藏状态要跟着变;用户在设置页改了主题色,首页要立即生效;用户登录成功之后,个人中心页和购物车页都要刷新数据。这些场景有一个共性:触发动作的页面和目标页面不是父子关系,甚至不是同级关系,它们可能隔了好几层导航,也可能一个还不在页面栈里。

针对这种跨页面通信,uniapp 里常见的有三种方案:url 带参数、Vuex 状态管理、全局事件。url 带参适合单向的、简单的数据传递,比如从列表页跳到详情页带个 id,但反向传递就麻烦了,还能被用户返回操作打断。Vuex 适合需要长期保持、多处引用的全局状态,比如用户信息、购物车数据,但如果你只是为了“通知某处刷新一下”这种动作,引入 Vuex 就有点杀鸡用牛刀。全局事件则适合那种“只通知、不带大量数据、不需要持久化”的场景,本质上是解耦了事件的发出者和接收者。

我一般是这么判断的:数据要存下来慢慢用,走 Vuex;数据是一次性的、用完就丢,走 url 参数;数据不重要但动作要触发,走全局事件。实际项目中,全局事件用的频率远比你想象的高,尤其是拿到一个已经写了很多业务、但通信方式乱七八糟的老项目时,用全局事件梳理一遍,代码能清爽很多。

1.2 全局事件的名字和回调函数:不被约束的灵活性

全局事件这套机制本身没有太多约束,它不像 Vue 组件事件需要 defineEmits 声明,也不像 Vuex 需要提前定义 state 和 mutation。你在任意一个地方 uni.$emit('someEvent'),在任意另一个地方 uni.$on('someEvent', callback) 就能接住。这种灵活性是它最大的优点,也是最容易出问题的地方。

因为没有约束,所以事件名的规范就全靠自觉。我见过一个项目里有人写 'refresh',有人写 'refreshData',还有人写 'refresh_data',表面上看是三个事件,实际上是同一个意图,结果就是某个页面注册了 'refresh',另一个页面发的是 'refreshData',永远接不到。后面我会专门讲事件名怎么取才不容易撞车,这里先记住一个原则:全局事件的事件名是整个应用的公共约定,一定要在项目里形成统一规范。

2. 核心 API 逐个拆解:$emit / $on / $once / $off

2.1 uni.$emit:把消息广播出去

uni.$emit 的职责最简单,就是触发一个事件,把数据带出去。它的完整签名是 uni.$emit(eventName, ...args),第一个参数是事件名,后面的参数可以任意多个,都会透传给回调函数。

// 在商品列表页触发 uni.$emit('product:collectChanged', { productId: 1001, isCollected: true })

接收端可以接一个参数,也可以接多个参数,甚至可以接对象展开。按我个人的习惯,如果数据量超过两个字段,直接用对象,可读性更好,后面加字段也不影响。注意这里 uni.$emit 是同步触发的,也就是说 emit 之后,所有注册了该事件的回调函数会立即执行,不是异步的。这个特性后面排查问题时会用到。

2.2 uni.$on:注册监听的关键细节

uni.$on(eventName, callback) 是注册监听,事件触发时执行回调。这里有个特别提示:官方文档里说,回调函数内的 this 指向的是当前组件实例。但实际项目中经常踩坑,因为如果你在回调里写了箭头函数,this 捕获的是定义时的上下文;如果你写普通函数,this 又是 uni 对象或者其它东西,跟页面实例经常对不上号。

我强烈建议在回调里少依赖 this,直接把需要的数据通过参数传进来,或者用 getCurrentPages() 拿到页面实例再操作。如果实在要用 this,就在 onLoad 里先把 this 存起来,比如 const vm = this,然后在回调里用 vm.xxx 访问。

export default { data() { return { isCollected: false } }, onLoad() { // 必须用箭头函数,或者外部存 this,否则 this 指向会乱 this.collectListener = (payload) => { this.isCollected = payload.isCollected } uni.$on('product:collectChanged', this.collectListener) } }

2.3 uni.$once:只触发一次的监听器

uni.$once(eventName, callback) 和 $on 的区别只有一个:回调函数被触发一次之后,会自动移除监听。适合什么场景呢?一次性初始化。比如某个页面跳转前需要等待一个异步配置加载完成,或者一个临时弹窗只关心用户第一次进入时的状态。

// 只监听一次,避免重复执行 uni.$once('config:loaded', (config) => { this.theme = config.theme })

这里有个容易忽略的细节:$once 注册的监听,如果一直没被触发,它会一直存在。所以即使它是“一次性”的,你也要在页面卸载时手动 $off 掉,否则当前页面已经销毁了,监听还在内存里,等事件真正触发时,回调会去操作一个已经不存在的页面实例,轻则报警告,重则引发变量访问错误。

2.4 uni.$off:移除监听的两种姿势

uni.$off 有两种主要用法:uni.$off(eventName, callback) 和 uni.$off(eventName)。前者只移除指定的那个回调,后者移除该事件名下的所有回调。还有一种极端情况,uni.$off() 不传任何参数,会移除所有事件的所有回调,这个要非常小心,因为它会把别的页面注册的监听也全部清掉。

// 只移除当前页面注册的那个回调(推荐) uni.$off('product:collectChanged', this.collectListener) // 移除该事件的所有回调(谨慎使用) uni.$off('product:collectChanged') // 清空所有事件(真的别乱用) uni.$off()

我见过不少初学者在 onUnload 里写 uni.$off('product:collectChanged'),不带回调函数。如果这个页面只有一个监听器,那没问题;如果有两个不同回调,就会被全部清掉,另一个业务逻辑就断了。所以最稳妥的写法永远是:把注册时的回调函数保存下来,移除时传同一个引用。

3. 实操篇:四类高频应用场景的完整写法

3.1 应用级配置变更的实时同步

假设你的应用有个主题色设置,用户在设置页切换了主题色,所有页面都要立即用新颜色。这个场景如果不用全局事件,每个页面都要在 onShow 里重新读一遍配置,非常啰嗦。用了全局事件,逻辑就变成:设置页保存配置后 emit 一个通知,其它页面收到通知后更新本地状态。

// 设置页 methods: { changeTheme(color) { this.theme = color uni.setStorageSync('themeColor', color) // 广播主题色变更 uni.$emit('app:themeChanged', color) } }
// 任意需要跟随主题的页面 onLoad() { this.themeListener = (color) => { this.themeColor = color // 这里还可以直接操作 DOM 或者更新样式变量 uni.setNavigationBarColor && uni.setNavigationBarColor({ frontColor: '#ffffff', backgroundColor: color }) } uni.$on('app:themeChanged', this.themeListener) }, onUnload() { uni.$off('app:themeChanged', this.themeListener) }

这个案例的典型特征是:事件名里的目标页面可能同时有好几个,每个页面只关心自己需要的那部分数据。全局事件天然支持一对多广播,emit 一次,所有监听者都收到。注意回调里做了 DOM 操作或者平台 API 调用时,要用 uni.xxx 或者条件编译区分平台,否则在 H5 上可能报错。

3.2 登录状态的全局通知与多页面联动

登录状态是最典型的全局状态。但用户登录成功之后,除了把 token 存起来,通常还要刷新一堆页面的状态:个人中心的头像昵称、购物车的会员价、首页的签到入口。如果用 Vuex,你得改 store 里的 userInfo,所有依赖它的 computed 会自动更新,看起来没什么问题。但如果某个页面根本没有用 Vuex 里的字段,只是需要在登录后去重新拉一次接口,那全局事件反而更直观。

我的做法是:登录成功后,把用户信息写进 Vuex 和 storage,同时 emit 一个 'user:loginSuccess' 事件。需要响应登录动作的页面,各自监听、各自处理。

// 登录页 methods: { async doLogin() { const res = await this.$api.login(this.form) uni.setStorageSync('token', res.token) uni.setStorageSync('userInfo', res.userInfo) // 通知全局:登录成功 uni.$emit('user:loginSuccess', res.userInfo) uni.navigateBack() } }
// 购物车页 onLoad() { this.loginListener = () => { this.fetchCartList() // 重新拉取购物车,因为会员价可能变了 } uni.$on('user:loginSuccess', this.loginListener) }, onShow() { // 这里不能只靠 onShow,因为登录后返回当前页时 onShow 会触发, // 但如果是别的页面跳转到登录页再回来,当前页的 onShow 并不一定知道是登录成功 }

这里有个经验:不要过度依赖 onShow 来实现数据刷新,因为你不知道页面重新显示的原因是什么,可能是登录成功回来,也可能是用户从后台切回来,这两种情况下要做的处理往往不一样。全局事件的价值就在于给了一个明确的语义:不是因为页面可见了,而是因为登录这件事发生了。

3.3 购物车角标更新的轻量方案

电商项目里购物车角标是一个高频变更的数据。加购、删除、修改数量、下单成功,每个地方都要更新角标。常规做法是全局用一个购物车总数变量,或者每次操作后都重新查一次购物车接口。但如果只是改角标,把整个购物车数据拉回来太重了。

我的简化方案是:定义一个全局事件 'cart:totalChanged',任何改动购物车数量的地方,操作成功后 emit 一个最新的 total 过去。需要显示角标的页面收到后直接 setBadge 或更新 tabBar 角标。

// 加购成功 addToCart(product) { // ... 业务逻辑 const newTotal = this.getCartTotal() uni.$emit('cart:totalChanged', newTotal) }
// tabBar 所在的页面(通常是首页或购物车页) onLoad() { this.cartListener = (total) => { if (total > 0) { uni.setTabBarBadge({ index: 2, text: total > 99 ? '99+' : String(total) }) } else { uni.removeTabBarBadge({ index: 2 }) } } uni.$on('cart:totalChanged', this.cartListener) }

这个做法比“每个操作后各自调用 setTabBarBadge”要好得多。因为逻辑被收敛到了一个监听器里,你只需要改这一处,所有页面收到通知后统一处理,误差概率小很多。但也因为逻辑被收敛了,你得保证事件名的唯一性和触发的及时性,否则角标更新会乱。

3.4 列表刷新与筛选条件联动

还有一个非常高频的场景:列表页 A 有一个筛选条件,用户跳转到筛选页 B 设置完条件后回到 A,A 要带着新条件重新拉取列表。传统的做法是 A 在 onShow 里去查存储的筛选条件,然后决定要不要重新请求。这么做有个问题:如果用户只是切走了又切回来,根本不需要重新拉数据,你也查了一遍。

全局事件就能做得很精确。筛选页保存条件时 emit 一个 'list:filterChanged',列表页收到后再执行拉取。这样你的列表页只管“收到通知就拉数据”,完全不用关心是哪个页面触发它拉数据的。

// 筛选页 methods: { applyFilter() { // 保存筛选条件到本地存储 uni.setStorageSync('currentFilter', this.selectedFilter) // 通知列表页 uni.$emit('list:filterChanged', this.selectedFilter) uni.navigateBack() } }
// 列表页 onLoad() { this.filterListener = (filter) => { this.filter = filter this.pageNum = 1 this.fetchList(true) // 重置分页并拉取 } uni.$on('list:filterChanged', this.filterListener) }, onUnload() { uni.$off('list:filterChanged', this.filterListener) }

4. 进阶:全局事件的底层逻辑与内存管理

4.1 全局事件的运行机制与事件表

要真正用好全局事件,还是得理解它的底层实现。uniapp 的全局事件机制其实是在应用维度维护了一个事件与回调的映射表。$on 就是往表里追加一个回调,$emit 就是根据事件名把对应的回调列表取出来挨个执行,$off 就是从表里把某一个回调或整条记录移除。

这个机制跟 Node.js 的 EventEmitter、浏览器里自己实现的发布订阅模式本质是一回事。理解了这一点,很多现象就能解释了:为什么 $emit 是同步的?因为它是遍历回调列表逐个调用,不是放进队列异步消费。为什么多次 $on 同一个事件同一个回调,触发时会执行多次?因为回调列表里有两份引用。为什么回调里报错会影响其它监听者?因为同步遍历时抛异常会中断整个循环。

4.2 内存泄漏是怎么发生的

先看代码:

// 页面 A onLoad() { uni.$on('someEvent', () => { console.log('执行了') }) } // 没有 onUnload 里 $off

这段代码在页面 A 销毁之后,由于监听器还被全局事件表引用着,页面 A 实例不能被垃圾回收。如果这个页面反复进出,每次都会新增一个监听器,事件触发时会执行所有历史残留的回调,造成重复执行和内存泄漏。

一个实际案例:某项目首页有个轮播图,进入时注册了一个全局事件来更新图片列表。当时的代码只在 onLoad 里 $on,没在 onUnload 里 $off。结果用户每次从其它页面返回首页,首页其实是在页面栈里复用并不会重新 onLoad,但用户每进入一次那个触发页面,就 $emit 一次,首页的监听器实际上是第一次 onLoad 时注册的那一个,好像也没问题。但如果首页被销毁之后再次进入,就会在旧的残留监听器之上又多注册一个,这时候 $emit 一次,回调执行两遍,bug 就是这么来的。

4.3 生命周期内安全注册与注销的模板

我后来把全局事件的注册注销统一封装成了一个可复用的模式,配合页面生命周期使用。核心就是保证每个 $on 都对应一个 $off,且 $off 一定发生在页面 onUnload 时。

export default { data() { return { // 这里存监听器引用 _eventHandlers: [] } }, // 简单的生命周期钩子 onLoad(options) { this._registerGlobalEvents() }, onUnload() { this._unregisterGlobalEvents() }, methods: { _registerGlobalEvents() { this._eventHandlers = [] const handler1 = (payload) => { this.handleSomething(payload) } uni.$on('eventA', handler1) this._eventHandlers.push(['eventA', handler1]) const handler2 = (payload) => { this.handleAnother(payload) } uni.$on('eventB', handler2) this._eventHandlers.push(['eventB', handler2]) }, _unregisterGlobalEvents() { if (Array.isArray(this._eventHandlers)) { this._eventHandlers.forEach(([eventName, handler]) => { uni.$off(eventName, handler) }) } this._eventHandlers = [] } } }

这样不管页面里注册多少个事件,注销时统一遍历一把,漏掉的概率极低。而且这个模式还能配合复用:如果你有多个页面都要处理同一种事件,可以把这段逻辑提取成 mixin,全局注册同一个事件名,各自在回调里执行自己的方法。

5. 常见问题与排查技巧实录

5.1 事件不触发的三大原因

这是我被问得最多的问题:“我明明 $emit 了,为什么那边没反应?”大部分情况是下面三种之一。

第一种,事件名不一致。这听起来很蠢,但实际项目里太常见了。比如在一个文件里写的是 'order:paySuccess',另一个文件里写的是 'order:pay-success',或者 'order:paidSuccess'。肉眼看着像,但 JS 字符串匹配精确到字符,一个横杠一个冒号的差异都会导致失配。排查方法很简单:全局搜事件名,把 $emit 和 $on 的名字对齐。有时候是因为复制的代码里带了个隐藏的不可见字符,这种最头疼。

第二种,监听注册的时间晚于事件触发。全局事件不会记住历史,$on 只能收到注册之后发生的 $emit。如果你在页面 onLoad 里 $on,但触发方在更早的时机就 $emit 了(比如由某个页面在 onLoad 里同步触发,而这两个页面是跳转关系,先触发后跳转,后跳转的页面才注册),那就会错过。解决方案是:如果确定事件可能在页面加载前发生,把 $on 放在 App.vue 的 onLaunch 里,或者在页面 onLoad 里先判断一下数据是否已就绪,而不是傻等事件。

第三种,页面被销毁后未注销,然后又重新注册导致回调逻辑错乱。这种表面上表现成“事件不触发”,实际是触发了但回调里的逻辑因为依赖了错误的页面实例而报错。开着控制台看一眼有没有红色报错,一般就能定位。

5.2 重复触发的经典场景

还有一个高频问题:同一个事件触发后,回调执行了两次甚至多次。最常见的原因就是 4.2 节说的重复注册。页面每次 onLoad 都会 $on 一次,如果页面没有被 unload,那么在页面栈中停留期间,它是不会重复 onLoad 的。但如果你使用了小程序的某些特殊页面形态,比如 tabBar 页面切换,或者用了自定义的页面复用逻辑,情况就会复杂。

另一个原因是 $emit 会被多个途径触发。比如加购事件,用户点了加购按钮,代码里调了一次 emit,但按钮可能被绑定了一次点击,又被某些全局插件自动触发了一次。这种要在 emit 的地方加日志排查。

排查时我一般直接打日志:

// 触发方 uni.$emit('cart:totalChanged', total) console.log('[emit] cart:totalChanged', total) // 接收方 uni.$on('cart:totalChanged', (total) => { console.log('[receive] cart:totalChanged', total) })

两行日志一对,就能看清楚事件到底触发了几次、有没有被重复注册、是触发方多发还是接收方多收。等确认无误再删掉日志。

5.3 特定平台上的行为差异

uniapp 是跨端框架,全局事件在 App、小程序、H5 上的基础行为基本一致,但细节有区别。

在 App 端,如果你的应用使用多窗口(比如某些平台上的半屏弹窗能力),全局事件的监听器可能会分散在不同 window 上,事件能不能跨 window 传递,跟具体渲染引擎有关。实际开发中我建议尽量保持全局事件只在页面生命周期内注册注销,避开多 window 边界问题。

在 H5 端,要小心页面刷新之后全局事件表会被重置。也就是说,如果你在 A 页面刷新了浏览器,那么之前在其它页面注册过的监听器全部丢失。如果你的应用有 H5 版本,并且依赖全局事件实现跨页面通信,要考虑刷新后重新初始化的逻辑,最好在 App.vue 的 onLaunch 里做一个全局事件的基础初始化,把必要的持久化监听挂上去。

在小程序端,有些平台对 uni.setTabBarBadge 这类 API 支持有差异,接收方回调里调用平台相关 API 时要注意条件编译。

另外还有一个跟 uniapp 版本相关的点:早版本的 HBuilderX 里,uni.$off 存在一些小问题,比如移除不存在的回调会静默失败但不会报错。升级到较新的版本后,行为会更标准。如果遇到诡异的事件问题,先看一眼基础库和编译器版本。

6. 高频问题速查与个人经验总结

6.1 全局事件常见问题速查表

现象可能原因排查方法
触发后无反应事件名不一致、监听未注册、监听已销毁全局搜索事件名、打印 emit/on 日志
回调执行多次重复注册未清理、emit 被多次触发检查 onLoad/onUnload 配对、打印计数日志
回调内 this 丢失普通函数 this 指向错误、箭头函数捕获上下文不对外部存 vm、回调中使用参数而非 this
页面 Destroyed 后回调仍在跑未 $off、事件长期持有页面引用封装统一注册注销、onUnload 里清空
切换 tab 后事件失效tabBar 页面复用机制、listener 未挂载在 tab 页 onShow 里重新注册并去重
H5 刷新后事件丢失全局事件表重置、持久监听未重建App.vue onLaunch 里初始化基础监听

6.2 我对全局事件使用的几条原则

用到现在,我总结了一套自己的使用原则,在这里分享给大家。

第一,事件名必须带模块前缀。比如 'cart:totalChanged'、'user:loginSuccess'、'order:paySuccess',用冒号或斜杠分隔模块和动作。这样一是可读性好,二是降低撞名概率,三是全局搜索时能快速定位。

第二,一个页面注册多少事件,必须能数出来。也就是说每个 $on 都要有明确的注销时机和注销方式。我甚至见过有人在项目里写了一个全局事件的注册中心,用一个 JS 模块统一管理所有事件名、参数结构和回调职责,页面里只调用,不直接 $on。这种方式适合团队项目,但对独立开发者来说略重。

第三,不要在全局事件回调里放太重的逻辑。全局事件是同步遍历执行的,一个回调里做个复杂计算,会阻塞其它回调执行,严重时影响页面渲染。如果真的需要耗时操作,在回调里拆成异步处理,或者用 setTimeout 延后。

第四,能用局部通信就别用全局事件。同页面内组件间的通信,优先用 props / $emit / provide / inject,全局事件是跨页面、跨组件的“最后手段”。无脑用全局事件只会让你的代码难以追踪。

6.3 和 Vuex 的协同定位

最后说说全局事件和 Vuex 怎么配合。我个人的取值是:数据共享用 Vuex,动作通知用全局事件。比如登录后用户信息放 Vuex,因为很多地方要读;但“登录成功”这个动作本身,适合用全局事件广播,因为不同页面要做的后续动作各不相同,而且还可能是页面销毁重进之后才响应。

再比如说购物车列表数据放 Vuex,但“购物车数量变化”这个信号用全局事件,因为有的页面只需要更新角标,不需要拉全量购物车数据。如果强行把所有东西都放 Vuex,mutation 会越写越杂,action 越写越厚,最后 store 变成一个大杂烩。

我有一个比较好的实践:在 Vuex 的 mutation 里主动 emit 相关事件。这样可以保证所有“用 Vuex 完成的状态变更”都能自动通知到关心这个状态变化的页面,全局事件和 Vuex 的配合就非常顺滑。

// store.js mutations: { setUserInfo(state, userInfo) { state.userInfo = userInfo uni.$emit('user:infoChanged', userInfo) }, setCartTotal(state, total) { state.cartTotal = total uni.$emit('cart:totalChanged', total) } }

用这个模式,页面就只管在 onLoad 里 $on、在 onUnload 里 $off,不用关心 Vuex 的 action 和 mutation 细节。数据层的变化会以事件的形式自然地触达所有需要感知的页面。

最后再分享一个小技巧:如果你在调试时怀疑某个全局事件有 bug,不用翻页面代码,可以直接在浏览器控制台(H5 端)执行 uni.$emit 来手动触发一次,看看页面有没有反应。小程序端的调试工具里,也可以通过在某个生命周期里打断点来手动触发。这个方法能帮你快速判断问题是出在触发方还是接收方。全局事件这个东西,理解了事件表模型之后其实很简单,但真正用好它,靠的是对项目结构和页面生命周期的把握。希望这篇能帮你少踩一些我踩过的坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 15:20:50

GJB软件测试报告实战:从模板填空到可验证证据链

简介:本资源为符合中国军用标准(GJB)的软件测试报告规范化模板,面向军工软件测试工程师、质量保证人员及需通过GJB认证的项目团队,解决测试报告结构不统一、内容缺项、不符合国军标审查要求等实际问题。模板严格依据GJ…

作者头像 李华
网站建设 2026/10/15 15:20:38

AI工具,DeepSeek Harness(dsh)安装说明,腾讯 WorkBuddy

DeepSeek Harness(dsh)安装说明 本说明根据本地「AI工具」文件夹内容整理,配图按数字顺序排列,图片名称即对应步骤说明。 一、安装 dsh 需要先安装 Node.js(自带 npm),然后在终端执行全局安装…

作者头像 李华
网站建设 2026/10/15 15:20:30

Python生成PDF任务表单:ReportLab与AcroForm实战指南

从“Python生成PDF任务表单”这个需求出发,我想大多数人都经历过这种场景:每天早上在Word里敲任务名称、负责人、截止日期、优先级,调一下表格线的粗细,再小心翼翼转成PDF发给相关人员,尚未到中午又来了五六条新任务&a…

作者头像 李华
网站建设 2026/10/15 15:20:17

串口与蓝牙通信原理、透传方案及联调避坑指南

1. 串口和蓝牙,到底是两个什么物种做嵌入式、物联网或者智能硬件开发的朋友,对串口和蓝牙肯定不陌生。串口全称叫通用异步收发传输器,行业里习惯叫UART,它是单片机、传感器、路由器、工业设备之间最古老也最通用的通信方式之一。蓝…

作者头像 李华
网站建设 2026/10/15 15:20:16

Cassie双足机器人MPC控制:从倒立摆到落脚点规划

如果你接触过腿足机器人控制,那一定绕不开 Cassie 这个名字。这台外形像鸵鸟的双足平台能跑、能跳、能上下楼梯,还能在户外草地上稳定行走。2018 年那篇关于它反馈控制的论文,是很多人入门双足机器人模型预测控制(MPC)…

作者头像 李华