feat: add shortcut keys

pull/48/MERGE
vben 2024-05-25 20:02:21 +08:00
parent 666371ed67
commit 977d108ca0
12 changed files with 104 additions and 12 deletions

View File

@ -7,6 +7,8 @@ export const IcRoundKeyboardArrowDown = createIcon(
); );
export const IcRoundChevronRight = createIcon('ic:round-chevron-right'); export const IcRoundChevronRight = createIcon('ic:round-chevron-right');
export const IcRoundKeyboard = createIcon('ic:round-keyboard');
// export const IcRoundMenuOpen = createIcon('ic:round-menu-open'); // export const IcRoundMenuOpen = createIcon('ic:round-menu-open');
export const IcRoundMenu = createIcon('ic:round-menu'); export const IcRoundMenu = createIcon('ic:round-menu');

View File

@ -91,6 +91,8 @@ interface Preference {
pageTransitionEnable: boolean; pageTransitionEnable: boolean;
/** 是否开启半深色菜单只在theme='light'时生效) */ /** 是否开启半深色菜单只在theme='light'时生效) */
semiDarkMenu: boolean; semiDarkMenu: boolean;
/** 是否启用快捷键 */
shortcutKeys: boolean;
/** 是否显示偏好设置 */ /** 是否显示偏好设置 */
showPreference: boolean; showPreference: boolean;
/** 侧边栏是否折叠 */ /** 侧边栏是否折叠 */

View File

@ -21,7 +21,7 @@ import { isWindowsOs } from '@vben-core/toolkit';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { useMagicKeys, useToggle, whenever } from '@vueuse/core'; import { useMagicKeys, useToggle, whenever } from '@vueuse/core';
import { onMounted, ref } from 'vue'; import { ref } from 'vue';
import SearchPanel from './search-panel.vue'; import SearchPanel from './search-panel.vue';
@ -29,9 +29,13 @@ defineOptions({
name: 'GlobalSearch', name: 'GlobalSearch',
}); });
withDefaults(defineProps<{ menus: MenuRecordRaw[] }>(), { const props = withDefaults(
menus: () => [], defineProps<{ enableShortcutKey?: boolean; menus: MenuRecordRaw[] }>(),
}); {
enableShortcutKey: true,
menus: () => [],
},
);
const [open, toggleOpen] = useToggle(); const [open, toggleOpen] = useToggle();
const keyword = ref(''); const keyword = ref('');
@ -41,13 +45,15 @@ function handleClose() {
keyword.value = ''; keyword.value = '';
} }
onMounted(() => { if (props.enableShortcutKey) {
const keys = useMagicKeys(); const keys = useMagicKeys();
const cmd = isWindowsOs() ? keys['ctrl+k'] : keys['cmd+k']; const cmd = isWindowsOs() ? keys['ctrl+k'] : keys['cmd+k'];
whenever(cmd, () => { whenever(cmd, () => {
open.value = true; if (props.enableShortcutKey) {
open.value = true;
}
}); });
}); }
</script> </script>
<template> <template>
@ -67,11 +73,13 @@ onMounted(() => {
{{ $t('search.search') }} {{ $t('search.search') }}
</span> </span>
<span <span
v-if="enableShortcutKey"
class="bg-background border-foreground/50 text-muted-foreground group-hover:text-foreground relative hidden rounded-sm rounded-r-xl px-1.5 py-1 text-xs leading-none group-hover:opacity-100 md:block" class="bg-background border-foreground/50 text-muted-foreground group-hover:text-foreground relative hidden rounded-sm rounded-r-xl px-1.5 py-1 text-xs leading-none group-hover:opacity-100 md:block"
> >
{{ isWindowsOs() ? 'Ctrl' : '⌘' }} {{ isWindowsOs() ? 'Ctrl' : '⌘' }}
<kbd>K</kbd> <kbd>K</kbd>
</span> </span>
<span v-else></span>
</div> </div>
</DialogTrigger> </DialogTrigger>
<DialogContent <DialogContent

View File

@ -13,6 +13,7 @@ defineOptions({
const locale = defineModel<string>('locale'); const locale = defineModel<string>('locale');
const dynamicTitle = defineModel<boolean>('dynamicTitle'); const dynamicTitle = defineModel<boolean>('dynamicTitle');
const shortcutKeys = defineModel<boolean>('shortcutKeys');
const localeItems: SelectListItem[] = staticPreference.supportLanguages.map( const localeItems: SelectListItem[] = staticPreference.supportLanguages.map(
(item) => ({ (item) => ({
@ -29,4 +30,7 @@ const localeItems: SelectListItem[] = staticPreference.supportLanguages.map(
<SwitchItem v-model="dynamicTitle"> <SwitchItem v-model="dynamicTitle">
{{ $t('preference.dynamic-title') }} {{ $t('preference.dynamic-title') }}
</SwitchItem> </SwitchItem>
<SwitchItem v-model="shortcutKeys">
{{ $t('preference.shortcut-key') }}
</SwitchItem>
</template> </template>

View File

@ -54,9 +54,11 @@ function updateLocale(value: string) {
:locale="preference.locale" :locale="preference.locale"
:navigation-accordion="preference.navigationAccordion" :navigation-accordion="preference.navigationAccordion"
:navigation-style="preference.navigationStyle" :navigation-style="preference.navigationStyle"
:shortcut-keys="preference.shortcutKeys"
:navigation-split="preference.navigationSplit" :navigation-split="preference.navigationSplit"
:side-collapse-show-title="preference.sideCollapseShowTitle" :side-collapse-show-title="preference.sideCollapseShowTitle"
:page-transition-enable="preference.pageTransitionEnable" :page-transition-enable="preference.pageTransitionEnable"
@update:shortcut-keys="(value) => handleUpdate('shortcutKeys', value)"
@update:navigation-style="(value) => handleUpdate('navigationStyle', value)" @update:navigation-style="(value) => handleUpdate('navigationStyle', value)"
@update:navigation-accordion=" @update:navigation-accordion="
(value) => handleUpdate('navigationAccordion', value) (value) => handleUpdate('navigationAccordion', value)

View File

@ -62,6 +62,7 @@ const pageTransitionEnable = defineModel<boolean>('pageTransitionEnable');
const layout = defineModel<LayoutType>('layout'); const layout = defineModel<LayoutType>('layout');
const contentCompact = defineModel<string>('contentCompact'); const contentCompact = defineModel<string>('contentCompact');
const sideVisible = defineModel<boolean>('sideVisible'); const sideVisible = defineModel<boolean>('sideVisible');
const shortcutKeys = defineModel<boolean>('shortcutKeys');
const tabsVisible = defineModel<boolean>('tabsVisible'); const tabsVisible = defineModel<boolean>('tabsVisible');
const tabsIcon = defineModel<boolean>('tabsIcon'); const tabsIcon = defineModel<boolean>('tabsIcon');
// const logoVisible = defineModel<boolean>('logoVisible'); // const logoVisible = defineModel<boolean>('logoVisible');
@ -95,6 +96,10 @@ const tabs = computed((): SegmentedItem[] => {
label: $t('preference.general'), label: $t('preference.general'),
value: 'general', value: 'general',
}, },
// {
// label: $t('preference.shortcut-key'),
// value: 'shortcutKey',
// },
]; ];
}); });
@ -233,6 +238,24 @@ function handleReset() {
<General <General
v-model:locale="locale" v-model:locale="locale"
v-model:dynamic-title="dynamicTitle" v-model:dynamic-title="dynamicTitle"
v-model:shortcut-keys="shortcutKeys"
/>
</Block>
<Block :title="$t('preference.animation')">
<Animation
v-model:page-progress="pageProgress"
v-model:page-transition="pageTransition"
v-model:page-transition-enable="pageTransitionEnable"
/>
</Block>
</template>
<template #shortcutKey>
<Block :title="$t('preference.general')">
<General
v-model:locale="locale"
v-model:dynamic-title="dynamicTitle"
v-model:shortcut-keys="shortcutKeys"
/> />
</Block> </Block>

View File

@ -9,17 +9,20 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger, DropdownMenuTrigger,
VbenAlertDialog, VbenAlertDialog,
VbenAvatar, VbenAvatar,
VbenIcon, VbenIcon,
} from '@vben-core/shadcn-ui'; } from '@vben-core/shadcn-ui';
import { isWindowsOs } from '@vben-core/toolkit';
import type { Component } from 'vue'; import type { Component } from 'vue';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { preference } from '@vben/preference'; import { preference } from '@vben/preference';
import { ref } from 'vue'; import { useMagicKeys, whenever } from '@vueuse/core';
import { computed, ref } from 'vue';
import { useOpenPreference } from '../preference/use-open-preference'; import { useOpenPreference } from '../preference/use-open-preference';
@ -32,15 +35,19 @@ interface Props {
* @zh_CN 描述 * @zh_CN 描述
*/ */
description?: string; description?: string;
/**
* 是否启用快捷键
*/
enableShortcutKey?: boolean;
/** /**
* 菜单数组 * 菜单数组
*/ */
menus?: Array<{ handler: AnyFunction; icon?: Component; text: string }>; menus?: Array<{ handler: AnyFunction; icon?: Component; text: string }>;
/** /**
* 标签文本 * 标签文本
*/ */
tagText?: string; tagText?: string;
/** /**
* 文本 * 文本
*/ */
@ -51,10 +58,12 @@ defineOptions({
name: 'UserDropdown', name: 'UserDropdown',
}); });
withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
avatar: '', avatar: '',
description: '', description: '',
enableShortcutKey: true,
menus: () => [], menus: () => [],
showShortcutKey: true,
tagText: '', tagText: '',
text: '', text: '',
}); });
@ -65,6 +74,12 @@ const openDialog = ref(false);
const { handleOpenPreference } = useOpenPreference(); const { handleOpenPreference } = useOpenPreference();
const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
const shortcutKeys = computed(() => {
return props.enableShortcutKey && preference.shortcutKeys;
});
function handleLogout() { function handleLogout() {
// emit // emit
openDialog.value = true; openDialog.value = true;
@ -75,6 +90,21 @@ function handleSubmitLogout() {
emit('logout'); emit('logout');
openDialog.value = false; openDialog.value = false;
} }
if (shortcutKeys.value) {
const keys = useMagicKeys();
whenever(keys['Alt+KeyQ'], () => {
if (shortcutKeys.value) {
handleLogout();
}
});
whenever(keys['Alt+Comma'], () => {
if (shortcutKeys.value) {
handleOpenPreference();
}
});
}
</script> </script>
<template> <template>
@ -137,6 +167,9 @@ function handleSubmitLogout() {
> >
<IcRoundSettingsSuggest class="mr-2 size-5" /> <IcRoundSettingsSuggest class="mr-2 size-5" />
{{ $t('preference.preferences') }} {{ $t('preference.preferences') }}
<DropdownMenuShortcut v-if="shortcutKeys">
{{ altView }} ,
</DropdownMenuShortcut>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
@ -145,6 +178,9 @@ function handleSubmitLogout() {
> >
<IcRoundLogout class="mr-2 size-5" /> <IcRoundLogout class="mr-2 size-5" />
{{ $t('common.logout') }} {{ $t('common.logout') }}
<DropdownMenuShortcut v-if="shortcutKeys">
{{ altView }} Q
</DropdownMenuShortcut>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>

View File

@ -2,6 +2,7 @@
import { VbenFullScreen } from '@vben-core/shadcn-ui'; import { VbenFullScreen } from '@vben-core/shadcn-ui';
import { GlobalSearch, LanguageToggle, ThemeToggle } from '@vben/common-ui'; import { GlobalSearch, LanguageToggle, ThemeToggle } from '@vben/common-ui';
import { preference } from '@vben/preference';
import { useAccessStore } from '@vben/stores'; import { useAccessStore } from '@vben/stores';
interface Props { interface Props {
@ -30,7 +31,11 @@ const accessStore = useAccessStore();
<slot name="menu"></slot> <slot name="menu"></slot>
</div> </div>
<div class="flex h-full min-w-0 flex-shrink-0 items-center"> <div class="flex h-full min-w-0 flex-shrink-0 items-center">
<GlobalSearch class="mr-4" :menus="accessStore.getAccessMenus" /> <GlobalSearch
class="mr-4"
:enable-shortcut-key="preference.shortcutKeys"
:menus="accessStore.getAccessMenus"
/>
<ThemeToggle class="mr-2" /> <ThemeToggle class="mr-2" />
<LanguageToggle class="mr-2" /> <LanguageToggle class="mr-2" />
<VbenFullScreen class="mr-2" /> <VbenFullScreen class="mr-2" />

View File

@ -42,6 +42,7 @@ preference:
preferences: Preferences preferences: Preferences
preferences-subtitle: Customize Preferences & Preview in Real Time preferences-subtitle: Customize Preferences & Preview in Real Time
theme: Theme theme: Theme
shortcut-key: Shortcut Key
appearance: Appearance appearance: Appearance
theme-color: Theme Color theme-color: Theme Color
layout: Layout layout: Layout

View File

@ -40,6 +40,7 @@ search:
preference: preference:
preferences: 偏好设置 preferences: 偏好设置
preferences-subtitle: 自定义偏好设置 & 实时预览 preferences-subtitle: 自定义偏好设置 & 实时预览
shortcut-key: 快捷键
theme: 主题 theme: 主题
appearance: 外观 appearance: 外观
theme-color: 主题色 theme-color: 主题色

View File

@ -34,6 +34,7 @@ const defaultPreference: Preference = {
pageTransition: 'fade-slide', pageTransition: 'fade-slide',
pageTransitionEnable: true, pageTransitionEnable: true,
semiDarkMenu: true, semiDarkMenu: true,
shortcutKeys: true,
showPreference: true, showPreference: true,
sideCollapse: false, sideCollapse: false,
sideCollapseShowTitle: true, sideCollapseShowTitle: true,

View File

@ -609,6 +609,9 @@ importers:
'@vben-core/toolkit': '@vben-core/toolkit':
specifier: workspace:* specifier: workspace:*
version: link:../../@vben-core/shared/toolkit version: link:../../@vben-core/shared/toolkit
'@vben/constants':
specifier: workspace:*
version: link:../../constants
'@vben/locales': '@vben/locales':
specifier: workspace:* specifier: workspace:*
version: link:../../locales version: link:../../locales
@ -681,7 +684,11 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../@vben-core/shared/typings version: link:../../@vben-core/shared/typings
packages/constants: {} packages/constants:
dependencies:
'@vben-core/toolkit':
specifier: workspace:*
version: link:../@vben-core/shared/toolkit
packages/hooks: packages/hooks:
dependencies: dependencies: