@truenewx/tnxvue3 3.0.0-alpha.25 → 3.0.0-alpha.26

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.25",
3
+ "version": "3.0.0-alpha.26",
4
4
  "description": "互联网技术解决方案:Vue3扩展支持",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -0,0 +1,404 @@
1
+ <template>
2
+ <div class="tnxel-edit-table-container" :id="id">
3
+ <el-table ref="table"
4
+ class="tnxel-edit-table"
5
+ :class="{'padding-none': !padding}"
6
+ :data="data"
7
+ :highlight-current-row="selectable"
8
+ border
9
+ @current-change="onSelectRow"
10
+ >
11
+ <slot></slot>
12
+ <el-table-column label="排序" align="center" width="60px" v-if="sortable">
13
+ <template #default="scope">
14
+ <tnxel-button
15
+ type="text"
16
+ icon="Top"
17
+ :class="{'text-transparent': scope.$index === 0}"
18
+ @click="sortUp(scope.$index)"
19
+ />
20
+ <tnxel-button
21
+ type="text"
22
+ icon="Bottom"
23
+ class="ms-0"
24
+ :class="{'text-transparent': scope.$index === data.length - 1}"
25
+ @click="sortDown(scope.$index)"
26
+ />
27
+ </template>
28
+ </el-table-column>
29
+ <el-table-column label="移除" align="center" width="60px" v-if="removable">
30
+ <template #default="scope">
31
+ <tnxel-button
32
+ type="primary" text
33
+ icon="CircleClose"
34
+ :tooltip="scope.row.removeTip"
35
+ @click="toRemoveRow(scope.$index)"
36
+ />
37
+ </template>
38
+ </el-table-column>
39
+ </el-table>
40
+ <el-table class="tnxel-edit-table-footer" :data="[{}]" border :show-header="false" v-if="addable">
41
+ <el-table-column align="center">
42
+ <template #default>
43
+ <tnxel-button type="text" icon="Plus" @click="toAddRow">{{ addText }}</tnxel-button>
44
+ </template>
45
+ </el-table-column>
46
+ </el-table>
47
+ </div>
48
+ </template>
49
+
50
+ <script>
51
+ import Button from '../button/Button.vue';
52
+
53
+ export default {
54
+ components: {
55
+ 'tnxel-button': Button,
56
+ },
57
+ name: 'TnxelEditTable',
58
+ props: {
59
+ data: Array,
60
+ addable: {
61
+ type: Boolean,
62
+ default: true,
63
+ },
64
+ removable: {
65
+ type: Boolean,
66
+ default: true,
67
+ },
68
+ addText: {
69
+ type: String,
70
+ default: '添加行',
71
+ },
72
+ newRow: {
73
+ type: Function,
74
+ default() {
75
+ return () => {
76
+ return {};
77
+ }
78
+ }
79
+ },
80
+ selectable: Boolean, // 可否选择行
81
+ padding: Boolean, // 单元格中是否有边距
82
+ sortable: Boolean, // 可否调整行的顺序
83
+ rules: [String, Object], // 加载字段校验规则的URL地址,或规则集对象
84
+ rulesApp: { // 加载字段校验规则的应用名称
85
+ type: String,
86
+ default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载,确保更改的值生效
87
+ },
88
+ rulesLoaded: Function, // 规则集加载后的附加处理函数,仅在rule为字符串类型的URL地址时有效
89
+ },
90
+ data() {
91
+ return {
92
+ id: window.tnx.util.string.uuid32(),
93
+ selectedRow: null,
94
+ validationRules: {},
95
+ }
96
+ },
97
+ watch: {
98
+ data(newData, oldData) {
99
+ if (!oldData) {
100
+ this.$nextTick(() => {
101
+ this.initElements();
102
+ });
103
+ }
104
+ },
105
+ rules() {
106
+ this.initRules();
107
+ },
108
+ },
109
+ mounted() {
110
+ if (this.selectable) {
111
+ window.tnx.util.dom.replaceKeyEvent(document, () => {
112
+ if (this.selectedRow) {
113
+ this.data.remove(record => record === this.selectedRow);
114
+ }
115
+ }, {key: 'Delete'});
116
+ }
117
+ this.$nextTick(() => {
118
+ setTimeout(() => {
119
+ this.initElements();
120
+ this.initRules();
121
+ });
122
+ });
123
+ },
124
+ methods: {
125
+ initElements() {
126
+ this.focusRowFirstInput(0);
127
+ // 为富文本输入框添加默认title
128
+ let table = this.$refs.table.$el;
129
+ let textareas = table.getElementsByTagName('textarea');
130
+ for (let textarea of textareas) {
131
+ if (!textarea.title) {
132
+ textarea.addEventListener('input', event => {
133
+ event.target.title = event.target.value;
134
+ });
135
+ }
136
+ }
137
+ },
138
+ initRules() {
139
+ if (this.rules) {
140
+ if (typeof this.rules === 'string') {
141
+ window.tnx.app.rpc.getMeta(this.rules, meta => {
142
+ if (this.rulesLoaded) {
143
+ this.rulesLoaded(meta.$rules);
144
+ } else {
145
+ this.$emit('rules-loaded', meta.$rules);
146
+ }
147
+ this.validationRules = meta.$rules;
148
+ this.bindRules();
149
+ }, this.rulesApp);
150
+ } else {
151
+ this.validationRules = this.rules;
152
+ this.bindRules();
153
+ }
154
+ } else {
155
+ this.validationRules = {};
156
+ this.bindRules();
157
+ }
158
+ },
159
+ loopColumns(consumer) {
160
+ let columns = this.$slots.default();
161
+ if (Array.isArray(columns)) {
162
+ let containers = document.getElementsByClassName('tnxel-edit-table-container');
163
+ for (let container of containers) {
164
+ if (container.id === this.id) {
165
+ let cols = container.querySelectorAll('.el-table__header colgroup col');
166
+ if (cols.length >= columns.length) {
167
+ for (let i = 0; i < columns.length; i++) {
168
+ let column = columns[i];
169
+ let props = column.props;
170
+ let fieldName = props.prop;
171
+ let columnName = cols[i].getAttribute('name');
172
+ consumer(fieldName, columnName);
173
+ }
174
+ return true;
175
+ }
176
+ break;
177
+ }
178
+ }
179
+ }
180
+ return false;
181
+ },
182
+ loopValidatableElements(consumer) {
183
+ this.loopColumns((fieldName, columnName) => {
184
+ if (this.validationRules[fieldName]) {
185
+ let tds = this.$refs.table.$el.getElementsByClassName(columnName);
186
+ for (let td of tds) {
187
+ let elements = td.getElementsByTagName('input');
188
+ if (elements.length === 0) {
189
+ elements = td.getElementsByTagName('textarea');
190
+ }
191
+ for (let element of elements) {
192
+ consumer(fieldName, element);
193
+ }
194
+ }
195
+ }
196
+ });
197
+ },
198
+ bindRules() {
199
+ this.loopValidatableElements((fieldName, element) => {
200
+ element.addEventListener('blur', event => {
201
+ let element = event.target;
202
+ this.validateElement(fieldName, element);
203
+ });
204
+ element.addEventListener('focus', event => {
205
+ this.removeErrorIcon(event.target);
206
+ });
207
+ });
208
+ this.loopColumns((fieldName, columnName) => {
209
+ let rules = this.validationRules[fieldName];
210
+ if (rules) {
211
+ for (let rule of rules) {
212
+ if (rule.required) {
213
+ let th = this.$refs.table.$el.querySelector('th.' + columnName);
214
+ if (th) {
215
+ th.classList.add('is-required');
216
+ }
217
+ break;
218
+ }
219
+ }
220
+ }
221
+ });
222
+ },
223
+ validateElement(fieldName, element) {
224
+ this.removeErrorIcon(element);
225
+ element.parentElement.classList.remove('is-error');
226
+
227
+ let rules = {};
228
+ rules[fieldName] = this.validationRules[fieldName];
229
+ let model = {};
230
+ model[fieldName] = element.value;
231
+ let successful = true;
232
+ window.tnx.app.validator.validate(rules, model, errors => {
233
+ if (errors) {
234
+ successful = false;
235
+ let message = '';
236
+ for (let error of errors) {
237
+ message += error.message + '\n';
238
+ }
239
+ message = message.trim();
240
+ element.parentElement.classList.add('is-error');
241
+ let icon = document.createElement('i');
242
+ icon.classList.add('error-icon');
243
+ icon.classList.add('el-icon'); // TODO
244
+ icon.classList.add('text-danger');
245
+ icon.title = message;
246
+ icon.style.top = (element.parentElement.offsetHeight - 16) / 2 + 'px';
247
+ element.parentElement.appendChild(icon);
248
+ }
249
+ });
250
+ return successful;
251
+ },
252
+ validateTable() {
253
+ let successful = true;
254
+ this.loopValidatableElements((fieldName, element) => {
255
+ let validated = this.validateElement(fieldName, element);
256
+ successful = validated && successful;
257
+ });
258
+ return successful;
259
+ },
260
+ removeErrorIcon(inputElement) {
261
+ let sibling = inputElement.nextSibling;
262
+ while (sibling) {
263
+ if (sibling.classList && sibling.classList.contains('error-icon')) {
264
+ sibling.remove();
265
+ }
266
+ sibling = sibling.nextSibling;
267
+ }
268
+ },
269
+ onSelectRow(row) {
270
+ if (this.selectable) {
271
+ this.selectedRow = row;
272
+ }
273
+ },
274
+ focusRowFirstInput(rowIndex) {
275
+ let table = this.$refs.table.$el;
276
+ let rows = table.getElementsByClassName('el-table__row');
277
+ if (rows.length) {
278
+ let row = rows[rowIndex];
279
+ if (row) {
280
+ let cells = row.getElementsByClassName('cell');
281
+ for (let cell of cells) {
282
+ let input = cell.querySelector('input:first-child');
283
+ if (input) {
284
+ input.focus();
285
+ return true;
286
+ } else {
287
+ let textarea = cell.querySelector('textarea:first-child');
288
+ if (textarea) {
289
+ textarea.focus();
290
+ return true;
291
+ }
292
+ }
293
+ }
294
+ }
295
+ }
296
+ return false;
297
+ },
298
+ toAddRow() {
299
+ let row = this.newRow();
300
+ this.data.push(row);
301
+ this.$nextTick(() => {
302
+ this.focusRowFirstInput(this.data.length - 1);
303
+ });
304
+ },
305
+ toRemoveRow(index) {
306
+ let row = this.data[index];
307
+ this.data.splice(index, 1);
308
+ this.$emit('removed', row, index);
309
+ this.$nextTick(() => {
310
+ if (!this.focusRowFirstInput(index)) {
311
+ this.focusRowFirstInput(index - 1);
312
+ }
313
+ });
314
+ },
315
+ sortDown(index) {
316
+ if (0 <= index && index < this.data.length - 1) {
317
+ let deleted = this.data.splice(index, 1);
318
+ this.data.splice(index + 1, 0, deleted[0]);
319
+ }
320
+ },
321
+ sortUp(index) {
322
+ if (0 < index && index < this.data.length) {
323
+ let deleted = this.data.splice(index, 1);
324
+ this.data.splice(index - 1, 0, deleted[0]);
325
+ }
326
+ },
327
+ }
328
+ }
329
+ </script>
330
+
331
+ <style>
332
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-table__cell,
333
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper td .cell,
334
+ .tnxel-edit-table-container .el-table--border.padding-none .el-table__body-wrapper td.el-table__cell:first-child .cell {
335
+ padding: 0;
336
+ }
337
+
338
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input__wrapper {
339
+ border-radius: 0;
340
+ height: 32px;
341
+ }
342
+
343
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-textarea__wrapper {
344
+ border-radius: 0;
345
+ height: 40px;
346
+ }
347
+
348
+ .tnxel-edit-table-container .tnxel-edit-table .is-center .el-input__inner {
349
+ text-align: center;
350
+ }
351
+
352
+ .tnxel-edit-table-container .tnxel-edit-table .is-right .el-input__inner {
353
+ text-align: right;
354
+ }
355
+
356
+ .tnxel-edit-table-container .tnxel-edit-table .el-cascader .el-input__inner {
357
+ text-align: left;
358
+ }
359
+
360
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input__inner:focus,
361
+ .tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-textarea__inner:focus {
362
+ border-color: var(--el-color-primary);
363
+ }
364
+
365
+ .tnxel-edit-table-container .el-table .is-error .el-input__inner,
366
+ .tnxel-edit-table-container .el-table .is-error .el-textarea__inner {
367
+ border-color: var(--el-color-danger);
368
+ }
369
+
370
+ .tnxel-edit-table-container .el-table th.is-required:not(.is-no-asterisk) .cell:after {
371
+ content: "*";
372
+ color: var(--el-color-danger);
373
+ margin-left: 4px;
374
+ }
375
+
376
+ .tnxel-edit-table-container .el-table .is-error .error-icon {
377
+ position: absolute;
378
+ top: 12px;
379
+ right: 10px;
380
+ font-size: 16px;
381
+ }
382
+
383
+ .tnxel-edit-table-container .el-table .el-input__inner:focus + .error-icon,
384
+ .tnxel-edit-table-container .el-table .el-textarea__inner:focus + .error-icon {
385
+ display: none;
386
+ }
387
+
388
+ .tnxel-edit-table-container .el-table .el-button.text-transparent {
389
+ cursor: default;
390
+ }
391
+
392
+ .tnxel-edit-table-container .el-table .disabled {
393
+ cursor: not-allowed;
394
+ }
395
+
396
+ .tnxel-edit-table-container .tnxel-edit-table-footer {
397
+ border-top: none;
398
+ }
399
+
400
+ .tnxel-edit-table-container .tnxel-edit-table-footer .el-table__cell {
401
+ padding-top: 4px;
402
+ padding-bottom: 3px;
403
+ }
404
+ </style>
@@ -1,41 +1,41 @@
1
- <template>
2
- <span v-if="item">{{ item.caption }}</span>
3
- </template>
4
-
5
- <script>
6
- export default {
7
- name: 'TnxelEnumView',
8
- props: {
9
- type: {
10
- type: String,
11
- required: true,
12
- },
13
- subtype: String,
14
- itemKey: {
15
- type: String,
16
- required: true,
17
- },
18
- app: {
19
- type: String,
20
- default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载,确保更改的值生效
21
- },
22
- },
23
- data() {
24
- return {
25
- item: null,
26
- };
27
- },
28
- mounted() {
29
- window.tnx.app.rpc.loadEnumType(this.type, this.subtype, enumType => {
30
- this.item = enumType.items.find(item => item.key === this.itemKey);
31
- }, {
32
- app: this.app,
33
- });
34
- },
35
- methods: {}
36
- }
37
- </script>
38
-
39
- <style scoped>
40
-
41
- </style>
1
+ <template>
2
+ <span v-if="item">{{ item.caption }}</span>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ name: 'TnxelEnumView',
8
+ props: {
9
+ type: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ subtype: String,
14
+ itemKey: {
15
+ type: String,
16
+ required: true,
17
+ },
18
+ app: {
19
+ type: String,
20
+ default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载,确保更改的值生效
21
+ },
22
+ },
23
+ data() {
24
+ return {
25
+ item: null,
26
+ };
27
+ },
28
+ mounted() {
29
+ window.tnx.app.rpc.loadEnumType(this.type, this.subtype, enumType => {
30
+ this.item = enumType.items.find(item => item.key === this.itemKey);
31
+ }, {
32
+ app: this.app,
33
+ });
34
+ },
35
+ methods: {}
36
+ }
37
+ </script>
38
+
39
+ <style scoped>
40
+
41
+ </style>
@@ -24,8 +24,10 @@
24
24
  <DocumentCopy v-else-if="value === 'DocumentCopy'"/>
