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