xt-element-ui 2.0.4 → 2.1.0

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.
@@ -1,15 +1,15 @@
1
1
  <template>
2
- <div class="ex-table-wrapper">
2
+ <div class="xt-table-wrapper">
3
3
  <!-- 标题栏 / 工具栏 -->
4
- <div class="ex-table-header" v-if="title || $slots.toolbar">
5
- <span class="ex-table-title" v-if="title">{{ title }}</span>
6
- <div class="ex-table-toolbar">
4
+ <div class="xt-table-header" v-if="title || $slots.toolbar">
5
+ <span class="xt-table-title" v-if="title">{{ title }}</span>
6
+ <div class="xt-table-toolbar">
7
7
  <slot name="toolbar"></slot>
8
8
  </div>
9
9
  </div>
10
10
 
11
11
  <!-- 主体表格:单 Table + 内置虚拟滚动(核心改造) -->
12
- <div class="ex-table-body">
12
+ <div class="xt-table-body">
13
13
  <VirtualElTable
14
14
  ref="table"
15
15
  :data="processedTableData"
@@ -24,7 +24,7 @@
24
24
  v-on="$listeners"
25
25
  @selection-change="handleSelectionChange"
26
26
  @sort-change="handleSortChange"
27
- class="ex-table"
27
+ class="xt-table"
28
28
  >
29
29
  <!-- 选择列 -->
30
30
  <el-table-column
@@ -95,7 +95,7 @@
95
95
  </div>
96
96
 
97
97
  <!-- 分页 -->
98
- <div class="ex-table-footer" v-if="showPagination">
98
+ <div class="xt-table-footer" v-if="showPagination">
99
99
  <el-pagination
100
100
  :current-page="pagination.pageNum"
101
101
  :page-size="pagination.pageSize"
@@ -122,6 +122,9 @@ export default {
122
122
  tableData: { type: Array, default: () => [] },
123
123
  columns: { type: Array, default: () => [] },
124
124
  groupColumns: { type: Array, default: () => [] },
125
+ // 排序配置
126
+ sortGroup: { type: Boolean, default: false },
127
+ defaultSort: { type: Object, default: null },
125
128
  title: { type: String, default: '' },
126
129
  height: { type: [Number, String], default: null },
127
130
  maxHeight: { type: [Number, String], default: null },
@@ -144,7 +147,10 @@ export default {
144
147
  return {
145
148
  spanCache: {},
146
149
  flattenedColumnsCache: [],
147
- selectedRows: []
150
+ selectedRows: [],
151
+ // 排序状态
152
+ sortProp: null,
153
+ sortOrder: null
148
154
  }
149
155
  },
150
156
 
@@ -159,10 +165,24 @@ export default {
159
165
  return this.height ? undefined : this.maxHeight || undefined
160
166
  },
161
167
 
168
+ // 排序后的数据
169
+ sortedTableData() {
170
+ if (!this.tableData.length) return []
171
+ if (!this.sortProp || !this.sortOrder) return [...this.tableData]
172
+
173
+ const order = this.sortOrder === 'ascending' ? 1 : -1
174
+ const sortFn = this.resolveSortMethod(this.sortProp)
175
+
176
+ if (this.sortGroup && this.groupColumns.length) {
177
+ return this.groupSort([...this.tableData], sortFn, order)
178
+ }
179
+ return [...this.tableData].sort((a, b) => sortFn(a, b) * order)
180
+ },
181
+
162
182
  // 处理小计、总计后的最终数据
163
183
  processedTableData() {
164
- if (!this.tableData.length) return []
165
- let data = [...this.tableData]
184
+ if (!this.sortedTableData.length) return []
185
+ let data = [...this.sortedTableData]
166
186
  const hasSubtotal = this.subtotalConfig && this.subtotalConfig.enabled
167
187
  const hasTotal = this.totalConfig && this.totalConfig.enabled
168
188
 
@@ -225,6 +245,25 @@ export default {
225
245
  this.spanCache = {}
226
246
  },
227
247
  deep: true
248
+ },
249
+ defaultSort: {
250
+ handler(val) {
251
+ if (val && val.prop) {
252
+ this.sortProp = val.prop
253
+ this.sortOrder = val.order || 'ascending'
254
+ }
255
+ },
256
+ immediate: false
257
+ },
258
+ sortProp() {
259
+ this.spanCache = {}
260
+ }
261
+ },
262
+
263
+ created() {
264
+ if (this.defaultSort && this.defaultSort.prop) {
265
+ this.sortProp = this.defaultSort.prop
266
+ this.sortOrder = this.defaultSort.order || 'ascending'
228
267
  }
229
268
  },
230
269
 
@@ -239,6 +278,52 @@ export default {
239
278
  },
240
279
 
