Vue Tab 组件再探究
初学 Vue 的时候,发现用 Vue 来写 Tab 组件是如此简单,利用“数据驱动”的思路还真和 js 控制 dom 不一样。请见下面第一版的代码,没有 js dom 那样 for 遍历各元素控制显示或隐藏,而是用 {'selected': index === selected}
控制样式,非常简洁。
第一版的 tab 组件
// 简单选项卡
Vue.component('aj-simple-tab', {
template :
'<div :class="isVertical ? \'aj-simple-tab-vertical\' : \'aj-simple-tab-horizontal\' ">\ <ul>\ <li v-for="(item, index) in items" :class="{\'selected\': index === selected}" @click="changeTab(index);">{ {item.name}}</li>\ </ul>\ <div class="content">\ <div v-for="(item, index) in items" :class="{\'selected\': index === selected}" v-html="item.content"></div>\ </div>\ </div>',
props: {
isVertical : Boolean, // 是否垂直方向的布局,默认 false,
initItems : Array
},
data() {
return {
selected : 0,
items : this.initItems || [
{ name : '杜甫:望岳', content : '岱宗夫如何,齊魯青未了。<br>\ 造化鐘神秀,陰陽割昏曉。<br>\ 蕩胸生層云,決眥入歸鳥,<br>\ 會當凌絕頂,一覽眾山小。'
},
{ name : '资质证照', content : '之所以如此,端在于中国传统中存在着发达的契约。予谓不信,可看看早年由福建师范大学内部印行的两册本《明清福建经济契约文书选集》,中国社会科学出版社出版的《自贡盐业契约档案选集》,花山文艺出版社出版的共二十册的《徽州千年契约文书》;再看看近些年由安徽师范大学出版社出版的十卷本的《千年徽州契约文书集萃》,广西师范大学出版社出版的四辑共四十册《徽州文书》、三辑共三十册的《清水江文书》,民族出版社出版的多卷本《贵州清水江流域明清契约文书》,凤凰出版社出版的《敦煌契约文书辑校》,天津古籍出版社出版的《清代宁波契约文书辑校》,浙江大学出版社出版的《清代浙东契约文书辑选》……不难发现,中国传统契约文书的整理出版,完全可称为如雨后春笋般在迅速成长!'},
{ name : '资质证照', content : '笔者出于个人兴趣,在关注这些已经整理出版的、卷帙浩繁的契约文献的同时,也游走各地,或亲自搜集各类契约文书,或到一些地方档案馆查看其业已编辑成册、内部印行的传统契约文书,如在台湾宜兰、高雄,山东青岛、威海,贵州锦屏、从江等地档案机构或民间都见到过相关契约文献。记忆尤深的一次,是我和几位学生共游山东浮来山。在一处值班室里,居然发现有人以清代契约文本粘糊墙壁!足见只要我们稍加留意,在这个文明发展历经数千年的国度,不时可以发现一些令人称心的古代契约文献。'}
]
};
},
methods : {
changeTab(index) {
this.selected = index;
}
}
});
演示的例子如下图。
该例子的 items 数据项还提供了默认的演示数据,不过问题是,tab 的内容要写在 js 里面,是比较麻烦的,能不能改写在标签里面呢?
第二版的 tab 组件
这问题一时还没有好的办法,因为碍于组件的关系,输入的数据还是变成 js 字符串,那样就涉及转义等的问题,处理起来也不方便。于是干脆放弃 vue 组件的概念,直接写标签然后用最简单的 vue 实例化方式。
<div class="aj-simple-tab-vertical tab" style="padding: 1% 5%;">
<ul>
<li :class="{ 'selected': 0 === selected}" @click="selected = 0">前端文档</li>
<li :class="{ 'selected': 1 === selected}" @click="selected = 1">数据库文档</li>
</ul>
<div class="content">
<div :class="{ 'selected': 0 === selected}">
<!-- TAB 内容 -->
<iframe src="${ajaxjsui}/ui-doc" frameborder="no" width="100%" height="96%"></iframe>
<!-- // TAB 内容 -->
</div>
<div :class="{ 'selected': 1 === selected}">
<!-- TAB 内容 -->
<iframe src="${ctx}/admin/DataBaseShowStru" frameborder="no" width="100%" height="96%"></iframe>
<!-- // TAB 内容 -->
</div>
</div>
</div>
<script> TAB = new Vue({ el : '.tab', data: { selected:0 } }); </script>
这样的方式比第一版跟简单,完全没有了 for 循环,都是一个个写死 0、1、2……的判断。虽然很脑残,但对于初学者来说这足够浅显的。放入如果 tab 数量不多,几个写死还是问题的不大的。
第三版的 tab 组件
第二版的方式只是玩玩,肯定还是要自动处理 tab 的。想到 vue 支持 slot 机制,也就是标签嵌套标签,对于不确定的内容,使用 slot 非常灵活。基于这一思路再次封装一 tab 组件,既解决 HTML 定义 tab 内容的需求,也能通过 vue 组件来封装 tab 逻辑,于是形成 <aj-tab>
组件如下。
// https://vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots
Vue.component('aj-tab', {
template:
'<div :class="isVertical ? \'aj-simple-tab-vertical\' : \'aj-simple-tab-horizontal\' ">\ <button v-for="tab in tabs" v-bind:key="tab.name"\ v-bind:class="[\'tab-button\', { active: currentTab.name === tab.name }]"\ v-on:click="currentTab = tab">{ {tab.name}}\ </button>\ <component v-bind:is="currentTab.component" class="tab"></component>\ </div>',
props: {
isVertical : Boolean // 是否垂直方向的布局,默认 false,
},
data() {
return {
tabs: [],
currentTab: { }
};
},
mounted() {
var arr = this.$slots.default;
for(var i = 0; i < arr.length; i++) {
var el = arr[i];
if(el.tag === 'textarea') {
this.tabs.push({
name : el.data.attrs['data-title'],
component: {
template: '<div>' + el.children[0].text + "</div>"
}
});
}
}
this.currentTab = this.tabs[0];
}
});
第三版使用了 <conmponent>
动态组件,现在想了下,其实应该可以优化而不用动态组件的。调用方式如下。
<div class="foo">
<aj-tab>
<textarea data-title="Home">Home componentzcc<span class="foo">hihi</span></textarea>
<textarea data-title="Home2">Home componentzcc2</textarea>
<textarea data-title="Home3">Home componentzcc3</textarea>
</aj-tab>
</div>
调用方式利用了 <textarea>
,避免了浏览器渲染 HTML。标题安排在 data-title
属性上。
第四版的 tab 组件
本来写到第三版,其实已经足够 ok 的了,但担心 tab 里面不能放置 vue 组件,于是又另辟蹊径搞了第四版的 tab,实际上第三版是可以的,我对 <textarea>
多虑了。
第四版却是放弃 vue 数据状态的控制,改用原始的 for 循环
控制。
调用方式如下,先声明 HTML,然后 new Vue 实例化(还没使用组件封装之)。
<div class="aj-simple-tab-horizontal tab" style="padding: 1% 5%;">
<ul>
<li>备份数据</li>
<li>数据库连接管理</li>
<li>代码生成器</li>
<li>后台日志浏览</li>
</ul>
<div>
<div>备份数据</div>
<div>数据库连接管理</div>
<div>代码生成器</div>
<div>后台日志浏览</div>
</div>
</div>
new Vue({
el:'.aj-json-form',
data: {
scheme : value,
selected:0
},
mounted() {
var ul = this.$el.querySelector('.aj-simple-tab-horizontal > ul');
ul.onclick = e => {
var el = e.target;
var index = Array.prototype.indexOf.call(el.parentElement.children, el);
this.selected = index;
};
//debugger;
this.$options.watch.selected.call(this, 0);
},
watch: {
selected(v){
var headers = this.$el.querySelectorAll('.aj-simple-tab-horizontal > ul > li');
var contents = this.$el.querySelectorAll('.aj-simple-tab-horizontal > div > div');
var each = arr => {
for(var i = 0, j = arr.length; i < j; i++) {
if(v === i) {
arr[i].classList.add('selected');
} else {
arr[i].classList.remove('selected');
}
}
};
each(headers);
each(headers);
}
}
});
那么接着封装成组件吧?不过稍等,能不能用多态 mixins 特性呢?可以的,且看……
aj.tab = {
data(){
return {
selected: 0
};
},
mounted() {
var ul = this.$el.querySelector('.aj-simple-tab-horizontal > ul');
ul.onclick = e => {
var el = e.target;
var index = Array.prototype.indexOf.call(el.parentElement.children, el);
this.selected = index;
};
this.$options.watch.selected.call(this, 0);
},
watch: {
selected(v) {
var headers = this.$el.querySelectorAll('.aj-simple-tab-horizontal > ul > li');
var contents = this.$el.querySelectorAll('.aj-simple-tab-horizontal > div > div');
var each = arr => {
for(var i = 0, j = arr.length; i < j; i++) {
if(v === i) {
arr[i].classList.add('selected');
} else {
arr[i].classList.remove('selected');
}
}
};
each(headers);
each(contents);
}
}
};
new Vue({
el:'.aj-json-form',
data: {
scheme : value
},
mixins: [aj.tab]
});
用法就是上面的例子了,注意 mixins: [aj.tab]
这里。
小小的 tab,竟然重构四次之多~哈哈哈,我都佩服我自己的孜孜不倦精神……
还没有评论,来说两句吧...