xiaoxian521
3 years ago
50 changed files with 1613 additions and 1010 deletions
-
1.vscode/extensions.json
-
12.vscode/settings.json
-
10README.md
-
8build/plugins.ts
-
30locales/en.yaml
-
30locales/zh-CN.yaml
-
3mock/asyncRoutes.ts
-
32package.json
-
1451pnpm-lock.yaml
-
1src/assets/svg/enter_outlined.svg
-
1src/assets/svg/mdi_keyboard_esc.svg
-
24src/components/ReIcon/src/iconifyIconOffline.ts
-
9src/layout/components/navbar.vue
-
38src/layout/components/notice/index.vue
-
2src/layout/components/notice/noticeItem.vue
-
2src/layout/components/notice/noticeList.vue
-
7src/layout/components/screenfull/index.vue
-
42src/layout/components/search/components/SearchFooter.vue
-
165src/layout/components/search/components/SearchModal.vue
-
93src/layout/components/search/components/SearchResult.vue
-
3src/layout/components/search/components/index.ts
-
30src/layout/components/search/index.vue
-
9src/layout/components/sidebar/horizontal.vue
-
9src/layout/components/sidebar/mixNav.vue
-
23src/layout/components/tag/index.vue
-
26src/layout/hooks/useBoolean.ts
-
0src/layout/redirect.vue
-
6src/main.ts
-
6src/plugins/element-plus/index.ts
-
72src/plugins/i18n.ts
-
20src/plugins/i18n/config.ts
-
21src/plugins/i18n/en/buttons.ts
-
22src/plugins/i18n/en/menus.ts
-
77src/plugins/i18n/index.ts
-
21src/plugins/i18n/zh-CN/buttons.ts
-
22src/plugins/i18n/zh-CN/menus.ts
-
3src/router/modules/error.ts
-
25src/router/modules/externalLink.ts
-
3src/router/modules/index.ts
-
3src/router/modules/remaining.ts
-
5src/style/element-plus.scss
-
6src/style/sidebar.scss
-
54src/views/error/403.vue
-
54src/views/error/404.vue
-
54src/views/error/500.vue
-
16src/views/permission/button/index.vue
-
22src/views/permission/page/index.vue
-
6src/views/type.ts
-
4vite.config.ts
@ -0,0 +1,30 @@ |
|||
buttons: |
|||
hsLoginOut: LoginOut |
|||
hsfullscreen: FullScreen |
|||
hsexitfullscreen: ExitFullscreen |
|||
hsrefreshRoute: RefreshRoute |
|||
hslogin: Login |
|||
hsadd: Add |
|||
hsmark: Mark/Cancel |
|||
hssave: Save |
|||
hssearch: Search |
|||
hsexpendAll: Expand All |
|||
hscollapseAll: Collapse All |
|||
hssystemSet: Open ProjectConfig |
|||
hsdelete: Delete |
|||
hsreload: Reload |
|||
hscloseCurrentTab: Close CurrentTab |
|||
hscloseLeftTabs: Close LeftTabs |
|||
hscloseRightTabs: Close RightTabs |
|||
hscloseOtherTabs: Close OtherTabs |
|||
hscloseAllTabs: Close AllTabs |
|||
menus: |
|||
hshome: Home |
|||
hslogin: Login |
|||
hserror: Error Page |
|||
hsfourZeroFour: "404" |
|||
hsfourZeroOne: "403" |
|||
hsFive: "500" |
|||
permission: Permission Manage |
|||
permissionPage: Page Permission |
|||
permissionButton: Button Permission |
@ -0,0 +1,30 @@ |
|||
buttons: |
|||
hsLoginOut: 退出系统 |
|||
hsfullscreen: 全屏 |
|||
hsexitfullscreen: 退出全屏 |
|||
hsrefreshRoute: 刷新路由 |
|||
hslogin: 登陆 |
|||
hsadd: 新增 |
|||
hsmark: 标记/取消 |
|||
hssave: 保存 |
|||
hssearch: 搜索 |
|||
hsexpendAll: 全部展开 |
|||
hscollapseAll: 全部折叠 |
|||
hssystemSet: 打开项目配置 |
|||
hsdelete: 删除 |
|||
hsreload: 重新加载 |
|||
hscloseCurrentTab: 关闭当前标签页 |
|||
hscloseLeftTabs: 关闭左侧标签页 |
|||
hscloseRightTabs: 关闭右侧标签页 |
|||
hscloseOtherTabs: 关闭其他标签页 |
|||
hscloseAllTabs: 关闭全部标签页 |
|||
menus: |
|||
hshome: 首页 |
|||
hslogin: 登陆 |
|||
hserror: 错误页面 |
|||
hsfourZeroFour: "404" |
|||
hsfourZeroOne: "403" |
|||
hsFive: "500" |
|||
permission: 权限管理 |
|||
permissionPage: 页面权限 |
|||
permissionButton: 按钮权限 |
1451
pnpm-lock.yaml
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--ant-design" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 1024 1024"><path fill="currentColor" d="M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8z"></path></svg> |
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--mdi" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1V7m10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2Z"></path></svg> |
@ -0,0 +1,42 @@ |
|||
<template> |
|||
<div class="search-footer"> |
|||
<span class="search-footer-item"> |
|||
<enterOutlined class="icon" /> |
|||
确认 |
|||
</span> |
|||
<span class="search-footer-item"> |
|||
<IconifyIconOffline icon="arrow-up-line" class="icon" /> |
|||
<IconifyIconOffline icon="arrow-down-line" class="icon" /> |
|||
切换 |
|||
</span> |
|||
<span class="search-footer-item"> |
|||
<mdiKeyboardEsc class="icon" /> |
|||
关闭 |
|||
</span> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import enterOutlined from "/@/assets/svg/enter_outlined.svg?component"; |
|||
import mdiKeyboardEsc from "/@/assets/svg/mdi_keyboard_esc.svg?component"; |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
.search-footer { |
|||
display: flex; |
|||
color: #333; |
|||
|
|||
.search-footer-item { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-right: 14px; |
|||
} |
|||
|
|||
.icon { |
|||
padding: 2px; |
|||
margin-right: 3px; |
|||
font-size: 20px; |
|||
box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff, |
|||
0 1px 2px 1px #1e235a66; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,165 @@ |
|||
<script lang="ts" setup> |
|||
import { useRouter } from "vue-router"; |
|||
import SearchResult from "./SearchResult.vue"; |
|||
import SearchFooter from "./SearchFooter.vue"; |
|||
import { deleteChildren } from "/@/utils/tree"; |
|||
import { transformI18n } from "/@/plugins/i18n"; |
|||
import { useDebounceFn, onKeyStroke } from "@vueuse/core"; |
|||
import { ref, watch, computed, nextTick, shallowRef } from "vue"; |
|||
import { usePermissionStoreHook } from "/@/store/modules/permission"; |
|||
|
|||
interface Props { |
|||
/** 弹窗显隐 */ |
|||
value: boolean; |
|||
} |
|||
|
|||
interface Emits { |
|||
(e: "update:value", val: boolean): void; |
|||
} |
|||
|
|||
const emit = defineEmits<Emits>(); |
|||
const props = withDefaults(defineProps<Props>(), {}); |
|||
const router = useRouter(); |
|||
|
|||
const keyword = ref(""); |
|||
const activePath = ref(""); |
|||
const inputRef = ref<HTMLInputElement | null>(null); |
|||
const resultOptions = shallowRef([]); |
|||
const handleSearch = useDebounceFn(search, 300); |
|||
|
|||
/** 菜单树形结构 */ |
|||
const menusData = computed(() => { |
|||
return deleteChildren(usePermissionStoreHook().menusTree); |
|||
}); |
|||
|
|||
const show = computed({ |
|||
get() { |
|||
return props.value; |
|||
}, |
|||
set(val: boolean) { |
|||
emit("update:value", val); |
|||
} |
|||
}); |
|||
|
|||
watch(show, async val => { |
|||
if (val) { |
|||
/** 自动聚焦 */ |
|||
await nextTick(); |
|||
inputRef.value?.focus(); |
|||
} |
|||
}); |
|||
|
|||
/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */ |
|||
function flatTree(arr) { |
|||
const res = []; |
|||
function deep(arr) { |
|||
arr.forEach(item => { |
|||
res.push(item); |
|||
item.children && deep(item.children); |
|||
}); |
|||
} |
|||
deep(arr); |
|||
return res; |
|||
} |
|||
|
|||
/** 查询 */ |
|||
function search() { |
|||
const flatMenusData = flatTree(menusData.value); |
|||
resultOptions.value = flatMenusData.filter( |
|||
menu => |
|||
keyword.value && |
|||
transformI18n(menu.meta?.title, menu.meta?.i18n) |
|||
.toLocaleLowerCase() |
|||
.includes(keyword.value.toLocaleLowerCase().trim()) |
|||
); |
|||
if (resultOptions.value?.length > 0) { |
|||
activePath.value = resultOptions.value[0].path; |
|||
} else { |
|||
activePath.value = ""; |
|||
} |
|||
} |
|||
|
|||
function handleClose() { |
|||
show.value = false; |
|||
/** 延时处理防止用户看到某些操作 */ |
|||
setTimeout(() => { |
|||
resultOptions.value = []; |
|||
keyword.value = ""; |
|||
}, 200); |
|||
} |
|||
|
|||
/** key up */ |
|||
function handleUp() { |
|||
const { length } = resultOptions.value; |
|||
if (length === 0) return; |
|||
const index = resultOptions.value.findIndex( |
|||
item => item.path === activePath.value |
|||
); |
|||
if (index === 0) { |
|||
activePath.value = resultOptions.value[length - 1].path; |
|||
} else { |
|||
activePath.value = resultOptions.value[index - 1].path; |
|||
} |
|||
} |
|||
|
|||
/** key down */ |
|||
function handleDown() { |
|||
const { length } = resultOptions.value; |
|||
if (length === 0) return; |
|||
const index = resultOptions.value.findIndex( |
|||
item => item.path === activePath.value |
|||
); |
|||
if (index + 1 === length) { |
|||
activePath.value = resultOptions.value[0].path; |
|||
} else { |
|||
activePath.value = resultOptions.value[index + 1].path; |
|||
} |
|||
} |
|||
|
|||
/** key enter */ |
|||
function handleEnter() { |
|||
const { length } = resultOptions.value; |
|||
if (length === 0 || activePath.value === "") return; |
|||
router.push(activePath.value); |
|||
handleClose(); |
|||
} |
|||
|
|||
onKeyStroke("Enter", handleEnter); |
|||
onKeyStroke("ArrowUp", handleUp); |
|||
onKeyStroke("ArrowDown", handleDown); |
|||
</script> |
|||
|
|||
<template> |
|||
<el-dialog top="5vh" v-model="show" :before-close="handleClose"> |
|||
<el-input |
|||
ref="inputRef" |
|||
v-model="keyword" |
|||
clearable |
|||
placeholder="请输入关键词搜索" |
|||
@input="handleSearch" |
|||
> |
|||
<template #prefix> |
|||
<el-icon class="el-input__icon"> |
|||
<IconifyIconOffline icon="search" /> |
|||
</el-icon> |
|||
</template> |
|||
</el-input> |
|||
<div class="search-result-container"> |
|||
<el-empty v-if="resultOptions.length === 0" description="暂无搜索结果" /> |
|||
<SearchResult |
|||
v-else |
|||
v-model:value="activePath" |
|||
:options="resultOptions" |
|||
@click="handleEnter" |
|||
/> |
|||
</div> |
|||
<template #footer> |
|||
<SearchFooter /> |
|||
</template> |
|||
</el-dialog> |
|||
</template> |
|||
<style lang="scss" scoped> |
|||
.search-result-container { |
|||
margin-top: 20px; |
|||
} |
|||
</style> |
@ -0,0 +1,93 @@ |
|||
<template> |
|||
<div class="result"> |
|||
<template v-for="item in options" :key="item.path"> |
|||
<div |
|||
class="result-item" |
|||
:style="{ |
|||
background: |
|||
item?.path === active ? useEpThemeStoreHook().epThemeColor : '', |
|||
color: item.path === active ? '#fff' : '' |
|||
}" |
|||
@click="handleTo" |
|||
@mouseenter="handleMouse(item)" |
|||
> |
|||
<component |
|||
:is="useRenderIcon(item.meta?.icon ?? 'bookmark-2-line')" |
|||
></component> |
|||
<span class="result-item-title">{{ t(item.meta?.title) }}</span> |
|||
<enterOutlined /> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed } from "vue"; |
|||
import { useI18n } from "vue-i18n"; |
|||
import { useEpThemeStoreHook } from "/@/store/modules/epTheme"; |
|||
import { useRenderIcon } from "/@/components/ReIcon/src/hooks"; |
|||
import enterOutlined from "/@/assets/svg/enter_outlined.svg?component"; |
|||
|
|||
const { t } = useI18n(); |
|||
|
|||
interface optionsItem { |
|||
path: string; |
|||
meta?: { |
|||
icon?: string; |
|||
title?: string; |
|||
}; |
|||
} |
|||
|
|||
interface Props { |
|||
value: string; |
|||
options: Array<optionsItem>; |
|||
} |
|||
|
|||
interface Emits { |
|||
(e: "update:value", val: string): void; |
|||
(e: "enter"): void; |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<Props>(), {}); |
|||
const emit = defineEmits<Emits>(); |
|||
|
|||
const active = computed({ |
|||
get() { |
|||
return props.value; |
|||
}, |
|||
set(val: string) { |
|||
emit("update:value", val); |
|||
} |
|||
}); |
|||
|
|||
/** 鼠标移入 */ |
|||
async function handleMouse(item) { |
|||
active.value = item.path; |
|||
} |
|||
|
|||
function handleTo() { |
|||
emit("enter"); |
|||
} |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
.result { |
|||
padding-bottom: 12px; |
|||
|
|||
&-item { |
|||
display: flex; |
|||
align-items: center; |
|||
height: 56px; |
|||
margin-top: 8px; |
|||
padding: 14px; |
|||
border-radius: 4px; |
|||
background: #e5e7eb; |
|||
cursor: pointer; |
|||
|
|||
&-title { |
|||
display: flex; |
|||
flex: 1; |
|||
margin-left: 5px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,3 @@ |
|||
import SearchModal from "./SearchModal.vue"; |
|||
|
|||
export { SearchModal }; |
@ -0,0 +1,30 @@ |
|||
<script lang="ts" setup> |
|||
import { SearchModal } from "./components"; |
|||
import useBoolean from "../../hooks/useBoolean"; |
|||
const { bool: show, toggle } = useBoolean(); |
|||
function handleSearch() { |
|||
toggle(); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="search-container" @click="handleSearch"> |
|||
<IconifyIconOffline icon="search" /> |
|||
</div> |
|||
<SearchModal v-model:value="show" /> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.search-container { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 48px; |
|||
width: 40px; |
|||
cursor: pointer; |
|||
|
|||
&:hover { |
|||
background: #f6f6f6; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,26 @@ |
|||
import { ref } from "vue"; |
|||
|
|||
export default function useBoolean(initValue = false) { |
|||
const bool = ref(initValue); |
|||
|
|||
function setBool(value: boolean) { |
|||
bool.value = value; |
|||
} |
|||
function setTrue() { |
|||
setBool(true); |
|||
} |
|||
function setFalse() { |
|||
setBool(false); |
|||
} |
|||
function toggle() { |
|||
setBool(!bool.value); |
|||
} |
|||
|
|||
return { |
|||
bool, |
|||
setBool, |
|||
setTrue, |
|||
setFalse, |
|||
toggle |
|||
}; |
|||
} |
@ -0,0 +1,72 @@ |
|||
// 多组件库的国际化和本地项目国际化兼容
|
|||
import { App, WritableComputedRef } from "vue"; |
|||
import { storageLocal } from "/@/utils/storage"; |
|||
import { type I18n, createI18n } from "vue-i18n"; |
|||
|
|||
// element-plus国际化
|
|||
import enLocale from "element-plus/lib/locale/lang/en"; |
|||
import zhLocale from "element-plus/lib/locale/lang/zh-cn"; |
|||
|
|||
function siphonI18n(prefix = "zh-CN") { |
|||
return Object.fromEntries( |
|||
Object.entries(import.meta.globEager("../../locales/*.y(a)?ml")).map( |
|||
([key, value]) => { |
|||
const matched = key.match(/([A-Za-z0-9-_]+)\./i)[1]; |
|||
return [matched, value.default]; |
|||
} |
|||
) |
|||
)[prefix]; |
|||
} |
|||
|
|||
export const localesConfigs = { |
|||
zh: { |
|||
...siphonI18n("zh-CN"), |
|||
...zhLocale |
|||
}, |
|||
en: { |
|||
...siphonI18n("en"), |
|||
...enLocale |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* 国际化转换工具函数 |
|||
* @param message message |
|||
* @param isI18n 如果true,获取对应的消息,否则返回本身 |
|||
* @returns message |
|||
*/ |
|||
export function transformI18n( |
|||
message: string | unknown | object = "", |
|||
isI18n: boolean | unknown = false |
|||
) { |
|||
if (!message) { |
|||
return ""; |
|||
} |
|||
|
|||
// 处理存储动态路由的title,格式 {zh:"",en:""}
|
|||
if (typeof message === "object") { |
|||
const locale: string | WritableComputedRef<string> | any = |
|||
i18n.global.locale; |
|||
return message[locale?.value]; |
|||
} |
|||
|
|||
if (isI18n) { |
|||
return i18n.global.t.call(i18n.global.locale, message); |
|||
} else { |
|||
return message; |
|||
} |
|||
} |
|||
|
|||
// 此函数只是配合i18n Ally插件来进行国际化智能提示,并无实际意义(只对提示起作用),如果不需要国际化可删除
|
|||
export const $t = (key: string) => key; |
|||
|
|||
export const i18n: I18n = createI18n({ |
|||
legacy: false, |
|||
locale: storageLocal.getItem("responsive-locale")?.locale ?? "zh", |
|||
fallbackLocale: "en", |
|||
messages: localesConfigs |
|||
}); |
|||
|
|||
export function useI18n(app: App) { |
|||
app.use(i18n); |
|||
} |
@ -1,20 +0,0 @@ |
|||
import { siphonI18n } from "./index"; |
|||
|
|||
// element-plus国际化
|
|||
import enLocale from "element-plus/lib/locale/lang/en"; |
|||
import zhLocale from "element-plus/lib/locale/lang/zh-cn"; |
|||
|
|||
// 项目内自定义国际化
|
|||
const zhModules = import.meta.globEager("./zh-CN/**/*.ts"); |
|||
const enModules = import.meta.globEager("./en/**/*.ts"); |
|||
|
|||
export const localesConfigs = { |
|||
zh: { |
|||
...siphonI18n(zhModules, "zh-CN"), |
|||
...zhLocale |
|||
}, |
|||
en: { |
|||
...siphonI18n(enModules, "en"), |
|||
...enLocale |
|||
} |
|||
}; |
@ -1,21 +0,0 @@ |
|||
export default { |
|||
hsLoginOut: "LoginOut", |
|||
hsfullscreen: "FullScreen", |
|||
hsexitfullscreen: "ExitFullscreen", |
|||
hsrefreshRoute: "RefreshRoute", |
|||
hslogin: "Login", |
|||
hsadd: "Add", |
|||
hsmark: "Mark/Cancel", |
|||
hssave: "Save", |
|||
hssearch: "Search", |
|||
hsexpendAll: "Expand All", |
|||
hscollapseAll: "Collapse All", |
|||
hssystemSet: "Open ProjectConfig", |
|||
hsdelete: "Delete", |
|||
hsreload: "Reload", |
|||
hscloseCurrentTab: "Close CurrentTab", |
|||
hscloseLeftTabs: "Close LeftTabs", |
|||
hscloseRightTabs: "Close RightTabs", |
|||
hscloseOtherTabs: "Close OtherTabs", |
|||
hscloseAllTabs: "Close AllTabs" |
|||
}; |
@ -1,22 +0,0 @@ |
|||
export default { |
|||
hshome: "Home", |
|||
hslogin: "Login", |
|||
hssysManagement: "System Manage", |
|||
hsBaseinfo: "Base Info", |
|||
hserror: "Error Page", |
|||
hsfourZeroFour: "404", |
|||
hsfourZeroOne: "403", |
|||
hsFive: "500", |
|||
hsmenus: "MultiLevel Menu", |
|||
hsmenu1: "Menu1", |
|||
"hsmenu1-1": "Menu1-1", |
|||
"hsmenu1-2": "Menu1-2", |
|||
"hsmenu1-2-1": "Menu1-2-1", |
|||
"hsmenu1-2-2": "Menu1-2-2", |
|||
"hsmenu1-3": "Menu1-3", |
|||
hsmenu2: "Menu2", |
|||
permission: "Permission Manage", |
|||
permissionPage: "Page Permission", |
|||
permissionButton: "Button Permission", |
|||
externalLink: "External Link" |
|||
}; |
@ -1,77 +0,0 @@ |
|||
// 多组件库的国际化和本地项目国际化兼容
|
|||
import { App } from "vue"; |
|||
import { set } from "lodash-unified"; |
|||
import { createI18n } from "vue-i18n"; |
|||
import { localesConfigs } from "./config"; |
|||
import { storageLocal } from "/@/utils/storage"; |
|||
|
|||
/** |
|||
* 国际化转换工具函数 |
|||
* @param message message |
|||
* @param isI18n 如果true,获取对应的消息,否则返回本身 |
|||
* @returns message |
|||
*/ |
|||
export function transformI18n( |
|||
message: string | unknown | object = "", |
|||
isI18n: boolean | unknown = false |
|||
) { |
|||
if (!message) { |
|||
return ""; |
|||
} |
|||
|
|||
// 处理存储动态路由的title,格式 {zh:"",en:""}
|
|||
if (typeof message === "object") { |
|||
return message[i18n.global?.locale]; |
|||
} |
|||
|
|||
if (isI18n) { |
|||
//@ts-ignore
|
|||
return i18n.global.tc.call(i18n.global, message); |
|||
} else { |
|||
return message; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 从模块中抽取国际化 |
|||
* @param langs 存放国际化模块 |
|||
* @param prefix 语言 默认 zh-CN |
|||
* @returns obj 格式:{模块名.**} |
|||
*/ |
|||
export function siphonI18n( |
|||
langs: Record<string, Record<string, any>>, |
|||
prefix = "zh-CN" |
|||
) { |
|||
const langsObj: Recordable = {}; |
|||
Object.keys(langs).forEach((key: string) => { |
|||
let fileName = key.replace(`./${prefix}/`, "").replace(/^\.\//, ""); |
|||
fileName = fileName.substring(0, fileName.lastIndexOf(".")); |
|||
const keyList = fileName.split("/"); |
|||
const moduleName = keyList.shift(); |
|||
const objKey = keyList.join("."); |
|||
const langFileModule = langs[key].default; |
|||
|
|||
if (moduleName) { |
|||
if (objKey) { |
|||
set(langsObj, moduleName, langsObj[moduleName] || {}); |
|||
set(langsObj[moduleName], objKey, langFileModule); |
|||
} else { |
|||
set(langsObj, moduleName, langFileModule || {}); |
|||
} |
|||
} |
|||
}); |
|||
return langsObj; |
|||
} |
|||
|
|||
// 此函数只是配合i18n Ally插件来进行国际化智能提示,并无实际意义(只对提示起作用),如果不需要国际化可删除
|
|||
export const $t = (key: string) => key; |
|||
|
|||
export const i18n = createI18n({ |
|||
locale: storageLocal.getItem("responsive-locale")?.locale ?? "zh", |
|||
fallbackLocale: "en", |
|||
messages: localesConfigs |
|||
}); |
|||
|
|||
export function usI18n(app: App) { |
|||
app.use(i18n); |
|||
} |
@ -1,21 +0,0 @@ |
|||
export default { |
|||
hsLoginOut: "退出系统", |
|||
hsfullscreen: "全屏", |
|||
hsexitfullscreen: "退出全屏", |
|||
hsrefreshRoute: "刷新路由", |
|||
hslogin: "登陆", |
|||
hsadd: "新增", |
|||
hsmark: "标记/取消", |
|||
hssave: "保存", |
|||
hssearch: "搜索", |
|||
hsexpendAll: "全部展开", |
|||
hscollapseAll: "全部折叠", |
|||
hssystemSet: "打开项目配置", |
|||
hsdelete: "删除", |
|||
hsreload: "重新加载", |
|||
hscloseCurrentTab: "关闭当前标签页", |
|||
hscloseLeftTabs: "关闭左侧标签页", |
|||
hscloseRightTabs: "关闭右侧标签页", |
|||
hscloseOtherTabs: "关闭其他标签页", |
|||
hscloseAllTabs: "关闭全部标签页" |
|||
}; |
@ -1,22 +0,0 @@ |
|||
export default { |
|||
hshome: "首页", |
|||
hslogin: "登陆", |
|||
hssysManagement: "系统管理", |
|||
hsBaseinfo: "基础信息", |
|||
hserror: "错误页面", |
|||
hsfourZeroFour: "404", |
|||
hsfourZeroOne: "403", |
|||
hsFive: "500", |
|||
hsmenus: "多级菜单", |
|||
hsmenu1: "菜单1", |
|||
"hsmenu1-1": "菜单1-1", |
|||
"hsmenu1-2": "菜单1-2", |
|||
"hsmenu1-2-1": "菜单1-2-1", |
|||
"hsmenu1-2-2": "菜单1-2-2", |
|||
"hsmenu1-3": "菜单1-3", |
|||
hsmenu2: "菜单2", |
|||
permission: "权限管理", |
|||
permissionPage: "页面权限", |
|||
permissionButton: "按钮权限", |
|||
externalLink: "外链" |
|||
}; |
@ -1,25 +0,0 @@ |
|||
import { $t } from "/@/plugins/i18n"; |
|||
const Layout = () => import("/@/layout/index.vue"); |
|||
|
|||
const externalLink = { |
|||
path: "/externals", |
|||
component: Layout, |
|||
meta: { |
|||
icon: "link", |
|||
title: $t("menus.externalLink"), |
|||
i18n: true, |
|||
rank: 190 |
|||
}, |
|||
children: [ |
|||
{ |
|||
path: "/external", |
|||
name: "https://pure-admin-doc.vercel.app", |
|||
meta: { |
|||
title: $t("menus.externalLink"), |
|||
i18n: true |
|||
} |
|||
} |
|||
] |
|||
}; |
|||
|
|||
export default externalLink; |
@ -1,6 +0,0 @@ |
|||
export type infoType = { |
|||
svg?: string; |
|||
code?: number; |
|||
info?: string; |
|||
accessToken?: string; |
|||
}; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue