vue造轮子之封装可编辑的tab选项卡

最终由于公司业务的需求,有一个新功能操作,就是双击选项卡头部让其变成可编辑的状态,点击其它区域则取消输入框输入,最终效果如下:
双击选择:
在这里插入图片描述
点击其它区域选择时:
在这里插入图片描述
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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值