组合hooks+ tabs 持久化方案

309 阅读1分钟

业务目标

目前业务中后台大概是这两种表现形式,在之后会具体区分

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>