feat(core): 增强核心事件通讯总线,增强跨组件交互能力
This commit is contained in:
1221
common/js/diy.js
1221
common/js/diy.js
File diff suppressed because it is too large
Load Diff
133
common/js/dom-event-bridge.js
Normal file
133
common/js/dom-event-bridge.js
Normal 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
260
common/js/event-bus.js
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* Uniapp 事件总线类,用于在应用内进行事件通信
|
||||
* 支持同步和异步事件触发,可配置是否继续传播事件
|
||||
* 可配置是否在任意监听器抛出错误后立即停止传播
|
||||
* 可配置是否在每个监听器执行后调用 defaultAsyncHandler
|
||||
* 如果 allowAsyncHandlerRun 为 true,在每个监听器执行后调用 defaultAsyncHandler
|
||||
* 如果 allowAsyncHandlerRun 为 false,不调用 defaultAsyncHandler
|
||||
* 注意:如果 allowAsyncHandlerRun 为 false,defaultAsyncHandler 不会被调用,也不会等待其 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 为 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()
|
||||
Reference in New Issue
Block a user