前端开发那些事儿

二、vue+ElementUI开发后台管理模板—vuex状态管理

2020-05-20  本文已影响0人  吃自己家大米

(获取本节完整代码 GitHub/chizijijiadami/vue-elementui-2

0、写在前面

关于布局:我们常见的后台管理页面结构一般有下图中显示的三种,如果把Tabs(导航的多标签显示)、Crumbs(面包屑)、Content(页面内容)和Footer(底部)当做中间区域视为一块整体,可以看到主要的区别就是Menu(菜单栏)位置的区别,待会我们就根据这些来设立vuex(vuex官网)的布局状态值,主要考虑以下两点:
(1)菜单栏的位置;
(2)设置是否需要该模块,包括Tabs、Crumbs、Foooter。

关于功能:在后台模板中我们会用到以下几种功能:
● Menu菜单自动化
● Tabs多标签
● Crumbs面包屑导航
● vuex 的 modules
● svg 图标雪碧图 svg-sprite-loader
● 批量导入资源
● 自定义全局组件—批量全局注册
● 自定义全局方法
● 获取接口数据、模拟数据——axios+Mockjs 【关联:axios源码学习到使用】
● 精确到按钮级别的权限控制

行动前准备:接下来我们用 GitHub/chizijijiadami/hand-to-hand 的代码来做准备些工作。可以参见下文章 vue实践1.1 企业官网——prerender-spa-plugin预渲染 ,第2段 css预处理器stylusstylus官网)、第5段vuex状态管理vuex官网 )。
a、运行后自动打开浏览器src>package.json

- "serve": "vue-cli-service serve --mode development",
+ "serve": "vue-cli-service serve --mode development --open",

b、安装

yarn add  vuex  element-ui
yarn add  stylus stylus-loader -D

c、src>App.vue

<template>
  <div id="app">
-    <header>
-      <nav><router-link to="/index">index</router-link><router-link to="/list">list</router-link></nav>
-    </header>
    <router-view></router-view>
  </div>
</template>
<script>
export default {
  name: 'App',
}
</script>

d、src>main.js

+ import Element from "element-ui";
+ import 'element-ui/lib/theme-chalk/index.css';
+ // 使用Element UI
+ Vue.use(Element, {
+   size: "small"
+ });

- import './assets/styles/reset.css'
- import './assets/styles/style.css'

e、src>pages>Index>index.vue

<template>
  <div>
    <p class="index-p">Index-index</p>
-    <img src="~@/assets/images/jerry.png" alt="" srcset="">
  </div>
</template>
<script>
export default {
    name:"IndexIndex",
-    created(){
-      console.log(process.env.VUE_APP_BASE_API,'输出VUE_APP_BASE_API');
      
-    }
}
</script>
- <style>
-  @import '~@/assets/styles/component.css'
- </style>

f、删除
src>assets>images>jerry.png
src>assets>styles>component.css
src>assets>styles>reset.css
src>assets>styles>style.css

准备工作到此结束。

1、新建布局文件

src>pages>Layout>components>Header.vue

<template>
  <div class="app-header">header</div>
</template>

src>pages>Layout>components>Mune.vue

<template>
  <div class="app-menu">
    <el-menu router default-active="/index/index" class="el-menu-vertical-demo">
        <el-menu-item index="/index/index">
          <i class="el-icon-location"></i>
          <span>首页</span>
        </el-menu-item>
      <el-submenu index="list">
        <template slot="title">
          <i class="el-icon-s-grid"></i>列表
        </template>
          <el-menu-item index="/list/detail">
            <i class="el-icon-goods"></i>详情
          </el-menu-item>
          <el-menu-item index="/list/feature">
            <i class="el-icon-document"></i>特性
          </el-menu-item>
      </el-submenu>
    </el-menu>
  </div>
</template>

src>pages>Layout>components>Tabs.vue

<template>
  <div clss="app-tabs">
    <el-tabs  type="card">
      <el-tab-pane
        label="首页"
      ></el-tab-pane>
    </el-tabs>
  </div>
</template>

src>pages>Layout>components>Crumbs.vue

