三、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閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374