diff --git a/.ls-lint.yml b/.ls-lint.yml index 8f73ee9..1e34a0c 100644 --- a/.ls-lint.yml +++ b/.ls-lint.yml @@ -21,3 +21,4 @@ ignore: - dist - .local - .husky + - src/locales/lang diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index 4cf6dc7..d6d0db0 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -1,5 +1,9 @@ ## Wip +### ✨ Refactor + +- 重构多语言模块,支持懒加载及远程加载 + ### ✨ Features - axios 支持 form-data 格式请求 diff --git a/package.json b/package.json index 015f587..3aac748 100644 --- a/package.json +++ b/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" }, diff --git a/src/App.vue b/src/App.vue index 79e5484..9b45da5 100644 --- a/src/App.vue +++ b/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> diff --git a/src/components/Application/src/AppLocalePicker.vue b/src/components/Application/src/AppLocalePicker.vue index c7f6915..9f196f6 100644 --- a/src/components/Application/src/AppLocalePicker.vue +++ b/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); } diff --git a/src/components/Markdown/src/index.vue b/src/components/Markdown/src/index.vue index 5ae7a68..4977c41 100644 --- a/src/components/Markdown/src/index.vue +++ b/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; diff --git a/src/components/SimpleMenu/src/SimpleSubMenu.vue b/src/components/SimpleMenu/src/SimpleSubMenu.vue index 4ec58ad..f67f0f1 100644 --- a/src/components/SimpleMenu/src/SimpleSubMenu.vue +++ b/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(() => { diff --git a/src/enums/cacheEnum.ts b/src/enums/cacheEnum.ts index 4b465e2..42ddbd0 100644 --- a/src/enums/cacheEnum.ts +++ b/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, diff --git a/src/hooks/setting/index.ts b/src/hooks/setting/index.ts index b33f750..30e4d90 100644 --- a/src/hooks/setting/index.ts +++ b/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; -}; diff --git a/src/hooks/setting/useLocaleSetting.ts b/src/hooks/setting/useLocaleSetting.ts deleted file mode 100644 index d6d556c..0000000 --- a/src/hooks/setting/useLocaleSetting.ts +++ /dev/null @@ -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, - }; -} diff --git a/src/hooks/web/useI18n.ts b/src/hooks/web/useI18n.ts index bbd809f..a960c56 100644 --- a/src/hooks/web/useI18n.ts +++ b/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 { diff --git a/src/hooks/web/usePermission.ts b/src/hooks/web/usePermission.ts index 35b1d71..ab0ae3b 100644 --- a/src/hooks/web/usePermission.ts +++ b/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; } diff --git a/src/layouts/default/header/components/Breadcrumb.vue b/src/layouts/default/header/components/Breadcrumb.vue index 3ed7943..e6efaf7 100644 --- a/src/layouts/default/header/components/Breadcrumb.vue +++ b/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', diff --git a/src/layouts/default/header/components/user-dropdown/index.vue b/src/layouts/default/header/components/user-dropdown/index.vue index e7ff7c7..ef710d2 100644 --- a/src/layouts/default/header/components/user-dropdown/index.vue +++ b/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')" diff --git a/src/layouts/default/header/index.vue b/src/layouts/default/header/index.vue index dc615cb..b15e021 100644 --- a/src/layouts/default/header/index.vue +++ b/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, diff --git a/src/layouts/default/tabs/useMultipleTabs.ts b/src/layouts/default/tabs/useMultipleTabs.ts index 2305268..c44a02b 100644 --- a/src/layouts/default/tabs/useMultipleTabs.ts +++ b/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(() => { diff --git a/src/locales/constant.ts b/src/locales/constant.ts deleted file mode 100644 index 554786b..0000000 --- a/src/locales/constant.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { DropMenu } from '/@/components/Dropdown'; - -// locale list -export const localeList: DropMenu[] = [ - { - text: '简体中文', - event: 'zh_CN', - }, - { - text: 'English', - event: 'en', - }, -]; diff --git a/src/locales/getMessage.ts b/src/locales/getMessage.ts deleted file mode 100644 index 5d6cfdb..0000000 --- a/src/locales/getMessage.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { genMessage } from './helper'; -const modules = import.meta.globEager('./lang/**/*.ts'); - -export default genMessage(modules); diff --git a/src/locales/helper.ts b/src/locales/helper.ts index ed12b21..9e6b5a4 100644 --- a/src/locales/helper.ts +++ b/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; diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts new file mode 100644 index 0000000..75f4233 --- /dev/null +++ b/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', +}; diff --git a/src/locales/lang/en/layout/multipleTab.ts b/src/locales/lang/en/layout/multipleTab.ts index 9889916..0e58808 100644 --- a/src/locales/lang/en/layout/multipleTab.ts +++ b/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', diff --git a/src/locales/lang/en/routes/demo/page.ts b/src/locales/lang/en/routes/demo/page.ts index 1595694..18998ef 100644 --- a/src/locales/lang/en/routes/demo/page.ts +++ b/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', }; diff --git a/src/locales/lang/zh_CN.ts b/src/locales/lang/zh_CN.ts new file mode 100644 index 0000000..bc0571b --- /dev/null +++ b/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', +}; diff --git a/src/locales/setupI18n.ts b/src/locales/setupI18n.ts index 05de7e6..2721862 100644 --- a/src/locales/setupI18n.ts +++ b/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); } diff --git a/src/locales/types.ts b/src/locales/types.ts deleted file mode 100644 index 98f1402..0000000 --- a/src/locales/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko'; diff --git a/src/locales/useLocale.ts b/src/locales/useLocale.ts index fac203e..7c865c3 100644 --- a/src/locales/useLocale.ts +++ b/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, }; } diff --git a/src/logics/error-handle/index.ts b/src/logics/error-handle/index.ts index 7b8bae6..16808ff 100644 --- a/src/logics/error-handle/index.ts +++ b/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; diff --git a/src/logics/initAppConfig.ts b/src/logics/initAppConfig.ts index c3a7d33..5d5abcc 100644 --- a/src/logics/initAppConfig.ts +++ b/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); + } + }); + }); } diff --git a/src/main.ts b/src/main.ts index 1d1da2a..3de61cf 100644 --- a/src/main.ts +++ b/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; + } +})(); diff --git a/src/router/guard/httpGuard.ts b/src/router/guard/httpGuard.ts index 649454a..1d3b3b1 100644 --- a/src/router/guard/httpGuard.ts +++ b/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(); diff --git a/src/router/guard/messageGuard.ts b/src/router/guard/messageGuard.ts index 529436d..9a01626 100644 --- a/src/router/guard/messageGuard.ts +++ b/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 { diff --git a/src/router/index.ts b/src/router/index.ts index 2c5f150..84ae742 100644 --- a/src/router/index.ts +++ b/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); } }); diff --git a/src/router/routes/mainOut.ts b/src/router/routes/mainOut.ts index fda8d85..6ecbaed 100644 --- a/src/router/routes/mainOut.ts +++ b/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 diff --git a/src/settings/localeSetting.ts b/src/settings/localeSetting.ts new file mode 100644 index 0000000..e5389ff --- /dev/null +++ b/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, + }, +]; diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts index f76d0de..38d42f2 100644 --- a/src/settings/projectSetting.ts +++ b/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 diff --git a/src/store/modules/error.ts b/src/store/modules/error.ts index 64252c6..81270e0 100644 --- a/src/store/modules/error.ts +++ b/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> = { diff --git a/src/store/modules/locale.ts b/src/store/modules/locale.ts new file mode 100644 index 0000000..758f6f7 --- /dev/null +++ b/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); diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts index 4504598..bb848e7 100644 --- a/src/store/modules/permission.ts +++ b/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) { diff --git a/src/utils/cache/index.ts b/src/utils/cache/index.ts index 6feb5f1..2004c66 100644 --- a/src/utils/cache/index.ts +++ b/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; diff --git a/src/utils/cache/memory.ts b/src/utils/cache/memory.ts index 08a9a36..eb7cf06 100644 --- a/src/utils/cache/memory.ts +++ b/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); } diff --git a/src/utils/cache/persistent.ts b/src/utils/cache/persistent.ts index 184237a..497b213 100644 --- a/src/utils/cache/persistent.ts +++ b/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(); diff --git a/src/utils/env.ts b/src/utils/env.ts index 124b3ac..fe5ca40 100644 --- a/src/utils/env.ts +++ b/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; } /** diff --git a/src/views/sys/lock/useNow.ts b/src/views/sys/lock/useNow.ts index c4695cc..12e61d6 100644 --- a/src/views/sys/lock/useNow.ts +++ b/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({ diff --git a/src/views/sys/login/Login.vue b/src/views/sys/login/Login.vue index 43ed1c8..a5c3375 100644 --- a/src/views/sys/login/Login.vue +++ b/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, }; }, }); diff --git a/types/config.d.ts b/types/config.d.ts index a37d8ee..2a7409e 100644 --- a/types/config.d.ts +++ b/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 diff --git a/types/module.d.ts b/types/module.d.ts index 5eadc8e..afd970a 100644 --- a/types/module.d.ts +++ b/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; diff --git a/yarn.lock b/yarn.lock index 976235e..71e7f2f 100644 --- a/yarn.lock +++ b/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: