/** * Uniapp 事件总线类,用于在应用内进行事件通信 * 支持同步和异步事件触发,可配置是否继续传播事件 * 可配置是否在任意监听器抛出错误后立即停止传播 * 可配置是否在每个监听器执行后调用 defaultAsyncHandler * 如果 allowAsyncHandlerRun 为 true,在每个监听器执行后调用 defaultAsyncHandler * 如果 allowAsyncHandlerRun 为 false,不调用 defaultAsyncHandler * 注意:如果 allowAsyncHandlerRun 为 false,defaultAsyncHandler 不会被调用,也不会等待其 resolve * * @author ZFSun * @version 1.0.0 * @createTime 2023-08-10 */ class EventBus { constructor() { this.events = new Map() this.domBridge = null this.platform = this.detectPlatform() } // 检测平台 detectPlatform() { // #ifdef H5 return 'h5' // #endif // #ifdef MP-WEIXIN return 'weapp' // #endif return 'unknown' } // 设置 DOM 桥接 setDomBridge(bridge) { this.domBridge = bridge bridge.setEventBus(this) } // 监听事件(自动处理平台差异),支持取消 on(eventName, callback, options = { priority: 0 }) { // 如果是 H5 平台且事件名是 DOM 事件,自动创建桥接 if (this.platform === 'h5' && this.isDomEvent(eventName) && this.domBridge) { return this.domBridge.bridgeDomEvent( this.getDomEventName(eventName), eventName, options ) } // 普通 EventBus 监听 if (!this.events.has(eventName)) { this.events.set(eventName, new Set()) } const handler = { callback, options } this.events.get(eventName).add(handler) // 返回取消函数 return () => this.off(eventName, handler) } // 监听多个事件,支持取消 onMany(eventNames = [], callback, options = { priority: 0 }) { // 支持字符串数组或空格分隔的字符串,自动转换为数组 if (typeof eventNames === 'string') { // 支持使用空格分隔事件名 // 支持使用逗号分隔事件名 // 下面代码使用正则表达式,自动处理逗号和空格分隔的字符串 eventNames = eventNames.replace(/\s+/g, ',').split(',') } const removeFns = eventNames.map(eventName => this.on(eventName, callback, options)) return () => removeFns.forEach(fn => fn()) } // 异步触发事件,支持取消传播,并支持在监听处理后调用异步函数 defaultAsyncHandler // 使用方式:await eventBus.emit(eventName, data, defaultAsyncHandler) // defaultAsyncHandler 会在每个监听器执行后被调用,签名为 defaultAsyncHandler(event, handler, handlerResult) // 如果 defaultAsyncHandler 返回 Promise,会等待其 resolve // 如果 allowContinuePropagation 为 false,事件触发后将立即返回,不继续传播 // 如果 stopOnError 为 true,在任意监听器抛出错误后立即停止传播 // 如果 allowAsyncHandlerRun 为 true,在每个监听器执行后调用 defaultAsyncHandler // 如果 allowAsyncHandlerRun 为 false,不调用 defaultAsyncHandler // 注意:如果 allowAsyncHandlerRun 为 false,defaultAsyncHandler 不会被调用,也不会等待其 resolve async emit(eventName, data, defaultAsyncHandler, { allowContinuePropagation = true, stopOnError = false, allowAsyncHandlerRun = true } = {}) { // 先创建事件对象,方便传入 defaultAsyncHandler const event = this.createEvent(eventName, data) // helper to call defaultAsyncHandler only when allowed const callDefaultAsyncHandler = async (evt, handler, handlerResult) => { if (typeof defaultAsyncHandler !== 'function' || !allowAsyncHandlerRun) return try { const res = defaultAsyncHandler(evt, handler, handlerResult) if (res instanceof Promise) { const awaited = await res if (awaited === false) evt.preventDefault() } else { if (res === false) evt.preventDefault() } } catch (err) { console.error(`EventBus defaultAsyncHandler error for ${eventName}:`, err) } } // 如果是 H5 平台且需要触发 DOM 事件 if (this.platform === 'h5' && data && data.triggerDomEvent) { const domResult = this.domBridge.triggerDomEventFromEventBus( this.getDomEventName(eventName), data.detail ) // 调用 defaultAsyncHandler(仅当允许) await callDefaultAsyncHandler(event, null, domResult) // 如果不允许继续传播,则直接返回(根据 defaultPrevented 判断结果) if (!allowContinuePropagation) { return !event.defaultPrevented } // 否则继续走普通 EventBus 处理(fallthrough) } // 普通 EventBus 触发 const handlers = this.events.get(eventName) // 如果没有监听器,执行 defaultAsyncHandler(如果有),并返回结果 if (!handlers) { await callDefaultAsyncHandler(event, null, true) return !event.defaultPrevented } // 如果不允许继续传播,则不执行任何监听器,只调用一次 defaultAsyncHandler(若提供),然后返回 if (!allowContinuePropagation) { await callDefaultAsyncHandler(event, null, true) return !event.defaultPrevented } // 按优先级排序执行 const sortedHandlers = Array.from(handlers).sort((a, b) => { return (b.options?.priority || 0) - (a.options?.priority || 0) }) // 按优先级顺序执行每个监听器 for (const handler of sortedHandlers) { if (event.defaultPrevented) break try { // 支持监听器返回 Promise 或同步值 const result = handler.callback(event) const awaitedResult = result instanceof Promise ? await result : result // 如果回调返回 false,也停止默认行为 if (awaitedResult === false) { event.preventDefault() } // 在每个监听器执行后,如果允许并且提供了 defaultAsyncHandler,就调用并等待它 await callDefaultAsyncHandler(event, handler, awaitedResult) } catch (error) { console.error(`EventBus ${eventName} error:`, error) // 如果全局或该 handler 指定遇错停止传播,则停止 if (stopOnError || handler.options?.stopOnError) { break } // 否则继续下一个 handler } // 一次性监听自动移除 if (handler.options?.once) { this.off(eventName, handler) } } return !event.defaultPrevented } // 移除监听 off(eventName, handler) { const handlers = this.events.get(eventName) if (handlers) { handlers.delete(handler) if (handlers.size === 0) { this.events.delete(eventName) } } } // 移除多个事件监听 offMany(eventNames, handler) { eventNames.forEach(eventName => this.off(eventName, handler)) } // 移除所有监听 offAll(eventName) { this.events.delete(eventName) } // 一次性监听 once(eventName, callback, options = {}) { return this.on(eventName, callback, { ...options, once: true }) } // 前置监听(高优先级) preOn(eventName, callback) { return this.on(eventName, callback, { priority: 100 }) } // 后置监听(低优先级) postOn(eventName, callback) { return this.on(eventName, callback, { priority: -100 }) } // 判断是否是 DOM 事件 isDomEvent(eventName) { const domEvents = ['click', 'touchstart', 'touchend', 'mousedown', 'mouseup'] return domEvents.includes(eventName.toLowerCase()) } // 获取对应的 DOM 事件名 getDomEventName(eventName) { const map = { 'tap': 'click', 'click': 'click', 'touchstart': 'touchstart', 'touchend': 'touchend' } return map[eventName] || eventName } // 创建可取消的事件对象 createEvent(eventName, data) { const event = { type: eventName, data, timestamp: Date.now(), defaultPrevented: false, _isPropagationStopped: false } // 阻止事件继续传播 event.preventDefault = function () { this.defaultPrevented = true } // 停止传播 event.stopPropagation = function () { this._isPropagationStopped = true } event.isPropagationStopped = function () { return this._isPropagationStopped } return event } } export default new EventBus()