收银POS系统快捷键篇

206 阅读4分钟

需求

最近在写一个POS收银系统,就是便利店收银员使用的那种

网上找个图片给大家参考一下:

a0dbe5b239edf2e8bcedc39f1eee82f.png

分析

主界面可以按快捷键唤起很多功能,大多是打开一个对话框。然后对话框界面有对应的快捷键,如果有个快捷键跟主界面的快捷键冲突了咋办?不用想,肯定是触发当前界面的快捷键事件,主界面的不触发。也就是打开对话框的时候,首页注册的快捷键事件得卸载,关闭对话框的时候,事件得重置。

技术方案

主入口注册快捷键事件,不同组件(文件)下注册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 就不会卸载所有事件了