如何在Vue.js中实现标签页组件详解


Posted in Javascript onJanuary 02, 2019

前言

标签页组件,即实现选项卡切换,常用于平级内容的收纳与展示。

因为每个标签页的内容是由使用组件的父级控制的,即这部分内容为一个 slot。所以一般的设计方案是,在 slot 中定义多个 div,然后在接到切换消息时,再显示或隐藏相关的 div。这里面就把相关的交互逻辑也编写进来了,我们希望在组件中处理这些交互逻辑,slot 只单纯处理业务逻辑。这可以通过再定义一个 pane 组件来实现,pane 组件嵌在 tabs 组件中。

1 基础版

因为 tabs 组件中的标题是在 pane 组件中定义的,所以在初始化或者动态变化标题时,tabs 组件需要从 pane 组件中获取标题。

html:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>标签页组件</title>
 <link rel="stylesheet" type="text/css" href="index.css">
</head>
<body>
<div id="app" v-cloak>
 <tabs v-model="activeIndex">
 <pane label="科技">
 火星疑似发现“外星人墓地”?至今无法解释
 </pane>
 <pane label="体育">
 全美沸腾!湖人队4年1.2亿迎顶级后卫,詹姆斯:有他就能夺冠
 </pane>
 <pane label="娱乐">
 阿米尔汗谈中国武侠 想拍印度版《鹿鼎记》
 </pane>
 </tabs>
</div>
<script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script>
<script src="tabs.js"></script>
<script>
 var app = new Vue({
 el: '#app',
 data: {
 activeIndex: 0
 }
 });
</script>
</body>
</html>

pane 组件:

Vue.component('pane', {
 name: 'pane',
 template: '\
 <div class="pane" v-show="isShow">\
 <slot></slot>\
 </div>\
 ',
 props: {
 //标题
 label: {
 type: String,
 default: ''
 }
 },
 data: function () {
 return {
 //显示或隐藏
 isShow: true
 }
 },
 methods: {
 //通知父组件,更新标题
 init() {
 this.$parent.init();
 }
 },
 watch: {
 //当 label 值发生变化时,更新标题
 label() {
 this.init();
 }
 },
 //挂载时,更新标题
 mounted() {
 this.init();
 }
});

在 pane 组件中,我们做了以下设计:

  • 因为 pane 组件需要控制标签页内容的显示与隐藏,所以我们在 data 中定义了一个 isShow,并用 v-show 指令来控制内容的显示或隐藏。当点击这个 pane 所对应的标签页标题时,它的 isShow 被设置为 true。
  • 我们需要一个标识来识别不同的标签页标题,本示例用的是 pane 组件定义顺序的索引。
  • 在 props 中定义了 label,用于存放标题。因为 label 可以动态变化,所以必须在挂载 pane 以及当 label 值发生变化(通过监听实现)时,通知父组件,重新初始化标题。因为 pane 是独立组件,所以这里使用了 this.$parent 来调用父组件 tabs 的初始化方法。

tabs 组件:

Vue.component('tabs', {
 template: '\
 <div class="tabs">\
 <div class="tabs-bar">\
 <!-- 标签页标题-->\
 <div :class="tabClass(item)"\
 v-for="(item, index) in titleList"\
 @click="change(index)">\
 {{ item.label }}\
 </div>\
 </div>\
 <div class="tabs-content">\
 <!-- pane 组件位置-->\
 <slot></slot>\
 </div>\
 </div>',
 props: {
 value: {
 type: [String, Number]
 }
 },
 data: function () {
 return {
 currentIndex: this.value,
 titleList: []//存放标题
 }
 },
 methods: {
 //设置样式
 tabClass: function (item) {
 return ['tabs-tab', {
 //为当前选中的 tab 添加选中样式
 'tabs-tab-active': (item.name === this.currentIndex)
 }]

 },
 //获取定义的所有 pane 组件
 getTabs() {
 return this.$children.filter(function (item) {
 return item.$options.name === 'pane';
 })
 },
 //更新 pane 是否显示状态
 updateIsShowStatus() {
 var tabs = this.getTabs();
 var that = this;
 //迭代判断并设置某个标签页是显示还是隐藏状态
 tabs.forEach(function (tab, index) {
 return tab.isShow = (index === that.currentIndex);
 })
 },
 //初始化
 init() {
 /**
 * 初始化标题数组
 */
 this.titleList = [];
 var that = this;//设置 this 引用
 this.getTabs().forEach(function (tab, index) {
 that.titleList.push({
  label: tab.label,
  name: index
 });

 //初始化默认选中的 tab 索引
 if (index === 0) {
  if (!that.currentIndex) {
  that.currentIndex = index;
  }
 }
 });

 this.updateIsShowStatus();
 },
 //点击 tab 标题时,更新 value 值为相应的索引值
 change: function (index) {
 var nav = this.titleList[index];
 var name = nav.name;
 this.$emit('input', name);
 }
 },
 watch: {
 //当 value 值发生改变时,更新 currentIndex
 value: function (val) {
 this.currentIndex = val;
 },
 //当 currentIndex 值发生改变时,更新 pane 是否显示状态
 currentIndex: function () {
 this.updateIsShowStatus();
 }
 }
});
  • getTabs() 中通过 this.$children 来获取定义的所有 pane 组件。因为很多地方都会用到getTabs() ,所以这里把它单独定义出来。
  • 注意: methods 中如果存在回调函数,那么需要在外层事先定义一个 var that = this;,在 that 中引用 Vue 实例本身,也可以使用 ES2015 的箭头函数。
  • 在初始化方法中,我们通过迭代 pane 组件,初始化了标题数组,label 取定义的标题,name 取所在的索引。 标题数组用于模板定义中。
  • updateIsShowStatus() 用于更新 tab 是否显示状态。之所以独立出来,是为了在监听 currentIndex 发生变化时,也能调用该方法。
  • 在模板定义中,我们使用 v-for 指令渲染出标题,并绑定了 tabClass 函数,从而实现了动态设置样式。因为需要传参,所以不能使用计算属性。
  • 点击每一个 tab 标题时,会触发 change(),来更新 value 值为相应的索引值。在 watch 中,我们监听了 value 值,当 value 值发生改变时,更新 currentIndex。也监听了 currentIndex 值,当 currentIndex 值发生改变时,更新 pane 是否显示状态。

