内容简介:用过
用过 Element ui 库的童鞋肯定知道 <el-tabs> 组件,简单、好用、可以自定义标签页,不知道广大童鞋们在刚开始使用 <el-tabs> 组件的时候有没有想过它是如何实现的?我咋刚开始使用 <el-tabs> 组件的时候就有去想过,也想去实现一个超级简单的tabs选项卡组件,无奈当时功力不够,未能实现。最近的一个简单项目中正好要用到选项卡组件,由于项目简单也就没有使用任何第三方库,于是就自己动手写了个选项卡组件。
1、实现tabs选项卡组件的思考
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
<el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
<el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
<el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane>
</el-tabs>
问题:
- 如何根据
<el-tab-pane>来生成标签页? - 如何过滤
<el-tabs>组件中的子元素,使得在使用的时候只显示<el-tab-pane>,而不会显示其他组件或div之类的元素?
2、实现思路
- 想根据
<el-tab-pane>来生成标签页就需要使用到<slot>,使用<slot>用<template>的形式肯定是不行的,因为无法获取到<slot>的数量;使用<template>的形式行不通,那就只有使用 render函数 了 - 过滤
<el-tabs>组件中的子元素也需要使用render函数
3、代码实现
index.js
import PTabs from './PTabs';
import PTabPane from './PTabPane';
export default function tabsInstall(Vue) {
if(tabsInstall.installed){
return;
}
Vue.component('PTabs', PTabs);
Vue.component('PTabPane', PTabPane);
}
PTabs.vue
<script>
import PTabNav from './PTabNav';
export default {
name: "PTabs",
props: {
value: {
type: [String, Number],
default: ''
},
beforeClick: {
type: Function,
default(){
return function () {};
}
}
},
components: {
PTabNav
},
data(){
return {
pTabPanes: [],
currentName: this.value || 0
}
},
methods: {
addPane(pane){
this.pTabPanes.push(pane);
if(!this.currentName){
this.setCurrentName(this.pTabPanes[0].name);
}
},
removePane(pane){
let index = this.pTabPanes.indexOf(pane);
if(index > -1){
this.pTabPanes.splice(index, 1);
}
},
setCurrentName(name){
if(this.currentName !== name){
this.currentName = name;
this.$emit('input', name);
}
},
// 标签页点击事件
handTabNavClick(name, pane, e){
if(this.currentName === name || pane.disabled){
return;
}
let before = this.beforeClick();
if(before && before.then){
before.then(() => {
this.setCurrentName(name);
this.$emit('tabClick', pane, e);
})
}else{
this.setCurrentName(name);
this.$emit('tabClick', pane, e);
}
}
},
watch: {
value(newVal){
this.setCurrentName(newVal);
},
currentName(){
this.$nextTick(() => {
this.$refs.p_tab_nav.scrollToActiveTab();
});
}
},
render(h) {
let {$scopedSlots} = this;
let $default = $scopedSlots.default();
let qTabPanes = $default.map(item => {
/* 过滤<PTabs>xxx</PTabs>中传递的xxx内容。这里只接收<PTabPane>组件,因为我们需要根据<PTabPane>组件的数量来生成
* <PTabNav>组件,如果参差了其它节点则会导致不能正确生成<PTabNav>组件 */
if(item.componentOptions && item.componentOptions.tag === 'PTabPane'){
return item;
}
});
let qTab = h('PTabNav', {
props: {
// 将tab-pane传递给 <PTabNav>组件,<PTabNav>组件就知道要有多少个tab-item了
tabPanes: this.pTabPanes,
handTabNavClick: this.handTabNavClick
},
ref: 'p_tab_nav'
});
let qTabBody = h('div', {
staticClass: 'p-tabs_content'
}, qTabPanes);
console.log($default)
return h('div', {
staticClass: 'p-tabs'
}, [qTab, qTabBody]);
},
mounted() {
//console.log(this)
this.$nextTick(() => {
this.$refs.p_tab_nav.scrollToActiveTab();
});
}
}
</script>
<style lang="stylus">
.p-tabs{
.p-tabs_header{
position: relative;
margin-bottom: 15px;
&.is-scrollable{
padding-left: 20px;
padding-right: 20px;
}
}
.p-tabs_nav-prev,
.p-tabs_nav-next{
position: absolute;
top: 0;
width: 20px;
height: 100%;
display: none;
&::before{
position: absolute;
content: ' ';
font-size: 0;
line-height: 0;
width: 10px;
height: 10px;
top: 50%;
left: 50%;
border-top: 1px solid #eee;
border-left: 1px solid #eee;
margin: -5px 0 0 -5px;
}
cursor: pointer;
&.disabled{
cursor: default;
border-color: #aaa;
}
}
.p-tabs_nav-prev{
left: 0;
&:before{
transform: rotate(-45deg);
}
}
.p-tabs_nav-next{
right: 0;
&:before{
transform: rotate(135deg);
}
}
.p-tabs_header{
&.is-scrollable{
.p-tabs_nav-prev,
.p-tabs_nav-next{
display: block;
}
}
}
.p-tabs_nav-scroll{
overflow: hidden;
}
.p-tabs_nav-list{
position: relative;
float: left;
white-space: nowrap;
transition: transform .3s;
}
.p-tabs_nav-item{
display: inline-block;
height: 40px;
line-height: 40px;
padding: 0 20px;
color: #fff;
cursor: pointer;
&.active,
&:hover{
color: #ffb845;
}
&.disabled{
cursor: not-allowed;
color: #aaa;
&:hover{
color: #aaa;
}
}
}
.p-tabs_content{
position: relative;
overflow: hidden;
}
.p-tabs-pane{
color: #fff;
}
}
</style>
PTabPane.vue
<template>
<div class="p-tabs-pane" v-show="show">
<slot></slot>
</div>
</template>
<script>
export default {
name: "PTabPane",
props: {
label: {
type: String,
default: ''
},
name: {
type: [String, Number],
default: ''
},
disabled: {
type: Boolean,
default: false
}
},
data(){
return {
loaded: false
}
},
computed: {
show(){
if(this.$parent.currentName === this.name){
if(!this.loaded){
this.loaded = true;
}
return true;
}
return false;
}
},
watch: {
label(){
// label更新的时候强制更新父组件,以触发PTabNav才能更新
this.$parent.$forceUpdate();
}
},
mounted() {
// 当当前组件创建的时候将当前组件添加到父组件的pTabPanes中,以触发PTabNav才能更新
this.$parent.addPane(this);
},
destroyed() {
if(this.$el && this.$el.parentNode){
this.$el.parentNode.removeChild(this.$el);
}
// 当当前组件销毁时需从父组件中的pTabPanes中移除当前组件,以触发PTabNav才能更新
this.$parent.removePane(this);
}
}
</script>
PTabNav.vue
<script>
function noop() {};
export default {
name: "PTabNav",
props: {
tabPanes: {
type: Array,
default(){
return [];
}
},
handTabNavClick: {
type: Function,
default(){
return function () {};
}
}
},
data(){
return {
navPrevDisabled: true,
navNextDisabled: true,
// 控制左右箭头显示
scrollable: false,
listOffset: 0
}
},
methods: {
navPrevClickEvent(){
if(!this.navPrevDisabled){
let navScrollW = this.$refs.nav_scroll.offsetWidth;
let navListW = this.$refs.nav_list.offsetWidth;
let maxTransformX = 0;
let transformX = this.listOffset - navScrollW;
if(transformX < maxTransformX){
transformX = maxTransformX;
}
if(transformX === this.listOffset){
return;
}
console.log('上一页按钮点击了', transformX);
this.listOffset = transformX;
if(transformX === 0){
this.navPrevDisabled = true;
this.navNextDisabled = false;
}else if(transformX === (navListW - navScrollW)){
this.navPrevDisabled = false;
this.navNextDisabled = true;
}else{
this.navPrevDisabled = false;
this.navNextDisabled = false;
}
}
},
navNextClickEvent(){
if(!this.navNextDisabled){
let navScrollW = this.$refs.nav_scroll.offsetWidth;
let navListW = this.$refs.nav_list.offsetWidth;
let maxTransformX = navListW - navScrollW;
let transformX = this.listOffset + navScrollW;
if(transformX > maxTransformX){
transformX = maxTransformX;
}
if(transformX === this.listOffset){
return;
}
console.log('下一页按钮点击了', transformX);
this.listOffset = transformX;
if(transformX === 0){
this.navPrevDisabled = true;
this.navNextDisabled = false;
}else if(transformX === (navListW - navScrollW)){
this.navPrevDisabled = false;
this.navNextDisabled = true;
}else{
this.navPrevDisabled = false;
this.navNextDisabled = false;
}
}
},
// 计算 .p-tabs_nav-list 是否溢出
calculateListSpilled(){
let navScrollW = this.$refs.nav_scroll.offsetWidth;
let navListW = this.$refs.nav_list.offsetWidth;
if(navScrollW < navListW){
this.scrollable = true;
}else{
if(this.listOffset > 0){
this.listOffset = 0;
}
this.scrollable = false;
}
},
// 滚动条滚动到激活的tab
scrollToActiveTab(){
if(this.scrollable){
this.$nextTick(() => {
let navScrollW = this.$refs.nav_scroll.offsetWidth;
let navList = this.$refs.nav_list;
let activeTab = navList.querySelector('.active');
let activeTabOffsetLeft = 0;
if(activeTab){
activeTabOffsetLeft = activeTab.offsetLeft;
}
let transformX = activeTabOffsetLeft + activeTab.offsetWidth - navScrollW;
transformX = transformX < 0 ? 0 : transformX;
this.listOffset = transformX;
if(transformX === 0){
this.navPrevDisabled = true;
this.navNextDisabled = false;
}else if(transformX === (navList.offsetWidth - navScrollW)){
this.navPrevDisabled = false;
this.navNextDisabled = true;
}else{
this.navPrevDisabled = false;
this.navNextDisabled = false;
}
});
}
}
},
computed: {
listOffsetTran(){
console.log('dddd',`translateX(-${this.listOffset}px);`)
return {
transform: `translateX(-${this.listOffset}px)`
}
}
},
render(h) {
/*dom结构
<div class="p-tabs_header is-scrollable">
<span class="p-tabs_nav-prev disabled"></span>
<span class="p-tabs_nav-next"></span>
<div class="p-tabs_nav-scroll">
<div class="p-tabs_nav-list">
<div class="p-tabs_nav-item active">全部</div>
<div class="p-tabs_nav-item disabled">技术教学</div>
<div class="p-tabs_nav-item">新手教学</div>
</div>
</div>
</div>
*/
let navPrev = h('span', {
staticClass: 'p-tabs_nav-prev',
'class': {
disabled: this.navPrevDisabled
},
on: {
click: this.navPrevClickEvent
}
});
let navNext = h('span', {
staticClass: 'p-tabs_nav-next',
'class': {
disabled: this.navNextDisabled
},
on: {
click: this.navNextClickEvent
}
});
// 生成标签页
let navItems = this.tabPanes.map(item => {
let $labelSlot = item.$scopedSlots.label ? item.$scopedSlots.label() : null;
let labelContent = $labelSlot ? $labelSlot : item.label;
return h('div', {
staticClass: 'p-tabs_nav-item',
'class': {
active: this.$parent.currentName === item.name,
disabled: item.disabled,
},
on: {
click: (e) => {
this.handTabNavClick(item.name, item, e);
}
}
}, [labelContent]);
});
let navScroll = h('div', {
staticClass: 'p-tabs_nav-scroll',
ref: 'nav_scroll'
}, [
h('div', {
staticClass: 'p-tabs_nav-list',
ref: 'nav_list',
style: this.listOffsetTran
}, [navItems])
]);
return h('div', {
staticClass: 'p-tabs_header',
'class': {
'is-scrollable': this.scrollable
},
}, [navPrev, navNext, navScroll]);
},
updated(){
this.calculateListSpilled();
},
mounted() {
this.calculateListSpilled();
}
}
</script>
4、使用
main.js
// 引入tabs组件 import tabs from './components/p-tabs'; // 全局注册p-tabs组件 Vue.use(tabs);
页面中使用
<PTabs v-model="activeName">
<PTabPane label="用户管理" name="first">用户管理</PTabPane>
<PTabPane label="配置管理" name="second">配置管理</PTabPane>
<PTabPane label="角色管理" name="third">角色管理</PTabPane>
<PTabPane label="定时任务补偿" name="fourth">定时任务补偿</PTabPane>
</PTabs>
以上所述就是小编给大家介绍的《Vue render函数实战--实现tabs选项卡组件》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- javascript数组常用函数与实战总结
- 如何通过 JavaScript 编写高质量的函数(四):函数式编程之实战篇
- 【JS必知必会】高阶函数详解与实战
- 实战:小程序云开发之云函数开发
- Python 拓展之特殊函数(lambda 函数,map 函数,filter 函数,reduce 函数)
- Python 函数调用&定义函数&函数参数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First HTML and CSS
Elisabeth Robson、Eric Freeman / O'Reilly Media / 2012-9-8 / USD 39.99
Tired of reading HTML books that only make sense after you're an expert? Then it's about time you picked up Head First HTML and really learned HTML. You want to learn HTML so you can finally create th......一起来看看 《Head First HTML and CSS》 这本书的介绍吧!