三、vue+ElementUI开发后台管理模板—功能、资源、全局组件

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

我们接着 二、vue+ElementUI开发后台管理模板—布局 这篇写功能,在此之前先说明下它末尾的文件引入路径问题,是在 vue.config.js 里文件路径加了别名,具体页面中小伙伴们可以自己试试看。

    chainWebpack:config=>{
        config.resolve.alias
         .set("@",resolve('src'))
+        .set("assets",resolve('src/assets'))
+        .set("common",resolve('src/common'))
+        .set("data",resolve('src/data'))
+        .set("store",resolve('src/data/store'))
+        .set("router",resolve('src/router'))
    }
0、写在前面

这篇文章主要内容包括:
● Menu菜单自动化
● svg 图标雪碧图 svg-sprite-loader
● 批量导入资源
● 自定义全局组件—批量全局注册
● vuex 的 modules
● Crumbs面包屑导航
● Tabs多标签
● 基于 elementUI Pagination 分页 分页自定义全局组件

1、Menu菜单自动化

(1)设置相关状态值
修改src>data>store>index.js,添加菜单列表状态及事件。

+        system:{
+            title:"大米工厂"
+        },
        menu: {
            isCollapse: false,
            location:"V",   //V、VH、H三个值,V表示在左侧,VH表示横跨头部,H表示在头部
 +          list:[]
        },

    mutations: {
        SET_MENU_ISCOLLAPSE: state => {
            state.menu.isCollapse = !state.menu.isCollapse
        },
+        SETMENU_LIST: (state,menuList) => {
+            state.menu.list=menuList
+        }
    },
    actions: {
        setMenuIsCollapse({ commit }) {
            commit('SET_MENU_ISCOLLAPSE')
        },
+        setMenuList({ commit },menuList) {
+            commit('SETMENU_LIST', menuList)
+        }
    }

(2)提取路由变量
修改src>router>index.js,要拿到导航路径就得路径值设成变量,这里就是把原本 routes 属性的值提取出来作为可外部引用 的变量,修改符号太繁琐就不加了。另外 name 后面 Crumbs 作为每个路径的唯一标识符或者权限问题都会用到的,先加上。

export const pagesRouterList=[
    {
        path: '',
        redirect: '/index/index'
    },
    {
        path: '/index',
        component: _import('Layout/index'),
        redirect: '/index/index',
        name:"Index",
        meta:{
            title:"首页",           //页面名称
            icon:"el-icon-location",   // icon名
            isShow: true         //是否在菜单栏中显示
        },
        children:[
            {
                path: 'index',
                component: _import('Index/index'), 
                name:"IndexIndex",
                meta:{
                    title:"首页",
                    icon:"el-icon-location",
                    isShow: true
                }
            }
        ]
    },
    {
        path: '/list',
        component: _import('Layout/index'),
        name:"List",
        meta:{
            title:"列表",
            icon:"el-icon-s-grid",
            isShow: true
        },
        children:[
            {
                path: 'detail',
                component: _import('List/Detail/index'), 
                name:"ListDetai",
                meta:{
                    title:"详情",
                    icon:"el-icon-goods",
                    isShow: true
                }
            },
            {
                path: 'feature',
                component: _import('List/Feature/index'), 
                name:"ListFeature",
                meta:{
                    title:"特性",
                    icon:"el-icon-document",
                    isShow: true
                }
            }
        ]
    },
    {
        path: '/404',
        component: _import('ErrorPages/404')
    },
    {
        path: '*',
        redirect: '/404'
    }
]
export default new Router({
    scrollBehavior() {
        return { x: 0, y: 0 }
    },
    routes: pagesRouterList
})

(3)全局导航守卫中过滤路由
新建src>common>routerFilter>filter.js,先写路由过滤函数。

