Parcourir la source

refactor: refactored multi-language modules to support lazy loading and remote loading

Vben il y a 4 ans
Parent
commit
f6cef1088d
47 fichiers modifiés avec 353 ajouts et 284 suppressions
  1. 1 0
      .ls-lint.yml
  2. 4 0
      CHANGELOG.zh_CN.md
  3. 2 2
      package.json
  4. 3 8
      src/App.vue
  5. 7 9
      src/components/Application/src/AppLocalePicker.vue
  6. 2 2
      src/components/Markdown/src/index.vue
  7. 1 1
      src/components/SimpleMenu/src/SimpleSubMenu.vue
  8. 4 8
      src/enums/cacheEnum.ts
  9. 3 18
      src/hooks/setting/index.ts
  10. 0 38
      src/hooks/setting/useLocaleSetting.ts
  11. 1 0
      src/hooks/web/useI18n.ts
  12. 1 1
      src/hooks/web/usePermission.ts
  13. 1 1
      src/layouts/default/header/components/Breadcrumb.vue
  14. 1 1
      src/layouts/default/header/components/user-dropdown/index.vue
  15. 5 4
      src/layouts/default/header/index.vue
  16. 2 2
      src/layouts/default/tabs/useMultipleTabs.ts
  17. 0 13
      src/locales/constant.ts
  18. 0 4
      src/locales/getMessage.ts
  19. 14 9
      src/locales/helper.ts
  20. 13 0
      src/locales/lang/en.ts
  21. 1 1
      src/locales/lang/en/layout/multipleTab.ts
  22. 2 0
      src/locales/lang/en/routes/demo/page.ts
  23. 13 0
      src/locales/lang/zh_CN.ts
  24. 26 17
      src/locales/setupI18n.ts
  25. 0 1
      src/locales/types.ts
  26. 49 40
      src/locales/useLocale.ts
  27. 2 2
      src/logics/error-handle/index.ts
  28. 29 10
      src/logics/initAppConfig.ts
  29. 24 21
      src/main.ts
  30. 2 2
      src/router/guard/httpGuard.ts
  31. 2 3
      src/router/guard/messageGuard.ts
  32. 4 3
      src/router/index.ts
  33. 5 0
      src/router/routes/mainOut.ts
  34. 29 0
      src/settings/localeSetting.ts
  35. 1 13
      src/settings/projectSetting.ts
  36. 2 2
      src/store/modules/error.ts
  37. 44 0
      src/store/modules/locale.ts
  38. 1 1
      src/store/modules/permission.ts
  39. 6 6
      src/utils/cache/index.ts
  40. 2 2
      src/utils/cache/memory.ts
  41. 6 6
      src/utils/cache/persistent.ts
  42. 15 8
      src/utils/env.ts
  43. 2 3
      src/views/sys/lock/useNow.ts
  44. 3 3
      src/views/sys/login/Login.vue
  45. 4 5
      types/config.d.ts
  46. 1 1
      types/module.d.ts
  47. 13 13
      yarn.lock

+ 1 - 0
.ls-lint.yml

@@ -21,3 +21,4 @@ ignore:
   - dist
   - .local
   - .husky
+  - src/locales/lang

+ 4 - 0
CHANGELOG.zh_CN.md

@@ -1,5 +1,9 @@
 ## Wip
 
+### ✨ Refactor
+
+- 重构多语言模块,支持懒加载及远程加载
+
 ### ✨ Features
 
 - axios 支持 form-data 格式请求

+ 2 - 2
package.json

@@ -28,7 +28,7 @@
     "@iconify/iconify": "^2.0.0-rc.6",
     "@vueuse/core": "^4.3.0",
     "@zxcvbn-ts/core": "^0.2.0",
-    "ant-design-vue": "2.0.0",
+    "ant-design-vue": "2.0.1",
     "apexcharts": "^3.25.0",
     "axios": "^0.21.1",
     "crypto-js": "^4.0.0",
@@ -106,7 +106,7 @@
     "vite-plugin-pwa": "^0.5.5",
     "vite-plugin-style-import": "^0.7.5",
     "vite-plugin-theme": "^0.4.8",
-    "vite-plugin-windicss": "0.5.4",
+    "vite-plugin-windicss": "0.6.0",
     "vue-eslint-parser": "^7.5.0",
     "yargs": "^16.2.0"
   },

+ 3 - 8
src/App.vue

@@ -1,5 +1,5 @@
 <template>
-  <ConfigProvider v-bind="lockEvent" :locale="antConfigLocale">
+  <ConfigProvider v-bind="lockEvent" :locale="getAntdLocale">
     <AppProvider>
       <RouterView />
     </AppProvider>
@@ -21,9 +21,7 @@
     components: { ConfigProvider, AppProvider },
     setup() {
       // support Multi-language
-      const { antConfigLocale, setLocale } = useLocale();
-
-      setLocale();
+      const { getAntdLocale } = useLocale();
 
       // Initialize vuex internal system configuration
       initAppConfigStore();
@@ -31,10 +29,7 @@
       // Create a lock screen monitor
       const lockEvent = useLockPage();
 
-      return {
-        antConfigLocale,
-        lockEvent,
-      };
+      return { getAntdLocale, lockEvent };
     },
   });
 </script>

+ 7 - 9
src/components/Application/src/AppLocalePicker.vue

@@ -18,7 +18,7 @@
   </Dropdown>
 </template>
 <script lang="ts">
-  import type { LocaleType } from '/@/locales/types';
+  import type { LocaleType } from '/#/config';
   import type { DropMenu } from '/@/components/Dropdown';
 
   import { defineComponent, ref, watchEffect, unref, computed } from 'vue';
@@ -26,7 +26,7 @@
   import Icon from '/@/components/Icon';
 
   import { useLocale } from '/@/locales/useLocale';
-  import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
+  import { localeList } from '/@/settings/localeSetting';
   import { useDesign } from '/@/hooks/web/useDesign';
   import { propTypes } from '/@/utils/propTypes';
 
