设置完基本的路由之后,接下来就可以进行常规的layout页面布局改造了
一、router/index.ts路由加入layout组件
import { createRouter, createWebHashHistory } from 'vue-router'
import Layout from '@/layout'
//公共路由
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index.vue')
}
]
},
{
path: '',
component: Layout,
redirect: '/index',
children: [
{
path: '/index',
component: () => import('@/views/index.vue'),
name: 'Index',
meta: { title: '首页', icon: 'home', affix: true }
}
]
},
{
path: '/',
component: Layout,
hidden: false,
redirect: 'noredirect',
children: [
{
path: 'userManage',
component: () => import('@/views/userManage/index.vue'),
name: 'UserManage',
meta: { title: '用户管理', icon: 'user' }
}
]
},
{
name: "Func",
path: "/func",
hidden: false,
redirect: "noRedirect",
component: Layout,
alwaysShow: true,
meta: {
title: "业务管理",
icon: "func",
noCache: false,
link: null
},
children: [
{
name: "NoteManage",
path: "noteManage",
hidden: false,
component: () => import('@/views/func/noteManage/index.vue'),
meta: {
title: "语录管理",
icon: "#",
noCache: false,
link: null
}
}
]
}
]
// 动态路由,基于用户权限动态去加载
export const dynamicRoutes = [
// {
// path: '/system/user-auth',
// component: Layout,
// hidden: true,
// permissions: ['system:user:edit'],
// children: [
// {
// path: 'role/:userId(\\d+)',
// component: () => import('@/views/func/noteManage/index.vue'),
// name: 'AuthRole',
// meta: { title: '分配角色', activeMenu: '/system/user' }
// }
// ]
// }
]
const router = createRouter({
// hash 模式。
history: createWebHashHistory(),
routes: constantRoutes
})
export default router
注意事项
electron + vue无法使用history模式
在Electron + Vue项目中,如果遇到无法使用history模式的问题,通常是因为Vue Router默认使用的是HTML5的history模式,它依赖于浏览器的history API来管理路由。然而,在Electron中直接使用history模式时会遇到问题,因为Electron的本地页面文件被认为是在本地文件系统上,而不是在服务器上,导致路由变化时尝试从文件系统获取资源而不是通过Vue的路由处理。
二、在src目录下新建layout组件文件结构
1、layout/index.vue
<script setup lang="ts">
import Sidebar from './components/Sidebar/index.vue'
import { AppMain, Navbar } from './components'
</script>
<template>
<div class="app-wrapper">
<sidebar class="sidebar-container" />
<div class="main-container">
<div class="fixed-header">
<navbar />
</div>
<app-main class="main"/>
</div>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/styles/mixin.scss";
@import "@/assets/styles/variables.module.scss";
.app-wrapper {
position: relative;
height: 100%;
width: 100%;
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$base-sidebar-width});
transition: width 0.28s;
background-color: #606787;
color: #ffffff;
}
.main{
width: 100%;
}
}
</style>
2、layout/components/Navbar.vue
<script setup lang="ts">
const route = useRoute()
const title = computed(() => route.meta.title);
</script>
<template>
<div class="navbar">
<div class="navbar-container">
<h3>{{ title }}</h3>
</div>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/styles/variables.module.scss";
.navbar {
height: $base-navbar-height;
overflow: hidden;
position: relative;
}
</style>
3、layout/components/AppMain.vue
<template>
<section class="app-main">
<div class="app-main-container">
<router-view v-slot="{ Component, route }" :key="keyVal">
<transition name="fade-transform" mode="out-in">
<keep-alive >
<component v-if="!route.meta.link" :is="Component" :key="route.path"/>
</keep-alive>
</transition>
</router-view>
</div>
</section>
</template>
<script setup>
const route = useRoute();
const keyVal = computed(() => {
return route.path + Math.random();
});
</script>
<style lang="scss" scoped>
@import "@/assets/styles/variables.module.scss";
.app-main {
min-height: calc(100vh - #{$base-navbar-height});
width: 100%;
position: relative;
overflow: hidden;
.app-main-container {
height: calc(100vh - #{$base-navbar-height});
border-radius:24px;
padding: 24px;
}
}
.fixed-header + .app-main {
padding-top: $base-navbar-height;
}
.hasTagsView {
.app-main {
/* 84 = navbar + tags-view = 50 + 34 */
min-height: calc(100vh - 132px);
}
.fixed-header + .app-main {
padding-top: 132px;
}
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 6px;
}
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background-color: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background-color: #c0c0c0;
border-radius: 3px;
}
</style>
4、layout/components/Sidebar/index.vue
<template>
<div>
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper" style="padding: 20px;">
<el-menu
:default-active="activeMenu"
:background-color="sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
:text-color="sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
v-for="(route, index) in sidebarRouters"
:key="route.path + index"
:item="route"
:base-path="route.path"
/>
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import SidebarItem from './SidebarItem'
import variables from '@/assets/styles/variables.module.scss'
import usePermissionStore from '@/store/modules/permission'
import useSettingsStore from '@/store/modules/settings'
const permissionStore = usePermissionStore()
const settingsStore = useSettingsStore()
const sidebarRouters = computed(() => permissionStore.sidebarRouters);
const sideTheme = computed(() => settingsStore.sideTheme);
const theme = computed(() => settingsStore.theme);
const route = useRoute()
const activeMenu = computed(() => {
const { meta, path } = route;
if (meta.activeMenu) {
return meta.activeMenu;
}
return path;
})
onBeforeMount(() => {
permissionStore.generateRoutes()
})
</script>
<style scoped lang="scss">
</style>
5、layout/components/Sidebar/sidebarItem.vue
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }" @click="handleClick(item.path)">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>
<template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>
</el-menu-item>
</app-link>
</template>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
<template v-if="item.meta" #title>
<svg-icon :icon-class="item.meta && item.meta.icon" />
<span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-sub-menu>
</div>
</template>
<script setup>
import { isExternal } from '@/utils/validate'
import AppLink from './Link'
import { getNormalPath } from '@/utils/mis'
const props = defineProps({
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
})
const onlyOneChild = ref({});
function hasOneShowingChild(children = [], parent) {
if (!children) {
children = [];
}
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
};
function handleClick(routePath){
localStorage.setItem('routePath', routePath)
}
function resolvePath(routePath, routeQuery) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(props.basePath)) {
return props.basePath
}
if (routeQuery) {
let query = JSON.parse(routeQuery);
return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
}
return getNormalPath(props.basePath + '/' + routePath)
}
function hasTitle(title){
if (title.length > 5) {
return title;
} else {
return "";
}
}
</script>
6、layout/components/Sidebar/Link.vue
<template>
<component :is="type" v-bind="linkProps()">
<slot />
</component>
</template>
<script setup>
import { isExternal } from '@/utils/validate'
const props = defineProps({
to: {
type: [String, Object],
required: true
}
})
const isExt = computed(() => {
return isExternal(props.to)
})
const type = computed(() => {
if (isExt.value) {
return 'a'
}
return 'router-link'
})
function linkProps() {
if (isExt.value) {
return {
href: props.to,
target: '_blank',
rel: 'noopener'
}
}
return {
to: props.to
}
}
</script>
7、layout/components/innerLink/index.vue
<template>
<div :style="'height:' + height">
<iframe
:id="iframeId"
style="width: 100%; height: 100%"
:src="src"
frameborder="no"
></iframe>
</div>
</template>
<script setup>
const props = defineProps({
src: {
type: String,
default: "/"
},
iframeId: {
type: String
}
});
const height = ref(document.documentElement.clientHeight - 94.5 + "px");
</script>
app.vue改造内容如下
<script setup lang="ts">
const route = useRoute();
const keyVal = computed(() => {
return route.path + Math.random();
});
</script>
<template>
<router-view v-slot="{ Component }" :key="keyVal">
<keep-alive>
<component :is="Component" :key="route.path"/>
</keep-alive>
</router-view>
</template>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。