mirror of
https://github.com/vbenjs/vben-admin-thin-next.git
synced 2025-01-23 17:50:22 +08:00
feat(hook): add useKeyPress
This commit is contained in:
parent
819bcbe526
commit
3c3e640d69
@ -1,5 +1,5 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { ref, onBeforeUnmount, onBeforeMount, unref, Ref } from 'vue';
|
||||
import { ref, onBeforeMount, unref, Ref } from 'vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { getMenus } from '/@/router/menus';
|
||||
import type { Menu } from '/@/router/types';
|
||||
@ -7,6 +7,7 @@ import { filter, forEach } from '/@/utils/helper/treeHelper';
|
||||
import { useDebounce } from '/@/hooks/core/useDebounce';
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
import { useScrollTo } from '/@/hooks/event/useScrollTo';
|
||||
import { useKeyPress } from '/@/hooks/event/useKeyPress';
|
||||
|
||||
export interface SearchResult {
|
||||
name: string;
|
||||
@ -50,12 +51,6 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
|
||||
forEach(menuList, (item) => {
|
||||
item.name = t(item.name);
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', registerKeyDown);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keydown', registerKeyDown);
|
||||
});
|
||||
|
||||
function search(e: ChangeEvent) {
|
||||
@ -151,8 +146,8 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
|
||||
emit('close');
|
||||
}
|
||||
|
||||
function registerKeyDown(e: KeyboardEvent) {
|
||||
const keyCode = window.event ? e.keyCode : e.which;
|
||||
useKeyPress(['enter', 'up', 'down'], (events) => {
|
||||
const keyCode = events.keyCode;
|
||||
switch (keyCode) {
|
||||
case KeyCodeEnum.UP:
|
||||
handleUp();
|
||||
@ -167,7 +162,7 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
|
||||
handleClose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter };
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { WatchOptions } from 'vue';
|
||||
import { watch } from 'vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
export const useEffect = (effectHandler: Fn, dependencies: any[]) => {
|
||||
export function useEffect<T extends any = any>(
|
||||
effectHandler: (deps: T[], prevDeps?: T[]) => () => void,
|
||||
dependencies: T[]
|
||||
) {
|
||||
return watch(
|
||||
dependencies,
|
||||
(changedDependencies, prevDependencies, onCleanUp) => {
|
||||
@ -11,6 +13,6 @@ export const useEffect = (effectHandler: Fn, dependencies: any[]) => {
|
||||
onCleanUp(effectCleaner);
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true } as WatchOptions
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
};
|
||||
}
|
||||
|
19
src/hooks/core/useLockFn.ts
Normal file
19
src/hooks/core/useLockFn.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { ref, unref } from 'vue';
|
||||
|
||||
export function useLockFn<P extends any[] = any[], V extends any = any>(
|
||||
fn: (...args: P) => Promise<V>
|
||||
) {
|
||||
const lockRef = ref(false);
|
||||
return async function (...args: P) {
|
||||
if (unref(lockRef)) return;
|
||||
lockRef.value = true;
|
||||
try {
|
||||
const ret = await fn(...args);
|
||||
lockRef.value = false;
|
||||
return ret;
|
||||
} catch (e) {
|
||||
lockRef.value = false;
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import { toRef, Ref, reactive, customRef, SetupContext, watch, UnwrapRef } from 'vue';
|
||||
|
||||
export type ModelProps<U> = Readonly<
|
||||
{ [props: string]: any } & {
|
||||
modelValue?: U;
|
||||
}
|
||||
>;
|
||||
|
||||
export function useModel<T>(
|
||||
props: ModelProps<T>,
|
||||
context: SetupContext,
|
||||
callback?: (val: T | undefined, internalState: { value: UnwrapRef<T | undefined> }) => any
|
||||
) {
|
||||
const outerModel: Ref<T | undefined> = toRef(props, 'modelValue');
|
||||
const internalState = reactive({
|
||||
value: props.modelValue,
|
||||
});
|
||||
|
||||
const internalModel = customRef<UnwrapRef<T> | undefined>((track, trigger) => {
|
||||
return {
|
||||
get() {
|
||||
track();
|
||||
return internalState.value;
|
||||
},
|
||||
set(newVal) {
|
||||
if (internalState.value === newVal) return;
|
||||
internalState.value = newVal;
|
||||
context.emit('update:modelValue', newVal);
|
||||
trigger();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
watch(outerModel, (val, oldVal) => {
|
||||
if (val === oldVal || val === internalState.value) return;
|
||||
if (callback) {
|
||||
callback(val, internalState);
|
||||
return;
|
||||
}
|
||||
internalState.value = val as UnwrapRef<T> | undefined;
|
||||
});
|
||||
|
||||
return {
|
||||
internalState,
|
||||
internalModel,
|
||||
};
|
||||
}
|
58
src/hooks/core/useState.ts
Normal file
58
src/hooks/core/useState.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { isObject } from '@vue/shared';
|
||||
import { reactive, Ref, ref, readonly } from 'vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
type State<T> = ((s: T) => T) | T;
|
||||
type Dispatch<T> = (t: T) => void;
|
||||
|
||||
type DispatchState<T> = Dispatch<State<T>>;
|
||||
|
||||
type ResultState<T> = Readonly<Ref<T>>;
|
||||
|
||||
export function useState<T extends undefined>(
|
||||
initialState: (() => T) | T
|
||||
): [ResultState<T>, DispatchState<T>];
|
||||
|
||||
export function useState<T extends null>(
|
||||
initialState: (() => T) | T
|
||||
): [ResultState<T>, DispatchState<T>];
|
||||
|
||||
export function useState<T extends boolean>(
|
||||
initialState: (() => T) | T
|
||||
): [ResultState<boolean>, DispatchState<boolean>];
|
||||
|
||||
export function useState<T extends string>(
|
||||
initialState: (() => T) | T
|
||||
): [ResultState<string>, DispatchState<string>];
|
||||
|
||||
export function useState<T extends number>(
|
||||
initialState: (() => T) | T
|
||||
): [ResultState<number>, DispatchState<number>];
|
||||
|
||||
export function useState<T extends object>(
|
||||
initialState: (() => T) | T
|
||||
): [Readonly<T>, DispatchState<T>];
|
||||
|
||||
export function useState<T extends any>(
|
||||
initialState: (() => T) | T
|
||||
): [Readonly<T>, DispatchState<T>];
|
||||
|
||||
export function useState<T>(initialState: (() => T) | T): [ResultState<T> | T, DispatchState<T>] {
|
||||
if (isFunction(initialState)) {
|
||||
initialState = (initialState as Fn)();
|
||||
}
|
||||
|
||||
if (isObject(initialState)) {
|
||||
const state = reactive({ data: initialState }) as any;
|
||||
const setState = (newState: T) => {
|
||||
state.data = newState;
|
||||
};
|
||||
return [readonly(state), setState];
|
||||
} else {
|
||||
const state = ref(initialState) as any;
|
||||
const setState = (newState: T) => {
|
||||
state.value = newState;
|
||||
};
|
||||
return [readonly(state), setState];
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import { ref, watch, Ref, SetupContext } from 'vue';
|
||||
|
||||
export function useToggle(internalModel: Ref<unknown>, { emit }: SetupContext) {
|
||||
const isActive = ref(!!internalModel.value);
|
||||
const isToggled = ref(false);
|
||||
watch(internalModel, (val) => {
|
||||
isActive.value = !!val;
|
||||
});
|
||||
watch(isActive, (value) => {
|
||||
!!value !== !!internalModel.value && emit('onUpdate:modelValue', value);
|
||||
});
|
||||
function toggleIt() {
|
||||
isToggled.value = !isToggled.value;
|
||||
}
|
||||
return {
|
||||
isActive,
|
||||
toggleIt,
|
||||
isToggled,
|
||||
};
|
||||
}
|
172
src/hooks/event/useKeyPress.ts
Normal file
172
src/hooks/event/useKeyPress.ts
Normal file
@ -0,0 +1,172 @@
|
||||
// https://ahooks.js.org/zh-CN/hooks/dom/use-key-press
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, unref } from 'vue';
|
||||
import { noop } from '/@/utils';
|
||||
import { isFunction, isString, isNumber, isArray } from '/@/utils/is';
|
||||
|
||||
export type KeyPredicate = (event: KeyboardEvent) => boolean;
|
||||
export type keyType = KeyboardEvent['keyCode'] | KeyboardEvent['key'];
|
||||
export type KeyFilter = keyType | keyType[] | ((event: KeyboardEvent) => boolean);
|
||||
export type EventHandler = (event: KeyboardEvent) => void;
|
||||
|
||||
export type keyEvent = 'keydown' | 'keyup';
|
||||
|
||||
export type TargetElement = HTMLElement | Element | Document | Window;
|
||||
export type Target = Ref<TargetElement>;
|
||||
|
||||
export type EventOption = {
|
||||
events?: keyEvent[];
|
||||
target?: Target;
|
||||
};
|
||||
|
||||
const defaultEvents: keyEvent[] = ['keydown'];
|
||||
|
||||
// 键盘事件 keyCode 别名
|
||||
const aliasKeyCodeMap: Record<string, number | number[]> = {
|
||||
esc: 27,
|
||||
tab: 9,
|
||||
enter: 13,
|
||||
space: 32,
|
||||
up: 38,
|
||||
left: 37,
|
||||
right: 39,
|
||||
down: 40,
|
||||
delete: [8, 46],
|
||||
};
|
||||
|
||||
// 键盘事件 key 别名
|
||||
const aliasKeyMap: Record<string, string | string[]> = {
|
||||
esc: 'Escape',
|
||||
tab: 'Tab',
|
||||
enter: 'Enter',
|
||||
space: ' ',
|
||||
// IE11 uses key names without `Arrow` prefix for arrow keys.
|
||||
up: ['Up', 'ArrowUp'],
|
||||
left: ['Left', 'ArrowLeft'],
|
||||
right: ['Right', 'ArrowRight'],
|
||||
down: ['Down', 'ArrowDown'],
|
||||
delete: ['Backspace', 'Delete'],
|
||||
};
|
||||
|
||||
// 修饰键
|
||||
const modifierKey: Record<string, (event: KeyboardEvent) => boolean> = {
|
||||
ctrl: (event: KeyboardEvent) => event.ctrlKey,
|
||||
shift: (event: KeyboardEvent) => event.shiftKey,
|
||||
alt: (event: KeyboardEvent) => event.altKey,
|
||||
meta: (event: KeyboardEvent) => event.metaKey,
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断按键是否激活
|
||||
* @param [event: KeyboardEvent]键盘事件
|
||||
* @param [keyFilter: any] 当前键
|
||||
* @returns Boolean
|
||||
*/
|
||||
function genFilterKey(event: any, keyFilter: any) {
|
||||
// 浏览器自动补全 input 的时候,会触发 keyDown、keyUp 事件,但此时 event.key 等为空
|
||||
if (!event.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 数字类型直接匹配事件的 keyCode
|
||||
if (isNumber(keyFilter)) {
|
||||
return event.keyCode === keyFilter;
|
||||
}
|
||||
// 字符串依次判断是否有组合键
|
||||
const genArr = keyFilter.split('.');
|
||||
let genLen = 0;
|
||||
for (const key of genArr) {
|
||||
// 组合键
|
||||
const genModifier = modifierKey[key];
|
||||
// key 别名
|
||||
const aliasKey = aliasKeyMap[key];
|
||||
// keyCode 别名
|
||||
const aliasKeyCode = aliasKeyCodeMap[key];
|
||||
/**
|
||||
* 满足以上规则
|
||||
* 1. 自定义组合键别名
|
||||
* 2. 自定义 key 别名
|
||||
* 3. 自定义 keyCode 别名
|
||||
* 4. 匹配 key 或 keyCode
|
||||
*/
|
||||
if (
|
||||
(genModifier && genModifier(event)) ||
|
||||
(aliasKey && isArray(aliasKey) ? aliasKey.includes(event.key) : aliasKey === event.key) ||
|
||||
(aliasKeyCode && isArray(aliasKeyCode)
|
||||
? aliasKeyCode.includes(event.keyCode)
|
||||
: aliasKeyCode === event.keyCode) ||
|
||||
event.key.toUpperCase() === key.toUpperCase()
|
||||
) {
|
||||
genLen++;
|
||||
}
|
||||
}
|
||||
return genLen === genArr.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 键盘输入预处理方法
|
||||
*/
|
||||
function genKeyFormat(keyFilter: any): KeyPredicate {
|
||||
if (isFunction(keyFilter)) {
|
||||
return keyFilter;
|
||||
}
|
||||
if (isString(keyFilter) || isNumber(keyFilter)) {
|
||||
return (event: KeyboardEvent) => genFilterKey(event, keyFilter);
|
||||
}
|
||||
if (isArray(keyFilter)) {
|
||||
return (event: KeyboardEvent) => keyFilter.some((item: any) => genFilterKey(event, item));
|
||||
}
|
||||
return keyFilter ? () => true : () => false;
|
||||
}
|
||||
|
||||
export function useKeyPress(
|
||||
keyFilter: KeyFilter,
|
||||
eventHandler: EventHandler = noop,
|
||||
option: EventOption = {}
|
||||
) {
|
||||
const { events = defaultEvents, target } = option;
|
||||
|
||||
let el: TargetElement | null | undefined;
|
||||
|
||||
function handler(event: any) {
|
||||
const genGuard: KeyPredicate = genKeyFormat(keyFilter);
|
||||
if (genGuard(event)) {
|
||||
return eventHandler(event);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
el = getTargetElement(target, window);
|
||||
if (!el) return;
|
||||
|
||||
for (const eventName of events) {
|
||||
el.addEventListener(eventName, handler);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (!el) return;
|
||||
for (const eventName of events) {
|
||||
el.removeEventListener(eventName, handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getTargetElement(
|
||||
target?: Target,
|
||||
defaultElement?: TargetElement
|
||||
): TargetElement | undefined | null {
|
||||
if (!target) {
|
||||
return defaultElement;
|
||||
}
|
||||
|
||||
let targetElement: TargetElement | undefined | null;
|
||||
|
||||
if (isFunction(target)) {
|
||||
targetElement = target();
|
||||
} else {
|
||||
targetElement = unref(target);
|
||||
}
|
||||
return targetElement;
|
||||
}
|
@ -16,7 +16,7 @@ export function useScript(opts: ScriptOptions) {
|
||||
isLoading.value = false;
|
||||
success.value = true;
|
||||
error.value = false;
|
||||
resolve();
|
||||
resolve('');
|
||||
};
|
||||
|
||||
script.onerror = function (err) {
|
||||
|
Loading…
Reference in New Issue
Block a user