业务目标
目前业务中后台大概是这两种表现形式,在之后会具体区分
tab栏只剩固定项【type: 1】
tab栏只剩一项不删 【type: 2】
实现思路
布局
layout布局中拆出tabs这一栏,便于区分管理,不多赘述
核心缓存和数据维护
掘金里有很多hook教程,不多说明,除了不能ssr其他的完全碾压vuex,这里也没必要用pina,核心缓存和数据维护 用hook组合函数写法完全够
- 定义一个动态数组,存放路由信息,一个变量记录激活的路由
- 在hooks只需要做一件事,记录变更的值(缓存 + 本地) ,即为增,删,改,初始化
import { reactive } from "vue";
const state = reactive({
openTab: [], //所有打开的路由
activeIndex: "Dashboard", //默认面板
});
export function useTabRouter(){
/**
* @description: 添加tab
* @param {*} tabItem
* @return {*}
*/
const addTab = (tabItem) => {
state.openTab.push(tabItem)
localStorage.setItem('defipay_control_tabs', JSON.stringify(state.openTab))
}
/**
* @description: 移除tab
* @param {*} tabItem
* @return {*}
*/
const removeTab = (tabItem) => {
let index = 0
for (const option of state.openTab) {
if (option.name === tabItem.name) {
break;
}
index ++
}
state.openTab.splice(index, 1)
localStorage.setItem('defipay_control_tabs', JSON.stringify(state.openTab))
}
const setActiveTab =(tabName) => {
state.activeIndex = tabName
}
const reSetTabs = () => {
state.openTab =[]
state.activeIndex ="Home"
}
return {
state,
reSetTabs,
addTab,
removeTab,
setActiveTab
}
}
显示控制
这里主要是面向业务,一般权限要求不高,首页是默认打开页面,标签是不能删除的 ,如果首页有权限,那么这里tabs的标配是至少保留一项(当然这条逻辑可以删掉,无关大局)
用于区别类型
v-if="state.openTab.length !== 1"
v-if="item.name !== 'Home'"
视图页面
在页面中,我们主要处理细节
- 引入hooks,只依赖于hook数据
- 使用onBeforeRouteUpdate来监听路由在变化时时候新增路由,新增的路由是否存在
- 点击已存在标签项的跳转
- 删除标签页的联动
<template>
<div class="global-tabs">
<div
class="tab-item"
:class="{ active: item.name === state.activeIndex }"
v-for="(item, index) in state.openTab"
:key="index + item.name"
@click="tabClick(item)"
>
<n-tooltip trigger="hover">
<template #trigger>
<span class="ellipsis-text"> {{ item.title }}</span>
</template>
<span>{{ item.title }}</span>
</n-tooltip>
<n-icon
size="15"
class="iconfont"
:component="Close"
v-if="state.openTab.length !== 1"
@click.stop="removeAction(item)"
/>
</div>
</div>
</template>
<script setup>
import { useRouter, useRoute, onBeforeRouteUpdate } from "vue-router";
import { useTabRouter } from "@/hooks/useTabRouter";
import { Close } from "@vicons/ionicons5";
const { state, addTab, removeTab, setActiveTab } = useTabRouter();
const router = useRouter();
let route = useRoute();
restartTabs();
/**
* @description: 初始化后从本地拉取缓存记录
* @param {*}
* @return {*}
*/
function restartTabs() {
if (
localStorage.defipay_control_tabs &&
JSON.parse(localStorage.defipay_control_tabs)
) {
console.log(JSON.parse(localStorage.defipay_control_tabs), "restartTabs");
state.openTab = JSON.parse(localStorage.defipay_control_tabs);
setActiveTab(route.name);
} else {
addTab({
name: "Dashboard",
path: "/dashboard",
title: "面板",
active: false,
});
setActiveTab("Dashboard");
}
}
/**
* @description: 监听路由
* @param {*}
* @return {*}
*/
onBeforeRouteUpdate((to, from) => {
let flag = false;
for (const item of state.openTab) {
if (item.name === to.name) {
console.log("路由已存在", to.name);
setActiveTab(to.name);
flag = true;
break;
}
}
if (!flag) {
console.log("检测到新路由", to.name);
addTab({
name: to.name,
path: to.path,
title: to.meta.title,
active: false,
});
setActiveTab(to.name);
}
});
/**
* @description: 打开标签页
* @param {*} tab
* @return {*}
*/
const tabClick = (tab) => {
if (tab) {
router.push({ name: tab.name });
} else {
router.push({ name: "Dashboard" });
}
};
/**
* @description: 关闭标签页
* @param {*} tab
* @return {*}
*/
const removeAction = (tab) => {
removeTab(tab);
if (state.openTab && state.openTab.length >= 1) {
let activeRouteName = state.openTab[state.openTab.length - 1].name;
router.push({ name: activeRouteName });
} else {
router.push({ name: "Dashboard" });
}
};
</script>