<template>
  <div clss="app-crumbs">
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
    </el-breadcrumb>
 </div>
</template>

src>pages>Layout>components>Content.vue

<template>
   <div class="app-content"><router-view></router-view></div>
</template>

src>pages>Layout>components>Footer.vue

<template>
  <div class="app-footer">大米工厂</div>
</template>

src>pages>Layout>index.vue

<template>
  <div class="app-page">
    <Header />
    <Menu />
    <div class="app-container">
      <Tabs />
      <Crumbs />
      <Content />
      <Footer />
    </div>
</div>
</template>
<script>
import Header from "./components/Header";
import Tabs from "./components/Tabs";
import Menu from "./components/Menu";
import Crumbs from "./components/Crumbs";
import Content from "./components/Content";
import Footer from "./components/Footer";
export default {
  components: {
    Header,
    Menu,
    Tabs,
    Crumbs,
    Content,
    Footer
  }
};
</script>
2、修改src>router>index.js

Vue Router 滚动行为

export default new Router({
+    scrollBehavior() {    //页面内容多过一屏时,需设置滚动位置
+        return { x: 0, y: 0 }
+    },
    routes: [
      {
            path: '',
-           redirect: '/index',
+           redirect: '/index/index'
        },
        {
              path: '/index',
-             component: _import('Index/index')
+             component: _import('Layout/index'),
+             redirect: '/index/index',
+             children:[
+                 {
+                     path: 'index',
+                     component: _import('Index/index'), 
+                 }
+             ]
         },
          {
             path: '/list',
-            component: _import('List/index'),
+            component: _import('Layout/index'),
             children: [
                  {
                      path: 'detail',
                      component: _import('List/Detail/index')
                  },
                  {
                    path: 'feature',
                     component: _import('List/Feature/index')
                }
             ]
        },
       {
            path: '/404',
            component: _import('ErrorPages/404')
        },
        {
            path: '*',
            redirect: '/404'
        }
    ]
})

访问检查下个模块都加载出来了,接下来我们先按照布局1来写样式.

3、布局1样式

(1)样式文件中
src>assets>styles>reset.styl

//  主要是一些原生样式的覆盖,直接看源代码

src>assets>styles>base.styl

$body-bgcolor=#f5f6f7
html
   background-color $body-bgcolor

src>assets>styles>layout.styl

$bg-color = #eee
$header_height = 60px
$menu_width = 200px
.app-header
  background-color white
  position absolute
  top 0
  left 0
  height $header_height
  width 100%
.app-menu
  background-color white
  position absolute
  top $header_height
  left 0
  width $menu_width
  height calc(100% - 60px) // $header_height
.app-container
  height 100vh
  padding $header_height  0 0 $menu_width
.app-tabs
  background-color white
  height 40px
.app-crumbs
  height 30px
  line-height 30px
.app-content
  background-color white
  padding 0 10px
  min-height calc(100% - 40px - 30px - 40px) // app-tabs_height、app-crumbs_height、app-footer_height
.app-footer
  background-color white
  height 40px
  line-height 40px

src>assets>styles>index.styl ,统一导入样式

@require './reset.styl'
@require './base.styl'
@require './layout.styl'

src>main.js中引用

//样式
import "@/assets/styles/index.styl";
浏览器中查看一下,有了。

尽管stylus中有了变量,但是无法跟 calc 友好结合,如果该变量涉及 calc 计算多的话在改版的时候容易漏掉,所以我们把这些搬运到布局文件中进行。
(2)在布局文件中计算
src>pages>Layout>index.vue

<template>
  <div class="app-page">
+    <Header :style="{height:header.height}" />
+    <Menu :style="{top:header.height,width:menu.width,height:menuHeight()}" />
+    <div class="app-container" :style="{padding:containerPadding()}">
+      <Tabs :style="{height:tabs.height}" />
+      <Crumbs :style="{height:crumbs.height,padding:'0 '+content.margin}"   :crumbs="crumbs" :content="content"/>
+      <Content :style="{'min-height':contentMinHeight(),margin:contentMargin()}"/>
+      <Footer :style="{height:footer.height,'line-height':footer.height}" />
    </div>
  </div>
</template>
<script>
import Header from "./components/Header";
import Tabs from "./components/Tabs";
import Menu from "./components/Menu";
import Crumbs from "./components/Crumbs";
import Content from "./components/Content";
import Footer from "./components/Footer";
export default {
  components: {
    Header,
    Menu,
    Tabs,
    Crumbs,
    Content,
    Footer
  },
+  data() {
+    return {
+      header: {
+        height: "60px"
+      },
+      menu: {
+        width: "200px"
+      },
+      content: {
+        margin: "10px"
+      },
+      tabs: {
+        height: "40px"
+      },
+      crumbs: {
+        height: "30px"
+      },
+      footer: {
+        height: "40px"
+      }
+    };
+  },
+  methods: {
+    menuHeight() {
+      return "calc(100% - " + this.header.height + ")";
+    },
+    containerPadding() {
+      return (
+        this.header.height +
+        " 0 0 " +
+        this.menu.width
+      );
+    },
+    contentMinHeight() {
+      return (
+        "calc(100% - " +
+        this.tabs.height +
+        " - " +
+        this.crumbs.height +
+        " - " +
+        this.footer.height +
+        ")"
+      );
+    }
+  }
};
</script>

src>assets>style>layout.styl

$bg-color = #eee
- $header_height = 60px
- $menu_width = 200px
.app-header
  background-color white
  position absolute
  top 0
  left 0
-   height $header_height
  width 100%
.app-menu
    background-color white
    position absolute
-   top $header_height
    left 0
-   width $menu_width
-   height calc(100% - 60px) // $header_height
.app-container
  height 100vh
+  .app-tabs,.app-crumbs,.app-content,.app-footer
+    background-color white   //这后面的都删掉
-   padding $header_height 0 0 $menu_width
- .app-tabs
-   background-color white
-   height 40px
- .app-crumbs
-   height 30px
-   line-height 30px
-.app-content
-   background-color white
-   padding 0 10px
-   min-height calc(100% - 40px - 30px - 40px) // app-tabs_height、app-crumbs_height、app-footer_height
-.app-footer
-   background-color white
-   height 40px
-   line-height 40px

调整结束,看一下浏览器是没有变化的。
另外左侧菜单栏,往往是可以收缩的,下面我们从这里入手结合vuex进行布局调整。

4、结合vuex实现多种布局样式

(1)菜单栏收缩
新建 src>data>store>index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store=new Vuex.Store({
    state: { 
        menu: {
            isCollapse: false  //默认菜单展开
        }
    },
    mutations: {
        SET_MENU_ISCOLLAPSE: state => {
            state.menu.isCollapse = !state.menu.isCollapse
        }
    },
    actions: {
        setMenuIsCollapse({ commit }) {
            commit('SET_MENU_ISCOLLAPSE')
        }
    }
  })
export default store

引入 src>main.js

+ import store from './data/store'

new Vue({
   router,
+  store,
   render: h => h(App),
}).$mount('#app')

菜单页面中取值 src>pages>Layout>components>Menu.vue

-  <el-menu router default-active="/index/index" class="el-menu-vertical-demo">
+  <el-menu :collapse="isCollapse" :collapse-transition="false"  router
                                                       default-active="/index/index" class="el-menu-vertical-demo">

+ <script>
+ export default {
+   computed: {
+     isCollapse() {
+       return this.$store.state.menu.isCollapse;
+     }
+   }
+ };
+ </script>

添加控制事件 src>pages>Layout>components>Hearder.vue

<template>
-  <div class="app-header"></div>
+  <div class="app-header">
+    <el-button type="primary" plain  :icon="isCollapse?'el-icon-s-fold':'el-icon-s-unfold'"></el-button>
+  </div>
</template>
+ <script>
+ export default {
+   computed: {
+     isCollapse() {
+       return this.$store.state.menu.isCollapse;
+     }
+   },
+   methods:{
+     setMenuIsCollapse(){
+       this.$store.dispatch('setMenuIsCollapse');
+     }
+   }
+ };
+ </script>

