在webpack5 +Vue3 的基础上,配合element ui 搭建了基础后台页面 ,包含layout +导航栏+路由
登录/注册页面切换方式
index.vue
<template>
<el-container>
<!-- 侧边导航栏 -->
<el-aside
:class="{
open: collapseStatus == false && currentWidth > 800,
close: collapseStatus == true && currentWidth > 800,
hidden: currentWidth <= 800
}"
>
<SideBar :collapseStatus="collapseStatus" :CurrentWidth="currentWidth"></SideBar>
</el-aside>
<el-container>
<!-- 顶层导航栏 -->
<NavBar @collapseStatus="setcollapse" :CurrentWidth="currentWidth"></NavBar>
<!-- 主要内容 -->
<el-main>
<AppMain></AppMain>
</el-main>
<el-footer>叮咚买菜!您菜到家了!</el-footer>
</el-container>
</el-container>
</template>
<script>
import { NavBar, SideBar, AppMain } from './components'
export default {
data() {
return {
collapseStatus: false,
currentWidth: 0
}
},
components: {
NavBar,
SideBar,
AppMain
},
mounted() {
this.getWidth()
},
methods: {
//侧边栏打开关闭状态s
setcollapse(params) {
// console.log('关闭打开状态', params)
this.collapseStatus = params
},
getWidth() {
this.currentWidth = document.body.clientWidth
// console.log(this.currentWidth)
window.onresize = () => {
this.currentWidth = document.body.clientWidth
console.log(this.currentWidth)
}
}
}
}
</script>
<style lang="scss" scoped>
.el-container {
height: 100%;
width: 100%;
border: 0px;
// .el-aside {
// display: flex;
// flex-direction: column;
// text-align: left;
// }
//屏幕宽度>800
.open {
width: 200px;
}
.close {
width: 75px;
::v-deep .menuItem > span {
display: none;
}
::v-deep .el-submenu__icon-arrow.el-icon-arrow-down {
display: none;
}
}
//屏幕宽度<800
.hidden {
width: 0px;
}
}
.el-footer {
text-align: center;
line-height: 60px;
padding: 0px;
overflow: hidden;
}
.el-main {
background-color: white;
margin: 10px 10px 0px;
box-shadow: -1px 1px 5px #888888;
padding: 16px;
color: #333;
}
</style>
<style lang="scss">
// 修改导航竖排模式下的样式
.el-popper.is-light.is-pure {
border: none;
box-shadow: -2px 2px 2px #333;
}
</style>
components
index.js
export { default as NavBar } from './Navbar.vue'
export { default as SideBar } from './SideBar.vue'
export { default as AppMain } from './AppMain.vue'
AppMain.vue
主内容
<template>
<section class="app-main">
<router-view :key="key" />
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
key() {
return this.$route.path
}
}
}
</script>
<style scoped>
.app-main {
/*60 = navbar */
/* min-height: calc(100vh - 60px); */
min-height: 100%;
width: 100%;
position: relative;
overflow: hidden;
}
/* .fixed-header + .app-main {
padding-top: 50px;
} */
</style>
<style lang="scss">
// fix css style bug in open el-dialog
// .el-popup-parent--hidden {
// .fixed-header {
// padding-right: 15px;
// }
// }
</style>
NavBar.vue
横排导航栏
<template>
<el-header>
<el-menu class="NavBar" mode="vertical" text-color="#fff" active-text-color="#ffd04b">
<!-- 打开收起 -->
<!-- width>800 PC 展示 -->
<div @click="iscollapse" class="collapse_content" v-if="currentWidth > 800">
<i class="el-icon-s-fold" v-if="collapse == false"></i>
<i class="el-icon-s-unfold" v-if="collapse"></i>
</div>
<!-- width<800 抽屉 展示 -->
<div v-if="currentWidth <= 800" class="collapse_content" @click="isdrawer">
<i class="el-icon-menu"></i>
</div>
<!-- 抽屉展示 -->
<el-drawer :with-header="false" v-model="drawer" direction="ltr" destroy-on-close size="150px">
<SideBar></SideBar>
</el-drawer>
<!-- 面包屑 -->
<div :class="{ breadcrumb_container: currentWidth > 400, breadcrumb_hidden: currentWidth <= 400 }">
<NavBread></NavBread>
</div>
<el-dropdown trigger="hover">
<span class="el-dropdown-link">
<el-tooltip class="item" effect="dark" content="admin菜到了" placement="left">
<span>
<img src="@/assets/avatar.png" />
</span>
</el-tooltip>
<i class="el-icon-arrow-down"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="el-icon-s-home" @click="backHome">
<span>首页</span>
</el-dropdown-item>
<el-dropdown-item icon="el-icon-switch-button" @click="logOut">
<span>登出</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-menu>
</el-header>
</template>
<script>
import NavBread from './NavBread.vue'
import SideBar from './SideBar.vue'
import { getToken, removeToken } from '@/utils/auth'
import { ElMessage } from 'element-plus'
export default {
data() {
return {
collapse: false, //false打开,true关闭
drawer: false,
currentWidth: 0,
isChoosed: false
}
},
props: {
CurrentWidth: {
type: Number
}
},
components: {
NavBread,
SideBar
},
watch: {
CurrentWidth() {
this.currentWidth = this.CurrentWidth
this.drawer = false //窗口变化直接关闭抽屉
if (this.CurrentWidth > 800) {
this.drawer = false //窗口样式不属于抽屉时关闭抽屉
}
// if (this.CurrentWidth > 800 && this.CurrentWidth <= 1000) {
// this.collapse = true
// this.$emit('collapseStatus', this.collapse)
// } else if (this.CurrentWidth > 1000) {
// this.collapse = false
// this.$emit('collapseStatus', this.collapse)
//震惊 !!!!这是个bug, 在1000以上启动项目时,能正常缩放///// 但小于1000下启动项目会造成路由出问题
//未解决,暂时去除
}
},
mounted() {},
methods: {
isLogin() {
var token = getToken('user_token')
if (token == undefined || token == '' || token == null) {
this.$router.push('/login')
}
},
logOut() {
removeToken()
this.isLogin()
ElMessage.success('成功退出')
},
backHome() {
//返回首页
this.$router.push('/home')
},
iscollapse() {
//打开关闭侧边栏
this.collapse = !this.collapse
this.$emit('collapseStatus', this.collapse)
},
//抽屉导航打开状态
isdrawer() {
this.drawer = !this.drawer
// console.log(11111, this.drawer)
}
// hasChoose(params) {
// this.isChoosed = params
// console.log('选择', this.isChoosed)
// if (this.isChoosed) {
// this.drawer = false
// this.isChoosed = false
// }
// }
}
}
</script>
<style lang="scss" scoped>
//NavBar样式
.el-header {
text-align: center;
line-height: 60px;
padding: 0px;
box-shadow: 3px 3px 5px #888888;
background-color: white;
.NavBar {
border: 0px;
}
}
//打开关闭侧边栏按钮样式
.collapse_content {
float: left;
cursor: pointer;
height: 100%;
font-size: 25px;
padding: 0 15px;
}
//面包屑样式
.breadcrumb_container {
float: left;
.el-breadcrumb {
font-size: 13px;
line-height: 60px;
}
}
.breadcrumb_hidden {
display: none;
}
//抽屉弹窗
::v-deep .el-drawer.ltr {
width: 150px;
}
//头像样式
::v-deep .el-dropdown {
float: right;
.el-dropdown-link {
display: flex;
align-items: center;
padding-right: 20px;
font-size: 18px;
img {
height: 40px;
width: 40px;
padding: 10px;
border-radius: 25px;
}
}
}
::v-deep .el-popper.is-light.is-pure {
border: none;
}
::v-deep .el-menu.el-menu--horizontal{
border: none;
}
// ul.el-menu.el-menu--popup.el-menu--popup-bottom-start
</style>
NavBreade.vue
面包屑
<template>
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/home' }">Ebuy</el-breadcrumb-item>
<el-breadcrumb-item v-for="item in pathMap" :key="item.meta">
{{ item.meta.title }}
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script>
export default {
data() {
return {
pathMap: ''
}
},
mounted() {
this.getPath()
},
methods: {
getPath() {
const route = this.$route
const { matched } = route
this.pathMap = matched
//过滤掉不存在meta.titie的路由
this.pathMap = this.pathMap.filter(function (item) {
return item.meta.title != null
})
}
},
//监听路由变化
watch: {
$route: 'getPath'
}
}
</script>
<style scoped></style>
SiderBar
左侧导航栏
<template>
<div v-if="CurrentWidth > 800" class="aside">
<img src="@/assets/ebuy-logo.png" class="SideBar_logo" />
<el-menu
ref="asideMenu"
:default-active="activeMenu"
:uniqueOpened="true"
:mode="mode"
:menu-trigger="menuOpenMethods"
class="SideBar_content"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
router
>
<AsideBarItem v-for="route in routes" :key="route.path" :item="route"></AsideBarItem>
</el-menu>
</div>
<!-- 小于800的样式要重写 -->
<div v-else class="aside_mini">
<!-- <img src="@/assets/ebuy-logo.png" class="SideBar_logo" v-if="CurrentWidth > 800" /> -->
<el-menu
:default-active="activeMenu"
:uniqueOpened="true"
mode="horizontal"
:menu-trigger="menuOpenMethods"
class="SideBar_content"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
router
>
<AsideBarItem v-for="route in routes" :key="route.path" :item="route"></AsideBarItem>
</el-menu>
</div>
</template>
<script>
import AsideBarItem from './asideItem.vue'
export default {
components: {
AsideBarItem
},
props: {
collapseStatus: {
type: Boolean
},
CurrentWidth: {
type: Number
}
},
watch: {
collapseStatus() {
if (this.collapseStatus == false) {
this.mode = 'vertical'
} else {
this.mode = 'horizontal'
this.closeMenu() //当路由模式变化时自动关闭当前激活的路由
}
}
// CurrentWidth() {
// if (this.CurrentWidth <= 1000) {
// this.mode = 'horizontal'
// } else if (this.CurrentWidth > 1000) {
// this.mode = 'vertical'
// }
// }
},
data() {
return {
mode: 'vertical',
menuOpenMethods: 'hover',
drawer: false,
closePath: ''
}
},
computed: {
routes() {
//返回路由
return this.$router.options.routes
},
activeMenu() {
//保持左侧导航栏的激活状态
const route = this.$route
const { path } = route
this.closePath = path.split('/')[1] //分割路由 用于关闭当前激活的路由导航
return path
}
},
methods: {
closeMenu() {
this.$refs['asideMenu'].close('/' + this.closePath) //关闭打开的menu
}
// handleSelect(hasChoose) {
// this.$emit('hasChoose', true)
// }
}
}
</script>
<style lang="scss" scoped>
.el-menu {
border: none;
}
.aside {
display: flex;
flex-direction: column;
height: 100%;
text-align: left;
.SideBar_logo {
height: 60px;
width: 100%;
}
.SideBar_content {
flex: 1;
}
}
.aside_mini {
display: flex;
text-align: left;
flex-direction: column;
height: 100%;
.SideBar_content {
flex: 1;
}
}
::v-deep .el-submenu .el-menu-item {
height: 50px;
line-height: 50px;
padding: 0 45px;
min-width: 100px;
}
</style>
<style lang="scss">
//保持激活状态的颜色
.el-submenu.is-active > .el-submenu__title {
.menuItem > i {
color: #409eef !important ;
}
color: #409eef !important ;
}
</style>
asideItem.Vue
左侧导航栏每一栏展示 ,与router 配合使用
<template>
<!-- 路由不隐藏就展示在侧边栏上 -->
<div class="AsideBarItem" v-if="!item.hidden">
<el-submenu :index="item.path" v-if="item.children && item.children.length > 1">
<!-- 如果存在子路由,并且超过一个 -->
<template #title>
<div class="menuItem">
<i :class="item.meta.icon"></i>
<span>{{ item.meta.title }}</span>
</div>
</template>
<!-- 递归 -->
<aside-item v-for="child in item.children" :key="child.path" :item="child"></aside-item>
</el-submenu>
<!-- 存在子路由,但只有一个就只展示子路由 -->
<el-menu-item :key="item.children[0].path" :index="item.children[0].path" v-else-if="item.children && item.children.length == 1">
<div class="menuItem">
<i :class="item.children[0].meta.icon"></i>
<span>{{ item.children[0].meta.title }}</span>
</div>
</el-menu-item>
<!-- 不存在子路由展示本身 -->
<el-menu-item :key="item.path" :index="item.path" v-else-if="item.meta.isShow === true">
<div class="menuItem">
<i :class="item.meta.icon"></i>
<span>{{ item.meta.title }}</span>
</div>
</el-menu-item>
</div>
</template>
<script>
export default {
props: {
item: {
type: Object,
required: true
}
},
data() {
return {}
},
method: {}
}
</script>
<style lang="scss" scoped>
.menuItem > i {
margin-right: 0px;
color: #fff;
}
::v-deep .el-submenu__title {
.el-submenu__icon-arrow {
font-size: 18px;
}
}
.menuItem {
span {
font-size: 16px;
}
}
</style>
router.js
import { createRouter, createWebHashHistory } from 'vue-router'
import { getToken } from '@/utils/auth'
import { ElMessage } from 'element-plus'
import Home from '@/views/Home.vue'
import Login from '@/views/Login/Login-swipe.vue'
import notFound from '@/views/404.vue'
import layout from '@/Layout/index.vue'
import AppMain from '@/Layout/components/AppMain.vue'
import addShop from '@/views/shop/addShop.vue'
import shopList1 from '@/views/shop/shopList.vue'
import shopList2 from '@/views/shop/shopListTest.vue'
const routerHistory = createWebHashHistory()
const router = createRouter({
history: routerHistory,
routes: [
{
path: '/login',
component: Login,
hidden: true
},
// 首页
{
path: '/',
component: layout,
children: [
{
path: '/home',
component: Home,
meta: { title: '首页', icon: 'el-icon-s-home', isShow: true }
}
]
},
// 商品
{
path: '/shop',
component: layout,
meta: {
title: '商品',
icon: 'el-icon-pie-chart'
},
children: [
{
path: '/shop/shopList',
component: AppMain,
meta: { title: '商品列表', icon: 'el-icon-menu', isShow: true },
children: [
{
path: '/shop/shopList/test1',
component: shopList1,
meta: { title: '商品列表测试', icon: 'el-icon-s-marketing', isShow: true }
},
{
path: '/shop/shopList/test2',
component: shopList2,
meta: { title: '商品列表测试2', icon: 'el-icon-s-custom', isShow: true }
}
]
},
{
path: '/shop/addshop',
component: addShop,
meta: { title: '添加商品', icon: 'el-icon-s-claim', isShow: true }
}
]
},
//notFound
{
path: '/',
component: layout,
hidden: true, //将404放进route中,但不展示
children: [
{
path: '/404',
component: notFound,
meta: { isShow: false }
}
]
},
{ path: '/:pathMatch(.*)', redirect: '/404', hidden: true }
]
})
router.beforeEach((to, from, next) => {
// 1.如果访问的是登录页面(无需权限),直接放行
if (to.path === '/login') return next()
// 2.如果访问的是有登录权限的页面,先要获取token
const tokenStr = getToken('user_token')
// 2.1如果token为空,强制跳转到登录页面;否则,直接放行
if (!tokenStr || tokenStr == 'null' || tokenStr == undefined) {
ElMessage({
message: '用户信息已失效!请重新登录',
type: 'error'
})
return next('/login')
}
next()
})
export default router