@truenewx/tnxvue3 3.0.0-alpha.32 → 3.0.0-alpha.34

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": "@truenewx/tnxvue3",
3
- "version": "3.0.0-alpha.32",
3
+ "version": "3.0.0-alpha.34",
4
4
  "description": "互联网技术解决方案:Vue3扩展支持",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -24,7 +24,7 @@
24
24
  "vue-router": "~4.4.0"
25
25
  },
26
26
  "dependencies": {
27
- "@truenewx/tnxcore": "3.0.0-alpha.19",
27
+ "@truenewx/tnxcore": "3.0.0-alpha.20",
28
28
  "@element-plus/icons-vue": "2.3.1",
29
29
  "async-validator": "4.2.5",
30
30
  "mitt": "3.0.1"
@@ -64,6 +64,8 @@
64
64
  ],
65
65
  "no-undef": "warn",
66
66
  "no-useless-escape": "warn",
67
+ "no-empty": "off",
68
+ "vue/no-mutating-props": "warn",
67
69
  "vue/no-v-model-argument": "off",
68
70
  "vue/multi-word-component-names": "off"
69
71
  }
@@ -3,37 +3,39 @@
3
3
  <el-table ref="table"
4
4
  class="tnxel-edit-table"
5
5
  :class="{'padding-none': !padding}"
6
- :data="data"
6
+ :data="tableDataList"
7
+ :row-key="rowKey"
8
+ :row-class-name="tableRowClassName"
7
9
  :highlight-current-row="selectable"
8
10
  border
9
11
  @current-change="onSelectRow"
10
12
  >
11
13
  <slot></slot>
12
14
  <el-table-column label="排序" align="center" width="60px" v-if="sortable">
13
- <template #default="scope">
15
+ <template #default="{row, $index}">
14
16
  <tnxel-button
15
17
  type="primary" link
16
18
  icon="Top"
17
- :class="{'text-transparent': scope.$index === 0}"
18
- @click="sortUp(scope.$index)"
19
+ :class="{'text-transparent': !isSortableRow(row, $index,true)}"
20
+ @click="sortUp(row, $index)"
19
21
  />
20
22
  <tnxel-button
21
23
  type="primary" link
22
24
  icon="Bottom"
23
25
  class="ms-0"
24
- :class="{'text-transparent': scope.$index === data.length - 1}"
25
- @click="sortDown(scope.$index)"
26
+ :class="{'text-transparent': !isSortableRow(row, $index, false)}"
27
+ @click="sortDown(row, $index)"
26
28
  />
27
29
  </template>
28
30
  </el-table-column>
29
31
  <el-table-column label="移除" align="center" width="60px" v-if="removable">
30
- <template #default="scope">
32
+ <template #default="{row, $index}">
31
33
  <tnxel-button
32
34
  type="primary" link
33
35
  icon="CircleClose"
34
- :tooltip="scope.row.removeTip"
35
- @click="toRemoveRow(scope.$index)"
36
- />
36
+ :tooltip="row.removeTip"
37
+ @click="removeRow(row, $index)"
38
+ v-if="isRemovableRow(row, $index)"/>
37
39
  </template>
38
40
  </el-table-column>
39
41
  </el-table>
@@ -61,7 +63,7 @@ export default {
61
63
  default: true,
62
64
  },
63
65
  removable: {
64
- type: Boolean,
66
+ type: [Boolean, Function],
65
67
  default: true,
66
68
  },
67
69
  addText: {
@@ -76,9 +78,11 @@ export default {
76
78
  }
77
79
  }
78
80
  },
81
+ rowKey: [Function, String],
82
+ rowClassName: [Function, String],
79
83
  selectable: Boolean, // 可否选择行
80
84
  padding: Boolean, // 单元格中是否有边距
81
- sortable: Boolean, // 可否调整行的顺序
85
+ sortable: [Boolean, Function], // 可否调整行的顺序
82
86
  rules: [String, Object], // 加载字段校验规则的URL地址,或规则集对象
83
87
  rulesApp: { // 加载字段校验规则的应用名称
84
88
  type: String,
@@ -86,13 +90,30 @@ export default {
86
90
  },
87
91
  rulesLoaded: Function, // 规则集加载后的附加处理函数,仅在rule为字符串类型的URL地址时有效
88
92
  },
93
+ emits: ['sorted', 'removed'],
89
94
  data() {
90
95
  return {
91
96
  id: window.tnx.util.string.uuid32(),
92
97
  selectedRow: null,
93
98
  validationRules: {},
99
+ indexes: {}, // 附加索引映射集,key:row,value:一级节点为单个索引下标,多级节点为其在各级节点中的索引下标集合
94
100
  }
95
101
  },
