Commit 0d13646576cd1ce46e506c5bd7e1fd1a3d24c72f

Authored by 梁灏
1 parent 744eb0af

update Table

update Table
src/components/table/mixin.js 0 → 100644
  1 +export default {
  2 + methods: {
  3 + alignCls (column) {
  4 + return column.align ? `${this.prefixCls}-column-${column.align}` : '';
  5 + }
  6 + }
  7 +}
0 \ No newline at end of file 8 \ No newline at end of file
src/components/table/table-head.vue
1 <template> 1 <template>
2 <thead> 2 <thead>
3 <tr> 3 <tr>
4 - <th v-for="column in columns" :class="fixedCls(column)">{{{ renderHeader(column, $index) }}}</th> 4 + <th v-for="column in columns" :class="alignCls(column)">
  5 + <div :class="[prefixCls + '-cell']">
  6 + <template v-if="column.type === 'selection'"><Checkbox :checked="isSelectAll" @on-change="selectAll"></Checkbox></template>
  7 + <template v-else>{{{ renderHeader(column, $index) }}}</template>
  8 + </div>
  9 + </th>
5 </tr> 10 </tr>
6 </thead> 11 </thead>
7 </template> 12 </template>
8 <script> 13 <script>
  14 + import Checkbox from '../checkbox/checkbox.vue';
  15 + import Mixin from './mixin';
  16 + import { deepCopy } from '../../utils/assist';
  17 +
9 export default { 18 export default {
  19 + mixins: [ Mixin ],
  20 + components: { Checkbox },
10 props: { 21 props: {
11 prefixCls: String, 22 prefixCls: String,
12 - columns: Array 23 + columns: Array,
  24 + cloneData: Array
13 }, 25 },
14 data () { 26 data () {
15 return { 27 return {
@@ -17,7 +29,9 @@ @@ -17,7 +29,9 @@
17 } 29 }
18 }, 30 },
19 computed: { 31 computed: {
20 - 32 + isSelectAll () {
  33 + return !this.cloneData.some(data => !data._isChecked);
  34 + }
21 }, 35 },
22 methods: { 36 methods: {
23 renderHeader (column, $index) { 37 renderHeader (column, $index) {
@@ -27,8 +41,18 @@ @@ -27,8 +41,18 @@
27 return column.title || '#'; 41 return column.title || '#';
28 } 42 }
29 }, 43 },
30 - fixedCls (column) {  
31 - return column.fixed ? `${this.prefixCls}-${column.fixed}` : ''; 44 + selectAll () {
  45 + const status = !this.isSelectAll;
  46 +
  47 + let tmpData = deepCopy(this.cloneData);
  48 + tmpData.forEach((data) => {
  49 + data._isChecked = status;
  50 + });
  51 + this.cloneData = tmpData;
  52 +
  53 + if (status) {
  54 + this.$parent.selectAll();
  55 + }
32 } 56 }
33 } 57 }
34 } 58 }
src/components/table/table.vue
1 <template> 1 <template>
2 <div :class="classes"> 2 <div :class="classes">
3 - <div :class="[prefixCls + '-header']">  
4 - <table cellspacing="0" cellpadding="0" border="0" :style="{width: tableWidth + 'px'}"> 3 + <div :class="[prefixCls + '-header']" v-if="showHeader">
  4 + <table cellspacing="0" cellpadding="0" border="0" :style="tableStyle">
5 <colgroup> 5 <colgroup>
6 <col v-for="column in columns" :width="setCellWidth(column, $index)"> 6 <col v-for="column in columns" :width="setCellWidth(column, $index)">
7 </colgroup> 7 </colgroup>
8 <thead 8 <thead
9 is="table-head" 9 is="table-head"
10 - :prefix-cls="prefixCls + '-thead'" 10 + :prefix-cls="prefixCls"
  11 + :clone-data.sync="cloneData"
