浏览代码

feat: add shortcut keys

vben 11 月之前
父节点
当前提交
977d108ca0

+ 2 - 0
packages/@vben-core/shared/iconify/src/material.ts

@@ -7,6 +7,8 @@ export const IcRoundKeyboardArrowDown = createIcon(
 );
 
 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 IcRoundMenu = createIcon('ic:round-menu');

+ 2 - 0
packages/@vben-core/shared/typings/src/preference.ts

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

+ 15 - 7
packages/business/common-ui/src/global-search/global-search.vue

@@ -21,7 +21,7 @@ import { isWindowsOs } from '@vben-core/toolkit';
 
 import { $t } from '@vben/locales';
 import { useMagicKeys, useToggle, whenever } from '@vueuse/core';
-import { onMounted, ref } from 'vue';
+import { ref } from 'vue';
 
 import SearchPanel from './search-panel.vue';
 
@@ -29,9 +29,13 @@ defineOptions({
   name: 'GlobalSearch',
 });
 
-withDefaults(defineProps<{ menus: MenuRecordRaw[] }>(), {
-  menus: () => [],
-});
+const props = withDefaults(
+  defineProps<{ enableShortcutKey?: boolean; menus: MenuRecordRaw[] }>(),
+  {
+    enableShortcutKey: true,
+    menus: () => [],
+  },
+);
 
 const [open, toggleOpen] = useToggle();
 const keyword = ref('');
@@ -41,13 +45,15 @@ function handleClose() {
   keyword.value = '';
 }
 
-onMounted(() => {
+if (props.enableShortcutKey) {
   const keys = useMagicKeys();
   const cmd = isWindowsOs() ? keys['ctrl+k'] : keys['cmd+k'];
   whenever(cmd, () => {
-    open.value = true;
+    if (props.enableShortcutKey) {
+      open.value = true;
+    }
   });
-});
+}
 </script>
 
 <template>
@@ -67,11 +73,13 @@ onMounted(() => {
             {{ $t('search.search') }}
           </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"
           >
             {{ isWindowsOs() ? 'Ctrl' : '⌘' }}
             <kbd>K</kbd>
           </span>
+          <span v-else></span>
         </div>
       </DialogTrigger>
       <DialogContent

+ 4 - 0
packages/business/common-ui/src/preference/blocks/general/general.vue

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

+ 2 - 0
packages/business/common-ui/src/preference/preference-widget.vue

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

+ 23 - 0
packages/business/common-ui/src/preference/preference.vue

@@ -62,6 +62,7 @@ const pageTransitionEnable = defineModel<boolean>('pageTransitionEnable');
 const layout = defineModel<LayoutType>('layout');
 const contentCompact = defineModel<string>('contentCompact');
 const sideVisible = defineModel<boolean>('sideVisible');
+const shortcutKeys = defineModel<boolean>('shortcutKeys');
 const tabsVisible = defineModel<boolean>('tabsVisible');
 const tabsIcon = defineModel<boolean>('tabsIcon');
 // const logoVisible = defineModel<boolean>('logoVisible');
@@ -95,6 +96,10 @@ const tabs = computed((): SegmentedItem[] => {
       label: $t('preference.general'),
       value: 'general',
     },
+    // {
+    //   label: $t('preference.shortcut-key'),
+    //   value: 'shortcutKey',
+    // },
   ];
 });
 
@@ -233,6 +238,24 @@ function handleReset() {
               <General
                 v-model:locale="locale"
                 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>
 

+ 39 - 3
packages/business/common-ui/src/user-dropdown/user-dropdown.vue

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

+ 6 - 1
packages/business/layouts/src/basic/header/header.vue

@@ -2,6 +2,7 @@
 import { VbenFullScreen } from '@vben-core/shadcn-ui';
 
 import { GlobalSearch, LanguageToggle, ThemeToggle } from '@vben/common-ui';
+import { preference } from '@vben/preference';
 import { useAccessStore } from '@vben/stores';
 
 interface Props {
@@ -30,7 +31,11 @@ const accessStore = useAccessStore();
     <slot name="menu"></slot>
   </div>
   <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" />
     <LanguageToggle class="mr-2" />
     <VbenFullScreen class="mr-2" />

+ 1 - 0
packages/locales/src/langs/en-US.yaml

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

+ 1 - 0
packages/locales/src/langs/zh-CN.yaml

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

+ 1 - 0
packages/preference/src/config.ts

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

+ 8 - 1
pnpm-lock.yaml

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