Commit 17f52abf5b890be2869991f760dee239f8e35e43

Authored by 梁灏
1 parent 871ed4d8

update Tabs

update Tabs
src/components/tabs/pane.vue
1 1 <template>
2   -
  2 + <div :class="prefixCls"><slot></slot></div>
3 3 </template>
4 4 <script>
  5 + const prefixCls = 'ivu-tabs-tabpane';
  6 +
5 7 export default {
6   - props: {},
  8 + name: 'TabPane',
  9 + props: {
  10 + key: {
  11 + type: String
  12 + },
  13 + label: {
  14 + type: String,
  15 + default: ''
  16 + },
  17 + icon: {
  18 + type: String
  19 + },
  20 + disabled: {
  21 + type: Boolean,
  22 + default: false
  23 + }
  24 + },
7 25 data () {
8   - return {}
  26 + return {
  27 + prefixCls: prefixCls,
  28 + show: true
  29 + }
  30 + },
  31 + computed: {
  32 +
  33 + },
  34 + methods: {
  35 + updateNav () {
  36 + this.$parent.updateNav();
  37 + }
9 38 },
10   - computed: {},
11   - methods: {}
  39 + watch: {
  40 + label () {
  41 + this.updateNav();
  42 + },
  43 + icon () {
  44 + this.updateNav();
  45 + },
  46 + disabled () {
  47 + this.updateNav();
  48 + }
  49 + }
12 50 }
13 51 </script>
14 52 \ No newline at end of file
... ...
src/components/tabs/tabs.vue
1 1 <template>
2   -
  2 + <div :class="classes">
  3 + <div :class="[prefixCls + '-bar']">
  4 + <div :class="[prefixCls + '-nav-container']">
  5 + <div :class="[prefixCls + '-nav-wrap']">
  6 + <div :class="[prefixCls + '-nav-scroll']">
  7 + <div :class="[prefixCls + '-nav']" v-el:nav>
  8 + <div :class="barClasses" :style="barStyle"></div>
  9 + <div :class="tabCls(item)" v-for="item in navList" @click="handleChange($index)">
  10 + <Icon v-if="item.icon !== ''" :type="item.icon"></Icon>
  11 + {{ item.label }}
  12 + <Icon v-if="closable && type === 'card'" type="ios-close-empty" @click.stop="handleRemove($index)"></Icon>
  13 + </div>
  14 + </div>
  15 + </div>
  16 + </div>
  17 + </div>
  18 + </div>
  19 + <div :class="contentClasses" :style="contentStyle"><slot></slot></div>
  20 + </div>
3 21 </template>
4 22 <script>
  23 + import Icon from '../icon/icon.vue';
  24 + import { oneOf, getStyle } from '../../utils/assist';
  25 +
  26 + const prefixCls = 'ivu-tabs';
  27 +
