[Vue学习] 实现Tab选项卡效果, 动态增删Tab, 内容支持动态组件

最近学习了一下Vue, 尝试实现一个自定义Tab组件, 效果如下:

支持动态添加tab项, 内容支持放入动态组件, 模拟支持keep-alive

效果图:

目录结构:

1. 使用vue-cli创建脚手架项目
2. 在components中创建C1,C2,C3,MyTab四个自定义组件

package.json

{
  "name": "hello",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^2.6.11"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.2.2",
    "vue-template-compiler": "^2.6.11"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}
package.json

MyTab.vue

<script>
export default {
  name: 'MyTab',
  components: {},
  props: {
    tabs: {
      type: Array,
      required: true,
      default: function() {
        return []
      }
    },
    currentTab: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      current: this.currentTab || ''
    }
  },
  render: function(h) {
    var self = this
    return (
      <div class="tab-container">
        <ol class="title">
          {self.tabs.map(function(t) {
            return (
              <li
                class={{ active: self.isActive(t) }}
                onclick={() => {
                  self.current = t.title
                }}
              >
                {t.title}
              </li>
            )
          })}
        </ol>

        <ol class="content">
          {self.tabs.map(function(t) {
            if (t.props && t.props.keepAlive) {
              if (!self.isActive(t) && !t.isInit) {
                return ''
              } else {
                t.isInit = '1'
                return (
                  <keepAlive>
                    <li class={{ active: self.isActive(t), hide: !self.isActive(t) }}>
                      keep-live
                      <hr />
                      {h(t.com, { props: t.props })}
                    </li>
                  </keepAlive>
                )
              }
            } else {
              return self.isActive(t) ? <li class={{ active: self.isActive(t), hide: !self.isActive(t) }}>{h(t.com, { props: t.props })}</li> : ''
            }
          })}
        </ol>
      </div>
    )
  },
  methods: {
    isActive(tab) {
      var cur = this.current
      if (cur == '') {
        cur = this.tabs[0].title
      }
      return cur == tab.title
    }
  },
  computed: {},
  created() {
    console.info('MyTab:created')
  },
  mounted() {
    console.info('MyTab:mounted')
  },
  updated() {
    console.info('MyTab:updated')
  },
  beforeDestroy() {
    console.info('MyTab:beforeDestroy')
  },
  destroyed() {
    console.info('MyTab:destroyed')
  },
  activated() {
    console.info('MyTab:activated')
  },
  deactivated() {
    console.info('MyTab:deactivated')
  }
}
</script>
<style scoped>
ol,
li {
  padding: 0;
  margin: 0;
  list-style: none;
}

.tab-container {
  border: solid 0px #f60;
}

.tab-container .title li {
  padding: 5px 10px;
  background-color: cadetblue;
  display: inline-block;
  margin: 0 2px 1px 0;
  cursor: pointer;
}

.tab-container .title li:hover {
  background-color: rgb(72, 142, 145);
  color: #fff;
}

.tab-container .title li.active {
  background-color: rgb(43, 105, 107);
  color: #fff;
}

.tab-container .content li {
  padding: 5px 10px;
  border: solid 2px rgb(51, 141, 148);
}
</style>

C1.vue

<template>
  <p>{{ content }}</p>
</template>
<script>
export default {
  props: {
    content: {
      type: String,
      default: "none",
    },
  },
  data() {
    return {}
  },
}
</script>
<style scoped>
ol li {
  border: solid 1px #abc;
  padding: 5px 10px;
}
</style>
C1.vue

  

C2.vue

<template>
  <ol>
    <li v-for="tr in list" :key="tr">{{ tr }}</li>
  </ol>
</template>
<script>
export default {
  props: ["list"],
  data1() {
    return {
      list: [1, 2, 3, 4, 5, 6],
    }
  },
}
</script>
<style scoped>
ol li {
  border: solid 1px #c0c0c0;
  margin: 2px;
  padding: 5px 10px;
}
</style>
C2.vue

  

C3.vue

