@truenewx/tnxvue3 3.0.0-alpha.33 → 3.0.0-alpha.35

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.33",
3
+ "version": "3.0.0-alpha.35",
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.21",
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
  }
@@ -30,6 +30,7 @@
30
30
  :loading="loading"
31
31
  :plain="plain"
32
32
  :link="link"
33
+ :text="text"
33
34
  :bg="bg"
34
35
  :autofocus="autofocus"
35
36
  :round="round"
@@ -75,6 +76,7 @@ export default {
75
76
  loading: Boolean,
76
77
  plain: Boolean,
77
78
  link: Boolean,
79
+ text: Boolean,
78
80
  bg: Boolean,
79
81
  autofocus: Boolean,
80
82
  round: Boolean,
@@ -42,7 +42,10 @@ export default {
42
42
  'tnxel-dialog-content': DialogContent,
43
43
  },
44
44
  props: {
45
- id: window.tnx.util.string.uuid32(),
45
+ id: {
46
+ type: String,
47
+ default: () => window.tnx.util.string.uuid32(),
48
+ },
46
49
  modelValue: Boolean,
47
50
  container: String,
48
51
  title: String,
@@ -153,7 +156,7 @@ export default {
153
156
  }
154
157
  }
155
158
  if (!$dialog || !$dialog.length) {
156
- $dialog = $('.el-dialog__wrapper[data-v-id="' + this.id + '"] .el-dialog:last');
159
+ $dialog = $('.tnxel-dialog[data-v-id="' + this.id + '"]');
157
160
  }
158
161
  if ($dialog.length) {
159
162
  let top = window.tnx.util.dom.getTopVerticallyCenteredOnPage($dialog[0]);
@@ -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], 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.rowIndex)];
309
+ if (typeof this.rowClassName === 'function') {
310
+ classNames.push(this.rowClassName(data));
311
+ } else if (this.rowClassName) {
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, rowIndex) {
321
+ if (row && this.$refs.table) {
322
+ let table = this.$refs.table.$el;
323
+ let rowClassName = this.getRowClassName(row, rowIndex);
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,107 @@ 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, rowIndex);
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];
380
+ let nextIndex = rowIndex;
381
+ if (!next) {
382
+ next = dataList[index - 1];
383
+ nextIndex = rowIndex - 1;
384
+ }
385
+ this.focusRowFirstInput(next, nextIndex);
386
+ });
387
+ });
323
388
  });
324
389
  },
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]);
390
+ locateRow(row, rowIndex, func) {
391
+ if (typeof rowIndex === 'function') {
392
+ func = rowIndex;
393
+ rowIndex = undefined;
394
+ }
395
+ let indexes = this.indexes[this.getRowKey(row, rowIndex)];
396
+ if (!Array.isArray(indexes)) {
397
+ indexes = [indexes];
329
398
  }
399
+ let dataList = this.tableDataList;
400
+ for (let i = 0; i < indexes.length - 1; i++) {
401
+ dataList = dataList[indexes[i]].children;
402
+ }
403
+ let index = indexes[indexes.length - 1];
404
+ return func(dataList, index);
330
405
  },
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]);
406
+ isSortableRow(row, rowIndex, up) {
407
+ if (typeof this.sortable === 'function') {
408
+ let index = this.indexes[this.getRowKey(row, rowIndex)];
409
+ let sortable = this.sortable(row, index, up);
410
+ if (sortable !== undefined) {
411
+ return sortable;
412
+ }
413
+ // 自定义函数如果返回undefined,则使用默认判断逻辑
414
+ }
415
+ if (this.sortable) {
416
+ if (up) {
417
+ let index = this.indexes[this.getRowKey(row, rowIndex)];
418
+ if (Array.isArray(index)) {
419
+ index = index[index.length - 1];
420
+ }
421
+ return index > 0;
422
+ } else {
423
+ return this.locateRow(row, rowIndex, (dataList, index) => {
424
+ return index < dataList.length - 1;
425
+ });
426
+ }
335
427
  }
428
+ return false;
429
+ },
430
+ sortUp(row, rowIndex) {
431
+ this.locateRow(row, rowIndex, (dataList, index) => {
432
+ if (index > 0) {
433
+ let deleted = dataList.splice(index, 1);
434
+ dataList.splice(index - 1, 0, deleted[0]);
435
+ this.$emit('sorted', row, rowIndex, true);
436
+ }
437
+ });
438
+ },
439
+ sortDown(row, rowIndex) {
440
+ this.locateRow(row, rowIndex, (dataList, index) => {
441
+ if (index < dataList.length - 1) {
442
+ let deleted = dataList.splice(index, 1);
443
+ dataList.splice(index + 1, 0, deleted[0]);
444
+ this.$emit('sorted', row, rowIndex, false);
445
+ }
446
+ });
447
+ },
448
+ toggleRowExpansion(row, expanded) {
449
+ this.$refs.table.toggleRowExpansion(row, expanded);
336
450
  },