5 28 export default {
6   - props: {},
  29 + components: { Icon },
  30 + props: {
  31 + activeKey: {
  32 + type: [String, Number]
  33 + },
  34 + type: {
  35 + validator (value) {
  36 + return oneOf(value, ['line', 'card']);
  37 + },
  38 + default: 'line'
  39 + },
  40 + size: {
  41 + validator (value) {
  42 + return oneOf(value, ['small', 'default']);
  43 + },
  44 + default: 'default'
  45 + },
  46 + animated: {
  47 + type: Boolean,
  48 + default: true
  49 + },
  50 + closable: {
  51 + type: Boolean,
  52 + default: false
  53 + }
  54 + },
7 55 data () {
8   - return {}
  56 + return {
  57 + prefixCls: prefixCls,
  58 + navList: [],
  59 + barWidth: 0,
  60 + barOffset: 0
  61 + }
  62 + },
  63 + computed: {
  64 + classes () {
  65 + return [
  66 + `${prefixCls}`,
  67 + {
  68 + [`${prefixCls}-card`]: this.type === 'card',
  69 + [`${prefixCls}-mini`]: this.size === 'small' && this.type === 'line',
  70 + [`${prefixCls}-no-animation`]: !this.animated
  71 + }
  72 + ]
  73 + },
  74 + contentClasses () {
  75 + return [
  76 + `${prefixCls}-content`,
  77 + `${prefixCls}-content-animated`
  78 + ]
  79 + },
  80 + barClasses () {
  81 + return [
  82 + `${prefixCls}-ink-bar`,
  83 + `${prefixCls}-ink-bar-animated`
  84 + ]
  85 + },
  86 + contentStyle () {
  87 + const x = this.navList.findIndex((nav, index) => nav.key === this.activeKey);
  88 + const p = x === 0 ? '0%' : `-${x}00%`;
  89 +
  90 + let style = {};
  91 + if (x > -1) {
  92 + style = {
  93 + transform: `translateX(${p}) translateZ(0px)`
  94 + }
  95 + }
  96 + return style;
  97 + },
  98 + barStyle () {
  99 + let style = {
  100 + display: 'none',
  101 + width: `${this.barWidth}px`,
  102 + transform: `translate3d(${this.barOffset}px, 0px, 0px)`
  103 + };
  104 + if (this.type === 'line') style.display = 'block';
  105 +
  106 + return style;
  107 + }
  108 + },
  109 + methods: {
  110 + getTabs () {
  111 + return this.$children.filter(item => item.$options.name === 'TabPane');
  112 + },
  113 + updateNav () {
  114 + this.navList = [];
  115 + this.getTabs().forEach((pane, index) => {
  116 + this.navList.push({
  117 + label: pane.label,
  118 + icon: pane.icon || '',
  119 + key: pane.key || index,
  120 + disabled: pane.disabled
  121 + });
  122 + if (!pane.key) pane.key = index;
  123 + if (index === 0) {
  124 + if (!this.activeKey) this.activeKey = pane.key || index;
  125 + }
  126 + });
  127 + this.updateBar();
  128 + },
  129 + updateBar () {
  130 + this.$nextTick(() => {
  131 + const index = this.navList.findIndex((nav, index) => nav.key === this.activeKey);
  132 + const prevTabs = this.$els.nav.querySelectorAll(`.${prefixCls}-tab`);
  133 + const tab = prevTabs[index];
  134 + this.barWidth = parseFloat(getStyle(tab, 'width'));
  135 +
  136 + if (index > 0) {
  137 + let offset = 0;
  138 + const gutter = this.size === 'small' ? 0 : 16;
  139 + for (let i = 0; i < index; i++) {
  140 + offset += parseFloat(getStyle(prevTabs[i], 'width')) + gutter;
  141 + }
  142 +
  143 + this.barOffset = offset;
  144 + } else {
  145 + this.barOffset = 0;
  146 + }
  147 + });
  148 + },
  149 + tabCls (item) {
  150 + return [
  151 + `${prefixCls}-tab`,
  152 + {
  153 + [`${prefixCls}-tab-disabled`]: item.disabled,
  154 + [`${prefixCls}-tab-active`]: item.key === this.activeKey
  155 + }
  156 + ]
  157 + },
  158 + handleChange (index) {
  159 + const nav = this.navList[index];
  160 + if (nav.disabled) return;
  161 + this.activeKey = nav.key;
  162 + this.$emit('on-click', nav.key);
  163 + },
  164 + handleRemove (index) {
  165 + const tabs = this.getTabs();
  166 + const tab = tabs[index];
  167 + tab.$destroy(true);
  168 +
  169 + if (tab.key === this.activeKey) {
  170 + const newTabs = this.getTabs();
  171 + let activeKey = -1;
  172 +
  173 + if (newTabs.length) {
  174 + const leftNoDisabledTabs = tabs.filter((item, itemIndex) => !item.disabled && itemIndex < index);
  175 + const rightNoDisabledTabs = tabs.filter((item, itemIndex) => !item.disabled && itemIndex > index);
  176 +
  177 + if (rightNoDisabledTabs.length) {
  178 + activeKey = rightNoDisabledTabs[0].key;
  179 + } else if (leftNoDisabledTabs.length) {
  180 + activeKey = leftNoDisabledTabs[leftNoDisabledTabs.length - 1].key;
  181 + } else {
  182 + activeKey = newTabs[0].key;
  183 + }
  184 + }
  185 + this.activeKey = activeKey;
  186 + }
  187 + this.$emit('on-tab-remove', tab.key);
  188 + this.updateNav();
  189 + }
  190 + },
  191 + compiled () {
  192 + this.updateNav();
9 193 },
10   - computed: {},
11   - methods: {}
  194 + watch: {
  195 + activeKey () {
  196 + this.updateBar();
  197 + }
  198 + }
12 199 }
13 200 </script>
14 201 \ No newline at end of file
... ...
src/styles/components/tabs.less
1   -@tabs-prefix-cls: ~"@{css-prefix}tabs";
2 1 \ No newline at end of file
  2 +@tabs-prefix-cls: ~"@{css-prefix}tabs";
  3 +
  4 +.@{tabs-prefix-cls} {
  5 + box-sizing: border-box;
  6 + position: relative;
  7 + overflow: hidden;
  8 + color: @text-color;
  9 + .clearfix;
  10 +
  11 + &-bar {
  12 + outline: none;
  13 + }
  14 +
  15 + &-ink-bar {
  16 + height: 2px;
  17 + box-sizing: border-box;
  18 + background-color: @primary-color;
  19 + position: absolute;
  20 + left: 0;
  21 + bottom: 1px;
  22 + z-index: 1;
  23 + transition: transform .3s @ease-in-out;
  24 + transform-origin: 0 0;
  25 + }
  26 +
  27 + &-bar {
  28 + border-bottom: 1px solid @border-color-base;
  29 + margin-bottom: 16px;
  30 + }
  31 +
  32 + &-nav-container {
  33 + margin-bottom: -1px;
  34 + line-height: @line-height-base;
  35 + font-size: @font-size-base;
  36 + box-sizing: border-box;
  37 + white-space: nowrap;
  38 + overflow: hidden;
  39 + position: relative;
  40 + .clearfix;
  41 + }
  42 +
  43 + &-nav-container-scrolling {
  44 + padding-left: 32px;
  45 + padding-right: 32px;
  46 + }
  47 +
  48 + &-nav-wrap {
  49 + overflow: hidden;
  50 + margin-bottom: -1px;
  51 + }
  52 +
  53 + &-nav-scroll {
  54 + overflow: hidden;
  55 + white-space: nowrap;
  56 + }
  57 +
  58 + &-nav {
  59 + padding-left: 0;
  60 + margin: 0;
  61 + float: left;
  62 + list-style: none;
  63 + box-sizing: border-box;
  64 + position: relative;
  65 + transition: transform 0.5s @ease-in-out;
  66 +
  67 + &:before,
  68 + &:after {
  69 + display: table;
  70 + content: " ";
  71 + }
  72 +
  73 + &:after {
  74 + clear: both;
  75 + }
  76 +
  77 + .@{tabs-prefix-cls}-tab-disabled {
  78 + pointer-events: none;
  79 + cursor: default;
  80 + color: #ccc;
  81 + }
  82 +
  83 + .@{tabs-prefix-cls}-tab {
  84 + display: inline-block;
  85 + height: 100%;
  86 + padding: 8px 16px;
  87 + margin-right: 16px;
  88 + box-sizing: border-box;
  89 + cursor: pointer;
  90 + text-decoration: none;
  91 + position: relative;
  92 + transition: color .3s @ease-in-out;
  93 +
  94 + &:hover {
  95 + color: @link-hover-color;
  96 + }
  97 +
  98 + &:active {
  99 + color: @link-active-color;
  100 + }
  101 + .@{css-prefix-iconfont} {
  102 + width: 14px;
  103 + height: 14px;
  104 + margin-right: 8px;
  105 + }
  106 + }
  107 +
  108 + .@{tabs-prefix-cls}-tab-active {
  109 + color: @primary-color;
  110 + }
  111 + }
  112 + &-mini &-nav-container {
  113 + font-size: @font-size-base;
  114 + }
  115 +
  116 + &-mini &-tab {
  117 + margin-right: 0;
  118 + padding: 8px 16px;
  119 + }
  120 +
  121 + & {
  122 + .@{tabs-prefix-cls}-content-animated {
  123 + display: flex;
  124 + flex-direction: row;
  125 + will-change: transform;
  126 + transition: transform .3s @ease-in-out;
  127 + }
  128 +
  129 + .@{tabs-prefix-cls}-tabpane {
  130 + flex-shrink: 0;
  131 + width: 100%;
  132 + transition: opacity .3s;
  133 + opacity: 1;
  134 + }
  135 +
  136 + .@{tabs-prefix-cls}-tabpane-inactive {
  137 + opacity: 0;
  138 + height: 0;
  139 + }
  140 + }
  141 +
  142 + // card style
  143 + &&-card > &-bar &-nav-container {
  144 + height: 32px;
  145 + }
  146 + &&-card > &-bar &-ink-bar {
  147 + visibility: hidden;
  148 + }
  149 + &&-card > &-bar &-tab {
  150 + margin: 0;
  151 + margin-right: 4px;
  152 + height: 31px;
  153 + padding: 5px 16px 4px;
  154 + border: 1px solid @border-color-base;
  155 + border-bottom: 0;
  156 + border-radius: @btn-border-radius @btn-border-radius 0 0;
  157 + transition: all 0.3s @ease-in-out;
  158 + background: @table-thead-bg;
  159 + }
  160 + &&-card > &-bar &-tab-active {
  161 + height: 32px;
  162 + padding-bottom: 5px;
  163 + background: #fff;
  164 + transform: translateZ(0);
  165 + border-color: @border-color-base;
  166 + color: @primary-color;
  167 + }
  168 + &&-card > &-bar &-nav-wrap {
  169 + margin-bottom: 0;
  170 + }
  171 + &&-card > &-bar &-tab .@{css-prefix-iconfont}-ios-close-empty {
  172 + width: 0;
  173 + height: 22px;
  174 + font-size: 22px;
  175 + margin-right: 0;
  176 + color: @legend-color;
  177 + text-align: right;
  178 + vertical-align: middle;
  179 + overflow: hidden;
  180 + position: relative;
  181 + top: -1px;
  182 + transform-origin: 100% 50%;
  183 + transition: all 0.3s @ease-in-out;
  184 + &:hover {
  185 + color: #444;
  186 + }
  187 + }
  188 +
  189 + &&-card > &-bar &-tab-active .@{css-prefix-iconfont}-ios-close-empty,
  190 + &&-card > &-bar &-tab:hover .@{css-prefix-iconfont}-ios-close-empty {
  191 + width: 14px;
  192 + transform: translateZ(0);
  193 + }
  194 +}
  195 +
  196 +.@{tabs-prefix-cls}-no-animation{
  197 + .@{tabs-prefix-cls}-content {
  198 + &-animated {
  199 + transform: none!important;
  200 + }
  201 +
  202 + > .@{tabs-prefix-cls}-tabpane-inactive {
  203 + display: none;
  204 + }
  205 + }
  206 +}