import {  MessageBox } from 'element-ui'
export function filterRouter(pagesRouterList) {
    let mennuList = pagesRouterList.filter(ele => ele.meta && ele.meta.isShow)
    try {
        if (mennuList.length <= 0) throw "没有可用菜单";
        filterPage(mennuList)
        return mennuList;
    } catch (err) {
        MessageBox({
            message: err,
            showCancelButton: false,
            confirmButtonText: '确定',
            type: 'error'
        })
    }
}
function filterPage(mennuList, pathFull, joinSign) {
    let pathFullCurrent = pathFull || ""
    let joinSignCurrent = joinSign || ""
    for (let i = 0; i < mennuList.length; i++) {
        const ele = mennuList[i];
        ele.pathFull = pathFullCurrent + joinSignCurrent + ele.path
        ele.showChildren=[]
        if (ele.children && ele.meta.isShow) {
            ele.showChildren=ele.children.filter(ele2=>ele2.meta.isShow)   //过滤出是否有要显示的子菜单
            filterPage(ele.children, ele.pathFull, "/")
        }
    }
}

新建 src>common>utils>getPageTitle.js,设置标签栏标题。

import store from '../../data/store'
export default function getPageTitle(pageTitle) {
    if (pageTitle) {
      return `${pageTitle} - ${store.state.system.title}`
    }
    return `${store.state.system.title}`
}

新建src>common>routerFilter>index.js,在全局导航守卫中判断添加 menu.list 的值。

import router from '../../router'
import store from '../../data/store'
import {pagesRouterList } from '../../router'
import {filterRouter} from './filter'
import getPageTitle from '../common/utils/getPageTitle'
router.beforeEach(async (to, from, next) => {
    document.title = getPageTitle(to.meta.title)  //设置标题栏名称
    if(store.state.menu.list.length===0){   //没值的时候添加一下
        store.dispatch("setMenuList",filterRouter(pagesRouterList)) 
        next({ ...to, replace: true })
    }else{
        next()
    }
})

src>main.js 中引入

+  //路由过滤
+ import './common/routerFilter'

到这里可以访问看看,标签栏已经有变化了,接下来就是Menu页面中的使用。

(4)使用:在Mune中渲染导航栏
新建 src>pages>Layout>components>MenuItem.vue

<template>
  <div class="app-menu-item">
    <template v-for="item in menuList">
      <el-menu-item v-if="item.showChildren.length<=0" :index="item.pathFull" :key="item.name">
        <i :class="item.meta.icon"></i>
        <span>{{item.meta.title}}</span>
      </el-menu-item>
      <el-submenu v-else :index="item.pathFull" :key="item.name">
        <template slot="title">
          <i :class="item.meta.icon"></i>
          <span>{{item.meta.title}}</span>
        </template>
        <MenuItem :menuList="item.showChildren" />
      </el-submenu>
    </template>
  </div>
</template>
<script>
export default {
  name: "MenuItem",
  props: {
    menuList: Array
  }
};
</script>

这里MenuItem涉及组件递归,可参见官网 组件循环引用

修改 src>pages>Layout>components>Menu.vue

<template>
  <div class="app-menu">
    <el-menu
      :collapse="isCollapse"
      :collapse-transition="false"
      router
      default-active="/index/index"
      class="el-menu-vertical-demo"
      :mode="menuLocation==='H'?'horizontal':'vertical'"
    >
    <!-- 这里删除ele-menu里的全部内容后新增这一句就可以了 -->
+    <MenuItem :menuList="menuList"/>
    </el-menu>
  </div>
</template>
<script>
import MenuItem from "./MenuItem";
export default {
  components: {
    MenuItem
  },
  computed: {
    isCollapse() {
      return this.$store.state.menu.isCollapse;
    },
    menuLocation() {
      return this.$store.state.menu.location;
    },
+    menuList() {
+      return this.$store.state.menu.list;
+    }
  }
};
</script>

到这里看下浏览器菜单栏中的路由显示,可以修改 meta.isShow 等属性值看下效果。


(5)样式修改
但是发现收缩时和 Menu 在 Header 里时样式是有问题的

这里要修改下样式,新建 src>assets>style>elementuiReset.styl

.el-menu--collapse .app-menu-item .el-menu-item span, .el-menu--collapse .app-menu-item .el-submenu>.el-submenu__title span
    height 0
    width 0
    overflow hidden
    visibility hidden
    display inline-block
.el-menu--horizontal>.app-menu-item>.el-menu-item
    float left
    height 60px
    line-height 60px
    margin 0
    border-bottom 2px solid transparent
    color #909399
