feat: add shortcut keys
parent
666371ed67
commit
977d108ca0
|
@ -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');
|
||||||
|
|
|
@ -91,6 +91,8 @@ interface Preference {
|
||||||
pageTransitionEnable: boolean;
|
pageTransitionEnable: boolean;
|
||||||
/** 是否开启半深色菜单(只在theme='light'时生效) */
|
/** 是否开启半深色菜单(只在theme='light'时生效) */
|
||||||
semiDarkMenu: boolean;
|
semiDarkMenu: boolean;
|
||||||
|
/** 是否启用快捷键 */
|
||||||
|
shortcutKeys: boolean;
|
||||||
/** 是否显示偏好设置 */
|
/** 是否显示偏好设置 */
|
||||||
showPreference: boolean;
|
showPreference: boolean;
|
||||||
/** 侧边栏是否折叠 */
|
/** 侧边栏是否折叠 */
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -40,6 +40,7 @@ search:
|
||||||
preference:
|
preference:
|
||||||
preferences: 偏好设置
|
preferences: 偏好设置
|
||||||
preferences-subtitle: 自定义偏好设置 & 实时预览
|
preferences-subtitle: 自定义偏好设置 & 实时预览
|
||||||
|
shortcut-key: 快捷键
|
||||||
theme: 主题
|
theme: 主题
|
||||||
appearance: 外观
|
appearance: 外观
|
||||||
theme-color: 主题色
|
theme-color: 主题色
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue