(1) 相關博文地址:html
SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(一):搭建基本環境:https://www.cnblogs.com/l-y-h/p/12930895.html SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(二):引入 element-ui 定義基本頁面顯示:https://www.cnblogs.com/l-y-h/p/12935300.html SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(三):引入 js-cookie、axios、mock 封裝請求處理以及返回結果:https://www.cnblogs.com/l-y-h/p/12955001.html SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(四):引入 vuex 進行狀態管理、引入 vue-i18n 進行國際化管理:https://www.cnblogs.com/l-y-h/p/12963576.html
(2)代碼地址:前端
https://github.com/lyh-man/admin-vue-template.git
以前在 搭建基本頁面時,已經簡單使用過,這裏再深刻了解一下。
(1)文件格式以下
因爲建立項目時,指定了 router,因此 vue-cli 自動生成了 router 文件夾以及相關的 js 文件。vue
(2)手動引入 router(可選操做)。ios
若初始化項目時未指定 router,能夠本身手動添加 router。git
【router 中文文檔:】 https://router.vuejs.org/zh/ 【router 使用參考:】 https://www.cnblogs.com/l-y-h/p/11661874.html
(1)簡介
此項目是單頁面應用,經過 vue-router 將 各個組件(components)映射到指定位置,實現頁面切換的效果。
以前定義基本頁面時,已經簡單應用了 router。github
(2)代碼以下:
根據路徑能夠進行路由匹配,也可根據 name 屬性去定位路由。
其中:
component 採用路由懶加載的形式( () => import() ),路由被訪問時再加載。
path: '/' 表示項目根路徑。
redirect 表示跳轉到另外一個路由。
name: "Login" 表示路由名,能夠根據 name 定位路由。
path: "*" 表示全匹配,通常寫在路由規則的最後一個(用於路徑不存在時跳轉到一個指定頁面)。vue-router
【基本路由:】 https://www.cnblogs.com/l-y-h/p/12935300.html#_label1_8 【路由規則:】 import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' Vue.use(VueRouter) const routes = [{ path: '/', redirect: { name: "Login" } }, { path: '/404', name: '404', component: () => import('@/components/common/404.vue') }, { path: '/Login', name: 'Login', component: () => import('@/components/common/Login.vue') }, { path: '/Home', name: 'Home', component: () => import('@/views/Home.vue'), redirect: { name: 'HomePage' }, children: [{ path: '/Home/Page', name: 'HomePage', component: () => import('@/views/menu/HomePage.vue') }, { path: '/Home/Demo/Echarts', name: 'Echarts', component: () => import('@/views/menu/Echarts.vue') }, { path: '/Home/Demo/Ueditor', name: 'Ueditor', component: () => import('@/views/menu/Ueditor.vue') } ] }, { path: "*", redirect: { name: '404' } } ] const router = new VueRouter({ // routes 用於定義 路由跳轉 規則 routes, // mode 用於去除地址中的 # mode: 'history', // scrollBehavior 用於定義路由切換時,頁面滾動。 scrollBehavior: () => ({ y: 0 }) }) // 解決相同路徑跳轉報錯 const routerPush = VueRouter.prototype.push; VueRouter.prototype.push = function push(location, onResolve, onReject) { if (onResolve || onReject) return routerPush.call(this, location, onResolve, onReject) return routerPush.call(this, location).catch(error => error) }; export default router
(1)簡介
導航守衛適用於 路由變化時。即當路由變化時,會觸發導航守衛。
路由元信息 能夠用於定義路由獨有的信息(meta)。
注:
同一個組件切換時,參數改變不會觸發導航守衛(複用組件)。能夠經過 watch 監聽 $route 對象的變化來定義導航守衛,或者 直接使用 beforeRouteUpdate 來進行導航守衛(組件內守衛)。vuex
(2)全局前置守衛(beforeEach)
使用 beforeEach 能夠定義一個全局前置守衛,路由跳轉前會觸發。
其有三個參數:
to:一個路由對象,表示即將進入的 目標路由對象。
from:一個路由對象,表示當前路由 離開時的路由對象。
next:一個方法(不能少,確保路由可以跳轉出去。
next() 表示執行下一個守衛規則,若全部規則執行完畢,則結束並跳轉到指定路由。
next({path: ''}) 或者 next({name: ''}) 表示指定路徑跳轉。vue-cli
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
(3)路由元信息
定義路由規則時,能夠經過 meta 指定路由的元信息。
經過 router對象.meta 能夠獲取到某個 router對象 的 meta 信息,並根據其進行處理。element-ui
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, meta: { requiresAuth: true } } ] } ] })
(4)項目中使用
進入主頁面後,當 token 過時或不存在時,須要跳轉到登陸頁面從新登陸。
使用 導航守衛,每次路由跳轉前,肯定 token 是否存在。可使用 beforeEach 定義全局守衛,也可使用 beforeEnter 爲某個路由定義獨有守衛。
此處演示使用 beforeEach 定義全局守衛。
Step1:
修改 Login.vue 登陸邏輯,保存 token 值。
以前將 cookie 相關的操做保存在 /http/auth.js 中,須要引入該 js。
import { setToken } from '@/http/auth.js' dataFormSubmit() { // TODO:登陸代碼邏輯待完善 // alert("登陸代碼邏輯未完善") this.$http({ url: '/auth/token', method: 'get' }).then(response => { this.$message({ message: this.$t("login.signInSuccess"), type: 'success' }) // 保存 token 值 setToken(response.data.token) this.updateName(this.dataForm.userName) console.log(response) this.$router.push({ name: 'Home' }) }) },
Step2:
修改路由。
定義路由元信息(meta)。meta 用於定義路由元信息,其中 isRouter 用於指示是否開啓路由守衛(true 表示開啓)。
{ path: '/Home', name: 'Home', component: () => import('@/views/Home.vue'), redirect: { name: 'HomePage' }, children: [{ path: '/Home/Page', name: 'HomePage', component: () => import('@/views/menu/HomePage.vue'), meta: { isRouter: true } }, { path: '/Home/Demo/Echarts', name: 'Echarts', component: () => import('@/views/menu/Echarts.vue'), meta: { isRouter: true } }, { path: '/Home/Demo/Ueditor', name: 'Ueditor', component: () => import('@/views/menu/Ueditor.vue'), meta: { isRouter: true } } ] }
Step3:
添加全局守衛(beforeEach)。
當 isRouter 爲 true 時,纔會去校驗 token,token 校驗失敗則跳轉到 Login 頁面從新登陸。
// 添加全局路由導航守衛 router.beforeEach((to, from, next) => { // 當開啓導航守衛時,驗證 token 是否存在。 if (to.meta.isRouter) { // 獲取 token 值 let token = getToken() console.log(token) // token 不存在時,跳轉到 登陸頁面 if (!token || !/\S/.test(token)) { next({name: 'Login'}) } } next() })
Step4:
完整 router 以下:
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' import { getToken } from '@/http/auth.js' Vue.use(VueRouter) // 定義路由跳轉規則 // component 採用 路由懶加載形式 // 此項目中,均採用 name 方式指定路由進行跳轉 // meta 用於定義路由元信息,其中 isRouter 用於指示是否開啓路由守衛(true 表示開啓)。 const routes = [{ path: '/', redirect: { name: "Login" } }, { path: '/404', name: '404', component: () => import('@/components/common/404.vue') }, { path: '/Login', name: 'Login', component: () => import('@/components/common/Login.vue') }, { path: '/Home', name: 'Home', component: () => import('@/views/Home.vue'), redirect: { name: 'HomePage' }, children: [{ path: '/Home/Page', name: 'HomePage', component: () => import('@/views/menu/HomePage.vue'), meta: { isRouter: true } }, { path: '/Home/Demo/Echarts', name: 'Echarts', component: () => import('@/views/menu/Echarts.vue'), meta: { isRouter: true } }, { path: '/Home/Demo/Ueditor', name: 'Ueditor', component: () => import('@/views/menu/Ueditor.vue'), meta: { isRouter: true } } ] }, // 路由匹配失敗時,跳轉到 404 頁面 { path: "*", redirect: { name: '404' } } ] // 建立一個 router 實例 const router = new VueRouter({ // routes 用於定義 路由跳轉 規則 routes, // mode 用於去除地址中的 # mode: 'history', // scrollBehavior 用於定義路由切換時,頁面滾動。 scrollBehavior: () => ({ y: 0 }) }) // 添加全局路由導航守衛 router.beforeEach((to, from, next) => { // 當開啓導航守衛時,驗證 token 是否存在。 if (to.meta.isRouter) { // 獲取 token 值 let token = getToken() console.log(token) // token 不存在時,跳轉到 登陸頁面 if (!token || !/\S/.test(token)) { next({name: 'Login'}) } } next() }) // 解決相同路徑跳轉報錯 const routerPush = VueRouter.prototype.push; VueRouter.prototype.push = function push(location, onResolve, onReject) { if (onResolve || onReject) return routerPush.call(this, location, onResolve, onReject) return routerPush.call(this, location).catch(error => error) }; export default router
Step5:
測試一下。
手動模擬 token 失效。token 失效後,點擊菜單欄,路由會跳轉到 登陸界面。
前面封裝了 axios,並在 main.js 中全局掛載,因此在組件中可使用 $http 進行訪問。
可是每次請求的相關處理都會寫在各個組件中,代碼看上去不太美觀,且不易維護。
因此能夠將請求根據功能、模塊進行劃分,並寫在固定位置,在組件中引入這些模塊便可。
(1)Step1:
按照功能將請求進行模塊劃分。
好比:
登陸、登出的請求爲 login.js,用戶信息相關的請求爲 user.js,菜單相關的請求爲 menu.js。
(2)Step2:
因爲以前封裝了 httpRequest.js,因此引入 該 js,對請求進行處理。
此處以 login.js 爲例。
import http from '@/http/httpRequest.js' export function getToken() { return http({ url: '/auth/token', method: 'get' }) }
(3)Step3:
定義一個 http.js,引入 login.js 模塊。
import * as login from './modules/login.js' import * as user from './modules/menu.js' export default { login, user }
(4)Step4:
在 main.js 中全局掛載。
import http from '@/http/http.js'
Vue.prototype.$http = http
(5)Step5:
修改 Login.vue 的登陸邏輯,經過全局掛載的 $http 調用 login 模塊的 getToken 方法。
dataFormSubmit() { // TODO:登陸代碼邏輯待完善 // alert("登陸代碼邏輯未完善") this.$http.login.getToken().then(response => { this.$message({ message: this.$t("login.signInSuccess"), type: 'success' }) // 保存 token setToken(response.data.token) this.updateName(this.dataForm.userName) console.log(response) this.$router.push({ name: 'Home' }) }) }
(6)頁面顯示:
(1)什麼是 iframe?
iframe 標籤會建立一個行內框架(包含另外一個文檔的內聯框架)。
簡單地理解:頁面中嵌套另外一個頁面。
(2)使用場景?
有的項目需求,須要在當前頁面中顯示外部網頁,好比訪問百度、查看接口文檔等,此時就可使用 iframe 標籤,嵌套一個頁面。
(3)簡單使用一下
以下,簡單使用一下 iframe
<template> <el-main class="content"> <el-card class="card" shadow="hover"> <!-- <keep-alive> <router-view /> </keep-alive> --> <iframe src="https://www.baidu.com/" frameborder="0" width="100%" height="700px"></iframe> </el-card> </el-main> </template>
(1)實現效果
每點擊一個菜單項,在內容區會顯示一個標籤頁,
點擊不一樣的標籤頁,會跳轉到相應的組件,並顯示不一樣的內容。
如果自身模塊,則使用 router-view 顯示,如果外部網頁,則使用 iframe 顯示。
(2)思路:
因爲涉及到組件間數據的交互,因此使用 vuex 維護狀態。側邊欄(Aside.vue)選中菜單項時,相關數據被修改,而 內容區(Content.vue)根據 相關數據進行展現。
須要維護的數據:
須要一個數組,用於保存點擊的菜單項(標籤屬性、url、標題等)。
須要兩個字符串,一個用於保存當前菜單選中項,一個保存當前標籤選中項。
因爲菜單內容的顯示經過路由跳轉完成,不一樣的菜單須要不一樣的顯示效果,因此可使用 router 的 meta,定義相關路由元信息。
路由元信息:
isTab: 表示能夠顯示爲標籤頁。
iframeUrl : 表示 url,其中 以 http 或者 https 開頭的 url 使用 iframe 標籤展現。
(3)實現
Step1:
在路由中添加一個路由元信息,並新增一個路由用於測試 iframe 使用(Baidu)。
其中:
isTab 用於表示是否顯示爲標籤頁(true 表示顯示)
iframeUrl 用於表示 url,使用 http 或者 https 開頭的 url 使用 iframe 標籤展現
meta: { isTab: true, iframeUrl: 'https://www.baidu.com/' } { path: '/Home', name: 'Home', component: () => import('@/views/Home.vue'), redirect: { name: 'HomePage' }, children: [{ path: '/Home/Page', name: 'HomePage', component: () => import('@/views/menu/HomePage.vue'), meta: { isRouter: true } }, { path: '/Home/Demo/Echarts', name: 'Echarts', component: () => import('@/views/menu/Echarts.vue'), meta: { isRouter: true, isTab: true } }, { path: '/Home/Demo/Ueditor', name: 'Ueditor', component: () => import('@/views/menu/Ueditor.vue'), meta: { isRouter: true, isTab: true } }, { path: '/Home/Demo/Baidu', name: 'Baidu', meta: { isRouter: true, isTab: true, iframeUrl: 'https://www.baidu.com/' } } ] }
Step2:
使用 vuex 維護幾個必要的狀態。
其中:
menuActiveName 表示側邊欄選中的菜單項的名
mainTabs 表示標籤頁數據,數組
mainTabsActiveName 表示標籤頁中選中的標籤名
以下,在 common.js 中進行相關定義。
export default { // 開啓命名空間(防止各模塊間命名衝突),訪問時須要使用 模塊名 + 方法名 namespaced: true, // 管理數據(狀態) state: { // 用於保存語言設置(國際化),默認爲中文 language: 'zh', // 表示側邊欄選中的菜單項的名 menuActiveName: '', // 表示標籤頁數據,數組 mainTabs: [], // 表示標籤頁中選中的標籤名 mainTabsActiveName: '' }, // 更改 state(同步) mutations: { updateLanguage(state, data) { state.language = data }, updateMenuActiveName(state, name) { state.menuActiveName = name }, updateMainTabs(state, tabs) { state.mainTabs = tabs }, updateMainTabsActiveName(state, name) { state.mainTabsActiveName = name }, }, // 異步觸發 mutations actions: { updateLanguage({commit, state}, data) { commit("updateLanguage", data) }, updateMenuActiveName({commit, state}, name) { commit("updateMenuActiveName", name) }, updateMainTabs({commit, state}, tabs) { commit("updateMainTabs", tabs) }, updateMainTabsActiveName({commit, state}, name) { commit("updateMainTabsActiveName", name) } } }
Step3:
在側邊欄中,引入 menuActiveName 、 mainTabs、mainTabsActiveName 以及其相關的修改方法。對其進行操做。
import {mapState, mapActions} from 'vuex' export default { computed: { ...mapState('common', ['menuActiveName', 'mainTabs']) }, methods: { ...mapActions('common', ['updateMenuActiveName', 'updateMainTabs', 'updateMainTabsActiveName']) } }
Step4:
監視 $route 的變化,路由發生變化後,就會觸發。
每次點擊 菜單項,均會觸發 路由的跳轉,因此監聽 $route 的變化,變化時能夠進行相關操做。
以下:
監視路由的變化,路由發生改變後,側邊欄菜單項選中狀態須要修改到選中位置。
根據路由元信息判斷,若是能夠顯示爲標籤頁,則處理標籤頁相關規則,不然直接跳過。
標籤頁規則:
使用 數組 保存標籤頁信息,若是當前選中的菜單項 未保存在 數組中,則向數組中添加該標籤信息並修改當前選中的標籤頁名,若已存在,則直接修改當前選中的標籤頁名。
標籤頁信息:
name 表示標籤名
params、query 表示參數(路由須要的參數)
type 表示顯示類型, iframe 表示使用 iframe 標籤顯示
iframeUrl 表示 url,默認爲 ‘’
watch: { // 監視路由的變化,每次點擊菜單項時會觸發 $route(route) { // 路由變化時,修改當前選中的菜單項 this.updateMenuActiveName(route.name) // 是否顯示標籤頁 if (route.meta.isTab) { // 判斷當前標籤頁數組中是否存在當前選中的標籤,根據標籤名匹配 let tab = this.mainTabs.filter(item => item.name === route.name)[0] // 若當前標籤頁數組不存在該標籤,則向數組中添加標籤 if (!tab) { // 設置標籤頁數據 tab = { name: route.name, params: route.params, query: route.query, type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module', iframeUrl: route.meta.iframeUrl || '' } // 將數據保存到標籤頁數組中 this.updateMainTabs(this.mainTabs.concat(tab)) } // 保存標籤頁中當前選中的標籤名 this.updateMainTabsActiveName(route.name) } } }
上面的 isURL 是封裝的一個方法,此處抽取到一個公用 js 中。
/** * URL地址 * @param {*} s */ export function isURL (s) { return /^http[s]?:\/\/.*/.test(s) }
固然使用時須要引入該 js。
import {isURL} from '@/utils/validate.js'
Step5:
添加一個菜單項(Baidu),將上面幾步整合。
完整的 Aside.vue 以下:
<template> <div> <!-- 系統 Logo --> <el-aside class="header-logo" :width="asideWidth"> <div @click="$router.push({ name: 'Home' })"> <a v-if="foldAside">{{language.adminCenter}}</a> <a v-else>{{language.admin}}</a> </div> </el-aside> <el-aside class="aside" :width="asideWidth" :class='"icon-size-" + iconSize'> <el-scrollbar style="height: 100%; width: 100%;"> <!-- default-active 表示當前選中的菜單,默認爲 HomePage。 collapse 表示是否摺疊菜單,僅 mode 爲 vertical(默認)可用。 collapseTransition 表示是否開啓摺疊動畫,默認爲 true。 background-color 表示背景顏色。 text-color 表示字體顏色。 --> <el-menu :default-active="menuActiveName || 'HomePage'" :collapse="!foldAside" :collapseTransition="false" background-color="#263238" text-color="#8a979e"> <el-menu-item index="HomePage" @click="$router.push({ name: 'Home' })"> <i class="el-icon-s-home"></i> <span slot="title">{{language.homePage}}</span> </el-menu-item> <el-submenu index="demo"> <template slot="title"> <i class="el-icon-star-off"></i> <span>demo</span> </template> <el-menu-item index="Echarts" @click="$router.push({ name: 'Echarts' })"> <i class="el-icon-s-data"></i> <span slot="title">echarts</span> </el-menu-item> <el-menu-item index="Ueditor" @click="$router.push({ name: 'Ueditor' })"> <i class="el-icon-document"></i> <span slot="title">ueditor</span> </el-menu-item> <el-menu-item index="Baidu" @click="$router.push({ name: 'Baidu' })"> <i class="el-icon-document"></i> <span slot="title">baidu</span> </el-menu-item> </el-submenu> </el-menu> </el-scrollbar> </el-aside> </div> </template> <script> import {mapState, mapActions} from 'vuex' import {isURL} from '@/utils/validate.js' export default { name: 'Aside', props: ['foldAside'], data() { return { // 保存當前選中的菜單 // menuActiveName: 'home', // 保存當前側邊欄的寬度 asideWidth: '200px', // 用於拼接當前圖標的 class 樣式 iconSize: 'true' } }, computed: { ...mapState('common', ['menuActiveName', 'mainTabs']), // 國際化 language() { return { adminCenter: this.$t("aside.adminCenter"), admin: this.$t("aside.admin"), homePage: this.$t("aside.homePage") } } }, methods: { ...mapActions('common', ['updateMenuActiveName', 'updateMainTabs', 'updateMainTabsActiveName']) }, watch: { // 監視是否摺疊側邊欄,摺疊則寬度爲 64px。 foldAside(val) { this.asideWidth = val ? '200px' : '64px' this.iconSize = val }, // 監視路由的變化,每次點擊菜單項時會觸發 $route(route) { // 路由變化時,修改當前選中的菜單項 this.updateMenuActiveName(route.name) // 是否顯示標籤頁 if (route.meta.isTab) { // 判斷當前標籤頁數組中是否存在當前選中的標籤,根據標籤名匹配 let tab = this.mainTabs.filter(item => item.name === route.name)[0] // 若當前標籤頁數組不存在該標籤,則向數組中添加標籤 if (!tab) { // 設置標籤頁數據 tab = { name: route.name, params: route.params, query: route.query, type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module', iframeUrl: route.meta.iframeUrl || '' } // 將數據保存到標籤頁數組中 this.updateMainTabs(this.mainTabs.concat(tab)) } // 保存標籤頁中當前選中的標籤名 this.updateMainTabsActiveName(route.name) } } } } </script> <style> .aside { margin-bottom: 0; height: 100%; max-height: calc(100% - 50px); width: 100%; max-width: 200px; background-color: #263238; text-align: left; right: 0; } .header-logo { background-color: #17b3a3; text-align: center; height: 50px; line-height: 50px; width: 200px; font-size: 24px; color: #fff; font-weight: bold; margin-bottom: 0; cursor: pointer; } .el-submenu .el-menu-item { max-width: 200px !important; } .el-scrollbar__wrap { overflow-x: hidden !important; } .icon-size-false i { font-size: 30px !important; } .icon-size-true i { font-size: 18px !important; } </style>
Step6:
修改內容區,用於顯示不一樣的頁面。
以下:
定義一個 Tab.vue 組件,當路由元信息 isTab 爲 true 時(便可以顯示爲標籤頁),則顯示標籤頁,不然不顯示標籤頁。
<template> <el-main class="content"> <Tab v-if="$route.meta.isTab"></Tab> <el-card v-else class="card" shadow="hover"> <keep-alive> <router-view /> </keep-alive> </el-card> </el-main> </template> <script> import Tab from '@/views/home/Tab.vue' export default { name: 'Content', components:{ Tab } } </script> <style> .content { background-color: #f1f4f5; } .card { height: 100%; } </style>
Step7:
如今只須要完善 Tab.vue 組件,便可實現想要的效果了。
Tab 組件中須要引入 mainTabs 、mainTabsActiveName 以及其相關修改方法。
其中:
mainTabs 用於展現當前標籤列表,可使用 v-for 進行遍歷展現。
mainTabsActiveName 用於顯示當前標籤選中項。
import { mapState, mapActions } from 'vuex' export default { computed: { ...mapState('common', ['mainTabs']), mainTabsActiveName: { get() { return this.$store.state.common.mainTabsActiveName }, set(val) { this.updateMainTabsActiveName(val) } } }, methods: { ...mapActions('common', ['updateMainTabs', 'updateMainTabsActiveName']) } }
Step8:
給 Tab.vue 組件引入基本頁面,
使用 v-for 遍歷 mainTabs 數組,若是 標籤中 type 爲 iframe,則使用 iframe 進行展現,不然使用 router-view 展現。根據 mainTabsActiveName 選中標籤頁。
<template> <!-- el-tabs 用於顯示標籤頁, 其中: v-model 綁定當前選中的 標籤 :closable = true 表示當前標籤能夠關閉 @tab-click 綁定標籤選中事件 @tab-remove 綁定標籤刪除事件 --> <el-tabs v-model="mainTabsActiveName" class="tab" :closable="true" @tab-click="selectedTabHandle" @tab-remove="removeTabHandle"> <!-- 循環遍歷標籤數組,用於生成標籤列表 --> <el-tab-pane v-for="item in mainTabs" :key="item.name" :label="item.name" :name="item.name"> <el-card class="card" shadow="hover"> <!-- 以 http 或者 https 開頭的地址,均使用 iframe 進行展現 --> <iframe v-if="item.type === 'iframe'" :src="item.iframeUrl" width="100%" height="650px" frameborder="0" scrolling="yes"> </iframe> <!-- 自身組件模塊路由跳轉,使用 router-view 表示 --> <keep-alive v-else> <router-view v-if="item.name === mainTabsActiveName" /> </keep-alive> </el-card> </el-tab-pane> <!-- 定義下拉框,用於操做標籤列表 --> <el-dropdown class="dropdown-tool" :show-timeout="0"> <i class="el-icon-arrow-down"></i> <el-dropdown-menu slot="dropdown"> <el-dropdown-item @click.native="closeCurrentTabsHandle">關閉當前標籤頁</el-dropdown-item> <el-dropdown-item @click.native="closeOtherTabsHandle">關閉其它標籤頁</el-dropdown-item> <el-dropdown-item @click.native="closeAllTabsHandle">關閉所有標籤頁</el-dropdown-item> <el-dropdown-item @click.native="refreshCurrentTabs">刷新當前標籤頁</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </el-tabs> </template> <style scoped="scoped"> .tab { background-color: #fff; margin: -15px -20px 10px -20px; padding: 0 10px 0 10px; height: 40px; } .dropdown-tool { float: left; position: fixed !important; right: 0; width: 40px; height: 40px; line-height: 40px; top: 55px; background-color: #f1f4f5; } .card { height: 650px; } </style>
Step9:
定義 Tab.vue 相關方法:
selectedTabHandle 標籤選中事件,選中標籤後觸發。
removeTabHandle 標籤移除事件,刪除標籤後觸發。
closeCurrentTabsHandle 關閉當前標籤。
closeOtherTabsHandle 關閉其餘標籤。
closeAllTabsHandle 關閉全部標籤。
refreshCurrentTabs 刷新當前選中的標籤。
【selectedTabHandle:】 選中事件處理很簡單,首先找到選中的標籤頁,而後路由跳轉便可, 因爲 Aside.vue 中,已經監聽了 $route,因此路由一變化,就會進行相關處理(修改 vuex 的三個值)。 注: 選中已選中的標籤時,因爲是同一個路由,路由($route)不變化, 若想實現變化,能夠見後面的 refreshCurrentTabs 方法處理。 // 處理標籤選中事件 selectedTabHandle(tab) { // 選擇某個標籤,標籤存在於標籤數組時,則跳轉到相應的路由(根據名字跳轉) tab = this.mainTabs.filter(item => item.name === tab.name)[0] if (tab) { // 已經在 Aside.vue 中使用 watch 監視了 $route,因此一旦路由變化,其就能夠感知到,從而維護 vuex 狀態。 this.$router.push({ name: tab.name, query: tab.query, params: tab.params }) } } 【removeTabHandle】 移除事件,只要從 標籤列表 中找到選中的標籤移除便可。 若標籤列表沒有數據,則跳轉到首頁。 若移除的標籤是當前選中的標籤,則移除後跳轉到最後一個標籤頁。 // 處理標籤刪除事件 removeTabHandle(tabName) { // 從 mainTabs 中刪除標籤便可 this.updateMainTabs(this.mainTabs.filter(item => item.name !== tabName)) // 若是當前 mainTabs 中仍有值,則進行當前選中標籤邏輯處理 if (this.mainTabs.length > 0) { // 若是刪除的是當前選中的標籤,則默認選擇最後一個標籤 let tab = this.mainTabs[this.mainTabs.length - 1] if (tabName === this.mainTabsActiveName) { this.$router.push({ name: tab.name, query: tab.query, params: tab.params }) } } else { // 若是當前 mainTabs 中沒有值,則跳轉到 HomePage 主頁面 this.updateMainTabsActiveName('') this.$router.push({name: 'HomePage'}) } } 【closeCurrentTabsHandle、closeOtherTabsHandle、closeAllTabsHandle】 直接操做 標籤列表 mainTabs 便可。 關閉全部列表後,須要跳轉到首頁。 // 關閉當前標籤 closeCurrentTabsHandle() { this.removeTabHandle(this.mainTabsActiveName) }, // 關閉其餘標籤 closeOtherTabsHandle() { this.updateMainTabs(this.mainTabs.filter(item => item.name === this.mainTabsActiveName)) }, // 關閉全部標籤 closeAllTabsHandle() { // 清空 mainTabs 數組,並跳轉到 主頁面 this.updateMainTabs([]) // 若是當前 mainTabs 中沒有值,則跳轉到 HomePage 主頁面 this.updateMainTabsActiveName('') this.$router.push({name: 'HomePage'}) } 【refreshCurrentTabs:】 因爲同一個路由跳轉時, $route 不會變化,即 watch 失效。 想要實現刷新效果,能夠先移除標籤,再添加標籤,並從新跳轉。 // 刷新當前選中的標籤 refreshCurrentTabs() { // 用於保存當前標籤數組 let tabs = [] Object.assign(tabs, this.mainTabs) // 保存當前選中的標籤 let tab = this.mainTabs.filter(item => item.name === this.mainTabsActiveName)[0] // 先移除標籤 this.removeTabHandle(tab.name) this.$nextTick(() => { // 移除渲染後,再從新添加標籤數組,並跳轉路由 this.updateMainTabs(tabs) this.$router.push({ name: tab.name, query: tab.query, params: tab.params }) }) }
注:
想要同一個路由跳轉不報錯,在 route 中須要定義以下代碼。
// 解決相同路徑跳轉報錯 const routerPush = VueRouter.prototype.push; VueRouter.prototype.push = function push(location, onResolve, onReject) { if (onResolve || onReject) return routerPush.call(this, location, onResolve, onReject) return routerPush.call(this, location).catch(error => error) }
Step10:
實現國際化。
以下,代碼須要實現國際化。
<el-dropdown class="dropdown-tool" :show-timeout="0"> <i class="el-icon-arrow-down"></i> <el-dropdown-menu slot="dropdown"> <el-dropdown-item @click.native="closeCurrentTabsHandle">關閉當前標籤頁</el-dropdown-item> <el-dropdown-item @click.native="closeOtherTabsHandle">關閉其它標籤頁</el-dropdown-item> <el-dropdown-item @click.native="closeAllTabsHandle">關閉所有標籤頁</el-dropdown-item> <el-dropdown-item @click.native="refreshCurrentTabs">刷新當前標籤頁</el-dropdown-item> </el-dropdown-menu> </el-dropdown>
修改 zh.json、en.json。
【zh.json】 "tab": { "closeCurrentTabs": "關閉當前標籤頁", "closeOtherTabs": "關閉其它標籤頁", "closeAllTabs": "關閉所有標籤頁", "refreshCurrentTabs": "刷新當前標籤頁" } 【en.json】 "tab": { "closeCurrentTabs": "Close Current Tabs", "closeOtherTabs": "Close Other Tabs", "closeAllTabs": "Close All Tabs", "refreshCurrentTabs": "Refresh Current Tabs" }
修改Tab.vue
<!-- 定義下拉框,用於操做標籤列表 --> <el-dropdown class="dropdown-tool" :show-timeout="0"> <i class="el-icon-arrow-down"></i> <el-dropdown-menu slot="dropdown"> <el-dropdown-item @click.native="closeCurrentTabsHandle">{{$t("tab.closeCurrentTabs")}}</el-dropdown-item> <el-dropdown-item @click.native="closeOtherTabsHandle">{{$t("tab.closeOtherTabs")}}</el-dropdown-item> <el-dropdown-item @click.native="closeAllTabsHandle">{{$t("tab.closeAllTabs")}}</el-dropdown-item> <el-dropdown-item @click.native="refreshCurrentTabs">{{$t("tab.refreshCurrentTabs")}}</el-dropdown-item> </el-dropdown-menu> </el-dropdown>
【頁面刷新時,如何保持原有vuex中的state信息】 https://www.cnblogs.com/l-y-h/p/11722007.html
因爲 使用 vuex 維護了數據,頁面一刷新,state 數據會變化,就會出現很詭異的效果。
以下圖:
選中了標籤後,可是一刷新頁面,數據相關效果就會變得很奇怪。
解決方法:
在頁面刷新以前,將 state 信息保存,頁面刷新後,再將該值賦給 state。
在 Home.vue 中添加以下代碼:
使用 localStorage 保存 state 信息(也可使用 sessionStorage)。
created() { //在頁面加載時讀取localStorage裏的狀態信息 if (localStorage.getItem("store") ) { this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(localStorage.getItem("store")))) } //在頁面刷新時將vuex裏的信息保存到localStorage裏 window.addEventListener("beforeunload",()=>{ localStorage.setItem("store",JSON.stringify(this.$store.state)) }) }
固然,爲了防止登陸時獲取到上一個用戶保存的 state 值,須要在 登陸時將其移除。
以下:
在 vuex 中定義一個重置數據的方法,並在登陸頁面建立時調用。
// 更改 state(同步) mutations: { resetState(state) { let stateTemp = { language: 'zh', menuActiveName: '', mainTabs: [], mainTabsActiveName: '' } Object.assign(state, stateTemp) } }, // 異步觸發 mutations actions: { resetState({commit, state}) { commit("resetState") } }
在 登陸頁面 引入並調用。
...mapActions('common', {resetState: "resetState"}) created() { // 進入畫面前,移除主頁面保存的 state 信息 localStorage.removeItem("store") this.resetState() }
完整效果:
主頁面中,頁面一刷新,state 就會保存在 localStorage 中,
進入登陸界面後,會移除掉 localStorage 中的 state 數據,並重置 state 數據。