mirror of
https://github.com/vbenjs/gf-vben-admin.git
synced 2025-01-23 20:00:19 +08:00
refactor: refactored multi-language modules to support lazy loading and remote loading
This commit is contained in:
parent
f57eb944ed
commit
f6cef1088d
@ -21,3 +21,4 @@ ignore:
|
|||||||
- dist
|
- dist
|
||||||
- .local
|
- .local
|
||||||
- .husky
|
- .husky
|
||||||
|
- src/locales/lang
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
## Wip
|
## Wip
|
||||||
|
|
||||||
|
### ✨ Refactor
|
||||||
|
|
||||||
|
- 重构多语言模块,支持懒加载及远程加载
|
||||||
|
|
||||||
### ✨ Features
|
### ✨ Features
|
||||||
|
|
||||||
- axios 支持 form-data 格式请求
|
- axios 支持 form-data 格式请求
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
"@iconify/iconify": "^2.0.0-rc.6",
|
"@iconify/iconify": "^2.0.0-rc.6",
|
||||||
"@vueuse/core": "^4.3.0",
|
"@vueuse/core": "^4.3.0",
|
||||||
"@zxcvbn-ts/core": "^0.2.0",
|
"@zxcvbn-ts/core": "^0.2.0",
|
||||||
"ant-design-vue": "2.0.0",
|
"ant-design-vue": "2.0.1",
|
||||||
"apexcharts": "^3.25.0",
|
"apexcharts": "^3.25.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"crypto-js": "^4.0.0",
|
"crypto-js": "^4.0.0",
|
||||||
@ -106,7 +106,7 @@
|
|||||||
"vite-plugin-pwa": "^0.5.5",
|
"vite-plugin-pwa": "^0.5.5",
|
||||||
"vite-plugin-style-import": "^0.7.5",
|
"vite-plugin-style-import": "^0.7.5",
|
||||||
"vite-plugin-theme": "^0.4.8",
|
"vite-plugin-theme": "^0.4.8",
|
||||||
"vite-plugin-windicss": "0.5.4",
|
"vite-plugin-windicss": "0.6.0",
|
||||||
"vue-eslint-parser": "^7.5.0",
|
"vue-eslint-parser": "^7.5.0",
|
||||||
"yargs": "^16.2.0"
|
"yargs": "^16.2.0"
|
||||||
},
|
},
|
||||||
|
11
src/App.vue
11
src/App.vue
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ConfigProvider v-bind="lockEvent" :locale="antConfigLocale">
|
<ConfigProvider v-bind="lockEvent" :locale="getAntdLocale">
|
||||||
<AppProvider>
|
<AppProvider>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</AppProvider>
|
</AppProvider>
|
||||||
@ -21,9 +21,7 @@
|
|||||||
components: { ConfigProvider, AppProvider },
|
components: { ConfigProvider, AppProvider },
|
||||||
setup() {
|
setup() {
|
||||||
// support Multi-language
|
// support Multi-language
|
||||||
const { antConfigLocale, setLocale } = useLocale();
|
const { getAntdLocale } = useLocale();
|
||||||
|
|
||||||
setLocale();
|
|
||||||
|
|
||||||
// Initialize vuex internal system configuration
|
// Initialize vuex internal system configuration
|
||||||
initAppConfigStore();
|
initAppConfigStore();
|
||||||
@ -31,10 +29,7 @@
|
|||||||
// Create a lock screen monitor
|
// Create a lock screen monitor
|
||||||
const lockEvent = useLockPage();
|
const lockEvent = useLockPage();
|
||||||
|
|
||||||
return {
|
return { getAntdLocale, lockEvent };
|
||||||
antConfigLocale,
|
|
||||||
lockEvent,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { LocaleType } from '/@/locales/types';
|
import type { LocaleType } from '/#/config';
|
||||||
import type { DropMenu } from '/@/components/Dropdown';
|
import type { DropMenu } from '/@/components/Dropdown';
|
||||||
|
|
||||||
import { defineComponent, ref, watchEffect, unref, computed } from 'vue';
|
import { defineComponent, ref, watchEffect, unref, computed } from 'vue';
|
||||||
@ -26,7 +26,7 @@
|
|||||||
import Icon from '/@/components/Icon';
|
import Icon from '/@/components/Icon';
|
||||||
|
|
||||||
import { useLocale } from '/@/locales/useLocale';
|
import { useLocale } from '/@/locales/useLocale';
|
||||||
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
|
import { localeList } from '/@/settings/localeSetting';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
|
|
||||||
@ -44,9 +44,7 @@
|
|||||||
|
|
||||||
const { prefixCls } = useDesign('app-locale-picker');
|
const { prefixCls } = useDesign('app-locale-picker');
|
||||||
|
|
||||||
const { localeList } = useLocaleSetting();
|
const { changeLocale, getLocale } = useLocale();
|
||||||
|
|
||||||
const { changeLocale, getLang } = useLocale();
|
|
||||||
|
|
||||||
const getLangText = computed(() => {
|
const getLangText = computed(() => {
|
||||||
const key = selectedKeys.value[0];
|
const key = selectedKeys.value[0];
|
||||||
@ -55,17 +53,17 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
selectedKeys.value = [unref(getLang)];
|
selectedKeys.value = [unref(getLocale)];
|
||||||
});
|
});
|
||||||
|
|
||||||
function toggleLocale(lang: LocaleType | string) {
|
async function toggleLocale(lang: LocaleType | string) {
|
||||||
changeLocale(lang as LocaleType);
|
await changeLocale(lang as LocaleType);
|
||||||
selectedKeys.value = [lang as string];
|
selectedKeys.value = [lang as string];
|
||||||
props.reload && location.reload();
|
props.reload && location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMenuEvent(menu: DropMenu) {
|
function handleMenuEvent(menu: DropMenu) {
|
||||||
if (unref(getLang) === menu.event) return;
|
if (unref(getLocale) === menu.event) return;
|
||||||
toggleLocale(menu.event as string);
|
toggleLocale(menu.event as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@
|
|||||||
|
|
||||||
const modalFn = useModalContext();
|
const modalFn = useModalContext();
|
||||||
|
|
||||||
const { getLang } = useLocale();
|
const { getLocale } = useLocale();
|
||||||
|
|
||||||
watchEffect(() => {});
|
watchEffect(() => {});
|
||||||
|
|
||||||
const getCurrentLang = computed((): 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' => {
|
const getCurrentLang = computed((): 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' => {
|
||||||
let lang: Lang;
|
let lang: Lang;
|
||||||
switch (unref(getLang)) {
|
switch (unref(getLocale)) {
|
||||||
case 'en':
|
case 'en':
|
||||||
lang = 'en_US';
|
lang = 'en_US';
|
||||||
break;
|
break;
|
||||||
|
@ -52,7 +52,6 @@
|
|||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '/@/hooks/web/useI18n';
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SimpleSubMenu',
|
name: 'SimpleSubMenu',
|
||||||
@ -73,6 +72,7 @@
|
|||||||
theme: propTypes.oneOf(['dark', 'light']),
|
theme: propTypes.oneOf(['dark', 'light']),
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const { t } = useI18n();
|
||||||
const { prefixCls } = useDesign('simple-menu');
|
const { prefixCls } = useDesign('simple-menu');
|
||||||
|
|
||||||
const getShowMenu = computed(() => {
|
const getShowMenu = computed(() => {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// token key
|
// token key
|
||||||
export const TOKEN_KEY = 'TOKEN__';
|
export const TOKEN_KEY = 'TOKEN__';
|
||||||
|
|
||||||
|
export const LOCALE_KEY = 'LOCALE__';
|
||||||
|
|
||||||
// user info key
|
// user info key
|
||||||
export const USER_INFO_KEY = 'USER__INFO__';
|
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__';
|
export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__';
|
||||||
|
|
||||||
// base global local key
|
// base global local key
|
||||||
export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__';
|
export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__';
|
||||||
|
|
||||||
// base global session key
|
// base global session key
|
||||||
export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__';
|
export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__';
|
||||||
|
|
||||||
// base global local key
|
|
||||||
export const APP_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__';
|
|
||||||
|
|
||||||
// base global session key
|
|
||||||
export const APP_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__';
|
|
||||||
|
|
||||||
export enum CacheTypeEnum {
|
export enum CacheTypeEnum {
|
||||||
SESSION,
|
SESSION,
|
||||||
|
@ -1,26 +1,16 @@
|
|||||||
import type { ProjectConfig, GlobConfig, GlobEnvConfig } from '/#/config';
|
import type { GlobConfig } from '/#/config';
|
||||||
|
|
||||||
import { getConfigFileName } from '../../../build/getConfigFileName';
|
|
||||||
|
|
||||||
import getProjectSetting from '/@/settings/projectSetting';
|
|
||||||
|
|
||||||
import { warn } from '/@/utils/log';
|
import { warn } from '/@/utils/log';
|
||||||
import { getGlobEnvConfig, isDevMode } from '/@/utils/env';
|
import { getAppEnvConfig } from '/@/utils/env';
|
||||||
|
|
||||||
export const useGlobSetting = (): Readonly<GlobConfig> => {
|
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 {
|
const {
|
||||||
VITE_GLOB_APP_TITLE,
|
VITE_GLOB_APP_TITLE,
|
||||||
VITE_GLOB_API_URL,
|
VITE_GLOB_API_URL,
|
||||||
VITE_GLOB_APP_SHORT_NAME,
|
VITE_GLOB_APP_SHORT_NAME,
|
||||||
VITE_GLOB_API_URL_PREFIX,
|
VITE_GLOB_API_URL_PREFIX,
|
||||||
VITE_GLOB_UPLOAD_URL,
|
VITE_GLOB_UPLOAD_URL,
|
||||||
} = ENV;
|
} = getAppEnvConfig();
|
||||||
|
|
||||||
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
||||||
warn(
|
warn(
|
||||||
@ -38,8 +28,3 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
|
|||||||
};
|
};
|
||||||
return glob as Readonly<GlobConfig>;
|
return glob as Readonly<GlobConfig>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useProjectSetting = (): ProjectConfig => {
|
|
||||||
// TODO computed
|
|
||||||
return getProjectSetting;
|
|
||||||
};
|
|
||||||
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
@ -40,6 +40,7 @@ export function useI18n(
|
|||||||
|
|
||||||
const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {
|
const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {
|
||||||
if (!key) return '';
|
if (!key) return '';
|
||||||
|
if (!key.includes('.')) return key;
|
||||||
return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters));
|
return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters));
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
|
@ -58,7 +58,7 @@ export function usePermission() {
|
|||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
if (!isArray(value)) {
|
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;
|
return (intersection(value, userStore.getRoleListState) as RoleEnum[]).length > 0;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
import { defineComponent, ref, toRaw, watchEffect } from 'vue';
|
import { defineComponent, ref, toRaw, watchEffect } from 'vue';
|
||||||
import { Breadcrumb } from 'ant-design-vue';
|
import { Breadcrumb } from 'ant-design-vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { filter } from '/@/utils/helper/treeHelper';
|
import { filter } from '/@/utils/helper/treeHelper';
|
||||||
@ -33,6 +32,7 @@
|
|||||||
import { propTypes } from '/@/utils/propTypes';
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
import { useGo } from '/@/hooks/web/usePage';
|
import { useGo } from '/@/hooks/web/usePage';
|
||||||
import { isString } from '/@/utils/is';
|
import { isString } from '/@/utils/is';
|
||||||
|
import { useI18n } from '/@/hooks/web/useI18n';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'LayoutBreadcrumb',
|
name: 'LayoutBreadcrumb',
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
icon="ion:document-text-outline"
|
icon="ion:document-text-outline"
|
||||||
v-if="getShowDoc"
|
v-if="getShowDoc"
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<MenuDivider v-if="getShowDoc" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key="lock"
|
key="lock"
|
||||||
:text="t('layout.header.tooltipLock')"
|
:text="t('layout.header.tooltipLock')"
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
<FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
|
<FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
|
||||||
|
|
||||||
<AppLocalePicker
|
<AppLocalePicker
|
||||||
v-if="getShowLocale"
|
v-if="getShowLocalePicker"
|
||||||
:reload="true"
|
:reload="true"
|
||||||
:showText="false"
|
:showText="false"
|
||||||
:class="`${prefixCls}-action__item`"
|
:class="`${prefixCls}-action__item`"
|
||||||
@ -69,7 +69,6 @@
|
|||||||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
|
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
|
||||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||||
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
|
|
||||||
|
|
||||||
import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
|
import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
|
||||||
import { SettingButtonPositionEnum } from '/@/enums/appEnum';
|
import { SettingButtonPositionEnum } from '/@/enums/appEnum';
|
||||||
@ -80,6 +79,7 @@
|
|||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
import { useLocale } from '/@/locales/useLocale';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'LayoutHeader',
|
name: 'LayoutHeader',
|
||||||
@ -112,7 +112,6 @@
|
|||||||
getMenuWidth,
|
getMenuWidth,
|
||||||
getIsMixSidebar,
|
getIsMixSidebar,
|
||||||
} = useMenuSetting();
|
} = useMenuSetting();
|
||||||
const { getShowLocale } = useLocaleSetting();
|
|
||||||
const {
|
const {
|
||||||
getUseErrorHandle,
|
getUseErrorHandle,
|
||||||
getShowSettingButton,
|
getShowSettingButton,
|
||||||
@ -130,6 +129,8 @@
|
|||||||
getShowHeader,
|
getShowHeader,
|
||||||
} = useHeaderSetting();
|
} = useHeaderSetting();
|
||||||
|
|
||||||
|
const { getShowLocalePicker } = useLocale();
|
||||||
|
|
||||||
const { getIsMobile } = useAppInject();
|
const { getIsMobile } = useAppInject();
|
||||||
|
|
||||||
const getHeaderClass = computed(() => {
|
const getHeaderClass = computed(() => {
|
||||||
@ -185,7 +186,7 @@
|
|||||||
getSplit,
|
getSplit,
|
||||||
getMenuMode,
|
getMenuMode,
|
||||||
getShowTopMenu,
|
getShowTopMenu,
|
||||||
getShowLocale,
|
getShowLocalePicker,
|
||||||
getShowFullScreen,
|
getShowFullScreen,
|
||||||
getShowNotice,
|
getShowNotice,
|
||||||
getUseLockPage,
|
getUseLockPage,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { toRaw, ref, nextTick } from 'vue';
|
import { toRaw, ref, nextTick } from 'vue';
|
||||||
import { RouteLocationNormalized } from 'vue-router';
|
import { RouteLocationNormalized } from 'vue-router';
|
||||||
import { useProjectSetting } from '/@/hooks/setting';
|
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
import { useSortable } from '/@/hooks/web/useSortable';
|
import { useSortable } from '/@/hooks/web/useSortable';
|
||||||
import router from '/@/router';
|
import router from '/@/router';
|
||||||
import { tabStore } from '/@/store/modules/tab';
|
import { tabStore } from '/@/store/modules/tab';
|
||||||
import { isNullAndUnDef } from '/@/utils/is';
|
import { isNullAndUnDef } from '/@/utils/is';
|
||||||
|
import projectSetting from '/@/settings/projectSetting';
|
||||||
|
|
||||||
export function initAffixTabs(): string[] {
|
export function initAffixTabs(): string[] {
|
||||||
const affixList = ref<RouteLocationNormalized[]>([]);
|
const affixList = ref<RouteLocationNormalized[]>([]);
|
||||||
@ -47,7 +47,7 @@ export function initAffixTabs(): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useTabsDrag(affixTextList: string[]) {
|
export function useTabsDrag(affixTextList: string[]) {
|
||||||
const { multiTabsSetting } = useProjectSetting();
|
const { multiTabsSetting } = projectSetting;
|
||||||
|
|
||||||
const { prefixCls } = useDesign('multiple-tabs');
|
const { prefixCls } = useDesign('multiple-tabs');
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import type { DropMenu } from '/@/components/Dropdown';
|
|
||||||
|
|
||||||
// locale list
|
|
||||||
export const localeList: DropMenu[] = [
|
|
||||||
{
|
|
||||||
text: '简体中文',
|
|
||||||
event: 'zh_CN',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'English',
|
|
||||||
event: 'en',
|
|
||||||
},
|
|
||||||
];
|
|
@ -1,4 +0,0 @@
|
|||||||
import { genMessage } from './helper';
|
|
||||||
const modules = import.meta.globEager('./lang/**/*.ts');
|
|
||||||
|
|
||||||
export default genMessage(modules);
|
|
@ -4,16 +4,21 @@ export function genMessage(langs: Record<string, Record<string, any>>, prefix =
|
|||||||
const obj: Recordable = {};
|
const obj: Recordable = {};
|
||||||
|
|
||||||
Object.keys(langs).forEach((key) => {
|
Object.keys(langs).forEach((key) => {
|
||||||
const mod = langs[key].default;
|
const langFileModule = langs[key].default;
|
||||||
let k = key.replace(`./${prefix}/`, '').replace(/^\.\//, '');
|
let fileName = key.replace(`./${prefix}/`, '').replace(/^\.\//, '');
|
||||||
const lastIndex = k.lastIndexOf('.');
|
const lastIndex = fileName.lastIndexOf('.');
|
||||||
k = k.substring(0, lastIndex);
|
fileName = fileName.substring(0, lastIndex);
|
||||||
const keyList = k.split('/');
|
const keyList = fileName.split('/');
|
||||||
const lang = keyList.shift();
|
const moduleName = keyList.shift();
|
||||||
const objKey = keyList.join('.');
|
const objKey = keyList.join('.');
|
||||||
if (lang) {
|
|
||||||
set(obj, lang, obj[lang] || {});
|
if (moduleName) {
|
||||||
set(obj[lang], objKey, mod);
|
if (objKey) {
|
||||||
|
set(obj, moduleName, obj[moduleName] || {});
|
||||||
|
set(obj[moduleName], objKey, langFileModule);
|
||||||
|
} else {
|
||||||
|
set(obj, moduleName, langFileModule || {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return obj;
|
return obj;
|
||||||
|
13
src/locales/lang/en.ts
Normal file
13
src/locales/lang/en.ts
Normal file
@ -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,5 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
redo: 'Refresh current',
|
reload: 'Refresh current',
|
||||||
close: 'Close current',
|
close: 'Close current',
|
||||||
closeLeft: 'Close Left',
|
closeLeft: 'Close Left',
|
||||||
closeRight: 'Close Right',
|
closeRight: 'Close Right',
|
||||||
|
@ -25,4 +25,6 @@ export default {
|
|||||||
list: 'List page',
|
list: 'List page',
|
||||||
listCard: 'Card list',
|
listCard: 'Card list',
|
||||||
basic: 'Basic list',
|
basic: 'Basic list',
|
||||||
|
listBasic: 'Basic list',
|
||||||
|
listSearch: 'Search list',
|
||||||
};
|
};
|
||||||
|
13
src/locales/lang/zh_CN.ts
Normal file
13
src/locales/lang/zh_CN.ts
Normal file
@ -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',
|
||||||
|
};
|
@ -3,27 +3,36 @@ import type { I18n, I18nOptions } from 'vue-i18n';
|
|||||||
|
|
||||||
import { createI18n } 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 = {
|
async function createI18nOptions(): Promise<I18nOptions> {
|
||||||
|
const locale = localeStore.getLocale;
|
||||||
|
const defaultLocal = await import(`./lang/${locale}.ts`);
|
||||||
|
const message = defaultLocal.default?.message;
|
||||||
|
|
||||||
|
return {
|
||||||
legacy: false,
|
legacy: false,
|
||||||
locale: lang,
|
locale,
|
||||||
fallbackLocale: fallback,
|
fallbackLocale: fallback,
|
||||||
messages,
|
messages: {
|
||||||
|
[locale]: message,
|
||||||
|
},
|
||||||
availableLocales: availableLocales,
|
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.
|
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
|
silentTranslationWarn: true, // true - warning off
|
||||||
missingWarn: false,
|
missingWarn: false,
|
||||||
silentFallbackWarn: true,
|
silentFallbackWarn: true,
|
||||||
};
|
};
|
||||||
export let i18n: I18n;
|
}
|
||||||
|
|
||||||
// setup i18n instance with glob
|
// setup i18n instance with glob
|
||||||
export function setupI18n(app: App) {
|
export async function setupI18n(app: App) {
|
||||||
i18n = createI18n(localeData) as I18n;
|
const options = await createI18nOptions();
|
||||||
|
i18n = createI18n(options) as I18n;
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko';
|
|
@ -1,64 +1,73 @@
|
|||||||
/**
|
/**
|
||||||
* Multi-language related operations
|
* Multi-language related operations
|
||||||
*/
|
*/
|
||||||
import type { LocaleType } from '/@/locales/types';
|
import type { LocaleType } from '/#/config';
|
||||||
import type { Ref } from 'vue';
|
|
||||||
|
|
||||||
import { unref, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
|
import moment from 'moment';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { i18n } from './setupI18n';
|
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 antConfigLocaleRef = ref<any>(null);
|
const antConfigLocale = ref<Nullable<Recordable>>(null);
|
||||||
|
|
||||||
|
const loadLocalePool: LocaleType[] = [];
|
||||||
|
|
||||||
|
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() {
|
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
|
// Switching the language will change the locale of useI18n
|
||||||
// And submit to configuration modification
|
// And submit to configuration modification
|
||||||
function changeLocale(lang: LocaleType): void {
|
async function changeLocale(locale: LocaleType) {
|
||||||
if (i18n.mode === 'legacy') {
|
const globalI18n = i18n.global;
|
||||||
i18n.global.locale = lang;
|
const currentLocale = unref(globalI18n.locale);
|
||||||
} else {
|
if (currentLocale === locale) return locale;
|
||||||
((i18n.global.locale as unknown) as Ref<string>).value = lang;
|
|
||||||
|
if (loadLocalePool.includes(locale)) {
|
||||||
|
setI18nLanguage(locale);
|
||||||
|
return locale;
|
||||||
}
|
}
|
||||||
setLocalSetting({ lang });
|
const langModule = ((await import(`./lang/${locale}.ts`)) as any).default as LangModule;
|
||||||
// i18n.global.setLocaleMessage(locale, messages);
|
if (!langModule) return;
|
||||||
|
|
||||||
switch (lang) {
|
const { message, momentLocale, momentLocaleName } = langModule;
|
||||||
// Simplified Chinese
|
|
||||||
case 'zh_CN':
|
|
||||||
import('ant-design-vue/es/locale/zh_CN').then((locale) => {
|
|
||||||
antConfigLocaleRef.value = locale.default;
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
globalI18n.setLocaleMessage(locale, message);
|
||||||
// English
|
moment.updateLocale(momentLocaleName, momentLocale);
|
||||||
case 'en':
|
loadLocalePool.push(locale);
|
||||||
import('ant-design-vue/es/locale/en_US').then((locale) => {
|
|
||||||
antConfigLocaleRef.value = locale.default;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
// other
|
setI18nLanguage(locale);
|
||||||
default:
|
return locale;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialization
|
|
||||||
function setLocale() {
|
|
||||||
const lang = unref(getLang);
|
|
||||||
lang && changeLocale(lang);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setLocale,
|
|
||||||
getLocale,
|
getLocale,
|
||||||
getLang,
|
getShowLocalePicker,
|
||||||
changeLocale,
|
changeLocale,
|
||||||
antConfigLocale: antConfigLocaleRef,
|
antConfigLocale,
|
||||||
|
getAntdLocale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { errorStore, ErrorInfo } from '/@/store/modules/error';
|
import { errorStore, ErrorInfo } from '/@/store/modules/error';
|
||||||
import { useProjectSetting } from '/@/hooks/setting';
|
|
||||||
import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
|
import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
|
||||||
import { App } from 'vue';
|
import { App } from 'vue';
|
||||||
|
import projectSetting from '/@/settings/projectSetting';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handling error stack information
|
* Handling error stack information
|
||||||
@ -160,7 +160,7 @@ function registerResourceErrorHandler() {
|
|||||||
* @param app
|
* @param app
|
||||||
*/
|
*/
|
||||||
export function setupErrorHandle(app: App) {
|
export function setupErrorHandle(app: App) {
|
||||||
const { useErrorHandle } = useProjectSetting();
|
const { useErrorHandle } = projectSetting;
|
||||||
if (!useErrorHandle) return;
|
if (!useErrorHandle) return;
|
||||||
// Vue exception monitoring;
|
// Vue exception monitoring;
|
||||||
app.config.errorHandler = vueErrorHandler;
|
app.config.errorHandler = vueErrorHandler;
|
||||||
|
@ -2,25 +2,22 @@
|
|||||||
* Application configuration
|
* Application configuration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ProjectConfig } from '/#/config';
|
|
||||||
|
|
||||||
import { PROJ_CFG_KEY } from '/@/enums/cacheEnum';
|
|
||||||
|
|
||||||
import projectSetting from '/@/settings/projectSetting';
|
import projectSetting from '/@/settings/projectSetting';
|
||||||
import { Persistent } from '/@/utils/cache/persistent';
|
|
||||||
import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground';
|
import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground';
|
||||||
import { updateColorWeak } from '/@/logics/theme/updateColorWeak';
|
import { updateColorWeak } from '/@/logics/theme/updateColorWeak';
|
||||||
import { updateGrayMode } from '/@/logics/theme/updateGrayMode';
|
import { updateGrayMode } from '/@/logics/theme/updateGrayMode';
|
||||||
import { changeTheme } from '/@/logics/theme';
|
import { changeTheme } from '/@/logics/theme';
|
||||||
|
|
||||||
import { appStore } from '/@/store/modules/app';
|
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';
|
import { primaryColor } from '../../build/config/themeConfig';
|
||||||
|
|
||||||
// Initial project configuration
|
// Initial project configuration
|
||||||
export function initAppConfigStore() {
|
export function initAppConfigStore() {
|
||||||
let projCfg: ProjectConfig = Persistent.getLocal(PROJ_CFG_KEY) as ProjectConfig;
|
|
||||||
projCfg = deepMerge(projectSetting, projCfg || {});
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
colorWeak,
|
colorWeak,
|
||||||
@ -28,7 +25,7 @@ export function initAppConfigStore() {
|
|||||||
themeColor,
|
themeColor,
|
||||||
headerSetting: { bgColor: headerBgColor } = {},
|
headerSetting: { bgColor: headerBgColor } = {},
|
||||||
menuSetting: { bgColor } = {},
|
menuSetting: { bgColor } = {},
|
||||||
} = projCfg;
|
} = projectSetting;
|
||||||
if (themeColor && themeColor !== primaryColor) {
|
if (themeColor && themeColor !== primaryColor) {
|
||||||
changeTheme(themeColor);
|
changeTheme(themeColor);
|
||||||
}
|
}
|
||||||
@ -39,5 +36,27 @@ export function initAppConfigStore() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
41
src/main.ts
41
src/main.ts
@ -14,33 +14,36 @@ import { registerGlobComp } from '/@/components/registerGlobComp';
|
|||||||
|
|
||||||
import { isDevMode } from '/@/utils/env';
|
import { isDevMode } from '/@/utils/env';
|
||||||
|
|
||||||
const app = createApp(App);
|
(async () => {
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
// Register global components
|
// Register global components
|
||||||
registerGlobComp(app);
|
registerGlobComp(app);
|
||||||
|
|
||||||
// Multilingual configuration
|
// Configure routing
|
||||||
setupI18n(app);
|
setupRouter(app);
|
||||||
|
|
||||||
// Configure routing
|
// Configure vuex store
|
||||||
setupRouter(app);
|
setupStore(app);
|
||||||
|
|
||||||
// Configure vuex store
|
// Register global directive
|
||||||
setupStore(app);
|
setupGlobDirectives(app);
|
||||||
|
|
||||||
// Register global directive
|
// Configure global error handling
|
||||||
setupGlobDirectives(app);
|
setupErrorHandle(app);
|
||||||
|
|
||||||
// Configure global error handling
|
await Promise.all([
|
||||||
setupErrorHandle(app);
|
// 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);
|
app.mount('#app', true);
|
||||||
});
|
|
||||||
|
|
||||||
// The development environment takes effect
|
// The development environment takes effect
|
||||||
if (isDevMode()) {
|
if (isDevMode()) {
|
||||||
app.config.performance = true;
|
app.config.performance = true;
|
||||||
window.__APP__ = app;
|
window.__APP__ = app;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import type { Router } from 'vue-router';
|
import type { Router } from 'vue-router';
|
||||||
import { useProjectSetting } from '/@/hooks/setting';
|
|
||||||
import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel';
|
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
|
* The interface used to close the current page to complete the request when the route is switched
|
||||||
* @param router
|
* @param router
|
||||||
*/
|
*/
|
||||||
export function createHttpGuard(router: Router) {
|
export function createHttpGuard(router: Router) {
|
||||||
const { removeAllHttpPending } = useProjectSetting();
|
const { removeAllHttpPending } = projectSetting;
|
||||||
let axiosCanceler: Nullable<AxiosCanceler>;
|
let axiosCanceler: Nullable<AxiosCanceler>;
|
||||||
if (removeAllHttpPending) {
|
if (removeAllHttpPending) {
|
||||||
axiosCanceler = new AxiosCanceler();
|
axiosCanceler = new AxiosCanceler();
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type { Router } from 'vue-router';
|
import type { Router } from 'vue-router';
|
||||||
import { useProjectSetting } from '/@/hooks/setting';
|
|
||||||
import { Modal, notification } from 'ant-design-vue';
|
import { Modal, notification } from 'ant-design-vue';
|
||||||
|
import projectSetting from '/@/settings/projectSetting';
|
||||||
import { warn } from '/@/utils/log';
|
import { warn } from '/@/utils/log';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -9,7 +8,7 @@ import { warn } from '/@/utils/log';
|
|||||||
* @param router
|
* @param router
|
||||||
*/
|
*/
|
||||||
export function createMessageGuard(router: Router) {
|
export function createMessageGuard(router: Router) {
|
||||||
const { closeMessageOnSwitch } = useProjectSetting();
|
const { closeMessageOnSwitch } = projectSetting;
|
||||||
|
|
||||||
router.beforeEach(async () => {
|
router.beforeEach(async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -4,9 +4,11 @@ import type { App } from 'vue';
|
|||||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||||
|
|
||||||
import { createGuard } from './guard';
|
import { createGuard } from './guard';
|
||||||
import { basicRoutes } from './routes';
|
import { basicRoutes, LoginRoute } from './routes';
|
||||||
import { REDIRECT_NAME } from './constant';
|
import { REDIRECT_NAME } from './constant';
|
||||||
|
|
||||||
|
const WHITE_NAME_LIST = [LoginRoute.name, REDIRECT_NAME];
|
||||||
|
|
||||||
// app router
|
// app router
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
@ -17,10 +19,9 @@ const router = createRouter({
|
|||||||
|
|
||||||
// reset router
|
// reset router
|
||||||
export function resetRouter() {
|
export function resetRouter() {
|
||||||
const resetWhiteNameList = ['Login', REDIRECT_NAME];
|
|
||||||
router.getRoutes().forEach((route) => {
|
router.getRoutes().forEach((route) => {
|
||||||
const { name } = 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);
|
router.hasRoute(name) && router.removeRoute(name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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';
|
import type { AppRouteModule } from '/@/router/types';
|
||||||
|
|
||||||
// test
|
// test
|
||||||
|
29
src/settings/localeSetting.ts
Normal file
29
src/settings/localeSetting.ts
Normal file
@ -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,5 +1,4 @@
|
|||||||
import type { ProjectConfig } from '/#/config';
|
import type { ProjectConfig } from '/#/config';
|
||||||
|
|
||||||
import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum';
|
import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum';
|
||||||
import { CacheTypeEnum } from '/@/enums/cacheEnum';
|
import { CacheTypeEnum } from '/@/enums/cacheEnum';
|
||||||
import {
|
import {
|
||||||
@ -26,8 +25,8 @@ const setting: ProjectConfig = {
|
|||||||
permissionCacheType: CacheTypeEnum.LOCAL,
|
permissionCacheType: CacheTypeEnum.LOCAL,
|
||||||
|
|
||||||
// color
|
// color
|
||||||
// TODO Theme color
|
|
||||||
themeColor: primaryColor,
|
themeColor: primaryColor,
|
||||||
|
|
||||||
// TODO dark theme
|
// TODO dark theme
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
|
|
||||||
@ -49,17 +48,6 @@ const setting: ProjectConfig = {
|
|||||||
// Whether to show footer
|
// Whether to show footer
|
||||||
showFooter: false,
|
showFooter: false,
|
||||||
|
|
||||||
// locale setting
|
|
||||||
locale: {
|
|
||||||
show: true,
|
|
||||||
// Locale
|
|
||||||
lang: 'zh_CN',
|
|
||||||
// Default locale
|
|
||||||
fallback: 'zh_CN',
|
|
||||||
// available Locales
|
|
||||||
availableLocales: ['zh_CN', 'en'],
|
|
||||||
},
|
|
||||||
|
|
||||||
// Header configuration
|
// Header configuration
|
||||||
headerSetting: {
|
headerSetting: {
|
||||||
// header bg color
|
// header bg color
|
||||||
|
@ -4,7 +4,7 @@ import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-dec
|
|||||||
|
|
||||||
import { formatToDateTime } from '/@/utils/dateUtil';
|
import { formatToDateTime } from '/@/utils/dateUtil';
|
||||||
import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
|
import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
|
||||||
import { useProjectSetting } from '/@/hooks/setting';
|
import projectSetting from '/@/settings/projectSetting';
|
||||||
|
|
||||||
export interface ErrorInfo {
|
export interface ErrorInfo {
|
||||||
type: ErrorTypeEnum;
|
type: ErrorTypeEnum;
|
||||||
@ -57,7 +57,7 @@ class Error extends VuexModule implements ErrorState {
|
|||||||
|
|
||||||
@Action
|
@Action
|
||||||
setupErrorHandle(error: any) {
|
setupErrorHandle(error: any) {
|
||||||
const { useErrorHandle } = useProjectSetting();
|
const { useErrorHandle } = projectSetting;
|
||||||
if (!useErrorHandle) return;
|
if (!useErrorHandle) return;
|
||||||
|
|
||||||
const errInfo: Partial<ErrorInfo> = {
|
const errInfo: Partial<ErrorInfo> = {
|
||||||
|
44
src/store/modules/locale.ts
Normal file
44
src/store/modules/locale.ts
Normal file
@ -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);
|
@ -88,7 +88,7 @@ class Permission extends VuexModule {
|
|||||||
let routes: AppRouteRecordRaw[] = [];
|
let routes: AppRouteRecordRaw[] = [];
|
||||||
const roleList = toRaw(userStore.getRoleListState);
|
const roleList = toRaw(userStore.getRoleListState);
|
||||||
|
|
||||||
const { permissionMode } = appStore.getProjectConfig;
|
const { permissionMode = PermissionModeEnum.ROLE } = appStore.getProjectConfig;
|
||||||
|
|
||||||
// role permissions
|
// role permissions
|
||||||
if (permissionMode === PermissionModeEnum.ROLE) {
|
if (permissionMode === PermissionModeEnum.ROLE) {
|
||||||
|
12
src/utils/cache/index.ts
vendored
12
src/utils/cache/index.ts
vendored
@ -11,7 +11,6 @@ const createOptions = (storage: Storage, options: Options = {}): Options => {
|
|||||||
hasEncrypt: enableStorageEncryption,
|
hasEncrypt: enableStorageEncryption,
|
||||||
storage,
|
storage,
|
||||||
prefixKey: getStorageShortName(),
|
prefixKey: getStorageShortName(),
|
||||||
|
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -22,11 +21,12 @@ export const createStorage = (storage: Storage = sessionStorage, options: Option
|
|||||||
return create(createOptions(storage, options));
|
return create(createOptions(storage, options));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createPersistentStorage = (
|
export const createSessionStorage = (options: Options = {}) => {
|
||||||
storage: Storage = sessionStorage,
|
return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
|
||||||
options: Options = {}
|
};
|
||||||
) => {
|
|
||||||
return createStorage(storage, { ...options, timeout: DEFAULT_CACHE_TIME });
|
export const createLocalStorage = (options: Options = {}) => {
|
||||||
|
return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WebStorage;
|
export default WebStorage;
|
||||||
|
4
src/utils/cache/memory.ts
vendored
4
src/utils/cache/memory.ts
vendored
@ -57,7 +57,7 @@ export class Memory<T = any, V = any> {
|
|||||||
if (!expires) {
|
if (!expires) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
item.time = new Date().getTime() + this.alive * 1000;
|
item.time = new Date().getTime() + this.alive;
|
||||||
item.timeoutId = setTimeout(() => {
|
item.timeoutId = setTimeout(() => {
|
||||||
this.remove(key);
|
this.remove(key);
|
||||||
}, expires);
|
}, expires);
|
||||||
@ -80,7 +80,7 @@ export class Memory<T = any, V = any> {
|
|||||||
const item = cache[k];
|
const item = cache[k];
|
||||||
if (item && item.time) {
|
if (item && item.time) {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
const expire = now + item.time * 1000;
|
const expire = item.time;
|
||||||
if (expire > now) {
|
if (expire > now) {
|
||||||
this.set(k, item.value, expire);
|
this.set(k, item.value, expire);
|
||||||
}
|
}
|
||||||
|
12
src/utils/cache/persistent.ts
vendored
12
src/utils/cache/persistent.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import { createPersistentStorage } from '/@/utils/cache';
|
import { createLocalStorage, createSessionStorage } from '/@/utils/cache';
|
||||||
import { Memory } from './memory';
|
import { Memory } from './memory';
|
||||||
import {
|
import {
|
||||||
TOKEN_KEY,
|
TOKEN_KEY,
|
||||||
@ -28,19 +28,19 @@ export type BasicKeys = keyof BasicStore;
|
|||||||
type LocalKeys = keyof LocalStore;
|
type LocalKeys = keyof LocalStore;
|
||||||
type SessionKeys = keyof SessionStore;
|
type SessionKeys = keyof SessionStore;
|
||||||
|
|
||||||
const ls = createPersistentStorage(localStorage);
|
const ls = createLocalStorage();
|
||||||
const ss = createPersistentStorage(sessionStorage);
|
const ss = createSessionStorage();
|
||||||
|
|
||||||
const localMemory = new Memory(DEFAULT_CACHE_TIME);
|
const localMemory = new Memory(DEFAULT_CACHE_TIME);
|
||||||
const sessionMemory = 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 localCache = ls.get(APP_LOCAL_CACHE_KEY);
|
||||||
const sessionCache = ls.get(APP_SESSION_CACHE_KEY);
|
const sessionCache = ls.get(APP_SESSION_CACHE_KEY);
|
||||||
localCache && localMemory.resetCache(localCache);
|
localCache && localMemory.resetCache(localCache);
|
||||||
sessionCache && sessionMemory.resetCache(sessionCache);
|
sessionCache && sessionMemory.resetCache(sessionCache);
|
||||||
}
|
}
|
||||||
initMemory();
|
|
||||||
export class Persistent {
|
export class Persistent {
|
||||||
static getLocal<T>(key: LocalKeys) {
|
static getLocal<T>(key: LocalKeys) {
|
||||||
return localMemory.get(key)?.value as Nullable<T>;
|
return localMemory.get(key)?.value as Nullable<T>;
|
||||||
@ -106,4 +106,4 @@ function storageChange(e: any) {
|
|||||||
|
|
||||||
window.addEventListener('storage', storageChange);
|
window.addEventListener('storage', storageChange);
|
||||||
|
|
||||||
export default {};
|
initPersistentMemory();
|
||||||
|
@ -2,19 +2,26 @@ import type { GlobEnvConfig } from '/#/config';
|
|||||||
|
|
||||||
import { useGlobSetting } from '/@/hooks/setting';
|
import { useGlobSetting } from '/@/hooks/setting';
|
||||||
import pkg from '../../package.json';
|
import pkg from '../../package.json';
|
||||||
|
import { getConfigFileName } from '../../build/getConfigFileName';
|
||||||
|
|
||||||
/**
|
export function getCommonStoragePrefix() {
|
||||||
* Get the global configuration (the configuration will be extracted independently when packaging)
|
const globSetting = useGlobSetting();
|
||||||
*/
|
return `${globSetting.shortName}__${getEnv()}`.toUpperCase();
|
||||||
export function getGlobEnvConfig(): GlobEnvConfig {
|
|
||||||
const env = import.meta.env;
|
|
||||||
return (env as unknown) as GlobEnvConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate cache key according to version
|
// Generate cache key according to version
|
||||||
export function getStorageShortName() {
|
export function getStorageShortName() {
|
||||||
const globSetting = useGlobSetting();
|
return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase();
|
||||||
return `${globSetting.shortName}__${getEnv()}${`__${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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { dateUtil } from '/@/utils/dateUtil';
|
import { dateUtil } from '/@/utils/dateUtil';
|
||||||
import { reactive, toRefs } from 'vue';
|
import { reactive, toRefs } from 'vue';
|
||||||
import { tryOnMounted, tryOnUnmounted } from '/@/utils/helper/vueHelper';
|
import { tryOnMounted, tryOnUnmounted } from '/@/utils/helper/vueHelper';
|
||||||
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
|
import { localeStore } from '/@/store/modules/locale';
|
||||||
|
|
||||||
export function useNow(immediate = true) {
|
export function useNow(immediate = true) {
|
||||||
const { getLang } = useLocaleSetting();
|
const localData = dateUtil.localeData(localeStore.getLocale);
|
||||||
const localData = dateUtil.localeData(getLang.value);
|
|
||||||
let timer: IntervalHandle;
|
let timer: IntervalHandle;
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
@ -53,9 +53,10 @@
|
|||||||
import MobileForm from './MobileForm.vue';
|
import MobileForm from './MobileForm.vue';
|
||||||
import QrCodeForm from './QrCodeForm.vue';
|
import QrCodeForm from './QrCodeForm.vue';
|
||||||
|
|
||||||
import { useGlobSetting, useProjectSetting } from '/@/hooks/setting';
|
import { useGlobSetting } from '/@/hooks/setting';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '/@/hooks/web/useI18n';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
import { localeStore } from '/@/store/modules/locale';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
@ -71,14 +72,13 @@
|
|||||||
setup() {
|
setup() {
|
||||||
const globSetting = useGlobSetting();
|
const globSetting = useGlobSetting();
|
||||||
const { prefixCls } = useDesign('login');
|
const { prefixCls } = useDesign('login');
|
||||||
const { locale } = useProjectSetting();
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
t,
|
t,
|
||||||
prefixCls,
|
prefixCls,
|
||||||
title: computed(() => globSetting?.title ?? ''),
|
title: computed(() => globSetting?.title ?? ''),
|
||||||
showLocale: computed(() => locale.show),
|
showLocale: localeStore.getShowPicker,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
9
types/config.d.ts
vendored
9
types/config.d.ts
vendored
@ -8,9 +8,10 @@ import {
|
|||||||
} from '/@/enums/appEnum';
|
} from '/@/enums/appEnum';
|
||||||
|
|
||||||
import { CacheTypeEnum } from '/@/enums/cacheEnum';
|
import { CacheTypeEnum } from '/@/enums/cacheEnum';
|
||||||
import type { LocaleType } from '/@/locales/types';
|
|
||||||
import { ThemeMode } from '../build/config/themeConfig';
|
import { ThemeMode } from '../build/config/themeConfig';
|
||||||
|
|
||||||
|
export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko';
|
||||||
|
|
||||||
export interface MenuSetting {
|
export interface MenuSetting {
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
fixed: boolean;
|
fixed: boolean;
|
||||||
@ -57,9 +58,9 @@ export interface HeaderSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LocaleSetting {
|
export interface LocaleSetting {
|
||||||
show: boolean;
|
showPicker: boolean;
|
||||||
// Current language
|
// Current language
|
||||||
lang: LocaleType;
|
locale: LocaleType;
|
||||||
// default language
|
// default language
|
||||||
fallback: LocaleType;
|
fallback: LocaleType;
|
||||||
// available Locales
|
// available Locales
|
||||||
@ -78,8 +79,6 @@ export interface TransitionSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ProjectConfig {
|
export interface ProjectConfig {
|
||||||
// Multilingual configuration
|
|
||||||
locale: LocaleSetting;
|
|
||||||
// Storage location of permission related information
|
// Storage location of permission related information
|
||||||
permissionCacheType: CacheTypeEnum;
|
permissionCacheType: CacheTypeEnum;
|
||||||
// Whether to show the configuration button
|
// Whether to show the configuration button
|
||||||
|
2
types/module.d.ts
vendored
2
types/module.d.ts
vendored
@ -4,7 +4,7 @@ declare module 'ant-design-vue/es/locale/*' {
|
|||||||
export default locale as Locale & ReadonlyRecordable;
|
export default locale as Locale & ReadonlyRecordable;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'moment/locale/*' {
|
declare module 'moment/dist/locale/*' {
|
||||||
import { LocaleSpecification } from 'moment';
|
import { LocaleSpecification } from 'moment';
|
||||||
const locale: LocaleSpecification & ReadonlyRecordable;
|
const locale: LocaleSpecification & ReadonlyRecordable;
|
||||||
export default locale;
|
export default locale;
|
||||||
|
26
yarn.lock
26
yarn.lock
@ -1718,10 +1718,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
vue-demi latest
|
vue-demi latest
|
||||||
|
|
||||||
"@windicss/plugin-utils@0.5.4":
|
"@windicss/plugin-utils@0.6.0":
|
||||||
version "0.5.4"
|
version "0.6.0"
|
||||||
resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.5.4.tgz#69476a9d1fee92046695766bf7fbfe48e48809a7"
|
resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.6.0.tgz#34eb852b7ff338bb933b0079112318a30d2aee00"
|
||||||
integrity sha512-zxpHdTsVZl7TF8A3uAymJCqMRlG83dMRAXf//fXonluoLDSJCuGBJyxN3NdkAyNZZR1L1DvoUUtkZLYOba+ElQ==
|
integrity sha512-CpXn3CRrAaDrpTjenidVfBz0JONLuGTFP6qjrwZ2tmhsKOuvTWw8Ic9JQ2a9L0AkYBH33lTso1qk70/PjnE6WQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild "^0.8.52"
|
esbuild "^0.8.52"
|
||||||
esbuild-register "^2.0.0"
|
esbuild-register "^2.0.0"
|
||||||
@ -1849,10 +1849,10 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
color-convert "^2.0.1"
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
ant-design-vue@2.0.0:
|
ant-design-vue@2.0.1:
|
||||||
version "2.0.0"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.0.tgz#d30ec06938dc3b43b08a117818fab91d7b083e5f"
|
resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.1.tgz#3a5964523aac10fd2b16d84d651145cd2b65f1d5"
|
||||||
integrity sha512-Uv35Z9V+8iT1PBO0QOqWHaVE4Gju94UfikL8NGxtAqy/yZDnTn8K2gz5n7PfQbB5oBqkEyn2O0mtOpUBUEXZ+g==
|
integrity sha512-CFIF+srTui4ZwdKPBXNoFA9/0fkSpypanQeOts0PAq1vEuMLxUoZHapDDn7wzsxZH3sYLF+mvMp8gYMRkaNn+w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ant-design-vue/use" "^0.0.1-0"
|
"@ant-design-vue/use" "^0.0.1-0"
|
||||||
"@ant-design/icons-vue" "^6.0.0"
|
"@ant-design/icons-vue" "^6.0.0"
|
||||||
@ -8968,12 +8968,12 @@ vite-plugin-theme@^0.4.8:
|
|||||||
es-module-lexer "^0.3.26"
|
es-module-lexer "^0.3.26"
|
||||||
tinycolor2 "^1.4.2"
|
tinycolor2 "^1.4.2"
|
||||||
|
|
||||||
vite-plugin-windicss@0.5.4:
|
vite-plugin-windicss@0.6.0:
|
||||||
version "0.5.4"
|
version "0.6.0"
|
||||||
resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.5.4.tgz#35764e91536d596ac2c9266c3e16c546915d8b3e"
|
resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.6.0.tgz#ac8f24e70439904b67adc1f133e692fb6257ecaf"
|
||||||
integrity sha512-iPLoqfpZdnRIY1AzweumpdE8ILQQnyhywZwJDqFpj8SZ3h43e5tfQFnJb5nS6FLccOsBcCV9JFugD2w6pGyfqg==
|
integrity sha512-PSFdm0hrAGaKFzkFOiz31+dODoKNbh9wo/3m/7/012WwV9oJ1mX/9OxDxACykW7hMR0YvWHFmC0UwtvMra+InQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@windicss/plugin-utils" "0.5.4"
|
"@windicss/plugin-utils" "0.6.0"
|
||||||
windicss "^2.2.0"
|
windicss "^2.2.0"
|
||||||
|
|
||||||
vite@2.0.4:
|
vite@2.0.4:
|
||||||
|
Loading…
Reference in New Issue
Block a user