Browse Source

feat: add shortcut keys preferencs

vben 10 months ago
parent
commit
222c73963d
38 changed files with 850 additions and 326 deletions
  1. 1 1
      .gitignore
  2. 1 1
      apps/web-antd/src/views/_essential/fallback/internal-error.vue
  3. 4 4
      internal/lint-configs/eslint-config/package.json
  4. 1 1
      internal/tsconfig/package.json
  5. 1 1
      internal/vite-config/package.json
  6. 2 2
      package.json
  7. 6 1
      packages/@core/forward/preferences/src/config.ts
  8. 6 0
      packages/@core/forward/preferences/src/types.ts
  9. 29 0
      packages/@core/forward/preferences/src/use-preferences.ts
  10. 1 1
      packages/@core/shared/toolkit/package.json
  11. 1 7
      packages/business/common-ui/src/fallback/fallback.ts
  12. 45 9
      packages/business/common-ui/src/fallback/fallback.vue
  13. 262 0
      packages/business/common-ui/src/fallback/icons/icon-hello.vue
  14. 2 2
      packages/business/common-ui/src/preferences/blocks/general/animation.vue
  15. 2 6
      packages/business/common-ui/src/preferences/blocks/general/general.vue
  16. 1 0
      packages/business/common-ui/src/preferences/blocks/index.ts
  17. 7 7
      packages/business/common-ui/src/preferences/blocks/layout/breadcrumb.vue
  18. 1 1
      packages/business/common-ui/src/preferences/blocks/layout/content.vue
  19. 2 2
      packages/business/common-ui/src/preferences/blocks/layout/footer.vue
  20. 6 6
      packages/business/common-ui/src/preferences/blocks/layout/header.vue
  21. 2 2
      packages/business/common-ui/src/preferences/blocks/layout/interface-control.vue
  22. 10 10
      packages/business/common-ui/src/preferences/blocks/layout/layout.vue
  23. 6 6
      packages/business/common-ui/src/preferences/blocks/layout/navigation.vue
  24. 3 3
      packages/business/common-ui/src/preferences/blocks/layout/sidebar.vue
  25. 3 3
      packages/business/common-ui/src/preferences/blocks/layout/tabbar.vue
  26. 42 0
      packages/business/common-ui/src/preferences/blocks/shortcut-keys/global.vue
  27. 3 0
      packages/business/common-ui/src/preferences/blocks/switch-item.vue
  28. 2 2
      packages/business/common-ui/src/preferences/blocks/theme/color-mode.vue
  29. 4 4
      packages/business/common-ui/src/preferences/blocks/theme/theme.vue
  30. 14 0
      packages/business/common-ui/src/preferences/preferences-widget.vue
  31. 46 42
      packages/business/common-ui/src/preferences/preferences.vue
  32. 1 1
      packages/business/common-ui/src/preferences/trigger.vue
  33. 3 3
      packages/business/common-ui/src/theme-toggle/theme-toggle.vue
  34. 18 9
      packages/business/common-ui/src/user-dropdown/user-dropdown.vue
  35. 3 2
      packages/business/layouts/src/basic/header/header.vue
  36. 8 2
      packages/locales/src/langs/en-US.yaml
  37. 8 2
      packages/locales/src/langs/zh-CN.yaml
  38. 293 183
      pnpm-lock.yaml

+ 1 - 1
.gitignore

@@ -4,7 +4,7 @@ dist
 dist-ssr
 coverage
 *.local
-**/*/.vitepress/cache/deps
+**/.vitepress/cache
 .cache
 .turbo
 .stylelintcache

+ 1 - 1
apps/web-antd/src/views/_essential/fallback/internal-error.vue

@@ -3,5 +3,5 @@ import { Fallback } from '@vben/common-ui';
 </script>
 
 <template>
-  <Fallback :show-back="false" status="500" />
+  <Fallback status="500" />
 </template>

+ 4 - 4
internal/lint-configs/eslint-config/package.json

@@ -33,7 +33,7 @@
     "eslint-plugin-command": "^0.2.3"
   },
   "devDependencies": {
-    "@eslint/js": "^9.4.0",
+    "@eslint/js": "^9.5.0",
     "@types/eslint": "^8.56.10",
     "@typescript-eslint/eslint-plugin": "^7.13.0",
     "@typescript-eslint/parser": "^7.13.0",
@@ -41,18 +41,18 @@
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-eslint-comments": "^3.2.0",
     "eslint-plugin-i": "^2.29.1",
-    "eslint-plugin-jsdoc": "^48.2.11",
+    "eslint-plugin-jsdoc": "^48.2.12",
     "eslint-plugin-jsonc": "^2.16.0",
     "eslint-plugin-n": "^17.9.0",
     "eslint-plugin-no-only-tests": "^3.1.0",
     "eslint-plugin-perfectionist": "^2.11.0",
     "eslint-plugin-prettier": "^5.1.3",
     "eslint-plugin-regexp": "^2.6.0",
-    "eslint-plugin-unicorn": "^53.0.0",
+    "eslint-plugin-unicorn": "^54.0.0",
     "eslint-plugin-unused-imports": "^4.0.0",
     "eslint-plugin-vitest": "^0.5.4",
     "eslint-plugin-vue": "^9.26.0",
-    "globals": "^15.4.0",
+    "globals": "^15.5.0",
     "jsonc-eslint-parser": "^2.4.0",
     "vue-eslint-parser": "^9.4.3"
   }

+ 1 - 1
internal/tsconfig/package.json

@@ -20,6 +20,6 @@
   ],
   "dependencies": {
     "@vben/types": "workspace:*",
-    "vite": "6.0.0-alpha.17"
+    "vite": "5.3.0"
   }
 }

+ 1 - 1
internal/vite-config/package.json

@@ -48,7 +48,7 @@
     "rollup-plugin-visualizer": "^5.12.0",
     "sass": "^1.77.5",
     "unplugin-turbo-console": "^1.8.6",
-    "vite": "6.0.0-alpha.17",
+    "vite": "5.3.0",
     "vite-plugin-compression": "^0.5.1",
     "vite-plugin-dts": "^3.9.1",
     "vite-plugin-html": "^3.2.2",

+ 2 - 2
package.json

@@ -65,10 +65,10 @@
     "jsdom": "^24.1.0",
     "rimraf": "^5.0.7",
     "taze": "^0.13.8",
-    "turbo": "^2.0.3",
+    "turbo": "^2.0.4",
     "typescript": "^5.4.5",
     "unbuild": "^2.0.0",
-    "vite": "6.0.0-alpha.17",
+    "vite": "5.3.0",
     "vitest": "^2.0.0-beta.3",
     "vue-tsc": "^2.0.21"
   },

+ 6 - 1
packages/@core/forward/preferences/src/config.ts

@@ -45,7 +45,12 @@ const defaultPreferences: Preferences = {
     split: true,
     styleType: 'rounded',
   },
