在webpack5 +Vue3 的基础上,配合element ui 搭建了基础后台页面 ,包含layout +导航栏+路由
登录/注册页面切换方式

记录layout: 修复增加
组件目录结构
图片.png

index.vue

  1. <template>
  2. <el-container>
  3. <!-- 侧边导航栏 -->
  4. <el-aside
  5. :class="{
  6. open: collapseStatus == false && currentWidth > 800,
  7. close: collapseStatus == true && currentWidth > 800,
  8. hidden: currentWidth <= 800
  9. }"
  10. >
  11. <SideBar :collapseStatus="collapseStatus" :CurrentWidth="currentWidth"></SideBar>
  12. </el-aside>
  13. <el-container>
  14. <!-- 顶层导航栏 -->
  15. <NavBar @collapseStatus="setcollapse" :CurrentWidth="currentWidth"></NavBar>
  16. <!-- 主要内容 -->
  17. <el-main>
  18. <AppMain></AppMain>
  19. </el-main>
  20. <el-footer>叮咚买菜!您菜到家了!</el-footer>
  21. </el-container>
  22. </el-container>
  23. </template>
  24. <script>
  25. import { NavBar, SideBar, AppMain } from './components'
  26. export default {
  27. data() {
  28. return {
  29. collapseStatus: false,
  30. currentWidth: 0
  31. }
  32. },
  33. components: {
  34. NavBar,
  35. SideBar,
  36. AppMain
  37. },
  38. mounted() {
  39. this.getWidth()
  40. },
  41. methods: {
  42. //侧边栏打开关闭状态s
  43. setcollapse(params) {
  44. // console.log('关闭打开状态', params)
  45. this.collapseStatus = params
  46. },
  47. getWidth() {
  48. this.currentWidth = document.body.clientWidth
  49. // console.log(this.currentWidth)
  50. window.onresize = () => {
  51. this.currentWidth = document.body.clientWidth
  52. console.log(this.currentWidth)
  53. }
  54. }
  55. }
  56. }
  57. </script>
  58. <style lang="scss" scoped>
  59. .el-container {
  60. height: 100%;
  61. width: 100%;
  62. border: 0px;
  63. // .el-aside {
  64. // display: flex;
  65. // flex-direction: column;
  66. // text-align: left;
  67. // }
  68. //屏幕宽度>800
  69. .open {
  70. width: 200px;
  71. }
  72. .close {
  73. width: 75px;
  74. ::v-deep .menuItem > span {
  75. display: none;
  76. }
  77. ::v-deep .el-submenu__icon-arrow.el-icon-arrow-down {
  78. display: none;
  79. }
  80. }
  81. //屏幕宽度<800
  82. .hidden {
  83. width: 0px;
  84. }
  85. }
  86. .el-footer {
  87. text-align: center;
  88. line-height: 60px;
  89. padding: 0px;
  90. overflow: hidden;
  91. }
  92. .el-main {
  93. background-color: white;
  94. margin: 10px 10px 0px;
  95. box-shadow: -1px 1px 5px #888888;
  96. padding: 16px;
  97. color: #333;
  98. }
  99. </style>
  100. <style lang="scss">
  101. // 修改导航竖排模式下的样式
  102. .el-popper.is-light.is-pure {
  103. border: none;
  104. box-shadow: -2px 2px 2px #333;
  105. }
  106. </style>

components

index.js

  1. export { default as NavBar } from './Navbar.vue'
  2. export { default as SideBar } from './SideBar.vue'
  3. export { default as AppMain } from './AppMain.vue'

AppMain.vue

主内容

  1. <template>
  2. <section class="app-main">
  3. <router-view :key="key" />
  4. </section>
  5. </template>
  6. <script>
  7. export default {
  8. name: 'AppMain',
  9. computed: {
  10. key() {
  11. return this.$route.path
  12. }
  13. }
  14. }
  15. </script>
  16. <style scoped>
  17. .app-main {
  18. /*60 = navbar */
  19. /* min-height: calc(100vh - 60px); */
  20. min-height: 100%;
  21. width: 100%;
  22. position: relative;
  23. overflow: hidden;
  24. }
  25. /* .fixed-header + .app-main {
  26. padding-top: 50px;
  27. } */
  28. </style>
  29. <style lang="scss">
  30. // fix css style bug in open el-dialog
  31. // .el-popup-parent--hidden {
  32. // .fixed-header {
  33. // padding-right: 15px;
  34. // }
  35. // }
  36. </style>