总结如下:

  • 使用组件嵌套方式,将多个 pane 组件作为 tabs 组件的 slot。
  • tabs 组件与 pane 组件,通过父子链(即 $parent 与 $children)实现通信。

样式:

[v-cloak] {
 display: none;
}

.tabs {
 font-size: 14px;
 color: #657180;
}

.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: #fff;
 border: 1px solid #d7dde4;
 cursor: pointer;
 position: relative;
}

.tabs-tab:hover {
 color: #336699;
 font-weight: bolder;
}

.tabs-tab-active {
 color: #336699;
 border-top: 1px solid #336699;
 border-bottom: 1px solid #fff;
}

.tabs-tab-active:before {
 content: '';
 display: block;
 height: 1px;
 background: #3399ff;
 position: absolute;
 top: 0;
 left: 0;
 right: 0;
}

.tabs_content {
 padding: 8px 0;
}

.pane {
 margin-top: 26px;
 font-size: 16px;
 line-height: 24px;
 color: #333;
 text-align: justify;
}

效果:

如何在Vue.js中实现标签页组件详解

2 关闭属性

我们为 pane 组件新增一个 closable 属性,用于控制该标签是否可关闭。

在子窗口组件的 props 中,新增 closable 属性:

props: {
	...
	//是否可关闭
	closable: {
		type: Boolean,
		default: false
	}
}

在标签页组件中的模板中,新增关闭标签:

...
template: '\
<div class="tabs">\
	<div class="tabs-bar">\
		<!-- 标签页标题-->\
		<div :class="tabClass(item)"\
			v-for="(item, index) in titleList"\
			@click="change(index)">\
			{{ item.label }}\
			<span v-if="item.closable" class="close" @click="close(index,item.name)"></span>\
			</div>\
		</div>\
		<div class="tabs-content">\
		 <!-- pane 组件位置-->\
			<slot></slot>\
		</div>\
	 </div>',
...
  • 这里使用 v-if 指令,根据 closable 的值来判断是否构建 “关闭” 标签。
  • 点击事件绑定了 close() 函数,传入标签所在索引以及标签的名称。

在标签页组件中的方法中,新增了 close(),用于执行关闭标签页逻辑:

close: function (index, name) {
 //删除对应的标题元素
	this.titleList.splice(index, 1);

	var tabs = this.getTabs();
	var that = this;
	//迭代判断并设置点击的标签页是隐藏状态
	tabs.forEach(function (tab, index) {
		if (index === name) {
			return tab.isShow = false;
		}
	});
}
  • 首先在标题数组中删除对应的标题元素,因为 Vue.js 的核心是数据与视图的双向绑定。因此当我们修改数组时, Vue.js 就会检测到数组发生了变化,所以用 v-for 渲染的视图也会同步更新 。
  • 接着,隐藏对应的 tab 内容,我们通过传入的 name 与某个 tab 中的 index,逐一比对,如果确定是我们需要关闭的标签页,那么就隐藏其内容。其实这里使用 key 来表达更合适。

新增的样式:

.close{
 color: #FF6666;
}
.close::before {
 content: "\2716";
}

.close:hover {
 color: #990033;
 font-weight: bolder;
}

为需要添加关闭标签的 pane ,添加 closable 属性:

<div id="app" v-cloak>
 <tabs v-model="activeIndex">
 <pane label="科技" closable="true">
 火星疑似发现“外星人墓地”?至今无法解释
 </pane>
 <pane label="体育">
 全美沸腾!湖人队4年1.2亿迎顶级后卫,詹姆斯:有他就能夺冠
 </pane>
 <pane label="娱乐" closable="true">
 阿米尔汗谈中国武侠 想拍印度版《鹿鼎记》
 </pane>
 </tabs>