-  shortcutKeys: { enable: true },
+  shortcutKeys: {
+    enable: true,
+    globalLogout: true,
+    globalPreferences: true,
+    globalSearch: true,
+  },
   sidebar: {
     collapsed: false,
     collapsedShowTitle: true,

+ 6 - 0
packages/@core/forward/preferences/src/types.ts

@@ -112,6 +112,12 @@ interface SidebarPreferences {
 interface ShortcutKeyPreferences {
   /** 是否启用快捷键-全局 */
   enable: boolean;
+  /** 是否启用全局注销快捷键 */
+  globalLogout: boolean;
+  /** 是否启用全局偏好设置快捷键 */
+  globalPreferences: boolean;
+  /** 是否启用全局搜索快捷键 */
+  globalSearch: boolean;
 }
 
 interface TabbarPreferences {

+ 29 - 0
packages/@core/forward/preferences/src/use-preferences.ts

@@ -16,6 +16,8 @@ function usePreferences() {
 
   const appPreferences = computed(() => preferences.app);
 
+  const shortcutKeysPreferences = computed(() => preferences.shortcutKeys);
+
   /**
    * @zh_CN 判断是否为暗黑模式
    * @param  preferences - 当前偏好设置对象,它的主题值将被用来判断是否为暗黑模式。
@@ -107,11 +109,38 @@ function usePreferences() {
     return appPreferences.value.authPageLayout === 'panel-center';
   });
 
+  /**
+   * @zh_CN 是否启用全局搜索快捷键
+   */
+  const globalSearchShortcutKey = computed(() => {
+    const { enable, globalSearch } = shortcutKeysPreferences.value;
+    return enable && globalSearch;
+  });
+
+  /**
+   * @zh_CN 是否启用全局注销快捷键
+   */
+  const globalLogoutShortcutKey = computed(() => {
+    const { enable, globalLogout } = shortcutKeysPreferences.value;
+    return enable && globalLogout;
+  });
+
+  /**
+   * @zh_CN 是否启用全局偏好设置快捷键
+   */
+  const globalPreferencesShortcutKey = computed(() => {
+    const { enable, globalPreferences } = shortcutKeysPreferences.value;
+    return enable && globalPreferences;
+  });
+
   return {
     authPanelCenter,
     authPanelLeft,
     authPanelRight,
     diffPreference,
+    globalLogoutShortcutKey,
+    globalPreferencesShortcutKey,
+    globalSearchShortcutKey,
     isDark,
     isFullContent,
     isHeaderNav,

+ 1 - 1
packages/@core/shared/toolkit/package.json

@@ -40,7 +40,7 @@
   },
   "dependencies": {
     "@ctrl/tinycolor": "4.1.0",
-    "@vue/shared": "^3.4.27",
+    "@vue/shared": "^3.4.29",
     "dayjs": "^1.11.11",
     "defu": "^6.1.4",
     "nprogress": "^0.2.0"

+ 1 - 7
packages/business/common-ui/src/fallback/fallback.ts

@@ -13,16 +13,10 @@ interface FallbackProps {
    * @default pageNotFoundSvg
    */
   image?: string;
-
-  /**
-   *  @zh_CN 是否显示返回首页按钮
-   *  @default true
-   */
-  showBack?: boolean;
   /**
    *  @zh_CN 内置类型
    */
-  status?: '403' | '404' | '500' | 'offline';
+  status?: '403' | '404' | '500' | 'hello' | 'offline';
   /**
    *  @zh_CN 页面提示语
    */

+ 45 - 9
packages/business/common-ui/src/fallback/fallback.vue

@@ -5,7 +5,7 @@ import { computed, defineAsyncComponent } from 'vue';
 import { useRouter } from 'vue-router';
 
 import { $t } from '@vben/locales';
-import { IcRoundArrowBackIosNew } from '@vben-core/iconify';
+import { IcRoundArrowBackIosNew, IcRoundRefresh } from '@vben-core/iconify';
 import { VbenButton } from '@vben-core/shadcn-ui';
 
 interface Props extends FallbackProps {}
@@ -19,13 +19,14 @@ const props = withDefaults(defineProps<Props>(), {
   homePath: '/',
   image: '',
   showBack: true,
-  status: '404',
+  status: 'hello',
   title: '',
 });
 
 const Icon403 = defineAsyncComponent(() => import('./icons/icon-403.vue'));
 const Icon404 = defineAsyncComponent(() => import('./icons/icon-404.vue'));
 const Icon500 = defineAsyncComponent(() => import('./icons/icon-500.vue'));
+const IconHello = defineAsyncComponent(() => import('./icons/icon-hello.vue'));
 const IconOffline = defineAsyncComponent(
   () => import('./icons/icon-offline.vue'),
 );
@@ -39,6 +40,9 @@ const titleText = computed(() => {
     case '403': {
       return $t('fallback.forbidden');
     }
+    case '404': {
+      return $t('fallback.page-not-found');
+    }
     case '500': {
       return $t('fallback.internal-error');
     }
@@ -46,7 +50,7 @@ const titleText = computed(() => {
       return $t('fallback.offline-error');
     }
     default: {
-      return $t('fallback.page-not-found');
+      return '';
     }
   }
 });
@@ -59,6 +63,9 @@ const descText = computed(() => {
     case '403': {
       return $t('fallback.forbidden-desc');
     }
+    case '404': {
+      return $t('fallback.page-not-found-desc');
+    }
     case '500': {
       return $t('fallback.internal-error-desc');
     }
@@ -66,7 +73,7 @@ const descText = computed(() => {
       return $t('fallback.offline-error-desc');
     }
     default: {
-      return $t('fallback.page-not-found-desc');
+      return '';
     }
   }
 });
@@ -76,47 +83,76 @@ const fallbackIcon = computed(() => {
     case '403': {
       return Icon403;
     }
+    case '404': {
+      return Icon404;
+    }
     case '500': {
       return Icon500;
     }
     case 'offline': {
       return IconOffline;
     }
+    case 'hello': {
+      return IconHello;
+    }
     default: {
-      return Icon404;
+      return null;
     }
   }
 });
 
+const showBack = computed(() => {
+  return ['403', '404'].includes(props.status);
+});
+
+const showRefresh = computed(() => {
+  return ['500', 'offline'].includes(props.status);
+});
+
 const { push } = useRouter();
 
 // 返回首页
 function back() {
   push(props.homePath);
 }
+
+function refresh() {
+  location.reload();
+}
 </script>
 
 <template>
   <div class="flex size-full flex-col items-center justify-center duration-300">
     <img v-if="image" :src="image" class="md:1/3 w-1/2 lg:w-1/4" />
-    <component :is="fallbackIcon" v-else class="md:1/3 h-1/3 w-1/2 lg:w-1/4" />
+    <component
+      :is="fallbackIcon"
+      v-else-if="fallbackIcon"
+      class="md:1/3 h-1/3 w-1/2 lg:w-1/4"
+    />
     <div class="flex-col-center">
+      <slot v-if="$slots.title" name="title"></slot>
       <p
-        v-if="titleText"
+        v-else-if="titleText"
         class="text-foreground mt-12 text-3xl md:text-4xl lg:text-5xl"
       >
         {{ titleText }}
       </p>
+      <slot v-if="$slots.describe" name="describe"></slot>
       <p
-        v-if="descText"
+        v-else-if="descText"
         class="text-muted-foreground md:text-md my-6 lg:text-lg"
       >
         {{ descText }}
       </p>
-      <VbenButton v-if="showBack" size="lg" @click="back">
+      <slot v-if="$slots.action" name="action"></slot>
+      <VbenButton v-else-if="showBack" size="lg" @click="back">
         <IcRoundArrowBackIosNew class="mr-2" />
         {{ $t('common.back-to-home') }}
       </VbenButton>
+      <VbenButton v-else-if="showRefresh" size="lg" @click="refresh">
+        <IcRoundRefresh class="mr-2" />
+        {{ $t('common.refresh') }}
+      </VbenButton>
     </div>
   </div>
 </template>

+ 262 - 0
packages/business/common-ui/src/fallback/icons/icon-hello.vue

@@ -0,0 +1,262 @@
+<template>
+  <svg
+    data-name="Layer 1"
+    height="424.8366"
+    viewBox="0 0 979.32677 424.8366"
+    width="979.32677"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+  >
+    <path
+      d="M993.71816,412.83936H419.142a9.19888,9.19888,0,0,0,0,18.39776H435.417V651.3026a9.19888,9.19888,0,0,0,18.39776,0l.1398-220.06548h461.1557l42.52,220.06548a9.19887,9.19887,0,1,0,18.39775,0l2.67633-220.06548h15.01383a9.19888,9.19888,0,0,0,0-18.39776Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M518.73716,371.85047v38.9547H421.141a19.48915,19.48915,0,1,1-1.35523-38.95474q.67739-.02358,1.35523,0Z"
+      fill="#f2f2f2"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M521.13449,410.50552a1.49881,1.49881,0,0,1-1.49822,1.49822H419.40273a20.52615,20.52615,0,0,1,0-41.05229H519.63627a1.49827,1.49827,0,1,1,0,2.99653H419.40273a17.52964,17.52964,0,0,0,0,35.05924H519.63627A1.49883,1.49883,0,0,1,521.13449,410.50552Z"
+      fill="hsl(var(--color-primary))"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M518.73716,380.84H413.85905a.29966.29966,0,0,1-.00552-.59929H518.73716a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M518.73716,388.03169H413.85905a.29966.29966,0,0,1-.00552-.59929H518.73716a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M518.73716,395.22332H413.85905a.29966.29966,0,0,1-.00552-.59929H518.73716a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M518.73716,402.41487H413.85905a.29966.29966,0,0,1-.00552-.59929H518.73716a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M500.33941,330.80932v38.95474H402.74324a19.48915,19.48915,0,0,1-1.35522-38.95474q.67737-.02358,1.35522,0Z"
+      fill="#f2f2f2"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M502.73673,369.46442a1.49885,1.49885,0,0,1-1.49822,1.49826H401.005a20.52614,20.52614,0,0,1,0-41.05229H501.23851a1.49826,1.49826,0,1,1,0,2.99652H401.005a17.52964,17.52964,0,0,0,0,35.05928H501.23851A1.49884,1.49884,0,0,1,502.73673,369.46442Z"
+      fill="#3f3d56"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M500.33941,339.79886H395.4613a.29966.29966,0,0,1-.00553-.59929H500.33941a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M500.33941,346.99054H395.4613a.29966.29966,0,0,1-.00553-.59929H500.33941a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M500.33941,354.18217H395.4613a.29966.29966,0,0,1-.00553-.59929H500.33941a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M500.33941,361.37376H395.4613a.29966.29966,0,0,1-.00553-.59929H500.33941a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M613.87355,550.68347V516.71838a5.661,5.661,0,0,0-5.66085-5.66085H479.4284a5.661,5.661,0,0,0-5.66084,5.66085v33.96509Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <rect
+      fill="#ccc"
+      height="43.87158"
+      width="140.10602"
+      x="363.43092"
+      y="325.83868"
+    />
+    <path
+      d="M473.76756,620.02887V653.994a5.661,5.661,0,0,0,5.66084,5.66084H608.2127a5.661,5.661,0,0,0,5.66085-5.66084V620.02887Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <circle cx="432.77633" cy="294.70402" fill="#fff" r="4.24564" />
+    <circle cx="432.77633" cy="351.3125" fill="#fff" r="4.24564" />
+    <circle cx="433.00385" cy="406.72228" fill="#fff" r="4.24564" />
+    <path
+      d="M597.989,472.33053v38.9547H500.39287a19.48916,19.48916,0,0,1-1.35647-38.9547q.678-.02358,1.35647,0Z"
+      fill="#f2f2f2"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M600.38637,510.98558a1.49881,1.49881,0,0,1-1.49822,1.49822H498.65461a20.52615,20.52615,0,0,1-.0247-41.05229H598.88815a1.49827,1.49827,0,1,1,0,2.99653H498.65461a17.52963,17.52963,0,0,0,0,35.05923H598.88815A1.49885,1.49885,0,0,1,600.38637,510.98558Z"
+      fill="#3f3d56"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M597.989,481.32H493.111a.29966.29966,0,0,1-.00553-.59929H597.98913a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M597.989,488.51175H493.111a.29966.29966,0,0,1-.00553-.59929H597.98913a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M597.989,495.70338H493.111a.29966.29966,0,0,1-.00553-.59929H597.98913a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M597.989,502.89493H493.111a.29966.29966,0,0,1-.00553-.59929H597.98913a.29966.29966,0,0,1,0,.59929Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M483.36747,317.81415H438.90162a2.74745,2.74745,0,0,0-1.21689.28306l-11.22288,5.61835a2.0452,2.0452,0,0,0,0,3.76443l11.22288,5.61835a2.74718,2.74718,0,0,0,1.21689.28306h44.46585a2.33381,2.33381,0,0,0,2.4628-2.16532v-11.2367A2.3338,2.3338,0,0,0,483.36747,317.81415Z"
+      fill="#3f3d56"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M485.83027,319.97947v11.2367a2.33383,2.33383,0,0,1-2.4628,2.16532h-8.8589V317.81415h8.8589A2.33383,2.33383,0,0,1,485.83027,319.97947Z"
+      fill="hsl(var(--color-primary))"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M216.78083,537.99332a35.33951,35.33951,0,0,0,34.12552-6.01134c11.95262-10.03214,15.70013-26.56,18.74934-41.864q4.50949-22.63308,9.019-45.26617l-18.88217,13.00153c-13.57891,9.34993-27.46375,18.99939-36.86572,32.54233S209.42082,522.42587,216.975,537.08"
+      fill="#e6e6e6"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M218.39489,592.79741c-1.91113-13.92071-3.87625-28.0202-2.53572-42.09016,1.19057-12.4956,5.00277-24.70032,12.764-34.70734a57.73582,57.73582,0,0,1,14.81307-13.42309c1.48131-.935,2.84468,1.41257,1.36983,2.34348a54.88844,54.88844,0,0,0-21.71125,26.19626c-4.72684,12.02273-5.48591,25.12848-4.67135,37.90006.4926,7.72345,1.53656,15.39627,2.58859,23.05926a1.40615,1.40615,0,0,1-.94781,1.66928,1.3653,1.3653,0,0,1-1.6693-.94781Z"
+      fill="#f2f2f2"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M236.80246,568.16434a26.01425,26.01425,0,0,0,22.6665,11.69871c11.47417-.54466,21.04-8.55293,29.651-16.15584l25.46969-22.48783-16.85671-.80672c-12.12234-.58011-24.55745-1.12124-36.10356,2.617s-22.19457,12.73508-24.30583,24.68624"
+      fill="#e6e6e6"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M212.99392,600.79976c9.19853-16.27621,19.86805-34.36538,38.93262-40.14695A43.445,43.445,0,0,1,268.3022,558.962c1.73863.14991,1.30448,2.82994-.431,2.6803a40.36111,40.36111,0,0,0-26.133,6.91386c-7.36852,5.01554-13.10573,11.98848-17.96161,19.383-2.97439,4.52936-5.63867,9.25082-8.30346,13.966-.85161,1.50687-3.34078.41915-2.47922-1.10534Z"
+      fill="#f2f2f2"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M198.25523,617.93168a19.69836,19.69836,0,0,1,12.0709-16.49847v-9.40956h15.782v9.70608a19.68812,19.68812,0,0,1,11.41362,16.202l3.711,43.13835H194.54417Z"
+      fill="#f2f2f2"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M734.973,411.955l-4.69488-1.97685-3.22067-23.53551h-42.889l-3.491,23.43936-4.20031,2.10013a.99744.99744,0,0,0,.44611,1.88955h57.66283A.99739.99739,0,0,0,734.973,411.955Z"
+      fill="#e6e6e6"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M811.1898,389.574H600.50692a4.174,4.174,0,0,1-4.16467-4.174V355.69092H815.35446V385.4A4.17408,4.17408,0,0,1,811.1898,389.574Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M815.57469,369.73213H596.15V242.61337a5.0375,5.0375,0,0,1,5.03186-5.03167h209.361a5.03755,5.03755,0,0,1,5.03191,5.03167Z"
+      fill="#3f3d56"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M802.46932,360.50584h-193.214a3.88344,3.88344,0,0,1-3.87919-3.87908V250.68707a3.88365,3.88365,0,0,1,3.87919-3.87932h193.214a3.88366,3.88366,0,0,1,3.8792,3.87932V356.62676A3.88345,3.88345,0,0,1,802.46932,360.50584Z"
+      fill="#fff"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M751.57964,397.88662a11.6159,11.6159,0,0,1,17.666,2.27241l26.13446-4.64642,6.69716,15.19317-36.99908,6.04328a11.67883,11.67883,0,0,1-13.49855-18.86244Z"
+      fill="#ffb6b6"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M775.77611,417.286l27.24571-.33963,3.44882-.04668,55.43253-.69843s15.05312-14.3609,28.16068-29.1465l-1.83719-13.28833A54.29159,54.29159,0,0,0,870.023,340.1519C851.24988,352.696,840.363,377.52559,840.363,377.52559l-34.37018,8.22071-3.43848.82227-21.35608,5.10326Z"
+      fill="hsl(var(--color-primary))"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M915.25011,498.96167H864.39249c0,2.17915-55.59414,3.94772-55.59414,3.94772a20.30858,20.30858,0,0,0-3.33166,3.15818,19.59694,19.59694,0,0,0-4.58,12.63271v3.15818a19.74588,19.74588,0,0,0,19.73861,19.73861h94.62478a19.75579,19.75579,0,0,0,19.73862-19.73861v-3.15818A19.76607,19.76607,0,0,0,915.25011,498.96167Z"
+      fill="#e4e4e4"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <rect
+      fill="#e4e4e4"
+      height="118.48951"
+      width="20.52816"
+      x="747.4019"
+      y="303.23122"
+    />
+    <path
+      d="M799.31222,658.58132c0,2.218,31.10721.858,69.47992.858s69.47991,1.36012,69.47991-.858-31.1072-19.807-69.47991-19.807S799.31222,656.36323,799.31222,658.58132Z"
+      fill="#e4e4e4"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <polygon
+      fill="#ffb6b6"
+      points="675.186 407.461 659.908 407.46 652.64 348.531 675.188 348.532 675.186 407.461"
+    />
+    <path
+      d="M789.41863,659.852l-49.2623-.00183v-.62309a19.17528,19.17528,0,0,1,19.17426-19.17395h.00122l30.08773.00122Z"
+      fill="#2f2e41"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <polygon
+      fill="#ffb6b6"
+      points="630.031 407.461 614.753 407.46 607.485 348.531 630.033 348.532 630.031 407.461"
+    />
+    <path
+      d="M744.2636,659.852l-49.2623-.00183v-.62309a19.1753,19.1753,0,0,1,19.17426-19.17395h.00122l30.08773.00122Z"
+      fill="#2f2e41"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <circle cx="766.88656" cy="41.63615" fill="#ffb6b6" r="26.56401" />
+    <path
+      d="M920.21655,461.22417s8.91308,47.1307-24.99958,53.13247-82.86639,10.21993-82.86639,10.21993L790.36706,627.14324l-29.53443-2.63675s3.928-123.46737,13.5876-133.127,70.71212-38.58282,70.71212-38.58282Z"
+      fill="#2f2e41"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M853.98286,441.47135,839.151,456.35062s-107.0941,17.25-111.22553,41.9852c-6.23747,37.34427-13.60493,118.552-13.60493,118.552l32.1988-2.41491,12.62647-92.31123,51.5182-11.71874L869.27729,478.5Z"
+      fill="#2f2e41"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M902.78526,263.36115c-2.6223-4.94751-5.95413-14.80785-11.24679-16.63657a42.07731,42.07731,0,0,0-9.05841-1.92972l-8.99618,3.46009,4.89616-3.808q-1.42988-.08519-2.85817-.13928l-6.0699,2.33453,3.10542-2.41532c-5.65883-.05808-11.5.53031-15.88468,3.9752-3.73817,2.93677-7.44169,14.06185-8.04057,18.77753a35.9171,35.9171,0,0,0,.6603,13.53055l1.53716,1.46166a18.85936,18.85936,0,0,0,1.206-3.83883,18.18056,18.18056,0,0,1,8.70263-11.80641l.08368-.0472c2.5782-1.451,5.7065-1.3841,8.66308-1.27769l14.04158.50527c3.37829.12158,7.01608.33533,9.64978,2.45443a15.888,15.888,0,0,1,3.85826,5.58929c1.30868,2.6414,3.8661,12.60418,3.8661,12.60418s1.44689-1.88062,2.1404-.48092a48.39766,48.39766,0,0,0,2.01437-11.23347A22.00877,22.00877,0,0,0,902.78526,263.36115Z"
+      fill="#2f2e41"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M995.69426,290.88349A11.61582,11.61582,0,0,0,985.181,305.26136l-21.3614,15.75722,6.40951,15.31674,29.8539-22.67594a11.67883,11.67883,0,0,0-4.38876-22.77589Z"
+      fill="#ffb6b6"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M992.25627,323.052l-53.551,59.4744s-25.60913-8.19816-45.41466-17.08624l-8.8977-27.32787a54.34329,54.34329,0,0,1-2.60112-19.66442c27.45606-7.306,59.391,19.87863,59.391,19.87863l40.08517-31.39877Z"
+      fill="hsl(var(--color-primary))"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M867.301,465.6169c-9.554-3.30029-19.43312-6.71277-30.08912-7.99385l-.45773-.05533.12632-.443c11.03073-38.7308,8.27761-63.50657,2.87195-100.72306a37.59072,37.59072,0,0,1,21.5483-39.50121l.06542-.02958,30.43436-1.93391.06935-.00423,22.13437,6.50989a15.18313,15.18313,0,0,1,10.86724,14.83111c-.23987,12.23937.26868,25.9043.80711,40.37114,1.20787,32.45569,2.45686,66.01647-4.63045,87.79166l-.03718.11412-.09462.07416a36.09883,36.09883,0,0,1-23.08086,8.10758C887.90057,472.73235,877.76186,469.23034,867.301,465.6169Z"
+      fill="hsl(var(--color-primary))"
+      transform="translate(-110.33661 -237.5817)"
+    />
+    <path
+      d="M1088.24817,662.4183H111.75183a1.41521,1.41521,0,1,1,0-2.83042h976.49634a1.41521,1.41521,0,1,1,0,2.83042Z"
+      fill="#ccc"
+      transform="translate(-110.33661 -237.5817)"
+    />
+  </svg>
+</template>

+ 2 - 2
packages/business/common-ui/src/preferences/blocks/general/animation.vue

@@ -23,10 +23,10 @@ function handleClick(value: string) {
 
 <template>
   <SwitchItem v-model="transitionProgress">
-    {{ $t('preference.page-progress') }}
+    {{ $t('preferences.page-progress') }}
   </SwitchItem>
   <SwitchItem v-model="transitionEnable">
-    {{ $t('preference.page-transition') }}
+    {{ $t('preferences.page-transition') }}
   </SwitchItem>
   <div
     v-if="transitionEnable"

+ 2 - 6
packages/business/common-ui/src/preferences/blocks/general/general.vue

@@ -13,7 +13,6 @@ defineOptions({
 
 const appLocale = defineModel<string>('appLocale');
 const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
-const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');
 
 const localeItems: SelectListItem[] = SUPPORT_LANGUAGES.map((item) => ({
   label: item.text,
@@ -23,12 +22,9 @@ const localeItems: SelectListItem[] = SUPPORT_LANGUAGES.map((item) => ({
 
 <template>
   <SelectItem v-model="appLocale" :items="localeItems">
-    {{ $t('preference.language') }}
+    {{ $t('preferences.language') }}
   </SelectItem>
   <SwitchItem v-model="appDynamicTitle">
-    {{ $t('preference.dynamic-title') }}
-  </SwitchItem>
-  <SwitchItem v-model="shortcutKeysEnable">
-    {{ $t('preference.shortcut-key') }}
+    {{ $t('preferences.dynamic-title') }}
   </SwitchItem>
 </template>

+ 1 - 0
packages/business/common-ui/src/preferences/blocks/index.ts

@@ -9,6 +9,7 @@ export { default as Layout } from './layout/layout.vue';
 export { default as Navigation } from './layout/navigation.vue';
 export { default as Sidebar } from './layout/sidebar.vue';
 export { default as Tabbar } from './layout/tabbar.vue';
+export { default as GlobalShortcutKeys } from './shortcut-keys/global.vue';
 export { default as SwitchItem } from './switch-item.vue';
 export { default as ThemeColor } from './theme/color.vue';
 export { default as ColorMode } from './theme/color-mode.vue';

+ 7 - 7
packages/business/common-ui/src/preferences/blocks/layout/breadcrumb.vue

@@ -21,8 +21,8 @@ const breadcrumbShowHome = defineModel<boolean>('breadcrumbShowHome');
 const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
 
 const typeItems: SelectListItem[] = [
-  { label: $t('preference.normal'), value: 'normal' },
-  { label: $t('preference.breadcrumb-background'), value: 'background' },
+  { label: $t('preferences.normal'), value: 'normal' },
+  { label: $t('preferences.breadcrumb-background'), value: 'background' },
 ];
 
 const disableItem = computed(() => {
@@ -32,22 +32,22 @@ const disableItem = computed(() => {
 
 <template>
   <SwitchItem v-model="breadcrumbEnable" :disabled="disabled">
-    {{ $t('preference.breadcrumb-enable') }}
+    {{ $t('preferences.breadcrumb-enable') }}
   </SwitchItem>
   <SwitchItem v-model="breadcrumbHideOnlyOne" :disabled="disableItem">
-    {{ $t('preference.breadcrumb-hide-only-one') }}
+    {{ $t('preferences.breadcrumb-hide-only-one') }}
   </SwitchItem>
   <SwitchItem v-model="breadcrumbShowHome" :disabled="disableItem">
-    {{ $t('preference.breadcrumb-home') }}
+    {{ $t('preferences.breadcrumb-home') }}
   </SwitchItem>
   <SwitchItem v-model="breadcrumbShowIcon" :disabled="disableItem">
-    {{ $t('preference.breadcrumb-icon') }}
+    {{ $t('preferences.breadcrumb-icon') }}
   </SwitchItem>
   <ToggleItem
     v-model="breadcrumbStyleType"
     :disabled="disableItem"
     :items="typeItems"
   >
-    {{ $t('preference.breadcrumb-style') }}
+    {{ $t('preferences.breadcrumb-style') }}
   </ToggleItem>
 </template>

+ 1 - 1
packages/business/common-ui/src/preferences/blocks/layout/content.vue

@@ -18,7 +18,7 @@ const components: Record<string, Component> = {
 
 const PRESET = computed(() => [
   {
-    name: $t('preference.wide'),
+    name: $t('preferences.wide'),
     type: 'wide',
   },
   {

+ 2 - 2
packages/business/common-ui/src/preferences/blocks/layout/footer.vue

@@ -13,9 +13,9 @@ const footerFixed = defineModel<boolean>('footerFixed');
 
 <template>
   <SwitchItem v-model="footerEnable">
-    {{ $t('preference.footer-visible') }}
+    {{ $t('preferences.footer-visible') }}
   </SwitchItem>
   <SwitchItem v-model="footerFixed" :disabled="!footerEnable">
-    {{ $t('preference.footer-fixed') }}
+    {{ $t('preferences.footer-fixed') }}
   </SwitchItem>
 </template>

+ 6 - 6
packages/business/common-ui/src/preferences/blocks/layout/header.vue

@@ -17,19 +17,19 @@ const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
 
 const localeItems: SelectListItem[] = [
   {
-    label: $t('preference.header-mode-static'),
+    label: $t('preferences.header-mode-static'),
     value: 'static',
   },
   {
-    label: $t('preference.header-mode-fixed'),
+    label: $t('preferences.header-mode-fixed'),
     value: 'fixed',
   },
   {
-    label: $t('preference.header-mode-auto'),
+    label: $t('preferences.header-mode-auto'),
     value: 'auto',
   },
   {
-    label: $t('preference.header-mode-auto-scroll'),
+    label: $t('preferences.header-mode-auto-scroll'),
     value: 'auto-scroll',
   },
 ];
@@ -37,13 +37,13 @@ const localeItems: SelectListItem[] = [
 
 <template>
   <SwitchItem v-model="headerEnable" :disabled="disabled">
-    {{ $t('preference.header-visible') }}
+    {{ $t('preferences.header-visible') }}
   </SwitchItem>
   <SelectItem
     v-model="headerMode"
     :disabled="!headerEnable"
     :items="localeItems"
   >
-    {{ $t('preference.mode') }}
+    {{ $t('preferences.mode') }}
   </SelectItem>
 </template>

+ 2 - 2
packages/business/common-ui/src/preferences/blocks/layout/interface-control.vue

@@ -13,9 +13,9 @@ const logoVisible = defineModel<boolean>('logoVisible');
 
 <template>
   <SwitchItem v-model="tabsVisible">
-    {{ $t('preference.tabs-visible') }}
+    {{ $t('preferences.tabs-visible') }}
   </SwitchItem>
   <SwitchItem v-model="logoVisible">
-    {{ $t('preference.logo-visible') }}
+    {{ $t('preferences.logo-visible') }}
   </SwitchItem>
 </template>

+ 10 - 10
packages/business/common-ui/src/preferences/blocks/layout/layout.vue

@@ -37,28 +37,28 @@ const components: Record<LayoutType, Component> = {
 
 const PRESET = computed((): PresetItem[] => [
   {
-    name: $t('preference.vertical'),
-    tip: $t('preference.vertical-tip'),
+    name: $t('preferences.vertical'),
+    tip: $t('preferences.vertical-tip'),
     type: 'sidebar-nav',
   },
   {
-    name: $t('preference.two-column'),
-    tip: $t('preference.two-column-tip'),
+    name: $t('preferences.two-column'),
+    tip: $t('preferences.two-column-tip'),
     type: 'sidebar-mixed-nav',
   },
   {
-    name: $t('preference.horizontal'),
-    tip: $t('preference.vertical-tip'),
+    name: $t('preferences.horizontal'),
+    tip: $t('preferences.vertical-tip'),
     type: 'header-nav',
   },
   {
-    name: $t('preference.mixed-menu'),
-    tip: $t('preference.mixed-menu-tip'),
+    name: $t('preferences.mixed-menu'),
+    tip: $t('preferences.mixed-menu-tip'),
     type: 'mixed-nav',
   },
   {
-    name: $t('preference.full-content'),
-    tip: $t('preference.full-content-tip'),
+    name: $t('preferences.full-content'),
+    tip: $t('preferences.full-content-tip'),
     type: 'full-content',
   },
 ]);

+ 6 - 6
packages/business/common-ui/src/preferences/blocks/layout/navigation.vue

@@ -17,8 +17,8 @@ const navigationSplit = defineModel<boolean>('navigationSplit');
 const navigationAccordion = defineModel<boolean>('navigationAccordion');
 
 const stylesItems: SelectListItem[] = [
-  { label: $t('preference.rounded'), value: 'rounded' },
-  { label: $t('preference.plain'), value: 'plain' },
+  { label: $t('preferences.rounded'), value: 'rounded' },
+  { label: $t('preferences.plain'), value: 'plain' },
 ];
 </script>
 
@@ -28,18 +28,18 @@ const stylesItems: SelectListItem[] = [
     :disabled="disabled"
     :items="stylesItems"
   >
-    {{ $t('preference.navigation-style') }}
+    {{ $t('preferences.navigation-style') }}
   </ToggleItem>
   <SwitchItem
     v-model="navigationSplit"
     :disabled="disabledNavigationSplit || disabled"
   >
-    {{ $t('preference.navigation-split') }}
+    {{ $t('preferences.navigation-split') }}
     <template #tip>
-      {{ $t('preference.navigation-split-tip') }}
+      {{ $t('preferences.navigation-split-tip') }}
     </template>
   </SwitchItem>
   <SwitchItem v-model="navigationAccordion" :disabled="disabled">
-    {{ $t('preference.navigation-accordion') }}
+    {{ $t('preferences.navigation-accordion') }}
   </SwitchItem>
 </template>

+ 3 - 3
packages/business/common-ui/src/preferences/blocks/layout/sidebar.vue

@@ -18,15 +18,15 @@ const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
 
 <template>
   <SwitchItem v-model="sidebarEnable" :disabled="disabled">
-    {{ $t('preference.side-visible') }}
+    {{ $t('preferences.side-visible') }}
   </SwitchItem>
   <SwitchItem v-model="sidebarCollapsed" :disabled="!sidebarEnable || disabled">
-    {{ $t('preference.collapse') }}
+    {{ $t('preferences.collapse') }}
   </SwitchItem>
   <SwitchItem
     v-model="sidebarCollapsedShowTitle"
     :disabled="!sidebarEnable || disabled"
   >
-    {{ $t('preference.collapse-show-title') }}
+    {{ $t('preferences.collapse-show-title') }}
   </SwitchItem>
 </template>

+ 3 - 3
packages/business/common-ui/src/preferences/blocks/layout/tabbar.vue

@@ -15,12 +15,12 @@ const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
 
 <template>
   <SwitchItem v-model="tabbarEnable" :disabled="disabled">
-    {{ $t('preference.tabs-visible') }}
+    {{ $t('preferences.tabs-visible') }}
   </SwitchItem>
   <SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
-    {{ $t('preference.tabs-icon') }}
+    {{ $t('preferences.tabs-icon') }}
   </SwitchItem>
   <!-- <SwitchItem v-model="sideCollapseShowTitle" :disabled="!tabsVisible">
-    {{ $t('preference.collapse-show-title') }}
+    {{ $t('preferences.collapse-show-title') }}
   </SwitchItem> -->
 </template>

+ 42 - 0
packages/business/common-ui/src/preferences/blocks/shortcut-keys/global.vue

@@ -0,0 +1,42 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+
+import { $t } from '@vben/locales';
+import { isWindowsOs } from '@vben-core/toolkit';
+
+import SwitchItem from '../switch-item.vue';
+
+defineOptions({
+  name: 'PreferenceGeneralConfig',
+});
+
+const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');
+const shortcutKeysGlobalSearch = defineModel<boolean>(
+  'shortcutKeysGlobalSearch',
+);
+const shortcutKeysLogout = defineModel<boolean>('shortcutKeysLogout');
+const shortcutKeysPreferences = defineModel<boolean>('shortcutKeysPreferences');
+
+const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
+</script>
+
+<template>
+  <SwitchItem v-model="shortcutKeysEnable">
+    {{ $t('preferences.shortcut-keys.title') }}
+  </SwitchItem>
+  <SwitchItem v-if="shortcutKeysEnable" v-model="shortcutKeysGlobalSearch">
+    {{ $t('preferences.shortcut-keys.search') }}
+    <template #shortcut>
+      {{ isWindowsOs() ? 'Ctrl' : '⌘' }}
+      <kbd> K </kbd>
+    </template>
+  </SwitchItem>
+  <SwitchItem v-if="shortcutKeysEnable" v-model="shortcutKeysLogout">
+    {{ $t('preferences.shortcut-keys.logout') }}
+    <template #shortcut> {{ altView }} Q </template>
+  </SwitchItem>
+  <SwitchItem v-if="shortcutKeysEnable" v-model="shortcutKeysPreferences">
+    {{ $t('preferences.shortcut-keys.preferences') }}
+    <template #shortcut> {{ altView }} , </template>
+  </SwitchItem>
+</template>

+ 3 - 0
packages/business/common-ui/src/preferences/blocks/switch-item.vue

@@ -39,6 +39,9 @@ function handleClick() {
         <slot name="tip"></slot>
       </VbenTooltip>
     </span>
+    <span v-if="$slots.shortcut" class="ml-auto mr-2 text-xs opacity-60">
+      <slot name="shortcut"></slot>
+    </span>
     <Switch v-model:checked="checked" @click.stop />
   </div>
 </template>

+ 2 - 2
packages/business/common-ui/src/preferences/blocks/theme/color-mode.vue

@@ -18,9 +18,9 @@ const appColorGrayMode = defineModel<boolean>('appColorGrayMode', {
 
 <template>
   <SwitchItem v-model="appColorWeakMode">
-    {{ $t('preference.weak-mode') }}
+    {{ $t('preferences.weak-mode') }}
   </SwitchItem>
   <SwitchItem v-model="appColorGrayMode">
-    {{ $t('preference.gray-mode') }}
+    {{ $t('preferences.gray-mode') }}
   </SwitchItem>
 </template>

+ 4 - 4
packages/business/common-ui/src/preferences/blocks/theme/theme.vue

@@ -39,13 +39,13 @@ function activeClass(theme: string): string[] {
 function nameView(name: string) {
   switch (name) {
     case 'light': {
-      return $t('preference.light');
+      return $t('preferences.light');
     }
     case 'dark': {
-      return $t('preference.dark');
+      return $t('preferences.dark');
     }
     case 'auto': {
-      return $t('preference.follow-system');
+      return $t('preferences.follow-system');
     }
   }
 }
@@ -75,7 +75,7 @@ function nameView(name: string) {
       :disabled="modelValue !== 'light'"
       class="mt-6"
     >
-      {{ $t('preference.dark-menu') }}
+      {{ $t('preferences.dark-menu') }}
     </SwitchItem>
   </div>
 </template>

+ 14 - 0
packages/business/common-ui/src/preferences/preferences-widget.vue

@@ -32,6 +32,11 @@ import Preferences from './preferences.vue';
     :navigation-split="preferences.navigation.split"
     :navigation-style-type="preferences.navigation.styleType"
     :shortcut-keys-enable="preferences.shortcutKeys.enable"
+    :shortcut-keys-global-logout="preferences.shortcutKeys.globalLogout"
+    :shortcut-keys-global-preferences="
+      preferences.shortcutKeys.globalPreferences
+    "
+    :shortcut-keys-global-search="preferences.shortcutKeys.globalSearch"
     :sidebar-collapsed="preferences.sidebar.collapsed"
     :sidebar-collapsed-show-title="preferences.sidebar.collapsedShowTitle"
     :sidebar-enable="preferences.sidebar.enable"
@@ -103,6 +108,15 @@ import Preferences from './preferences.vue';
     @update:shortcut-keys-enable="
       (val) => updatePreferences({ shortcutKeys: { enable: val } })
     "
+    @update:shortcut-keys-global-logout="
+      (val) => updatePreferences({ shortcutKeys: { globalLogout: val } })
+    "
+    @update:shortcut-keys-global-preferences="
+      (val) => updatePreferences({ shortcutKeys: { globalPreferences: val } })
+    "
+    @update:shortcut-keys-global-search="
+      (val) => updatePreferences({ shortcutKeys: { globalSearch: val } })
+    "
     @update:sidebar-collapsed="
       (val) => updatePreferences({ sidebar: { collapsed: val } })
     "

+ 46 - 42
packages/business/common-ui/src/preferences/preferences.vue

@@ -39,6 +39,7 @@ import {
   Content,
   Footer,
   General,
+  GlobalShortcutKeys,
   Header,
   Layout,
   Navigation,
@@ -101,6 +102,15 @@ const footerEnable = defineModel<boolean>('footerEnable');
 const footerFixed = defineModel<boolean>('footerFixed');
 
 const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');
+const shortcutKeysGlobalSearch = defineModel<boolean>(
+  'shortcutKeysGlobalSearch',
+);
+const shortcutKeysGlobalLogout = defineModel<boolean>(
+  'shortcutKeysGlobalLogout',
+);
+const shortcutKeysGlobalPreferences = defineModel<boolean>(
+  'shortcutKeysGlobalPreferences',
+);
 
 const {
   diffPreference,
@@ -116,21 +126,21 @@ const { copy } = useClipboard();
 const tabs = computed((): SegmentedItem[] => {
   return [
     {
-      label: $t('preference.appearance'),
+      label: $t('preferences.appearance'),
       value: 'appearance',
     },
     {
-      label: $t('preference.layout'),
+      label: $t('preferences.layout'),
       value: 'layout',
     },
     {
-      label: $t('preference.general'),
+      label: $t('preferences.general'),
       value: 'general',
     },
-    // {
-    //   label: $t('preference.shortcut-key'),
-    //   value: 'shortcutKey',
-    // },
+    {
+      label: $t('preferences.shortcut-keys.title'),
+      value: 'shortcutKey',
+    },
   ];
 });
 
@@ -148,7 +158,7 @@ const { openPreferences } = useOpenPreferences();
 async function handleCopy() {
   await copy(JSON.stringify(diffPreference.value, null, 2));
 
-  toast($t('preference.copy-success'));
+  toast($t('preferences.copy-success'));
 }
 
 function handleReset() {
@@ -156,7 +166,7 @@ function handleReset() {
     return;
   }
   resetPreferences();
-  toast($t('preference.reset-success'));
+  toast($t('preferences.reset-success'));
 }
 </script>
 
@@ -164,8 +174,8 @@ function handleReset() {
   <div class="z-100 fixed right-0 top-1/3">
     <VbenSheet
       v-model:open="openPreferences"
-      :description="$t('preference.preferences-subtitle')"
-      :title="$t('preference.preferences')"
+      :description="$t('preferences.preferences-subtitle')"
+      :title="$t('preferences.preferences')"
     >
       <template #trigger>
         <Trigger />
@@ -173,7 +183,7 @@ function handleReset() {
       <template #extra>
         <VbenIconButton
           :disabled="!diffPreference"
-          :tooltip="$t('preference.reset-tip')"
+          :tooltip="$t('preferences.reset-tip')"
           class="relative"
         >
           <span
@@ -187,19 +197,19 @@ function handleReset() {
       <div class="p-5 pt-4">
         <VbenSegmented :tabs="tabs" default-value="appearance">
           <template #appearance>
-            <Block :title="$t('preference.theme')">
+            <Block :title="$t('preferences.theme')">
               <Theme
                 v-model="appThemeMode"
                 v-model:app-semi-dark-menu="appSemiDarkMenu"
               />
             </Block>
-            <Block :title="$t('preference.theme-color')">
+            <Block :title="$t('preferences.theme-color')">
               <ThemeColor
                 v-model="themeColorPrimary"
                 :color-primary-presets="colorPrimaryPresets"
               />
             </Block>
-            <Block :title="$t('preference.other')">
+            <Block :title="$t('preferences.other')">
               <ColorMode
                 v-model:app-color-gray-mode="appColorGrayMode"
                 v-model:app-color-weak-mode="appColorWeakMode"
@@ -207,14 +217,14 @@ function handleReset() {
             </Block>
           </template>
           <template #layout>
-            <Block :title="$t('preference.layout')">
+            <Block :title="$t('preferences.layout')">
               <Layout v-model="appLayout" />
             </Block>
-            <Block :title="$t('preference.content')">
+            <Block :title="$t('preferences.content')">
               <Content v-model="appContentCompact" />
             </Block>
 
-            <Block :title="$t('preference.sidebar')">
+            <Block :title="$t('preferences.sidebar')">
               <Sidebar
                 v-model:sidebar-collapsed="sidebarCollapsed"
                 v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle"
@@ -223,7 +233,7 @@ function handleReset() {
               />
             </Block>
 
-            <Block :title="$t('preference.header')">
+            <Block :title="$t('preferences.header')">
               <Header
                 v-model:headerEnable="headerEnable"
                 v-model:headerMode="headerMode"
@@ -231,7 +241,7 @@ function handleReset() {
               />
             </Block>
 
-            <Block :title="$t('preference.navigation-menu')">
+            <Block :title="$t('preferences.navigation-menu')">
               <Navigation
                 v-model:navigation-accordion="navigationAccordion"
                 v-model:navigation-split="navigationSplit"
@@ -241,7 +251,7 @@ function handleReset() {
               />
             </Block>
 
-            <Block :title="$t('preference.breadcrumb')">
+            <Block :title="$t('preferences.breadcrumb')">
               <Breadcrumb
                 v-model:breadcrumb-enable="breadcrumbEnable"
                 v-model:breadcrumb-hide-only-one="breadcrumbHideOnlyOne"
@@ -254,13 +264,13 @@ function handleReset() {
               />
             </Block>
 
-            <Block :title="$t('preference.tabs')">
+            <Block :title="$t('preferences.tabs')">
               <Tabbar
                 v-model:tabbar-enable="tabbarEnable"
                 v-model:tabbar-show-icon="tabbarShowIcon"
               />
             </Block>
-            <Block :title="$t('preference.footer')">
+            <Block :title="$t('preferences.footer')">
               <Footer
                 v-model:footer-enable="footerEnable"
                 v-model:footer-fixed="footerFixed"
@@ -268,15 +278,14 @@ function handleReset() {
             </Block>
           </template>
           <template #general>
-            <Block :title="$t('preference.general')">
+            <Block :title="$t('preferences.general')">
               <General
                 v-model:app-dynamic-title="appDynamicTitle"
                 v-model:app-locale="appLocale"
-                v-model:shortcut-keys-enable="shortcutKeysEnable"
               />
             </Block>
 
-            <Block :title="$t('preference.animation')">
+            <Block :title="$t('preferences.animation')">
               <Animation
                 v-model:transition-enable="transitionEnable"
                 v-model:transition-name="transitionName"
@@ -284,23 +293,18 @@ function handleReset() {
               />
             </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 :title="$t('preference.animation')">
-              <Animation
-                v-model:page-progress="pageProgress"
-                v-model:page-transition="pageTransition"
-                v-model:transition-enable="transitionEnable"
+          <template #shortcutKey>
+            <Block :title="$t('preferences.shortcut-keys.global')">
+              <GlobalShortcutKeys
+                v-model:shortcut-keys-enable="shortcutKeysEnable"
+                v-model:shortcut-keys-global-search="shortcutKeysGlobalSearch"
+                v-model:shortcut-keys-logout="shortcutKeysGlobalLogout"
+                v-model:shortcut-keys-preferences="
+                  shortcutKeysGlobalPreferences
+                "
               />
             </Block>
-          </template> -->
+          </template>
         </VbenSegmented>
       </div>
 
@@ -313,7 +317,7 @@ function handleReset() {
           @click="handleCopy"
         >
           <IcRoundFolderCopy class="mr-2 size-3" />
-          {{ $t('preference.copy') }}
+          {{ $t('preferences.copy') }}
         </VbenButton>
       </template>
     </VbenSheet>

+ 1 - 1
packages/business/common-ui/src/preferences/trigger.vue

@@ -11,7 +11,7 @@ defineOptions({
 
 <template>
   <VbenButton
-    :title="$t('preference.preferences')"
+    :title="$t('preferences.preferences')"
     class="bg-primary flex-col-center h-9 w-9 cursor-pointer rounded-l-md rounded-r-none border-none"
   >
     <IconSetting class="text-lg" />

+ 3 - 3
packages/business/common-ui/src/theme-toggle/theme-toggle.vue

@@ -39,17 +39,17 @@ const PRESETS = [
   {
     icon: IcRoundWbSunny,
     name: 'light',
-    title: $t('preference.light'),
+    title: $t('preferences.light'),
   },
   {
     icon: MdiMoonAndStars,
     name: 'dark',
-    title: $t('preference.dark'),
+    title: $t('preferences.dark'),
   },
   {
     icon: IcRoundMotionPhotosAuto,
     name: 'auto',
-    title: $t('preference.follow-system'),
+    title: $t('preferences.follow-system'),
   },
 ];
 </script>

+ 18 - 9
packages/business/common-ui/src/user-dropdown/user-dropdown.vue

@@ -6,7 +6,7 @@ import { computed, ref } from 'vue';
 
 import { $t } from '@vben/locales';
 import { IcRoundLogout, IcRoundSettingsSuggest } from '@vben-core/iconify';
-import { preferences } from '@vben-core/preferences';
+import { preferences, usePreferences } from '@vben-core/preferences';
 import {
   Badge,
   DropdownMenu,
@@ -72,14 +72,24 @@ const emit = defineEmits<{ logout: [] }>();
 const openPopover = ref(false);
 const openDialog = ref(false);
 
+const { globalLogoutShortcutKey, globalPreferencesShortcutKey } =
+  usePreferences();
 const { handleOpenPreference } = useOpenPreferences();
 
 const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));
 
-const shortcutKeys = computed(() => {
+const enableLogoutShortcutKey = computed(() => {
+  return props.enableShortcutKey && globalLogoutShortcutKey.value;
+});
+
+const enableShortcutKey = computed(() => {
   return props.enableShortcutKey && preferences.shortcutKeys.enable;
 });
 
+const enablePreferencesShortcutKey = computed(() => {
+  return props.enableShortcutKey && globalPreferencesShortcutKey.value;
+});
+
 function handleLogout() {
   // emit
   openDialog.value = true;
@@ -91,16 +101,16 @@ function handleSubmitLogout() {
   openDialog.value = false;
 }
 
-if (shortcutKeys.value) {
+if (enableShortcutKey.value) {
   const keys = useMagicKeys();
   whenever(keys['Alt+KeyQ'], () => {
-    if (shortcutKeys.value) {
+    if (enableLogoutShortcutKey.value) {
       handleLogout();
     }
   });
 
   whenever(keys['Alt+Comma'], () => {
-    if (shortcutKeys.value) {
+    if (enablePreferencesShortcutKey.value) {
       handleOpenPreference();
     }
   });
@@ -161,13 +171,12 @@ if (shortcutKeys.value) {
       </DropdownMenuItem>
       <DropdownMenuSeparator />
       <DropdownMenuItem
-        v-if="preferences.shortcutKeys.enable"
         class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
         @click="handleOpenPreference"
       >
         <IcRoundSettingsSuggest class="mr-2 size-5" />
-        {{ $t('preference.preferences') }}
-        <DropdownMenuShortcut v-if="shortcutKeys">
+        {{ $t('preferences.preferences') }}
+        <DropdownMenuShortcut v-if="enablePreferencesShortcutKey">
           {{ altView }} ,
         </DropdownMenuShortcut>
       </DropdownMenuItem>
@@ -178,7 +187,7 @@ if (shortcutKeys.value) {
       >
         <IcRoundLogout class="mr-2 size-5" />
         {{ $t('common.logout') }}
-        <DropdownMenuShortcut v-if="shortcutKeys">
+        <DropdownMenuShortcut v-if="enableLogoutShortcutKey">
           {{ altView }} Q
         </DropdownMenuShortcut>
       </DropdownMenuItem>

+ 3 - 2
packages/business/layouts/src/basic/header/header.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 import { GlobalSearch, LanguageToggle, ThemeToggle } from '@vben/common-ui';
-import { preferences } from '@vben-core/preferences';
+import { usePreferences } from '@vben-core/preferences';
 import { VbenFullScreen } from '@vben-core/shadcn-ui';
 import { useAccessStore } from '@vben-core/stores';
 
@@ -20,6 +20,7 @@ withDefaults(defineProps<Props>(), {
 });
 
 const accessStore = useAccessStore();
+const { globalSearchShortcutKey } = usePreferences();
 </script>
 
 <template>
@@ -31,7 +32,7 @@ const accessStore = useAccessStore();
   </div>
   <div class="flex h-full min-w-0 flex-shrink-0 items-center">
     <GlobalSearch
-      :enable-shortcut-key="preferences.shortcutKeys.enable"
+      :enable-shortcut-key="globalSearchShortcutKey"
       :menus="accessStore.getAccessMenus"
       class="mr-4"
     />

+ 8 - 2
packages/locales/src/langs/en-US.yaml

@@ -8,6 +8,7 @@ common:
   confirm: Comfirm
   search: Search
   not-data: No data
+  refresh: Refresh
 
 layout:
   center: Align Center
@@ -114,11 +115,10 @@ page:
   fallback:
     page: Exception Page
 
-preference:
+preferences:
   preferences: Preferences
   preferences-subtitle: Customize Preferences & Preview in Real Time
   theme: Theme
-  shortcut-key: Shortcut Key
   appearance: Appearance
   theme-color: Theme Color
   layout: Layout
@@ -187,3 +187,9 @@ preference:
   footer-visible: Display Footer
   logo-visible: Display Logo
   reset-tip: The data has changed, click to reset
+  shortcut-keys:
+    title: Shortcut Keys
+    global: Global
+    search: Global Search
+    logout: Logout
+    preferences: Preferences

+ 8 - 2
packages/locales/src/langs/zh-CN.yaml

@@ -7,6 +7,7 @@ common:
   cancel: 取消
   confirm: 确认
   not-data: 暂无数据
+  refresh: 刷新
 
 layout:
   center: 居中
@@ -113,10 +114,9 @@ page:
   fallback:
     page: 异常页面
 
-preference:
+preferences:
   preferences: 偏好设置
   preferences-subtitle: 自定义偏好设置 & 实时预览
-  shortcut-key: 快捷键
   theme: 主题
   appearance: 外观
   theme-color: 主题色
@@ -186,3 +186,9 @@ preference:
   footer-fixed: 固定在底部
   logo-visible: 显示 Logo
   reset-tip: 数据有变化,点击可进行重置
+  shortcut-keys:
+    title: 快捷键
+    global: 全局
+    search: 全局搜索
+    logout: 退出登录
+    preferences: 偏好设置

File diff suppressed because it is too large
+ 293 - 183
pnpm-lock.yaml


Some files were not shown because too many files changed in this diff