ELADMIN-02 页面布局

ELADMIN-02 页面布局

1.页面的总体布局

​ 首先最高一级的页面显示是由App.vue来引入的,通过一个<router-view />标签根据路由的不同来显示不同的页面。这些页面就是四个一级路由页面,包括

  • /login, 系统的登录页面
  • /401, 为授权的页面显示
  • /404,
  • /, 系统的主页面Layout,对应的组件是src/layout/index.vue,系统的各个界面都是嵌套路由的子路由

2.主页面的布局

​ 主页面的布局是由src/layout/index.vue这个组件来控制的,在这个组件中又将1页面分为三个部分,其中第一个div是控制在移动设备上的菜单栏显示的时候的一个黑色背景,也就是下面这行代码。

<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" /> <!-- 侧边栏 -->
<div :class="{hasTagsView:needTagsView}" class="main-container"> <!-- 主体 -->
  <div :class="{'fixed-header':fixedHeader}">
    <navbar /> <!-- 顶部导航栏 -->
    <tags-view v-if="needTagsView" /> <!-- 打开的标签 -->
  </div>
  <app-main />
  <right-panel v-if="showSettings"> <!-- 布局设置面板 -->
    <settings />
  </right-panel>
</div>

剩下两部分则控制的是页面的侧边栏sidebar和主体部分,其中主体部分又可以划分为三个部分

  • 头部
    • 顶部导航栏
    • 已打开的页签栏
  • 主体部分
  • 布局设置面板,只有在进行布局设置的时候才会显示在页面的最右侧

在这里插入图片描述

2.2 侧边栏的组成

​ 侧边栏由两部分组分,即顶部的logo和下面的菜单项组成,顶部的logo也可以看作是一个特殊的菜单项,点击它会路由挑战到首页,下面来详细介绍一下各个部分。

<!--侧边栏-->
<template>
  <div :class="{'has-logo':showLogo}">
    <!-- 侧边栏最上方的LOGO   -->
    <logo v-if="showLogo" :collapse="isCollapse" />
<!--   滚动条容器,用来包裹菜单项,当菜单超出可视区域自动出现滚动条 -->
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="variables.menuBg"
        :text-color="variables.menuText"
        :active-text-color="variables.menuActiveText"
        :collapse-transition="false"
        unique-opened
        mode="vertical"
      ><!-- :collapse控制是否水平折叠收起菜单(仅在 mode 为 vertical 时可用)
            :default-active表示当前激活的菜单
            mode选择垂直模式-->
        <sidebar-item v-for="route in sidebarRouters" :key="route.path" :item="route" :base-path="route.path" />
      </el-menu>
    </el-scrollbar>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/assets/styles/variables.scss'

export default {
  components: { SidebarItem, Logo },
  computed: {
    ...mapGetters([
      'sidebarRouters',
      'sidebar'
      /* 在当前 Vue 组件中创建两个计算属性 sidebarRouters
     和 sidebar,这两个属性的值分别对应 Vuex store 中的
      sidebarRouters 和 sidebar getter 的值。当 Vuex store
      中的 sidebarRouters 和 sidebar 的值发生变化时,
      这两个计算属性的值也会自动更新 */
    ]),
    activeMenu() { /* 这个函数用来返回一个路径,来让菜单组件显示正在激活的菜单项 */
      const route = this.$route
      const { meta, path } = route
      console.log('激活路由:' + meta.activeMenu + ' ' + path)
      // if set path, the sidebar will highlight the path you set
      if (meta.activeMenu) {
        return meta.activeMenu
      }
      return path
    },
    showLogo() {
      return this.$store.state.settings.sidebarLogo // 从store中获取sidebarLogo的值,该值用于判断是否显示logo
    },
    variables() {
      return variables
    },
    isCollapse() {
      return !this.sidebar.opened // 从store中获取sidebar的值,该值用于判断是否折叠菜单
    }
  }
}
</script>
2.2.1 Logo图标(src/layout/components/Sidebar/Logo.vue)

logo比较简单,就是分为侧边栏收缩和展开两种情况下,直接看代码即可