<template>
  <table>
    <tr v-for="(tr, idx) in table" :key="tr[0] + '_' + idx">
      <td v-for="(td, idx2) in tr" :key="td + '_' + idx + '_' + idx2">{{ td }}</td>
    </tr>
  </table>
</template>
<script>
export default {
  props: ["table"],
  data1() {
    return {
      table: [
        [1, 2, 3, 4, 5, 6],
        [2, 4, 5, 6, 7, 8],
        [3, 4, 5, 6, 7, 8],
      ],
    }
  },
}
</script>
<style scoped>
table {
  border: solid 1px #c0c0c0;
}

table tr td {
  padding: 5px 10px;
  border: solid 1px #c0c0c0;
}
</style>
C3.vue

App.vue

<template>
  <div id="app">
    <MyTab :tabs="tabs"></MyTab>
  </div>
</template>

<script>
//import HelloWorld from './components/HelloWorld.vue'
import MyTab from "@/components/MyTab.vue"

export default {
  name: "App",
  components: { MyTab },
  data() {
    return {
      tabs: [
        {
          title: "HelloWorld",
          com: this.$Com("HelloWorld"),
        },
        {
          title: "Tab",
          com: this.$Com("TabExample"),
          props: {
            keepAlive: true,
          },
        },
      ],
    }
  },
  methods: {},
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  margin: 20px;
  font-size: 14px;
}
</style>

TabExample.vue

<template>
  <div>
    <div class="opt">
      <button @click="tabAdd">AddTab</button>
      <button @click="tabRemove">RemoveTab</button>
      <button>{{ index }}</button>
    </div>
    <MyTab :tabs="tabs" v-on:tabChange="tabChange"> </MyTab>
  </div>
</template>
<script>
import MyTab from "@/components/MyTab.vue"

export default {
  components: { MyTab },
  data() {
    return {
      index: 3,
      tabs: [
        {
          title: "textTab",
          com: this.$Com("example/C1"),
          props: {
            content: "一段文字而已..",
          },
        },
        {
          title: "listTab",
          com: this.$Com("example/C2"),
          props: {
            list: [1, 2, 3, 4, 5, 6],
            keepAlive: true,
          },
        },
        {
          title: "tableTab",
          com: this.$Com("example/C3"),
          props: {
            table: [
              [1, 2, 3, 4, 5, 6],
              [2, 4, 5, 6, 7, 8],
              [3, 4, 5, 6, 7, 8],
            ],
            keepAlive: true,
          },
        },
      ],
    }
  },
  methods: {
    tabAdd() {
      this.tabs.push({
        title: "tableTab" + this.index++,
        com: this.$Com("example/C3"),
        props: {
          table: [
            [1 + this.index, 2, 3, 4, 5, 6],
            [7 + this.index, 8, 9, 10, 11, 12],
          ],
        },
      })
    },
    tabRemove() {
      if (this.index > 3) {
        this.tabs.splice(this.tabs.length - 1, 1)
        this.index--
        this.currentTab = this.tabs[0].title
      }
    },
    tabChange() {},
  },
  created() {
    console.info("TabExample:created")
  },
  mounted() {
    console.info("TabExample:mounted")
  },
  updated() {
    console.info("TabExample:updated")
  },
  beforeDestroy() {
    console.info("TabExample:beforeDestroy")
  },
  destroyed() {
    console.info("TabExample:destroyed")
  },
  activated() {
    console.info("TabExample:activated")
  },
  deactivated() {
    console.info("TabExample:deactivated")
  },
}
</script>

<style scoped>
.opt {
  margin: 10px 0;
}
.opt button {
  margin-right: 5px;
}
</style>

main.js

import Vue from "vue"
import App from "./App.vue"

Vue.config.productionTip = false

Vue.prototype.$Com = function(fileName) {
  return Vue.component(fileName, () => import("./components/" + fileName + ".vue"))
}

new Vue({
  render: (h) => h(App),
}).$mount("#app")

参考1: https://cn.vuejs.org/v2/guide/components-dynamic-async.html
参考2: https://cn.vuejs.org/v2/guide/render-function.html

原文地址:https://www.cnblogs.com/sanshizi/p/14691163.html