.el-menu--horizontal>.app-menu-item>.el-menu-item.is-active
    border-bottom 2px solid #409EFF
    color #303133
.el-menu--horizontal>.app-menu-item>.el-submenu
    float left
.el-menu--horizontal>.app-menu-item>.el-submenu .el-submenu__title
    height 60px
    line-height 60px
    border-bottom 2px solid transparent
    color #909399
.el-menu--horizontal>.app-menu-item>.el-submenu.is-active .el-submenu__title
    border-bottom 2px solid #409EFF
    color #303133
.el-menu--horizontal>.app-menu-item>.el-submenu .el-submenu__icon-arrow
    position static
    vertical-align middle
    margin-left 8px
    margin-top -3px
.el-menu--horizontal .el-menu .el-menu-item.is-active
    color #409eff

修改 src>assets>style>index.styl

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

(6)刷新后问题及主菜单的高亮
收缩没问题后,因为我们在 Menu 页面中设置的默认活动路由是 /index/index,与当前访问的路由 /list/index 不匹配,刷新后就出现了如下两张图显示的两个小问题:
图1:刷新后,主菜单没有展开;
图2:点开没有展开的主菜单,当前页面对应的子菜单没有高亮;

参见一下官网API 路由对象属性$route 我们直接可绑定 $route.path 属性。
修改 src>pages>Layout>components>Menu.vue

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

这么改完后, /list/index 刷新就正常,但是 /index/index 这里又有问题了,因为首页虽然重定向到了 /index/index 但是原本 path 值还是 /index 无法匹配,得在渲染路径的时候判断一下。
修改 src>pages>Layout>components>MenuItem.vue

      <el-menu-item
        class="test"
        v-if="item.showChildren.length<=0"
-       :index="item.pathFull"
+       :index="item.redirect ? item.redirect : item.pathFull"
        :key="item.name"
      >

现在再刷新试试看,完美 :)。

还有最后一个问题咯,如下图,子菜单高亮时,主菜单没有高亮。

添加高亮样式,修改 src>assets>style>index.styl

 $bg-color = #eee
 .app-header
   background-color white
   position fixed
   top 0
 .app-menu
   background-color white
   position fixed
+  .app-menu-item-actived
+    color #409EFF
 .app-tabs
   position fixed
 .app-container
   height 100vh
   .app-tabs,.app-content,.app-footer
     background-color white

修改 src>pages>Layout>components>MenuItem.vue

<template>
  <div class="app-menu-item">
    <template v-for="item in menuList">
      <el-menu-item
        class="test"
        v-if="item.showChildren.length<=0"
        :index="item.redirect?item.redirect:item.pathFull"
        :key="item.name"
      >
        <i :class="item.meta.icon"></i>
        <span>{{item.meta.title}}</span>
      </el-menu-item>
      <el-submenu v-else index="list" :key="item.name">
        <template slot="title">
-          <i :class="item.meta.icon"></i>
-          <span>{{item.meta.title}}</span>
+          <i :class="[item.meta.icon,isActived(item)?'app-menu-item-actived':'']"></i>
+          <span :class="isActived(item)?'app-menu-item-actived':''">{{item.meta.title}}</span>
        </template>
        <MenuItem :menuList="item.showChildren" />
      </el-submenu>
    </template>
  </div>
</template>

<script>
export default {
  name: "MenuItem",
  props: {
    menuList: Array
  },
+  methods: {
+    isActived(item) {
+      let path = this.$route.path;
+      let isActived = false;
+      let hasPath = this.hasPathFn(item);
+      hasPath ? (isActived = true) : "";
+      return isActived;
+    },
+    hasPathFn(item, path) {  
+      //这里是判断该项下面是否包含有当前路径,一般最多三层菜单栏,我们只判断到这里
+      let path = this.$route.path;
+      let hasPath = false;
+      item.showChildren.forEach(item2 => {
+        if (item2.pathFull === path) {
+          hasPath = true;
+        } else {
+          item2.showChildren.forEach(item3 => {
+            if (item3.pathFull === path) {
+              hasPath = true;
+            }
+          });
+        }
+      });
+      return hasPath;
+    }
+  }
};
</script>

