Commit 3c3e640d69b48d8e9382acd25b60d906af038a9d

Authored by vben
1 parent 819bcbe5

feat(hook): add useKeyPress

src/components/Application/src/search/useMenuSearch.ts
1 1 import { cloneDeep } from 'lodash-es';
2   -import { ref, onBeforeUnmount, onBeforeMount, unref, Ref } from 'vue';
  2 +import { ref, onBeforeMount, unref, Ref } from 'vue';
3 3 import { useI18n } from '/@/hooks/web/useI18n';
4 4 import { getMenus } from '/@/router/menus';
5 5 import type { Menu } from '/@/router/types';
... ... @@ -7,6 +7,7 @@ import { filter, forEach } from '/@/utils/helper/treeHelper';
7 7 import { useDebounce } from '/@/hooks/core/useDebounce';
8 8 import { useGo } from '/@/hooks/web/usePage';
9 9 import { useScrollTo } from '/@/hooks/event/useScrollTo';
  10 +import { useKeyPress } from '/@/hooks/event/useKeyPress';
10 11  
11 12 export interface SearchResult {
12 13 name: string;
... ... @@ -50,12 +51,6 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
50 51 forEach(menuList, (item) => {
51 52 item.name = t(item.name);
52 53 });
53   -
54   - document.addEventListener('keydown', registerKeyDown);
55   - });
56   -
57   - onBeforeUnmount(() => {
58   - document.removeEventListener('keydown', registerKeyDown);
59 54 });
60 55  
61 56 function search(e: ChangeEvent) {
... ... @@ -151,8 +146,8 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
151 146 emit('close');
152 147 }
153 148  
154   - function registerKeyDown(e: KeyboardEvent) {
155   - const keyCode = window.event ? e.keyCode : e.which;
  149 + useKeyPress(['enter', 'up', 'down'], (events) => {
  150 + const keyCode = events.keyCode;
156 151 switch (keyCode) {
157 152 case KeyCodeEnum.UP:
158 153 handleUp();
... ... @@ -167,7 +162,7 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
167 162 handleClose();
168 163 break;
169 164 }
170   - }
  165 + });
171 166  
172 167 return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter };
173 168 }
... ...
src/hooks/core/useEffect.ts
1   -import { WatchOptions } from 'vue';
2 1 import { watch } from 'vue';
3 2 import { isFunction } from '/@/utils/is';
4 3  
5   -export const useEffect = (effectHandler: Fn, dependencies: any[]) => {
  4 +export function useEffect<T extends any = any>(
  5 + effectHandler: (deps: T[], prevDeps?: T[]) => () => void,
  6 + dependencies: T[]
  7 +) {
6 8 return watch(
7 9 dependencies,
8 10 (changedDependencies, prevDependencies, onCleanUp) => {
... ... @@ -11,6 +13,6 @@ export const useEffect = (effectHandler: Fn, dependencies: any[]) =&gt; {
11 13 onCleanUp(effectCleaner);
12 14 }
13 15 },
14   - { immediate: true, deep: true } as WatchOptions
  16 + { immediate: true, deep: true }
15 17 );
16   -};
  18 +}
... ...
src/hooks/core/useLockFn.ts 0 → 100644
  1 +import { ref, unref } from 'vue';
  2 +
  3 +export function useLockFn<P extends any[] = any[], V extends any = any>(
  4 + fn: (...args: P) => Promise<V>
  5 +) {
  6 + const lockRef = ref(false);
  7 + return async function (...args: P) {
  8 + if (unref(lockRef)) return;
  9 + lockRef.value = true;
  10 + try {
  11 + const ret = await fn(...args);
  12 + lockRef.value = false;
  13 + return ret;
  14 + } catch (e) {
  15 + lockRef.value = false;
  16 + throw e;
  17 + }
  18 + };
  19 +}