102
+ computed: {
103
+ tableDataList() {
104
+ for (let rowIndex = 0; rowIndex < this.data.length; rowIndex++) {
105
+ let row = this.data[rowIndex];
106
+ this.indexes[this.getRowKey(row, rowIndex)] = rowIndex;
107
+ if (this.rowKey && row.children) {
108
+ for (let j = 0; j < row.children.length; j++) {
109
+ let child = row.children[j];
110
+ this.indexes[this.getRowKey(child)] = [rowIndex, j];
111
+ }
112
+ }
113
+ }
114
+ return this.data;
115
+ },
116
+ },
96
117
  watch: {
97
118
  data(newData, oldData) {
98
119
  if (!oldData) {
@@ -121,8 +142,11 @@ export default {
121
142
  });
122
143
  },
123
144
  methods: {
145
+ getRowKey(row, rowIndex) {
146
+ return this.rowKey ? row[this.rowKey] : (rowIndex + '');
147
+ },
124
148
  initElements() {
125
- this.focusRowFirstInput(0);
149
+ this.focusRowFirstInput(this.tableDataList[0]);
126
150
  // 为富文本输入框添加默认title
127
151
  let table = this.$refs.table.$el;
128
152
  let textareas = table.getElementsByTagName('textarea');
@@ -280,23 +304,39 @@ export default {
280
304
  this.selectedRow = row;
281
305
  }
282
306
  },
283
- focusRowFirstInput(rowIndex) {
284
- let table = this.$refs.table.$el;
285
- let rows = table.getElementsByClassName('el-table__row');
286
- if (rows.length) {
287
- let row = rows[rowIndex];
288
- if (row) {
289
- let cells = row.getElementsByClassName('cell');
290
- for (let cell of cells) {
291
- let input = cell.querySelector('input:first-child');
292
- if (input) {
293
- input.focus();
294
- return true;
295
- } else {
296
- let textarea = cell.querySelector('textarea:first-child');
297
- if (textarea) {
298
- textarea.focus();
307
+ tableRowClassName(data) {
308
+ let classNames = [this.getRowClassName(data.row, data.$index)];
309
+ if (typeof this.rowClassName === 'function') {
310
+ classNames.push(this.rowClassName(data));
311
+ } else {
312
+ classNames.push(this.rowClassName);
313
+ }
314
+ return classNames.join(' ');
315
+ },
316
+ getRowClassName(row, rowIndex) {
317
+ let index = this.indexes[this.getRowKey(row, rowIndex)];
318
+ return 'tnxel-edit-table__row--' + (Array.isArray(index) ? index.join('_') : index);
319
+ },
320
+ focusRowFirstInput(row) {
321
+ if (row) {
322
+ let table = this.$refs.table.$el;
323
+ let rowClassName = this.getRowClassName(row);
324
+ let rowDoms = table.getElementsByClassName(rowClassName);
325
+ if (rowDoms.length) {
326
+ let rowDom = rowDoms[0];
327
+ if (rowDom) {
328
+ let cells = rowDom.getElementsByClassName('cell');
329
+ for (let cell of cells) {
330
+ let input = cell.querySelector('input:first-child');
331
+ if (input) {
332
+ input.focus();
299
333
  return true;
334
+ } else {
335
+ let textarea = cell.querySelector('textarea:first-child');
336
+ if (textarea) {
337
+ textarea.focus();
338
+ return true;
339
+ }
300
340
  }
301
341
  }
302
342
  }
@@ -306,33 +346,102 @@ export default {
306
346
  },
307
347
  toAddRow() {
308
348
  let row = this.newRow();
309
- this.data.push(row);
310
- this.$nextTick(() => {
311
- this.bindElementRules();
312
- this.focusRowFirstInput(this.data.length - 1);
313
- });
349
+ if (row) {
350
+ let rowIndex = this.data.length;
351
+ this.indexes[this.getRowKey(row, rowIndex)] = rowIndex;
352
+ this.data.push(row);
353
+ this.$nextTick(() => {
354
+ setTimeout(() => {
355
+ this.bindElementRules();
356
+ this.focusRowFirstInput(row);
357
+ });
358
+ });
359
+ }
314
360
  },
315
- toRemoveRow(index) {
316
- let row = this.data[index];
317
- this.data.splice(index, 1);
318
- this.$emit('removed', row, index);
319
- this.$nextTick(() => {
320
- if (!this.focusRowFirstInput(index)) {
321
- this.focusRowFirstInput(index - 1);
361
+ isRemovableRow(row, rowIndex) {
362
+ if (typeof this.removable === 'function') {
363
+ let index = this.indexes[this.getRowKey(row, rowIndex)];
364
+ let removable = this.removable(row, index);
365
+ if (removable !== undefined) {
366
+ return removable;
322
367
  }
368
+ // 自定义函数如果返回undefined,则使用默认判断逻辑
369
+ }
370
+ return !!this.removable;
371
+ },
372
+ removeRow(row, rowIndex) {
373
+ this.locateRow(row, rowIndex, (dataList, index) => {
374
+ delete this.indexes[this.getRowKey(row, rowIndex)];
375
+ dataList.splice(index, 1);
376
+ this.$emit('removed', row, rowIndex);
377
+ this.$nextTick(() => {
378
+ setTimeout(() => {
379
+ let next = dataList[index] || dataList[index - 1];
380
+ this.focusRowFirstInput(next);
381
+ });
382
+ });
323
383
  });
324
384
  },
325
- sortDown(index) {
326
- if (0 <= index && index < this.data.length - 1) {
327
- let deleted = this.data.splice(index, 1);
328
- this.data.splice(index + 1, 0, deleted[0]);
385
+ locateRow(row, rowIndex, func) {
386
+ if (typeof rowIndex === 'function') {
387
+ func = rowIndex;
388
+ rowIndex = undefined;
329
389
  }
390
+ let indexes = this.indexes[this.getRowKey(row, rowIndex)];
391
+ if (!Array.isArray(indexes)) {
392
+ indexes = [indexes];
393
+ }
394
+ let dataList = this.tableDataList;
395
+ for (let i = 0; i < indexes.length - 1; i++) {
396
+ dataList = dataList[indexes[i]].children;
397
+ }
398
+ let index = indexes[indexes.length - 1];
399
+ return func(dataList, index);
330
400
  },
331
- sortUp(index) {
332
- if (0 < index && index < this.data.length) {
333
- let deleted = this.data.splice(index, 1);
334
- this.data.splice(index - 1, 0, deleted[0]);
401
+ isSortableRow(row, rowIndex, up) {
402
+ if (typeof this.sortable === 'function') {
403
+ let index = this.indexes[this.getRowKey(row, rowIndex)];
404
+ let sortable = this.sortable(row, index, up);
405
+ if (sortable !== undefined) {
406
+ return sortable;
407
+ }
408
+ // 自定义函数如果返回undefined,则使用默认判断逻辑
409
+ }
410
+ if (this.sortable) {
411
+ if (up) {
412
+ let index = this.indexes[this.getRowKey(row, rowIndex)];
413
+ if (Array.isArray(index)) {
414
+ index = index[index.length - 1];
415
+ }
416
+ return index > 0;
417
+ } else {
418
+ return this.locateRow(row, rowIndex, (dataList, index) => {
419
+ return index < dataList.length - 1;
420
+ });
421
+ }
335
422
  }
423
+ return false;
424
+ },
425
+ sortUp(row, rowIndex) {
426
+ this.locateRow(row, rowIndex, (dataList, index) => {
427
+ if (index > 0) {
428
+ let deleted = dataList.splice(index, 1);
429
+ dataList.splice(index - 1, 0, deleted[0]);
430
+ this.$emit('sorted', row, rowIndex, true);
431
+ }
432
+ });
433
+ },
434
+ sortDown(row, rowIndex) {
435
+ this.locateRow(row, rowIndex, (dataList, index) => {
436
+ if (index < dataList.length - 1) {
437
+ let deleted = dataList.splice(index, 1);
438
+ dataList.splice(index + 1, 0, deleted[0]);
439
+ this.$emit('sorted', row, rowIndex, false);
440
+ }
441
+ });
442
+ },
443
+ toggleRowExpansion(row, expanded) {
444
+ this.$refs.table.toggleRowExpansion(row, expanded);
336
445
  },
337
446
  }
338
447
  }
@@ -351,6 +460,12 @@ export default {
351
460
  height: 32px;
352
461
  }
353
462
 
463
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input-group__append,
464
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input-group__prepend {
465
+ border-radius: 0;
466
+ padding: 0 16px;
467
+ }
468
+
354
469
  .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input--small .el-input__wrapper,
355
470
  .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input--small .el-select__wrapper {
356
471
  height: 24px;
@@ -377,6 +492,45 @@ export default {
377
492
  text-align: left;
378
493
  }
379
494
 
495
+ .tnxel-edit-table-container .el-table .cell {
496
+ display: flex;
497
+ align-items: center;
498
+ }
499
+
500
+ .tnxel-edit-table-container .el-table .is-center .cell {
501
+ justify-content: center;
502
+ }
503
+
504
+ .tnxel-edit-table-container .el-table .el-table__placeholder {
505
+ width: 20px;
506
+ }
507
+
508
+ .tnxel-edit-table-container .el-table .el-table__indent + .el-table__placeholder {
509
+ width: 16px;
510
+ }
511
+
512
+ .tnxel-edit-table-container .el-table .is-center .el-table__placeholder {
513
+ width: 16px;
514
+ }
515
+
516
+ .tnxel-edit-table-container .el-table .is-center .el-table__indent + .el-table__placeholder {
517
+ width: 24px;
518
+ }
519
+
520
+ .tnxel-edit-table-container .el-table [class*=el-table__row--level] .el-table__expand-icon {
521
+ margin-left: 4px;
522
+ margin-right: 4px;
523
+ }
524
+
525
+ .tnxel-edit-table-container .el-table [class*=el-table__row--level] .is-center .el-table__expand-icon {
526
+ margin-left: 0;
527
+ }
528
+
529
+ .tnxel-edit-table-container .tnxel-edit-table .el-table__expand-icon + div {
530
+ width: auto;
531
+ flex-grow: 1;
532
+ }
533
+
380
534
  .tnxel-edit-table-container .el-table .is-error {
381
535
  box-shadow: 0 0 0 1px var(--el-color-danger) inset;
382
536
  }
@@ -1,16 +1,16 @@
1
1
  <template>
2
2
  <div class="tnxel-input-number" :class="containerClassObject">
3
3
  <el-input-number ref="input" class="flex-grow-1" :class="{'rounded-end-0': suffix, 'text-start': !controls}"
4
- v-model="model"
5
- :min="min" :max="max"
6
- :controls="controls" controls-position="right"
7
- :placeholder="placeholderText" :disabled="disabled"
8
- :step="step || Math.pow(10, -this.scale)" step-strictly
9
- :precision="scale"
10
- :value-on-clear="null"
11
- :size="size"
12
- @change="onChange"
13
- @blur="$emit('blur', $event)"/>
4
+ v-model="model"
5
+ :min="min" :max="max"
6
+ :controls="controls" controls-position="right"
7
+ :placeholder="placeholderText" :disabled="disabled"
8
+ :step="step || Math.pow(10, -this.scale)" step-strictly
9
+ :precision="scale"
10
+ :value-on-clear="null"
11
+ :size="size"
12
+ @change="onChange"
13
+ @blur="$emit('blur', $event)"/>
14
14
  <div class="el-input-group__append" v-if="suffix">{{ suffix }}</div>
15
15
  </div>
16
16
  </template>
@@ -41,7 +41,7 @@ export default {
41
41
  required: Boolean,
42
42
  size: String,
43
43
  },
44
- emits: ['update:modelValue', 'blur'],
44
+ emits: ['update:modelValue', 'blur', 'change'],
45
45
  data() {
46
46
  return {
47
47
  model: typeof this.modelValue === 'string' ? Number(this.modelValue) : this.modelValue,
@@ -103,7 +103,11 @@ export default {
103
103
  this.$refs.input.focus();
104
104
  },
105
105
  onChange() {
106
- this.validateRequired(false);
106
+ if (this.validateRequired(false)) {
107
+ this.$nextTick(() => {
108
+ this.$emit('change', this.modelValue);
109
+ });
110
+ }
107
111
  },
108
112
  }
109
113
  }
package/src/tnxvue.js CHANGED
@@ -38,6 +38,42 @@ export default build('tnxvue', () => {
38
38
  window.tnx.app.page.stopCache(router, from.path);
39
39
  }
40
40
  },
41
+ /**
42
+ * 深度监听指定对象,在created()中调用才能生效
43
+ * @param vm 页面vue实例
44
+ * @param target 要监听的对象
45
+ * @param handler 处理函数
46
+ */
47
+ deepWatch(vm, target, handler) {
48
+ vm.$watch(() => JSON.stringify(target), (newValue, oldValue) => {
49
+ if (newValue !== oldValue) {
50
+ const newObject = JSON.parse(newValue);
51
+ const oldObject = JSON.parse(oldValue);
52
+ this.deepCompare(newObject, oldObject, '', handler);
53
+ }
54
+ }, {deep: true});
55
+ },
56
+ deepCompare(object1, object2, path = '', handler) {
57
+ if (object1) {
58
+ const keys = Object.keys(object1);
59
+ keys.forEach(key => {
60
+ const fullPath = path ? `${path}.${key}` : key;
61
+ if (object2) {
62
+ if (Array.isArray(object1[key]) && Array.isArray(object2[key])) {
63
+ object1[key].forEach((item, index) => {
64
+ this.deepCompare(item, object2[key][index], `${fullPath}[${index}]`, handler);
65
+ });
66
+ } else if (typeof object1[key] === 'object' && typeof object2[key] === 'object') {
67
+ this.deepCompare(object1[key], object2[key], fullPath, handler);
68
+ } else if (object1[key] !== object2[key]) {
69
+ handler(object1[key], object2[key], fullPath);
70
+ }
71
+ } else {
72
+ handler(object1[key], undefined, fullPath);
73
+ }
74
+ });
75
+ }
76
+ },
41
77
  createVueInstance(rootComponent, router, rootProps) {
42
78
  let vm = Vue.createApp(rootComponent, rootProps);
43
79
  vm.use(this);