11 :columns="columns"></thead> 12 :columns="columns"></thead>
12 </table> 13 </table>
13 </div> 14 </div>
14 <div :class="[prefixCls + '-body']"> 15 <div :class="[prefixCls + '-body']">
15 - <table cellspacing="0" cellpadding="0" border="0" :style="{width: tableWidth + 'px'}" v-el:tbody> 16 + <table cellspacing="0" cellpadding="0" border="0" :style="tableStyle" v-el:tbody>
16 <colgroup> 17 <colgroup>
17 <col v-for="column in columns" :width="setCellWidth(column, $index)"> 18 <col v-for="column in columns" :width="setCellWidth(column, $index)">
18 </colgroup> 19 </colgroup>
19 <tbody :class="[prefixCls + '-tbody']" v-el:render> 20 <tbody :class="[prefixCls + '-tbody']" v-el:render>
20 - <tr :class="[prefixCls + '-row']" v-for="(index, row) in data">  
21 - <td v-for="column in columns">{{{ renderRow(row, column) }}}</td> 21 + <tr
  22 + v-for="(index, row) in data"
  23 + :class="[prefixCls + '-row', {[prefixCls + '-row-highlight']: cloneData[index] && cloneData[index]._isHighlight}]"
  24 + @click.stop="highlightCurrentRow(index)">
  25 + <td v-for="column in columns" :class="alignCls(column)">
  26 + <div :class="[prefixCls + '-cell']">
  27 + <template v-if="column.type === 'selection'">
  28 + <Checkbox :checked="cloneData[index] && cloneData[index]._isChecked" @on-change="toggleSelect(index)"></Checkbox>
  29 + </template>
  30 + <template v-else>{{{ renderRow(row, column, index) }}}</template>
  31 + </div>
  32 + </td>
22 </tr> 33 </tr>
23 </tbody> 34 </tbody>
24 </table> 35 </table>
@@ -27,11 +38,14 @@ @@ -27,11 +38,14 @@
27 </template> 38 </template>
28 <script> 39 <script>
29 import TableHead from './table-head.vue'; 40 import TableHead from './table-head.vue';
30 - import { oneOf, getStyle } from '../../utils/assist'; 41 + import Checkbox from '../checkbox/checkbox.vue';
  42 + import Mixin from './mixin';
  43 + import { oneOf, getStyle, deepCopy } from '../../utils/assist';