241
280
  methods: {
281
+ // ========== 排序逻辑 ==========
282
+ resolveSortMethod(prop) {
283
+ const col = this.findColumnByProp(prop)
284
+ if (col && typeof col.sortMethod === 'function') {
285
+ return col.sortMethod
286
+ }
287
+ return (a, b) => {
288
+ const va = a[prop]
289
+ const vb = b[prop]
290
+ if (va == null && vb == null) return 0
291
+ if (va == null) return -1
292
+ if (vb == null) return 1
293
+ if (typeof va === 'number' && typeof vb === 'number') return va - vb
294
+ return String(va).localeCompare(String(vb), undefined, { numeric: true })
295
+ }
296
+ },
297
+
298
+ findColumnByProp(prop) {
299
+ for (const col of this.columns) {
300
+ if (col.children && col.children.length) {
301
+ for (const child of col.children) {
302
+ if (child.prop === prop) return child
303
+ }
304
+ } else if (col.prop === prop) {
305
+ return col
306
+ }
307
+ }
308
+ return null
309
+ },
310
+
311
+ groupSort(data, sortFn, order) {
312
+ const groups = {}
313
+ data.forEach(row => {
314
+ const key = this.groupColumns.map(f => row[f] == null ? '' : String(row[f])).join('|||')
315
+ if (!groups[key]) groups[key] = []
316
+ groups[key].push(row)
317
+ })
318
+ const result = []
319
+ Object.keys(groups).sort().forEach(key => {
320
+ const groupRows = groups[key]
321
+ groupRows.sort((a, b) => sortFn(a, b) * order)
322
+ result.push(...groupRows)
323
+ })
324
+ return result
325
+ },
326
+
242
327
  handleResize() {
243
328
  this.$nextTick(() => {
244
329
  this.$refs.table && this.$refs.table.doLayout()
@@ -326,14 +411,14 @@ export default {
326
411
  },
327
412
 
328
413
  getRowClassName({ row }) {
329
- if (row._rowType === 'subtotal') return 'ex-table-row-subtotal'
330
- if (row._rowType === 'total') return 'ex-table-row-total'
414
+ if (row._rowType === 'subtotal') return 'xt-table-row-subtotal'
415
+ if (row._rowType === 'total') return 'xt-table-row-total'
331
416
  return ''
332
417
  },
333
418
 
334
419
  // ========== 列处理 ==========
335
420
  getColumnProps(col) {
336
- const { _key, children, render, formatter, slot, ...props } = col
421
+ const { _key, children, render, formatter, slot, sortMethod, ...props } = col
337
422
  return props
338
423
  },
339
424
 
@@ -399,6 +484,8 @@ export default {
399
484
  this.$emit('selection-change', this.selectedRows)
400
485
  },
401
486
  handleSortChange(info) {
487
+ this.sortProp = info.prop
488
+ this.sortOrder = info.order
402
489
  this.$emit('sort-change', info)
403
490
  },
404
491
  handleSizeChange(size) {
@@ -416,6 +503,15 @@ export default {
416
503
  this.$refs.table && this.$refs.table.clearSelection()
417
504
  this.selectedRows = []
418
505
  },
506
+ clearSort() {
507
+ this.sortProp = null
508
+ this.sortOrder = null
509
+ this.$nextTick(() => {
510
+ if (this.$refs.table && this.$refs.table.$refs.innerTable) {
511
+ this.$refs.table.$refs.innerTable.clearSort()
512
+ }
513
+ })
514
+ },
419
515
  toggleRowSelection(row, selected) {
420
516
  this.$refs.table && this.$refs.table.toggleRowSelection(row, selected)
421
517
  },
@@ -432,26 +528,26 @@ export default {
432
528
  }
433
529
  </script>
434
530
  <style scoped>
435
- .ex-table-wrapper {
531
+ .xt-table-wrapper {
436
532
  width: 100%;
437
533
  }
438
534
 
439
535
  /* 表头栏 */
440
- .ex-table-header {
536
+ .xt-table-header {
441
537
  display: flex;
442
538
  align-items: center;
443
539
  justify-content: space-between;
444
540
  padding: 12px 0;
445
541
  margin-bottom: 8px;
446
542
  }
447
- .ex-table-title {
543
+ .xt-table-title {
448
544
  font-size: 16px;
449
545
  font-weight: 600;
450
546
  color: #303133;
451
547
  position: relative;
452
548
  padding-left: 12px;
453
549
  }
454
- .ex-table-title::before {
550
+ .xt-table-title::before {
455
551
  content: '';
456
552
  position: absolute;
457
553
  left: 0;
@@ -462,40 +558,40 @@ export default {
462
558
  background: #409eff;
463
559
  border-radius: 2px;
464
560
  }
465
- .ex-table-toolbar {
561
+ .xt-table-toolbar {
466
562
  display: flex;
467
563
  align-items: center;
468
564
  gap: 8px;
469
565
  }
470
566
 
471
- .ex-table-body {
567
+ .xt-table-body {
472
568
  position: relative;
473
569
  z-index: 1;
474
570
  }
475
571
 
476
572
  /* 分页区域 */
477
- .ex-table-footer {
573
+ .xt-table-footer {
478
574
  display: flex;
479
575
  justify-content: flex-end;
480
576
  padding: 16px 0;
481
577
  }
482
578
 
483
579
  /* ========== 小计/合计行样式 ========== */
484
- ::v-deep .ex-table-row-subtotal {
580
+ ::v-deep .xt-table-row-subtotal {
485
581
  background-color: #f5f7fa;
486
582
  font-weight: 600;
487
583
  color: #303133;
488
584
  }
489
- ::v-deep .ex-table-row-total {
585
+ ::v-deep .xt-table-row-total {
490
586
  background-color: #e8eaed;
491
587
  font-weight: 700;
492
588
  color: #303133;
493
589
  border-top: 2px solid #c0c4cc;
494
590
  }
495
- ::v-deep .ex-table-row-subtotal:hover > td {
591
+ ::v-deep .xt-table-row-subtotal:hover > td {
496
592
  background-color: #ebeef5 !important;
497
593
  }
498
- ::v-deep .ex-table-row-total:hover > td {
594
+ ::v-deep .xt-table-row-total:hover > td {
499
595
  background-color: #dcdfe6 !important;
500
596
  }
501
597
 
package/src/index.js CHANGED
@@ -37,6 +37,7 @@ import XtDatePicker from './components/xt-date-picker'
37
37
  import XtChart from './components/xt-chart' // XtChart 组件(基于 ECharts 封装)
38
38
  import XtIcon from './components/xt-icon' // XtIcon 组件(支持 el-icon / svg / 自定义字体)
39
39
  import XtTable from './components/xt-table' // XtTable 组件(基于 ElementUI Table 封装)
40
+ import XtList from './components/xt-list' // XtList 组件(卡片列表)
40
41
  import XtScrollArrow from './components/xt-scroll-arrow'
41
42
  import XtBar from './components/xt-chart/XtBar.vue'
42
43
  import XtLine from './components/xt-chart/XtLine.vue'
@@ -69,6 +70,7 @@ const components = [
69
70
  XtChart,
70
71
  XtIcon,
71
72
  XtTable,
73
+ XtList,
72
74
  XtScrollArrow,
73
75
  XtBar,
74
76
  XtLine,
@@ -146,6 +148,7 @@ export default {
146
148
  XtDatePicker,
147
149
  XtIcon,
148
150
  XtTable,
151
+ XtList,
149
152
  XtScrollArrow,
150
153
  XtBar,
151
154
  XtLine,
@@ -1,72 +0,0 @@
1
- @import '../../../styles/variables.scss';
2
-
3
- .xt-card-item {
4
- --xt-card-item-color: var(--xt-color-primary, #1890ff);
5
-
6
- min-height: var(--xt-card-item-min-height, 40px);
7
- }
8
-
9
- .xt-card-item + .xt-card-item {
10
- margin-top: var(--xt-card-item-gap, 10px);
11
- }
12
-
13
- .xt-card-item.is-border {
14
- border-right: var(--xt-card-item-border-width, 3px) solid transparent;
15
- background-color: var(--xt-color-bg-secondary, #f5f7fa);
16
- }
17
-
18
- .xt-card-item.is-border.is-primary {
19
- border-right-color: var(--xt-card-item-color);
20
- }
21
-
22
- .xt-card-item.is-border.is-primary .item__value .value {
23
- color: var(--xt-card-item-color);
24
- }
25
-
26
- .xt-card-item.is-border.is-success {
27
- border-right-color: var(--xt-color-success, #67C23A);
28
- }
29
-
30
- .xt-card-item.is-border.is-success .item__value .value {
31
- color: var(--xt-color-success, #67C23A);
32
- }
33
-
34
- .xt-card-item.is-border.is-warning {
35
- border-right-color: var(--xt-color-warning, #E6A23C);
36
- }
37
-
38
- .xt-card-item.is-border.is-warning .item__value .value {
39
- color: var(--xt-color-warning, #E6A23C);
40
- }
41
-
42
- .xt-card-item.is-border.is-danger {
43
- border-right-color: var(--xt-color-danger, #F56C6C);
44
- }
45
-
46
- .xt-card-item.is-border.is-danger .item__value .value {
47
- color: var(--xt-color-danger, #F56C6C);
48
- }
49
-
50
- .xt-card-item .item__value {
51
- padding-right: 10px;
52
- }
53
-
54
- .xt-card-item .item__value .value {
55
- font-weight: 600;
56
- font-size: var(--xt-font-size-base, 14px);
57
- }
58
-
59
- .xt-card-item .item__value .unit {
60
- font-size: var(--xt-font-size-small, 13px);
61
- color: var(--xt-color-text-secondary, #909399);
62
- }
63
-
64
- .xt-card-item .item__label {
65
- padding-left: 10px;
66
- color: var(--xt-color-text-secondary, #909399);
67
- }
68
-
69
- .xt-card-item .item__unit {
70
- padding-left: 5px;
71
- color: var(--xt-color-text-secondary, #909399);
72
- }