<template>
<el-container>
<sidebar />
<el-main style="margin:0;padding:0;">
<el-container class="main-container">
<el-header class="bg-blue-600">头部</el-header>
<el-main style="margin:0;padding:0;">
<AppMain />
</el-main>
<el-footer class="bg-blue-800">底部</el-footer>
</el-container>
</el-main>
</el-container>
</template>
<script setup>
import { Sidebar, AppMain } from './components'
</script>
components/AppMain.vue
<template>
<router-view v-slot="{ Component, route }">
<transition name="fade-transform" mode="out-in">
<keep-alive >
<component :is="Component" :key="route.path" />
</keep-alive>
</transition>
</router-view>
</template>
<script setup>
const route = useRoute()
</script>
<style lang="scss" scoped>
// .app-main {
// /* 50= navbar 50 */
// // min-height: calc(100vh - 230px);
// width: 100%;
// position: relative;
// overflow: hidden;
// height:calc( 100% - 52px );
// }
.app-main {
flex: auto;
min-height: 0;
height: 100%;
}
</style>
import { createWebHashHistory, createRouter } from 'vue-router'
import Layout from '@/layout'
// createWebHistory 相当于router3的 'history' '/index'
// createWebHashHistory 相当于router3的 'hash' '/#/index'
// 公共路由
export const constantRoutes = [
{
path: '/login',
component: () => import('../views/login.vue'),
hidden: true
},
{
path: '',
component: Layout,
redirect: '/index',
children: [
{
path: '/index',
component: () => import('@/views/dashboard'),
name: 'Index',
meta: { title: '首页', icon: 'home', affix: true }
}
]
},
{
path: "/:pathMatch(.*)*",
component: () => import('../views/error/404.vue'),
hidden: true
},
{
path: '/401',
component: () => import('../views/error/401.vue'),
hidden: true
},
];
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
},
});
export default router;
// 需要创建 index.vue
<template>
<el-aside :width="variables.sideBarWidth" >
<el-menu default-active="2" class="el-menu-vertical-demo" :collapse="isCollapse" @open="handleOpen" @close="handleClose">
<sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
</el-menu>
</el-aside>
</template>
<script setup>
import variables from '@/assets/styles/variables.module.scss' // scss 公共样式
import usePermissionStore from '@/store/modules/permission'
import SidebarItem from './SidebarItem'
const permissionStore = usePermissionStore()
const sidebarRouters = computed(() => permissionStore.sidebarRouters);
const isCollapse = ref(false)
</script>
<style lang="scss" scoped>
</style>
// 创建 SidebarItem.vue
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children)">
<!-- 一级菜单 -->
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<el-icon>
<setting />
</el-icon>
<template #title>{{ onlyOneChild.meta.title }}</template>
</el-menu-item>
</app-link>
</template>
<!-- 二级菜单 -->
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template #title>
<el-icon>
<location />
</el-icon>
<span>{{ 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, getNormalPath } from '@/utils/validate'
import AppLink from './Link'
const props = defineProps({
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
})
console.log(props);
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 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)
}
</script>
<style lang="scss" scoped>
</style>
<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>
/**
* 判断url是否是http或https
* @param {string} path
* @returns {Boolean}
*/
export function isHttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
}
/**
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
// 返回项目路径
export function getNormalPath(p) {
if (p.length === 0 || !p || p == 'undefined') {
return p
};
let res = p.replace('//', '/')
if (res[res.length - 1] === '/') {
return res.slice(0, res.length - 1)
}
return res;
}