点击看看效果,可以收缩,但是外边 div 没有同时进行收缩,布局文件中再改点东西。
src>pages>Layout>components>Hearder.vue

-   <Menu :style="{top:header.height,widthmenu.width,height:menuHeight()}" />
+   <Menu :style="{top:header.height,width:isCollapse?menu.widthCollapse:menu.width,height:menuHeight()}" />

+  computed: {  //script中添加
+    isCollapse() {
+      return this.$store.state.menu.isCollapse;
+    }
+  },

这么加只是收缩了菜单栏外层,还有 app-container 的 padding 也要调整

    containerPadding() {
      return (
         this.header.height +
         " 0 0 " +
-         this.menu.width
+        (this.isCollapse ? this.menu.widthCollapse : this.menu.width)
      );
    },

现在点点看,就正常了。

(2)菜单栏位置变化
根据我们前面的布局分类给菜单栏位置设定状态值。
src>data>store>index.js

        menu: {
            isCollapse: false,
 +           location:"VH"   //V、VH、H三个值,V表示在左侧,VH表示横跨头部,H表示在头部,默认 V
        }

根据这个设定,我们目前的布局是 V,现在我们做 VH 。
src>pages>Layout>index.vue——在布局页获取

  computed: {
    isCollapse() {
      return this.$store.state.menu.isCollapse;
    },
+    menuLocation(){
+        return this.$store.state.menu.location;
+    }
  },

观察布局图,从V到VH只是菜单栏跟头部的决定定位值不同。那么我们要做的,修改Menu的 top、height 属性加,Header的 left、width属性。
src>pages>Layout>index.vue

- <Header :style="{height:header.height}" />
- <Menu :style="{top:header.height,width:isCollapse?menu.widthCollapse:menu.width,height:menuHeight()}" />
+ <Header :style="{height:header.height,left:headerLeft(),width:headerWidth()}" />
+ <Menu :style="{top:menuTop(),width:this.menuWidth(),height:menuHeight()}" />

 methods:{
+  headerLeft(){  //涉及三目运算多时,最好加上括号方便读码——这里别忘记菜单栏的收缩
+     return this.menuLocation === "V" ? "0" : ( this.isCollapse ? this.menu.widthCollapse : this.menu.width );
+  },
+  headerWidth(){
+      return this.menuLocation==="V" ? "100%" : "calc(100% - "+this.menuWidth()+")"
+  },
+  menuTop() {
+     return this.menuLocation === "V" ? this.header.height : "0";
+  },
   menuHeight() {
-     return "calc(100% - " + this.header.height + ")";
+     return  this.menuLocation==="V" ? "calc(100% - " + this.header.height + ")" : "100vh";
   },
+  menuWidth(){    //这个值用到的比较多我们单独写成一个方式使用
+     return this.isCollapse?this.menu.widthCollapse:this.menu.width;
+  },
    containerPadding() {
      return (
        this.header.height +
        " 0 0 " +
-      (this.isCollapse ? this.menu.widthCollapse : this.menu.width)
+      this.menuWidth()
      );
    },
 ......
 }

src>assets>style>layout.styl

.app-header
   background-color white
   position absolute
   top 0
-  left 0
-  width 100%
.app-menu
   background-color white
   position absolute
-  left 0
.app-container
   height 100vh
   .app-tabs,.app-content,.app-footer
     background-color white

修改完成,浏览器里点点看。
接下来,我们写 H 布局。
src>data>store>index.js

        menu: {
            isCollapse: false,
-           location:"VH"   //V、VH、H三个值,V表示在左侧,VH表示横跨头部,H表示在头部
+           location:"H"   //V、VH、H三个值,V表示在左侧,VH表示横跨头部,H表示在头部
        }

观察一下,相对其他两个布局,布局文件Menu这块是直接没了的,还要修改app-container的 padding,Herder的 left、width 属性。
src>pages>Layout>index.vue

