evui 2.0.9 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/dist/0b8d1200e71cae8d747dce4e69c4efb6.png +0 -0
  2. package/dist/1.css +4 -0
  3. package/dist/1.css.map +1 -0
  4. package/dist/1.evui.min.js +2 -0
  5. package/dist/1.evui.min.js.map +1 -0
  6. package/dist/1ba679c05036b34bf359aa2e6c450faa.ttf +0 -0
  7. package/dist/2.css +4 -0
  8. package/dist/2.css.map +1 -0
  9. package/dist/2.evui.min.js +2 -0
  10. package/dist/2.evui.min.js.map +1 -0
  11. package/dist/278156e41e0ad908cf7f841b17130502.woff2 +0 -0
  12. package/dist/3.evui.min.js +2 -0
  13. package/dist/3.evui.min.js.map +1 -0
  14. package/dist/32be89b11725274cd3e801192ba88361.ttf +0 -0
  15. package/dist/38c6d8bab26db77d8c806813e1497763.woff2 +0 -0
  16. package/dist/4.evui.min.js +2 -0
  17. package/dist/4.evui.min.js.map +1 -0
  18. package/dist/425399f81e4ce7cbd967685402ba0260.woff +0 -0
  19. package/dist/4730076470a665bbc7b783c56d29a72e.svg +261 -0
  20. package/dist/52e9a7f6ff3af5ad261e5292d07ebdca.eot +0 -0
  21. package/dist/5367103510b27b78482794590e1ce3b0.ttf +0 -0
  22. package/dist/57e963e3d6dd0a9cf05150b40eebf69b.svg +1008 -0
  23. package/dist/65a2fb6d9aaa164b41a039302093995b.ttf +0 -0
  24. package/dist/687a4990ea22bb1a49d469a5d9319790.woff2 +0 -0
  25. package/dist/6c1d906bf5ba48676f65b2d65e935e1a.ttf +0 -0
  26. package/dist/6dafca5a4f1e31f2bdf11939b24ff422.ttf +0 -0
  27. package/dist/752905fa5edf21fc52a10a0c1ca9c7a4.eot +0 -0
  28. package/dist/76c05d80dda67cdc5d03f345b7bd063f.ttf +0 -0
  29. package/dist/7d62eb50e7bb05eedb2a4656f7fe8f3b.svg +366 -0
  30. package/dist/a01e3f2d6c83dc3aee175e2482b3f777.eot +0 -0
  31. package/dist/b30fd8419d7e6d5918856c7531d33482.svg +1518 -0
  32. package/dist/c57dd55fa982e8940f69ca1d69a8a999.woff +0 -0
  33. package/dist/c656b8caa454ed19b9a2ef7f4f5b8fea.ttf +0 -0
  34. package/dist/cac87dc00c87a5d74711d0276713808a.woff +0 -0
  35. package/dist/d68fa3e67dbb653a13cec44b1bcabcfe.eot +0 -0
  36. package/dist/ddae9b1ba9b0b42f58809904b0b21349.woff +0 -0
  37. package/dist/evui.min.js +19 -0
  38. package/dist/evui.min.js.gz +0 -0
  39. package/dist/evui.min.js.map +1 -0
  40. package/dist/main.css +85 -0
  41. package/dist/main.css.gz +0 -0
  42. package/dist/main.css.map +1 -0
  43. package/package.json +56 -76
  44. package/src/common/emitter.js +20 -0
  45. package/src/common/utils.debounce.js +223 -0
  46. package/src/common/utils.js +51 -17
  47. package/src/common/utils.throttle.js +83 -0
  48. package/src/common/utils.tree.js +18 -0
  49. package/src/components/button/button.vue +317 -241
  50. package/src/components/chart/chart.core.js +378 -85
  51. package/src/components/chart/chart.vue +133 -115
  52. package/src/components/chart/element/element.bar.js +219 -25
  53. package/src/components/chart/element/element.bar.time.js +115 -0
  54. package/src/components/chart/element/element.line.js +172 -21
  55. package/src/components/chart/element/element.pie.js +86 -0
  56. package/src/components/chart/element/element.scatter.js +9 -2
  57. package/src/components/chart/element/element.tip.js +356 -0
  58. package/src/components/chart/helpers/helpers.canvas.js +94 -0
  59. package/src/components/chart/helpers/helpers.constant.js +25 -6
  60. package/src/components/chart/helpers/helpers.util.js +83 -38
  61. package/src/components/chart/index.js +0 -1
  62. package/src/components/chart/model/model.series.js +43 -14
  63. package/src/components/chart/model/model.store.js +440 -46
  64. package/src/components/chart/plugins/plugins.interaction.js +324 -0
  65. package/src/components/chart/plugins/plugins.legend.js +233 -91
  66. package/src/components/chart/plugins/plugins.pie.js +179 -0
  67. package/src/components/chart/plugins/plugins.title.js +25 -2
  68. package/src/components/chart/plugins/plugins.tooltip.js +384 -0
  69. package/src/components/chart/scale/scale.js +91 -29
  70. package/src/components/chart/scale/scale.linear.js +12 -0
  71. package/src/components/chart/scale/scale.logarithmic.js +25 -0
  72. package/src/components/chart/scale/scale.step.js +89 -52
  73. package/src/components/chart/scale/scale.time.category.js +204 -0
  74. package/src/components/chart/scale/scale.time.js +19 -1
  75. package/src/components/checkbox/checkbox-group.vue +15 -11
  76. package/src/components/checkbox/checkbox.vue +210 -138
  77. package/src/components/codeview/code.vue +42 -29
  78. package/src/components/contextmenu/contextmenu.child.vue +79 -0
  79. package/src/components/contextmenu/contextmenu.vue +276 -0
  80. package/src/components/contextmenu/contextmenu.wrap.vue +189 -0
  81. package/src/components/contextmenu/index.js +3 -0
  82. package/src/components/datepicker/calendar.core.js +588 -492
  83. package/src/components/datepicker/calendar.vue +0 -3
  84. package/src/components/datepicker/datepicker.vue +43 -15
  85. package/src/components/datepicker/index.js +5 -1
  86. package/src/components/grid/grid.filter.vue +290 -0
  87. package/src/components/grid/grid.filter.window.vue +411 -0
  88. package/src/components/grid/grid.render.vue +45 -0
  89. package/src/components/grid/grid.vue +1338 -0
  90. package/src/components/icon/icon.vue +23 -7
  91. package/src/components/input/input.number.vue +309 -277
  92. package/src/components/label/label.vue +2 -2
  93. package/src/components/loadingmask/loadingmask.vue +6 -13
  94. package/src/components/loginfield/loginfield.vue +46 -37
  95. package/src/components/markdown/index.js +3 -0
  96. package/src/components/markdown/markdown.vue +1001 -0
  97. package/src/components/menu/index.js +1 -3
  98. package/src/components/menu/menu.nav.item.vue +115 -0
  99. package/src/components/menu/menu.nav.sub.vue +42 -0
  100. package/src/components/menu/menu.nav.vue +71 -98
  101. package/src/components/message/index.js +3 -0
  102. package/src/components/message/message.js +63 -0
  103. package/src/components/message/message.vue +191 -0
  104. package/src/components/message-box/index.js +3 -0
  105. package/src/components/message-box/message-box.js +31 -0
  106. package/src/components/message-box/message-box.vue +299 -0
  107. package/src/components/notification/index.js +3 -0
  108. package/src/components/notification/notification.js +75 -0
  109. package/src/components/notification/notification.vue +242 -0
  110. package/src/components/radio/radio-group.vue +6 -2
  111. package/src/components/radio/radio.vue +156 -76
  112. package/src/components/selectbox/dropdown.vue +86 -40
  113. package/src/components/selectbox/listbox.vue +47 -18
  114. package/src/components/selectbox/option.vue +1 -1
  115. package/src/components/selectbox/selectbox.vue +304 -316
  116. package/src/components/slider/slider-tooltip.vue +7 -7
  117. package/src/components/slider/slider.vue +20 -25
  118. package/src/components/splitter/splitter.vue +104 -94
  119. package/src/components/table/table.black.css +1 -1
  120. package/src/components/table/table.filter.lite.vue +7 -7
  121. package/src/components/table/table.filter.vue +1 -1
  122. package/src/components/table/table.grey.css +5 -6
  123. package/src/components/table/table.navy.css +1 -1
  124. package/src/components/table/table.vue +55 -48
  125. package/src/components/tabs/tab-panel.vue +19 -5
  126. package/src/components/tabs/tabs.vue +182 -87
  127. package/src/components/textfield/textfield.vue +110 -87
  128. package/src/components/timepicker/index.js +2 -2
  129. package/src/components/timepicker/spinner.vue +15 -17
  130. package/src/components/timepicker/timepicker.vue +98 -53
  131. package/src/components/toggle/toggle.vue +148 -109
  132. package/src/components/tree/index.js +2 -6
  133. package/src/components/tree/render.js +17 -0
  134. package/src/components/tree/tree-node.vue +214 -0
  135. package/src/components/tree/tree.vue +296 -0
  136. package/src/components/tree-table/index.js +7 -0
  137. package/src/components/{tree → tree-table}/tree.table.black.css +0 -0
  138. package/src/components/{tree → tree-table}/tree.table.grey.css +0 -0
  139. package/src/components/{tree → tree-table}/tree.table.vue +36 -41
  140. package/src/components/{tree → tree-table}/tree.util.js +0 -0
  141. package/src/components/window/window.vue +238 -191
  142. package/src/index.js +25 -12
  143. package/src/styles/base/base.scss +50 -0
  144. package/src/styles/base/index.scss +1 -0
  145. package/src/styles/default.scss +5 -0
  146. package/src/styles/{codemirror.css → lib/codemirror.css} +0 -0
  147. package/src/styles/{all.css → lib/fontawesome.css} +1 -1
  148. package/src/styles/lib/icon.css +792 -0
  149. package/src/styles/themes/index.scss +2 -0
  150. package/src/styles/themes/mixin.scss +33 -0
  151. package/src/styles/themes/variables.scss +206 -0
  152. package/src/styles/utils/colors.scss +222 -0
  153. package/src/styles/utils/index.scss +2 -0
  154. package/src/styles/utils/mixins.scss +34 -0
  155. package/src/styles/utils/variables.scss +27 -0
  156. package/src/webfonts/EVUI.eot +0 -0
  157. package/src/webfonts/EVUI.svg +251 -173
  158. package/src/webfonts/EVUI.ttf +0 -0
  159. package/src/webfonts/EVUI.woff +0 -0
  160. package/src/webfonts/Roboto-Bold.ttf +0 -0
  161. package/src/webfonts/Roboto-Medium.ttf +0 -0
  162. package/src/webfonts/Roboto-Regular.ttf +0 -0
  163. package/src/components/chart/charts/chart.bar.js +0 -334
  164. package/src/components/chart/charts/chart.base.js +0 -1075
  165. package/src/components/chart/charts/chart.line.js +0 -262
  166. package/src/components/chart/charts/chart.pie.js +0 -383
  167. package/src/components/chart/charts/chart.scatter.js +0 -349
  168. package/src/components/chart/charts/chart.sunburst.js +0 -193
  169. package/src/components/chart/core/axis/axis.js +0 -217
  170. package/src/components/chart/core/axis/axis.scale.auto.js +0 -69
  171. package/src/components/chart/core/axis/axis.scale.fixed.js +0 -65
  172. package/src/components/chart/core/axis/axis.scale.steps.js +0 -149
  173. package/src/components/chart/core/core.constant.js +0 -116
  174. package/src/components/chart/core/core.legend.js +0 -473
  175. package/src/components/chart/core/core.util.js +0 -66
  176. package/src/components/chart/core/data/data.js +0 -412
  177. package/src/components/chart/core/data/data.pie.js +0 -70
  178. package/src/components/chart/core/data/data.stack.js +0 -222
  179. package/src/components/chart/core/data/data.sunburst.js +0 -172
  180. package/src/components/menu/menu.context.children.vue +0 -201
  181. package/src/components/menu/menu.context.vue +0 -144
  182. package/src/components/tabs/jun/tab.vue +0 -123
  183. package/src/components/tabs/jun/tabs.vue +0 -484
  184. package/src/styles/evui.css +0 -386
  185. package/src/styles/icon.css +0 -557
