内容简介:用过
用过 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 函数调用&定义函数&函数参数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法Ⅰ~Ⅳ(C++实现):基础、数据结构、排序和搜索
Sedgewick / 高等教育出版社 / 2002-1 / 49.00元
本书通过C++实现方案以简洁、直接的方式对书中的算法和数据结构进行表述,并向学生提供在实际应用中验证这种方法的手段。 本书广泛地论述了与排序、搜索及相关应用有关的基本数据结构和算法。覆盖了数组、链表、串、树和其他基本数据结构,更多地强调抽象数据类型(ADT)、模块化程序设计、面向对象程序设计和C++类。本书包括排序、选择、优先队列ADT实现和符号表ADT(搜索)实现,配有帮助学生学习计算......一起来看看 《算法Ⅰ~Ⅳ(C++实现):基础、数据结构、排序和搜索》 这本书的介绍吧!