SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(五):引入 vue-router 進行路由管理、模塊化封裝 axios 請求、使用 iframe 標簽嵌套頁面

来源:https://www.cnblogs.com/l-y-h/archive/2020/05/27/12973364.html
-Advertisement-
Play Games

前提: (1) 相關博文地址: SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(一):搭建基本環境:https://www.cnblogs.com/l-y-h/p/12930895.html SpringBoot + Vue + ElementUI 實現 ...


前提:

(1) 相關博文地址:

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

 

一、引入 vue-router 進行路由管理

1、簡單瞭解下

  之前在 搭建基本頁面時,已經簡單使用過,這裡再深入瞭解一下。
(1)文件格式如下
  由於創建項目時,指定了 router,所以 vue-cli 自動生成了 router 文件夾以及相關的 js 文件。

 

 

 

 

 

 

(2)手動引入 router(可選操作)。

  若初始化項目時未指定 router,可以自己手動添加 router。

【router 中文文檔:】
    https://router.vuejs.org/zh/

【router 使用參考:】
    https://www.cnblogs.com/l-y-h/p/11661874.html

 

2、項目中使用

(1)簡介
  此項目是單頁面應用,通過 vue-router 將 各個組件(components)映射到指定位置,實現頁面切換的效果。
  之前定義基本頁面時,已經簡單應用了 router。

(2)代碼如下:
  根據路徑可以進行路由匹配,也可根據 name 屬性去定位路由。
其中:
  component 採用路由懶載入的形式( () => import() ),路由被訪問時再載入。
  path: '/' 表示項目根路徑。
  redirect 表示跳轉到另一個路由。
  name: "Login" 表示路由名,可以根據 name 定位路由。
  path: "*" 表示全匹配,一般寫在路由規則的最後一個(用於路徑不存在時跳轉到一個指定頁面)。

【基本路由:】
    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

 

3、導航守衛、路由元信息

(1)簡介
  導航守衛適用於 路由變化時。即當路由變化時,會觸發導航守衛。
  路由元信息 可以用於定義路由獨有的信息(meta)。
註:
  同一個組件切換時,參數改變不會觸發導航守衛(復用組件)。可以通過 watch 監聽 $route 對象的變化來定義導航守衛,或者 直接使用 beforeRouteUpdate 來進行導航守衛(組件內守衛)。

 

(2)全局前置守衛(beforeEach)
  使用 beforeEach 可以定義一個全局前置守衛,路由跳轉前會觸發。
其有三個參數:
  to:一個路由對象,表示即將進入的 目標路由對象。
  from:一個路由對象,表示當前路由 離開時的路由對象。
  next:一個方法(不能少,確保路由能夠跳轉出去。
    next() 表示執行下一個守衛規則,若所有規則執行完畢,則結束並跳轉到指定路由。
    next({path: ''}) 或者 next({name: ''}) 表示指定路徑跳轉。

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

 

(3)路由元信息
  定義路由規則時,可以通過 meta 指定路由的元信息。
  通過 router對象.meta 可以獲取到某個 router對象 的 meta 信息,並根據其進行處理。

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 請求

1、簡介

  前面封裝了 axios,併在 main.js 中全局掛載,所以在組件中可以使用 $http 進行訪問。
  但是每次請求的相關處理都會寫在各個組件中,代碼看上去不太美觀,且不易維護。
  所以可以將請求根據功能、模塊進行劃分,並寫在固定位置,在組件中引入這些模塊即可。

2、代碼實現

(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)頁面顯示:

 

 

 

三、使用 iframe 標簽嵌套頁面

1、簡單瞭解一下

(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>

 

 

 

 

 

 

2、項目中使用

(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(() => {
        // 移除渲染後,再重新添加標

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 新聞 更方便、更強大: Flutter package 生態系統更新 教程 Fish-Lottie:純Dart如何實現一個高性能動畫框架? 插件 frefresh Help you to build pull-down refresh and pull-up loading in the simpl ...
  • 工作流引擎的測試容器-功能-使用方法-註意事項 關鍵字 Ccbpm, ccflow,jflow,工作流引擎,工作流引擎測試容器,表單引擎 功能說明 工作流的測試容器是為瞭解決手工模擬人工登錄模式下測試繁瑣的問題,而開發的一個測試功能,原來手工測試的按鈕仍然保留。 手工測試是通過輸入用戶名密碼的方式登... ...
  • 一、數組 1.數組是一種引用數據類型,屬於對象 2.數組的存儲性能比普通對象要好,在開發中我們經常使用數組來存儲一些數據。 3.創建數組的方式:(1)使用Array構造函數; i.創建一個空數組 var arr1 = new Array(); ii.創建一個長度為30的數組 var arr2 = n ...
  • BFC的定義 BFC到底是個啥呢?先貼出一段大佬們在分析BFC的時候字面上的理解: 在CSS佈局中,是通過對一個個box的佈局,來實現整體頁面的佈局,這一個個box也就是一個個容器元素,這些元素分為兩類:塊級元素(block),行內元素(inline)。 對於不同類型的元素,有不同的處理規則,這個元 ...
  • # 5.標簽篇:audio和video - audio 和 video ```html <style> * { margin:0; padding:0; } .video_player{ position:relative; width:1000px; height:500px; margin:0  ...
  • # 4.標簽篇:svg - SVG:矢量圖,放大不會失真,適合大面積的貼圖,通常動畫較少或較簡單。使用標簽和CSS去畫 canvas:適合用於小面積的繪圖,適合動畫 ```html <style> .line1{ stroke:black; stroke-width:10px; } .line2{ ...
  • 腳本用於在Chrome的Console內定時刷新網頁 timeout=prompt("Set timeout (Second):"); count=0 current=location.href; if(timeout>0) setTimeout('reload()',1000*timeout); ...
  • 上周某個普通的一天,deno 1.0發佈了。於是利用業餘時間寫了一個服務端的程式。 GIT:https://github.com/shinku/deno-demokoa 之於 node,相當於oak就之於deno了。 雖然是奇怪的設定,不過僅僅換了個字母順序而已,koa 和 oak 就是一個同父異母 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...