From eeaa36058af8e516921c0f71702273f4a6866ac6 Mon Sep 17 00:00:00 2001 From: neverland <chenjiahan@buaa.edu.cn> Date: Thu, 12 Oct 2017 22:00:34 -0500 Subject: [PATCH] [new feature] add Tabbar component (#204) * [Document] add english document of Checkbox * [Document] add english document of Field * [Document] add english document of NumberKeyboard * [bugfix] NumberKeyboard should not dispaly title when title is empty * [Document] add english document of PasswordInput * [Document] add english document of Radio * [document] add english document of Switch * [bugfix] remove redundent styles in english document * [Document] fix details * fix Switch test cases * [bugfix] Swipe shouid reinitialize when item changes * [new feature] ImagePreview reconstruct * [new feature] add Tabbar component --- docs/examples-docs/zh-CN/tab.md | 2 +- docs/examples-docs/zh-CN/tabbar.md | 110 +++++++++++++++++++++++++++++ docs/src/doc.config.js | 6 +- packages/index.js | 6 ++ packages/swipe-item/index.vue | 13 ++-- packages/tabbar-item/index.vue | 49 +++++++++++++ packages/tabbar/index.vue | 46 ++++++++++++ packages/vant-css/src/index.css | 1 + packages/vant-css/src/tabbar.css | 50 +++++++++++++ test/unit/components/tabbar.vue | 40 +++++++++++ test/unit/specs/tabbar.spec.js | 23 ++++++ 11 files changed, 337 insertions(+), 9 deletions(-) create mode 100644 docs/examples-docs/zh-CN/tabbar.md create mode 100644 packages/tabbar-item/index.vue create mode 100644 packages/tabbar/index.vue create mode 100644 packages/vant-css/src/tabbar.css create mode 100644 test/unit/components/tabbar.vue create mode 100644 test/unit/specs/tabbar.spec.js diff --git a/docs/examples-docs/zh-CN/tab.md b/docs/examples-docs/zh-CN/tab.md index 3ca6131af..f10d38f1e 100644 --- a/docs/examples-docs/zh-CN/tab.md +++ b/docs/examples-docs/zh-CN/tab.md @@ -47,7 +47,7 @@ export default { }; </script> -## Tab 标签 +## Tab 标签页 ### 使用指南 ``` javascript diff --git a/docs/examples-docs/zh-CN/tabbar.md b/docs/examples-docs/zh-CN/tabbar.md new file mode 100644 index 000000000..22a32b8ad --- /dev/null +++ b/docs/examples-docs/zh-CN/tabbar.md @@ -0,0 +1,110 @@ +## Tabbar 标签栏 + +<style> +.demo-tabbar { + .van-tabbar { + position: relative; + + &-item { + cursor: pointer; + } + } +} +</style> + +<script> +export default { + data() { + return { + active: 0, + active2: 0, + icon: { + normal: 'https://img.yzcdn.cn/public_files/2017/10/13/c547715be149dd3faa817e4a948b40c4.png', + active: 'https://img.yzcdn.cn/public_files/2017/10/13/793c77793db8641c4c325b7f25bf130d.png' + } + } + } +} +</script> + +### 使用指南 +``` javascript +import { Tabbar, TabbarItem } from 'vant'; + +Vue.component(Tabbar.name, Tabbar); +Vue.component(TabbarItem.name, TabbarItem); +``` + +### 代码演示 + +#### 基础用法 + +:::demo 基础用法 +```html +<van-tabbar v-model="active"> + <van-tabbar-item icon="shop">标签</van-tabbar-item> + <van-tabbar-item icon="chat" dot>标签</van-tabbar-item> + <van-tabbar-item icon="records">标签</van-tabbar-item> + <van-tabbar-item icon="gold-coin">标签</van-tabbar-item> +</van-tabbar> +``` + +```javascript +export default { + data() { + return { + active: 0 + } + } +} +``` +::: + +#### 自定义图标 +通过 icon slot 自定义图标 + +:::demo 自定义图标 +```html +<van-tabbar v-model="active2"> + <van-tabbar-item icon="shop"> + <span>自定义</span> + <img slot="icon" :src="active2 === 0 ? icon.active : icon.normal" /> + </van-tabbar-item> + <van-tabbar-item icon="chat">标签</van-tabbar-item> + <van-tabbar-item icon="records">标签</van-tabbar-item> +</van-tabbar> +``` + +```javascript +export default { + data() { + return { + active2: 0, + icon: { + normal: '//img.yzcdn.cn/1.png', + active: '//img.yzcdn.cn/2.png' + } + } + } +} +``` +::: + +### Tabbar API + +| 参数 | 说明 | 类型 | 默认值 | 可选值 | +|-----------|-----------|-----------|-------------|-------------| +| v-model | 当前选中标签的索引 | `Number` | - | - | + +### Tabbar Event + +| 事件名 | 说明 | 参数 | +|-----------|-----------|-----------| +| change | 切换标签时触发 | active: 当前选中标签 | + +### TabbarItem API + +| 参数 | 说明 | 类型 | 默认值 | 可选值 | +|-----------|-----------|-----------|-------------|-------------| +| icon | 图标名称 | `String` | - | Icon 组件中可用的类型 | +| dot | 是否显示小红点 | `Boolean` | - | - | diff --git a/docs/src/doc.config.js b/docs/src/doc.config.js index ed205e30a..382792506 100644 --- a/docs/src/doc.config.js +++ b/docs/src/doc.config.js @@ -121,7 +121,11 @@ module.exports = { }, { "path": "/tab", - "title": "Tab - 标签" + "title": "Tab - 标签页" + }, + { + "path": "/tabbar", + "title": "Tabbar - 标签栏" }, { "path": "/tag", diff --git a/packages/index.js b/packages/index.js index c6d551ad6..e0ec4d901 100644 --- a/packages/index.js +++ b/packages/index.js @@ -50,6 +50,8 @@ import SwipeItem from './swipe-item'; import Switch from './switch'; import SwitchCell from './switch-cell'; import Tab from './tab'; +import Tabbar from './tabbar'; +import TabbarItem from './tabbar-item'; import Tabs from './tabs'; import Tag from './tag'; import Toast from './toast'; @@ -108,6 +110,8 @@ const components = [ Switch, SwitchCell, Tab, + Tabbar, + TabbarItem, Tabs, Tag, TreeSelect, @@ -182,6 +186,8 @@ export { Switch, SwitchCell, Tab, + Tabbar, + TabbarItem, Tabs, Tag, Toast, diff --git a/packages/swipe-item/index.vue b/packages/swipe-item/index.vue index d960e7ece..3cc80f0bd 100644 --- a/packages/swipe-item/index.vue +++ b/packages/swipe-item/index.vue @@ -8,14 +8,9 @@ export default { name: 'van-swipe-item', - beforeCreate() { - this.$parent.swipes.push(this); - }, - data() { return { - offset: 0, - index: this.$parent.swipes.indexOf(this) + offset: 0 }; }, @@ -28,8 +23,12 @@ export default { } }, + beforeCreate() { + this.$parent.swipes.push(this); + }, + destroyed() { - this.$parent.swipes.splice(this.index, 1); + this.$parent.swipes.splice(this.$parent.swipes.indexOf(this), 1); } }; </script> diff --git a/packages/tabbar-item/index.vue b/packages/tabbar-item/index.vue new file mode 100644 index 000000000..0e82852a9 --- /dev/null +++ b/packages/tabbar-item/index.vue @@ -0,0 +1,49 @@ +<template> + <div :class="['van-tabbar-item', { 'van-tabbar-item--active': active }]" @click="onClick"> + <div :class="['van-tabbar-item__icon', { 'van-tabbar-item__icon-dot': dot }]"> + <slot name="icon"> + <van-icon v-if="icon" :name="icon" /> + </slot> + </div> + <div class="van-tabbar-item__text"> + <slot></slot> + </div> + </div> +</template> + +<script> +import Icon from '../icon'; + +export default { + name: 'van-tabbar-item', + + components: { + [Icon.name]: Icon + }, + + props: { + icon: String, + dot: Boolean + }, + + data() { + return { + active: false + }; + }, + + beforeCreate() { + this.$parent.items.push(this); + }, + + destroyed() { + this.$parent.items.splice(this.$parent.items.indexOf(this), 1); + }, + + methods: { + onClick() { + this.$parent.onChange(this.$parent.items.indexOf(this)); + } + } +}; +</script> diff --git a/packages/tabbar/index.vue b/packages/tabbar/index.vue new file mode 100644 index 000000000..833d8d9fc --- /dev/null +++ b/packages/tabbar/index.vue @@ -0,0 +1,46 @@ +<template> + <div :class="['van-tabbar', 'van-hairline--top-bottom', { 'van-tabbar--fixed': fixed }]"> + <slot></slot> + </div> +</template> + +<script> +export default { + name: 'van-tabbar', + + data() { + return { + items: [] + }; + }, + + props: { + value: Number, + fixed: { + type: Boolean, + default: true + } + }, + + watch: { + items() { + this.setActiveItem(); + }, + value() { + this.setActiveItem(); + } + }, + + methods: { + setActiveItem() { + this.items.forEach((item, index) => { + item.active = index === this.value; + }); + }, + onChange(active) { + this.$emit('input', active); + this.$emit('change', active); + } + } +}; +</script> diff --git a/packages/vant-css/src/index.css b/packages/vant-css/src/index.css index 266d2779d..f9e94648f 100644 --- a/packages/vant-css/src/index.css +++ b/packages/vant-css/src/index.css @@ -22,6 +22,7 @@ @import './steps.css'; @import './tag.css'; @import './tab.css'; +@import './tabbar.css'; @import './image-preview.css'; @import './stepper.css'; @import './progress.css'; diff --git a/packages/vant-css/src/tabbar.css b/packages/vant-css/src/tabbar.css new file mode 100644 index 000000000..cbfab2b49 --- /dev/null +++ b/packages/vant-css/src/tabbar.css @@ -0,0 +1,50 @@ +@import './common/var.css'; + +.van-tabbar { + width: 100%; + height: 50px; + display: flex; + background-color: #fff; + + &--fixed { + left: 0; + bottom: 0; + position: fixed; + } + + &-item { + flex: 1; + color: #666; + display: flex; + line-height: 1; + font-size: 12px; + align-items: center; + flex-direction: column; + justify-content: center; + + &__icon { + font-size: 18px; + margin-bottom: 5px; + position: relative; + + &-dot { + &::after { + width: 8px; + height: 8px; + content: ' '; + position: absolute; + border-radius: 100%; + background-color: $red; + } + } + + img { + height: 18px; + } + } + + &--active { + color: $blue; + } + } +} diff --git a/test/unit/components/tabbar.vue b/test/unit/components/tabbar.vue new file mode 100644 index 000000000..9b91bec79 --- /dev/null +++ b/test/unit/components/tabbar.vue @@ -0,0 +1,40 @@ +<template> + <van-tabbar v-model="active" @change="onChange"> + <van-tabbar-item icon="shop"> + <span>自定义</span> + <img slot="icon" :src="active === 0 ? icon.active : icon.normal" /> + </van-tabbar-item> + <van-tabbar-item icon="chat">标签</van-tabbar-item> + <van-tabbar-item icon="chat">标签</van-tabbar-item> + <van-tabbar-item icon="records">标签</van-tabbar-item> + </van-tabbar> +</template> + +<script> +import Tabbar from 'packages/tabbar'; +import TabbarItem from 'packages/tabbar-item'; + +export default { + components: { + [Tabbar.name]: Tabbar, + [TabbarItem.name]: TabbarItem + }, + + data() { + return { + active: 0, + changeRecord: 0, + icon: { + normal: 'https://img.yzcdn.cn/public_files/2017/10/13/c547715be149dd3faa817e4a948b40c4.png', + active: 'https://img.yzcdn.cn/public_files/2017/10/13/793c77793db8641c4c325b7f25bf130d.png' + } + }; + }, + + methods: { + onChange(val) { + this.changeRecord = val; + } + } +}; +</script> diff --git a/test/unit/specs/tabbar.spec.js b/test/unit/specs/tabbar.spec.js new file mode 100644 index 000000000..86add5d3f --- /dev/null +++ b/test/unit/specs/tabbar.spec.js @@ -0,0 +1,23 @@ +import TabbarExample from '../components/tabbar'; +import { mount } from 'avoriaz'; + +describe('Progress', () => { + let wrapper; + + afterEach(() => { + wrapper && wrapper.destroy(); + }); + + it('Tabbar with four items', (done) => { + wrapper = mount(TabbarExample); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.van-tabbar-item').length).to.equal(4); + + wrapper.find('.van-tabbar-item')[3].element.click(); + expect(wrapper.vm.active).to.equal(3); + expect(wrapper.vm.changeRecord).to.equal(3); + done(); + }); + }); +});