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