... ...
src/hooks/core/useModel.ts deleted 100644 → 0
1   -import { toRef, Ref, reactive, customRef, SetupContext, watch, UnwrapRef } from 'vue';
2   -
3   -export type ModelProps<U> = Readonly<
4   - { [props: string]: any } & {
5   - modelValue?: U;
6   - }
7   ->;
8   -
9   -export function useModel<T>(
10   - props: ModelProps<T>,
11   - context: SetupContext,
12   - callback?: (val: T | undefined, internalState: { value: UnwrapRef<T | undefined> }) => any
13   -) {
14   - const outerModel: Ref<T | undefined> = toRef(props, 'modelValue');
15   - const internalState = reactive({
16   - value: props.modelValue,
17   - });
18   -
19   - const internalModel = customRef<UnwrapRef<T> | undefined>((track, trigger) => {
20   - return {
21   - get() {
22   - track();
23   - return internalState.value;
24   - },
25   - set(newVal) {
26   - if (internalState.value === newVal) return;
27   - internalState.value = newVal;
28   - context.emit('update:modelValue', newVal);
29   - trigger();
30   - },
31   - };
32   - });
33   -
34   - watch(outerModel, (val, oldVal) => {
35   - if (val === oldVal || val === internalState.value) return;
36   - if (callback) {
37   - callback(val, internalState);
38   - return;
39   - }
40   - internalState.value = val as UnwrapRef<T> | undefined;
41   - });
42   -
43   - return {
44   - internalState,
45   - internalModel,
46   - };
47   -}
src/hooks/core/useState.ts 0 → 100644
  1 +import { isObject } from '@vue/shared';
  2 +import { reactive, Ref, ref, readonly } from 'vue';
  3 +import { isFunction } from '/@/utils/is';
  4 +
  5 +type State<T> = ((s: T) => T) | T;
  6 +type Dispatch<T> = (t: T) => void;
  7 +
  8 +type DispatchState<T> = Dispatch<State<T>>;
  9 +
  10 +type ResultState<T> = Readonly<Ref<T>>;
  11 +
  12 +export function useState<T extends undefined>(
  13 + initialState: (() => T) | T
  14 +): [ResultState<T>, DispatchState<T>];
  15 +
  16 +export function useState<T extends null>(
  17 + initialState: (() => T) | T
  18 +): [ResultState<T>, DispatchState<T>];
  19 +
  20 +export function useState<T extends boolean>(
  21 + initialState: (() => T) | T
  22 +): [ResultState<boolean>, DispatchState<boolean>];
  23 +
  24 +export function useState<T extends string>(
  25 + initialState: (() => T) | T
  26 +): [ResultState<string>, DispatchState<string>];
  27 +
  28 +export function useState<T extends number>(
  29 + initialState: (() => T) | T
  30 +): [ResultState<number>, DispatchState<number>];
  31 +
  32 +export function useState<T extends object>(
  33 + initialState: (() => T) | T
  34 +): [Readonly<T>, DispatchState<T>];
  35 +
  36 +export function useState<T extends any>(
  37 + initialState: (() => T) | T
  38 +): [Readonly<T>, DispatchState<T>];
  39 +
  40 +export function useState<T>(initialState: (() => T) | T): [ResultState<T> | T, DispatchState<T>] {
  41 + if (isFunction(initialState)) {
  42 + initialState = (initialState as Fn)();
  43 + }
  44 +
  45 + if (isObject(initialState)) {
  46 + const state = reactive({ data: initialState }) as any;
  47 + const setState = (newState: T) => {
  48 + state.data = newState;
  49 + };
  50 + return [readonly(state), setState];
  51 + } else {
  52 + const state = ref(initialState) as any;
  53 + const setState = (newState: T) => {
  54 + state.value = newState;
  55 + };
  56 + return [readonly(state), setState];
  57 + }
  58 +}