NavBar.vue

横排导航栏

  1. <template>
  2. <el-header>
  3. <el-menu class="NavBar" mode="vertical" text-color="#fff" active-text-color="#ffd04b">
  4. <!-- 打开收起 -->
  5. <!-- width>800 PC 展示 -->
  6. <div @click="iscollapse" class="collapse_content" v-if="currentWidth > 800">
  7. <i class="el-icon-s-fold" v-if="collapse == false"></i>
  8. <i class="el-icon-s-unfold" v-if="collapse"></i>
  9. </div>
  10. <!-- width<800 抽屉 展示 -->
  11. <div v-if="currentWidth <= 800" class="collapse_content" @click="isdrawer">
  12. <i class="el-icon-menu"></i>
  13. </div>
  14. <!-- 抽屉展示 -->
  15. <el-drawer :with-header="false" v-model="drawer" direction="ltr" destroy-on-close size="150px">
  16. <SideBar></SideBar>
  17. </el-drawer>
  18. <!-- 面包屑 -->
  19. <div :class="{ breadcrumb_container: currentWidth > 400, breadcrumb_hidden: currentWidth <= 400 }">
  20. <NavBread></NavBread>
  21. </div>
  22. <el-dropdown trigger="hover">
  23. <span class="el-dropdown-link">
  24. <el-tooltip class="item" effect="dark" content="admin菜到了" placement="left">
  25. <span>
  26. <img src="@/assets/avatar.png" />
  27. </span>
  28. </el-tooltip>
  29. <i class="el-icon-arrow-down"></i>
  30. </span>
  31. <template #dropdown>
  32. <el-dropdown-menu>
  33. <el-dropdown-item icon="el-icon-s-home" @click="backHome">
  34. <span>首页</span>
  35. </el-dropdown-item>
  36. <el-dropdown-item icon="el-icon-switch-button" @click="logOut">
  37. <span>登出</span>
  38. </el-dropdown-item>
  39. </el-dropdown-menu>
  40. </template>
  41. </el-dropdown>
  42. </el-menu>
  43. </el-header>
  44. </template>
  45. <script>
  46. import NavBread from './NavBread.vue'
  47. import SideBar from './SideBar.vue'
  48. import { getToken, removeToken } from '@/utils/auth'
  49. import { ElMessage } from 'element-plus'
  50. export default {
  51. data() {
  52. return {
  53. collapse: false, //false打开,true关闭
  54. drawer: false,
  55. currentWidth: 0,
  56. isChoosed: false
  57. }
  58. },
  59. props: {
  60. CurrentWidth: {
  61. type: Number
  62. }
  63. },
  64. components: {
  65. NavBread,
  66. SideBar
  67. },
  68. watch: {
  69. CurrentWidth() {
  70. this.currentWidth = this.CurrentWidth
  71. this.drawer = false //窗口变化直接关闭抽屉
  72. if (this.CurrentWidth > 800) {
  73. this.drawer = false //窗口样式不属于抽屉时关闭抽屉
  74. }
  75. // if (this.CurrentWidth > 800 && this.CurrentWidth <= 1000) {
  76. // this.collapse = true
  77. // this.$emit('collapseStatus', this.collapse)
  78. // } else if (this.CurrentWidth > 1000) {
  79. // this.collapse = false
  80. // this.$emit('collapseStatus', this.collapse)
  81. //震惊 !!!!这是个bug, 在1000以上启动项目时,能正常缩放///// 但小于1000下启动项目会造成路由出问题
  82. //未解决,暂时去除
  83. }
  84. },
  85. mounted() {},
  86. methods: {
  87. isLogin() {
  88. var token = getToken('user_token')
  89. if (token == undefined || token == '' || token == null) {
  90. this.$router.push('/login')
  91. }
  92. },
  93. logOut() {
  94. removeToken()
  95. this.isLogin()
  96. ElMessage.success('成功退出')
  97. },
  98. backHome() {
  99. //返回首页
  100. this.$router.push('/home')
  101. },
  102. iscollapse() {
  103. //打开关闭侧边栏
  104. this.collapse = !this.collapse
  105. this.$emit('collapseStatus', this.collapse)
  106. },
  107. //抽屉导航打开状态
  108. isdrawer() {
  109. this.drawer = !this.drawer
  110. // console.log(11111, this.drawer)
  111. }
  112. // hasChoose(params) {
  113. // this.isChoosed = params
  114. // console.log('选择', this.isChoosed)
  115. // if (this.isChoosed) {
  116. // this.drawer = false
  117. // this.isChoosed = false
  118. // }
  119. // }
  120. }
  121. }
  122. </script>
  123. <style lang="scss" scoped>
  124. //NavBar样式
  125. .el-header {
  126. text-align: center;
  127. line-height: 60px;
  128. padding: 0px;
  129. box-shadow: 3px 3px 5px #888888;
  130. background-color: white;
  131. .NavBar {
  132. border: 0px;
  133. }
  134. }
  135. //打开关闭侧边栏按钮样式
  136. .collapse_content {
  137. float: left;
  138. cursor: pointer;
  139. height: 100%;
  140. font-size: 25px;
  141. padding: 0 15px;
  142. }
  143. //面包屑样式
  144. .breadcrumb_container {
  145. float: left;
  146. .el-breadcrumb {
  147. font-size: 13px;
  148. line-height: 60px;
  149. }
  150. }
  151. .breadcrumb_hidden {
  152. display: none;
  153. }
  154. //抽屉弹窗
  155. ::v-deep .el-drawer.ltr {
  156. width: 150px;
  157. }
  158. //头像样式
  159. ::v-deep .el-dropdown {
  160. float: right;
  161. .el-dropdown-link {
  162. display: flex;
  163. align-items: center;
  164. padding-right: 20px;
  165. font-size: 18px;
  166. img {
  167. height: 40px;
  168. width: 40px;
  169. padding: 10px;
  170. border-radius: 25px;
  171. }
  172. }
  173. }
  174. ::v-deep .el-popper.is-light.is-pure {
  175. border: none;
  176. }
  177. ::v-deep .el-menu.el-menu--horizontal{
  178. border: none;
  179. }
  180. // ul.el-menu.el-menu--popup.el-menu--popup-bottom-start
  181. </style>

