提交 737b1b19 编写于 作者: V vben

wip: multi-language support

上级 b49950a3
## Wip
### 🎫 Chores
- 移除 messageSetting 配置
## 2.0.0-rc.11 (2020-11-18)
### ✨ Features
......
<template>
<ConfigProvider v-bind="lockEvent" :locale="zhCN" :transform-cell-text="transformCellText">
<ConfigProvider
v-bind="lockEvent"
:locale="antConfigLocale"
:transform-cell-text="transformCellText"
>
<router-view />
</ConfigProvider>
</template>
......@@ -7,16 +11,12 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { ConfigProvider } from 'ant-design-vue';
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import moment from 'moment';
import 'moment/dist/locale/zh-cn';
import { getConfigProvider, initAppConfigStore } from '/@/setup/App';
import { useLockPage } from '/@/hooks/web/useLockPage';
moment.locale('zh-cn');
import { useLockPage } from '/@/hooks/web/useLockPage';
import { useLocale } from '/@/hooks/web/useLocale';
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
export default defineComponent({
name: 'App',
......@@ -34,9 +34,12 @@
// Create a lock screen monitor
const lockEvent = useLockPage();
// support Multi-language
const { antConfigLocale } = useLocale();
return {
transformCellText,
zhCN,
antConfigLocale,
lockEvent,
};
},
......
import AppLocalPicker from './src/AppLocalPicker.vue';
export { AppLocalPicker };
<template>
<Dropdown
:trigger="['click']"
:dropMenuList="localeList"
:selectedKeys="selectedKeys"
@menuEvent="handleMenuEvent"
>
<GlobalOutlined class="app-locale" />
</Dropdown>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect, unref } from 'vue';
import { Dropdown, DropMenu } from '/@/components/Dropdown';
import { GlobalOutlined } from '@ant-design/icons-vue';
import { useLocale } from '/@/hooks/web/useLocale';
import { useLocaleSetting } from '/@/settings/use/useLocaleSetting';
import { LocaleType } from '/@/locales/types';
export default defineComponent({
name: 'AppLocalPicker',
components: { GlobalOutlined, Dropdown },
setup() {
const { localeList } = useLocaleSetting();
const selectedKeys = ref<string[]>([]);
const { changeLocale, getLang } = useLocale();
watchEffect(() => {
selectedKeys.value = [unref(getLang)];
});
function toggleLocale(lang: LocaleType | string) {
changeLocale(lang as LocaleType);
selectedKeys.value = [lang as string];
}
function handleMenuEvent(menu: DropMenu) {
toggleLocale(menu.event as string);
}
return { localeList, handleMenuEvent, selectedKeys };
},
});
</script>
<style lang="less" scoped>
.app-locale {
cursor: pointer;
}
</style>
export { default as Dropdown } from './Dropdown';
export * from './types';
export { default as Dropdown } from './src/Dropdown';
export * from './src/types';
import { defineComponent, computed, unref } from 'vue';
import { Dropdown, Menu } from 'ant-design-vue';
import { Dropdown, Menu, Divider } from 'ant-design-vue';
import Icon from '/@/components/Icon/index';
import { basicDropdownProps } from './props';
import { getSlot } from '/@/utils/helper/tsxHelper';
import { Trigger } from './types';
export default defineComponent({
name: 'Dropdown',
props: basicDropdownProps,
emits: ['menuEvent'],
setup(props, { slots, emit, attrs }) {
const getMenuList = computed(() => props.dropMenuList);
function handleClickMenu({ key }: any) {
const menu = unref(getMenuList)[key];
const menu = unref(getMenuList).find((item) => item.event === key);
emit('menuEvent', menu);
}
function renderMenus() {
return (
<Menu onClick={handleClickMenu}>
<Menu onClick={handleClickMenu} selectedKeys={props.selectedKeys}>
{() => (
<>
{unref(getMenuList).map((item, index) => {
const { disabled, icon, text, divider } = item;
const { disabled, icon, text, divider, event } = item;
return [
<Menu.Item key={`${index}`} disabled={disabled}>
<Menu.Item key={`${event}`} disabled={disabled}>
{() => (
<>
{icon && <Icon icon={icon} />}
......@@ -34,8 +35,7 @@ export default defineComponent({
</>
)}
</Menu.Item>,
// @ts-ignore
divider && <Menu.Divider key={`d-${index}`} />,
divider && <Divider key={`d-${index}`} />,
];
})}
</>
......@@ -45,7 +45,7 @@ export default defineComponent({
}
return () => (
<Dropdown trigger={props.trigger as any} {...attrs}>
<Dropdown trigger={props.trigger as Trigger[]} {...attrs}>
{{
default: () => <span>{getSlot(slots)}</span>,
overlay: () => renderMenus(),
......
import type { PropType } from 'vue';
import type { DropMenu } from './types';
export const dropdownProps = {
/**
......@@ -15,7 +16,11 @@ export const dropdownProps = {
};
export const basicDropdownProps = Object.assign({}, dropdownProps, {
dropMenuList: {
type: Array as PropType<any[]>,
type: Array as PropType<DropMenu[]>,
default: () => [],
},
selectedKeys: {
type: Array as PropType<string[]>,
default: () => [],
},
});
......@@ -6,3 +6,5 @@ export interface DropMenu {
disabled?: boolean;
divider?: boolean;
}
export type Trigger = 'click' | 'hover' | 'contextMenu';
import { createI18n } from 'vue-i18n';
import { ref, watch } from 'vue';
import type { I18nOptions } from 'vue-i18n';
export function useI18n(options?: I18nOptions) {
const i18n = createI18n(options);
const localeRef = ref(i18n.global.locale);
watch(localeRef, () => {
i18n.global.locale = localeRef.value as any;
});
return {
t: i18n.global.t,
localeRef,
};
}
/**
* Multi-language related operations
*/
import type { LocaleType } from '/@/locales/types';
import { appStore } from '/@/store/modules/app';
import { unref, ref } from 'vue';
import { getI18n } from '/@/setup/i18n';
import { useLocaleSetting } from '/@/settings/use/useLocaleSetting';
import moment from 'moment';
import 'moment/dist/locale/zh-cn';
moment.locale('zh-cn');
const antConfigLocaleRef = ref<any>(null);
export function useLocale() {
/**
*
*/
function getLocale(): string {
return appStore.getProjectConfig.locale;
const { getLang, getLocale, setLocale: setLocalSetting } = useLocaleSetting();
// Switching the language will change the locale of useI18n
// And submit to configuration modification
function changeLocale(lang: LocaleType): void {
(getI18n().global.locale as any).value = lang;
setLocalSetting({ lang });
// i18n.global.setLocaleMessage(locale, messages);
antConfigLocaleRef.value = { a: 1 };
switch (lang) {
// Simplified Chinese
case 'zh_CN':
import('ant-design-vue/es/locale/zh_CN').then((locale) => {
antConfigLocaleRef.value = locale.default;
});
moment.locale('cn');
break;
// English
case 'en':
import('ant-design-vue/es/locale/en_US').then((locale) => {
antConfigLocaleRef.value = locale.default;
});
moment.locale('en-us');
break;
// other
default:
break;
}
}
/**
*
* @param locale
*/
async function changeLocale(locale: LocaleType): Promise<void> {
appStore.commitProjectConfigState({ locale: locale });
// initialization
function setupLocale() {
const lang = unref(getLang);
lang && changeLocale(lang);
}
return { getLocale, changeLocale };
return {
setupLocale,
getLocale,
getLang,
changeLocale,
antConfigLocale: antConfigLocaleRef,
};
}
/**
* For non-setup use
*/
export function useExternalI18n() {
return getI18n().global;
}
......@@ -3,7 +3,6 @@ import type { ModalFunc, ModalFuncProps } from 'ant-design-vue/lib/modal/Modal';
import { Modal, message as Message, notification } from 'ant-design-vue';
import { InfoCircleFilled, CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons-vue';
import { useSetting } from '/@/hooks/core/useSetting';
import { ArgsProps, ConfigProps } from 'ant-design-vue/lib/notification';
export interface NotifyApi {
......@@ -33,8 +32,6 @@ interface ConfirmOptions {
warning: ModalFunc;
}
const { projectSetting } = useSetting();
function getIcon(iconType: string) {
if (iconType === 'warning') {
return <InfoCircleFilled class="modal-icon-warning" />;
......@@ -60,7 +57,6 @@ function createConfirm(options: ModalOptionsEx): ConfirmOptions {
const opt: ModalFuncProps = {
centered: true,
icon: getIcon(iconType),
...projectSetting.messageSetting,
...options,
};
return Modal.confirm(opt) as any;
......
......@@ -5,7 +5,6 @@ import Button from '/@/components/Button/index.vue';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
import { CopyOutlined, RedoOutlined, CheckOutlined } from '@ant-design/icons-vue';
import { appStore } from '/@/store/modules/app';
import { userStore } from '/@/store/modules/user';
import { ProjectConfig } from '/@/types/config';
import { useMessage } from '/@/hooks/web/useMessage';
......@@ -97,7 +96,7 @@ export default defineComponent({
function handleClearAndRedo() {
localStorage.clear();
userStore.resumeAllState();
appStore.resumeAllState();
location.reload();
}
......
......@@ -7,7 +7,7 @@
<script lang="ts">
import { computed, defineComponent, PropType, ref, watch } from 'vue';
// hooks
import { useSetting } from '/@/hooks/core/useSetting';
import { useGlobSetting } from '/@/settings/use';
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { useGo } from '/@/hooks/web/usePage';
......@@ -30,7 +30,7 @@
},
setup(props) {
const showRef = ref<boolean>(!!props.showTitle);
const { globSetting } = useSetting();
const globSetting = useGlobSetting();
const go = useGo();
function handleGoHome() {
......
......@@ -4,7 +4,7 @@ import { RouterView, RouteLocation } from 'vue-router';
import FrameLayout from '/@/layouts/iframe/index.vue';
import { useTransition } from './useTransition';
import { useSetting } from '/@/hooks/core/useSetting';
import { useProjectSetting } from '/@/settings/use';
import { tabStore } from '/@/store/modules/tab';
import { appStore } from '/@/store/modules/app';
......@@ -29,7 +29,7 @@ export default defineComponent({
const { on: transitionOn } = useTransition();
on = transitionOn;
}
const { projectSetting } = useSetting();
const projectSetting = useProjectSetting();
return () => {
const {
routerTransition,
......
import messages from 'globby?locale!/@/locales/lang/**/*.@(ts)';
import type { DropMenu } from '/@/components/Dropdown';
// locale list
export const localeList: DropMenu[] = [
{
text: '简体中文',
event: 'zh_CN',
},
{
text: 'English',
event: 'en',
},
];
export default messages;
export default {
tableTitle: 'Error log list',
tableColumnType: 'Type',
tableColumnDate: 'Time',
tableColumnFile: 'File',
tableColumnMsg: 'Error message',
tableColumnStackMsg: 'Stack info',
tableActionDesc: 'Details',
modalTitle: 'Error details',
fireVueError: 'Fire vue error',
fireResourceError: 'Fire resource error',
fireAjaxError: 'Fire ajax error',
enableMessage: 'Only effective when useErrorHandle=true in `/src/settings/projectSetting.ts`.',
};
export default {
backLogin: 'Back Login',
backHome: 'Back Home',
redo: 'Refresh',
subTitle403: "Sorry, you don't have access to this page.",
subTitle404: 'Sorry, the page you visited does not exist.',
subTitle500: 'Sorry, the server is reporting an error.',
noDataTitle: 'No data on the current page.',
networkErrorTitle: 'Network Error',
networkErrorSubTitle:
'Sorry,Your network connection has been disconnected, please check your network!',
};
export default {
alert: 'Lock screen password error',
backToLogin: 'Back to login',
entry: 'Enter the system',
placeholder: 'Please enter the lock screen password or user password',
};
export default {
loginButton: 'Login',
autoLogin: 'AutoLogin',
forgetPassword: 'Forget Password',
// notify
loginSuccessTitle: 'Login successful',
loginSuccessDesc: 'Welcome back',
// placeholder
accountPlaceholder: 'Please input Username',
passwordPlaceholder: 'Please input Password',
};
export default {
some: 'Get Out',
};
export default {
button: 'Login',
};
export default {
someentry: 'some text',
};
export default {
some: 'Get Out',
};
export default {
button: 'Login',
validation: {
account: 'Required Field account',
password: 'Required Field password',
},
};
export default {
some: '出去',
};
export default {
button: '登录',
};
export default {
tableTitle: '错误日志列表',
tableColumnType: '类型',
tableColumnDate: '时间',
tableColumnFile: '文件',
tableColumnMsg: '错误信息',
tableColumnStackMsg: 'stack信息',
tableActionDesc: '详情',
modalTitle: '错误详情',
fireVueError: '点击触发vue错误',
fireResourceError: '点击触发资源加载错误',
fireAjaxError: '点击触发ajax错误',
enableMessage: '只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效.',
};
export default {
backLogin: '返回登录',
backHome: '返回首页',
redo: '刷新',
subTitle403: '抱歉,您无权访问此页面。',
subTitle404: '抱歉,您访问的页面不存在。',
subTitle500: '抱歉,服务器报告错误。',
noDataTitle: '当前页无数据',
networkErrorTitle: '网络错误',
networkErrorSubTitle: '抱歉,您的网络连接已断开,请检查您的网络!',
};
export default {
alert: '锁屏密码错误',
backToLogin: '返回登录',
entry: '进入系统',
placeholder: '请输入锁屏密码或者用户密码',
};
export default {
loginButton: '登录',
autoLogin: '自动登录',
forgetPassword: '忘记密码',
// notify
loginSuccessTitle: '登录成功',
loginSuccessDesc: '欢迎回来',
// placeholder
accountPlaceholder: '请输入账号',
passwordPlaceholder: '请输入密码',
};
export type LocaleType = 'zhCN' | 'en' | 'ru' | 'ja';
export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja';
......@@ -6,7 +6,7 @@ import { createProgressGuard } from './progressGuard';
import { createPermissionGuard } from './permissionGuard';
import { createPageLoadingGuard } from './pageLoadingGuard';
import { useSetting } from '/@/hooks/core/useSetting';
import { useGlobSetting, useProjectSetting } from '/@/settings/use';
import { getIsOpenTab, setCurrentTo } from '/@/utils/helper/routeHelper';
import { setTitle } from '/@/utils/browser';
......@@ -14,9 +14,9 @@ import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel';
import { tabStore } from '/@/store/modules/tab';
const { projectSetting, globSetting } = useSetting();
const globSetting = useGlobSetting();
export function createGuard(router: Router) {
const { openNProgress, closeMessageOnSwitch, removeAllHttpPending } = projectSetting;
const { openNProgress, closeMessageOnSwitch, removeAllHttpPending } = useProjectSetting();
let axiosCanceler: AxiosCanceler | null;
if (removeAllHttpPending) {
axiosCanceler = new AxiosCanceler();
......
import type { Router, RouteRecordRaw } from 'vue-router';
import { userStore } from '/@/store/modules/user';
import { appStore } from '/@/store/modules/app';
import { permissionStore } from '/@/store/modules/permission';
import { PageEnum } from '/@/enums/pageEnum';
......@@ -72,7 +72,7 @@ export function createPermissionGuard(router: Router) {
router.afterEach((to) => {
// Just enter the login page and clear the authentication information
if (to.path === LOGIN_PATH) {
userStore.resumeAllState();
appStore.resumeAllState();
}
});
}
......@@ -35,10 +35,6 @@ const menu: MenuModule = {
path: 'img-preview',
name: '图片预览',
},
{
path: 'i18n',
name: '国际化',
},
{
path: 'copy',
name: '剪切板',
......
......@@ -80,14 +80,6 @@ const feat: AppRouteModule = {
title: '消息提示',
},
},
{
path: '/i18n',
name: 'I18nDemo',
component: () => import('/@/views/demo/feat/i18n/index.vue'),
meta: {
title: '国际化',
},
},
{
path: '/watermark',
name: 'WatermarkDemo',
......
......@@ -7,7 +7,16 @@ import { isProdMode } from '/@/utils/env';
// ! You need to clear the browser cache after the change
const setting: ProjectConfig = {
locale: 'en',
// locale setting
locale: {
// Locales
lang: 'zh_CN',
// Default locale
fallback: 'zh_CN',
// available Locales
availableLocales: ['zh_CN', 'en'],
},
// color
// TODO 主题色
themeColor: primaryColor,
......@@ -87,15 +96,7 @@ const setting: ProjectConfig = {
// 开启手风琴模式,只显示一个菜单
accordion: true,
},
// 消息配置
messageSetting: {
// 弹窗title
title: '操作提示',
// 取消按钮的文子,
cancelText: '取消',
// 确认按钮的文字
okText: '确定',
},
// 多标签
multiTabsSetting: {
// 开启
......
import type { ProjectConfig, GlobConfig, SettingWrap, GlobEnvConfig } from '/@/types/config';
import type { ProjectConfig, GlobConfig, GlobEnvConfig } from '/@/types/config';
import getProjectSetting from '/@/settings/projectSetting';
import { getGlobEnvConfig, isDevMode } from '/@/utils/env';
import { getShortName } from '../../../build/getShortName';
import { warn } from '/@/utils/log';
import { getGlobEnvConfig, isDevMode } from '/@/utils/env';
const reg = /[a-zA-Z\_]*/;
......@@ -12,6 +12,7 @@ const ENV_NAME = getShortName(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,
......@@ -25,7 +26,7 @@ if (!reg.test(VITE_GLOB_APP_SHORT_NAME)) {
);
}
export const useSetting = (): SettingWrap => {
export const useGlobSetting = (): Readonly<GlobConfig> => {
// Take global configuration
const glob: Readonly<GlobConfig> = {
title: VITE_GLOB_APP_TITLE,
......@@ -33,9 +34,10 @@ export const useSetting = (): SettingWrap => {
shortName: VITE_GLOB_APP_SHORT_NAME,
urlPrefix: VITE_GLOB_API_URL_PREFIX,
};
const projectSetting: Readonly<ProjectConfig> = getProjectSetting;
return {
globSetting: glob as Readonly<GlobConfig>,
projectSetting,
};
return glob as Readonly<GlobConfig>;
};
export const useProjectSetting = (): ProjectConfig => {
// TODO computed
return getProjectSetting;
};
import type { LocaleSetting } from '/@/types/config';
import { computed } from 'vue';
import { appStore } from '/@/store/modules/app';
import getProjectSetting from '/@/settings/projectSetting';
import { localeList } from '/@/locales';
export function useLocaleSetting() {
// Get locale configuration
const getLocale = computed(() => {
return appStore.getProjectConfig.locale || getProjectSetting.locale;
});
// get current language
const getLang = computed(() => {
return getLocale.value.lang;
});
// get Available Locales
const getAvailableLocales = computed((): string[] => {
return getLocale.value.availableLocales;
});
// get Fallback Locales
const getFallbackLocale = computed((): string => {
return getLocale.value.fallback;
});
// Set locale configuration
function setLocale(locale: Partial<LocaleSetting>): void {
appStore.commitProjectConfigState({ locale });
}
return { getLocale, getLang, localeList, setLocale, getAvailableLocales, getFallbackLocale };
}
......@@ -14,9 +14,7 @@ function isAuth(el: Element, binding: any) {
const value = binding.value;
if (!value) return;
if (!hasPermission(value)) {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
el.parentNode?.removeChild(el);
}
}
......
......@@ -9,7 +9,7 @@ const repeatDirective: Directive = {
beforeMount(el: Element, binding: DirectiveBinding<any>) {
let interval: Nullable<IntervalHandle> = null;
let startTime = 0;
const handler = (): void => binding.value && binding.value();
const handler = (): void => binding?.value();
const clear = (): void => {
if (Date.now() - startTime < 100) {
handler();
......
......@@ -3,7 +3,7 @@
*/
import { errorStore, ErrorInfo } from '/@/store/modules/error';
import { useSetting } from '/@/hooks/core/useSetting';
import { useProjectSetting } from '/@/settings/use';
import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
import { App } from 'vue';
......@@ -89,7 +89,7 @@ export function scriptErrorHandler(
const errorInfo: Partial<ErrorInfo> = {};
colno = colno || (window.event && (window.event as any).errorCharacter) || 0;
errorInfo.message = event as string;
if (error && error.stack) {
if (error?.stack) {
errorInfo.stack = error.stack;
} else {
errorInfo.stack = '';
......@@ -160,8 +160,7 @@ function registerResourceErrorHandler() {
* @param app
*/
export function setupErrorHandle(app: App) {
const { projectSetting } = useSetting();
const { useErrorHandle } = projectSetting;
const { useErrorHandle } = useProjectSetting();
if (!useErrorHandle) return;
// Vue exception monitoring;
app.config.errorHandler = vueErrorHandler;
......
import type { App } from 'vue';
import type { I18n, Locale, I18nOptions } from 'vue-i18n';
import { App, unref } from 'vue';
import type { I18n, I18nOptions } from 'vue-i18n';
import { createI18n } from 'vue-i18n';
import localeMessages from '/@/locales';
import { useLocale } from '/@/hooks/web/useLocale';
import { useLocaleSetting } from '/@/settings/use/useLocaleSetting';
const { getLocale } = useLocale();
const { setupLocale } = useLocale();
const { getLang, getAvailableLocales, getFallbackLocale } = useLocaleSetting();
const localeData: I18nOptions = {
legacy: false,
locale: getLocale(),
// TODO: setting fallback inside settings
fallbackLocale: 'en',
locale: unref(getLang),
fallbackLocale: unref(getFallbackLocale),
messages: localeMessages,
// availableLocales: ['ru'],
availableLocales: unref(getAvailableLocales),
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: false, // true - warning off
silentFallbackWarn: true,
......@@ -24,12 +25,10 @@ let i18n: I18n;
// setup i18n instance with glob
export function setupI18n(app: App) {
i18n = createI18n(localeData) as I18n;
setI18nLanguage(getLocale());
setupLocale();
app.use(i18n);
}
export function setI18nLanguage(locale: Locale): void {
// @ts-ignore
i18n.global.locale.value = locale;
// i18n.global.setLocaleMessage(locale, messages);
export function getI18n(): I18n {
return i18n;
}
......@@ -6,9 +6,19 @@ import store from '/@/store';
import { PROJ_CFG_KEY, LOCK_INFO_KEY } from '/@/enums/cacheEnum';
import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper';
import { setLocal, getLocal, removeLocal } from '/@/utils/helper/persistent';
import {
setLocal,
getLocal,
removeLocal,
clearSession,
clearLocal,
} from '/@/utils/helper/persistent';
import { deepMerge } from '/@/utils';
import { resetRouter } from '/@/router';
import { permissionStore } from './permission';
import { tabStore } from './tab';
import { userStore } from './user';
export interface LockInfo {
......@@ -77,6 +87,17 @@ class App extends VuexModule {
this.lockInfoState = null;
}
@Action
async resumeAllState() {
resetRouter();
clearSession();
clearLocal();
permissionStore.commitResetState();
tabStore.commitResetState();
userStore.commitResetState();
}
@Action
public async setPageLoadingAction(loading: boolean): Promise<void> {
if (loading) {
......
......@@ -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 { useSetting } from '/@/hooks/core/useSetting';
import { useProjectSetting } from '/@/settings/use';
export interface ErrorInfo {
type: ErrorTypeEnum;
......@@ -16,6 +16,7 @@ export interface ErrorInfo {
url: string;
time?: string;
}
export interface ErrorState {
errorInfoState: ErrorInfo[] | null;
errorListCountState: number;
......@@ -56,8 +57,7 @@ class Error extends VuexModule implements ErrorState {
@Action
setupErrorHandle(error: any) {
const { projectSetting } = useSetting();
const { useErrorHandle } = projectSetting;
const { useErrorHandle } = useProjectSetting();
if (!useErrorHandle) return;
const errInfo: Partial<ErrorInfo> = {
......
......@@ -15,13 +15,11 @@ import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum';
import { useMessage } from '/@/hooks/web/useMessage';
import router, { resetRouter } from '/@/router';
import { permissionStore } from './permission';
import { tabStore } from './tab';
import router from '/@/router';
import { loginApi, getUserInfoById } from '/@/api/sys/user';
import { setLocal, getLocal, clearSession, clearLocal } from '/@/utils/helper/persistent';
import { setLocal, getLocal } from '/@/utils/helper/persistent';
// import { FULL_PAGE_NOT_FOUND_ROUTE } from '/@/router/constant';
export type UserInfo = Omit<GetUserInfoByUserIdModel, 'roles'>;
......@@ -52,7 +50,7 @@ class User extends VuexModule {
}
@Mutation
resetState(): void {
commitResetState(): void {
this.userInfoState = null;
this.tokenState = '';
this.roleListState = [];
......@@ -128,16 +126,6 @@ class User extends VuexModule {
goLogin && router.push(PageEnum.BASE_LOGIN);
}
@Action
async resumeAllState() {
resetRouter();
clearSession();
clearLocal();
permissionStore.commitResetState();
tabStore.commitResetState();
this.resetState();
}
/**
* @description: Confirm before logging out
*/
......
......@@ -2,13 +2,7 @@
import { MenuTypeEnum, MenuModeEnum, TriggerEnum } from '/@/enums/menuEnum';
import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum } from '/@/enums/appEnum';
import type { LocaleType } from '/@/locales/types';
export interface MessageSetting {
title: string;
// 取消按钮的文字,
cancelText: string;
// 确认按钮的文字
okText: string;
}
export interface MenuSetting {
collapsed: boolean;
collapsedShowTitle: boolean;
......@@ -54,8 +48,18 @@ export interface HeaderSetting {
// 显示消息中心按钮
showNotice: boolean;
}
export interface LocaleSetting {
// Current language
lang: LocaleType;
// default language
fallback: LocaleType;
// available Locales
availableLocales: LocaleType[];
}
export interface ProjectConfig {
locale: LocaleType;
locale: LocaleSetting;
// header背景色
headerBgColor: string;
// 左侧菜单背景色
......@@ -81,8 +85,6 @@ export interface ProjectConfig {
// menuType: MenuTypeEnum;
menuSetting: MenuSetting;
messageSetting: MessageSetting;
// 多标签页设置
multiTabsSetting: MultiTabsSetting;
// pageLayout是否开启keep-alive
......@@ -133,12 +135,6 @@ export interface GlobEnvConfig {
VITE_GLOB_APP_SHORT_NAME: string;
}
// 修改配置
export type SetProjectSettingFn = <T extends keyof ProjectConfig>(
key: T,
value: ProjectConfig[T]
) => void;
interface GlobWrap {
globSetting: Readonly<GlobConfig>;
}
......@@ -146,5 +142,3 @@ interface GlobWrap {
interface ProjectSettingWrap {
projectSetting: Readonly<ProjectConfig>;
}
export type SettingWrap = GlobWrap & ProjectSettingWrap;
import { getEnv } from '/@/utils/env';
import { useSetting } from '/@/hooks/core/useSetting';
import { useGlobSetting } from '/@/settings/use';
import pkg from '../../../package.json';
const { globSetting } = useSetting();
const globSetting = useGlobSetting();
// Generate cache key according to version
export const getStorageShortName = () => {
......
......@@ -10,7 +10,7 @@ import { AxiosTransform } from './axiosTransform';
import { checkStatus } from './checkStatus';
import { useSetting } from '/@/hooks/core/useSetting';
import { useGlobSetting } from '/@/settings/use';
import { useMessage } from '/@/hooks/web/useMessage';
import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum';
......@@ -21,7 +21,7 @@ import { setObjToUrlParams, deepMerge } from '/@/utils';
import { errorStore } from '/@/store/modules/error';
import { errorResult } from './const';
const { globSetting } = useSetting();
const globSetting = useGlobSetting();
const prefix = globSetting.urlPrefix;
const { createMessage, createErrorModal } = useMessage();
......
<template>
<div class="p-4">
<Alert message="国际化方式,没有进行全局国际化,有需要可以自行处理。" type="info" />
<Divider />
国际化信息: {{ t('hello') }}
<Divider />
<a-button :type="localeRef === 'zhCN' ? 'primary' : 'default'" @click="localeRef = 'zhCN'">
中文
</a-button>
<a-button :type="localeRef === 'en' ? 'primary' : 'default'" @click="localeRef = 'en'">
英文
</a-button>
<Divider />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Alert, Divider } from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({
components: { Alert, Divider },
setup() {
const { t, localeRef } = useI18n({
locale: 'zhCN',
messages: {
en: {
hello: 'hello',
},
zhCN: {
hello: '你好',
},
},
});
return { localeRef, t };
},
});
</script>
<template>
<BasicModal :width="800" title="错误详情" v-bind="$attrs">
<BasicModal :width="800" :title="t('sys.errorLog.tableActionDesc')" v-bind="$attrs">
<Description :data="info" @register="register" />
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { useI18n } from 'vue-i18n';
import { BasicModal } from '/@/components/Modal/index';
import { ErrorInfo } from '/@/store/modules/error';
import { Description, useDescription } from '/@/components/Description/index';
import { getDescSchema } from './data';
export default defineComponent({
......@@ -20,12 +23,15 @@
},
},
setup() {
const { t } = useI18n();
const [register] = useDescription({
column: 2,
schema: getDescSchema(),
});
return {
register,
useI18n,
t,
};
},
});
......
......@@ -2,11 +2,15 @@ import { Tag } from 'ant-design-vue';
import { BasicColumn } from '/@/components/Table/index';
import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
import { useExternalI18n } from '/@/hooks/web/useLocale';
const { t } = useExternalI18n();
export function getColumns(): BasicColumn[] {
return [
{
dataIndex: 'type',
title: '类型',
title: t('sys.errorLog.tableColumnType'),
width: 80,
customRender: ({ text }) => {
const color =
......@@ -24,17 +28,17 @@ export function getColumns(): BasicColumn[] {
},
{
dataIndex: 'url',
title: '地址',
title: 'URL',
width: 200,
},
{
dataIndex: 'time',
title: '时间',
title: t('sys.errorLog.tableColumnDate'),
width: 160,
},
{
dataIndex: 'file',
title: '文件',
title: t('sys.errorLog.tableColumnFile'),
width: 200,
},
{
......@@ -44,12 +48,12 @@ export function getColumns(): BasicColumn[] {
},
{
dataIndex: 'message',
title: '错误信息',
title: t('sys.errorLog.tableColumnMsg'),
width: 300,
},
{
dataIndex: 'stack',
title: 'stack信息',
title: t('sys.errorLog.tableColumnStackMsg'),
width: 300,
},
];
......
......@@ -6,12 +6,22 @@
<DetailModal :info="rowInfoRef" @register="registerModal" />
<BasicTable @register="register" class="error-handle-table">
<template #toolbar>
<a-button @click="fireVueError" type="primary"> 点击触发vue错误 </a-button>
<a-button @click="fireResourceError" type="primary"> 点击触发resource错误 </a-button>
<a-button @click="fireAjaxError" type="primary"> 点击触发ajax错误 </a-button>
<a-button @click="fireVueError" type="primary">
{{ t('sys.errorLog.fireVueError') }}
</a-button>
<a-button @click="fireResourceError" type="primary">
{{ t('sys.errorLog.fireResourceError') }}
</a-button>
<a-button @click="fireAjaxError" type="primary">
{{ t('sys.errorLog.fireAjaxError') }}
</a-button>
</template>
<template #action="{ record }">
<TableAction :actions="[{ label: '详情', onClick: handleDetail.bind(null, record) }]" />
<TableAction
:actions="[
{ label: t('sys.errorLog.tableActionDesc'), onClick: handleDetail.bind(null, record) },
]"
/>
</template>
</BasicTable>
</div>
......@@ -21,10 +31,11 @@
import { defineComponent, watch, ref, nextTick } from 'vue';
import DetailModal from './DetailModal.vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table/index';
import { useModal } from '/@/components/Modal/index';
import { useMessage } from '/@/hooks/web/useMessage';
import { BasicTable, useTable, TableAction } from '/@/components/Table/index';
import { useI18n } from 'vue-i18n';
import { errorStore, ErrorInfo } from '/@/store/modules/error';
......@@ -42,12 +53,14 @@
const rowInfoRef = ref<ErrorInfo>();
const imgListRef = ref<string[]>([]);
const { t } = useI18n();
const [register, { setTableData }] = useTable({
title: '错误日志列表',
title: t('sys.errorLog.tableTitle'),
columns: getColumns(),
actionColumn: {
width: 80,
title: '操作',
title: 'Action',
dataIndex: 'action',
slots: { customRender: 'action' },
},
......@@ -67,7 +80,7 @@
);
const { createMessage } = useMessage();
if (isDevMode()) {
createMessage.info('只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效!');
createMessage.info(t('sys.errorLog.enableMessage'));
}
// 查看详情
function handleDetail(row: ErrorInfo) {
......@@ -96,6 +109,7 @@
fireAjaxError,
imgListRef,
rowInfoRef,
t,
};
},
});
......
......@@ -12,6 +12,7 @@ import { useRoute } from 'vue-router';
import { useGo, useRedo } from '/@/hooks/web/usePage';
import { PageEnum } from '/@/enums/pageEnum';
import { useI18n } from 'vue-i18n';
import './exception.less';
interface MapValue {
......@@ -47,9 +48,12 @@ export default defineComponent({
},
setup(props) {
const statusMapRef = ref(new Map<string | number, MapValue>());
const { query } = useRoute();
const go = useGo();
const redo = useRedo();
const { t } = useI18n();
const getStatus = computed(() => {
const { status: routeStatus } = query;
const { status } = props;
......@@ -62,41 +66,44 @@ export default defineComponent({
}
);
const backLoginI18n = t('sys.exception.backLogin');
const backHomeI18n = t('sys.exception.backHome');
unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_ACCESS, {
title: '403',
status: `${ExceptionEnum.PAGE_NOT_ACCESS}`,
subTitle: `Sorry, you don't have access to this page.!`,
btnText: props.full ? 'Back Login' : 'Back Home',
subTitle: t('sys.exception.subTitle403'),
btnText: props.full ? backLoginI18n : backHomeI18n,
handler: () => (props.full ? go(PageEnum.BASE_LOGIN) : go()),
});
unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_FOUND, {
title: '404',
status: `${ExceptionEnum.PAGE_NOT_FOUND}`,
subTitle: `Sorry, the page you visited does not exist.`,
btnText: props.full ? 'Back Login' : 'Back Home',
subTitle: t('sys.exception.subTitle404'),
btnText: props.full ? backLoginI18n : backHomeI18n,
handler: () => (props.full ? go(PageEnum.BASE_LOGIN) : go()),
});
unref(statusMapRef).set(ExceptionEnum.ERROR, {
title: '500',
status: `${ExceptionEnum.ERROR}`,
subTitle: `Sorry, the server is reporting an error.`,
btnText: 'Back Home',
subTitle: t('sys.exception.subTitle500'),
btnText: backHomeI18n,
handler: () => go(),
});
unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_DATA, {
title: 'No data on the current page',
title: t('sys.exception.noDataTitle'),
subTitle: '',
btnText: 'Refresh',
btnText: t('sys.exception.redo'),
handler: () => redo(),
icon: notDataImg,
});
unref(statusMapRef).set(ExceptionEnum.NET_WORK_ERROR, {
title: 'Network Error',
subTitle: 'Sorry,Your network connection has been disconnected, please check your network!',
title: t('sys.exception.networkErrorTitle'),
subTitle: t('sys.exception.networkErrorSubTitle'),
btnText: 'Refresh',
handler: () => redo(),
icon: netWorkImg,
......
......@@ -6,36 +6,38 @@
<p class="lock-page__header-name">{{ realName }}</p>
</div>
<BasicForm @register="register" v-if="!getIsNotPwd" />
<Alert v-if="errMsgRef" type="error" message="锁屏密码错误" banner />
<Alert v-if="errMsgRef" type="error" :message="t('sys.lock.alert')" banner />
<div class="lock-page__footer">
<a-button type="default" class="mt-2 mr-2" @click="goLogin" v-if="!getIsNotPwd">
返回登录
{{ t('sys.lock.backToLogin') }}
</a-button>
<a-button type="primary" class="mt-2" @click="unLock(!getIsNotPwd)" :loading="loadingRef">
进入系统
{{ t('sys.lock.entry') }}
</a-button>
</div>
</div>
</div>
</template>
<script lang="ts">
// 组件相关
import { defineComponent, ref, computed } from 'vue';
import { Alert } from 'ant-design-vue';
// hook
import { BasicForm, useForm } from '/@/components/Form';
import { userStore } from '/@/store/modules/user';
import { appStore } from '/@/store/modules/app';
import { useI18n } from 'vue-i18n';
export default defineComponent({
name: 'LockPage',
components: { Alert, BasicForm },
setup() {
// 获取配置文件
// 样式前缀
const loadingRef = ref(false);
const errMsgRef = ref(false);
const { t } = useI18n();
const [register, { validateFields }] = useForm({
showActionButtonGroup: false,
schemas: [
......@@ -45,7 +47,7 @@
component: 'InputPassword',
componentProps: {
style: { width: '100%' },
placeholder: '请输入锁屏密码或者用户密码',
placeholder: t('sys.lock.placeholder'),
},
rules: [{ required: true }],
},
......@@ -55,6 +57,14 @@
const { realName } = userStore.getUserInfoState || {};
return realName;
});
const getIsNotPwd = computed(() => {
if (!appStore.getLockInfo) {
return true;
}
return appStore.getLockInfo.pwd === undefined;
});
/**
* @description: unLock
*/
......@@ -76,17 +86,12 @@
loadingRef.value = false;
}
}
function goLogin() {
userStore.loginOut(true);
appStore.resetLockInfo();
}
const getIsNotPwd = computed(() => {
if (!appStore.getLockInfo) {
return true;
}
return appStore.getLockInfo.pwd === undefined;
});
// 账号密码登录
return {
register,
getIsNotPwd,
......@@ -95,6 +100,7 @@
unLock,
errMsgRef,
loadingRef,
t,
};
},
});
......
......@@ -4,6 +4,7 @@
<div class="login-form-wrap">
<div class="login-form mx-6">
<div class="login-form__content px-2 py-10">
<AppLocalPicker class="login-form__locale" />
<header>
<img :src="logo" class="mr-4" />
<h1>{{ title }}</h1>
......@@ -29,13 +30,15 @@
<a-col :span="12">
<a-form-item>
<!-- No logic, you need to deal with it yourself -->
<a-checkbox v-model:checked="autoLogin" size="small">自动登录</a-checkbox>
<a-checkbox v-model:checked="autoLogin" size="small">{{
t('sys.login.autoLogin')
}}</a-checkbox>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item :style="{ 'text-align': 'right' }">
<!-- No logic, you need to deal with it yourself -->
<a-button type="link" size="small">忘记密码</a-button>
<a-button type="link" size="small">{{ t('sys.login.forgetPassword') }}</a-button>
</a-form-item>
</a-col>
</a-row>
......@@ -47,7 +50,7 @@
:block="true"
@click="login"
:loading="formState.loading"
>{{ t('system.login.button') }}</a-button
>{{ t('sys.login.loginButton') }}</a-button
>
</a-form-item>
</a-form>
......@@ -61,6 +64,7 @@
import { Checkbox } from 'ant-design-vue';
import Button from '/@/components/Button/index.vue';
import { AppLocalPicker } from '/@/components/Application';
// import { BasicDragVerify, DragVerifyActionType } from '/@/components/Verify/index';
import { userStore } from '/@/store/modules/user';
......@@ -68,7 +72,7 @@
// import { appStore } from '/@/store/modules/app';
import { useMessage } from '/@/hooks/web/useMessage';
import { useSetting } from '/@/hooks/core/useSetting';
import { useGlobSetting } from '/@/settings/use';
import logo from '/@/assets/images/logo.png';
export default defineComponent({
......@@ -76,14 +80,16 @@
// BasicDragVerify,
AButton: Button,
ACheckbox: Checkbox,
AppLocalPicker,
},
setup() {
const formRef = ref<any>(null);
const autoLoginRef = ref(false);
// const verifyRef = ref<RefInstanceType<DragVerifyActionType>>(null);
const { globSetting } = useSetting();
const globSetting = useGlobSetting();
const { notification } = useMessage();
const { t } = useI18n();
// const openLoginVerifyRef = computed(() => appStore.getProjectConfig.openLoginVerify);
......@@ -97,8 +103,10 @@
});
const formRules = reactive({
account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
account: [{ required: true, message: t('sys.login.accountPlaceholder'), trigger: 'blur' }],
password: [
{ required: true, message: t('sys.login.passwordPlaceholder'), trigger: 'blur' },
],
// verify: unref(openLoginVerifyRef) ? [{ required: true, message: '请通过验证码校验' }] : [],
});
......@@ -123,8 +131,8 @@
);
if (userInfo) {
notification.success({
message: '登录成功',
description: `欢迎回来: ${userInfo.realName}`,
message: t('sys.login.loginSuccessTitle'),
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`,
duration: 3,
});
}
......@@ -134,7 +142,6 @@
formState.loading = false;
}
}
const { t } = useI18n();
return {
formRef,
// verifyRef,
......@@ -195,7 +202,14 @@
.respond-to(xlarge, { width: 540px; right:0});
}
&__locale {
position: absolute;
top: 10px;
right: 10px;
}
&__content {
position: relative;
width: 100%;
height: 100%;
border: 1px solid #999;
......
......@@ -126,7 +126,12 @@ const viteConfig: UserConfig = {
},
// The package will be recompiled using rollup, and the new package compiled into the esm module specification will be put into node_modules/.vite_opt_cache
optimizeDeps: {
include: ['echarts/map/js/china', 'ant-design-vue/es/locale/zh_CN', '@ant-design/icons-vue'],
include: [
'echarts/map/js/china',
'ant-design-vue/es/locale/zh_CN',
'ant-design-vue/es/locale/en_US',
'@ant-design/icons-vue',
],
},
// Local cross-domain proxy
......
......@@ -2926,6 +2926,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"
crypto-es@^1.2.6:
version "1.2.6"
resolved "https://registry.npmjs.org/crypto-es/-/crypto-es-1.2.6.tgz#468f3573a5d7b82e3b63b0004f55f905a6d3b12c"
integrity sha512-PQnrovdr5ibmOxqAh/Vy+A30RokHom7kb9Z61EPwfASfbcJCrCG4+vNNegmebNVHiXvS7WjYpHDePxnE/biEbA==
crypto-random-string@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册