</div>

效果:

如何在Vue.js中实现标签页组件详解

3 切换动画

我们在切换标签页时,加上滑动动画吧,这很简单,只要在激活的样式中加上 transform 与 transition 样式即可:

.tabs-tab-active {
 color: #336699;
 border-top: 1px solid #336699;
 border-bottom: 1px solid #fff;
 transform:translateY(-1px);
 transition: transform 0.5s;
}

效果:

如何在Vue.js中实现标签页组件详解

我们让标签页标题被点击时,以动画的形式往上移动 1 个像素。是不是很酷呀O(∩_∩)O~

本文示例代码

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JavaScript中的Location地址对象
Jan 16 Javascript
javascript整除实现代码
Nov 23 Javascript
jquery动画4.升级版遮罩效果的图片走廊--带自动运行效果
Aug 24 Javascript
jQuery function的正确书写方法
Aug 02 Javascript
String.prototype实现的一些javascript函数介绍
Nov 22 Javascript
简述JavaScript提交表单的方式 (Using JavaScript Submit Form)
Mar 18 Javascript
Bootstrap每天必学之级联下拉菜单
Mar 27 Javascript
vue.js中$watch的用法示例
Oct 04 Javascript
react.js 翻页插件实例代码
Jan 19 Javascript
如何使用JS在HTML中自定义字符串格式化
Jul 20 Javascript
Vue通过URL传参如何控制全局console.log的开关详解
Dec 07 Javascript
js将键值对字符串转为json字符串的方法
Mar 30 Javascript
如何使用less实现随机下雪动画详解
Jan 02 #Javascript
详解Vue2 添加对scss的支持
Jan 02 #Javascript
详解@Vue/Cli 3 Invalid Host header 错误解决办法
Jan 02 #Javascript
JS中数据结构之栈
Jan 01 #Javascript
微信小程序自定义导航栏
Dec 31 #Javascript
JavaScript ES6中的简写语法总结与使用技巧
Dec 30 #Javascript
JavaScript ES6箭头函数使用指南
Dec 30 #Javascript
You might like
一个目录遍历函数
2006/10/09 PHP
PHP+JS三级菜单联动菜单实现方法
2016/02/24 PHP
PHP实现防止表单重复提交功能【基于token验证】
2018/05/24 PHP
innertext , insertadjacentelement , insertadjacenthtml , insertadjacenttext 等区别
2007/06/29 Javascript
可实现多表单提交的javascript函数
2007/08/01 Javascript
不要小看注释掉的JS 引起的安全问题
2008/12/27 Javascript
JS异常处理的一个想法(sofish)
2013/03/14 Javascript
js获得页面的高度和宽度的方法
2014/02/23 Javascript
Jquery组件easyUi实现选项卡切换示例
2016/08/23 Javascript
vue多级多选菜单组件开发
2020/09/08 Javascript
webpack3之loader全解析
2017/10/26 Javascript
Node.js使用MySQL连接池的方法实例
2018/02/11 Javascript
浅谈HTTP 缓存的那些事儿
2018/10/17 Javascript
js实现多张图片每隔一秒切换一张图片
2019/07/29 Javascript
使用Vue.js中的过滤器实现幂方求值的方法
2019/08/27 Javascript
解决layer弹出层自适应页面大小的问题
2019/09/16 Javascript
Vue 3自定义指令开发的相关总结
2021/01/29 Vue.js
[04:11]DOTA2上海特级锦标赛主赛事首日TOP10
2016/03/03 DOTA
python mysqldb连接数据库
2009/03/16 Python
在Python中使用列表生成式的教程
2015/04/27 Python
python snownlp情感分析简易demo(分享)
2017/06/04 Python
python 字符串和整数的转换方法
2018/06/25 Python
详解numpy的argmax的具体使用
2019/05/27 Python
Python操作远程服务器 paramiko模块详细介绍
2019/08/07 Python
Python读写文件模式和文件对象方法实例详解
2019/09/17 Python
Python实现图片裁剪的两种方式(Pillow和OpenCV)
2019/10/30 Python
Python unittest如何生成HTMLTestRunner模块
2020/09/08 Python
美国CVS药店官网:CVS Pharmacy
2018/07/26 全球购物
生物化工专业个人自荐信
2013/09/26 职场文书
文案策划求职信
2014/04/14 职场文书
机关保密承诺书
2014/06/03 职场文书
运动会演讲稿300字
2014/08/25 职场文书
赵氏孤儿观后感
2015/06/09 职场文书
2015入党自传格式范文
2015/06/26 职场文书
小学四年级作文之写景
2019/08/23 职场文书
jackson json序列化实现首字母大写,第二个字母需小写
2021/06/29 Java/Android