Vue3 uses parent-child component communication to implement page tab function

1. General process

2. Vue3 knowledge used

1. Component communication

(1) Father to son

In vue3, parent components use binding and props to pass values ​​to child components.

Because the array of tabs needs to be placed in the parent page,


  data(){
    return {
      tabs: []
    }
  },

So the top bar needs to get the tab array from the parent page

First define props in the tab page to receive

  props:{
    tabs: Array // 声明一个 props,指定数据类型为数组
  },

Then bind it with the :binding character in the child page tag in the parent page

  <NavBar :tabs="tabs" ></NavBar>

In this way, the tab array of the parent page can be passed to the child page.

(2) The Son gives to the Father

Because there is a route in the child page to jump to a new page, you need to add a tab, that is, add the new page as a tab to the tab array, and the tab array is placed in the parent page, so the child needs to pass the value to the parent.

The child transfers the value to the parent by calling a method using this.$emit("communication name", data)

For example, the operation of adding a tab here is

     this.$emit("addtab",tab)

Then use @ in the child tag of the parent page to accept the communication name and bind the method to be called,

 <router-view  @addtab="addTab"></router-view>

 At the same time, pass the data into the method as the data parameter.

    addTab(data) {
      //最简单的push操作,还没完成其它逻辑
      this.tabs.push(data);

    }

3. Implement the overall logic

1. In the parent page

(1) Write the logic for adding tabs
    addTab(data) {
      // this.tabs.push(data);
        // 判断是否已存在相同的 title 和 route
        const exists = this.tabs.some(tab => tab.title === data.title && tab.route === data.route);
        if (!exists) {
          this.tabs.forEach(tab => {
            tab.selected = false;
          });
          this.tabs.push(data);
        }else{
          this.tabs.forEach(tab => {
            tab.selected = tab.title === data.title && tab.route === data.route;
          });
        }

        // 更新浏览器缓存
      this.saveTabsToLocalStorage()
    }
(2) Write the logic for closing the tab
    closeTab(index) {
      this.tabs.splice(index, 1); // 从数组中移除页签

      if (this.tabs.length > 0) {
        this.tabs.forEach(tab => {
          tab.selected = false;
        });
        // 如果还有其他选项卡,跳转到最后一个选项卡的路由
        const lastTab = this.tabs[this.tabs.length - 1];
        this.$router.push(lastTab.route);
        this.tabs[this.tabs.length - 1].selected=true;
      } else {
        // 如果没有选项卡了,跳转到默认的首页路由
        this.$router.push("/1/C");
      }

      // 更新浏览器缓存
      this.saveTabsToLocalStorage()
    },
(3) Caching the tab array to the browser and loading it from the cache
 mounted() {
    this.loadTabsFromLocalStorage();
  },
  methods:{
    // 缓存到本地
    saveTabsToLocalStorage() {
      localStorage.setItem('tabs', JSON.stringify(this.tabs));
    },
    // 从缓存加载
    loadTabsFromLocalStorage() {
      const storedTabs = localStorage.getItem('tabs');
      if (storedTabs) {
        this.tabs = JSON.parse(storedTabs);
      }
    },
  }

Cache the tab data to the browser. When the page is refreshed, the tab status retains the current status and will not be cleared.

(4) Communicate with the top bar
<NavBar :tabs="tabs" @asideCollapse="collapse" @closetab="closeTab">
(5) Communicate with subpages that need to generate tabs
<router-view  @addtab="addTab"></router-view>

2. Top bar

(1) Rendering tab
    <div class="top-bar">
      <!-- 渲染页签 -->
      <div
          v-for="(tab, index) in tabs"
          :key="index"
          :class="['tab', { 'selected': tab.selected }]"
          @click="switchTab(tab)"
      >
        {
   
   { tab.title }}
        <span class="close-btn" @click.stop="closeTab(index)">×</span>
      </div>
    </div>
(2) Write tab style
<style lang="scss" scoped>
 
.top-bar{
  display: flex;
  margin-left: 20px;
  caret-color: transparent; /*去除鼠标光标*/
  width: 100vw;
  overflow-x: auto; /* 允许横向滚动 */
  //overflow: hidden;

  div:hover{
       cursor:pointer;
    }

  div:not(:first-child){
    margin-left: 10px;
  }

  div{
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 5px;
    font-weight: 500;
    font-size: 14px;
    color: #606266;
    border: 1px solid #DCDFE6;
    border-radius: 4px;
    //width: 100%;
    height: 30px;

    white-space: nowrap; /* 防止内容换行 */
    span{
      width: 15px;
      height: 15px;
      margin-left: 4px;
      display: flex;
      align-items: center;
      justify-content: center;


    }


  }

  .tab{
    background-color: #eeeeee;

    span:hover{
      background: linear-gradient(rgba(96, 98, 102, 0.1), rgba(96, 98, 102, 0.1)); /* 在悬停时更改透明度 */
    }
  }

  .selected{
    background-color: #c6fce5;

  }
}

</style>
(3) Accept parent page data
  props:{
    tabs: Array // 声明一个 props,指定数据类型为数组
  },
(4) Send a request to close the tab to the parent page
    // 关闭页签
    closeTab(index) {
      this.$emit("closetab",index)
    },

3. Sub-page

(1) Send a request to add a tab to the parent page
  methods:{
   addTab(tab){
     this.$emit("addtab",tab)
   }

  }
 (2) Label binding requests with jump routing requirements

 For example, menu items

        <el-menu-item index="/1/C" 
        @click="addTab({
        title: '模拟计算', // 页面标题
        route: '/1/C', // 路由
        selected: true // 设置选中状态
        })"
        >

4. Display effect

5. Possible errors

1. It runs without errors in the local environment and will be reported after being deployed to the production environment.

TypeError: Cannot read properties of null (reading 'insertBefore')的错误

solution 

(1) The v-for rendering tabs array in the NavBar top bar component does not determine whether the tabs are empty.
(2) Switch vue to version ([email protected]) to fix it.
npm i [email protected]

Guess you like

Origin blog.csdn.net/qq_53478650/article/details/132397198