@@ -0,0 +1,1338 @@
1
+ <template>
2
+ <div
3
+ v-cloak
4
+ v-resize.debounce="onResize"
5
+ v-observe-visibility="{
6
+ callback: onShow,
7
+ once: true,
8
+ }"
9
+ :class="getTableClass"
10
+ >
11
+ <div
12
+ v-show="showHeader"
13
+ ref="header"
14
+ class="table-header"
15
+ >
16
+ <ul class="column-list">
17
+ <li
18
+ v-if="useCheckbox.use"
19
+ style="width: 40px;"
20
+ class="column"
21
+ >
22
+ <ev-checkbox
23
+ v-if="isHeaderCheckBox"
24
+ v-model="isHeaderChecked"
25
+ :type="`square`"
26
+ :after-type="`check`"
27
+ @on-click="onCheckAll"
28
+ />
29
+ </li><li
30
+ v-for="(column, index) in orderedColumns"
31
+ v-if="!column.hide"
32
+ :key="index"
33
+ :data-index="index"
34
+ :style="`width: ${column.width}px;`"
35
+ :class="{
36
+ column: true,
37
+ render: isRenderer(column),
38
+ }"
39
+ >
40
+ <span
41
+ v-if="isFiltering &&
42
+ filterList[column.field] &&
43
+ filterList[column.field].find(item => item.use)"
44
+ class="column-filter-status"
45
+ >
46
+ <ev-icon :cls="'ei-filter'"/>
47
+ </span>
48
+ <span
49
+ :title="column.caption"
50
+ class="column-name"
51
+ @click.stop="onSort(column.field)"
52
+ >{{ column.caption }}</span>
53
+ <ev-icon
54
+ v-if="sortField === column.field"
55
+ :cls="`${sortOrder === 'desc' ? 'ei-text-vertical' : 'ei-text-up'} sort-icon`"
56
+ />
57
+ <span
58
+ v-if="isFiltering"
59
+ class="column-filter"
60
+ @click.stop.prevent="onClickFilter(column)"
61
+ >
62
+ <ev-icon :cls="'ei-filter-list set-filter-icon'"/>
63
+ </span>
64
+ <span
65
+ class="column-resize"
66
+ @mousedown.stop.left="onColumnResize(index, $event)"
67
+ />
68
+ </li>
69
+ <li
70
+ :style="`width: ${hasVerticalScrollBar ? scrollWidth : 0}px;`"
71
+ class="column-dummy"
72
+ />
73
+ </ul>
74
+ </div>
75
+ <div
76
+ ref="body"
77
+ :class="{
78
+ 'table-body': true,
79
+ stripe: stripeRows
80
+ }"
81
+ @scroll="onScroll"
82
+ @contextmenu="onContextMenu($event)"
83
+ >
84
+ <div
85
+ :style="`height: ${vScrollTopHeight}px;`"
86
+ class="vscroll-spacer"
87
+ />
88
+ <table>
89
+ <tbody>
90
+ <tr
91
+ v-if="!viewStore.length"
92
+ class="dummy"
93
+ >
94
+ <td
95
+ v-if="useCheckbox.use"
96
+ :style="`width: 40px; height: ${rowHeight}px; line-height: ${rowHeight}px`"
97
+ />
98
+ <td
99
+ v-for="(column, cellIndex) in orderedColumns"
100
+ v-show="!column.hide"
101
+ :key="cellIndex"
102
+ :style="`
103
+ width: ${column.width}px; height: ${rowHeight}px; line-height: ${rowHeight}px`"
104
+ />
105
+ </tr>
106
+ <tr
107
+ v-for="(row, rowIndex) in viewStore"
108
+ :key="rowIndex"
109
+ :data-index="rowIndex"
110
+ :class="{
111
+ selected: row[2] === selectedRow,
112
+ }"
113
+ @click="onRowClick($event, row)"
114
+ @dblclick="onRowDblClick($event, row)"
115
+ >
116
+ <td
117
+ v-if="useCheckbox.use"
118
+ :style="`width: 40px; height: ${rowHeight}px;`"
119
+ class="row-checkbox"
120
+ >
121
+ <ev-checkbox
122
+ v-model="row[1]"
123
+ :type="`square`"
124
+ :after-type="`check`"
125
+ @on-click="onCheck($event, row)"
126
+ @click.native.stop=""
127
+ />
128
+ </td>
129
+ <td
130
+ v-for="(column, cellIndex) in orderedColumns"
131
+ v-if="!column.hide"
132
+ :key="cellIndex"
133
+ :data-name="column.field"
134
+ :data-index="column.index"
135
+ :class="{
136
+ [column.type]: column.type,
137
+ [column.align]: column.align,
138
+ render: isRenderer(column),
139
+ }"
140
+ :style="
141
+ `width: ${column.width}px; height: ${rowHeight}px; line-height: ${rowHeight}px`"
142
+ >
143
+ <Renderer
144
+ v-if="isRenderer(column)"
145
+ :name="column.field"
146
+ :item="{
147
+ row: row[2],
148
+ rowIndex: row[0],
149
+ cellIndex: column.index,
150
+ value: row[2][column.index],
151
+ props: column.render.props,
152
+ }"
153
+ />
154
+ <span
155
+ v-else
156
+ :title="getConvertValue(column.type, row[2][column.index])"
157
+ >{{ getConvertValue(column.type, row[2][column.index]) }}</span>
158
+ </td>
159
+ </tr>
160
+ </tbody>
161
+ </table>
162
+ <div
163
+ :style="`height: ${vScrollBottomHeight}px;`"
164
+ class="vscroll-spacer"
165
+ />
166
+ <ev-context-menu
167
+ v-show="showContextMenu"
168
+ :items="contextMenuItems"
169
+ @click="onClickCtxMenu"
170
+ />
171
+ </div>
172
+ <div
173
+ v-show="showResizeLine"
174
+ ref="resizeLine"
175
+ class="table-resize-line"
176
+ />
177
+ <filter-window
178
+ v-show="showFilterWindow"
179
+ :is-show="showFilterWindow"
180
+ :target-column="currentFilter.column"
181
+ :filter-items="currentFilter.items"
182
+ @apply-filter="onApplyFilter"
183
+ @before-close="onCloseFilterWindow"
184
+ />
185
+ </div>
186
+ </template>
187
+ <script>
188
+ import resize from 'vue-resize-directive';
189
+ import { ObserveVisibility } from 'vue-observe-visibility';
190
+ import { uniqBy, isEqual } from 'lodash-es';
191
+ import { numberWithComma } from '@/common/utils';
192
+ import FilterWindow from './grid.filter.window';
193
+ import Renderer from './grid.render';
194
+
195
+ const ROW_INDEX = 0;
196
+ const ROW_CHECK_INDEX = 1;
197
+ const ROW_DATA_INDEX = 2;
198
+
199
+ export default {
200
+ name: 'EvGrid',
201
+ directives: {
202
+ resize,
203
+ ObserveVisibility,
204
+ },
205
+ components: {
206
+ FilterWindow,
207
+ Renderer,
208
+ },
209
+ props: {
210
+ /**
211
+ * 컬럼 정보 목록
212
+ */
213
+ columns: {
214
+ type: Array,
215
+ default: () => [],
216
+ },
217
+ /**
218
+ * row 데이터
219
+ */
220
+ rows: {
221
+ type: Array,
222
+ default: () => [],
223
+ },
224
+ /**
225
+ * 선택된 row 데이터 (sync)
226
+ */
227
+ selected: {
228
+ type: Array,
229
+ default: () => [],
230
+ },
231
+ /**
232
+ * 체크된 row 데이터 (sync)
233
+ */
234
+ checked: {
235
+ type: Array,
236
+ default: () => [],
237
+ },
238
+ /**
239
+ * 그리드 옵션 정보
240
+ */
241
+ option: {
242
+ type: Object,
243
+ default: () => ({}),
244
+ },
245
+ },
246
+ data() {
247
+ return {
248
+ originStore: [],
249
+ filteredStore: [],
250
+ viewStore: [],
251
+ orderedColumns: [],
252
+ sortOrder: 'desc',
253
+ sortField: '',
254
+ adjust: this.option.adjust || false,
255
+ stripeRows: this.option.stripeRows || false,
256
+ showHeader: this.option.showHeader === undefined ? true : this.option.showHeader,
257
+ useSelect: this.option.useSelect === undefined ? true : this.option.useSelect,
258
+ useCheckbox: this.option.useCheckbox || {},
259
+ customContextMenu: this.option.customContextMenu || [],
260
+ useFilter: this.option.useFilter === undefined ? true : this.option.useFilter,
261
+ rowHeight: this.option.rowHeight || 32,
262
+ columnWidth: this.option.columnWidth || 80,
263
+ scrollWidth: this.option.scrollWidth || 16,
264
+ lastScroll: {},
265
+ vScrollTopHeight: 0,
266
+ vScrollBottomHeight: 0,
267
+ hasVerticalScrollBar: false,
268
+ showColumnOption: false,
269
+ showResizeLine: false,
270
+ showFilterWindow: false,
271
+ contextMenuItems: [],
272
+ currentFilter: { column: {}, items: [] },
273
+ sortList: {},
274
+ filterList: {},
275
+ selectedRow: this.selected,
276
+ checkedRows: this.checked,
277
+ prevCheckedRow: [],
278
+ isHeaderChecked: false,
279
+ isClickedCtxMenu: false,
280
+ isFiltering: false,
281
+ };
282
+ },
283
+ computed: {
284
+ getTableClass() {
285
+ return {
286
+ table: true,
287
+ adjust: this.adjust,
288
+ 'v-scroll': this.hasVerticalScrollBar,
289
+ 'non-header': !this.showHeader,
290
+ };
291
+ },
292
+ isHeaderCheckBox() {
293
+ const option = this.useCheckbox;
294
+
295
+ return option.use && option.headerCheck && option.mode !== 'single';
296
+ },
297
+ showContextMenu() {
298
+ return !!(this.contextMenuItems.length && this.isClickedCtxMenu);
299
+ },
300
+ },
301
+ watch: {
302
+ rows(value) {
303
+ this.setStore(value);
304
+ },
305
+ selected(value) {
306
+ this.selectedRow = value;
307
+ },
308
+ checked(value) {
309
+ const store = this.originStore;
310
+
311
+ this.checkedRows = value;
312
+ for (let ix = 0; ix < store.length; ix++) {
313
+ store[ix][ROW_CHECK_INDEX] = value.includes(store[ix][ROW_DATA_INDEX]);
314
+ }
315
+ },
316
+ hasVerticalScrollBar() {
317
+ this.onResize();
318
+ },
319
+ },
320
+ created() {
321
+ this.orderedColumns = this.columns.map((column, index) => ({ index, ...column }));
322
+ },
323
+ mounted() {
324
+ this.calculatedColumn();
325
+ this.setStore(this.rows);
326
+ this.$forceUpdate();
327
+ },
328
+ methods: {
329
+ /**
330
+ * 해당 컬럼이 사용자 지정 컬럼인지 확인한다.
331
+ *
332
+ * @param {object} column - 컬럼 정보
333
+ * @returns {boolean} 사용자 지정 컬럼 유무
334
+ */
335
+ isRenderer(column = {}) {
336
+ return column.render && column.render.use;
337
+ },
338
+ /**
339
+ * 해당 컬럼 인덱스가 마지막인지 확인한다.
340
+ *
341
+ * @param {number} index - 컬럼 인덱스
342
+ * @returns {boolean} 마지막 컬럼 유무
343
+ */
344
+ isLastColumn(index) {
345
+ const columns = this.orderedColumns;
346
+ let lastIndex = -1;
347
+
348
+ for (let ix = columns.length - 1; ix >= 0; ix--) {
349
+ if (!columns[ix].hide) {
350
+ lastIndex = ix;
351
+ break;
352
+ }
353
+ }
354
+
355
+ return lastIndex === index;
356
+ },
357
+ /**
358
+ * 전달받은 필드명과 일치하는 컬럼 인덱스를 반환한다.
359
+ *
360
+ * @param {string} field - 컬럼 필드명
361
+ * @returns {number} 일치한다면 컬럼 인덱스, 일치하지 않는다면 -1
362
+ */
363
+ getColumnIndex(field) {
364
+ return this.columns.findIndex(column => column.field === field);
365
+ },
366
+ /**
367
+ * 데이터 타입에 따라 변환된 데이터을 반환한다.
368
+ *
369
+ * @param {string} type - 데이터 유형
370
+ * @param {number|string} value - 데이터
371
+ * @returns {number|string} 변환된 데이터
372
+ */
373
+ getConvertValue(type, value) {
374
+ let convertValue;
375
+
376
+ if (type === 'number') {
377
+ convertValue = numberWithComma(value);
378
+ convertValue = convertValue === false ? value : convertValue;
379
+ } else if (type === 'float') {
380
+ convertValue = value.toFixed(3);
381
+ } else {
382
+ convertValue = value;
383
+ }
384
+
385
+ return convertValue;
386
+ },
387
+ /**
388
+ * 고정 너비, 스크롤 유무 등에 따른 컬럼 너비를 계산한다.
389
+ */
390
+ calculatedColumn() {
391
+ let columnWidth = this.columnWidth;
392
+ let remainWidth = 0;
393
+ if (this.adjust) {
394
+ const el = this.$refs.body;
395
+ let elWidth = el.offsetWidth;
396
+ const elHeight = el.offsetHeight;
397
+ const result = this.orderedColumns.reduce((acc, column) => {
398
+ if (column.hide) {
399
+ return acc;
400
+ }
401
+
402
+ if (column.width) {
403
+ acc.totalWidth += column.width;
404
+ } else {
405
+ acc.emptyCount++;
406
+ }
407
+
408
+ return acc;
409
+ }, { totalWidth: 0, emptyCount: 0 });
410
+
411
+ if (this.rowHeight * this.rows.length > elHeight) {
412
+ elWidth -= this.scrollWidth;
413
+ }
414
+
415
+ if (this.useCheckbox.use) {
416
+ elWidth -= 40;
417
+ }
418
+
419
+ // 1을 빼주는 이유는 돔에서는 소수점까지 너비를 취급하나 offsetWidth 같은 속성값은 반올림되어 저장되어 있음
420
+ columnWidth = elWidth - result.totalWidth - 1;
421
+ if (columnWidth > 0) {
422
+ remainWidth = columnWidth
423
+ - (Math.floor(columnWidth / result.emptyCount) * result.emptyCount);
424
+ columnWidth = Math.floor(columnWidth / result.emptyCount);
425
+ } else {
426
+ columnWidth = this.columnWidth;
427
+ }
428
+
429
+ columnWidth = columnWidth < 40 ? 40 : columnWidth;
430
+ this.columnWidth = columnWidth;
431
+ }
432
+
433
+ this.orderedColumns.map((column) => {
434
+ const item = column;
435
+ if (!item.width && !item.hide) {
436
+ item.width = columnWidth;
437
+ }
438
+ return item;
439
+ });
440
+
441
+ if (remainWidth) {
442
+ this.orderedColumns[this.orderedColumns.length - 1].width += remainWidth;
443
+ }
444
+ },
445
+ /**
446
+ * 컨텍스트 메뉴를 설정한다.
447
+ *
448
+ * @param {boolean} useCustom - 사용자 지정 메뉴 사용 유무
449
+ */
450
+ setContextMenu(useCustom = true) {
451
+ const menuItems = [];
452
+
453
+ if (useCustom && this.customContextMenu.length) {
454
+ const row = this.selectedRow;
455
+ const customItems = this.customContextMenu.map(
456
+ (item) => {
457
+ const menuItem = item;
458
+ if (menuItem.validate) {
459
+ menuItem.disabled = !menuItem.validate(menuItem.itemId, row);
460
+ }
461
+
462
+ return menuItem;
463
+ });
464
+
465
+ menuItems.push(...customItems);
466
+ }
467
+
468
+ if (this.useFilter) {
469
+ menuItems.push({
470
+ text: this.isFiltering ? 'Filter Off' : 'Filter On',
471
+ itemId: 'set_filter',
472
+ callback: () => {
473
+ this.isFiltering = !this.isFiltering;
474
+ this.filteredStore = [];
475
+
476
+ this.setStore([], false);
477
+ },
478
+ });
479
+ }
480
+
481
+ this.contextMenuItems = menuItems;
482
+ },
483
+ /**
484
+ * 설정값에 따라 해당 컬럼 데이터에 대해 정렬한다.
485
+ */
486
+ setSort() {
487
+ const index = this.getColumnIndex(this.sortField);
488
+ const desc = (a, b) => (a > b ? -1 : 1);
489
+ const asc = (a, b) => (a < b ? -1 : 1);
490
+ const type = this.columns[index].type || 'string';
491
+ const sortFn = this.sortOrder === 'desc' ? desc : asc;
492
+ const store = this.isFiltering ? this.filteredStore : this.originStore;
493
+
494
+ if (type === 'string') {
495
+ store.sort((a, b) => sortFn(a[ROW_DATA_INDEX][index].toLowerCase(),
496
+ b[ROW_DATA_INDEX][index].toLowerCase()));
497
+ } else {
498
+ store.sort((a, b) => sortFn(a[ROW_DATA_INDEX][index],
499
+ b[ROW_DATA_INDEX][index]));
500
+ }
501
+ },
502
+ /**
503
+ * 전달받은 문자열 내 해당 키워드가 존재하는지 확인한다.
504
+ *
505
+ * @param {string} search - 검색 키워드
506
+ * @param {string} origin - 기준 문자열
507
+ * @returns {boolean} 문자열 내 키워드 존재 유무
508
+ */
509
+ likeSearch(search, origin) {
510
+ if (typeof search !== 'string' || origin === null) {
511
+ return false;
512
+ }
513
+
514
+ // test 시에 사용될 정규식 부분이 문자열로 넣어줘야하다 보니
515
+ // 특수문자에 대한 처리가 필요하여 아래 replace 처리를 함
516
+ let regx = search.replace(new RegExp('([\\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:\\-])', 'g'), '\\$1');
517
+ regx = regx.replace(/%/g, '.*').replace(/_/g, '.');
518
+
519
+ return RegExp(`^${regx}$`, 'gi').test(origin);
520
+ },
521
+ /**
522
+ * 필터 조건에 따라 문자열을 확인한다.
523
+ *
524
+ * @param {array} item - row 데이터
525
+ * @param {object} condition - 필터 정보
526
+ * @returns {boolean} 확인 결과
527
+ */
528
+ stringFilter(item, condition) {
529
+ const comparison = condition.comparison;
530
+ const value = condition.value;
531
+ const index = condition.index;
532
+ let result;
533
+
534
+ if (comparison === 'Equal') {
535
+ result = item[ROW_DATA_INDEX][index] === value;
536
+ } else if (comparison === 'Not Equal') {
537
+ result = item[ROW_DATA_INDEX][index] !== value;
538
+ } else if (comparison === 'Like') {
539
+ result = this.likeSearch(`%${value}%`, item[ROW_DATA_INDEX][index]);
540
+ } else if (comparison === 'Not Like') {
541
+ result = !this.likeSearch(`%${value}%`, item[ROW_DATA_INDEX][index]);
542
+ }
543
+
544
+ return result;
545
+ },
546
+ /**
547
+ * 필터 조건에 따라 숫자를 확인한다.
548
+ *
549
+ * @param {array} item - row 데이터
550
+ * @param {object} condition - 필터 정보
551
+ * @returns {boolean} 확인 결과
552
+ */
553
+ numberFilter(item, condition) {
554
+ const comparison = condition.comparison;
555
+ const value = condition.value;
556
+ const index = condition.index;
557
+ let result;
558
+
559
+ if (comparison === '=') {
560
+ result = item[ROW_DATA_INDEX][index] === value;
561
+ } else if (comparison === '>') {
562
+ result = item[ROW_DATA_INDEX][index] > value;
563
+ } else if (comparison === '<') {
564
+ result = item[ROW_DATA_INDEX][index] < value;
565
+ }
566
+
567
+ return result;
568
+ },
569
+ /**
570
+ * 필터 조건이 적용된 데이터를 반환한다.
571
+ *
572
+ * @param {array} data - row 데이터
573
+ * @param {string} filterType - 데이터 유형
574
+ * @param {object} condition - 필터 정보
575
+ * @returns {boolean} 확인 결과
576
+ */
577
+ getFilteredData(data, filterType, condition) {
578
+ const filterFn = filterType === 'string' ? this.stringFilter : this.numberFilter;
579
+ const filteredData = [];
580
+
581
+ for (let ix = 0; ix < data.length; ix++) {
582
+ if (filterFn(data[ix], condition)) {
583
+ filteredData.push(data[ix]);
584
+ }
585
+ }
586
+
587
+ return filteredData;
588
+ },
589
+ /**
590
+ * 전체 데이터에서 설정된 필터 적용 후 결과를 filterStore에 저장한다.
591
+ */
592
+ setFilter() {
593
+ let field;
594
+ let index;
595
+ let filters;
596
+ let columnType;
597
+ let filteredStore = [];
598
+ let isAppliedFilter = false;
599
+ const filterByColumn = this.filterList;
600
+ const fields = Object.keys(filterByColumn || {});
601
+ const store = this.originStore;
602
+
603
+ for (let ix = 0; ix < fields.length; ix++) {
604
+ field = fields[ix];
605
+ filters = filterByColumn[field];
606
+ index = this.getColumnIndex(field);
607
+ columnType = this.columns[index].type;
608
+ for (let jx = 0; jx < filters.length; jx++) {
609
+ const filterItem = filters[jx];
610
+ if (filterItem.use) {
611
+ isAppliedFilter = true;
612
+ if (!filteredStore.length) {
613
+ filteredStore = this.getFilteredData(store, columnType, {
614
+ ...filterItem,
615
+ index,
616
+ });
617
+ } else if (filterItem.type === 'OR') {
618
+ filteredStore.push(...this.getFilteredData(store, columnType, {
619
+ ...filterItem,
620
+ index,
621
+ }));
622
+ } else {
623
+ filteredStore = this.getFilteredData(filteredStore, columnType, {
624
+ ...filterItem,
625
+ index,
626
+ });
627
+ }
628
+ }
629
+ }
630
+ }
631
+
632
+ if (!isAppliedFilter) {
633
+ this.filteredStore = store;
634
+ } else {
635
+ this.filteredStore = uniqBy(filteredStore, JSON.stringify);
636
+ }
637
+ },
638
+ /**
639
+ * 전달된 데이터를 내부 store 및 속성에 저장한다.
640
+ *
641
+ * @param {array} value - row 데이터
642
+ * @param {boolean} makeIndex - 인덱스 생성 유무
643
+ */
644
+ setStore(value, makeIndex = true) {
645
+ const store = [];
646
+ let checked;
647
+ let selected = false;
648
+
649
+ if (makeIndex) {
650
+ let hasUnChecked = false;
651
+
652
+ for (let ix = 0; ix < value.length; ix++) {
653
+ checked = this.checked.includes(value[ix]);
654
+ if (!checked) {
655
+ hasUnChecked = true;
656
+ }
657
+
658
+ if (!selected && isEqual(this.selectedRow, value[ix])) {
659
+ this.selectedRow = value[ix];
660
+ selected = true;
661
+ }
662
+
663
+ store.push([ix, checked, value[ix]]);
664
+ }
665
+
666
+ if (!selected) {
667
+ this.selectedRow = [];
668
+ }
669
+
670
+ this.isHeaderChecked = value.length > 0 ? !hasUnChecked : false;
671
+ this.originStore = store;
672
+ }
673
+
674
+ if (this.isFiltering) {
675
+ this.setFilter();
676
+ }
677
+
678
+ if (this.sortField) {
679
+ this.setSort();
680
+ }
681
+
682
+ this.updateVScroll();
683
+ },
684
+ /**
685
+ * 수평 스크롤의 위치 계산 후 적용한다.
686
+ */
687
+ updateHScroll() {
688
+ const headerEl = this.$refs.header;
689
+ const bodyEl = this.$refs.body;
690
+
691
+ headerEl.scrollLeft = bodyEl.scrollLeft;
692
+ },
693
+ /**
694
+ * 수직 스크롤의 위치 계산 후 적용한다.
695
+ */
696
+ updateVScroll() {
697
+ const el = this.$refs.body;
698
+ const offset = 5;
699
+ const rowHeight = this.rowHeight;
700
+ const store = this.isFiltering ? this.filteredStore : this.originStore;
701
+ const rowCount = el.clientHeight > rowHeight
702
+ ? Math.ceil(el.clientHeight / rowHeight) : store.length;
703
+ const totalScrollHeight = store.length * rowHeight;
704
+ let firstVisibleIndex = Math.floor(el.scrollTop / rowHeight);
705
+ if (firstVisibleIndex > store.length - 1) {
706
+ firstVisibleIndex = 0;
707
+ }
708
+
709
+ const lastVisibleIndex = firstVisibleIndex + rowCount;
710
+ const firstIndex = Math.max(firstVisibleIndex - offset, 0);
711
+ const lastIndex = lastVisibleIndex + offset;
712
+
713
+ this.hasVerticalScrollBar = rowCount < store.length;
714
+ this.viewStore = store.slice(firstIndex, lastIndex);
715
+
716
+ this.vScrollTopHeight = firstIndex * rowHeight;
717
+ this.vScrollBottomHeight = totalScrollHeight - (this.viewStore.length * rowHeight)
718
+ - this.vScrollTopHeight;
719
+ },
720
+ /**
721
+ * row에 대한 체크 상태를 해제한다.
722
+ *
723
+ * @param {array} row - row 데이터
724
+ */
725
+ unCheckedRow(row) {
726
+ const index = this.originStore.findIndex(
727
+ item => item[ROW_DATA_INDEX] === row[ROW_DATA_INDEX]);
728
+
729
+ if (index !== -1) {
730
+ this.$set(this.originStore[index], ROW_CHECK_INDEX, row[ROW_CHECK_INDEX]);
731
+ }
732
+ },
733
+ /**
734
+ * sort 이벤트를 처리한다.
735
+ *
736
+ * @param {string} field - 컬럼 field
737
+ */
738
+ onSort(field) {
739
+ if (this.sortField === field) {
740
+ this.sortOrder = this.sortOrder === 'desc' ? 'asc' : 'desc';
741
+ } else {
742
+ this.sortField = field;
743
+ this.sortOrder = 'desc';
744
+ }
745
+
746
+ this.setStore(this.originStore, false);
747
+ },
748
+ /**
749
+ * scroll 이벤트를 처리한다.
750
+ */
751
+ onScroll() {
752
+ const el = this.$refs.body;
753
+ const scrollTop = el.scrollTop;
754
+ const scrollLeft = el.scrollLeft;
755
+ const lastTop = this.lastScroll.top;
756
+ const lastLeft = this.lastScroll.left;
757
+ const isHorizontal = !(scrollLeft === lastLeft);
758
+ const isVertical = !(scrollTop === lastTop);
759
+
760
+ if (isVertical) {
761
+ this.updateVScroll();
762
+ }
763
+
764
+ if (isHorizontal) {
765
+ this.updateHScroll();
766
+ }
767
+
768
+ this.lastScroll.top = scrollTop;
769
+ this.lastScroll.left = scrollLeft;
770
+ },
771
+ /**
772
+ * 필터 팝업 관련 데이터 초기화 및 숨김 처리한다.
773
+ */
774
+ onCloseFilterWindow() {
775
+ this.currentFilter = {
776
+ column: {},
777
+ items: [],
778
+ };
779
+ this.showFilterWindow = false;
780
+ },
781
+ /**
782
+ * 전달된 필터 정보를 저장하고 store에 반영한다.
783
+ *
784
+ * @param {string} columnField - row 데이터
785
+ * @param {array} filters - 필터 정보
786
+ */
787
+ onApplyFilter(columnField, filters) {
788
+ this.$set(this.filterList, columnField, filters);
789
+ this.filteredStore = [];
790
+
791
+ this.setStore([], false);
792
+ },
793
+ /**
794
+ * 해당 컬럼에 대한 필터 팝업을 보여준다.
795
+ *
796
+ * @param {object} column - 컬럼 정보
797
+ */
798
+ onClickFilter(column) {
799
+ const filter = {
800
+ column,
801
+ items: [],
802
+ };
803
+ const filterItems = this.filterList[column.field];
804
+
805
+ if (filterItems) {
806
+ filter.items = filterItems;
807
+ }
808
+
809
+ this.currentFilter = filter;
810
+ this.showFilterWindow = true;
811
+ },
812
+ /**
813
+ * 컨텍스트 메뉴 선택 이벤트를 처리한다.
814
+ *
815
+ * @param {object} item - 선택된 메뉴 정보
816
+ */
817
+ onClickCtxMenu(item) {
818
+ if (item && item.callback) {
819
+ item.callback(item.itemId, this.selectedRow);
820
+ }
821
+
822
+ this.isClickedCtxMenu = false;
823
+ },
824
+ /**
825
+ * 마우스 우클릭 이벤트를 처리한다.
826
+ *
827
+ * @param {object} event - 이벤트 객체
828
+ */
829
+ onContextMenu(event) {
830
+ const target = event.target;
831
+ const tagName = target.tagName.toLowerCase();
832
+ let rowIndex;
833
+
834
+ if (tagName === 'td') {
835
+ rowIndex = target.parentElement.dataset.index;
836
+ } else {
837
+ rowIndex = target.parentElement.parentElement.dataset.index;
838
+ }
839
+
840
+ this.isClickedCtxMenu = true;
841
+ if (rowIndex) {
842
+ const rowData = this.viewStore[+rowIndex][ROW_DATA_INDEX];
843
+ this.selectedRow = rowData;
844
+ this.setContextMenu();
845
+ this.$emit('update:selected', rowData);
846
+ } else {
847
+ this.selectedRow = [];
848
+ this.setContextMenu(false);
849
+ this.$emit('update:selected', []);
850
+ }
851
+ },
852
+ /**
853
+ * row click 이벤트를 처리한다.
854
+ *
855
+ * @param {object} event - 이벤트 객체
856
+ * @param {array} row - row 데이터
857
+ */
858
+ onRowClick(event, row) {
859
+ if (!this.useSelect) {
860
+ return;
861
+ }
862
+
863
+ const cellInfo = event.target.dataset;
864
+ const rowData = row[ROW_DATA_INDEX];
865
+ const rowIndex = row[ROW_INDEX];
866
+
867
+ this.selectedRow = rowData;
868
+ this.$emit('update:selected', rowData);
869
+ /**
870
+ * row click 이벤트
871
+ *
872
+ * @property {object} event - 이벤트 객체
873
+ * @property {number} rowIndex - row 인덱스
874
+ * @property {string} cellName - 셀 이름
875
+ * @property {number} cellIndex - 셀 인덱스
876
+ * @property {array} rowData - row 데이터
877
+ */
878
+ this.$emit('click-row', event, rowIndex, cellInfo.name, cellInfo.index, rowData);
879
+ },
880
+ /**
881
+ * row dblclick 이벤트를 처리한다.
882
+ *
883
+ * @param {object} event - 이벤트 객체
884
+ * @param {array} row - row 데이터
885
+ */
886
+ onRowDblClick(event, row) {
887
+ const cellInfo = event.target.dataset;
888
+ const rowData = row[ROW_DATA_INDEX];
889
+ const rowIndex = row[ROW_INDEX];
890
+
891
+ /**
892
+ * row dblclick 이벤트
893
+ *
894
+ * @property {object} event - 이벤트 객체
895
+ * @property {number} rowIndex - row 인덱스
896
+ * @property {string} cellName - 셀 이름
897
+ * @property {number} cellIndex - 셀 인덱스
898
+ * @property {array} rowData - row 데이터
899
+ */
900
+ this.$emit('dblclick-row', {
901
+ event,
902
+ rowData,
903
+ rowIndex,
904
+ cellName: cellInfo.name,
905
+ cellIndex: cellInfo.index,
906
+ });
907
+ },
908
+ /**
909
+ * checkbox click 이벤트를 처리한다.
910
+ *
911
+ * @param {object} event - 이벤트 객체
912
+ * @param {array} row - row 데이터
913
+ */
914
+ onCheck(event, row) {
915
+ if (this.useCheckbox.mode === 'single' && this.prevCheckedRow.length) {
916
+ this.prevCheckedRow[1] = false;
917
+ this.unCheckedRow(this.prevCheckedRow);
918
+ }
919
+
920
+ if (row[ROW_CHECK_INDEX]) {
921
+ if (this.useCheckbox.mode === 'single') {
922
+ this.checkedRows = [row[ROW_DATA_INDEX]];
923
+ } else {
924
+ this.checkedRows.push(row[ROW_DATA_INDEX]);
925
+ }
926
+
927
+ if (this.checkedRows.length === this.originStore.length) {
928
+ this.isHeaderChecked = true;
929
+ }
930
+ } else {
931
+ if (this.isHeaderChecked) {
932
+ this.isHeaderChecked = false;
933
+ }
934
+
935
+ if (this.useCheckbox.mode === 'single') {
936
+ this.checkedRows = [];
937
+ } else {
938
+ this.checkedRows.splice(this.checkedRows.indexOf(row[ROW_DATA_INDEX]), 1);
939
+ }
940
+ }
941
+
942
+ this.prevCheckedRow = row.slice();
943
+ this.$emit('update:checked', this.checkedRows);
944
+ /**
945
+ * check single row 이벤트
946
+ *
947
+ * @property {object} event - 이벤트 객체
948
+ * @property {number} rowIndex - row 인덱스
949
+ * @property {array} rowData - row 데이터
950
+ */
951
+ this.$emit('check-one', event, row[ROW_INDEX], row[ROW_DATA_INDEX]);
952
+ },
953
+ /**
954
+ * all checkbox click 이벤트를 처리한다.
955
+ *
956
+ * @param {object} event - 이벤트 객체
957
+ */
958
+ onCheckAll(event) {
959
+ const status = this.isHeaderChecked;
960
+ const checked = [];
961
+ let item;
962
+
963
+ for (let ix = 0; ix < this.originStore.length; ix++) {
964
+ item = this.originStore[ix];
965
+ if (status) {
966
+ checked.push(item[ROW_DATA_INDEX]);
967
+ }
968
+
969
+ item[ROW_CHECK_INDEX] = status;
970
+ }
971
+
972
+ this.checkedRows = checked;
973
+ this.$emit('update:checked', checked);
974
+ /**
975
+ * check all row 이벤트
976
+ *
977
+ * @property {object} event - 이벤트 객체
978
+ * @property {array} checked - 선택된 row 데이터
979
+ */
980
+ this.$emit('check-all', event, checked);
981
+ this.$forceUpdate();
982
+ },
983
+ /**
984
+ * dom resize 이벤트를 처리한다.
985
+ */
986
+ onResize() {
987
+ if (this.adjust) {
988
+ // return 값을 고려하면 forEach가 맞으나 성능를 고려하여 map을 사용하도록 함
989
+ this.orderedColumns.map((column) => {
990
+ const item = column;
991
+
992
+ if (!this.columns[column.index].width && !item.resized) {
993
+ item.width = 0;
994
+ }
995
+
996
+ return item;
997
+ }, this);
998
+ }
999
+
1000
+ this.calculatedColumn();
1001
+ this.$forceUpdate();
1002
+ },
1003
+ onShow(isVisible) {
1004
+ if (isVisible) {
1005
+ this.onResize();
1006
+ }
1007
+ },
1008
+
1009
+ /**
1010
+ * column resize 이벤트를 처리한다.
1011
+ *
1012
+ * @param {number} columnIndex - 컬럼 인덱스
1013
+ * @param {object} event - 이벤트 객체
1014
+ */
1015
+ onColumnResize(columnIndex, event) {
1016
+ if (this.isLastColumn(columnIndex)) {
1017
+ return;
1018
+ }
1019
+
1020
+ const nextColumnIndex = columnIndex + 1;
1021
+ const headerEl = this.$refs.header;
1022
+ const headerLeft = headerEl.getBoundingClientRect().left;
1023
+ const columnEl = headerEl.querySelector(`li[data-index="${columnIndex}"]`);
1024
+ const nextColumnEl = headerEl.querySelector(`li[data-index="${nextColumnIndex}"]`);
1025
+ const columnRect = columnEl.getBoundingClientRect();
1026
+ const maxRight = nextColumnEl.getBoundingClientRect().right - headerLeft - 40;
1027
+ const resizeLineEl = this.$refs.resizeLine;
1028
+ const minLeft = columnRect.left - headerLeft + 40;
1029
+ const startLeft = columnRect.right - headerLeft;
1030
+ const startMouseLeft = event.clientX;
1031
+ const startColumnLeft = columnRect.left - headerLeft;
1032
+
1033
+ resizeLineEl.style.left = `${startLeft}px`;
1034
+
1035
+ this.showResizeLine = true;
1036
+
1037
+ const handleMouseMove = (evt) => {
1038
+ const deltaLeft = evt.clientX - startMouseLeft;
1039
+ const proxyLeft = startLeft + deltaLeft;
1040
+ let resizeWidth = Math.max(minLeft, proxyLeft);
1041
+
1042
+ resizeWidth = Math.min(maxRight, resizeWidth);
1043
+
1044
+ resizeLineEl.style.left = `${resizeWidth}px`;
1045
+ };
1046
+
1047
+ const handleMouseUp = () => {
1048
+ const destLeft = parseInt(resizeLineEl.style.left, 10);
1049
+ const changedWidth = destLeft - startColumnLeft;
1050
+
1051
+ if (this.orderedColumns[columnIndex]) {
1052
+ const columnWidth = this.orderedColumns[columnIndex].width;
1053
+ this.orderedColumns[columnIndex].width = changedWidth;
1054
+ this.orderedColumns[columnIndex].resized = true;
1055
+ this.orderedColumns[nextColumnIndex].width += (columnWidth - changedWidth);
1056
+ this.orderedColumns[nextColumnIndex].resized = true;
1057
+ }
1058
+
1059
+ this.showResizeLine = false;
1060
+ document.removeEventListener('mousemove', handleMouseMove);
1061
+ this.onResize();
1062
+ };
1063
+
1064
+ document.addEventListener('mousemove', handleMouseMove);
1065
+ document.addEventListener('mouseup', handleMouseUp, { once: true });
1066
+ },
1067
+ },
1068
+ };
1069
+ </script>
1070
+ <style lang="scss" scoped>
1071
+ @import '~@/styles/default';
1072
+
1073
+ $header-height: 33px;
1074
+
1075
+ .table {
1076
+ position: relative;
1077
+ width: 100%;
1078
+ height: 100%;
1079
+ padding-top: $header-height;
1080
+
1081
+ &.non-header {
1082
+ padding-top: 0;
1083
+ }
1084
+ }
1085
+
1086
+ .table-header {
1087
+ overflow: hidden;
1088
+ position: absolute;
1089
+ top: 0;
1090
+ width: 100%;
1091
+ height: $header-height;
1092
+
1093
+ @include evThemify() {
1094
+ border-top: 2px solid evThemed('grid-header-border');
1095
+ border-bottom: $border-solid evThemed('grid-bottom-border');
1096
+ }
1097
+ }
1098
+
1099
+ .column-list {
1100
+ position: relative;
1101
+ width: 100%;
1102
+ height: 100%;
1103
+ white-space: nowrap;
1104
+ list-style-type: none;
1105
+
1106
+ .column-dummy {
1107
+ position: relative;
1108
+ display: inline-block;
1109
+ width: 0;
1110
+ height: 30px;
1111
+ padding: 0;
1112
+ user-select: none;
1113
+ }
1114
+ }
1115
+
1116
+ .column {
1117
+ position: relative;
1118
+ display: inline-flex;
1119
+ min-width: 40px;
1120
+ height: 100%;
1121
+ padding: 0 10px;
1122
+ line-height: 30px;
1123
+ justify-content: center;
1124
+ align-items: center;
1125
+ text-align: center;
1126
+ vertical-align: top;
1127
+ user-select: none;
1128
+
1129
+ @include evThemify() {
1130
+ border-right: $border-solid evThemed('grid-bottom-border');
1131
+ }
1132
+
1133
+ &:nth-last-child(2) {
1134
+ border-right: 0;
1135
+
1136
+ .column-resize {
1137
+ cursor: default !important;
1138
+ }
1139
+ }
1140
+
1141
+ .sort-icon {
1142
+ display: inline-block;
1143
+ float: right;
1144
+ font-size: 14px;
1145
+ line-height: 30px;
1146
+
1147
+ @include evThemify() {
1148
+ color: evThemed('font-color-base');
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ .column-name {
1154
+ display: inline-block;
1155
+ float: left;
1156
+ overflow: hidden;
1157
+ text-overflow: ellipsis;
1158
+ font-weight: bold;
1159
+ font-size: 14px;
1160
+
1161
+ @include evThemify() {
1162
+ color: evThemed('font-color-base');
1163
+ }
1164
+ }
1165
+
1166
+ .column-filter {
1167
+ position: absolute;
1168
+ right: 0;
1169
+ background-color: transparent;
1170
+ display: none;
1171
+
1172
+ .set-filter-icon {
1173
+ margin-right: 2px;
1174
+ font-size: 14px;
1175
+ vertical-align: middle;
1176
+
1177
+ @include evThemify() {
1178
+ color: evThemed('font-color-base');
1179
+ }
1180
+ }
1181
+ }
1182
+
1183
+ .column:hover .column-filter {
1184
+ display: block;
1185
+ }
1186
+
1187
+ .column-filter-status {
1188
+ position: absolute;
1189
+ top: 3px;
1190
+ left: 0;
1191
+ background-color: transparent;
1192
+
1193
+ .ei {
1194
+ font-size: 10px;
1195
+ vertical-align: top;
1196
+
1197
+ @include evThemify() {
1198
+ color: evThemed('color-primary');
1199
+ }
1200
+ }
1201
+ }
1202
+
1203
+ .column-resize {
1204
+ position: absolute;
1205
+ width: 10px;
1206
+ height: 100%;
1207
+ right: -5px;
1208
+ bottom: 0;
1209
+
1210
+ &:hover {
1211
+ cursor: col-resize;
1212
+ }
1213
+ }
1214
+
1215
+ .v-scroll .dummy {
1216
+ width: 16px;
1217
+ }
1218
+
1219
+ .table-body {
1220
+ position: relative;
1221
+ width: 100%;
1222
+ height: 100%;
1223
+ overflow: auto;
1224
+ overflow-anchor: none;
1225
+
1226
+ @include evThemify() {
1227
+ border-bottom: $border-solid evThemed('grid-bottom-border');
1228
+ }
1229
+
1230
+ table {
1231
+ clear: both;
1232
+ border-spacing: 0;
1233
+ border-collapse: collapse;
1234
+ }
1235
+
1236
+ &.stripe tr:nth-child(even) {
1237
+ @include evThemify() {
1238
+ background-color: evThemed('grid-row-stripe');
1239
+ }
1240
+ }
1241
+
1242
+ tr {
1243
+ white-space: nowrap;
1244
+
1245
+ @include evThemify() {
1246
+ border-bottom: $border-solid evThemed('grid-bottom-border');
1247
+ }
1248
+
1249
+ /* stylelint-disable */
1250
+ &.selected {
1251
+ @include evThemify() {
1252
+ background-color: evThemed('grid-row-selected') !important;
1253
+ }
1254
+ }
1255
+
1256
+ &.dummy {
1257
+ border-bottom: none;
1258
+ background: transparent;
1259
+ }
1260
+ /* stylelint-enable */
1261
+ }
1262
+
1263
+ td {
1264
+ display: inline-block;
1265
+ padding: 0 10px;
1266
+ text-align: center;
1267
+
1268
+ @include truncate(100%);
1269
+ @include evThemify() {
1270
+ color: evThemed('grid-cell-text');
1271
+ border-right: $border-solid evThemed('grid-bottom-border');
1272
+ }
1273
+
1274
+ &.row-checkbox {
1275
+ display: inline-flex;
1276
+ justify-content: center;
1277
+ align-items: center;
1278
+ }
1279
+ &.render {
1280
+ overflow: initial;
1281
+ }
1282
+
1283
+ &.number,
1284
+ &.float {
1285
+ text-align: right;
1286
+ }
1287
+
1288
+ &.string,
1289
+ &.stringnumber {
1290
+ text-align: left;
1291
+ }
1292
+
1293
+ &.center {
1294
+ text-align: center;
1295
+ }
1296
+ &.left {
1297
+ text-align: left;
1298
+ .wrap {
1299
+ justify-content: flex-start;
1300
+ }
1301
+ }
1302
+ &.right {
1303
+ text-align: right;
1304
+ .wrap {
1305
+ justify-content: flex-end;
1306
+ }
1307
+ }
1308
+
1309
+ &:last-child {
1310
+ border-right: 0;
1311
+ }
1312
+ }
1313
+
1314
+ tr.dummy td {
1315
+ border-right: 0;
1316
+ }
1317
+ }
1318
+
1319
+ .table-resize-line {
1320
+ position: absolute;
1321
+ width: 1px;
1322
+ top: 0;
1323
+ bottom: 0;
1324
+
1325
+ @include evThemify() {
1326
+ border-right: $border-solid evThemed('grid-bottom-border');
1327
+ }
1328
+ }
1329
+
1330
+ .vscroll-spacer {
1331
+ opacity: 0;
1332
+ clear: both;
1333
+ }
1334
+
1335
+ [v-cloak] {
1336
+ display: none;
1337
+ }
1338
+ </style>