NavBreade.vue

面包屑

  1. <template>
  2. <el-breadcrumb separator="/">
  3. <el-breadcrumb-item :to="{ path: '/home' }">Ebuy</el-breadcrumb-item>
  4. <el-breadcrumb-item v-for="item in pathMap" :key="item.meta">
  5. {{ item.meta.title }}
  6. </el-breadcrumb-item>
  7. </el-breadcrumb>
  8. </template>
  9. <script>
  10. export default {
  11. data() {
  12. return {
  13. pathMap: ''
  14. }
  15. },
  16. mounted() {
  17. this.getPath()
  18. },
  19. methods: {
  20. getPath() {
  21. const route = this.$route
  22. const { matched } = route
  23. this.pathMap = matched
  24. //过滤掉不存在meta.titie的路由
  25. this.pathMap = this.pathMap.filter(function (item) {
  26. return item.meta.title != null
  27. })
  28. }
  29. },
  30. //监听路由变化
  31. watch: {
  32. $route: 'getPath'
  33. }
  34. }
  35. </script>
  36. <style scoped></style>

SiderBar

左侧导航栏

  1. <template>
  2. <div v-if="CurrentWidth > 800" class="aside">
  3. <img src="@/assets/ebuy-logo.png" class="SideBar_logo" />
  4. <el-menu
  5. ref="asideMenu"
  6. :default-active="activeMenu"
  7. :uniqueOpened="true"
  8. :mode="mode"
  9. :menu-trigger="menuOpenMethods"
  10. class="SideBar_content"
  11. background-color="#304156"
  12. text-color="#bfcbd9"
  13. active-text-color="#409EFF"
  14. router
  15. >
  16. <AsideBarItem v-for="route in routes" :key="route.path" :item="route"></AsideBarItem>
  17. </el-menu>
  18. </div>
  19. <!-- 小于800的样式要重写 -->
  20. <div v-else class="aside_mini">
  21. <!-- <img src="@/assets/ebuy-logo.png" class="SideBar_logo" v-if="CurrentWidth > 800" /> -->
  22. <el-menu
  23. :default-active="activeMenu"
  24. :uniqueOpened="true"
  25. mode="horizontal"
  26. :menu-trigger="menuOpenMethods"
  27. class="SideBar_content"
  28. background-color="#304156"
  29. text-color="#bfcbd9"
  30. active-text-color="#409EFF"
  31. router
  32. >
  33. <AsideBarItem v-for="route in routes" :key="route.path" :item="route"></AsideBarItem>
  34. </el-menu>
  35. </div>
  36. </template>
  37. <script>
  38. import AsideBarItem from './asideItem.vue'
  39. export default {
  40. components: {
  41. AsideBarItem
  42. },
  43. props: {
  44. collapseStatus: {
  45. type: Boolean
  46. },
  47. CurrentWidth: {
  48. type: Number
  49. }
  50. },
  51. watch: {
  52. collapseStatus() {
  53. if (this.collapseStatus == false) {
  54. this.mode = 'vertical'
  55. } else {
  56. this.mode = 'horizontal'
  57. this.closeMenu() //当路由模式变化时自动关闭当前激活的路由
  58. }
  59. }
  60. // CurrentWidth() {
  61. // if (this.CurrentWidth <= 1000) {
  62. // this.mode = 'horizontal'
  63. // } else if (this.CurrentWidth > 1000) {
  64. // this.mode = 'vertical'
  65. // }
  66. // }
  67. },
  68. data() {
  69. return {
  70. mode: 'vertical',
  71. menuOpenMethods: 'hover',
  72. drawer: false,
  73. closePath: ''
  74. }
  75. },
  76. computed: {
  77. routes() {
  78. //返回路由
  79. return this.$router.options.routes
  80. },
  81. activeMenu() {
  82. //保持左侧导航栏的激活状态
  83. const route = this.$route
  84. const { path } = route
  85. this.closePath = path.split('/')[1] //分割路由 用于关闭当前激活的路由导航
  86. return path
  87. }
  88. },
  89. methods: {
  90. closeMenu() {
  91. this.$refs['asideMenu'].close('/' + this.closePath) //关闭打开的menu
  92. }
  93. // handleSelect(hasChoose) {
  94. // this.$emit('hasChoose', true)
  95. // }
  96. }
  97. }
  98. </script>
  99. <style lang="scss" scoped>
  100. .el-menu {
  101. border: none;
  102. }
  103. .aside {
  104. display: flex;
  105. flex-direction: column;
  106. height: 100%;
  107. text-align: left;
  108. .SideBar_logo {
  109. height: 60px;
  110. width: 100%;
  111. }
  112. .SideBar_content {
  113. flex: 1;
  114. }
  115. }
  116. .aside_mini {
  117. display: flex;
  118. text-align: left;
  119. flex-direction: column;
  120. height: 100%;
  121. .SideBar_content {
  122. flex: 1;
  123. }
  124. }
  125. ::v-deep .el-submenu .el-menu-item {
  126. height: 50px;
  127. line-height: 50px;
  128. padding: 0 45px;
  129. min-width: 100px;
  130. }
  131. </style>
  132. <style lang="scss">
  133. //保持激活状态的颜色
  134. .el-submenu.is-active > .el-submenu__title {
  135. .menuItem > i {
  136. color: #409eef !important ;
  137. }
  138. color: #409eef !important ;
  139. }
  140. </style>