<template>
  <div class="sidebar-logo-container" :class="{'collapse':collapse}">
    <transition name="sidebarLogoFade">
      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
        <img v-if="logo" :src="logo" class="sidebar-logo"><!--在侧边栏收缩的情况下,存在logo则只显示logo,否则显示标题-->
        <h1 v-else class="sidebar-title">{{ title }} </h1>
      </router-link>
      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
        <img v-if="logo" :src="logo" class="sidebar-logo"><!--侧边栏展开的情况下,显示logo和标题-->
        <h1 class="sidebar-title">{{ title }}</h1>
      </router-link>
    </transition>
  </div>
</template>

<script>
import Logo from '@/assets/images/logo.png'
export default {
  name: 'SidebarLogo',
  props: {
    collapse: {
      type: Boolean,
      required: true
    }
  },
  data() {
    return {
      title: 'ELADMIN-后台管理',
      logo: Logo
    }
  }
}
</script>
2.2.2 SidebarItem组件(src/layout/components/Sidebar/SidebarItem.vue)

这个组件的作用就是渲染出侧边栏中的一个个菜单项,这个组件接收三个属性:

  • item:必需的,类型为 Object,表示要显示的菜单项的路由对象。
  • isNest:可选的,类型为 Boolean,默认值为 false,表示当前菜单项是否是嵌套的子菜单项。
  • basePath:可选的,类型为 String,默认值为空字符串,表示当前菜单项的基础路径。

在部分进行了判断,首先判断item是否需要隐藏,只有当不需要隐藏的时候才会显示这个菜单项,

  • 接着判断是否一个菜单项只有一个子菜单项需要显示,且这个子菜单项没有自己的子菜单项,或者父菜单项设置了 alwaysShow(v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow")这种强况下就是使用app-link组件来判断是否是一个外连接,如果是一个外连接就渲染为标签否则就渲染为组件,然后使用el-menu-item显示这个子菜单项。
  • 如果不满足上面的条件,就使用el-submenu显示出所有子菜单项,每个子菜单项再递归调用SiderbarItem进行渲染

此外这个组件还混入了 FixiOSBug 模块,用于修复在 iOS 设备上的一个 bug。这个 bug 的具体表现是,当在 iOS 设备上点击菜单时,会触发 mouseleave 事件。为了解决这个问题,FixiOSBug 在 mounted 钩子函数中调用了 fixBugIniOS 方法。

在渲染菜单项的使用使用了item组件,这个组件的作用就是显示侧边菜单项的图标和标题

2.3 顶部导航栏

顶部导航栏比较简单,这里就不详细说明

<template>
  <div class="navbar">
<!--   控制侧边导航栏的伸缩 -->
    <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<!--面包屑导航栏-->
    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
<!-- 头像左边的四个图标控制-->
    <div class="right-menu">
      <template v-if="device!=='mobile'">
        <search id="header-search" class="right-menu-item" />

        <el-tooltip content="项目文档" effect="dark" placement="bottom">
          <Doc class="right-menu-item hover-effect" />
        </el-tooltip>

        <el-tooltip content="全屏缩放" effect="dark" placement="bottom">
          <screenfull id="screenfull" class="right-menu-item hover-effect" />
        </el-tooltip>

        <el-tooltip content="布局设置" effect="dark" placement="bottom">
          <size-select id="size-select" class="right-menu-item hover-effect" />
        </el-tooltip>

      </template>
<!--头像已经点击头像时显示的下拉选择框-->
      <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
        <div class="avatar-wrapper">
          <img :src="user.avatarName ? baseApi + '/avatar/' + user.avatarName : Avatar" class="user-avatar">
          <i class="el-icon-caret-bottom" />
        </div>
        <el-dropdown-menu slot="dropdown">
          <span style="display:block;" @click="show = true">
            <el-dropdown-item>
              布局设置
            </el-dropdown-item>
          </span>
          <!--    进入个人中心需要进行路由挑战      -->
          <router-link to="/user/center">
            <el-dropdown-item>
              个人中心
            </el-dropdown-item>
          </router-link>
          <span style="display:block;" @click="open">
            <el-dropdown-item divided>
              退出登录
            </el-dropdown-item>
          </span>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
import Doc from '@/components/Doc'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import Avatar from '@/assets/images/avatar.png'

export default {
  components: {
    Breadcrumb,
    Hamburger,
    Screenfull,
    SizeSelect,
    Search,
    Doc
  },
  data() {
    return {
      Avatar: Avatar,
      dialogVisible: false
    }
  },
  computed: {
    ...mapGetters([
      'sidebar',
      'device',
      'user',
      'baseApi'
    ]),
    show: {
      get() {
        return this.$store.state.settings.showSettings
      },
      set(val) {
        this.$store.dispatch('settings/changeSetting', {
          key: 'showSettings',
          value: val
        })
      }
    }
  },
  methods: {
    // 控制侧边导航栏的伸缩
    toggleSideBar() {
      this.$store.dispatch('app/toggleSideBar')
    },
    // 退出登录
    open() {
      this.$confirm('确定注销并退出系统吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.logout()
      })
    },
    logout() {
      this.$store.dispatch('LogOut').then(() => {
        location.reload()
      })
    }
  }
}
</script>
2.4 TagsView(src/layout/components/TagsView/index.vue)