3 207 \ No newline at end of file
... ...
test/app.vue
... ... @@ -43,6 +43,7 @@ li + li {
43 43 <li><a v-link="'/transfer'">Transfer</a></li>
44 44 <li><a v-link="'/table'">Table</a></li>
45 45 <li><a v-link="'/dropdown'">Dropdown</a></li>
  46 + <li><a v-link="'/tabs'">Tabs</a></li>
46 47 </ul>
47 48 </nav>
48 49 <router-view></router-view>
... ...
test/main.js
... ... @@ -112,6 +112,11 @@ router.map({
112 112 component: function (resolve) {
113 113 require(['./routers/dropdown.vue'], resolve);
114 114 }
  115 + },
  116 + '/tabs': {
  117 + component: function (resolve) {
  118 + require(['./routers/tabs.vue'], resolve);
  119 + }
115 120 }
116 121 });
117 122  
... ...
test/routers/table.vue
1 1 <template>
2   - <i-table border :columns="columns1" :data="data1">
3   - <div slot="header">表格标题</div>
4   - <div slot="footer">表格页脚</div>
5   - </i-table>
  2 + <i-table border :content="self" :columns="columns7" :data="data6"></i-table>
6 3 </template>
7 4 <script>
8 5 export default {
9 6 data () {
10 7 return {
11   - columns1: [
  8 + self: this,
  9 + columns7: [
12 10 {
13 11 title: '姓名',
14   - key: 'name'
  12 + key: 'name',
  13 + render (row, column, index) {
  14 + return `<Icon type="person"></Icon> <strong>${row.name}</strong>`;
  15 + }
15 16 },
16 17 {
17 18 title: '年龄',
... ... @@ -20,9 +21,18 @@
20 21 {
21 22 title: '地址',
22 23 key: 'address'
  24 + },
  25 + {
  26 + title: '操作',
  27 + key: 'action',
  28 + width: 150,
  29 + align: 'center',
  30 + render (row, column, index) {
  31 + return `<i-button type="primary" size="small" @click="show(${index})">查看</i-button> <i-button type="error" size="small" @click="remove(${index})">删除</i-button>`;
  32 + }
23 33 }
24 34 ],
25   - data1: [
  35 + data6: [
26 36 {
27 37 name: '王小明',
28 38 age: 18,
... ... @@ -45,6 +55,17 @@
45 55 }
46 56 ]
47 57 }
  58 + },
  59 + methods: {
  60 + show (index) {
  61 + this.$Modal.info({
  62 + title: '用户信息',
  63 + content: `姓名:${this.data6[index].name}<br>年龄:${this.data6[index].age}<br>地址:${this.data6[index].address}`
  64 + })
  65 + },
  66 + remove (index) {
  67 + this.data6.splice(index, 1);
  68 + }
48 69 }
49 70 }
50 71 </script>
... ...
test/routers/tabs.vue 0 → 100644
  1 +<template>
  2 + <i-button @click="toggleClose" :animated="false">closable</i-button>
  3 +
  4 + <Tabs type="line" :closable="closable" :animated="false">
  5 + <Tab-pane label="Tab 1">Tab1 content</Tab-pane>
  6 + <Tab-pane label="Tab 2" icon="ionic">Tab2 content</Tab-pane>
  7 + <Tab-pane label="Tab 3" disabled>Tab3 content</Tab-pane>
  8 + <Tab-pane label="Tab 4">Tab4 content</Tab-pane>
  9 + <Tab-pane label="Tab 5">Tab5 content</Tab-pane>
  10 + <Tab-pane label="Tab 6" icon="ionic">Tab6 content</Tab-pane>
  11 + <Tab-pane label="Tab 7" disabled>Tab7 content</Tab-pane>
  12 + <Tab-pane label="Tab 888888888">Tab8 content</Tab-pane>
  13 + </Tabs>
  14 +</template>
  15 +<script>
  16 + export default {
  17 + props: {
  18 +
  19 + },
  20 + data () {
  21 + return {
  22 + closable: true
  23 + }
  24 + },
  25 + computed: {
  26 +
  27 + },
  28 + methods: {
  29 + toggleClose () {
  30 + this.closable = !this.closable;
  31 + }
  32 + }
  33 + }
  34 +</script>
0 35 \ No newline at end of file
... ...