到这里 Menu 自动根据路径渲染就写好了,不妨接着通过优化菜单栏的自定义图标来自定义全局组件。

2、自定义全局组件

(1)svg图标雪碧图
在自定义全局组件前,先来进行一个优化。我们将菜单栏的图标放在了路由配置里,若是带着路径引用,不管是开发还是修改维护都是挺麻烦的一件事,这里我们用一个插件 svg-sprite-loader 将所有svg图标的引用都做成像我们菜单代码里使用的一样,只需加一个名称。
安装

yarn add svg-sprite-loader -D

配置
修改 vue.config.js

    chainWebpack: config => {
        config.resolve.alias
            .set("@", resolve('src'))
            .set("assets", resolve('src/assets'))
            .set("common", resolve('src/common'))
            .set("data", resolve('src/data'))
            .set("store", resolve('src/data/store'))
            .set("router", resolve('src/router'))

+        config.module.rules.delete('svg') //删除默认svg的处理
+        config.module
+            .rule('svg-sprite-loader')
+            .test(/\.svg$/)
+            .include.add(resolve('src/assets/icons')) //svg目录
+            .end()
+            .use('svg-sprite-loader')
+            .loader('svg-sprite-loader')
+            .options({
+                symbolId: 'icon-[name]'   //使用时 id 名
+            })
    }

导出icons至全局
自己新建一个svg资源

新建 src>assets>icons>index.js

const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)

main.js中引入

+ // SVG 图标
+ import '@/assets/icons'

在首页测试一下
修改 src>pages>Index>index.vue

<template>
  <div>
    <p class="index-p">Index-index</p>
+    <p>
+    <svg aria-hidden="true">
+      <use xlink:href="#icon-breakfast"></use>
+    </svg>
+    </p>
  </div>
</template>
<script>
export default {
    name:"IndexIndex",
}
</script>

如图,使用成功

(2)注册全局组件
这里我们可以将刚刚使用的几行代码注册为全局组件在项目任意位置使用。另外组件有时候不同项目会共用,我们一般把相关样式跟资源放在一个文件夹里,方便复用
新建 src>components>SvgIcon>index.vue

<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconId" />
  </svg>
</template>

<script>
export default {
  name: "SvgIcon",
  props: {
    iconName: String,
    className: String
  },
  computed: {
    iconId() {
      return `#icon-${this.iconName}`;
    },
    svgClass() {
      return `svg-icon ${this.iconName} ${this.className} `;
    }
  }
};
</script>

<style scoped lang="stylus">  
.svg-icon
  width 2rem
  height 2rem
  fill currentColor
  overflow hidden
</style>

注册,修改 main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './data/store'
import Element from "element-ui";
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
// 使用Element UI
Vue.use(Element, {
  size: "small"
});
//样式
import "@/assets/styles/index.styl";
//路由过滤
import 'common/routerFilter'
// SVG 图标
import '@/assets/icons'
+ //注册全局组件
+ import SvgIcon from'./components/SvgIcon/index'
+ Vue.component('svg-icon', SvgIcon)

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

使用,修改 src>pages>Index>index.vue

<template>
  <div>
    <p class="index-p">Index-index</p>
    <p>
      <svg aria-hidden="true">
        <use xlink:href="#icon-breakfast" />
      </svg>
    </p>
+    <p>
+      <svg-icon iconName="breakfast"></svg-icon>
+    </p>
  </div>
</template>
<script>
export default {
  name: "IndexIndex"
};
</script>

查看浏览器有了 :)


另外我们开发大型项目的时候可能会有很多全局组件,一般不会像上面那样去一个一个注册,容易造成文件的臃肿,所以我们来优化一下。
新建 src>components>index.js

import Vue from 'vue'

// 读取文件夹下以.vue格式的文件
const requireComponent = require.context('./', true, /\.vue$/)
requireComponent.keys().forEach(filePath => {
    const componentConfig = requireComponent(filePath)
    const fileName = validateFileName(filePath)
    //如果文件名为index,那么取组件中的name作为注册的组件名,否则文件名作为组件名
    const componentName =
        fileName.toLowerCase() === 'index'
            ? capitalizeFirstLetter(componentConfig.default.name)
            : fileName
    Vue.component(componentName, componentConfig.default || componentConfig)
})