@@ -44,9 +44,7 @@
 
       const { prefixCls } = useDesign('app-locale-picker');
 
-      const { localeList } = useLocaleSetting();
-
-      const { changeLocale, getLang } = useLocale();
+      const { changeLocale, getLocale } = useLocale();
 
       const getLangText = computed(() => {
         const key = selectedKeys.value[0];
@@ -55,17 +53,17 @@
       });
 
       watchEffect(() => {
-        selectedKeys.value = [unref(getLang)];
+        selectedKeys.value = [unref(getLocale)];
       });
 
-      function toggleLocale(lang: LocaleType | string) {
-        changeLocale(lang as LocaleType);
+      async function toggleLocale(lang: LocaleType | string) {
+        await changeLocale(lang as LocaleType);
         selectedKeys.value = [lang as string];
         props.reload && location.reload();
       }
 
       function handleMenuEvent(menu: DropMenu) {
-        if (unref(getLang) === menu.event) return;
+        if (unref(getLocale) === menu.event) return;
         toggleLocale(menu.event as string);
       }
 

+ 2 - 2
src/components/Markdown/src/index.vue

@@ -34,13 +34,13 @@
 
       const modalFn = useModalContext();
 
-      const { getLang } = useLocale();
+      const { getLocale } = useLocale();
 
       watchEffect(() => {});
 
       const getCurrentLang = computed((): 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' => {
         let lang: Lang;
-        switch (unref(getLang)) {
+        switch (unref(getLocale)) {
           case 'en':
             lang = 'en_US';
             break;

+ 1 - 1
src/components/SimpleMenu/src/SimpleSubMenu.vue

@@ -52,7 +52,6 @@
   import { propTypes } from '/@/utils/propTypes';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
-  const { t } = useI18n();
 
   export default defineComponent({
     name: 'SimpleSubMenu',
@@ -73,6 +72,7 @@
       theme: propTypes.oneOf(['dark', 'light']),
     },
     setup(props) {
+      const { t } = useI18n();
       const { prefixCls } = useDesign('simple-menu');
 
       const getShowMenu = computed(() => {

+ 4 - 8
src/enums/cacheEnum.ts

@@ -1,6 +1,8 @@
 // token key
 export const TOKEN_KEY = 'TOKEN__';
 
+export const LOCALE_KEY = 'LOCALE__';
+
 // user info key
 export const USER_INFO_KEY = 'USER__INFO__';
 
@@ -14,16 +16,10 @@ export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__';
 export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__';
 
 // base global local key
-export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__';
-
-// base global session key
-export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__';
-
-// base global local key
-export const APP_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__';
+export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__';
 
 // base global session key
-export const APP_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__';
+export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__';
 
 export enum CacheTypeEnum {
   SESSION,

+ 3 - 18
src/hooks/setting/index.ts

@@ -1,26 +1,16 @@
-import type { ProjectConfig, GlobConfig, GlobEnvConfig } from '/#/config';
-
-import { getConfigFileName } from '../../../build/getConfigFileName';
-
-import getProjectSetting from '/@/settings/projectSetting';
+import type { GlobConfig } from '/#/config';
 
 import { warn } from '/@/utils/log';
-import { getGlobEnvConfig, isDevMode } from '/@/utils/env';
+import { getAppEnvConfig } from '/@/utils/env';
 
 export const useGlobSetting = (): Readonly<GlobConfig> => {
-  const ENV_NAME = getConfigFileName(import.meta.env);
-
-  const ENV = ((isDevMode()
-    ? getGlobEnvConfig()
-    : window[ENV_NAME as any]) as unknown) as GlobEnvConfig;
-
   const {
     VITE_GLOB_APP_TITLE,
     VITE_GLOB_API_URL,
     VITE_GLOB_APP_SHORT_NAME,
     VITE_GLOB_API_URL_PREFIX,
     VITE_GLOB_UPLOAD_URL,
-  } = ENV;
+  } = getAppEnvConfig();
 
   if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
     warn(
@@ -38,8 +28,3 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
   };
   return glob as Readonly<GlobConfig>;
 };
-
-export const useProjectSetting = (): ProjectConfig => {
-  // TODO computed
-  return getProjectSetting;
-};

+ 0 - 38
src/hooks/setting/useLocaleSetting.ts

@@ -1,38 +0,0 @@
-import type { LocaleSetting } from '/#/config';
-
-import { computed, unref } from 'vue';
-import { appStore } from '/@/store/modules/app';
-
-import getProjectSetting from '/@/settings/projectSetting';
-import { localeList } from '/@/locales/constant';
-
-// Get locale configuration
-const getLocale = computed(() => appStore.getProjectConfig.locale || getProjectSetting.locale);
-
-// get current language
-const getLang = computed(() => unref(getLocale).lang);
-
-// get Available Locales
-const getAvailableLocales = computed((): string[] => unref(getLocale).availableLocales);
-
-// get Fallback Locales
-const getFallbackLocale = computed((): string => unref(getLocale).fallback);
-
-const getShowLocale = computed(() => unref(getLocale).show);
-
-// Set locale configuration
-function setLocale(locale: Partial<LocaleSetting>): void {
-  appStore.commitProjectConfigState({ locale });
-}
-
-export function useLocaleSetting() {
-  return {
-    getLocale,
-    getLang,
-    localeList,
-    setLocale,
-    getShowLocale,
-    getAvailableLocales,
-    getFallbackLocale,
-  };
-}

+ 1 - 0
src/hooks/web/useI18n.ts

@@ -40,6 +40,7 @@ export function useI18n(
 
   const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {
     if (!key) return '';
+    if (!key.includes('.')) return key;
     return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters));
   };
   return {

+ 1 - 1
src/hooks/web/usePermission.ts

@@ -58,7 +58,7 @@ export function usePermission() {
         return def;
       }
       if (!isArray(value)) {
-        return userStore.getRoleListState.includes(value as RoleEnum);
+        return userStore.getRoleListState?.includes(value as RoleEnum);
       }
       return (intersection(value, userStore.getRoleListState) as RoleEnum[]).length > 0;
     }

+ 1 - 1
src/layouts/default/header/components/Breadcrumb.vue

@@ -18,7 +18,6 @@
 
   import { defineComponent, ref, toRaw, watchEffect } from 'vue';
   import { Breadcrumb } from 'ant-design-vue';
-  import { useI18n } from 'vue-i18n';
 
   import { useRouter } from 'vue-router';
   import { filter } from '/@/utils/helper/treeHelper';
@@ -33,6 +32,7 @@
   import { propTypes } from '/@/utils/propTypes';
   import { useGo } from '/@/hooks/web/usePage';
   import { isString } from '/@/utils/is';
+  import { useI18n } from '/@/hooks/web/useI18n';
 
   export default defineComponent({
     name: 'LayoutBreadcrumb',

+ 1 - 1
src/layouts/default/header/components/user-dropdown/index.vue

@@ -17,7 +17,7 @@
           icon="ion:document-text-outline"
           v-if="getShowDoc"
         />
-        <MenuDivider />
+        <MenuDivider v-if="getShowDoc" />
         <MenuItem
           key="lock"
           :text="t('layout.header.tooltipLock')"

+ 5 - 4
src/layouts/default/header/index.vue

@@ -42,7 +42,7 @@
       <FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
 
       <AppLocalePicker
-        v-if="getShowLocale"
+        v-if="getShowLocalePicker"
         :reload="true"
         :showText="false"
         :class="`${prefixCls}-action__item`"
@@ -69,7 +69,6 @@
   import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
   import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
   import { useRootSetting } from '/@/hooks/setting/useRootSetting';
-  import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
 
   import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
   import { SettingButtonPositionEnum } from '/@/enums/appEnum';
@@ -80,6 +79,7 @@
   import { useDesign } from '/@/hooks/web/useDesign';
 
   import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+  import { useLocale } from '/@/locales/useLocale';
 
   export default defineComponent({
     name: 'LayoutHeader',
@@ -112,7 +112,6 @@
         getMenuWidth,
         getIsMixSidebar,
       } = useMenuSetting();
-      const { getShowLocale } = useLocaleSetting();
       const {
         getUseErrorHandle,
         getShowSettingButton,
@@ -130,6 +129,8 @@
         getShowHeader,
       } = useHeaderSetting();
 
+      const { getShowLocalePicker } = useLocale();
+
       const { getIsMobile } = useAppInject();
 
       const getHeaderClass = computed(() => {
@@ -185,7 +186,7 @@
         getSplit,
         getMenuMode,
         getShowTopMenu,
-        getShowLocale,
+        getShowLocalePicker,
         getShowFullScreen,
         getShowNotice,
         getUseLockPage,

+ 2 - 2
src/layouts/default/tabs/useMultipleTabs.ts

@@ -1,11 +1,11 @@
 import { toRaw, ref, nextTick } from 'vue';
 import { RouteLocationNormalized } from 'vue-router';
-import { useProjectSetting } from '/@/hooks/setting';
 import { useDesign } from '/@/hooks/web/useDesign';
 import { useSortable } from '/@/hooks/web/useSortable';
 import router from '/@/router';
 import { tabStore } from '/@/store/modules/tab';
 import { isNullAndUnDef } from '/@/utils/is';
+import projectSetting from '/@/settings/projectSetting';
 
 export function initAffixTabs(): string[] {
   const affixList = ref<RouteLocationNormalized[]>([]);
@@ -47,7 +47,7 @@ export function initAffixTabs(): string[] {
 }
 
 export function useTabsDrag(affixTextList: string[]) {
-  const { multiTabsSetting } = useProjectSetting();
+  const { multiTabsSetting } = projectSetting;
 
   const { prefixCls } = useDesign('multiple-tabs');
   nextTick(() => {

+ 0 - 13
src/locales/constant.ts

@@ -1,13 +0,0 @@
-import type { DropMenu } from '/@/components/Dropdown';
-
-// locale list
-export const localeList: DropMenu[] = [
-  {
-    text: '简体中文',
-    event: 'zh_CN',
-  },
-  {
-    text: 'English',
-    event: 'en',
-  },
-];

+ 0 - 4
src/locales/getMessage.ts

@@ -1,4 +0,0 @@
-import { genMessage } from './helper';
-const modules = import.meta.globEager('./lang/**/*.ts');
-
-export default genMessage(modules);

+ 14 - 9
src/locales/helper.ts

@@ -4,16 +4,21 @@ export function genMessage(langs: Record<string, Record<string, any>>, prefix =
   const obj: Recordable = {};
 
   Object.keys(langs).forEach((key) => {
-    const mod = langs[key].default;
-    let k = key.replace(`./${prefix}/`, '').replace(/^\.\//, '');
-    const lastIndex = k.lastIndexOf('.');
-    k = k.substring(0, lastIndex);
-    const keyList = k.split('/');
-    const lang = keyList.shift();
+    const langFileModule = langs[key].default;
+    let fileName = key.replace(`./${prefix}/`, '').replace(/^\.\//, '');
+    const lastIndex = fileName.lastIndexOf('.');
+    fileName = fileName.substring(0, lastIndex);
+    const keyList = fileName.split('/');
+    const moduleName = keyList.shift();
     const objKey = keyList.join('.');
-    if (lang) {
-      set(obj, lang, obj[lang] || {});
-      set(obj[lang], objKey, mod);
+
+    if (moduleName) {
+      if (objKey) {
+        set(obj, moduleName, obj[moduleName] || {});
+        set(obj[moduleName], objKey, langFileModule);
+      } else {
+        set(obj, moduleName, langFileModule || {});
+      }
     }
   });
   return obj;

+ 13 - 0
src/locales/lang/en.ts

@@ -0,0 +1,13 @@
+import { genMessage } from '../helper';
+const modules = import.meta.globEager('./en/**/*.ts');
+import antdLocale from 'ant-design-vue/es/locale/en_US';
+import momentLocale from 'moment/dist/locale/eu';
+
+export default {
+  message: {
+    ...genMessage(modules, 'en'),
+    antdLocale,
+  },
+  momentLocale,
+  momentLocaleName: 'eu',
+};

+ 1 - 1
src/locales/lang/en/layout/multipleTab.ts

@@ -1,5 +1,5 @@
 export default {
-  redo: 'Refresh current',
+  reload: 'Refresh current',
   close: 'Close current',
   closeLeft: 'Close Left',
   closeRight: 'Close Right',

+ 2 - 0
src/locales/lang/en/routes/demo/page.ts

@@ -25,4 +25,6 @@ export default {
   list: 'List page',
   listCard: 'Card list',
   basic: 'Basic list',
+  listBasic: 'Basic list',
+  listSearch: 'Search list',
 };

+ 13 - 0
src/locales/lang/zh_CN.ts

@@ -0,0 +1,13 @@
+import { genMessage } from '../helper';
+const modules = import.meta.globEager('./zh_CN/**/*.ts');
+import antdLocale from 'ant-design-vue/es/locale/zh_CN';
+import momentLocale from 'moment/dist/locale/zh-cn';
+
+export default {
+  message: {
+    ...genMessage(modules, 'zh_CN'),
+    antdLocale,
+  },
+  momentLocale,
+  momentLocaleName: 'zh-cn',
+};

+ 26 - 17
src/locales/setupI18n.ts

@@ -3,27 +3,36 @@ import type { I18n, I18nOptions } from 'vue-i18n';
 
 import { createI18n } from 'vue-i18n';
 
-import projectSetting from '/@/settings/projectSetting';
+import { localeStore } from '/@/store/modules/locale';
+import { localeSetting } from '/@/settings/localeSetting';
 
-import messages from './getMessage';
+const { fallback, availableLocales } = localeSetting;
 
-const { lang, availableLocales, fallback } = projectSetting?.locale;
+export let i18n: ReturnType<typeof createI18n>;
 
-const localeData: I18nOptions = {
-  legacy: false,
-  locale: lang,
-  fallbackLocale: fallback,
-  messages,
-  availableLocales: availableLocales,
-  sync: true, //If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false.
-  silentTranslationWarn: true, // true - warning off
-  missingWarn: false,
-  silentFallbackWarn: true,
-};
-export let i18n: I18n;
+async function createI18nOptions(): Promise<I18nOptions> {
+  const locale = localeStore.getLocale;
+  const defaultLocal = await import(`./lang/${locale}.ts`);
+  const message = defaultLocal.default?.message;
+
+  return {
+    legacy: false,
+    locale,
+    fallbackLocale: fallback,
+    messages: {
+      [locale]: message,
+    },
+    availableLocales: availableLocales,
+    sync: true, //If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false.
+    silentTranslationWarn: true, // true - warning off
+    missingWarn: false,
+    silentFallbackWarn: true,
+  };
+}
 
 // setup i18n instance with glob
-export function setupI18n(app: App) {
-  i18n = createI18n(localeData) as I18n;
+export async function setupI18n(app: App) {
+  const options = await createI18nOptions();
+  i18n = createI18n(options) as I18n;
   app.use(i18n);
 }

+ 0 - 1
src/locales/types.ts

@@ -1 +0,0 @@
-export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko';

+ 49 - 40
src/locales/useLocale.ts

@@ -1,64 +1,73 @@
 /**
  * Multi-language related operations
  */
-import type { LocaleType } from '/@/locales/types';
-import type { Ref } from 'vue';
+import type { LocaleType } from '/#/config';
 
-import { unref, ref } from 'vue';
-import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
+import { ref } from 'vue';
+import moment from 'moment';
+import { computed } from 'vue';
 
 import { i18n } from './setupI18n';
+import { localeStore } from '/@/store/modules/locale';
+import { unref } from 'vue';
 
-import 'moment/dist/locale/zh-cn';
+interface LangModule {
+  message: Recordable;
+  momentLocale: Recordable;
+  momentLocaleName: string;
+}
+
+const antConfigLocale = ref<Nullable<Recordable>>(null);
+
+const loadLocalePool: LocaleType[] = [];
 
-const antConfigLocaleRef = ref<any>(null);
+function setI18nLanguage(locale: LocaleType) {
+  if (i18n.mode === 'legacy') {
+    i18n.global.locale = locale;
+  } else {
+    (i18n.global.locale as any).value = locale;
+  }
+  localeStore.setLocaleInfo({ locale });
+  document.querySelector('html')?.setAttribute('lang', locale);
+}
 
 export function useLocale() {
-  const { getLang, getLocale, setLocale: setLocalSetting } = useLocaleSetting();
+  const getLocale = computed(() => localeStore.getLocale);
+  const getShowLocalePicker = computed(() => localeStore.getShowPicker);
+
+  const getAntdLocale = computed(() => {
+    return i18n.global.getLocaleMessage(unref(getLocale))?.antdLocale;
+  });
 
   // Switching the language will change the locale of useI18n
   // And submit to configuration modification
-  function changeLocale(lang: LocaleType): void {
-    if (i18n.mode === 'legacy') {
-      i18n.global.locale = lang;
-    } else {
-      ((i18n.global.locale as unknown) as Ref<string>).value = lang;
-    }
-    setLocalSetting({ lang });
-    // i18n.global.setLocaleMessage(locale, messages);
+  async function changeLocale(locale: LocaleType) {
+    const globalI18n = i18n.global;
+    const currentLocale = unref(globalI18n.locale);
+    if (currentLocale === locale) return locale;
 
-    switch (lang) {
-      // Simplified Chinese
-      case 'zh_CN':
-        import('ant-design-vue/es/locale/zh_CN').then((locale) => {
-          antConfigLocaleRef.value = locale.default;
-        });
+    if (loadLocalePool.includes(locale)) {
+      setI18nLanguage(locale);
+      return locale;
+    }
+    const langModule = ((await import(`./lang/${locale}.ts`)) as any).default as LangModule;
+    if (!langModule) return;
 
-        break;
-      // English
-      case 'en':
-        import('ant-design-vue/es/locale/en_US').then((locale) => {
-          antConfigLocaleRef.value = locale.default;
-        });
-        break;
+    const { message, momentLocale, momentLocaleName } = langModule;
 
-      // other
-      default:
-        break;
-    }
-  }
+    globalI18n.setLocaleMessage(locale, message);
+    moment.updateLocale(momentLocaleName, momentLocale);
+    loadLocalePool.push(locale);
 
-  // initialization
-  function setLocale() {
-    const lang = unref(getLang);
-    lang && changeLocale(lang);
+    setI18nLanguage(locale);
+    return locale;
   }
 
   return {
-    setLocale,
     getLocale,
-    getLang,
+    getShowLocalePicker,
     changeLocale,
-    antConfigLocale: antConfigLocaleRef,
+    antConfigLocale,
+    getAntdLocale,
   };
 }

+ 2 - 2
src/logics/error-handle/index.ts

@@ -3,9 +3,9 @@
  */
 
 import { errorStore, ErrorInfo } from '/@/store/modules/error';
-import { useProjectSetting } from '/@/hooks/setting';
 import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
 import { App } from 'vue';
+import projectSetting from '/@/settings/projectSetting';
 
 /**
  * Handling error stack information
@@ -160,7 +160,7 @@ function registerResourceErrorHandler() {
  * @param app
  */
 export function setupErrorHandle(app: App) {
-  const { useErrorHandle } = useProjectSetting();
+  const { useErrorHandle } = projectSetting;
   if (!useErrorHandle) return;
   // Vue exception monitoring;
   app.config.errorHandler = vueErrorHandler;

+ 29 - 10
src/logics/initAppConfig.ts

@@ -2,25 +2,22 @@
  * Application configuration
  */
 
-import type { ProjectConfig } from '/#/config';
-
-import { PROJ_CFG_KEY } from '/@/enums/cacheEnum';
-
 import projectSetting from '/@/settings/projectSetting';
-import { Persistent } from '/@/utils/cache/persistent';
+
 import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground';
 import { updateColorWeak } from '/@/logics/theme/updateColorWeak';
 import { updateGrayMode } from '/@/logics/theme/updateGrayMode';
 import { changeTheme } from '/@/logics/theme';
 
 import { appStore } from '/@/store/modules/app';
-import { deepMerge } from '/@/utils';
+import { localeStore } from '/@/store/modules/locale';
+
+import { getCommonStoragePrefix, getStorageShortName } from '/@/utils/env';
+
 import { primaryColor } from '../../build/config/themeConfig';
 
 // Initial project configuration
 export function initAppConfigStore() {
-  let projCfg: ProjectConfig = Persistent.getLocal(PROJ_CFG_KEY) as ProjectConfig;
-  projCfg = deepMerge(projectSetting, projCfg || {});
   try {
     const {
       colorWeak,
@@ -28,7 +25,7 @@ export function initAppConfigStore() {
       themeColor,
       headerSetting: { bgColor: headerBgColor } = {},
       menuSetting: { bgColor } = {},
-    } = projCfg;
+    } = projectSetting;
     if (themeColor && themeColor !== primaryColor) {
       changeTheme(themeColor);
     }
@@ -39,5 +36,27 @@ export function initAppConfigStore() {
   } catch (error) {
     console.log(error);
   }
-  appStore.commitProjectConfigState(projCfg);
+  appStore.commitProjectConfigState(projectSetting);
+  localeStore.initLocale();
+
+  setTimeout(() => {
+    clearObsoleteStorage();
+  }, 16);
+}
+
+/**
+ * As the version continues to iterate, there will be more and more cache keys stored in localStorage.
+ * This method is used to delete useless keys
+ */
+export function clearObsoleteStorage() {
+  const commonPrefix = getCommonStoragePrefix();
+  const shortPrefix = getStorageShortName();
+
+  [localStorage, sessionStorage].forEach((item: Storage) => {
+    Object.keys(item).forEach((key) => {
+      if (key && key.startsWith(commonPrefix) && !key.startsWith(shortPrefix)) {
+        item.removeItem(key);
+      }
+    });
+  });
 }

+ 24 - 21
src/main.ts

@@ -14,33 +14,36 @@ import { registerGlobComp } from '/@/components/registerGlobComp';
 
 import { isDevMode } from '/@/utils/env';
 
-const app = createApp(App);
+(async () => {
+  const app = createApp(App);
 
-// Register global components
-registerGlobComp(app);
+  // Register global components
+  registerGlobComp(app);
 
-// Multilingual configuration
-setupI18n(app);
+  // Configure routing
+  setupRouter(app);
 
-// Configure routing
-setupRouter(app);
+  // Configure vuex store
+  setupStore(app);
 
-// Configure vuex store
-setupStore(app);
+  // Register global directive
+  setupGlobDirectives(app);
 
-// Register global directive
-setupGlobDirectives(app);
+  // Configure global error handling
+  setupErrorHandle(app);
 
-// Configure global error handling
-setupErrorHandle(app);
+  await Promise.all([
+    // Multilingual configuration
+    setupI18n(app),
+    // Mount when the route is ready
+    router.isReady(),
+  ]);
 
-// Mount when the route is ready
-router.isReady().then(() => {
   app.mount('#app', true);
-});
 
-// The development environment takes effect
-if (isDevMode()) {
-  app.config.performance = true;
-  window.__APP__ = app;
-}
+  // The development environment takes effect
+  if (isDevMode()) {
+    app.config.performance = true;
+    window.__APP__ = app;
+  }
+})();

+ 2 - 2
src/router/guard/httpGuard.ts

@@ -1,13 +1,13 @@
 import type { Router } from 'vue-router';
-import { useProjectSetting } from '/@/hooks/setting';
 import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel';
+import projectSetting from '/@/settings/projectSetting';
 
 /**
  * The interface used to close the current page to complete the request when the route is switched
  * @param router
  */
 export function createHttpGuard(router: Router) {
-  const { removeAllHttpPending } = useProjectSetting();
+  const { removeAllHttpPending } = projectSetting;
   let axiosCanceler: Nullable<AxiosCanceler>;
   if (removeAllHttpPending) {
     axiosCanceler = new AxiosCanceler();

+ 2 - 3
src/router/guard/messageGuard.ts

@@ -1,7 +1,6 @@
 import type { Router } from 'vue-router';
-import { useProjectSetting } from '/@/hooks/setting';
 import { Modal, notification } from 'ant-design-vue';
-
+import projectSetting from '/@/settings/projectSetting';
 import { warn } from '/@/utils/log';
 
 /**
@@ -9,7 +8,7 @@ import { warn } from '/@/utils/log';
  * @param router
  */
 export function createMessageGuard(router: Router) {
-  const { closeMessageOnSwitch } = useProjectSetting();
+  const { closeMessageOnSwitch } = projectSetting;
 
   router.beforeEach(async () => {
     try {

+ 4 - 3
src/router/index.ts

@@ -4,9 +4,11 @@ import type { App } from 'vue';
 import { createRouter, createWebHashHistory } from 'vue-router';
 
 import { createGuard } from './guard';
-import { basicRoutes } from './routes';
+import { basicRoutes, LoginRoute } from './routes';
 import { REDIRECT_NAME } from './constant';
 
+const WHITE_NAME_LIST = [LoginRoute.name, REDIRECT_NAME];
+
 // app router
 const router = createRouter({
   history: createWebHashHistory(),
@@ -17,10 +19,9 @@ const router = createRouter({
 
 // reset router
 export function resetRouter() {
-  const resetWhiteNameList = ['Login', REDIRECT_NAME];
   router.getRoutes().forEach((route) => {
     const { name } = route;
-    if (name && !resetWhiteNameList.includes(name as string)) {
+    if (name && !WHITE_NAME_LIST.includes(name as string)) {
       router.hasRoute(name) && router.removeRoute(name);
     }
   });

+ 5 - 0
src/router/routes/mainOut.ts

@@ -1,3 +1,8 @@
+/**
+The routing of this file will not show the layout.
+It is an independent new page.
+the contents of the file still need to log in to access
+ */
 import type { AppRouteModule } from '/@/router/types';
 
 // test

+ 29 - 0
src/settings/localeSetting.ts

@@ -0,0 +1,29 @@
+import type { DropMenu } from '/@/components/Dropdown/src/types';
+import type { LocaleSetting, LocaleType } from '/#/config';
+
+export const LOCALE: { [key: string]: LocaleType } = {
+  ZH_CN: 'zh_CN',
+  EN_US: 'en',
+};
+
+export const localeSetting: LocaleSetting = {
+  showPicker: true,
+  // Locale
+  locale: LOCALE.ZH_CN,
+  // Default locale
+  fallback: LOCALE.ZH_CN,
+  // available Locales
+  availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US],
+};
+
+// locale list
+export const localeList: DropMenu[] = [
+  {
+    text: '简体中文',
+    event: LOCALE.ZH_CN,
+  },
+  {
+    text: 'English',
+    event: LOCALE.EN_US,
+  },
+];

+ 1 - 13
src/settings/projectSetting.ts

@@ -1,5 +1,4 @@
 import type { ProjectConfig } from '/#/config';
-
 import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum';
 import { CacheTypeEnum } from '/@/enums/cacheEnum';
 import {
@@ -26,8 +25,8 @@ const setting: ProjectConfig = {
   permissionCacheType: CacheTypeEnum.LOCAL,
 
   // color
-  // TODO Theme color
   themeColor: primaryColor,
+
   // TODO dark theme
   themeMode: themeMode,
 
@@ -49,17 +48,6 @@ const setting: ProjectConfig = {
   // Whether to show footer
   showFooter: false,
 
-  // locale setting
-  locale: {
-    show: true,
-    // Locale
-    lang: 'zh_CN',
-    // Default locale
-    fallback: 'zh_CN',
-    // available Locales
-    availableLocales: ['zh_CN', 'en'],
-  },
-
   // Header configuration
   headerSetting: {
     // header bg color

+ 2 - 2
src/store/modules/error.ts

@@ -4,7 +4,7 @@ import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-dec
 
 import { formatToDateTime } from '/@/utils/dateUtil';
 import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
-import { useProjectSetting } from '/@/hooks/setting';
+import projectSetting from '/@/settings/projectSetting';
 
 export interface ErrorInfo {
   type: ErrorTypeEnum;
@@ -57,7 +57,7 @@ class Error extends VuexModule implements ErrorState {
 
   @Action
   setupErrorHandle(error: any) {
-    const { useErrorHandle } = useProjectSetting();
+    const { useErrorHandle } = projectSetting;
     if (!useErrorHandle) return;
 
     const errInfo: Partial<ErrorInfo> = {

+ 44 - 0
src/store/modules/locale.ts

@@ -0,0 +1,44 @@
+import store from '/@/store';
+
+import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-decorators';
+
+import { LOCALE_KEY } from '/@/enums/cacheEnum';
+
+import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper';
+import { LocaleSetting, LocaleType } from '/#/config';
+import { createLocalStorage } from '/@/utils/cache';
+import { localeSetting } from '/@/settings/localeSetting';
+
+const ls = createLocalStorage();
+
+const lsSetting = (ls.get(LOCALE_KEY) || localeSetting) as LocaleSetting;
+
+const NAME = 'locale';
+hotModuleUnregisterModule(NAME);
+@Module({ dynamic: true, namespaced: true, store, name: NAME })
+class Locale extends VuexModule {
+  private info: LocaleSetting = lsSetting;
+
+  get getShowPicker(): boolean {
+    return !!this.info?.showPicker;
+  }
+
+  get getLocale(): LocaleType {
+    return this.info?.locale;
+  }
+
+  @Mutation
+  setLocaleInfo(info: Partial<LocaleSetting>): void {
+    this.info = { ...this.info, ...info };
+    ls.set(LOCALE_KEY, this.info);
+  }
+
+  @Action
+  initLocale(): void {
+    this.setLocaleInfo({
+      ...localeSetting,
+      ...this.info,
+    });
+  }
+}
+export const localeStore = getModule<Locale>(Locale);

+ 1 - 1
src/store/modules/permission.ts

@@ -88,7 +88,7 @@ class Permission extends VuexModule {
     let routes: AppRouteRecordRaw[] = [];
     const roleList = toRaw(userStore.getRoleListState);
 
-    const { permissionMode } = appStore.getProjectConfig;
+    const { permissionMode = PermissionModeEnum.ROLE } = appStore.getProjectConfig;
 
     // role permissions
     if (permissionMode === PermissionModeEnum.ROLE) {

+ 6 - 6
src/utils/cache/index.ts

@@ -11,7 +11,6 @@ const createOptions = (storage: Storage, options: Options = {}): Options => {
     hasEncrypt: enableStorageEncryption,
     storage,
     prefixKey: getStorageShortName(),
-
     ...options,
   };
 };
@@ -22,11 +21,12 @@ export const createStorage = (storage: Storage = sessionStorage, options: Option
   return create(createOptions(storage, options));
 };
 
-export const createPersistentStorage = (
-  storage: Storage = sessionStorage,
-  options: Options = {}
-) => {
-  return createStorage(storage, { ...options, timeout: DEFAULT_CACHE_TIME });
+export const createSessionStorage = (options: Options = {}) => {
+  return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
+};
+
+export const createLocalStorage = (options: Options = {}) => {
+  return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
 };
 
 export default WebStorage;

+ 2 - 2
src/utils/cache/memory.ts

@@ -57,7 +57,7 @@ export class Memory<T = any, V = any> {
     if (!expires) {
       return value;
     }
-    item.time = new Date().getTime() + this.alive * 1000;
+    item.time = new Date().getTime() + this.alive;
     item.timeoutId = setTimeout(() => {
       this.remove(key);
     }, expires);
@@ -80,7 +80,7 @@ export class Memory<T = any, V = any> {
       const item = cache[k];
       if (item && item.time) {
         const now = new Date().getTime();
-        const expire = now + item.time * 1000;
+        const expire = item.time;
         if (expire > now) {
           this.set(k, item.value, expire);
         }

+ 6 - 6
src/utils/cache/persistent.ts

@@ -1,4 +1,4 @@
-import { createPersistentStorage } from '/@/utils/cache';
+import { createLocalStorage, createSessionStorage } from '/@/utils/cache';
 import { Memory } from './memory';
 import {
   TOKEN_KEY,
@@ -28,19 +28,19 @@ export type BasicKeys = keyof BasicStore;
 type LocalKeys = keyof LocalStore;
 type SessionKeys = keyof SessionStore;
 
-const ls = createPersistentStorage(localStorage);
-const ss = createPersistentStorage(sessionStorage);
+const ls = createLocalStorage();
+const ss = createSessionStorage();
 
 const localMemory = new Memory(DEFAULT_CACHE_TIME);
 const sessionMemory = new Memory(DEFAULT_CACHE_TIME);
 
-function initMemory() {
+function initPersistentMemory() {
   const localCache = ls.get(APP_LOCAL_CACHE_KEY);
   const sessionCache = ls.get(APP_SESSION_CACHE_KEY);
   localCache && localMemory.resetCache(localCache);
   sessionCache && sessionMemory.resetCache(sessionCache);
 }
-initMemory();
+
 export class Persistent {
   static getLocal<T>(key: LocalKeys) {
     return localMemory.get(key)?.value as Nullable<T>;
@@ -106,4 +106,4 @@ function storageChange(e: any) {
 
 window.addEventListener('storage', storageChange);
 
-export default {};
+initPersistentMemory();

+ 15 - 8
src/utils/env.ts

@@ -2,19 +2,26 @@ import type { GlobEnvConfig } from '/#/config';
 
 import { useGlobSetting } from '/@/hooks/setting';
 import pkg from '../../package.json';
+import { getConfigFileName } from '../../build/getConfigFileName';
 
-/**
- * Get the global configuration (the configuration will be extracted independently when packaging)
- */
-export function getGlobEnvConfig(): GlobEnvConfig {
-  const env = import.meta.env;
-  return (env as unknown) as GlobEnvConfig;
+export function getCommonStoragePrefix() {
+  const globSetting = useGlobSetting();
+  return `${globSetting.shortName}__${getEnv()}`.toUpperCase();
 }
 
 // Generate cache key according to version
 export function getStorageShortName() {
-  const globSetting = useGlobSetting();
-  return `${globSetting.shortName}__${getEnv()}${`__${pkg.version}`}__`.toUpperCase();
+  return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase();
+}
+
+export function getAppEnvConfig() {
+  const ENV_NAME = getConfigFileName(import.meta.env);
+
+  const ENV = ((isDevMode()
+    ? // Get the global configuration (the configuration will be extracted independently when packaging)
+      ((import.meta.env as unknown) as GlobEnvConfig)
+    : window[ENV_NAME as any]) as unknown) as GlobEnvConfig;
+  return ENV;
 }
 
 /**

+ 2 - 3
src/views/sys/lock/useNow.ts

@@ -1,11 +1,10 @@
 import { dateUtil } from '/@/utils/dateUtil';
 import { reactive, toRefs } from 'vue';
 import { tryOnMounted, tryOnUnmounted } from '/@/utils/helper/vueHelper';
-import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
+import { localeStore } from '/@/store/modules/locale';
 
 export function useNow(immediate = true) {
-  const { getLang } = useLocaleSetting();
-  const localData = dateUtil.localeData(getLang.value);
+  const localData = dateUtil.localeData(localeStore.getLocale);
   let timer: IntervalHandle;
 
   const state = reactive({

+ 3 - 3
src/views/sys/login/Login.vue

@@ -53,9 +53,10 @@
   import MobileForm from './MobileForm.vue';
   import QrCodeForm from './QrCodeForm.vue';
 
-  import { useGlobSetting, useProjectSetting } from '/@/hooks/setting';
+  import { useGlobSetting } from '/@/hooks/setting';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useDesign } from '/@/hooks/web/useDesign';
+  import { localeStore } from '/@/store/modules/locale';
 
   export default defineComponent({
     name: 'Login',
@@ -71,14 +72,13 @@
     setup() {
       const globSetting = useGlobSetting();
       const { prefixCls } = useDesign('login');
-      const { locale } = useProjectSetting();
       const { t } = useI18n();
 
       return {
         t,
         prefixCls,
         title: computed(() => globSetting?.title ?? ''),
-        showLocale: computed(() => locale.show),
+        showLocale: localeStore.getShowPicker,
       };
     },
   });

+ 4 - 5
types/config.d.ts

@@ -8,9 +8,10 @@ import {
 } from '/@/enums/appEnum';
 
 import { CacheTypeEnum } from '/@/enums/cacheEnum';
-import type { LocaleType } from '/@/locales/types';
 import { ThemeMode } from '../build/config/themeConfig';
 
+export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko';
+
 export interface MenuSetting {
   bgColor: string;
   fixed: boolean;
@@ -57,9 +58,9 @@ export interface HeaderSetting {
 }
 
 export interface LocaleSetting {
-  show: boolean;
+  showPicker: boolean;
   // Current language
-  lang: LocaleType;
+  locale: LocaleType;
   // default language
   fallback: LocaleType;
   // available Locales
@@ -78,8 +79,6 @@ export interface TransitionSetting {
 }
 
 export interface ProjectConfig {
-  // Multilingual configuration
-  locale: LocaleSetting;
   // Storage location of permission related information
   permissionCacheType: CacheTypeEnum;
   // Whether to show the configuration button

+ 1 - 1
types/module.d.ts

@@ -4,7 +4,7 @@ declare module 'ant-design-vue/es/locale/*' {
   export default locale as Locale & ReadonlyRecordable;
 }
 
-declare module 'moment/locale/*' {
+declare module 'moment/dist/locale/*' {
   import { LocaleSpecification } from 'moment';
   const locale: LocaleSpecification & ReadonlyRecordable;
   export default locale;

+ 13 - 13
yarn.lock

@@ -1718,10 +1718,10 @@
   dependencies:
     vue-demi latest
 
-"@windicss/plugin-utils@0.5.4":
-  version "0.5.4"
-  resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.5.4.tgz#69476a9d1fee92046695766bf7fbfe48e48809a7"
-  integrity sha512-zxpHdTsVZl7TF8A3uAymJCqMRlG83dMRAXf//fXonluoLDSJCuGBJyxN3NdkAyNZZR1L1DvoUUtkZLYOba+ElQ==
+"@windicss/plugin-utils@0.6.0":
+  version "0.6.0"
+  resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.6.0.tgz#34eb852b7ff338bb933b0079112318a30d2aee00"
+  integrity sha512-CpXn3CRrAaDrpTjenidVfBz0JONLuGTFP6qjrwZ2tmhsKOuvTWw8Ic9JQ2a9L0AkYBH33lTso1qk70/PjnE6WQ==
   dependencies:
     esbuild "^0.8.52"
     esbuild-register "^2.0.0"
@@ -1849,10 +1849,10 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
   dependencies:
     color-convert "^2.0.1"
 
-ant-design-vue@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.0.tgz#d30ec06938dc3b43b08a117818fab91d7b083e5f"
-  integrity sha512-Uv35Z9V+8iT1PBO0QOqWHaVE4Gju94UfikL8NGxtAqy/yZDnTn8K2gz5n7PfQbB5oBqkEyn2O0mtOpUBUEXZ+g==
+ant-design-vue@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.1.tgz#3a5964523aac10fd2b16d84d651145cd2b65f1d5"
+  integrity sha512-CFIF+srTui4ZwdKPBXNoFA9/0fkSpypanQeOts0PAq1vEuMLxUoZHapDDn7wzsxZH3sYLF+mvMp8gYMRkaNn+w==
   dependencies:
     "@ant-design-vue/use" "^0.0.1-0"
     "@ant-design/icons-vue" "^6.0.0"
@@ -8968,12 +8968,12 @@ vite-plugin-theme@^0.4.8:
     es-module-lexer "^0.3.26"
     tinycolor2 "^1.4.2"
 
-vite-plugin-windicss@0.5.4:
-  version "0.5.4"
-  resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.5.4.tgz#35764e91536d596ac2c9266c3e16c546915d8b3e"
-  integrity sha512-iPLoqfpZdnRIY1AzweumpdE8ILQQnyhywZwJDqFpj8SZ3h43e5tfQFnJb5nS6FLccOsBcCV9JFugD2w6pGyfqg==
+vite-plugin-windicss@0.6.0:
+  version "0.6.0"
+  resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.6.0.tgz#ac8f24e70439904b67adc1f133e692fb6257ecaf"
+  integrity sha512-PSFdm0hrAGaKFzkFOiz31+dODoKNbh9wo/3m/7/012WwV9oJ1mX/9OxDxACykW7hMR0YvWHFmC0UwtvMra+InQ==
   dependencies:
-    "@windicss/plugin-utils" "0.5.4"
+    "@windicss/plugin-utils" "0.6.0"
     windicss "^2.2.0"
 
 vite@2.0.4: