练习 1 : 给 pane 组件新增 一个 prop: closable 的布尔值 , 来支持是否可 以 关 闭 这个 pane , 如
果开启 , 在 tabs 的标签标题上会有一个关 闭的 按钮。
效果图:
共有四个文件:index.html pane.js tabs.js style.css
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标签页组件</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app" v-vloak>
<tabs v-model="activeKey">
<pane label="标签一" name="1" :closable=true>
标签一的内容
</pane>
<pane label="标签二" name="2" :closable=true>
标签二的内容
</pane>
<pane label="标签三" name="3" :closable=true>
标签三的内容
</pane>
</tabs>
</div>
<script src="../../vue.min.js"></script>
<script src="pane.js"></script>
<script src="tabs.js"></script>
<script>
let app = new Vue({
el: "#app",
data() {
return {
activeKey: "1"
}
},
})
</script>
</body>
</html>
pane.js
Vue.component("pane", {
name: "pane",
template: "\<div class='pane' v-show='show'> \
<slot></slot> \
</div>",
data() {
return {
show: true
}
},
props: {
name: {
type: String
},
label: {
type: String,
default: ""
},
closable: {
type: Boolean
}
},
methods: {
updateNav () {
this.$parent.updateNav();
}
},
watch: {
label () {
this.updateNav();
}
},
mounted() {
this.updateNav();
},
})
tabs.js
Vue.component("tabs", {
template: "\
<div class='tabs'> \
<div class='tabs-bar'> \
<div \
:class='tabCls(item)' \
v-for='(item, index) in navLsit' \
@click='handleChange(index)'> \
{{ item.label }} \
<span v-show='item.closable' :class='closeCls(item.closable)' @click='closeTab(index, event)'>关闭</span> \
</div> \
</div> \
<div class='tabs-content'> \
<slot></slot> \
</div> \
</div>",
props: {
value: {
type: [String, Number, Boolean]
}
},
data() {
return {
currentValue: this.value,
navLsit: []
}
},
methods: {
tabCls(item) {
return [
"tabs-tab",
{
"tabs-tab-active": item.name === this.currentValue,
}
]
},
closeCls(isClose) {
return { "close-icon": isClose }
},
handleChange(index) {
let nav = this.navLsit[index];
let name = nav.name;
this.currentValue = name;
this.$emit("input", name);
this.$emit("on-click", name);
},
getTabs() {
return this.$children.filter(function (item) {
return item.$options.name === "pane"
})
},
updateNav() {
this.navLsit = [];
let _this = this;
this.getTabs().forEach((pane, index) => {
_this.navLsit.push({
label: pane.label,
name: pane.name || index,
closable: pane.closable
});
if (!pane.name) pane.name = index;
if (index === 0) {
if (!_this.currentValue) {
_this.currentValue = pane.name || index;
}
}
});
this.updateStatus();
},
updateStatus() {
let tabs = this.getTabs();
let _this = this;
tabs.forEach(tab => {
return tab.show = tab.name === _this.currentValue;
})
},
closeTab(index, event) {
event.stopPropagation();
this.navLsit.splice(index, 1);
if (index == 0 && this.navLsit.length > 0) {
this.currentValue = this.navLsit[0].name;
}else if(index > 0 && index < this.navLsit.length){
this.currentValue = this.navLsit[index].name;
}else if(index > 0 && index >= this.navLsit.length){
this.currentValue = this.navLsit[index - 1].name;
} else{
this.currentValue = '';
}
}
},
watch: {
value: function (val) {
this.currentValue = val;
},
currentValue: function () {
this.updateStatus();
}
}
})
style.css
[v-cloak] {
display: none;
}
.tabs {
font-size: 14px;
color: #657180;
}
.tabs-bar {
border-left: 1px solid #d7dde4;
}
.tabs-bar::after {
content: "";
display: block;
width: 100%;
height: 1px;
background: #d7dde4;
margin-top: -1px;
}
.tabs-tab {
display: inline-block;
padding: 4px 16px;
/* margin-right: 6px; */
background: #ffffff;
border: 1px solid #d7dde4;
cursor: pointer;
position: relative;
border-left: none;
}
.tabs-tab-active {
color: #3399ff;
border-top: 1px solid #3399ff;
border-bottom: 1px solid #ffffff;
}
.close-icon {
display: none;
}
.tabs-tab-active .close-icon {
display: inline-block;
}
.tabs-tab-active::before {
content: '';
display: block;
height: 1px;
background: #3399ff;
position: absolute;
top: 0;
left: 0;
right: 0;
}
.tabs-content {
padding: 8px 0;
}