//设置首字母大写
function capitalizeFirstLetter(str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
}

//提取文件名
function validateFileName(str) {
    return (
        /^\S+\.vue$/.test(str) &&
        str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => capitalizeFirstLetter($1))
    )
}

修改 main.js

 //注册全局组件
- import SvgIcon from'./components/SvgIcon/index'
- Vue.component('svg-icon', SvgIcon)
+ import './components'

查看页面没有变化,设置成功。可以拿 components>HelloWorld.vue 这个文件之前我们没有进行过全局注册,现在可以用 HelloWorld(hello-world) 去页面试一下。

(3)Menu 菜单栏优化
小伙伴们自己放几个svg,改下路由里 meta.icon 里的名称。
再修改 src>pages>Layout>components>MenuItem.vue

<template>
 <div class="app-menu-item">
   <template v-for="item in menuList">
     <el-menu-item
       class="test"
       v-if="item.showChildren.length<=0"
       :index="item.redirect?item.redirect:item.pathFull"
       :key="item.name"
     >
-        <i :class="item.meta.icon"></i>
+        <svg-icon
+          :iconName="item.meta.icon"
+          :className="isActived(item)?'app-menu-item-actived':''"
+        ></svg-icon>
       <span>{{item.meta.title}}</span>
     </el-menu-item>
     <el-submenu v-else :index="item.pathFull" :key="item.name">
       <template slot="title">
-        <i :class="item.meta.icon"></i>
+          <svg-icon
+            :iconName="item.meta.icon"
+            :className="isActived(item)?'app-menu-item-actived':''"
+          ></svg-icon>
         <span :class="isActived(item)?'app-menu-item-actived':''">{{item.meta.title}}</span>
       </template>
       <MenuItem :menuList="item.showChildren" />
     </el-submenu>
   </template>
 </div>
</template>

<script>
export default {
 name: "MenuItem",
 props: {
   menuList: Array
 },
 methods: {
   isActived(item) {
       let isActived = false;
+      let path = this.$route.path;
       let hasPath = this.hasPathFn(item);
-      hasPath ? (isActived = true) : "";     
+      //之前用elementui里自带的icon,活动路由会自动变化颜色,现在是我们自定义的,得自己加样式
+      (hasPath || item.pathFull === path || path == item.redirect) ? (isActived = true) : "";
     return isActived;
   },
   hasPathFn(item) {
     //这里是判断该项下面是否包含有当前路径,一般最多三层菜单栏,我们只判断到这里
     let path = this.$route.path;
     let hasPath = false;
     item.showChildren.forEach(item2 => {
       if (item2.pathFull === path) {
         hasPath = true;
       } else {
         item2.showChildren.forEach(item3 => {
           if (item3.pathFull === path) {
             hasPath = true;
           }
         });
       }
     });
     return hasPath;
   }
 }
};
</script>

到这里 全局组件 及 Menu 的优化告一段落,接下来我们写 Crumbs 面包屑导航。

3、Crumbs面包屑导航

修改store为 mosules 形式
这里开始我们会在多个组件模块里用到状态管理,所以在这里把 store 改成按照模块划分去使用 vuex官网Module
新建 src>data>store>modules>index.js

const app={
    state: {
        system:{
            title:"大米工厂"
        },
        menu: {
            isCollapse: false,
            location: "V",   //V、VH、H三个值,V表示在左侧,VH表示横跨头部,H表示在头部
            list: []
        },
        tabs: {
            isShow: false
        },
        crumbs: {
            isShow: true
        },
        footer: {
            isShow: false
        }
    },
    mutations: {
        SET_MENU_ISCOLLAPSE: state => {
            state.menu.isCollapse = !state.menu.isCollapse
        },
        SETMENU_LIST: (state,menuList) => {
            state.menu.list=menuList
        }
    },
    actions: {
        setMenuIsCollapse({ commit }) {
            commit('SET_MENU_ISCOLLAPSE')
        },
        setMenuList({ commit },menuList) {
            commit('SETMENU_LIST', menuList)
        }
    }
}
export default app

新建 src>data>store>modules>index.js

