Commit 0d13646576cd1ce46e506c5bd7e1fd1a3d24c72f
1 parent
744eb0af
update Table
update Table
Showing
7 changed files
with
304 additions
and
57 deletions
Show diff stats
src/components/table/table-head.vue
1 | 1 | <template> |
2 | 2 | <thead> |
3 | 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 | 10 | </tr> |
6 | 11 | </thead> |
7 | 12 | </template> |
8 | 13 | <script> |
14 | + import Checkbox from '../checkbox/checkbox.vue'; | |
15 | + import Mixin from './mixin'; | |
16 | + import { deepCopy } from '../../utils/assist'; | |
17 | + | |
9 | 18 | export default { |
19 | + mixins: [ Mixin ], | |
20 | + components: { Checkbox }, | |
10 | 21 | props: { |
11 | 22 | prefixCls: String, |
12 | - columns: Array | |
23 | + columns: Array, | |
24 | + cloneData: Array | |
13 | 25 | }, |
14 | 26 | data () { |
15 | 27 | return { |
... | ... | @@ -17,7 +29,9 @@ |
17 | 29 | } |
18 | 30 | }, |
19 | 31 | computed: { |
20 | - | |
32 | + isSelectAll () { | |
33 | + return !this.cloneData.some(data => !data._isChecked); | |
34 | + } | |
21 | 35 | }, |
22 | 36 | methods: { |
23 | 37 | renderHeader (column, $index) { |
... | ... | @@ -27,8 +41,18 @@ |
27 | 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 | 1 | <template> |
2 | 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 | 5 | <colgroup> |
6 | 6 | <col v-for="column in columns" :width="setCellWidth(column, $index)"> |
7 | 7 | </colgroup> |
8 | 8 | <thead |
9 | 9 | is="table-head" |
10 | - :prefix-cls="prefixCls + '-thead'" | |
10 | + :prefix-cls="prefixCls" | |
11 | + :clone-data.sync="cloneData" | |
11 | 12 | :columns="columns"></thead> |
12 | 13 | </table> |
13 | 14 | </div> |
14 | 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 | 17 | <colgroup> |
17 | 18 | <col v-for="column in columns" :width="setCellWidth(column, $index)"> |
18 | 19 | </colgroup> |
19 | 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 | 33 | </tr> |
23 | 34 | </tbody> |
24 | 35 | </table> |
... | ... | @@ -27,11 +38,14 @@ |
27 | 38 | </template> |
28 | 39 | <script> |
29 | 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 | 44 | const prefixCls = 'ivu-table'; |
32 | 45 | |
33 | 46 | export default { |
34 | - components: { TableHead }, | |
47 | + mixins: [ Mixin ], | |
48 | + components: { TableHead, Checkbox }, | |
35 | 49 | props: { |
36 | 50 | data: { |
37 | 51 | type: Array, |
... | ... | @@ -58,21 +72,11 @@ |
58 | 72 | type: Boolean, |
59 | 73 | default: false |
60 | 74 | }, |
61 | - fit: { | |
62 | - type: Boolean, | |
63 | - default: true | |
64 | - }, | |
65 | 75 | showHeader: { |
66 | 76 | type: Boolean, |
67 | 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 | 80 | type: Boolean, |
77 | 81 | default: false |
78 | 82 | } |
... | ... | @@ -82,7 +86,8 @@ |
82 | 86 | tableWidth: 0, |
83 | 87 | columnsWidth: [], |
84 | 88 | prefixCls: prefixCls, |
85 | - compiledUids: [] | |
89 | + compiledUids: [], | |
90 | + cloneData: deepCopy(this.data) | |
86 | 91 | } |
87 | 92 | }, |
88 | 93 | computed: { |
... | ... | @@ -90,14 +95,21 @@ |
90 | 95 | return [ |
91 | 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 | 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 | 114 | compileRender (update = false) { |
103 | 115 | this.$nextTick(() => { |
... | ... | @@ -117,7 +129,7 @@ |
117 | 129 | const column = this.columns[i]; |
118 | 130 | if (column.render) { |
119 | 131 | for (let j = 0; j < this.data.length; j++) { |
120 | - // todo 做一个缓存,只在需要改render时再重新编译,否则data改变时不用再编译 | |
132 | + // todo 做一个缓存,只在需要改render时再重新编译,data改变时不用再编译 | |
121 | 133 | const row = this.data[j]; |
122 | 134 | const template = column.render(row, column, j); |
123 | 135 | const cell = document.createElement('div'); |
... | ... | @@ -129,8 +141,8 @@ |
129 | 141 | if (_oldParentChildLen !== _newParentChildLen) { // if render normal html node, do not tag |
130 | 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 | 160 | }, |
149 | 161 | setCellWidth (column, index) { |
150 | 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 | 208 | ready () { |
... | ... | @@ -160,6 +215,7 @@ |
160 | 215 | watch: { |
161 | 216 | data: { |
162 | 217 | handler () { |
218 | + this.cloneData = deepCopy(this.data); | |
163 | 219 | this.compileRender(true); |
164 | 220 | }, |
165 | 221 | deep: true | ... | ... |
src/styles/components/table.less
1 | 1 | @table-prefix-cls: ~"@{css-prefix}table"; |
2 | 2 | |
3 | 3 | .@{table-prefix-cls} { |
4 | - position: relative; | |
5 | - overflow: hidden; | |
6 | - box-sizing: border-box; | |
4 | + width: 100%; | |
7 | 5 | max-width: 100%; |
8 | - background-color: #fff; | |
9 | - border-collapse: collapse; | |
10 | - border: 1px solid @border-color-base; | |
6 | + overflow: hidden; | |
11 | 7 | color: @text-color; |
12 | 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 | 30 | white-space: nowrap; |
20 | 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 | 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 | 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 | 119 | \ No newline at end of file | ... | ... |
src/styles/themes/default/custom.less
... | ... | @@ -38,6 +38,10 @@ |
38 | 38 | @background-color-select-hover: @input-disabled-bg; |
39 | 39 | @tooltip-bg : rgba(70, 76, 91, .9); |
40 | 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 | 46 | // Shadow |
43 | 47 | @shadow-color : rgba(0, 0, 0, .2); | ... | ... |
src/utils/assist.js
... | ... | @@ -70,7 +70,7 @@ export function getStyle (element, styleName) { |
70 | 70 | styleName = 'cssFloat'; |
71 | 71 | } |
72 | 72 | try { |
73 | - var computed = document.defaultView.getComputedStyle(element, ''); | |
73 | + const computed = document.defaultView.getComputedStyle(element, ''); | |
74 | 74 | return element.style[styleName] || computed ? computed[styleName] : null; |
75 | 75 | } catch(e) { |
76 | 76 | return element.style[styleName]; |
... | ... | @@ -87,4 +87,48 @@ export function warnProp(component, prop, correctType, wrongType) { |
87 | 87 | correctType = firstUpperCase(correctType); |
88 | 88 | wrongType = firstUpperCase(wrongType); |
89 | 89 | console.error(`[iView warn]: Invalid prop: type check failed for prop ${prop}. Expected ${correctType}, got ${wrongType}. (found in component: ${component})`); |
90 | -} | |
91 | 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 | 136 | \ No newline at end of file | ... | ... |
test/routers/table.vue
1 | 1 | <template> |
2 | 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 | 8 | </div> |
5 | 9 | </template> |
6 | 10 | <script> |
... | ... | @@ -12,15 +16,19 @@ |
12 | 16 | return { |
13 | 17 | columns: [ |
14 | 18 | { |
19 | + type: 'selection', | |
20 | + width: 50 | |
21 | + }, | |
22 | + { | |
15 | 23 | title: '姓名', |
16 | 24 | key: 'name', |
17 | - fixed: 'left', | |
25 | + align: 'left', | |
18 | 26 | // width: 100 |
19 | 27 | }, |
20 | 28 | { |
21 | 29 | title: '年龄', |
22 | 30 | key: 'age', |
23 | - fixed: 'right', | |
31 | + align: 'right', | |
24 | 32 | // width: 100 |
25 | 33 | // render (row) { |
26 | 34 | // return `<i-button>${row.age}</i-button>` |
... | ... | @@ -29,7 +37,7 @@ |
29 | 37 | { |
30 | 38 | title: '地址', |
31 | 39 | key: 'address', |
32 | - fixed: 'center', | |
40 | + align: 'center', | |
33 | 41 | // width: 100 |
34 | 42 | // render (row, column, index) { |
35 | 43 | // if (row.edit) { |
... | ... | @@ -65,7 +73,13 @@ |
65 | 73 | name: '刘天娇', |
66 | 74 | age: 27, |
67 | 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 | 92 | this.$Message.info(name); |
79 | 93 | }, |
80 | 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 | 113 | ready () { |
85 | 114 | setTimeout(() => { |
86 | - return; | |
115 | +// return | |
87 | 116 | this.data.push({ |
88 | 117 | name: '刘天娇2', |
89 | 118 | age: 272, | ... | ... |