最近学习了一下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" ] }
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>
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>
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>
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