n20-common-lib 3.1.5 → 3.1.7

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n20-common-lib",
3
- "version": "3.1.5",
3
+ "version": "3.1.7",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -74,6 +74,7 @@
74
74
  "devDependencies": {
75
75
  "@babel/plugin-proposal-optional-chaining": "^7.14.5",
76
76
  "@babel/plugin-transform-flow-comments": "^7.14.5",
77
+ "@trivago/prettier-plugin-sort-imports": "^4.3.0",
77
78
  "@vue/cli-plugin-babel": "~4.5.0",
78
79
  "@vue/cli-plugin-eslint": "~4.5.0",
79
80
  "@vue/cli-plugin-unit-jest": "^5.0.8",
@@ -50,7 +50,9 @@
50
50
  @import './page.scss';
51
51
  @import './view-toggle.scss';
52
52
  @import './pivot.scss';
53
+ @import './dynamic-table.scss';
53
54
  @import './v3/table.scss';
54
55
  @import './v3/secondary-tab.scss';
56
+
55
57
  //临时引入
56
58
  @import '../../components/ChildRange/style.scss';
@@ -0,0 +1,6 @@
1
+ .dynamic-table-pro {
2
+ .el-form-item,
3
+ .el-form-item--small.el-form-item {
4
+ margin-bottom: 0;
5
+ }
6
+ }
@@ -33,6 +33,8 @@
33
33
  /* 左侧边栏 */
34
34
  .pivot-sidebar-left {
35
35
  width: 280px;
36
+ min-width: 280px;
37
+ flex-shrink: 0;
36
38
  background: #f2f3f5;
37
39
  border-right: 1px solid #e5e6eb;
38
40
  display: flex;
@@ -257,6 +259,7 @@
257
259
  /* 主内容区域 */
258
260
  .pivot-main {
259
261
  flex: 1;
262
+ min-width: 0;
260
263
  display: flex;
261
264
  flex-direction: column;
262
265
  position: relative;
@@ -484,6 +487,7 @@
484
487
  width: 100%;
485
488
  height: 100%;
486
489
  position: relative;
490
+ overflow: hidden;
487
491
  }
488
492
 
489
493
  .chart-wrapper {
@@ -528,81 +532,6 @@
528
532
  min-height: 0;
529
533
  }
530
534
 
531
- .table-container {
532
- width: 100%;
533
- height: 100%;
534
- overflow: auto;
535
- }
536
-
537
- .pivot-table {
538
- width: 100%;
539
- min-width: 769px;
540
- border-collapse: collapse;
541
- table-layout: fixed;
542
- font-size: 14px;
543
-
544
- th,
545
- td {
546
- padding: 10px 16px;
547
- border-right: 1px solid #e5e6eb;
548
- border-bottom: 1px solid #e5e6eb;
549
- height: 57px;
550
- box-sizing: border-box;
551
- }
552
-
553
- th {
554
- background: #f2f3f5;
555
- font-weight: 500;
556
- color: #1d2129;
557
- position: sticky;
558
- top: 0;
559
- z-index: 1;
560
- text-align: left;
561
- height: 40px;
562
- }
563
-
564
- th.metric-header {
565
- text-align: right;
566
- }
567
-
568
- td.dimension-cell {
569
- text-align: left;
570
- color: #1d2129;
571
- vertical-align: middle;
572
- }
573
-
574
- td.metric-cell {
575
- text-align: right;
576
- vertical-align: middle;
577
-
578
- .cell-content {
579
- display: flex;
580
- flex-direction: column;
581
- align-items: flex-end;
582
- justify-content: center;
583
- height: 100%;
584
- }
585
-
586
- .cell-value {
587
- font-family: 'Open Sans', sans-serif;
588
- font-size: 14px;
589
- line-height: 22px;
590
- color: #1d2129;
591
- }
592
-
593
- .cell-count {
594
- font-family: 'PingFang SC', sans-serif;
595
- font-size: 12px;
596
- line-height: 20px;
597
- color: #86909c;
598
- }
599
- }
600
-
601
- tbody tr:hover {
602
- background: #f7f8fa;
603
- }
604
- }
605
-
606
535
  .empty-state {
607
536
  width: 100%;
608
537
  height: 100%;
@@ -615,6 +544,8 @@
615
544
  /* 右侧边栏 */
616
545
  .pivot-sidebar-right {
617
546
  width: 280px;
547
+ min-width: 280px;
548
+ flex-shrink: 0;
618
549
  background: #fff;
619
550
  border-left: 1px solid #e5e6eb;
620
551
  display: flex;
@@ -1067,6 +998,8 @@
1067
998
  display: flex;
1068
999
  flex-direction: column;
1069
1000
  height: 100%;
1001
+ width: 100%;
1002
+ overflow: hidden;
1070
1003
  }
1071
1004
 
1072
1005
  .table-header {
@@ -1088,88 +1021,76 @@
1088
1021
  font-family: 'PingFang SC', sans-serif;
1089
1022
  }
1090
1023
 
1091
- /* 交叉统计表格样式 */
1092
- .cross-table-container {
1024
+ /* vxe-table 表格样式 */
1025
+ .table-container {
1093
1026
  flex: 1;
1094
- overflow: auto;
1095
- }
1096
-
1097
- .cross-pivot-table {
1027
+ min-height: 0;
1028
+ overflow: hidden;
1098
1029
  width: 100%;
1099
- min-width: 100%;
1100
- border-collapse: collapse;
1101
- table-layout: fixed;
1102
1030
  }
1103
1031
 
1104
- .cross-header-row th {
1105
- background: #f2f3f5;
1106
- border-right: 1px solid #e5e6eb;
1107
- border-bottom: 1px solid #e5e6eb;
1108
- padding: 9px 16px;
1109
- font-size: 14px;
1110
- font-weight: 500;
1111
- color: #1d2129;
1112
- text-align: center;
1113
- height: 40px;
1114
- box-sizing: border-box;
1115
- }
1032
+ .pivot-vxe-table {
1033
+ font-family: 'PingFang SC', sans-serif;
1116
1034
 
1117
- .cross-dimension-header {
1118
- width: 177px;
1119
- min-width: 177px;
1120
- text-align: left !important;
1121
- }
1035
+ /* 表头样式 */
1036
+ .vxe-table--header-wrapper {
1037
+ .vxe-header--column {
1038
+ background: #f2f3f5;
1039
+ font-weight: 500;
1040
+ color: #1d2129;
1041
+ font-size: 14px;
1042
+ border-right: 1px solid #e5e6eb;
1043
+ }
1122
1044
 
1123
- .cross-metric-header {
1124
- width: 148px;
1125
- min-width: 148px;
1126
- text-align: right !important;
1127
- }
1045
+ /* 多级表头样式 */
1046
+ .vxe-header--row {
1047
+ .vxe-header--column {
1048
+ border-bottom: 1px solid #e5e6eb;
1049
+ }
1050
+ }
1128
1051
 
1129
- .cross-data-row td {
1130
- padding: 10px 16px;
1131
- border-right: 1px solid #e5e6eb;
1132
- border-bottom: 1px solid #e5e6eb;
1133
- font-size: 14px;
1134
- color: #1d2129;
1135
- vertical-align: middle;
1136
- height: 57px;
1137
- box-sizing: border-box;
1138
- }
1052
+ /* 多级表头父级样式 */
1053
+ .vxe-header--column.col--group {
1054
+ background: #f7f8fa;
1055
+ text-align: center;
1056
+ border-bottom: 1px solid #e5e6eb;
1057
+ }
1058
+ }
1139
1059
 
1140
- .cross-dimension-cell {
1141
- background: #fff;
1142
- text-align: left !important;
1143
- width: 177px;
1144
- min-width: 177px;
1060
+ /* 单元格样式 */
1061
+ .vxe-body--column {
1062
+ font-size: 14px;
1063
+ color: #1d2129;
1064
+ border-right: 1px solid #e5e6eb;
1065
+ }
1066
+
1067
+ /* 悬停行样式 */
1068
+ .vxe-body--row.row--hover {
1069
+ background: #f7f8fa;
1070
+ }
1145
1071
  }
1146
1072
 
1147
- .cross-metric-cell {
1148
- background: #fff;
1149
- text-align: right !important;
1150
- width: 148px;
1151
- min-width: 148px;
1073
+ /* 指标单元格内容 */
1074
+ .metric-cell-content {
1075
+ display: flex;
1076
+ flex-direction: column;
1077
+ align-items: flex-end;
1078
+ justify-content: center;
1079
+ height: 100%;
1152
1080
  }
1153
1081
 
1154
- .cross-metric-value {
1155
- display: block;
1082
+ .metric-value {
1156
1083
  font-family: 'Open Sans', sans-serif;
1157
1084
  font-size: 14px;
1158
- font-weight: 400;
1159
- color: #1d2129;
1160
1085
  line-height: 22px;
1086
+ color: #1d2129;
1161
1087
  }
1162
1088
 
1163
- .cross-metric-count {
1164
- display: block;
1089
+ .metric-count {
1165
1090
  font-family: 'PingFang SC', sans-serif;
1166
1091
  font-size: 12px;
1167
- color: #86909c;
1168
1092
  line-height: 20px;
1169
- }
1170
-
1171
- .cross-data-row:hover td {
1172
- background: #f7f8fa;
1093
+ color: #86909c;
1173
1094
  }
1174
1095
 
1175
1096
  /* 分页组件 */
@@ -8,7 +8,7 @@ import inputNumber from '../InputNumber/index.vue'
8
8
  import InputNumberRange from '../InputNumber/numberRange.vue'
9
9
  import selectTree from '../SelectTree/index.vue'
10
10
  import selectTreePro from '../SelectTree/pro.vue'
11
- import { setOptionsMap } from './utils.js'
11
+
12
12
  const canvas = document.createElement('canvas')
13
13
  const context = canvas.getContext('2d')
14
14
  context.font = '14px Ping Fang SC'
@@ -83,7 +83,6 @@ export default {
83
83
  resKeys.forEach((key) => {
84
84
  list = list[key]
85
85
  })
86
- setOptionsMap(this.item.id, list)
87
86
  this.$set(this.item, 'options', list)
88
87
  // 同步更新 filterList 中的 options,确保两者一致,
89
88
  // 防止 AdvancedFilter 的 filterList watcher 用旧数据覆盖 reqOptions 结果
@@ -5,7 +5,7 @@
5
5
  <el-form-item
6
6
  v-for="item in GroupData"
7
7
  :key="getOnlyKey(item)"
8
- :class="[prefixCls + '-item', activeClass(item)]"
8
+ :class="[prefixCls + '-item', activeClassMap[item.value || item[onlyKey]] || '']"
9
9
  :label="item.label"
10
10
  :required="!!item.required"
11
11
  :disabled="item.props && item.props.disabled"
@@ -107,14 +107,16 @@
107
107
  </template>
108
108
 
109
109
  <script>
110
- // import formItemInput from './form-item-input.vue'
111
110
  import XEUtils from 'xe-utils'
111
+
112
+ import filterItem from './filterItem.vue'
113
+ import formItemRender from './formItemRender.vue'
114
+
112
115
  import axios from '../../utils/axios.js'
113
116
  import ClDialog from '../Dialog/index.vue'
114
117
  import InputSearch from '../InputSearch/index.vue'
115
- import filterItem from './filterItem.vue'
116
- import formItemRender from './formItemRender.vue'
117
- import { getOnlyKey } from './utils'
118
+
119
+ const getOnlyKeys = (key, data) => (key ? data[key] : Date.now().toString(36) + Math.random().toString(36).slice(2, 7))
118
120
 
119
121
  const prefixCls = 'n20-advanced-filter'
120
122
  export default {
@@ -219,6 +221,48 @@ export default {
219
221
 
220
222
  const nonFixedItemIds = nonFixedItems.map((item) => item[this.onlyKey])
221
223
  return nonFixedItemIds.every((id) => this.checkList.includes(id))
224
+ },
225
+ // 通过深度遍历 model 所有属性触发 getter,建立完整响应式依赖
226
+ // 同时收集对象级 __ob__.dep,确保 $set 新增属性时也能触发重算
227
+ _modelValues() {
228
+ const model = this.model
229
+ // 遍历所有自有属性触发 getter(收集属性级 dep)
230
+ // Object.keys 在 Vue 2 内部会触发对象级 dep 收集
231
+ const keys = Object.keys(model)
232
+ const values = []
233
+ for (let i = 0; i < keys.length; i++) {
234
+ values.push(model[keys[i]])
235
+ }
236
+ return values
237
+ },
238
+ activeClassMap() {
239
+ // 访问 _modelValues 建立深度依赖,确保 model 任何值变化都能触发重算
240
+ void this._modelValues
241
+
242
+ const model = this.model
243
+ const hasValue = (val) => {
244
+ if (Array.isArray(val)) return val.length > 0
245
+ return val !== null && val !== undefined && val !== ''
246
+ }
247
+ const hasRange = (start, end) => hasValue(model[start]) && hasValue(model[end])
248
+ const rangeTypes = ['daterange', 'datetimerange', 'monthrange']
249
+
250
+ const map = {}
251
+ this.GroupData.forEach((item) => {
252
+ const key = item.value || item[this.onlyKey]
253
+ let isActive = false
254
+ if (item.type === 'numberrange') {
255
+ isActive = hasRange(item.startValue, item.endValue)
256
+ } else if (rangeTypes.includes(item.type)) {
257
+ isActive = hasRange(item.startDate, item.endDate)
258
+ } else if (item.slotFields && item.slotFields.length > 0) {
259
+ isActive = item.slotFields.some((field) => hasValue(model[field]))
260
+ } else {
261
+ isActive = hasValue(model[item.value])
262
+ }
263
+ map[key] = isActive ? this.prefixCls + '-active' : ''
264
+ })
265
+ return map
222
266
  }
223
267
  },
224
268
  watch: {
@@ -296,7 +340,7 @@ export default {
296
340
  this.defineCheckList = XEUtils.clone(_checkList, true)
297
341
  },
298
342
  getOnlyKey(data) {
299
- return getOnlyKey(this.onlyKey, data)
343
+ return getOnlyKeys(this.onlyKey, data)
300
344
  },
301
345
  setOptions(id, opts) {
302
346
  this.optionsMap[id] = opts
@@ -316,26 +360,7 @@ export default {
316
360
  filter() {
317
361
  return this.model
318
362
  },
319
- activeClass(item) {
320
- // 判断值是否有效(非空)
321
- const hasValue = (val) => {
322
- if (Array.isArray(val)) return val.length > 0
323
- return val !== null && val !== undefined && val !== ''
324
- }
325
-
326
- // 判断范围类型是否有效(起止值都存在)
327
- const hasRange = (start, end) => hasValue(this.model[start]) && hasValue(this.model[end])
328
363
 
329
- const rangeTypes = ['daterange', 'datetimerange', 'monthrange']
330
-
331
- if (item.type === 'numberrange') {
332
- return hasRange(item.startValue, item.endValue) ? this.prefixCls + '-active' : ''
333
- }
334
- if (rangeTypes.includes(item.type)) {
335
- return hasRange(item.startDate, item.endDate) ? this.prefixCls + '-active' : ''
336
- }
337
- return hasValue(this.model[item.value]) ? this.prefixCls + '-active' : ''
338
- },
339
364
  handleClose(item) {
340
365
  const key = item[this.onlyKey] || item.value
341
366
  if (this.optionsMap[key]) {
@@ -397,35 +422,42 @@ export default {
397
422
  this.$emit('clear')
398
423
  },
399
424
  setModelData(list) {
400
- let arr = []
425
+ // 收集当前选中列表中的所有字段
426
+ const selectedFields = new Set()
401
427
  list.forEach((res) => {
402
- // 收集普通字段的 value
403
- if (res.value) {
404
- arr.push(res.value)
405
- }
406
- // 收集日期范围类型的字段
407
- if (res.startDate && res.endDate) {
408
- arr.push(res.startDate)
409
- arr.push(res.endDate)
410
- }
411
- // 收集数字范围类型的字段
412
- if (res.startValue && res.endValue) {
413
- arr.push(res.startValue)
414
- arr.push(res.endValue)
415
- }
416
- // 收集插槽类型可能的自定义字段(slotFields 数组)
428
+ if (res.value) selectedFields.add(res.value)
429
+ if (res.startDate) selectedFields.add(res.startDate)
430
+ if (res.endDate) selectedFields.add(res.endDate)
431
+ if (res.startValue) selectedFields.add(res.startValue)
432
+ if (res.endValue) selectedFields.add(res.endValue)
417
433
  if (res.type === 'slot' && Array.isArray(res?.slotFields)) {
418
434
  res?.slotFields.forEach((field) => {
419
- if (field && !arr.includes(field)) {
420
- arr.push(field)
421
- }
435
+ if (field) selectedFields.add(field)
422
436
  })
423
437
  }
424
438
  })
439
+
440
+ // 收集 filterList 中定义的所有字段
441
+ const allDefinedFields = new Set()
442
+ this.filterList.forEach((item) => {
443
+ if (item.value) allDefinedFields.add(item.value)
444
+ if (item.startDate) allDefinedFields.add(item.startDate)
445
+ if (item.endDate) allDefinedFields.add(item.endDate)
446
+ if (item.startValue) allDefinedFields.add(item.startValue)
447
+ if (item.endValue) allDefinedFields.add(item.endValue)
448
+ if (item.type === 'slot' && Array.isArray(item?.slotFields)) {
449
+ item?.slotFields.forEach((field) => {
450
+ if (field) allDefinedFields.add(field)
451
+ })
452
+ }
453
+ })
454
+
455
+ // 只清除:在 filterList 中定义但不在选中列表中的字段
425
456
  for (const key in this.model) {
426
457
  if (Object.hasOwnProperty.call(this.model, key)) {
427
- if (!arr.includes(key)) {
428
- delete this.model[key]
458
+ // 仅当该字段在 filterList 中定义且未被选中时才清除
459
+ if (allDefinedFields.has(key) && !selectedFields.has(key)) {
460
+ this.$delete(this.model, key)
429
461
  }
430
462
  }
431
463
  }
@@ -2,10 +2,6 @@
2
2
  <!-- 图表视图 -->
3
3
  <div class="chart-view">
4
4
  <div v-if="hasData" class="chart-wrapper">
5
- <div class="chart-header">
6
- <div class="chart-title">数据概览</div>
7
- <div class="chart-desc">按维度「{{ dimensionLabel }}」聚合后的结果,共 {{ dataCount }} 条</div>
8
- </div>
9
5
  <div v-if="unitLabel" class="chart-unit-label">{{ unitLabel }}</div>
10
6
  <div ref="chartContainer" class="chart-container"></div>
11
7
  </div>
@@ -16,8 +12,8 @@
16
12
  </template>
17
13
 
18
14
  <script>
19
- import Empty from '../Empty/index.vue'
20
15
  import importG from '../../utils/importGlobal.js'
16
+ import Empty from '../Empty/index.vue'
21
17
 
22
18
  export default {
23
19
  name: 'ChartView',
@@ -106,4 +102,4 @@ export default {
106
102
  this.destroyChart()
107
103
  }
108
104
  }
109
- </script>
105
+ </script>