全网最全攻略之基于vue-admin-template模版改造左上布局侧边栏

先看示例图

image.png

我们逐一来分析:

1,既然是在顶部改造topbar,那么我们首先来找到对应的位置来写dom元素。(在src > layout > index.vue)

<div :class="classObj" class="app-wrapper">
    <!-- 改动的地方 start-->
    我是topBar我是topBar我是topBar我是topBar我是topBar我是topBar
    <!-- 改动的地方 end-->
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
    <sidebar class="sidebar-container" />
    <div class="main-container">
      <div :class="{'fixed-header':fixedHeader}">
        <navbar />
      </div>
      <app-main />
    </div>
</div>

2,写完可以发现,顶部出现了 我是topBar我是topBar我是topBar我是topBar我是topBar我是topBar 的字样,但是左侧没有被顶下来,是因为左侧使用了定位,我们来找到定位的地方,把top注释掉(src > styles > sidebar.scss 大概在第10行可以找到 .sidebar-container 的样式

image.png

.sidebar-container { 
        transition: width 0.28s;
        width: $sideBarWidth !important;
        background-color: $menuBg;
        height: 100%;
        position: fixed;
        font-size: 0px;
        //改动的地方start
        // top: 0;
        //改动的地方end
        bottom: 0;
        left: 0;
        z-index: 1001;
        overflow: hidden;
}

3,改完发现没效果,别急,我们来改一下height:90%试一下

.sidebar-container { 
        transition: width 0.28s;
        width: $sideBarWidth !important;
        background-color: $menuBg;
       //改动的地方start
        height: 90%;
      //改动的地方end
        position: fixed;
        font-size: 0px;
        //改动的地方start
        // top: 0;
        //改动的地方end
        bottom: 0;
        left: 0;
        z-index: 1001;
        overflow: hidden;
}

4,改完再看下,发现下来了,说明我们改的没毛病,就是这


image.png

5,这时候 你会怎么想怎么做?我想到的是左侧区域怎么也能下来呢?怎么和左侧的顶部持平呢?带着疑问,我们继续往下走。

image.png

通过上图可以看出,右侧主体区域的最外层的class类名是main-container,我们尝试着改下min-height试试看呢
ps:改过以后,发现没有变化,无用,回退
这时候,就要想,可不可以 加一个margin-top,让他下来呢,那就加上试试。

.main-container {
        min-height: 100%;
        transition: margin-left .28s;
        margin-left: $sideBarWidth;
        position: relative;
        // 改动的地方start
        margin-top: 50px;
        // 改动的地方end
}

下图可以看到,确实生效了。

image.png

但是 margin-top 的值给多少才合适呢?通过分析得知, margin-top 的值 应该是顶部你想要设置的高度的像素,比如我们顶部 想要设置高度56px,那么我们的 margin-top 的值就是56px,这时候最好设置一个变量方便我们去管理。在哪设置变量呢,路径(src > styles > variables.scss

$subMenuBg:#1f2d3d;
$subMenuHover:#001528;

$sideBarWidth: 210px;
// 改的的地方start
$topBarHeight: 56px;
// 改的的地方end

然后把 margin-top 的值 改成$topBarHeight:,就是这样了

image.png

6,问题随着而来,左侧slidbar 的高度肯定不能是固定的90%,那应该是多少呢?应该是100vh - 顶部的高度 ,所以我们还要设置一个变量来管理这个slidbar的高度,同样在(src > styles > variables.scss

$menuBg:#304156;
$menuHover:#263445;

$subMenuBg:#1f2d3d;
$subMenuHover:#001528;

$sideBarWidth: 210px;
// 改的的地方start
$topBarHeight: 56px;
$contentHeight: calc(100vh - 56px);
// 改的的地方end
.sidebar-container {
        transition: width 0.28s;
        width: $sideBarWidth !important;
        background-color: $menuBg;
         // 改的的地方start
        height: $contentHeight;
        // 改的的地方end
        position: fixed;
        font-size: 0px;
        //改动的地方start
        // top: 0;
        //改动的地方end
        bottom: 0;
        left: 0;
        z-index: 1001;
        overflow: hidden;
}

然后把顶部的文字注释掉 完事是这样的!


image.png

image.png

左边和右边都持平了。达到了我们想要的效果

下面开始改造顶部。

1,在《src > layout > components》下新建 Topbar.vue,效仿 Navbar的引入方式

// 改的地方start
import { Navbar, Sidebar, AppMain, Topbar } from './components'
// 改的地方end
 components: {
    Navbar,
    Sidebar,
    AppMain,
    // 改的地方start
    Topbar
    // 改的地方end
  },

/*src > layout > components > index.js */
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar'
export { default as AppMain } from './AppMain'
/** 改动的地方 start */
export { default as Topbar } from './Topbar'
/** 改动的地方 end */

完事在如图的地方引入

image.png

然后就是这样


image.png

2,在topbar页面里写入这些代码

<template>
  <div class="top-nav">
    <div class="log">后台管理系统</div>

    <el-menu :active-text-color="variables.menuActiveText" :default-active="activeMenu" mode="horizontal" @select="handleSelect">
      <div v-for="item in routes" :key="item.path" class="nav-item">
        <app-link :to="resolvePath(item)">
          <el-menu-item v-if="!item.hidden" :index="item.path">{{ item.meta ? item.meta.title : item.children[0].meta.title }}</el-menu-item>
        </app-link>
      </div>
    </el-menu>

    <div class="right-menu">
      <el-dropdown class="avatar-container" trigger="click">
        <div class="avatar-wrapper">
          <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
          <i class="el-icon-caret-bottom" />
        </div>
        <el-dropdown-menu slot="dropdown" class="user-dropdown">
          <router-link to="/">
            <el-dropdown-item>Home</el-dropdown-item>
          </router-link>
          <a href="https://github.com/PanJiaChen/vue-admin-template/" target="_blank">
            <el-dropdown-item>Github</el-dropdown-item>
          </a>
          <a href="https://panjiachen.github.io/vue-element-admin-site/#/" target="_blank">
            <el-dropdown-item>Docs</el-dropdown-item>
          </a>
          <el-dropdown-item divided @click.native="logout">
            <span style="display:block;">Log Out</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>

<script>
export default {

}
</script>

<style lang="scss" scoped>
</style>

页面肯定会报错,我们跟着报错一步一步来解决

image.png

首先来解决 "method "variables" is not defined的问题, variables 是一个计算属性,return出来 variables 文件 先把 variables scss文件 引过来

import variables from '@/styles/variables.scss'

然后在计算属性里 这样写

computed:{
  variables() {
      return variables
    },
}

在来解决 method "activeMenu" is not defined的问题
activeMenu也是一个计算属性

activeMenu() {
      const route = this.$route
      const { meta, path } = route
      // if set path, the sidebar will highlight the path you set
      if (meta.activeMenu) {
        return meta.activeMenu
      }
      // 如果是首页,首页高亮
      if (path === '/dashboard') {
        return '/'
      }
      // 如果不是首页,高亮一级菜单
      const activeMenu = '/' + path.split('/')[1]
      return activeMenu
},

在来解决 method "handleSelect" is not defined的问题,在methods里定义这个方法

 methods: {
    handleSelect() {}
  },

在解决 method "routes" is not defined,routes是我们要循环遍历的路由,在data里定义一个变量,把路由引过来赋值给这个变量

import { constantRoutes } from '@/router'

 data() {
    return {
      routes: constantRoutes
    }
  },

刷新页面在来解决 method "resolvePath" is not defined的问题,在methods里定义

 resolvePath(item) {
      // 如果是个完成的url直接返回
      if (isExternal(item.path)) {
        return item.path
      }
      // 如果是首页,就返回重定向路由
      if (item.path === '/') {
        const path = item.redirect
        return path
      }

      // 如果有子项,默认跳转第一个子项路由
      let path = ''
      /**
       * item 路由子项
       * parent 路由父项
       */
      const getDefaultPath = (item, parent) => {
        // 如果path是个外部链接(不建议),直接返回链接,存在个问题:如果是外部链接点击跳转后当前页内容还是上一个路由内容
        if (isExternal(item.path)) {
          path = item.path
          return
        }
        // 第一次需要父项路由拼接,所以只是第一个传parent
        if (parent) {
          path += (parent.path + '/' + item.path)
        } else {
          path += ('/' + item.path)
        }
        // 如果还有子项,继续递归
        if (item.children) {
          getDefaultPath(item.children[0])
        }
      }

      if (item.children) {
        getDefaultPath(item.children[0], item)
        return path
      }

      return item.path
    },

在引入 import { isExternal } from '@/utils/validate'
在把app-link组件引入过来

import AppLink from './Sidebar/Link'
components: {
    AppLink
  },

完事是这样


image.png

3,在来把css样式写一写,在styles文件夹下新建topbar.scss

.top-nav {
    // margin-left: $sideBarWidth;
    width: 100%;
    background-color: #304156;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1001;
    overflow: hidden;

    .log {
        padding: 0 20px;
        line-height: 56px;
        font-size: 24px;
        font-weight: bold;
        color: rgb(191, 203, 217);
        float: left;
        margin-right: 50px;
    }

    .el-menu {
        float: left;
        border: none !important;
        background-color: #304156;

        .nav-item {
            display: inline-block;

            .el-menu-item {
                color: rgb(191, 203, 217);

                &:hover {
                    background-color: $subMenuHover !important;
                }

                &:focus {
                    background-color: $subMenuHover !important;
                    // color: $subMenuActiveText !important;
                }
            }
        }
    }

    .right-menu {
        float: right;
        height: 100%;

        &:focus {
            outline: none;
        }

        .right-menu-item {
            display: inline-block;
            padding: 0 8px;
            height: 100%;
            font-size: 18px;
            color: #5a5e66;
            vertical-align: text-bottom;

            &.hover-effect {
                cursor: pointer;
                transition: background .3s;

                &:hover {
                    background: rgba(0, 0, 0, .025)
                }
            }
        }

        .avatar-container {
            margin-right: 30px;

            .avatar-wrapper {
                margin-top: 5px;
                position: relative;

                .user-avatar {
                    cursor: pointer;
                    width: 40px;
                    height: 40px;
                    border-radius: 10px;
                }

                .el-icon-caret-bottom {
                    cursor: pointer;
                    position: absolute;
                    right: -20px;
                    top: 25px;
                    font-size: 12px;
                }
            }
        }
    }
}

4,在index.js里引入topbar.scss

@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
// 修改的地方start
@import './topbar.scss';
// 修改的地方end

这样我们就把一级菜单渲染到了顶部


image.png

然后把头像的东西渲染到上面


image.png

把代码放这里
image.png

下面解决报错,把头像从vuex拿进来


image.png
import { mapGetters } from 'vuex'
...mapGetters(['avatar'])

最后把下面的右侧区域删掉就ok了。

image.png

5,上方的布局算是实现了,但是左侧我们想要渲染子级菜单,并不想渲染所有的,下面我们就来改造,(思路是这样 :当点击顶部的一级菜单时,拿到点击菜单的子级路由,保存起来,左侧去渲染这个保存起来的子级路由
6,找到我们topbar.vue 找到select事件定义的方法handleSelect
image.png

我们可以看到该事件会回调选中菜单的index 和path,开始写逻辑

 handleSelect(key, keyPath) {
      //得到子级路由
      const route = this.routes.find(item => item.path === key)
      console.log(route);
      // 把选中路由的子路由保存store
    },

7,下一步就是 把选中路由的子路由保存store,那么我们就要在store里定义方法供这里调用。

现在 《src > store > modules 》下新建一个 permission.js

const state = {
    currentRoutes: {}
}

const mutations = {
    SET_CURRENT_ROUTES: (state, routes) => {
        state.currentRoutes = routes
    }
}

export default {
    namespaced: true,
    state,
    mutations
}

在 《src > store > index.js 》下引入 permission.js

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
/** 改动的地方*/
import permission from './modules/permission'

Vue.use(Vuex)

const store = new Vuex.Store({
    modules: {
        app,
        settings,
        user,
        /** 改动的地方*/
        permission
    },
    getters
})

export default store

好,我们现在调用store的方法,把子级路由存起来,

handleSelect(key, keyPath) {
      // 把选中路由的子路由保存store
      const route = this.routes.find(item => item.path === key)
      this.$store.commit('permission/SET_CURRENT_ROUTES', route)
    },

通过调试工具可以看到,确实把子级路由存进去了。

下面把 src > layout > components > Sidebar > index.vue 里的routes计算属性改成下面这样

 routes() {
      // return this.$router.options.routes
      // 改动的地方
      return this.$store.state.permission.currentRoutes.children
    },

点击一级菜单看页面,已经正确渲染了。但是存在几个问题,现在一个一个来解决。

(1)刷新页面左侧子级路导航没了!
众所周知,vuex里面的数据刷新页面就会丢失,这就是 左侧子级路导航没了 的原因,下面开始解决
在topbar.vue里新建一个方法 initCurrentRoutes ,在页面加载的时候就调用,代码如下

// 通过当前路径找到二级菜单对应项,存到store,用来渲染左侧菜单
    initCurrentRoutes() {
      const { path } = this.$route
      let route = this.routes.find(
        item => item.path === '/' + path.split('/')[1]
      )
      // 如果找不到这个路由,说明是首页
      if (!route) {
        route = this.routes.find(item => item.path === '/')
      }
      this.$store.commit('permission/SET_CURRENT_ROUTES', route)
    },
 mounted() {
    this.initCurrentRoutes()
  },

在刷新页面就不会出现上面那种情况了。

(2)点击顶部一级菜单左侧没有高亮显示第一个子级路由
src > layout > components > Sidebar > SidebarItem.vue里 修改这里

image.png

/** 改动的地方 */
      const currentRoutes = this.$store.state.permission.currentRoutes
      if (currentRoutes && currentRoutes.path) {
        return path.resolve(currentRoutes.path, this.basePath, routePath)
  }

ok!

(3)点击左侧跳转404
完事你会惊讶的发现,这一个问题竟然也被修复了 哈哈...

至此,我们已经完成了 顶部一级导航,左侧子级导航的布局修改!如果你想精益求精,在左侧没有一个子级的时候,把侧边栏隐藏掉。
topbar.vue新建一个方法

// 设置侧边栏的显示和隐藏
    setSidebarHide(route) {
      if (!route.children || route.children.length === 1) {
        this.$store.dispatch('app/toggleSideBarHide', true)
      } else {
        this.$store.dispatch('app/toggleSideBarHide', false)
      }
    },

handleSelect 、 initCurrentRoutes方法内调用

 this.setSidebarHide(route)

store的 app.js里的actions里新建toggleSideBarHide方法,在mutations里新建SET_SIDEBAR_HIDE ,state 里 加一个hide

SET_SIDEBAR_HIDE: (state, status) => {
        state.sidebar.hide = status
    }
 /** 改动的地方*/
    toggleSideBarHide({ commit }, status) {
        commit('SET_SIDEBAR_HIDE', status)
    },
 /** 改动的地方*/
const state = {
    sidebar: {
        opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
        withoutAnimation: false,
        hide: false
    },
    device: 'desktop'
}

然后在这里加上 v-if 判断


image.png
 <sidebar v-if="!sidebar.hide" class="sidebar-container" />
 <div :class="{sidebarHide: sidebar.hide}" class="main-container">

完事可以看到,侧边栏消失了,但是左侧还占位,但是可以看到class类名加上了,我们把margin-left 设置成0 就可以了


image.png

ok 最后的效果


image.png

但是,主体区域好像高出了顶部的高度的距离,
看图理解


image.png

AppMain.vue里修改

.app-main {
  /** 改动的地方 */
  /*107 = navbar 50 + topbar 57  */
  min-height: calc(100vh - 107px);
  width: 100%;
  position: relative;
  overflow: hidden;
}
image.png

image.png

image.png

image.png

image.png

image.png

好了 根据截图的代码全局搜一下 就可以找到位置啦
大工告成。累死我了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • Sass 是一款强化 CSS 的辅助工具,它在 CSS 语法的基础上增加了变量 (variables)、嵌套 (n...
    孙小小的日常阅读 289评论 0 1
  • 1 注释规范 2 缩进/空格/换行规范 每个缩进使用4个空格,不允许使用 2 个空格 或 tab//正确.samp...
    OutRunM阅读 2,103评论 0 1
  • 1 注释规范 2 缩进/空格/换行规范 每个缩进使用4个空格,不允许使用 2 个空格 或 tab//正确.samp...
    壹枕星河阅读 570评论 0 0
  • 本文主要讲述页面布局样式方面涉及的知识点,更全面的对CSS相应的技术进行归类、整理、说明,没有特别详细的技术要点说...
    Joel_zh阅读 835评论 0 1
  • 1.1CSS 基础与选择器初识 | CSS 1. CSS 加载方式有几种? CSS样式加载一共有四种方式: 1、行...
    没糖_cristalle阅读 684评论 0 0