337
451
  }
338
452
  }
@@ -351,6 +465,16 @@ export default {
351
465
  height: 32px;
352
466
  }
353
467
 
468
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input-group__append,
469
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input-group__prepend {
470
+ border-radius: 0;
471
+ padding: 0 16px;
472
+ }
473
+
474
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-button {
475
+ border-radius: 0;
476
+ }
477
+
354
478
  .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input--small .el-input__wrapper,
355
479
  .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input--small .el-select__wrapper {
356
480
  height: 24px;
@@ -377,6 +501,45 @@ export default {
377
501
  text-align: left;
378
502
  }
379
503
 
504
+ .tnxel-edit-table-container .el-table .cell {
505
+ display: flex;
506
+ align-items: center;
507
+ }
508
+
509
+ .tnxel-edit-table-container .el-table .is-center .cell {
510
+ justify-content: center;
511
+ }
512
+
513
+ .tnxel-edit-table-container .el-table .el-table__placeholder {
514
+ width: 20px;
515
+ }
516
+
517
+ .tnxel-edit-table-container .el-table .el-table__indent + .el-table__placeholder {
518
+ width: 16px;
519
+ }
520
+
521
+ .tnxel-edit-table-container .el-table .is-center .el-table__placeholder {
522
+ width: 16px;
523
+ }
524
+
525
+ .tnxel-edit-table-container .el-table .is-center .el-table__indent + .el-table__placeholder {
526
+ width: 24px;
527
+ }
528
+
529
+ .tnxel-edit-table-container .el-table [class*=el-table__row--level] .el-table__expand-icon {
530
+ margin-left: 4px;
531
+ margin-right: 4px;
532
+ }
533
+
534
+ .tnxel-edit-table-container .el-table [class*=el-table__row--level] .is-center .el-table__expand-icon {
535
+ margin-left: 0;
536
+ }
537
+
538
+ .tnxel-edit-table-container .tnxel-edit-table .el-table__expand-icon + div {
539
+ width: auto;
540
+ flex-grow: 1;
541
+ }
542
+
380
543
  .tnxel-edit-table-container .el-table .is-error {
381
544
  box-shadow: 0 0 0 1px var(--el-color-danger) inset;
382
545
  }
@@ -38,9 +38,11 @@
38
38
  <Management v-else-if="value === 'Management'"/>
39
39
  <Memo v-else-if="value === 'Memo'"/>
40
40
  <Minus v-else-if="value === 'Minus'"/>
41
+ <More v-else-if="value === 'More'"/>
41
42
  <MoreFilled v-else-if="value === 'MoreFilled'"/>
42
43
  <Notebook v-else-if="value === 'Notebook'"/>
43
44
  <OfficeBuilding v-else-if="value === 'OfficeBuilding'"/>
45
+ <Operation v-else-if="value === 'Operation'"/>
44
46
  <Picture v-else-if="value === 'Picture'"/>
45
47
  <PictureFilled v-else-if="value === 'PictureFilled'"/>
46
48
  <Plus v-else-if="value === 'Plus'"/>
@@ -105,9 +107,11 @@ import {
105
107
  Management,
106
108
  Memo,
107
109
  Minus,
110
+ More,
108
111
  MoreFilled,
109
112
  Notebook,
110
113
  OfficeBuilding,
114
+ Operation,
111
115
  Picture,
112
116
  PictureFilled,
113
117
  Plus,
@@ -169,10 +173,12 @@ const components = {
169
173
  Management,
170
174
  Memo,
171
175
  Minus,
176
+ More,
172
177
  MoreFilled,
173
178
  Notebook,
174
179
  Loading,
175
180
  OfficeBuilding,
181
+ Operation,
176
182
  Picture,
177
183
  PictureFilled,
178
184
  Plus,
@@ -124,7 +124,7 @@ export default {
124
124
  }
125
125
 
126
126
  .tnxel-input-number .el-input__inner {
127
- min-width: 50px;
127
+ min-width: 20px;
128
128
  }
129
129
 
130
130
  .tnxel-input-number .el-input-number.text-start .el-input__inner {
@@ -350,9 +350,14 @@ export default build('tnxel', () => {
350
350
  text: message,
351
351
  });
352
352
  this._closeMessage();
353
- window.tnx.loadingInstance = ElLoading.service(options);
354
- this._handleZIndex('.el-loading-mask');
355
- this.app.eventBus.emit('tnx.showLoading', options);
353
+ try {
354
+ window.tnx.loadingInstance = ElLoading.service(options);
355
+ this._handleZIndex('.el-loading-mask');
356
+ this.app.eventBus.emit('tnx.showLoading', options);
357
+ } catch (e) {
358
+ window.tnx.loadingInstance = null;
359
+ console.error(e);
360
+ }
356
361
  return window.tnx.loadingInstance;
357
362
  },
358
363
  closeLoading() {
package/src/tnxvue.js CHANGED
@@ -38,6 +38,53 @@ 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(() => {
49
+ try {
50
+ return JSON.stringify(target);
51
+ } catch (e) {
52
+ console.error(e);
53
+ }
54
+ return undefined;
55
+ }, (newValue, oldValue) => {
56
+ try {
57
+ if (newValue !== oldValue) {
58
+ const newObject = JSON.parse(newValue);
59
+ const oldObject = JSON.parse(oldValue);
60
+ this.deepCompare(newObject, oldObject, '', handler);
61
+ }
62
+ } catch (e) {
63
+ console.error(e);
64
+ }
65
+ }, {deep: true});
66
+ },
67
+ deepCompare(object1, object2, path = '', handler) {
68
+ if (object1) {
69
+ const keys = Object.keys(object1);
70
+ keys.forEach(key => {
71
+ const fullPath = path ? `${path}.${key}` : key;
72
+ if (object2) {
73
+ if (Array.isArray(object1[key]) && Array.isArray(object2[key])) {
74
+ object1[key].forEach((item, index) => {
75
+ this.deepCompare(item, object2[key][index], `${fullPath}[${index}]`, handler);
76
+ });
77
+ } else if (typeof object1[key] === 'object' && typeof object2[key] === 'object') {
78
+ this.deepCompare(object1[key], object2[key], fullPath, handler);
79
+ } else if (object1[key] !== object2[key]) {
80
+ handler(object1[key], object2[key], fullPath);
81
+ }
82
+ } else {
83
+ handler(object1[key], undefined, fullPath);
84
+ }
85
+ });
86
+ }
87
+ },
41
88
  createVueInstance(rootComponent, router, rootProps) {
42
89
  let vm = Vue.createApp(rootComponent, rootProps);
43
90
  vm.use(this);
@@ -49,7 +96,7 @@ export default build('tnxvue', () => {
49
96
  vm.config.globalProperties.$router = window.tnx.router.instance;
50
97
  }
51
98
  // vm.config.unwrapInjectedRef = true;
52
- window.tnx.app.eventBus = mitt();
99
+ window.tnx.app.eventBus = window.tnx.app.eventBus || mitt();
53
100
  window.tnx.app.eventBus.once = function (name, handler) {
54
101
  this.all.set(name, [handler]);
55
102
  }