最终由于公司业务的需求,有一个新功能操作,就是双击选项卡头部让其变成可编辑的状态,点击其它区域则取消输入框输入,最终效果如下:
双击选择:
点击其它区域选择时:
1.创建文件:
在src/components下创建editTab,里面包含着editTabs,Tab.vue,还有index.js
2.明确组件需求和编写规则
我们要考虑这个组件给其它模块应该怎么使用,那么我这里就约定一下的使用规则:
<editTabs :value="currentValue" :edit="isEdit">
<Tab label="字典1" name="first">
字典一的内容
</Tab>
<Tab label="字典2" name="second">
字典二的内容
</Tab>
<Tab label="字典3" name="third">
字典三的内容
</Tab>
</editTabs>
export default {
name: "home",
components: {
},
data () {
return {
currentValue: "first",
isEdit:true
}
},
methods: {
}
};
以上的代码是组件使用的规范,通过示例我们可以知道一下几点。
1.需要给editTabs组件传入一个value让它默认显示第几个tab标签和需要传递一个edit属性激活它可编辑的状态。而且我们看到editTabs组件中是有内容编写的,所以我们需要在editTabs组件内部编写默认的插槽。
2.我们再来看Tab组件,这个组件需要传递两个参数一个是label和name,label是用来显示出标题的名称而它的name则是对应该Tab唯一的name值用于父组件editTabs中的value绑定。而且Tab组件中也需要插槽来展示内容。
3.编写editTabs和Tab组件代码
editTabs.vue
<template>
<div class="edit-tab">
<div class="edit-tab-bar">
<div :class="tabCls(item)"
v-for="(item,index) in navList"
:key="item.name || index">
<input v-model="item.label"
v-show="item.edit"
size="mini"
ref="input" />
<!-- {{item.label}} -->
<span v-show="!item.edit">
{{item.label}}
</span>
<i class="el-icon-close"
v-show="!item.edit">x</i>
</div>
</div>
<div class="edit-tab-content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "editTab",
props: {
value: {
type: [String, Number]
}
},
data () {
return {
navList: [], // 存放子组件Tab的label和name
currentValue: this.value // 当前默认选择tab
}
}
}
</script>
<style lang="scss" scope>
.edit-tab {
width: 100%;
position: relative;
}
.edit-tab-wrapper32 {
padding: 0 32px;
}
.edit-tab-bar-scroll {
overflow: hidden;
}
.edit-tab__nav-prev {
width: 32px;
text-align: center;
position: absolute;
line-height: 44px;
cursor: pointer;
left: 0;
}
.edit-tab__nav-next {
width: 32px;
text-align: center;
position: absolute;
line-height: 44px;
cursor: pointer;
right: 0;
}
.edit-tab-bar {
white-space: nowrap;
position: relative;
transition: transform 0.3s;
z-index: 2;
border: 1px solid #e4e7ed;
border-bottom: none;
border-radius: 4px 4px 0 0;
box-sizing: border-box;
}
.edit-tab-item {
padding: 0 20px;
height: 40px;
box-sizing: border-box;
line-height: 40px;
display: inline-block;
list-style: none;
font-size: 14px;
font-weight: 500;
color: #303133;
position: relative;
border-bottom: 1px solid #e4e7ed;
border-left: 1px solid #e4e7ed;
transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
cursor: pointer;
.el-icon-close {
position: relative;
font-size: 12px;
width: 0;
height: 14px;
vertical-align: middle;
line-height: 15px;
overflow: hidden;
top: -1px;
right: -2px;
transform-origin: 100% 50%;
border-radius: 50%;
text-align: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
margin-left: 5px;
}
&:hover {
.el-icon-close {
width: 12px;
}
}
&:first-child {
border-left: none;
}
&..edit-tab-item-active {
border-bottom-color: #fff;
color: #409eff;
.el-icon-close {
width: 12px;
}
}
}
.el-icon-close:hover {
background: #dddddd;
color: #fff;
}
</style>
上面的代码先确定所有样式文件的编写和一个基本的html结构。后面如果没有修改则默认省略
注意:
再editTabs组件中的data属性中,我们定义了navList数据和currentValue,由于label属性和name是tab组件中传递则我们必须从组件中获取到label和name把它供给父组件中使用,在vue的生命周期中,子组件的加载是快于父组件的加载的,也就是说Tab组件中mounted是快于editTab组件的。那么在父组件editTab组件编写方法来获取子组件tab的label和name和定义tabcls方法来确定默认选中tab样式
editTab.vue中:
methods: {
//获取子组件下tab的数量
getTabs () {
const allTabPanes = findComponentsDownward(this, 'TabPane'); // 找到下方name为tabpane组件
return allTabPanes;
},
// 设置tabitem样式
tabCls (item) {
console.log(item);
return [
"edit-tab-item",
{
["edit-tab-item-active"]: item.name === this.currentValue
}
];
},
// 更新头部label和name
updateNav () {
this.navList = [];
this.getTabs().forEach((pane, index) => {
if (!pane.name) { // 没有name的容错判断
pane.name = index;
}
if (index === 0) {
if (!this.currentValue) {
this.currentValue = pane.name || index
}
}
this.navList.push({
label: pane.label,
name: pane.name || index,
edit: pane.edit
})
});
this.updateStatus();
// console.log(this.navList)
},
// 控制tab内容是否显示
updateStatus () {
const tabs = this.getTabs();
tabs.forEach(tab => tab.show = (tab.name === this.currentValue));
}
}
在editTab目录下建立一个utils.js,用来编写向下找组件的方法findComponentsDownward()
utils.js
// 根据当前组件向下找子组件
export const findComponentsDownward = (context, componentName) => {
return context.$children.reduce((components, child) => {
if (child.$options.name === componentName) components.push(child);
const foundChilds = findComponentsDownward(child, componentName);
return components.concat(foundChilds);
}, []);
}
Tab.vue
<template>
<div v-show="show">
<slot></slot>
</div>
</template>
<script>
export default {
name: "TabPane",
inject: ["TabsInstance"],
props: {
label: {
type: [String, Number]
},
name: {
type: [String, Number]
},
edit: {
type: [Boolean]
}
},
data () {
return {
show: true
}
},
methods: {
updateNav () {
// this.$parent.updateNave();
this.TabsInstance.updateNav();
}
},
mounted () {
this.updateNav();
},
watch: {
label () {
this.updateNav();
},
}
}
</script>
以上的用到的知识点有2个:
1.vue中的provider和inject方法使用,来实现父子组件的通信
2.es6中reduce中函数的使用。
如果一切顺利的话界面会出现以下截图
4.添加点击和双击事件
经过上方代码的处理,我们就不需要再去理会tabs组件的代码,它的使命已经完成了,它只需对父组件editTabs提供label,name,edit即可,后续可能还会加上disable等等属性。
现在来处理tab点击切换显示出不同的内容和切换active状态。
editTab.vue
<div class="edit-tab-bar">
<div :class="tabCls(item)"
v-for="(item,index) in navList"
:key="item.name || index"
@click="handleChange(item)"
>
<input v-model="item.label"
v-show="item.edit"
size="mini"
ref="input" />
<!-- {{item.label}} -->
<span v-show="!item.edit">
{{item.label}}
</span>
<i class="el-icon-close"
v-show="!item.edit">x</i>
</div>
</div>
// 点击切换内容和active状态
methods:{
handleChange (item) {
const nav = this.navList.find(v => v.name === item.name);
this.currentValue = nav.name;
this.$emit("tab-click", nav);
}
},
watch: {
value (val) {
this.currentValue = val;
},
currentValue () {
this.updateStatus();
}
}
我们通过vue中watch属性,观察currentValue是否会变化,变化则改变子组件内容显示与隐藏
添加双击事件
<div :class="tabCls(item)"
v-for="(item,index) in navList"
:key="item.name || index"
@click="handleChange(index)"
@dblclick="handDbClick(item)">
<el-input v-model="item.label"
v-show="item.edit"
@blur="blurFun(item)"
size="mini"
ref="input"
autofocus></el-input>
<!-- {{item.label}} -->
<span v-show="!item.edit">
{{item.label}}
</span>
<i class="el-icon-close"
@click="handClickDelFun(item)"
v-show="!item.edit"></i>
</div>
methods:{
// 双击
handDbClick (item) {
this.$set(item, "edit", true);
this.$emit("db-tab-click", item);
}
}
那么这个时候就就可以双击编辑tab元素了。以上的是为最基础可编辑tabs的代码,下面我们接着探讨和优化这个editTabs
5.解决数据过多tab滚动的问题
当我们外部元素的宽度很小的时候,我们期望就会有个滚动的两个按钮出现,那么我们需要在样式上先动点手脚。
在原有的edit-tab-bar外面套一层div类名为edit-tab-bar-scroll,并且给edit-tab-bar添加float:left的样式
<div class="edit-tab-bar-scroll" ref="navScroll">
<div class="edit-tab-bar" :style="navStyle" ref="nav">
<div :class="tabCls(item)"
v-for="(item,index) in navList"
:key="item.name || index"
@click="handleChange(item)"
@dblclick="handDbClick(item)">
<input v-model="item.label"
v-show="item.edit"
size="mini"
ref="input" />
<!-- {{item.label}} -->
<span v-show="!item.edit">
{{item.label}}
</span>
<i class="el-icon-close"
v-show="!item.edit">x</i>
</div>
</div>
</div>
<script>
export default {
data(){
return {
navList: [],
currentValue: this.value,
navStyle: {
transform: ""
},
}
}
}
</script>
我们要知道需要移动的元素是edit-tab-bar,那么我们就是用css3中transform