-  <Menu :style="{top:menuTop(),width:this.menuWidth(),height:menuHeight()}" />
+  <Menu v-if="menuLocation!=='H'" :style="{top:menuTop(),width:this.menuWidth(),height:menuHeight()}" />
//methods中
    headerLeft(){
-     return this.menuLocation==="V" ? "0": this.menuWidth();
+     return this.menuLocation==="V" || this.menuLocation==="H" ? "0": this.menuWidth();
    },
    headerWidth(){
-        return this.menuLocation==="V" ? "100%" : "calc(100% - "+this.menuWidth()+")"
+        return this.menuLocation==="V" || this.menuLocation==="H" ? "100%" : "calc(100% - "+this.menuWidth()+")"
    },

    menuWidth() {
-     return this.isCollapse ? this.menu.widthCollapse : this.menu.width;
+     return this.menuLocation === "H"?"0px":this.isCollapse ? this.menu.widthCollapse : this.menu.width;
    },

到这里还剩下菜单栏没有在Header中显示,我们接着改Header。
src>pages>Layout>componets>Header.vue

<template>
  <div class="app-header">
-    <el-button type="primary"  plain  @click='setMenuIsCollapse' :icon="isCollapse?'el-icon-s-fold':'el-icon-s-unfold'"></el-button>
+    <Menu  v-if="menuLocation==='H'"/>
+    <el-button v-if="menuLocation!=='H'" type="primary"  plain  @click='setMenuIsCollapse' :icon="isCollapse?'el-icon-s-fold':'el-icon-s-unfold'"></el-button>
  </div>
</template>
<script>
+ import Menu from "./Menu";
+ export default {
+   components:{
+     Menu
+   },
  computed: {
    isCollapse() {
      return this.$store.state.menu.isCollapse;
    },
+     menuLocation() {
+       return this.$store.state.menu.location;
+     }
  },
  methods:{
    setMenuIsCollapse(){
      this.$store.dispatch('setMenuIsCollapse');
    }
  }
};
</script>

src>pages>Layout>componets>Menu.vue

    <el-menu
      :collapse="isCollapse"
      :collapse-transition="false"
      router
      default-active="/index/index"
      class="el-menu-vertical-demo"
+      :mode="menuLocation==='H'?'horizontal':'vertical'"
    >

<script>
export default {
  computed: {
     isCollapse() {
       return this.$store.state.menu.isCollapse;
     },
+    menuLocation() {
+       return this.$store.state.menu.location;
+    }
  }
};
</script>

这就全部改好了,随意切换下布局状态值,完美 :)

(3)Tabs、Crumbs、Footer状态
src>data>store>index.js

    state: { 
        menu: {
            isCollapse: false,
            location:"VH"   //V、VH、H三个值,V表示在左侧,VH表示横跨头部,H表示在头部
        },
+        tabs:{
+             isShow:true
+         },
+         crumbs:{
+             isShow:true
+         },
+         footer:{
+             isShow:true
+         }
    },

src>pages>Layout>index.vue

    <div class="app-container" :style="{padding:containerPadding()}">
-     <Tabs :style="{height:tabs.height}" />
-     <Crumbs v-if="isShowCrumbs" :style="{height:crumbs.height,padding:'0 '+content.margin}" :crumbs="crumbs" :content="content" />
+     <Tabs v-if="isShowTabs" :style="{height:tabs.height}" />
+     <Crumbs v-if="isShowCrumbs" :style="{height:crumbs.height,padding:'0 '+content.margin}" :crumbs="crumbs" :content="content" />
      <Content :style="{'min-height':contentMinHeight(),margin:contentMargin()}" />
-     <Footer  :style="{height:footer.height,'line-height':footer.height}" />
+     <Footer v-if="isShowFooter" :style="{height:footer.height,'line-height':footer.height}" />
    </div>

  computed: {
    isCollapse() {
      return this.$store.state.menu.isCollapse;
    },
    menuLocation() {
      return this.$store.state.menu.location;
    },
+    isShowTabs(){
+      return this.$store.state.tabs.isShow;
+    },
+    isShowCrumbs(){
+      return this.$store.state.crumbs.isShow;
+    },
+    isShowFooter(){
+      return this.$store.state.footer.isShow;
+    }
  },