asideItem.Vue

左侧导航栏每一栏展示 ,与router 配合使用

  1. <template>
  2. <!-- 路由不隐藏就展示在侧边栏上 -->
  3. <div class="AsideBarItem" v-if="!item.hidden">
  4. <el-submenu :index="item.path" v-if="item.children && item.children.length > 1">
  5. <!-- 如果存在子路由,并且超过一个 -->
  6. <template #title>
  7. <div class="menuItem">
  8. <i :class="item.meta.icon"></i>
  9. <span>{{ item.meta.title }}</span>
  10. </div>
  11. </template>
  12. <!-- 递归 -->
  13. <aside-item v-for="child in item.children" :key="child.path" :item="child"></aside-item>
  14. </el-submenu>
  15. <!-- 存在子路由,但只有一个就只展示子路由 -->
  16. <el-menu-item :key="item.children[0].path" :index="item.children[0].path" v-else-if="item.children && item.children.length == 1">
  17. <div class="menuItem">
  18. <i :class="item.children[0].meta.icon"></i>
  19. <span>{{ item.children[0].meta.title }}</span>
  20. </div>
  21. </el-menu-item>
  22. <!-- 不存在子路由展示本身 -->
  23. <el-menu-item :key="item.path" :index="item.path" v-else-if="item.meta.isShow === true">
  24. <div class="menuItem">
  25. <i :class="item.meta.icon"></i>
  26. <span>{{ item.meta.title }}</span>
  27. </div>
  28. </el-menu-item>
  29. </div>
  30. </template>
  31. <script>
  32. export default {
  33. props: {
  34. item: {
  35. type: Object,
  36. required: true
  37. }
  38. },
  39. data() {
  40. return {}
  41. },
  42. method: {}
  43. }
  44. </script>
  45. <style lang="scss" scoped>
  46. .menuItem > i {
  47. margin-right: 0px;
  48. color: #fff;
  49. }
  50. ::v-deep .el-submenu__title {
  51. .el-submenu__icon-arrow {
  52. font-size: 18px;
  53. }
  54. }
  55. .menuItem {
  56. span {
  57. font-size: 16px;
  58. }
  59. }
  60. </style>

