頁面效果
需求分析
在導航條上,默認打開一個頁面(列表頁面),當點擊列表項,打開一個詳情頁面,需添加到標籤導航中,同時顯示當前標籤的頁面內容,繼續點擊列表項,添加標籤到導航中,依次類推,效果如上圖。
具體功能:
- 標籤可關閉;
- 標籤可切換,同時內容切換;
- 標籤大於可視範圍,需添加到選項卡列表中,可點擊切換;
- 當點擊選項卡列表中的標籤,需要在可視區中與最後一個標籤進行切換;
- 標籤的顯示個數自適應;
思考
在一些後臺系統中,見過類似的功能,是利用 iframe標籤添加鏈接的方式,實現這個功能。那麼在vue中怎麼去實現?
首先,這是一個單頁面網站應用,使用了vue-router進行前端路由,那麼我們可以使用 vue-router 的命名視圖,這種方式去代替iframe。
vue-router命名視圖官方代碼:
<router-view><router-view><router-view>
複製代碼
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
複製代碼
項目目錄大同小異,就不過多解釋了~~
路由文件-命名視圖
router代碼:
import Vue from "vue";import Router from "vue-router";import store from '../store';Vue.use(Router);// layout頁面const Layout = () => import(/* webpackChunkName: "layout" */ "@/views/layout/Layout.vue");// 產品相關頁面const Product = () => import(/* webpackChunkName: "product" */ "@/views/product/Index.vue");const ProductDetail = () => import(/* webpackChunkName: "productDetail" */ "@/views/product/Detail.vue");// 新聞相關頁面const News = () => import(/* webpackChunkName: "news" */ "@/views/news/Index.vue");const NewsDetail = () => import(/* webpackChunkName: "newsDetail" */ "@/views/news/Detail.vue");const rotuer = new Router({ mode: "history", base: process.env.BASE_URL, routes: [ { path: "/", name: "layout", component: Layout, redirect: "/product", children: [ { path: "/product", name: "product", meta: { title: "產品", tabName: "產品列表" }, components: { default: Product, detail: ProductDetail } }, { path: "/news", name: "news", meta: { title: "新聞", tabName: "新聞列表" }, components: { default: News, detail: NewsDetail } } ] } ]});rotuer.afterEach(to => { // 初始化navTag標籤 if (to.meta && to.meta.tabName) { store.dispatch('navTab/addTab', { tabName: to.meta.tabName, initStatus: true }) }});export default rotuer;
複製代碼
根據上面的功能分析,路由使用了命名視圖的方式,每個模塊的列表頁面有個name屬性,同時components定義了一個默認的頁面,和一個詳情頁面,如果還有其他頁面可以繼續添加到commontents中,如:
components: {
default: Product,
detail: ProductDetail,
create: ProductCreate
}
複製代碼
佈局文件
在layou.vue的文件中,對 navTabs(vuex中定義的標籤數組) 進行循環生成 <router-view> 標籤,這個 name 對應的是路由裡 components 屬性定義的key值,如:"detail"。
layou.vue的部分代碼:
<template> <el-row> <el-col> <header> /<el-col> <el-col> <menu> <el-main> <nav-tab><router-view>/<el-main> /<el-col> /<el-row> /<template>
複製代碼
主要使用<router-view>,顯示命名的組件。
使用vuex
我們來看看navTabs是什麼?將tab標籤數據放在了vuex的state裡,可以全局對標籤集合繼續操作控制,後面會貼出完整代碼。
以下是對navTab.js中的部分代碼說明:
1、state定義
const tabIdStr = "tab_"; // 標籤的id前綴
const state = {
tabs: [],
tabMaxNum: 0
};
複製代碼
tabIdStr變量是在新增標籤的時,防止與其他命名衝突
state對象:
- tabs屬性:保存標籤的數據
- tabMaxNum:最大可顯示的標籤數量(導航可視區)
2、新增標籤
/**
* 添加標籤
* @param {Any} tabId tab的唯一id標識
* @param {String} tabName tab的標題
* @param {String} vName 對應router命名視圖的名稱
* @param {Object} pParams 參數傳遞
* @param {Boolean} initStatus 初始化狀態
*/
addTab({ commit, state }, { tabId, tabName, vName, pParams, initStatus = false }) {
// 設置標籤id
let newTabId = tabIdStr + (tabId || 0); // 默認 0
let opts = {
tabName: "",
vName: "default",
pParams: {},
active: true,
...{
tabId: newTabId,
tabName,
vName,
pParams
}
};
// 初始化時,重置標籤
if (initStatus) {
commit("resetTabs");
}
// 判斷函數
let hasTabId = item => {
return item.tabId === newTabId;
};
// 判斷新增標籤是否已存在,如果存在直接激活,否則新增
if (state.tabs.some(hasTabId)) {
// 激活標籤
commit("activeTab", newTabId);
return false;
}
// 添加標籤
commit("addTab", opts);
},
複製代碼
這裡代碼主要注意的是:
// 初始化時,重置標籤
if (initStatus) {
commit("resetTabs");
}
複製代碼
例如在產品模塊中,切換到新聞模塊時,需重置tabs的數據,可以在router的afterEach方法中使用,路由部分代碼:
rotuer.afterEach(to => {
// 初始化navTag標籤
if (to.meta && to.meta.tabName) {
store.dispatch('navTab/addTab', {
tabName: to.meta.tabName,
initStatus: true
})
}
});
複製代碼
navTab.js完整代碼:
const tabIdStr = "tab_"; // 標籤的id前綴
const state = {
tabs: [],
tabMaxNum: 0
};
const getters = {
getNavTabs: state => state.tabs
};
const actions = {
/**
* 添加標籤
* @param {Any} tabId tab的唯一id標識
* @param {String} tabName tab的標題
* @param {String} vName 對應router命名視圖的名稱
* @param {Object} pParams 參數傳遞
* @param {Boolean} initStatus 初始化狀態
*/
addTab({ commit, state }, { tabId, tabName, vName, pParams, initStatus = false }) {
// 設置標籤id
let newTabId = tabIdStr + (tabId || 0); // 默認 0
let opts = {
tabName: "",
vName: "default",
pParams: {},
active: true,
...{
tabId: newTabId,
tabName,
vName,
pParams
}
};
// 初始化時,重置標籤
if (initStatus) {
commit("resetTabs");
}
// 判斷函數
let hasTabId = item => {
return item.tabId === newTabId;
};
// 判斷新增標籤是否已存在,如果存在直接激活,否則新增
if (state.tabs.some(hasTabId)) {
// 激活標籤
commit("activeTab", newTabId);
return false;
}
// 添加標籤
commit("addTab", opts);
},
/**
* 切換標籤
* @param {String} tabId tab的唯一id標識
*/
changeTab({ commit }, { tabId }) {
// 激活標籤
commit('activeTab', tabId);
},
/**
* 更多標籤處理
* @param {Number} index tabs數組下標
*/
handleMoreTab({ commit }, { index }) {
commit('handleMoreTab', index);
},
/**
* 刪除標籤
* @param {Number} index tabs數組下標
*/
deleteTab({ commit }, { index }) {
commit('deleteTab', index);
},
/**
* 刪除其他標籤
*/
deleteOtherTab({ commit, state }) {
// 保存第一個標籤
let firstTab = state.tabs[0];
// 如果第一個當前標籤是第一個,則直接刪除全部
if(firstTab.active) {
commit('deleteAllTab');
} else {
commit('deleteOtherTab');
}
},
/**
* 刪除全部標籤
*/
deleteAllTab({ commit }) {
commit('deleteAllTab');
}
};
const mutations = {
/**
* 添加標籤
*/
addTab(state, opts) {
// 隱藏其他標籤狀態
state.tabs.forEach(item => {
item.active = false;
});
// 當tabs數量大於或等於標籤的最大顯示數,新添加的標籤放在可顯示的最後一位
if(state.tabs.length >= state.tabMaxNum) {
state.tabs.splice(state.tabMaxNum - 1, 0, opts);
} else {
state.tabs.push(opts);
}
},
/**
* 激活標籤
*/
activeTab(state, tabId) {
state.tabs.forEach(item => {
item.active = false;
if (item.tabId === tabId) {
item.active = true;
}
});
},
/**
* 更多標籤處理
*/
handleMoreTab(state, index) {
let tabs = state.tabs;
let _index = state.tabMaxNum + index;
// 激活點擊標籤
tabs[_index].active = true;
// 拷貝點擊標籤
let copyTab = [tabs[_index]];
// 刪除點擊標籤
tabs.splice(_index, 1);
// 隱藏其他標籤
tabs.forEach(item => {
item.active = false;
});
// 插入到可顯示的標籤最後一個位置
tabs.splice([state.tabMaxNum - 1], 0, ...copyTab);
},
/**
* 刪除標籤
*/
deleteTab(state, index) {
let tabs = state.tabs;
// 判斷刪除的是當前標籤,需激活上一個標籤
if(tabs[index].active && tabs.length > 0) {
tabs[index -1].active = true;
}
tabs.splice(index, 1);
},
/**
* 刪除其他標籤
*/
deleteOtherTab(state) {
// 解構第一個標籤,其他標籤
let [firstTab, ...otherTabs] = state.tabs;
// 獲取當前標籤
let curTab = otherTabs.filter(item => item.active);
state.tabs = [firstTab, ...curTab];
},
/**
* 刪除全部標籤
*/
deleteAllTab(state) {
let tabs = state.tabs;
// 除了第一個標籤其他的都刪除
let firstTab = tabs[0];
firstTab.active = true;
state.tabs = [firstTab];
},
/**
* 重置標籤
*/
resetTabs(state) {
state.tabs = [];
},
/**
* 設置顯示標籤最大值
*/
setMaxTabVal(state, val) {
state.tabMaxNum = parseInt(val);
},
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
複製代碼
tps:navTab.js實現了標籤的新增、點擊切換、更多點擊切換、刪除等功能
NavTab組件
標籤的導航組件,對於標籤進行操作,使用navTab.js裡面的方法,對tabs數據進行增、刪、改、查~~
部分代碼:
<template>/<template>
複製代碼
這裡初始化的時候進行了,最大可視標籤數量的計算。
calcTabMaxNum方法,根據標籤導航的寬度和標籤的寬度進行計算,得到的值賦值給 state 裡的 tabMaxNum。
創建一個標籤頁面
handleRowClick(row) {
let { id, title, intro } = row;
this.$store.dispatch('navTab/addTab', {
tabId: 'detail_' + id,
tabName: title,
vName: 'detail',
pParams: {
title,
intro
}
})
}
複製代碼
調用標籤的 addTab 方法。
- tabId: 標籤的唯一Id標識
- tabName: 標籤導航的title值
- vName: 'detail' 就是指向路由裡面 components 定義的key值
- pParams: 需要傳遞到其他頁面的參數
怎麼獲取到傳遞過來的參數(pParams值)
在utils/index.js中定義的方法:
/**
* 獲取當前標籤的傳遞參數
* @param {Array} tabs 標籤數據
*/
export const getCurTabParams = (tabs) => {
if(!tabs || !Array.isArray(tabs)) return {};
// 查找當前標籤
let curTab = tabs.filter(item => {
return item.active;
});
return curTab.length > 0 ? curTab[0].pParams : {};
}
複製代碼
Detail.vue中的部分代碼,getCurTabParams方法的使用:
computed: {
...mapGetters({
navTabs: 'navTab/getNavTabs'
}),
tabParams() {
return getCurTabParams(this.navTabs) ? getCurTabParams(this.navTabs) : {};
}
},
複製代碼
基本功能實現完成,到這裡就結束了,第一次寫分析,思路有點亂,如果有錯誤的地方歡迎指正~~
附上項目地址:github.com/GuJiBao/vue…
閱讀更多 前端風雲 的文章