现在可以修改相应的状态值,看下页面这些组件的显示情况,内容不够一屏时需要页面撑开,就需要调整Content的 min-height 属性,先穷举组合情况:
● Content
● Tabs + Content
● Crumbs + Content
● Tabs + Crumbs + Content
● Tabs + Crumbs + Content + Footer
● Crumbs + Content + Footer
● Tabs + Content + Footer
● Content + Footer
src>pages>Layout>index.vue

    contentMargin() {
-     return "0 " + this.content.margin;
+     let marginTop = this.isShowCrumbs ? "0" : this.content.margin;
+     let marginBottom = this.isShowFooter ? "0" : this.content.margin;
+     return marginTop + " " + this.content.margin +" "+ marginBottom;
    },
    contentMinHeight() {
-      return (
-        "calc(100% - " +
-        this.tabs.height +
-        " - " +
-        this.crumbs.height +
-        " - " +
-        this.footer.height +
        ")"
-      );
+      let tabsHeight = this.isShowTabs ?  this.tabs.height: "0px" ;
+      let crumbsHeight = this.isShowCrumbs ? this.crumbs.height : this.content.margin;  //只有content时留有底部空隙
+      let footerHeight = this.isShowFooter ? this.footer.height : this.content.margin;
+      return ("calc(100% - " +tabsHeight + " - " +crumbsHeight +" - " +footerHeight +")");
    }

现在任意调整状态值看看。

(4)Tabs位置调整
随意在一个页面添加超过一屏的内容,滚动看看,发现忘记将 Tabs 浮动置顶了,现在修改添加一下。
src>assets>style>layout.styl

  .app-menu
    background-color white
    position fixed
+ .app-tabs
+   position fixed

src>pages>Layout>index.vue

-  <Tabs v-if="isShowTabs" :style="{height:tabs.height}" />
+  <Tabs v-if="isShowTabs" :style="{height:tabs.height,top:header.height,left:tabsLeft(),width:tabsWidth()}" />

    containerPadding() {
+    let tabsHeight=this.isShowTabs ? this.tabs.height : "0px"
+    let paddingTop=parseInt(this.header.height)+parseInt(tabsHeight)+"px"
      return (
-       this.header.height +
+       paddingTop +
        " 0 0 " +
        this.menuWidth()
      );
     },
+    tabsLeft(){
+        return this.menuWidth();
+    },
+    tabsWidth(){
+        return "calc(100% - "+this.menuWidth()+" )"
+    },

    contentMinHeight() {
-      let tabsHeight = this.isShowTabs ?  this.tabs.height: "0px" ;   //tabs的高已经在containerPadding中有计算,此处去掉
       let crumbsHeight = this.isShowCrumbs ? this.crumbs.height : this.content.margin;
       let footerHeight = this.isShowFooter ? this.footer.height : this.content.margin;
-      return ("calc(100% - " +tabsHeight + " - " +crumbsHeight +" - " +footerHeight +")");
+      return ("calc(100% - " +crumbsHeight +" - " +footerHeight +")");
    }

到此我们后台模板最基本的布局就写成了,接下来我们需要进行模板块功能的实现:Menu菜单自动化(现在Menu中的导航都是我们手写的,应当根据路由文件router自动生成相应数据为佳)、Tabs多标签、Crumbs路径等将在接下来的文章中实现。

这里拿到的代码中一些文件的引入路径跟文章里的是有些区别的,小伙伴么可以想到是为什么么,答案下章说哦。

感谢阅读,喜欢的话点个赞吧:)
更多内容请关注后续文章。。。

一、vue入门基础开发—手把手教你用vue开发
三、vue+ElementUI开发后台管理模板—功能、资源、全局组件
四、vue+ElementUI开发后台管理模板—方法指令、接口数据
五、vue+ElementUI开发后台管理模板—精确到按钮的权限控制

vue3 + vite + ElementPlus开发后台管理模板

vue实践1.1 企业官网——prerender-spa-plugin预渲染

上一篇下一篇

猜你喜欢

热点阅读