需求
最近在写一个POS收银系统,就是便利店收银员使用的那种
网上找个图片给大家参考一下:
分析
主界面可以按快捷键唤起很多功能,大多是打开一个对话框。然后对话框界面有对应的快捷键,如果有个快捷键跟主界面的快捷键冲突了咋办?不用想,肯定是触发当前界面的快捷键事件,主界面的不触发。也就是打开对话框的时候,首页注册的快捷键事件得卸载,关闭对话框的时候,事件得重置。
技术方案
主入口注册快捷键事件,不同组件(文件)下注册event bus对应的key code事件,由主入口统一emit。
问题
那么问题来了。卸载的时候需要对应事件函数的引用,然后这些事件都写在不同组件(文件)下进行注册的,在a组件中拿不到b组件注册事件函数的引用啊。难不成我还要一个个导出、导入?不行太麻烦了,天无绝人之路…… 好在我发现mitt有个all属性是个Map对象 记录着所有事件的函数
开发
那么就开搞吧……先写一个最简单的
const shortcutKeysEventBusInstance = mitt()
export const installEventBus = () => {
document.addEventListener('keydown', (e: KeyboardEvent) => {
console.log('keydown', e, e.code)
shortcutKeysEventBusInstance.emit(e.code, e)
})
}
export const useShortcutKeysEventBus = (type: string, callback: ICallback) => {
const handle = (e: any) => {
e.preventDefault()
callback()
}
const install = () => {
shortcutKeysEventBusInstance.on(type, handle)
}
const unInstall = () => {
shortcutKeysEventBusInstance.off(type, handle)
}
onBeforeUnmount(unInstall)
return {
install,
unInstall
}
}
麻烦
现在有个麻烦,就是每次触发一个快捷键事件,得卸载掉所有事件,关闭对话框后,得恢复所有事件。
下面我统一封装了一下,事件函数调用会卸载所有事件,如果返回一个promise 不论成功或者失败,结束后都会恢复所有事件
import mitt, { type Handler } from 'mitt'
import { cloneDeep } from 'lodash-es'
const shortcutKeysEventBusInstance = mitt()
export const installEventBus = () => {
document.addEventListener('keydown', (e: KeyboardEvent) => {
console.log('keydown', e, e.code)
shortcutKeysEventBusInstance.emit(e.code, e)
})
}
interface IOption {
install?: boolean
}
const toggleEventBus = () => {
console.log('toggleEventBus')
const lastMap = cloneDeep(shortcutKeysEventBusInstance.all)
shortcutKeysEventBusInstance.all.forEach(function (handleList, type) {
handleList.forEach((handle) => {
shortcutKeysEventBusInstance.off(type, handle as Handler)
})
})
return () => {
console.log('finally')
lastMap.forEach(function (handleList, type) {
handleList.forEach((handle) => {
shortcutKeysEventBusInstance.on(type, handle as Handler)
})
})
}
}
export type ICallback = () => any
// 注册单个快捷键
export const useShortcutKeysEventBus = (
type: string,
callback: ICallback,
option: IOption = { install: true }
) => {
const handle = (e: any) => {
e.preventDefault()
const callbackResult = callback()
if (callbackResult instanceof Promise) {
const activateEventBus = toggleEventBus()
callbackResult.finally(activateEventBus)
}
}
const install = () => {
shortcutKeysEventBusInstance.on(type, handle)
}
option.install && install()
const unInstall = () => {
shortcutKeysEventBusInstance.off(type, handle)
}
onBeforeUnmount(unInstall)
return {
install,
unInstall
}
}
批量注册快捷键事件
export const useShortcutKeysEventBusByMap = (
map: Map<string, ICallback | ICallback[]>,
option: IOption = { install: true }
) => {
const handle = (callback: ICallback) => (e: any) => {
e.preventDefault()
const callbackResult = callback()
if (callbackResult instanceof Promise) {
const activateEventBus = toggleEventBus()
callbackResult.finally(activateEventBus)
}
}
const getEventCallbackMap = () => {
const eventCallbackMap = new Map<string, ReturnType<typeof handle>[]>()
map.forEach(function (value, type) {
if (Array.isArray(value)) {
value.forEach(function (fn) {
const eventCallback = handle(fn)
if (!eventCallbackMap.has(type)) {
eventCallbackMap.set(type, [eventCallback])
} else {
eventCallbackMap.set(type, eventCallbackMap.get(type)!.concat(eventCallback))
}
})
} else {
const eventCallback = handle(value)
eventCallbackMap.set(type, [eventCallback])
}
})
return eventCallbackMap
}
const eventCallbackMap = getEventCallbackMap()
const install = () => {
eventCallbackMap.forEach((eventCallbackList, type) => {
eventCallbackList.forEach((eventCallback) => {
shortcutKeysEventBusInstance.on(type, eventCallback)
})
})
}
option.install && install()
const unInstall = () => {
eventCallbackMap.forEach((eventCallbackList, type) => {
eventCallbackList.forEach((eventCallback) => {
shortcutKeysEventBusInstance.off(type, eventCallback)
})
})
}
onBeforeUnmount(unInstall)
return {
install,
unInstall
}
}
使用
import { useShortcutKeysEventBus, useShortcutKeysEventBusByMap } from '../shortcutKeysEventBus'
import { getPromise } from '@/utils'
let { promise, resolve } = getPromise()
const { install: installShortcutKeys, unInstall: unInstallShortcutKeys } =
useShortcutKeysEventBusByMap(
new Map([
['F1', () => changTab('F1')],
['F2', () => changTab('F2')]
]),
{
install: false
}
)
const customerLoginModalVisible = ref<boolean>(false)
const handleCancel = (e: MouseEvent) => {
console.log(e)
customerLoginModalVisible.value = false
unInstallShortcutKeys() //卸载当前对话框的快捷键
resolve(null) //恢复所有事件
// 重新创建一个新的promise
const { promise: newPromise, resolve: newResolve } = getPromise()
promise = newPromise
resolve = newResolve
}
const handleCustomerLogin = () => {
console.log('customerLogin')
customerLoginModalVisible.value = true
installShortcutKeys() //注册当前对话框的快捷键
return promise
}
useShortcutKeysEventBus('KeyL', handleCustomerLogin)
getPromise
如果不清楚上面为什么这样使用promise也可以看下面的例子,或者看我另外一篇文章了解getPromise
同上
import { useShortcutKeysEventBusByMap, type ICallback } from '../shortcutKeysEventBus'
const editButtons = [
{
key: 'F1',
name: '手动优惠'
},
{
key: 'F2',
name: '重置'
},
{
key: 'F3',
name: '锁定'
},
{
key: 'F4',
name: '挂单'
},
{
key: 'F5',
name: '取消订单'
},
{
key: 'F6',
name: '订单'
},
{
key: 'F7',
name: '钱箱'
},
{
key: 'F8',
name: '代收'
},
{
key: 'F9',
name: '当日结算'
},
{
key: 'F10',
name: '设置'
}
]
const handleEdit = (item: any) => () => {
console.log('handleEdit', item.key)
return new Promise((resolve) => {
setTimeout(resolve, 1000)
})
}
const shortcutKeysEventBusMap = new Map<string, ICallback>(
editButtons.map((item) => [item.key, handleEdit(item)])
)
useShortcutKeysEventBusByMap(shortcutKeysEventBusMap)
全部代码
import mitt, { type Handler } from 'mitt'
import { cloneDeep } from 'lodash-es'
const shortcutKeysEventBusInstance = mitt()
export const installEventBus = () => {
document.addEventListener('keydown', (e: KeyboardEvent) => {
console.log('keydown', e, e.code)
shortcutKeysEventBusInstance.emit(e.code, e)
})
}
interface IOption {
install?: boolean
}
const toggleEventBus = () => {
console.log('toggleEventBus')
const lastMap = cloneDeep(shortcutKeysEventBusInstance.all)
shortcutKeysEventBusInstance.all.forEach(function (handleList, type) {
handleList.forEach((handle) => {
shortcutKeysEventBusInstance.off(type, handle as Handler)
})
})
return () => {
console.log('finally')
lastMap.forEach(function (handleList, type) {
handleList.forEach((handle) => {
shortcutKeysEventBusInstance.on(type, handle as Handler)
})
})
}
}
export type ICallback = () => any
// 注册单个快捷键
export const useShortcutKeysEventBus = (
type: string,
callback: ICallback,
option: IOption = { install: true }
) => {
const handle = (e: any) => {
e.preventDefault()
const callbackResult = callback()
if (callbackResult instanceof Promise) {
const activateEventBus = toggleEventBus()
callbackResult.finally(activateEventBus)
}
}
const install = () => {
shortcutKeysEventBusInstance.on(type, handle)
}
option.install && install()
const unInstall = () => {
shortcutKeysEventBusInstance.off(type, handle)
}
onBeforeUnmount(unInstall)
return {
install,
unInstall
}
}
// 注册多个快捷键
export const useShortcutKeysEventBusByMap = (
map: Map<string, ICallback | ICallback[]>,
option: IOption = { install: true }
) => {
const handle = (callback: ICallback) => (e: any) => {
e.preventDefault()
const callbackResult = callback()
if (callbackResult instanceof Promise) {
const activateEventBus = toggleEventBus()
callbackResult.finally(activateEventBus)
}
}
const getEventCallbackMap = () => {
const eventCallbackMap = new Map<string, ReturnType<typeof handle>[]>()
map.forEach(function (value, type) {
if (Array.isArray(value)) {
value.forEach(function (fn) {
const eventCallback = handle(fn)
if (!eventCallbackMap.has(type)) {
eventCallbackMap.set(type, [eventCallback])
} else {
eventCallbackMap.set(type, eventCallbackMap.get(type)!.concat(eventCallback))
}
})
} else {
const eventCallback = handle(value)
eventCallbackMap.set(type, [eventCallback])
}
})
return eventCallbackMap
}
const eventCallbackMap = getEventCallbackMap()
const install = () => {
eventCallbackMap.forEach((eventCallbackList, type) => {
eventCallbackList.forEach((eventCallback) => {
shortcutKeysEventBusInstance.on(type, eventCallback)
})
})
}
option.install && install()
const unInstall = () => {
eventCallbackMap.forEach((eventCallbackList, type) => {
eventCallbackList.forEach((eventCallback) => {
shortcutKeysEventBusInstance.off(type, eventCallback)
})
})
}
onBeforeUnmount(unInstall)
return {
install,
unInstall
}
}
小结
使用event bus去注册、卸载不同快捷键的事件,减少依赖关系,降低使用负担。
快捷键事件也可以不返回一个promise 就不会卸载所有事件了