... ...
src/hooks/core/useToggle.ts deleted 100644 → 0
1   -import { ref, watch, Ref, SetupContext } from 'vue';
2   -
3   -export function useToggle(internalModel: Ref<unknown>, { emit }: SetupContext) {
4   - const isActive = ref(!!internalModel.value);
5   - const isToggled = ref(false);
6   - watch(internalModel, (val) => {
7   - isActive.value = !!val;
8   - });
9   - watch(isActive, (value) => {
10   - !!value !== !!internalModel.value && emit('onUpdate:modelValue', value);
11   - });
12   - function toggleIt() {
13   - isToggled.value = !isToggled.value;
14   - }
15   - return {
16   - isActive,
17   - toggleIt,
18   - isToggled,
19   - };
20   -}
src/hooks/event/useKeyPress.ts 0 → 100644
  1 +// https://ahooks.js.org/zh-CN/hooks/dom/use-key-press
  2 +
  3 +import type { Ref } from 'vue';
  4 +import { onBeforeUnmount, onMounted, unref } from 'vue';
  5 +import { noop } from '/@/utils';
  6 +import { isFunction, isString, isNumber, isArray } from '/@/utils/is';
  7 +
  8 +export type KeyPredicate = (event: KeyboardEvent) => boolean;
  9 +export type keyType = KeyboardEvent['keyCode'] | KeyboardEvent['key'];
  10 +export type KeyFilter = keyType | keyType[] | ((event: KeyboardEvent) => boolean);
  11 +export type EventHandler = (event: KeyboardEvent) => void;
  12 +
  13 +export type keyEvent = 'keydown' | 'keyup';
  14 +
  15 +export type TargetElement = HTMLElement | Element | Document | Window;
  16 +export type Target = Ref<TargetElement>;
  17 +
  18 +export type EventOption = {
  19 + events?: keyEvent[];
  20 + target?: Target;
  21 +};
  22 +
  23 +const defaultEvents: keyEvent[] = ['keydown'];
  24 +
  25 +// 键盘事件 keyCode 别名
  26 +const aliasKeyCodeMap: Record<string, number | number[]> = {
  27 + esc: 27,
  28 + tab: 9,
  29 + enter: 13,
  30 + space: 32,
  31 + up: 38,
  32 + left: 37,
  33 + right: 39,
  34 + down: 40,
  35 + delete: [8, 46],
  36 +};
  37 +
  38 +// 键盘事件 key 别名
  39 +const aliasKeyMap: Record<string, string | string[]> = {
  40 + esc: 'Escape',
  41 + tab: 'Tab',
  42 + enter: 'Enter',
  43 + space: ' ',
  44 + // IE11 uses key names without `Arrow` prefix for arrow keys.
  45 + up: ['Up', 'ArrowUp'],
  46 + left: ['Left', 'ArrowLeft'],
  47 + right: ['Right', 'ArrowRight'],
  48 + down: ['Down', 'ArrowDown'],
  49 + delete: ['Backspace', 'Delete'],
  50 +};
  51 +
  52 +// 修饰键
  53 +const modifierKey: Record<string, (event: KeyboardEvent) => boolean> = {
  54 + ctrl: (event: KeyboardEvent) => event.ctrlKey,
  55 + shift: (event: KeyboardEvent) => event.shiftKey,
  56 + alt: (event: KeyboardEvent) => event.altKey,
  57 + meta: (event: KeyboardEvent) => event.metaKey,
  58 +};
  59 +
  60 +/**
  61 + * 判断按键是否激活
  62 + * @param [event: KeyboardEvent]键盘事件
  63 + * @param [keyFilter: any] 当前键
  64 + * @returns Boolean
  65 + */
  66 +function genFilterKey(event: any, keyFilter: any) {
  67 + // 浏览器自动补全 input 的时候,会触发 keyDown、keyUp 事件,但此时 event.key 等为空
  68 + if (!event.key) {
  69 + return false;
  70 + }
  71 +
  72 + // 数字类型直接匹配事件的 keyCode
  73 + if (isNumber(keyFilter)) {
  74 + return event.keyCode === keyFilter;
  75 + }
  76 + // 字符串依次判断是否有组合键
  77 + const genArr = keyFilter.split('.');
  78 + let genLen = 0;
  79 + for (const key of genArr) {
  80 + // 组合键
  81 + const genModifier = modifierKey[key];
  82 + // key 别名
  83 + const aliasKey = aliasKeyMap[key];
  84 + // keyCode 别名
  85 + const aliasKeyCode = aliasKeyCodeMap[key];
  86 + /**
  87 + * 满足以上规则
  88 + * 1. 自定义组合键别名
  89 + * 2. 自定义 key 别名
  90 + * 3. 自定义 keyCode 别名
  91 + * 4. 匹配 key 或 keyCode
  92 + */
  93 + if (
  94 + (genModifier && genModifier(event)) ||
  95 + (aliasKey && isArray(aliasKey) ? aliasKey.includes(event.key) : aliasKey === event.key) ||
  96 + (aliasKeyCode && isArray(aliasKeyCode)
  97 + ? aliasKeyCode.includes(event.keyCode)
  98 + : aliasKeyCode === event.keyCode) ||
  99 + event.key.toUpperCase() === key.toUpperCase()
  100 + ) {
  101 + genLen++;
  102 + }
  103 + }
  104 + return genLen === genArr.length;
  105 +}
  106 +
  107 +/**
  108 + * 键盘输入预处理方法
  109 + */
  110 +function genKeyFormat(keyFilter: any): KeyPredicate {
  111 + if (isFunction(keyFilter)) {
  112 + return keyFilter;
  113 + }
  114 + if (isString(keyFilter) || isNumber(keyFilter)) {
  115 + return (event: KeyboardEvent) => genFilterKey(event, keyFilter);
  116 + }
  117 + if (isArray(keyFilter)) {
  118 + return (event: KeyboardEvent) => keyFilter.some((item: any) => genFilterKey(event, item));
  119 + }
  120 + return keyFilter ? () => true : () => false;
  121 +}
  122 +
  123 +export function useKeyPress(
  124 + keyFilter: KeyFilter,
  125 + eventHandler: EventHandler = noop,
  126 + option: EventOption = {}
  127 +) {
  128 + const { events = defaultEvents, target } = option;
  129 +
  130 + let el: TargetElement | null | undefined;
  131 +
  132 + function handler(event: any) {
  133 + const genGuard: KeyPredicate = genKeyFormat(keyFilter);
  134 + if (genGuard(event)) {
  135 + return eventHandler(event);
  136 + }
  137 + }
  138 +
  139 + onMounted(() => {
  140 + el = getTargetElement(target, window);
  141 + if (!el) return;
  142 +
  143 + for (const eventName of events) {
  144 + el.addEventListener(eventName, handler);
  145 + }
  146 + });
  147 +
  148 + onBeforeUnmount(() => {
  149 + if (!el) return;
  150 + for (const eventName of events) {
  151 + el.removeEventListener(eventName, handler);
  152 + }
  153 + });
  154 +}
  155 +
  156 +export function getTargetElement(
  157 + target?: Target,
  158 + defaultElement?: TargetElement
  159 +): TargetElement | undefined | null {
  160 + if (!target) {
  161 + return defaultElement;
  162 + }
  163 +
  164 + let targetElement: TargetElement | undefined | null;
  165 +
  166 + if (isFunction(target)) {
  167 + targetElement = target();
  168 + } else {
  169 + targetElement = unref(target);
  170 + }
  171 + return targetElement;
  172 +}
... ...
src/hooks/web/useScript.ts
... ... @@ -16,7 +16,7 @@ export function useScript(opts: ScriptOptions) {
16 16 isLoading.value = false;
17 17 success.value = true;
18 18 error.value = false;
19   - resolve();
  19 + resolve('');
20 20 };
21 21  
22 22 script.onerror = function (err) {
... ...