31 const prefixCls = 'ivu-table'; 44 const prefixCls = 'ivu-table';
32 45
33 export default { 46 export default {
34 - components: { TableHead }, 47 + mixins: [ Mixin ],
  48 + components: { TableHead, Checkbox },
35 props: { 49 props: {
36 data: { 50 data: {
37 type: Array, 51 type: Array,
@@ -58,21 +72,11 @@ @@ -58,21 +72,11 @@
58 type: Boolean, 72 type: Boolean,
59 default: false 73 default: false
60 }, 74 },
61 - fit: {  
62 - type: Boolean,  
63 - default: true  
64 - },  
65 showHeader: { 75 showHeader: {
66 type: Boolean, 76 type: Boolean,
67 default: true 77 default: true
68 }, 78 },
69 - selection: {  
70 - validator (value) {  
71 - return oneOf(value, ['single', 'multiple', false]);  
72 - },  
73 - default: false  
74 - },  
75 - showIndex: { 79 + highlightRow: {
76 type: Boolean, 80 type: Boolean,
77 default: false 81 default: false
78 } 82 }
@@ -82,7 +86,8 @@ @@ -82,7 +86,8 @@
82 tableWidth: 0, 86 tableWidth: 0,
83 columnsWidth: [], 87 columnsWidth: [],
84 prefixCls: prefixCls, 88 prefixCls: prefixCls,
85 - compiledUids: [] 89 + compiledUids: [],
  90 + cloneData: deepCopy(this.data)
86 } 91 }
87 }, 92 },
88 computed: { 93 computed: {
@@ -90,14 +95,21 @@ @@ -90,14 +95,21 @@
90 return [ 95 return [
91 `${prefixCls}`, 96 `${prefixCls}`,
92 { 97 {
93 - [`${prefixCls}-${this.size}`]: !!this.size 98 + [`${prefixCls}-${this.size}`]: !!this.size,
  99 + [`${prefixCls}-border`]: this.border,
  100 + [`${prefixCls}-stripe`]: this.stripe
94 } 101 }
95 ] 102 ]
  103 + },
  104 + tableStyle () {
  105 + let style = {};
  106 + if (this.tableWidth !== 0) style.width = `${this.tableWidth}px`;
  107 + return style;
96 } 108 }
97 }, 109 },
98 methods: { 110 methods: {
99 - renderRow (row, column) {  
100 - return 'render' in column ? '' : row[column.key]; 111 + renderRow (row, column, index) {
  112 + return column.type === 'index' ? index + 1 : column.render ? '' : row[column.key];
101 }, 113 },
102 compileRender (update = false) { 114 compileRender (update = false) {
103 this.$nextTick(() => { 115 this.$nextTick(() => {
@@ -117,7 +129,7 @@ @@ -117,7 +129,7 @@
117 const column = this.columns[i]; 129 const column = this.columns[i];
118 if (column.render) { 130 if (column.render) {
119 for (let j = 0; j < this.data.length; j++) { 131 for (let j = 0; j < this.data.length; j++) {
120 - // todo 做一个缓存,只在需要改render时再重新编译,否则data改变时不用再编译 132 + // todo 做一个缓存,只在需要改render时再重新编译,data改变时不用再编译
121 const row = this.data[j]; 133 const row = this.data[j];
122 const template = column.render(row, column, j); 134 const template = column.render(row, column, j);
123 const cell = document.createElement('div'); 135 const cell = document.createElement('div');
@@ -129,8 +141,8 @@ @@ -129,8 +141,8 @@
129 if (_oldParentChildLen !== _newParentChildLen) { // if render normal html node, do not tag 141 if (_oldParentChildLen !== _newParentChildLen) { // if render normal html node, do not tag
130 this.compiledUids.push(this.$parent.$children[this.$parent.$children.length - 1]._uid); // tag it, and delete when data or columns update 142 this.compiledUids.push(this.$parent.$children[this.$parent.$children.length - 1]._uid); // tag it, and delete when data or columns update
131 } 143 }
132 - $el.children[j].children[i].innerHTML = '';  
133 - $el.children[j].children[i].appendChild(cell); 144 + $el.children[j].children[i].children[0].innerHTML = '';
  145 + $el.children[j].children[i].children[0].appendChild(cell);
134 } 146 }
135 } 147 }
136 } 148 }
@@ -148,6 +160,49 @@ @@ -148,6 +160,49 @@
148 }, 160 },
149 setCellWidth (column, index) { 161 setCellWidth (column, index) {
150 return column.width ? column.width : this.columnsWidth[index]; 162 return column.width ? column.width : this.columnsWidth[index];
  163 + },
  164 + highlightCurrentRow (index) {
  165 + if (!this.highlightRow || this.cloneData[index]._isHighlight) return;
  166 +
  167 + let oldIndex = -1;
  168 + this.cloneData.forEach((item, index) => {
  169 + if (item._isHighlight) {
  170 + oldIndex = index;
  171 + item._isHighlight = false;
  172 + return true;
  173 + }
  174 + });
  175 + const row = Object.assign({}, this.cloneData[index], {
  176 + _isHighlight: true
  177 + });
  178 + this.cloneData.$set(index, row);
  179 +
  180 + const oldData = oldIndex < 0 ? null : JSON.parse(JSON.stringify(this.data[oldIndex]));
  181 + this.$emit('on-current-change', JSON.parse(JSON.stringify(this.data[index])), oldData);
  182 + },
  183 + getSelection () {
  184 + let selectionIndexes = [];
  185 + this.cloneData.forEach((data, index) => {
  186 + if (data._isChecked) selectionIndexes.push(index);
  187 + });
  188 +
  189 + return JSON.parse(JSON.stringify(this.data.filter((data, index) => selectionIndexes.indexOf(index) > -1)));
  190 + },
  191 + toggleSelect (index) {
  192 + const status = !this.cloneData[index]._isChecked;
  193 + const row = Object.assign({}, this.cloneData[index], {
  194 + _isChecked: status
  195 + });
  196 + this.cloneData.$set(index, row);
  197 +
  198 + const selection = this.getSelection();
  199 + if (status) {
  200 + this.$emit('on-select', selection, JSON.parse(JSON.stringify(this.data[index])));
  201 + }
  202 + this.$emit('on-selection-change', selection);
  203 + },
  204 + selectAll () {
  205 + this.$emit('on-select-all', this.getSelection());
151 } 206 }
152 }, 207 },
153 ready () { 208 ready () {
@@ -160,6 +215,7 @@ @@ -160,6 +215,7 @@
160 watch: { 215 watch: {
161 data: { 216 data: {
162 handler () { 217 handler () {
  218 + this.cloneData = deepCopy(this.data);
163 this.compileRender(true); 219 this.compileRender(true);
164 }, 220 },
165 deep: true 221 deep: true
src/styles/components/table.less
1 @table-prefix-cls: ~"@{css-prefix}table"; 1 @table-prefix-cls: ~"@{css-prefix}table";
2 2
3 .@{table-prefix-cls} { 3 .@{table-prefix-cls} {
4 - position: relative;  
5 - overflow: hidden;  
6 - box-sizing: border-box; 4 + width: 100%;
7 max-width: 100%; 5 max-width: 100%;
8 - background-color: #fff;  
9 - border-collapse: collapse;  
10 - border: 1px solid @border-color-base; 6 + overflow: hidden;
11 color: @text-color; 7 color: @text-color;
12 font-size: @font-size-small; 8 font-size: @font-size-small;
  9 + background-color: #fff;
  10 + border: 1px solid @border-color-base;
  11 + border-bottom: 0;
  12 + border-collapse: collapse;
  13 + box-sizing: border-box;
  14 + position: relative;
13 15
14 - &-large {  
15 - font-size: @font-size-base; 16 + th, td
  17 + {
  18 + min-width: 0;
  19 + height: 48px;
  20 + box-sizing: border-box;
  21 + text-align: left;
  22 + text-overflow: ellipsis;
  23 + vertical-align: middle;
  24 + position: relative;
  25 + border-bottom: 1px solid @border-color-split;
16 } 26 }
17 27
18 - & th { 28 + th {
  29 + height: 40px;
19 white-space: nowrap; 30 white-space: nowrap;
20 overflow: hidden; 31 overflow: hidden;
  32 + background-color: @table-thead-bg;
  33 + }
  34 + td{
  35 + background-color: #fff;
  36 + transition: background-color @transition-time @ease-in-out;
21 } 37 }
22 38
23 - & th,  
24 - td 39 + th&-column,
  40 + td&-column
25 { 41 {
26 - min-width: 0;  
27 - height: 40px;  
28 - box-sizing: border-box;  
29 - text-align: left; 42 + &-left{
  43 + text-align: left;
  44 + }
  45 + &-center{
  46 + text-align: center;
  47 + }
  48 + &-right{
  49 + text-align: right;
  50 + }
  51 + }
  52 +
  53 + & table{
  54 + width: 100%;
  55 + }
  56 + &-border{
  57 + th,td{
  58 + border-right: 1px solid @border-color-split;
  59 + }
  60 + }
  61 + &-cell{
  62 + padding-left: 18px;
  63 + padding-right: 18px;
  64 + overflow: hidden;
30 text-overflow: ellipsis; 65 text-overflow: ellipsis;
31 - vertical-align: middle; 66 + white-space: normal;
  67 + word-break: break-all;
  68 + box-sizing: border-box;
  69 + }
  70 + th &-cell{
  71 + display: inline-block;
32 position: relative; 72 position: relative;
33 - border-bottom: 1px solid @border-color-base; 73 + word-wrap: normal;
  74 + vertical-align: middle;
  75 + }
  76 +
  77 + &-stripe &-body{
  78 + tr:nth-child(2n) {
  79 + td{
  80 + background-color: @table-td-stripe-bg;
  81 + }
  82 + }
  83 + }
  84 +
  85 + tr:hover{
  86 + td{
  87 + background-color: @table-td-hover-bg;
  88 + }
  89 + }
  90 +
  91 + &-large {
  92 + font-size: @font-size-base;
  93 + th{
  94 + height: 48px;
  95 + }
  96 + td{
  97 + height: 60px;
  98 + }
  99 + }
  100 +
  101 + &-small{
  102 + th{
  103 + height: 32px;
  104 + }
  105 + td{
  106 + height: 40px;
  107 + }
  108 + }
  109 +
  110 + &-row-highlight,
  111 + tr&-row-highlight:hover,
  112 + &-stripe &-body tr&-row-highlight:nth-child(2n)
  113 + {
  114 + td{
  115 + background-color: @table-td-highlight-bg;
  116 + }
34 } 117 }
35 } 118 }
36 \ No newline at end of file 119 \ No newline at end of file
src/styles/themes/default/custom.less
@@ -38,6 +38,10 @@ @@ -38,6 +38,10 @@
38 @background-color-select-hover: @input-disabled-bg; 38 @background-color-select-hover: @input-disabled-bg;
39 @tooltip-bg : rgba(70, 76, 91, .9); 39 @tooltip-bg : rgba(70, 76, 91, .9);
40 @head-bg : #f9fafc; 40 @head-bg : #f9fafc;
  41 +@table-thead-bg : #f5f7f9;
  42 +@table-td-stripe-bg : #f5f7f9;
  43 +@table-td-hover-bg : #ebf7ff;
  44 +@table-td-highlight-bg : #ebf7ff;
41 45
42 // Shadow 46 // Shadow
43 @shadow-color : rgba(0, 0, 0, .2); 47 @shadow-color : rgba(0, 0, 0, .2);
src/utils/assist.js
@@ -70,7 +70,7 @@ export function getStyle (element, styleName) { @@ -70,7 +70,7 @@ export function getStyle (element, styleName) {
70 styleName = 'cssFloat'; 70 styleName = 'cssFloat';
71 } 71 }
72 try { 72 try {
73 - var computed = document.defaultView.getComputedStyle(element, ''); 73 + const computed = document.defaultView.getComputedStyle(element, '');
74 return element.style[styleName] || computed ? computed[styleName] : null; 74 return element.style[styleName] || computed ? computed[styleName] : null;
75 } catch(e) { 75 } catch(e) {
76 return element.style[styleName]; 76 return element.style[styleName];
@@ -87,4 +87,48 @@ export function warnProp(component, prop, correctType, wrongType) { @@ -87,4 +87,48 @@ export function warnProp(component, prop, correctType, wrongType) {
87 correctType = firstUpperCase(correctType); 87 correctType = firstUpperCase(correctType);
88 wrongType = firstUpperCase(wrongType); 88 wrongType = firstUpperCase(wrongType);
89 console.error(`[iView warn]: Invalid prop: type check failed for prop ${prop}. Expected ${correctType}, got ${wrongType}. (found in component: ${component})`); 89 console.error(`[iView warn]: Invalid prop: type check failed for prop ${prop}. Expected ${correctType}, got ${wrongType}. (found in component: ${component})`);
90 -}  
91 \ No newline at end of file 90 \ No newline at end of file
  91 +}
  92 +
  93 +function typeOf(obj) {
  94 + const toString = Object.prototype.toString;
  95 + const map = {
  96 + '[object Boolean]' : 'boolean',
  97 + '[object Number]' : 'number',
  98 + '[object String]' : 'string',
  99 + '[object Function]' : 'function',
  100 + '[object Array]' : 'array',
  101 + '[object Date]' : 'date',
  102 + '[object RegExp]' : 'regExp',
  103 + '[object Undefined]': 'undefined',
  104 + '[object Null]' : 'null',
  105 + '[object Object]' : 'object'
  106 + };
  107 + return map[toString.call(obj)];
  108 +}
  109 +
  110 +// deepCopy
  111 +function deepCopy(data) {
  112 + const t = typeOf(data);
  113 + let o;
  114 +
  115 + if (t === 'array') {
  116 + o = [];
  117 + } else if ( t === 'object') {
  118 + o = {};
  119 + } else {
  120 + return data;
  121 + }
  122 +
  123 + if (t === 'array') {
  124 + for (let i = 0; i < data.length; i++) {
  125 + o.push(deepCopy(data[i]));
  126 + }
  127 + } else if ( t === 'object') {
  128 + for (let i in data) {
  129 + o[i] = deepCopy(data[i]);
  130 + }
  131 + }
  132 + return o;
  133 +}
  134 +
  135 +export {deepCopy}
92 \ No newline at end of file 136 \ No newline at end of file
test/routers/table.vue
1 <template> 1 <template>
2 <div> 2 <div>
3 - <i-table :columns="columns" :data="data"></i-table> 3 + <!--<i-table size="large" border stripe :columns="columns" :data="data"></i-table>-->
  4 + <br>
  5 + <i-table border :columns="columns" :data="data" @on-current-change="current" @on-select="select" @on-selection-change="schange" @on-select-all="sall"></i-table>
  6 + <br>
  7 + <!--<i-table size="small" border stripe :columns="columns" :data="data"></i-table>-->
4 </div> 8 </div>
5 </template> 9 </template>
6 <script> 10 <script>
@@ -12,15 +16,19 @@ @@ -12,15 +16,19 @@
12 return { 16 return {
13 columns: [ 17 columns: [
14 { 18 {
  19 + type: 'selection',
  20 + width: 50
  21 + },
  22 + {
15 title: '姓名', 23 title: '姓名',
16 key: 'name', 24 key: 'name',
17 - fixed: 'left', 25 + align: 'left',
18 // width: 100 26 // width: 100
19 }, 27 },
20 { 28 {
21 title: '年龄', 29 title: '年龄',
22 key: 'age', 30 key: 'age',
23 - fixed: 'right', 31 + align: 'right',
24 // width: 100 32 // width: 100
25 // render (row) { 33 // render (row) {
26 // return `<i-button>${row.age}</i-button>` 34 // return `<i-button>${row.age}</i-button>`
@@ -29,7 +37,7 @@ @@ -29,7 +37,7 @@
29 { 37 {
30 title: '地址', 38 title: '地址',
31 key: 'address', 39 key: 'address',
32 - fixed: 'center', 40 + align: 'center',
33 // width: 100 41 // width: 100
34 // render (row, column, index) { 42 // render (row, column, index) {
35 // if (row.edit) { 43 // if (row.edit) {
@@ -65,7 +73,13 @@ @@ -65,7 +73,13 @@
65 name: '刘天娇', 73 name: '刘天娇',
66 age: 27, 74 age: 27,
67 address: '北京市东城区', 75 address: '北京市东城区',
68 - edit: true 76 + edit: false
  77 + },
  78 + {
  79 + name: '胡国伟',
  80 + age: 28,
  81 + address: '北京市西城区',
  82 + edit: false
69 } 83 }
70 ] 84 ]
71 } 85 }
@@ -78,12 +92,27 @@ @@ -78,12 +92,27 @@
78 this.$Message.info(name); 92 this.$Message.info(name);
79 }, 93 },
80 edit (index) { 94 edit (index) {
81 - this.data[index].edit = true; 95 +// this.data[index].edit = true;
  96 + this.$Message.info(this.data[index].name);
  97 + },
  98 + current (newData, oldData) {
  99 + console.log(newData);
  100 + console.log(oldData);
  101 + },
  102 + select (a,b){
  103 + console.log(a);
  104 + console.log(b);
  105 + },
  106 + schange (a) {
  107 + console.log(a)
  108 + },
  109 + sall (a) {
  110 + console.log(a)
82 } 111 }
83 }, 112 },
84 ready () { 113 ready () {
85 setTimeout(() => { 114 setTimeout(() => {
86 - return; 115 +// return
87 this.data.push({ 116 this.data.push({
88 name: '刘天娇2', 117 name: '刘天娇2',
89 age: 272, 118 age: 272,