25
25
  <Download v-else-if="value === 'Download'"/>
26
26
  <Edit v-else-if="value === 'Edit'"/>
27
+ <Files v-else-if="value === 'Files'"/>
27
28
  <Finished v-else-if="value === 'Finished'"/>
28
29
  <Folder v-else-if="value === 'Folder'"/>
30
+ <Histogram v-else-if="value === 'Histogram'"/>
29
31
  <HomeFilled v-else-if="value === 'HomeFilled'"/>
30
32
  <InfoFilled v-else-if="value === 'InfoFilled'"/>
31
33
  <List v-else-if="value === 'List'"/>
@@ -83,8 +85,10 @@ import {
83
85
  DocumentCopy,
84
86
  Download,
85
87
  Edit,
88
+ Files,
86
89
  Finished,
87
90
  Folder,
91
+ Histogram,
88
92
  HomeFilled,
89
93
  InfoFilled,
90
94
  List,
@@ -141,8 +145,10 @@ const components = {
141
145
  DocumentCopy,
142
146
  Download,
143
147
  Edit,
148
+ Files,
144
149
  Finished,
145
150
  Folder,
151
+ Histogram,
146
152
  HomeFilled,
147
153
  InfoFilled,
148
154
  List,
@@ -20,6 +20,7 @@ import DetailForm from './detail-form/DetailForm.vue';
20
20
  import Dialog from './dialog/Dialog.vue';
21
21
  import Drawer from './drawer/Drawer.vue';
22
22
  import DropdownItem from './dropdown-item/DropdownItem.vue';
23
+ import EditTable from './edit-table/EditTable.vue';
23
24
  import EnumSelect from './enum-select/EnumSelect.vue';
24
25
  import EnumView from './enum-view/EnumView.vue';
25
26
  import FetchCascader from './fetch-cascader/FetchCascader.vue';
@@ -64,6 +65,7 @@ export default build('tnxel', () => {
64
65
  Dialog,
65
66
  Drawer,
66
67
  DropdownItem,
68
+ EditTable,
67
69
  EnumSelect,
68
70
  EnumView,
69
71
  FetchCascader,
@@ -519,7 +519,8 @@ export default {
519
519
  }
520
520
  },
521
521
  _onError(error, file, fileList) {
522
- $('#' + this.id + ' .el-upload').show();
522
+ this.files.remove(f => f.uid === file.uid);
523
+ $('#' + this.id + ' .el-upload').removeClass('d-none');
523
524
  let message;
524
525
  try {
525
526
  message = JSON.parse(error.message);
@@ -662,7 +663,7 @@ export default {
662
663
  display: flex;
663
664
  align-items: center;
664
665
  min-height: 32px;
665
- color: var(--el-text-color-regular);
666
+ color: var(--el-text-color-placeholder);
666
667
  }
667
668
 
668
669
  .tnxel-upload-container div.upload-trigger {