router.js

  1. import { createRouter, createWebHashHistory } from 'vue-router'
  2. import { getToken } from '@/utils/auth'
  3. import { ElMessage } from 'element-plus'
  4. import Home from '@/views/Home.vue'
  5. import Login from '@/views/Login/Login-swipe.vue'
  6. import notFound from '@/views/404.vue'
  7. import layout from '@/Layout/index.vue'
  8. import AppMain from '@/Layout/components/AppMain.vue'
  9. import addShop from '@/views/shop/addShop.vue'
  10. import shopList1 from '@/views/shop/shopList.vue'
  11. import shopList2 from '@/views/shop/shopListTest.vue'
  12. const routerHistory = createWebHashHistory()
  13. const router = createRouter({
  14. history: routerHistory,
  15. routes: [
  16. {
  17. path: '/login',
  18. component: Login,
  19. hidden: true
  20. },
  21. // 首页
  22. {
  23. path: '/',
  24. component: layout,
  25. children: [
  26. {
  27. path: '/home',
  28. component: Home,
  29. meta: { title: '首页', icon: 'el-icon-s-home', isShow: true }
  30. }
  31. ]
  32. },
  33. // 商品
  34. {
  35. path: '/shop',
  36. component: layout,
  37. meta: {
  38. title: '商品',
  39. icon: 'el-icon-pie-chart'
  40. },
  41. children: [
  42. {
  43. path: '/shop/shopList',
  44. component: AppMain,
  45. meta: { title: '商品列表', icon: 'el-icon-menu', isShow: true },
  46. children: [
  47. {
  48. path: '/shop/shopList/test1',
  49. component: shopList1,
  50. meta: { title: '商品列表测试', icon: 'el-icon-s-marketing', isShow: true }
  51. },
  52. {
  53. path: '/shop/shopList/test2',
  54. component: shopList2,
  55. meta: { title: '商品列表测试2', icon: 'el-icon-s-custom', isShow: true }
  56. }
  57. ]
  58. },
  59. {
  60. path: '/shop/addshop',
  61. component: addShop,
  62. meta: { title: '添加商品', icon: 'el-icon-s-claim', isShow: true }
  63. }
  64. ]
  65. },
  66. //notFound
  67. {
  68. path: '/',
  69. component: layout,
  70. hidden: true, //将404放进route中,但不展示
  71. children: [
  72. {
  73. path: '/404',
  74. component: notFound,
  75. meta: { isShow: false }
  76. }
  77. ]
  78. },
  79. { path: '/:pathMatch(.*)', redirect: '/404', hidden: true }
  80. ]
  81. })
  82. router.beforeEach((to, from, next) => {
  83. // 1.如果访问的是登录页面(无需权限),直接放行
  84. if (to.path === '/login') return next()
  85. // 2.如果访问的是有登录权限的页面,先要获取token
  86. const tokenStr = getToken('user_token')
  87. // 2.1如果token为空,强制跳转到登录页面;否则,直接放行
  88. if (!tokenStr || tokenStr == 'null' || tokenStr == undefined) {
  89. ElMessage({
  90. message: '用户信息已失效!请重新登录',
  91. type: 'error'
  92. })
  93. return next('/login')
  94. }
  95. next()
  96. })
  97. export default router