​ TagsView组件用于显示已访问视图的标签。它包含了一个 scroll-pane 组件,用于在有多个标签时提供滚动功能,这个组件相对复杂,不仅要用store来记录打开的标签和路由,还要对每一个标签的点击事件进行详细的区分。

  • 左键点击进行路由跳转
  • 中间点击关闭当前标签
  • 右键点击显示右键菜单

组件的结构比较简单就是一个scroll-pane组件包含全部的标签,除此之外还有一个标签右键显示的菜单

<template>
  <div id="tags-view-container" class="tags-view-container">
    <scroll-pane ref="scrollPane" class="tags-view-wrapper">
      <router-link
        v-for="tag in visitedViews"
        ref="tag"
        :key="tag.path"
        :class="isActive(tag)?'active':''"
        :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
        tag="span"
        class="tags-view-item"
        @click.middle.native="closeSelectedTag(tag)"
        @contextmenu.prevent.native="openMenu(tag,$event)"
      >
        <!--   对标签的点击进行了详细的分类,首先根据router-link的默认行为来实现左键进行路由跳转切换页面,
    @click.middle.native="closeSelectedTag(tag)"来实现鼠标中键点击关闭当前标签
        @contextmenu.prevent.native="openMenu(tag,$event)"实现鼠标右键点击显示右键菜单栏-->
        {{ tag.title }}
        <!-- 标签的关闭按钮-->
        <span v-if="!tag.meta.affix" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
      </router-link>
    </scroll-pane>
    <!-- 右键菜单栏-->
    <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
      <li @click="refreshSelectedTag(selectedTag)">刷新</li>
      <li v-if="!(selectedTag.meta&&selectedTag.meta.affix)" @click="closeSelectedTag(selectedTag)">关闭</li>
      <li @click="closeOthersTags">关闭其他</li>
      <li @click="closeAllTags(selectedTag)">关闭全部</li>
    </ul>
  </div>
</template>

补充知识:

  1. @contextmenu是监听contextmenu事件,也就是鼠标的右键点击事件,当右键点击的时候触发该事件,代码中使用@contextmenu.prevent.native 是在用户在元素上右键点击时,阻止默认的右键菜单显示,并且这个元素是原生的 HTML 元素,而不是 Vue 组件。
2.5 AppMain.vue组(src/layout/components/AppMain.vue)

​ 这个组件就是显示应用的主要内容的区域,包含了一个 router-view 组件和一个 el-backtop 组件。其中el-backtop 组件:用于快速回到页面顶部。它的 bottom 和 right 属性设置了它在页面中的位置。

​ router-view 组件:用于显示当前路由对应的视图。它的 key 属性绑定了当前路由的路径,这样当路由改变时,可以强制重新渲染视图。router-view 组件被包裹在一个 keep-alive 组件中,keep-alive 组件的 include 属性绑定了 cachedViews,这样可以缓存已访问的视图,提高性能。

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值