// 系统状态
import app from './app'
export default {
  app
}

新建 src>data>store>getter.js

import modules from './modules/index'
let modulesGetters = {}
Object.keys(modules).forEach(item => {
  modulesGetters[item] = state => state[item]
})
const getters = {
  ...modulesGetters
}
export default getters

修改 src>data>store>index.js,这里贴了改完的全部代码

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

import getters from './getters'
import modules from './modules/index'

const store = new Vuex.Store({
    modules:{
        ...modules
    },
    getters
})

export default store

这里修改完后,就是修改页面中之前使用的地方(改完后完整代码),我们在修改时加了gettter,所以这里可以用 getter 获取状态值。
修改 src>common>utils>getPageTitle.js

import store from 'store'
const title=store.getters.app.system.title
export default function getPageTitle(pageTitle) {
    if (pageTitle) {
      return `${pageTitle} - ${title}`
    }
    return `${title}`
}

修改 src>common>routerFilter>index .js。


router.beforeEach(async (to, from, next) => {
  document.title = getPageTitle(to.meta.title)
-    if(store.state.menu.list.length===0){
+    if(store.getters.app.menu.list.length===0){
      store.dispatch("setMenuList",filterRouter(pagesRouterList))
      next({ ...to, replace: true })
  }else{
      next()
  }
})

修改 src>pages>Layout>index.vue,computed内为修改后完整代码,剩下的 Header、Menu 依次修改。

   import Footer from "./components/Footer";
+  import { mapGetters } from "vuex";

 computed: {
    ...mapGetters(["app"]),
    isCollapse() {
      return this.app.menu.isCollapse;
    },
    menuLocation() {
      return this.app.menu.location;
    },
    isShowTabs() {
      return this.app.tabs.isShow;
    },
    isShowCrumbs() {
      return this.app.crumbs.isShow;
    },
    isShowFooter() {
      return this.app.footer.isShow;
    }
  },

新建 Crumbs 状态值
新建 src>data>store>modules>coponents>crumbs.js

const crumbs = {
    state: {
        crumbsList: []
    },
    mutations: {
        SET_CRUMBS(state, list) {
            state.crumbsList = list
        }
    },
    actions: {
        setCrumbs({ commit }, list) {
            commit("SET_CRUMBS", list)
        }
    }
}
export default crumbs

修改 src>data>store>modules>index.js

   // 系统状态
   import app from './app'
+  //面包屑导航
+  import crumbs from './components/crumbs'
export default {
   app,
+  crumbs
}

修改 src>common>routerFilter>index.js ,添加全局导航守卫 router-aftereach
(很多场景下,Crumbs 的值这么设置是不符合实际需要的,这里后面抽时间来补上。)

router.afterEach(to => {
    if (store.getters.app.crumbs.isShow) {
       //很多场景下,Crumbs的值这么设置是不符合实际需要的,这里后面抽时间来补上。
        store.dispatch('setCrumbs', to.matched)
    }
})

修改 src>pages>Layoute>components>Crumbs.vue,这里有变量冲突,我们改个名字,记得 Layoute/index.vue 中绑定的变量名也要改一下。

<template>
  <div class="app-crumbs">
    <el-breadcrumb
      separator-class="el-icon-arrow-right"
-     :style="{'line-height':crumbs.height,padding:'0 '+content.padding}"
+     :style="{'line-height':crumbsParams.height,padding:'0 '+content.padding}"
    >
-      <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
+      <el-breadcrumb-item v-for="item in getCrumbs" :key="item.path">{{item.meta.title}}</el-breadcrumb-item>
    </el-breadcrumb>
  </div>
</template>

<script>
+import { mapGetters } from "vuex";
export default {
  props: {
-   crumbs: Object,
+   crumbsParams: Object,
    content: Object
  },
+  computed: {
+    ...mapGetters(["crumbs"]),
+    getCrumbs() {
+      return this.crumbs.crumbsList;
+    }
  }
};
</script>

Layoute/index.vue

      <Crumbs
        v-if="isShowCrumbs"
        :style="{height:crumbs.height,padding:'0 '+content.margin}"
-       :crumbs="crumbs"
+       :crumbsParams="crumbs"
        :content="content"
      />

看下页面,有了
4、Tabs多标签

新建 src>data>store>modules>components>tabs.js

const tabs = {
    state: {
        activeTab: '',
        tabsList: []
    },
    mutations: {
        SET_TABS_ACTIVETAB: (state, name) => {
            state.activeTab = name
        },
        SET_TABS_LIST: (state, item) => {
            !state.tabsList.some(ele => ele.name === item.name) && state.tabsList.push(item)
        },
        DEL_TABS_LIST: (state, name) => {
            state.tabsList.forEach((item, index) => {
                if (item.name === name) {
                    state.tabsList.splice(index, 1)
                }
            })
        }
    },
    actions: {
        setTabsActivetab({ commit }, name) {
            commit('SET_TABS_ACTIVETAB', name)
        },
        setTabsList({ commit }, item) {
            commit('SET_TABS_LIST', item)
        },
        delTabsList({ commit }, name) {
            commit('DEL_TABS_LIST', name)
        }
    }
}
export default tabs;

修改 src>data>store>modules>index.js

   // 系统状态
  import app from './app'
  //面包屑导航
  import crumbs from './components/crumbs'
+ //多标签
+ import tabs from './components/tabs'
  export default {
    app,
    crumbs,
+   tabs
  }

修改 src>data>store>index.js,Tab刷新时需要取到当前标签的路径,这里加一下菜单栏的对象形式

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

修改 src>common>routerFilter>filter.js,给 menu.obj 赋值

  import { MessageBox } from 'element-ui'
+ import store from 'store'

...

function filterPage(mennuList, pathFull, joinSign) {
    let pathFullCurrent = pathFull || ""
    let joinSignCurrent = joinSign || ""
    for (let i = 0; i < mennuList.length; i++) {
         const ele = mennuList[i];
         ele.pathFull = pathFullCurrent + joinSignCurrent + ele.path
         ele.showChildren = []
+        store.getters.app.menu.obj[ele.name] = ele    
        if (ele.children && ele.meta.isShow) {
            ele.showChildren = ele.children.filter(ele2 => ele2.meta.isShow)
            filterPage(ele.children, ele.pathFull, "/")
        }
    }
}

修改 src>pages>Layout>components>Tabs.vue,全部删掉,改完完整代码如下。

<template>
  <div class="app-tabs">
    <el-tabs type="card" v-model="activeTab" @tab-click="clickTab" @tab-remove="removeTab">
      <el-tab-pane
        v-for="item in tabsList"
        :closable="tabsList.length>1"
        :key="item.name"
        :label="item.meta.title"
        :name="item.name"
      ></el-tab-pane>
    </el-tabs>
  </div>
</template>

<script>
import { mapGetters } from "vuex";
export default {
  computed: {
    ...mapGetters(["tabs", "app"]),
    tabsList() {
      return this.tabs.tabsList.filter(item => item.name);
    },
    activeTab: {
      get() {
        return this.tabs.activeTab;
      },
      set(val) {
        this.$store.dispatch("setTabsActivetab", val);
      }
    }
  },
  methods: {
    clickTab(tab) {
      if (this.$route.fullPath != this.app.menu.obj[tab.name].pathFull) {
        this.$router.replace(this.app.menu.obj[tab.name].pathFull);
      }
    },
    removeTab(name) {
      let tabsList = this.tabsList;
      let activeTab = this.activeTab;
      for (let i = 0; i < tabsList.length; i++) {
        const ele = tabsList[i];
        if (activeTab === name && name === ele.name) {  //关闭的标签是当前标签时,需要重设活动标签
          let activeListName = tabsList[i + 1]
            ? tabsList[i + 1].name
            : tabsList[i - 1].name;
          this.$store.dispatch("setTabsActivetab", activeListName);
          this.$router.replace(this.app.menu.obj[activeListName].pathFull);
          break;
        }
      }
      this.$store.dispatch("delTabsList", name);
    }
  }
};
</script>

现在可以在浏览器里操作试一下了。

5、基于 elementUI Pagination 分页 分页自定义全局组件

一个项目相同组件往往有相同的风格样式,如果用的地方比较多,客户要求改一两个可配置功能样式的话也是很繁琐的,所以我们自定义一个全局组件,里面用elementUI的 Pagination 分页组件。
新建 src>components>Pagination>index.vue

<template>
  <el-pagination layout="prev, pager, next" :total="total"></el-pagination>
</template>
<script>
export default {
    name:"Pagination",
    props: {
      total: {
        type: Number,
        required: true
      }
  },
}
</script>

我们之前已经写了批量注册,这里建完就可以用了,修改 src>pages>Index>index.vue

<template>
  <div>
    <p class="index-p">Index-index</p>
    <p>
      <svg aria-hidden="true">
        <use xlink:href="#icon-breakfast" />
      </svg>
    </p>
    <p>
      <svg-icon iconName="breakfast"></svg-icon>
    </p>
+    <div>
+      <pagination  :total="500"/>
+    </div>
  </div>
</template>
<script>
export default {
  name: "IndexIndex"
};
</script>

看下截图,接下来我们简单的加几个参数。

修改 src>components>Pagination>index.vue 后完整代码

<template>
  <div class="app-pagination">
    <el-pagination
      :small="small"
      :background="background"
      :page-size="pageSize"
      :total="total"
      :page-count="pageCount"
      :layout="layout"
      :page-sizes="pageSizes"
      :prev-text="prevText"
      :next-text="nextText"
      :hide-on-single-page="hideOnSinglePage"
      v-if="!disabled"
      @size-change="sizeChange"
      @current-change="currentChange"
      @prev-click="prevClick"
      @next-click="nextClick"
    ></el-pagination>
  </div>
</template>
<script>
export default {
  name: "Pagination",
  props: {
    small: {
      // 是否小规格
      type: Boolean,
      default: false
    },
    background: {
      // 是否为分页按钮添加背景色
      type: Boolean,
      default: false
    },
    pageSize: {
      // 默认每页条数
      type: Number,
      default: 10
    },
    total: {
      //总条数
      type: Number,
      required: true
    },
    pageCount: {
      //页码按钮的数量,当总页数超过该值时会折叠
      type: Number,
      default: 7
    },
    layout: {
      // 组件布局,子组件名用逗号分隔
      type: String,
      default: "total, sizes, prev, pager, next, jumper"
    },
    pageSizes: {
      type: Array,
      default: () => [10, 20, 50, 100]
    },
    prevText: {
      //替代图标显示的上一页文字
      type: String
    },
    nextText: {
      //替代图标显示的下一页文字
      type: String
    },
    disabled: {
      //是否分页
      type: Boolean,
      default: false
    },
    hideOnSinglePage: {
      //只有一页时是否隐藏
      type: Boolean
    }
  },
  data() {
    return {};
  },
  methods: {
    sizeChange(pageSize) {
      console.log(pageSize,'sizeChange');
    //   this.$emit('sizeChange',pageSize)
    },
    currentChange(currentPage) {
      console.log(currentPage,'currentChange');
    //   this.$emit('currentChange',currentPage)
    },
    prevClick(currentPage) {
      console.log(currentPage,'prevClick');
    //   this.$emit('prevClick',currentPage)
    },
    nextClick(currentPage) {
      console.log(currentPage,'nextClick');
    //   this.$emit('nextClick',currentPage)
    }
  }
};
</script>

修改 src>pages>Index>index.vue

<template>
  <div>
    <p class="index-p">Index-index</p>
    <p>
      <svg aria-hidden="true">
        <use xlink:href="#icon-breakfast" />
      </svg>
    </p>
    <p>
      <svg-icon iconName="breakfast"></svg-icon>
    </p>
    <div>
      <pagination :total="500" />
+      <!-- <pagination :total="500" @sizeChange="sizeChange"/> -->
    </div>
  </div>
</template>
<script>
export default {
  name: "IndexIndex",
+  // methods:{
+  //   sizeChange(pageSize){
+  //     console.log(pageSize,'---index');      
+  //   }
+  // }
};
</script>

这么设置分页就可以全局统一修改,如果有特殊的地方还可以绑定自定义的属性、事件。分页往往是跟着表格列表一起的,小伙伴们可以自己写个表格的,然后跟分页的相关事件结合起来。

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

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

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

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

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