Browse Source

fix tabs initial with bug & improve docs

baiyaaaaa 8 years ago
parent
commit
2eea08af23

+ 29 - 9
examples/docs/en-US/tabs.md

@@ -2,7 +2,18 @@
   export default {
     data() {
       return {
-        activeName: 'first'
+        activeName: 'first',
+        activeName2: 'first',
+        tabs: [{
+          title: 'Tab 1',
+          name: '1',
+          content: 'Tab 1 content'
+        }, {
+          title: 'Tab 2',
+          name: '2',
+          content: 'Tab 2 content'
+        }],
+        tabIndex: 2
       }
     },
     methods: {
@@ -27,11 +38,11 @@ Divide data collections which are related yet belong to different types.
 
 Basic and concise tabs.
 
-:::demo Tabs provide a selective card functionality and it can be achieved by just using `el-tabs` and child element `el-tab-pane`. In these two elements, we provide a list of attributes. The `label` in `el-tab-pane` determines the label of selective cards, and you can write content in the label. In this example, we add a `active-name` attribute indicating the active card in `el-tabs`, which can take a `String` value. In the `el-tab-pane` you can set corresponding `name` attribute, and if there is no `name`, the default sequence is `1`/`2`/`3`/`4`. In this example, the selected card is card 2. If `name` is omitted, setting `active-name` to `2` can reach the same goal.
+:::demo Tabs provide a selective card functionality. By default the first tab is selected as active, and you can activate any tab by setting the `value` attribute.
 
 ```html
 <template>
-  <el-tabs :active-name="activeName">
+  <el-tabs v-model="activeName" @tab-click="handleClick">
     <el-tab-pane label="User" name="first">User</el-tab-pane>
     <el-tab-pane label="Config" name="second">Config</el-tab-pane>
     <el-tab-pane label="Role" name="third">Role</el-tab-pane>
@@ -44,6 +55,11 @@ Basic and concise tabs.
       return {
         activeName: 'first'
       };
+    },
+    methods: {
+      handleClick(tab, event) {
+        console.log(tab, event);
+      }
     }
   };
 </script>
@@ -58,7 +74,7 @@ Tabs styled as cards.
 
 ```html
 <template>
-  <el-tabs type="card" @tab-click="handleClick" @tab-remove="handleRemove">
+  <el-tabs type="card" @tab-click="handleClick">
     <el-tab-pane label="User">User</el-tab-pane>
     <el-tab-pane label="Config">Config</el-tab-pane>
     <el-tab-pane label="Role">Role</el-tab-pane>
@@ -67,10 +83,12 @@ Tabs styled as cards.
 </template>
 <script>
   export default {
+    data() {
+      return {
+        activeName: 'first'
+      };
+    },
     methods: {
-      handleRemove(tab) {
-        console.log(tab);
-      },
       handleClick(tab, event) {
         console.log(tab, event);
       }
@@ -84,7 +102,7 @@ Tabs styled as cards.
 
 Closable tabs.
 
-:::demo You can set `closable` attribute in `el-tabs`. It accept `Boolean` and Tab will be closable when the boolean is `true`.
+:::demo You can set the closable attribute in el-tabs to make all tabs closable. Also, closable can be set in a tab panel to make that specific tab closable.
 
 ```html
 <template>
@@ -157,7 +175,8 @@ You can use `label-content` property to customize the tab
 |---------- |-------- |---------- |-------------  |-------- |
 | type     | type of Tab | string   | card/border-card  |     —    |
 | closable  | whether Tab is closable | boolean   | — |  false  |
-| active-name  | name of the selected tab  | string   |  —  |  name of first tab |
+| active-name(deprecated)  | name of the selected tab  | string   |  —  |  name of first tab |
+| value  | name of the selected tab  | string   |  —  |  name of first tab |
 
 ### Tabs Events
 | Event Name | Description | Parameters |
@@ -172,3 +191,4 @@ You can use `label-content` property to customize the tab
 | label-content | render function for tab title | Function(h, tab:vueInstance) | - | - |
 | disabled | whether Tab is disabled | boolean | - | false |
 | name      | identifier corresponding to the activeName of Tabs, representing the alias of the tab-pane | string | — | ordinal number of the tab-pane in the sequence, i.e. the first tab-pane is '1' |
+| closable  | whether Tab is closable | boolean   | — |  false  |

+ 52 - 15
examples/docs/zh-CN/tabs.md

@@ -2,7 +2,18 @@
   export default {
     data() {
       return {
-        activeName: 'first'
+        activeName: 'first',
+        activeName2: 'first',
+        tabs: [{
+          title: 'Tab 1',
+          name: '1',
+          content: 'Tab 1 content'
+        }, {
+          title: 'Tab 2',
+          name: '2',
+          content: 'Tab 2 content'
+        }],
+        tabIndex: 2
       }
     },
     methods: {
@@ -18,18 +29,20 @@
     }
   }
 </script>
+
 ## Tabs 标签页
+
 分隔内容上有关联但属于不同类别的数据集合。
 
 ### 基础用法
 
 基础的、简洁的标签页。
 
-:::demo Tabs 组件提供了选项卡功能,只需要使用`el-tabs`和子元素`el-tab-pane`即可,在两个元素中,我们分别提供了一系列的属性来方便使用,`el-tab-pane`中`label`决定了选项卡标题,标签内部写入内容即可。在下例中我们在`el-tabs`中设置了`active-name`属性,接受一个`String`值,表明选中的选项卡,在`el-tab-pane`中可以设置对应的`name`属性,如果没有设置`name`,则默认值为顺序的`1`/`2`/`3`/`4`。例子选中选项卡2,如果不设置`name`,将`active-name`设为`2`,可以达成相同效果
+:::demo Tabs 组件提供了选项卡功能,默认选中第一个标签页,你也可以通过 `value` 属性来指定当前选中的标签页
 
 ```html
 <template>
-  <el-tabs :active-name="activeName">
+  <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>
@@ -42,6 +55,11 @@
       return {
         activeName: 'first'
       };
+    },
+    methods: {
+      handleClick(tab, event) {
+        console.log(tab, event);
+      }
     }
   };
 </script>
@@ -52,23 +70,25 @@
 
 选项卡样式的标签页。
 
-:::demo 只需要设置`type`属性即可,如果需要标签风格,将其设置为`card`。
+:::demo 只需要设置 `type` 属性为 `card` 就可以使选项卡改变为标签风格
 
 ```html
 <template>
-  <el-tabs type="card" @tab-click="handleClick" @tab-remove="handleRemove">
-    <el-tab-pane label="用户管理">用户管理</el-tab-pane>
-    <el-tab-pane label="配置管理">配置管理</el-tab-pane>
-    <el-tab-pane label="角色管理">角色管理</el-tab-pane>
-    <el-tab-pane label="定时任务补偿">定时任务补偿</el-tab-pane>
+  <el-tabs v-model="activeName2" type="card" @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>
 </template>
 <script>
   export default {
+    data() {
+      return {
+        activeName: 'first'
+      };
+    },
     methods: {
-      handleRemove(tab) {
-        console.log(tab);
-      },
       handleClick(tab, event) {
         console.log(tab, event);
       }
@@ -82,11 +102,11 @@
 
 可以关闭标签页。
 
-:::demo 在`el-tabs`中设置`closable`属性,接受一个`Boolean`,设置为`true`时为可关闭
+:::demo 通过设置 `closable` 属性来打开 `Tabs` 的可关闭标签效果, `closable` 也可以设置在 `Tab Panel` 中实现部分标签页的可关闭效果
 
 ```html
 <template>
-  <el-tabs type="card" :closable="true" @tab-click="handleClick" @tab-remove="handleRemove">
+  <el-tabs type="card" closable @tab-click="handleClick" @tab-remove="handleRemove">
     <el-tab-pane label="用户管理">用户管理</el-tab-pane>
     <el-tab-pane label="配置管理">配置管理</el-tab-pane>
     <el-tab-pane label="角色管理">角色管理</el-tab-pane>
@@ -147,12 +167,28 @@
 ```
 :::
 
+### 动态增加标签页
+
+展示如何通过触发器来动态增加标签页
+
+:::demo
+```html
+<div style="margin-bottom: 20px;">
+  <el-button size="small" @click="tabs.push({ name: 'Tab ' + ++tabIndex, title: 'new Tab', content: 'new Tab content' })">add tab</el-button>
+</div>
+<el-tabs type="card" closable>
+  <el-tab-pane v-for="(item, index) in tabs" :label="item.title" :name="item.name">{{item.content}}</el-tab-pane>
+</el-tabs>
+```
+:::
+
 ### Tabs Attributes
 | 参数       | 说明     | 类型      | 可选值       | 默认值   |
 |---------- |-------- |---------- |-------------  |-------- |
 | type     | 风格类型   | string   | card/border-card  |     —    |
 | closable  | 标签是否可关闭   | boolean   | — |  false  |
-| active-name  | 选中选项卡的 name  | string   |  —  |  第一个选项卡的 name |
+| active-name(deprecated)  | 选中选项卡的 name  | string   |  —  |  第一个选项卡的 name |
+| value  | 绑定值,选中选项卡的 name  | string   |  —  |  第一个选项卡的 name |
 
 ### Tabs Events
 | 事件名称 | 说明 | 回调参数 |
@@ -167,3 +203,4 @@
 | label-content | 选项卡的标题的渲染 Function | Function(h, tab:vueInstance) | - | - |
 | disabled | 是否禁用 | boolean | - | false |
 | name      | 与选项卡 activeName 对应的标识符,表示选项卡别名 | string | — | 该选项卡在选项卡列表中的顺序值,如第一个选项卡则为'1' |
+| closable  | 标签是否可关闭   | boolean   | — |  false  |

+ 1 - 1
package.json

@@ -58,7 +58,7 @@
     "babel-loader": "^6.2.5",
     "babel-plugin-module-resolver": "^2.2.0",
     "babel-plugin-syntax-jsx": "^6.8.0",
-    "babel-plugin-transform-vue-jsx": "^3.1.0",
+    "babel-plugin-transform-vue-jsx": "^3.3.0",
     "babel-preset-es2015": "^6.14.0",
     "chai": "^3.5.0",
     "cheerio": "^0.18.0",

+ 16 - 49
packages/tabs/src/tab-pane.vue

@@ -1,3 +1,10 @@
+<template>
+  <div class="el-tab-pane">
+    <div class="el-tab-pane__content" v-show="active">
+      <slot></slot>
+    </div>
+  </div>
+</template>
 <script>
   module.exports = {
     name: 'el-tab-pane',
@@ -12,73 +19,33 @@
 
     data() {
       return {
-        counter: 0,
-        transition: '',
-        paneStyle: {
-          position: 'relative'
-        },
-        isClosable: null,
-        index: ''
+        index: null
       };
     },
 
-    created() {
-      const propsData = this.$options.propsData;
-      if (propsData && typeof propsData.closable !== 'undefined') {
-        this.isClosable = propsData.closable === '' || propsData.closable;
-      } else {
-        this.isClosable = this.$parent.closable;
-      }
-      if (!this.index) {
-        this.index = this.$parent.$children.indexOf(this) + 1 + '';
-      }
-      if (this.$parent.panes) {
-        this.$parent.panes.push(this);
+    computed: {
+      isClosable() {
+        return this.closable || this.$parent.closable;
+      },
+      active() {
+        return this.$parent.currentName === (this.name || this.index);
       }
     },
 
-    computed: {
-      show() {
-        return this.$parent.currentName === this.index;
-      }
+    created() {
+      this.$parent.$forceUpdate();
     },
 
     destroyed() {
       if (this.$el && this.$el.parentNode) {
         this.$el.parentNode.removeChild(this.$el);
       }
-      const panes = this.$parent.panes;
-      if (panes) {
-        panes.splice(this, panes.indexOf(this));
-      }
     },
 
     watch: {
-      name: {
-        immediate: true,
-        handler(val) {
-          this.index = val;
-        }
-      },
-      closable(val) {
-        this.isClosable = val;
-      },
-      '$parent.currentName'(newValue, oldValue) {
-        if (this.index === newValue) {
-          this.transition = newValue > oldValue ? 'slideInRight' : 'slideInLeft';
-        }
-        if (this.index === oldValue) {
-          this.transition = oldValue > newValue ? 'slideInRight' : 'slideInLeft';
-        }
-      },
       label() {
         this.$parent.$forceUpdate();
       }
     }
   };
 </script>
-<template>
-  <div class="el-tab-pane" v-show="show && $slots.default">
-    <slot></slot>
-  </div>
-</template>

+ 88 - 79
packages/tabs/src/tabs.vue

@@ -4,69 +4,84 @@
 
     props: {
       type: String,
-      tabPosition: String,
       activeName: String,
-      closable: false,
-      tabWidth: 0
+      closable: {
+        type: Boolean,
+        default: false
+      },
+      value: {}
     },
 
     data() {
       return {
         children: null,
-        activeTab: null,
-        currentName: 0,
-        panes: []
+        currentName: this.value || this.activeName
       };
     },
 
     watch: {
-      activeName: {
-        handler(val) {
-          this.currentName = val;
-        }
+      activeName(val) {
+        this.currentName = val;
+      },
+      value(val) {
+        this.currentName = val;
+      },
+      currentName(val) {
+        this.$emit('input', val);
       }
     },
 
     methods: {
       handleTabRemove(tab, event) {
         event.stopPropagation();
-        let tabs = this.$children;
+        const tabs = this.$children;
 
-        var index = tabs.indexOf(tab);
-        tab.$destroy(true);
-
-        if (tab.index === this.currentName) {
-          let nextChild = tabs[index];
-          let prevChild = tabs[index - 1];
-
-          while (prevChild && prevChild.disabled) {
-            prevChild = tabs[tabs.indexOf(prevChild) - 1];
-          }
+        let index = tabs.indexOf(tab);
+        tab.$destroy();
 
-          this.currentName = nextChild
-            ? nextChild.index
-            : prevChild
-            ? prevChild.index
-            : '-1';
-        }
         this.$emit('tab-remove', tab);
         this.$forceUpdate();
+
+        this.$nextTick(_ => {
+          if (tab.active) {
+            let nextChild = tabs[index];
+            let prevChild = tabs[index - 1];
+            let nextActiveTab = nextChild || prevChild || null;
+
+            if (nextActiveTab) {
+              this.currentName = nextActiveTab.name || nextActiveTab.index;
+            }
+          }
+        });
       },
-      handleTabClick(tab, event) {
+      handleTabClick(tab, tabName, event) {
         if (tab.disabled) return;
-        this.currentName = tab.index;
+        this.currentName = tabName;
         this.$emit('tab-click', tab, event);
-      },
-      calcBarStyle() {
+      }
+    },
+    mounted() {
+      this.$forceUpdate();
+    },
+    render(h) {
+      let {
+        type,
+        handleTabRemove,
+        handleTabClick,
+        currentName
+      } = this;
+
+      const getBarStyle = () => {
         if (this.type || !this.$refs.tabs) return {};
-        var style = {};
-        var offset = 0;
-        var tabWidth = 0;
+        let style = {};
+        let offset = 0;
+        let tabWidth = 0;
 
-        this.$children.every((panel, index) => {
+        this.$children.every((tab, index) => {
           let $el = this.$refs.tabs[index];
           if (!$el) { return false; }
-          if (panel.index !== this.currentName) {
+
+          if (!tab.active) {
             offset += $el.clientWidth;
             return true;
           } else {
@@ -79,51 +94,45 @@
         style.transform = `translateX(${offset}px)`;
 
         return style;
-      }
-    },
-    mounted() {
-      this.currentName = this.activeName || this.$children[0] && this.$children[0].index || '1';
-      this.$nextTick(() => {
-        this.$forceUpdate();
-      });
-    },
-    render(h) {
-      let {
-        type,
-        panes, // eslint-disable-line
-        handleTabRemove,
-        handleTabClick,
-        currentName
-      } = this;
-
-      const barStyle = this.calcBarStyle();
-      const activeBar = !type
-        ? <div class="el-tabs__active-bar" style={barStyle}></div>
-        : null;
+      };
 
       const tabs = this.$children.map((tab, index) => {
-        let btnClose = h('span', {
-          class: {
-            'el-icon-close': true
-          },
-          on: { click: (ev) => { handleTabRemove(tab, ev); } }
-        });
-        const _tab = h('div', {
-          class: {
-            'el-tabs__item': true,
-            'is-active': currentName === tab.index,
-            'is-disabled': tab.disabled,
-            'is-closable': tab.isClosable
-          },
-          ref: 'tabs',
-          refInFor: true,
-          on: { click: (ev) => { handleTabClick(tab, ev); } }
-        }, [
-          tab.labelContent ? tab.labelContent.call(this._renderProxy, h, tab) : tab.label,
-          tab.isClosable ? btnClose : null,
-          index === 0 ? activeBar : null
-        ]);
-        return _tab;
+        let tabName = tab.name || tab.index || index;
+        if (currentName === undefined && index === 0) {
+          this.currentName = tabName;
+        }
+
+        tab.index = index;
+
+        const activeBar = !type && index === 0
+          ? <div class="el-tabs__active-bar" style={getBarStyle()}></div>
+          : null;
+
+        const btnClose = tab.isClosable
+          ? <span class="el-icon-close" on-click={(ev) => { handleTabRemove(tab, ev); }}></span>
+          : null;
+
+        const tabLabelContent = tab.labelContent
+          ? tab.labelContent.call(this._renderProxy, h, tab)
+          : tab.label;
+
+        return (
+          <div
+            class={{
+              'el-tabs__item': true,
+              'is-active': tab.active,
+              'is-disabled': tab.disabled,
+              'is-closable': tab.isClosable
+            }}
+            ref="tabs"
+            refInFor
+            on-click={(ev) => { handleTabClick(tab, tabName, ev); }}
+          >
+            {tabLabelContent}
+            {btnClose}
+            {activeBar}
+          </div>
+        );
       });
       return (
         <div class={{

+ 0 - 4
packages/theme-default/src/tabs.css

@@ -147,10 +147,6 @@
       }
     }
   }
-  @b tab-pane {
-    width: 100%;
-    display: inline-block;
-  }
 }
 
 .slideInRight-transition,

+ 5 - 5
test/unit/specs/tabs.spec.js

@@ -188,7 +188,7 @@ describe('Tabs', () => {
   it('closable edge', done => {
     vm = createVue({
       template: `
-        <el-tabs type="card" :closable="true">
+        <el-tabs type="card" :closable="true" ref="tabs">
           <el-tab-pane label="用户管理">A</el-tab-pane>
           <el-tab-pane label="配置管理">B</el-tab-pane>
           <el-tab-pane label="角色管理">C</el-tab-pane>
@@ -199,7 +199,7 @@ describe('Tabs', () => {
 
     let tabList = vm.$el.querySelector('.el-tabs__header').children;
     let paneList = vm.$el.querySelector('.el-tabs__content').children;
-    setTimeout(_ => {
+    vm.$nextTick(_ => {
       tabList[0].querySelector('.el-icon-close').click();
       vm.$nextTick(_ => {
         expect(tabList.length).to.be.equal(3);
@@ -209,16 +209,16 @@ describe('Tabs', () => {
 
         tabList[2].click();
         tabList[2].querySelector('.el-icon-close').click();
-        vm.$nextTick(_ => {
+        setTimeout(_ => {
           expect(tabList.length).to.be.equal(2);
           expect(paneList.length).to.be.equal(2);
           expect(tabList[1].classList.contains('is-active')).to.be.true;
           expect(tabList[1].innerText.trim()).to.be.equal('角色管理');
           expect(paneList[1].innerText.trim()).to.be.equal('C');
           done();
-        });
+        }, 100);
       });
-    }, 100);
+    });
   });
   it('tab title render function', done => {
     vm = createVue({

+ 3 - 3
yarn.lock

@@ -620,9 +620,9 @@ babel-plugin-transform-strict-mode@^6.18.0:
     babel-runtime "^6.0.0"
     babel-types "^6.18.0"
 
-babel-plugin-transform-vue-jsx@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.2.0.tgz#27d550d5fca27ef6f694cf294133179754d184dc"
+babel-plugin-transform-vue-jsx@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.3.0.tgz#d6163f93f129e9fac3768813470f5eed9fd26f7e"
   dependencies:
     esutils "^2.0.2"