feat(core): 增强核心事件通讯总线,增强跨组件交互能力

This commit is contained in:
2025-10-30 16:57:33 +08:00
parent 66bf2504e0
commit 0fabacd71c
8 changed files with 1191 additions and 750 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,133 @@
/**
* Uniapp DOM 事件桥接类,用于将 DOM 事件桥接到 EventBus
* 支持将 DOM 事件转换为 EventBus 事件,可配置是否阻止默认行为和停止传播
*
* @author ZFSun <lauer3912@gmail.com>
* @version 1.0.0
* @createTime 2023-08-10
*/
class DomEventBridge {
constructor() {
this.eventBus = null
this.domListeners = new Map()
}
// 设置 EventBus 实例
setEventBus(eventBus) {
this.eventBus = eventBus
}
// 桥接 DOM 事件到 EventBus
bridgeDomEvent(domEventName, eventBusEventName, options = {}) {
const handler = (domEvent) => {
if (!this.eventBus) return
// 创建 EventBus 兼容的事件对象
const eventBusEvent = this.createEventBusEvent(domEvent, eventBusEventName)
// 触发 EventBus 事件
const shouldContinue = this.eventBus.emit(eventBusEventName, eventBusEvent)
// 根据 EventBus 处理结果决定是否阻止 DOM 事件默认行为
if (!shouldContinue && options.preventDefault) {
domEvent.preventDefault()
}
// 根据 EventBus 处理结果决定是否停止 DOM 事件传播
if (!shouldContinue && options.stopPropagation) {
domEvent.stopPropagation()
}
}
// 保存监听器引用以便清理
const listenerKey = `${domEventName}-${eventBusEventName}`
this.domListeners.set(listenerKey, handler)
// 添加 DOM 事件监听
// #ifdef H5
document.addEventListener(domEventName, handler, options)
// #endif
return () => this.unbridgeDomEvent(domEventName, eventBusEventName)
}
// 创建 EventBus 兼容的事件对象
createEventBusEvent(domEvent, eventBusEventName) {
return {
type: eventBusEventName,
domEvent: domEvent,
originalEvent: domEvent,
target: domEvent.target,
currentTarget: domEvent.currentTarget,
timestamp: Date.now(),
// EventBus 的取消方法
preventDefault: function () {
this._preventDefault = true
},
stopPropagation: function () {
this._stopPropagation = true
},
// DOM 事件的代理方法
stopImmediatePropagation: function () {
domEvent.stopImmediatePropagation()
},
get isDefaultPrevented() {
return this._preventDefault || false
},
get isPropagationStopped() {
return this._stopPropagation || false
}
}
}
// 取消桥接
unbridgeDomEvent(domEventName, eventBusEventName) {
const listenerKey = `${domEventName}-${eventBusEventName}`
const handler = this.domListeners.get(listenerKey)
if (handler) {
// #ifdef H5
document.removeEventListener(domEventName, handler)
// #endif
this.domListeners.delete(listenerKey)
}
}
// 从 EventBus 触发 DOM 事件
triggerDomEventFromEventBus(domEventName, detail = {}) {
// #ifdef H5
const event = new CustomEvent(domEventName, {
bubbles: true,
cancelable: true,
detail: detail
})
document.dispatchEvent(event)
return !event.defaultPrevented
// #endif
// #ifdef MP - WEIXIN
// 小程序环境不支持 DOM 事件
return true
// #endif
}
// 清理所有桥接
destroy() {
for (const [listenerKey, handler] of this.domListeners) {
const [domEventName] = listenerKey.split('-')
// #ifdef H5
document.removeEventListener(domEventName, handler)
// #endif
}
this.domListeners.clear()
}
}
export default new DomEventBridge()

260
common/js/event-bus.js Normal file
View File

@@ -0,0 +1,260 @@
/**
* Uniapp 事件总线类,用于在应用内进行事件通信
* 支持同步和异步事件触发,可配置是否继续传播事件
* 可配置是否在任意监听器抛出错误后立即停止传播
* 可配置是否在每个监听器执行后调用 defaultAsyncHandler
* 如果 allowAsyncHandlerRun 为 true在每个监听器执行后调用 defaultAsyncHandler
* 如果 allowAsyncHandlerRun 为 false不调用 defaultAsyncHandler
* 注意:如果 allowAsyncHandlerRun 为 falsedefaultAsyncHandler 不会被调用,也不会等待其 resolve
*
* @author ZFSun <lauer3912@gmail.com>
* @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 为 falsedefaultAsyncHandler 不会被调用,也不会等待其 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()