@truenewx/tnxvue3 3.4.3 → 3.4.5

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.
Files changed (47) hide show
  1. package/package.json +12 -6
  2. package/src/bootstrap-vue/dialog/Dialog.vue +22 -10
  3. package/src/element-plus/aj-captcha/api/index.js +2 -2
  4. package/src/element-plus/avatar/Avatar.vue +4 -27
  5. package/src/element-plus/date-picker/DatePicker.vue +8 -9
  6. package/src/element-plus/dialog/Dialog.vue +34 -22
  7. package/src/element-plus/drawer/Drawer.vue +22 -5
  8. package/src/element-plus/edit-table/EditTable.vue +10 -10
  9. package/src/element-plus/enum-select/EnumSelect.vue +30 -30
  10. package/src/element-plus/enum-view/EnumView.vue +1 -3
  11. package/src/element-plus/fetch-cascader/FetchCascader.vue +4 -4
  12. package/src/element-plus/fetch-select/FetchSelect.vue +3 -3
  13. package/src/element-plus/fetch-tags/FetchTags.vue +1 -1
  14. package/src/element-plus/fss-upload/FssUpload.vue +76 -115
  15. package/src/element-plus/fss-view/FssView.vue +28 -30
  16. package/src/element-plus/icon/Icon.vue +3 -0
  17. package/src/element-plus/query-form/QueryForm.vue +3 -3
  18. package/src/element-plus/query-table/QueryTable.vue +12 -12
  19. package/src/element-plus/region-cascader/RegionCascader.vue +3 -3
  20. package/src/element-plus/select/Select.vue +56 -56
  21. package/src/element-plus/submit-form/SubmitForm.vue +5 -5
  22. package/src/element-plus/tnxel-validator.ts +347 -0
  23. package/src/element-plus/tnxel.css +0 -8
  24. package/src/element-plus/tnxel.ts +561 -0
  25. package/src/element-plus/transfer/Transfer.vue +2 -2
  26. package/src/element-plus/upload/Upload.vue +68 -70
  27. package/src/tdesign/desktop/tnxtdd.ts +5 -5
  28. package/src/tdesign/mobile/calendar/Calendar.vue +121 -0
  29. package/src/tdesign/mobile/date-time-picker/DateTimePicker.vue +147 -0
  30. package/src/tdesign/mobile/dialog/Dialog.vue +179 -0
  31. package/src/tdesign/mobile/dialog/DialogContent.vue +13 -0
  32. package/src/tdesign/mobile/drawer/Drawer.vue +176 -0
  33. package/src/tdesign/mobile/drawer/DrawerContent.vue +13 -0
  34. package/src/tdesign/mobile/enum-select/EnumSelect.vue +160 -0
  35. package/src/tdesign/mobile/popup/Popup.vue +106 -0
  36. package/src/tdesign/mobile/region-picker/RegionPicker.vue +223 -0
  37. package/src/tdesign/mobile/select/Select.vue +478 -0
  38. package/src/tdesign/mobile/slide-radio-group/SlideRadioGroup.vue +392 -0
  39. package/src/tdesign/mobile/tnxtdm.css +132 -0
  40. package/src/tdesign/mobile/tnxtdm.ts +310 -7
  41. package/src/tdesign/{foundation/validator.ts → tnxtd-validator.ts} +18 -17
  42. package/src/tdesign/tnxtd.css +66 -0
  43. package/src/tdesign/tnxtd.ts +10 -7
  44. package/src/tnxvue-router.ts +65 -18
  45. package/src/tnxvue.ts +71 -31
  46. package/tsconfig.json +33 -19
  47. package/src/element-plus/tnxel.js +0 -598
@@ -0,0 +1,478 @@
1
+ <template>
2
+ <t-checkbox-group class="tnxtdm-select tnxtdm-checkbox-group" v-model="model"
3
+ :disabled="isDisabled" :key="`checkbox-${groupKey}`" v-if="selector === 'checkbox'">
4
+ <t-checkbox v-for="item in items" :key="item[valueName]" :value="item[valueName]"
5
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
6
+ :label="item[textName]">
7
+ </t-checkbox>
8
+ </t-checkbox-group>
9
+
10
+ <div class="tnxtdm-select tnxtdm-tag-group" :key="`tag-${groupKey}`"
11
+ v-else-if="selector === 'tag' || selector === 'tags'">
12
+ <template v-if="items">
13
+ <t-tag v-for="item in items" :key="item[valueName]"
14
+ :theme="isSelected(item[valueName]) ? 'primary' : 'default'"
15
+ :variant="isSelected(item[valueName]) ? 'dark' : 'light'"
16
+ :disabled="isDisabled || item.disabled === true || typeof item.disabled === 'string'"
17
+ @click="!isDisabled && select(item[valueName])">
18
+ <t-icon :name="item[iconName]" v-if="item[iconName]"/>
19
+ <span>{{ item[textName] }}</span>
20
+ </t-tag>
21
+ </template>
22
+ <t-loading v-else/>
23
+ </div>
24
+
25
+ <div class="tnxtdm-select tnxtdm-text-button-group" :key="`text-${groupKey}`"
26
+ v-else-if="selector === 'text' || selector === 'texts'">
27
+ <template v-if="items">
28
+ <t-button v-for="item in items" :key="item[valueName]"
29
+ :variant="isSelected(item[valueName]) ? 'base' : 'text'"
30
+ :theme="isSelected(item[valueName]) ? 'primary' : 'default'"
31
+ size="small"
32
+ :disabled="isDisabled || item.disabled === true || typeof item.disabled === 'string'"
33
+ @click="!isDisabled && select(item[valueName], $event)">
34
+ <t-icon :name="item[iconName]" v-if="item[iconName]"/>
35
+ <span>{{ item[textName] }}</span>
36
+ </t-button>
37
+ </template>
38
+ <t-loading v-else/>
39
+ </div>
40
+
41
+ <t-radio-group class="tnxtdm-select tnxtdm-radio-group" v-model="model"
42
+ :disabled="isDisabled" :key="`radio-${groupKey}`" borderless
43
+ v-else-if="selector === 'radio-group'">
44
+ <t-radio v-for="item in items" :key="item[valueName]" :value="item[valueName]"
45
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
46
+ :label="item[textName]">
47
+ </t-radio>
48
+ </t-radio-group>
49
+
50
+ <tnxtdm-slide-radio-group
51
+ :key="`radio-group-${groupKey}`"
52
+ v-model="model"
53
+ :items="items"
54
+ :value-name="valueName"
55
+ :text-name="textName"
56
+ :icon-name="iconName"
57
+ :default-value="defaultValue"
58
+ :width="width"
59
+ :size="size"
60
+ :theme="theme"
61
+ :disabled="isDisabled"
62
+ @change="onSlideRadioChange"
63
+ v-else-if="selector === 'radio-button'"
64
+ />
65
+
66
+ <div class="tnxtdm-select tnxtdm-select-picker" :key="`select-${groupKey}`" v-else>
67
+ <t-cell :note="currentText || placeholder" arrow @click="openPicker"/>
68
+ <t-popup v-model="pickerVisible" placement="bottom">
69
+ <t-picker
70
+ v-if="!isMulti()"
71
+ v-model="pickerValue"
72
+ :columns="pickerColumns"
73
+ :keys="{ label: textName, value: valueName }"
74
+ @confirm="onPickerConfirm"
75
+ @cancel="pickerVisible = false"
76
+ />
77
+ <!-- 多选情况下的简单实现,使用 CheckboxGroup -->
78
+ <div v-else class="tnxtdm-select-multi-popup">
79
+ <div class="tnxtdm-select-multi-header">
80
+ <t-button variant="text" @click="pickerVisible = false">取消</t-button>
81
+ <div class="tnxtdm-select-multi-title">请选择</div>
82
+ <t-button variant="text" theme="primary" @click="onMultiConfirm">确定</t-button>
83
+ </div>
84
+ <div class="tnxtdm-select-multi-body">
85
+ <t-checkbox-group v-model="tempModel">
86
+ <t-cell v-for="item in items" :key="item[valueName]" :title="item[textName]">
87
+ <template #right-icon>
88
+ <t-checkbox :value="item[valueName]"/>
89
+ </template>
90
+ </t-cell>
91
+ </t-checkbox-group>
92
+ </div>
93
+ </div>
94
+ </t-popup>
95
+ </div>
96
+ </template>
97
+
98
+ <script>
99
+ import {util} from '../../../tnxvue.ts';
100
+ import TnxtdmSlideRadioGroup from '../slide-radio-group/SlideRadioGroup.vue';
101
+
102
+ export const isMultiSelector = function (selector) {
103
+ return selector === 'checkbox' || selector === 'tags' || selector === 'multi-select' || selector === 'texts';
104
+ }
105
+
106
+ export default {
107
+ name: 'TnxtdmSelect',
108
+ components: {
109
+ TnxtdmSlideRadioGroup,
110
+ },
111
+ props: {
112
+ id: [Number, String],
113
+ modelValue: {
114
+ type: [String, Number, Boolean, Array],
115
+ default: null,
116
+ },
117
+ selector: String,
118
+ items: Array,
119
+ valueName: {
120
+ type: String,
121
+ default: 'value',
122
+ },
123
+ textName: {
124
+ type: String,
125
+ default: 'text',
126
+ },
127
+ indexName: {
128
+ type: String,
129
+ default: 'index',
130
+ },
131
+ iconName: {
132
+ type: String,
133
+ default: 'icon',
134
+ },
135
+ defaultValue: {
136
+ type: [String, Number, Boolean, Array],
137
+ default: null,
138
+ },
139
+ empty: {
140
+ type: [Boolean, String],
141
+ default: false,
142
+ },
143
+ emptyValue: {
144
+ type: [String, Number, Boolean, Array],
145
+ default: '',
146
+ },
147
+ emptyClass: String,
148
+ placeholder: String,
149
+ width: String,
150
+ disabled: Boolean,
151
+ tagClick: Function,
152
+ change: Function,
153
+ filterable: Boolean,
154
+ theme: String,
155
+ size: String,
156
+ border: Boolean,
157
+ },
158
+ emits: ['update:modelValue', 'change'],
159
+ data() {
160
+ let model = this.getModel(this.items);
161
+ if (this.items && model !== this.modelValue) {
162
+ this.$emit('update:modelValue', model);
163
+ }
164
+ return {
165
+ groupKey: new Date().getTime(),
166
+ model: model,
167
+ hiddenValues: [],
168
+ pickerVisible: false,
169
+ pickerValue: [], // TDesign Picker v-model is array
170
+ tempModel: [], // For multi-select popup
171
+ formDisabled: false,
172
+ };
173
+ },
174
+ mounted() {
175
+ this.initFormDisabled();
176
+ },
177
+ computed: {
178
+ isDisabled() {
179
+ return this.disabled || this.formDisabled;
180
+ },
181
+ emptyText() {
182
+ return typeof this.empty === 'string' ? this.empty : '';
183
+ },
184
+ currentText() {
185
+ let item = this.getItem(this.model);
186
+ if (item) {
187
+ return item[this.textName];
188
+ }
189
+ if (this.isMulti() && Array.isArray(this.model) && this.model.length > 0) {
190
+ // 多选时显示选中的数量或第一个选项的文本等,这里简化处理,显示第一个选中的文本 + 等
191
+ let firstItem = this.getItem(this.model[0]);
192
+ if (firstItem) {
193
+ return firstItem[this.textName] + (this.model.length > 1 ? ` 等${this.model.length}项` : '');
194
+ }
195
+ }
196
+ return this.emptyText;
197
+ },
198
+ modelEqualsModelValue() {
199
+ if (Array.isArray(this.model) && Array.isArray(this.modelValue)) {
200
+ // 简单的数组比较
201
+ if (this.model.length !== this.modelValue.length) {
202
+ return false;
203
+ }
204
+ for (let i = 0; i < this.model.length; i++) {
205
+ if (this.model[i] !== this.modelValue[i]) {
206
+ return false;
207
+ }
208
+ }
209
+ return true;
210
+ } else {
211
+ return this.model === this.modelValue;
212
+ }
213
+ },
214
+ firstEnabledItem() {
215
+ if (!this.items) {
216
+ return undefined;
217
+ }
218
+ for (let item of this.items) {
219
+ if (!item.disabled) {
220
+ return item;
221
+ }
222
+ }
223
+ return undefined;
224
+ },
225
+ pickerColumns() {
226
+ return [this.items || []];
227
+ }
228
+ },
229
+ watch: {
230
+ model(newValue, oldValue) {
231
+ if (!this.modelEqualsModelValue) {
232
+ this.$emit('update:modelValue', newValue);
233
+ if (util.object.isNotEmpty(newValue) || util.object.isNotEmpty(oldValue)) {
234
+ this.$nextTick(() => {
235
+ this.triggerChange(newValue);
236
+ });
237
+ }
238
+ }
239
+ },
240
+ modelValue() {
241
+ this.model = this.getModel(this.items);
242
+ },
243
+ items(items) {
244
+ this.model = this.getModel(items);
245
+ },
246
+ },
247
+ methods: {
248
+ initFormDisabled() {
249
+ let parent = this.$parent;
250
+ while (parent) {
251
+ if (parent.$options && (parent.$options.name === 't-form' || parent.$options.name === 'TForm')) {
252
+ this.$watch(() => parent.disabled || (parent.$props && parent.$props.disabled), (val) => {
253
+ this.formDisabled = val;
254
+ }, {immediate: true});
255
+ break;
256
+ }
257
+ parent = parent.$parent;
258
+ }
259
+ },
260
+ isMulti() {
261
+ return isMultiSelector(this.selector);
262
+ },
263
+ triggerChange(value) {
264
+ if (this.change) {
265
+ let item = undefined;
266
+ if (this.isMulti()) {
267
+ item = [];
268
+ if (Array.isArray(value)) {
269
+ for (let v of value) {
270
+ item.push(this.getItem(v));
271
+ }
272
+ }
273
+ } else {
274
+ item = this.getItem(value);
275
+ }
276
+ this.change(item, this.id);
277
+ } else {
278
+ this.$emit('change', value);
279
+ }
280
+ },
281
+ getItem(value) {
282
+ if (this.empty && value === this.emptyValue) {
283
+ let item = {};
284
+ item[this.valueName] = this.emptyValue;
285
+ item[this.textName] = this.emptyText;
286
+ return item;
287
+ }
288
+ if (value !== undefined && value !== null && this.items) {
289
+ for (let item of this.items) {
290
+ if ((item[this.valueName] + '') === (value + '')) {
291
+ return item;
292
+ }
293
+ }
294
+ }
295
+ return undefined;
296
+ },
297
+ getModel(items) {
298
+ let model = this.modelValue;
299
+ if (util.object.isNull(model)) {
300
+ model = this.defaultValue;
301
+ }
302
+ if (this.isMulti()) {
303
+ if (util.object.isNull(model)) {
304
+ return [];
305
+ }
306
+ if (!Array.isArray(model)) {
307
+ model = [model];
308
+ }
309
+ if (items && items.length) {
310
+ for (let i = model.length - 1; i >= 0; i--) {
311
+ let item = this.getItem(model[i]);
312
+ if (!item) {
313
+ model.splice(i, 1);
314
+ }
315
+ }
316
+ } else {
317
+ model = [];
318
+ }
319
+ return model;
320
+ }
321
+ if (util.object.isNull(model)) {
322
+ return null;
323
+ }
324
+ if (items && items.length) {
325
+ let item = this.getItem(model);
326
+ if (item) {
327
+ return item[this.valueName];
328
+ } else {
329
+ if (!this.empty) {
330
+ let firstItem = this.firstEnabledItem;
331
+ if (firstItem && firstItem[this.valueName]) {
332
+ return firstItem[this.valueName];
333
+ }
334
+ }
335
+ }
336
+ }
337
+ return model;
338
+ },
339
+ isSelected(value) {
340
+ if (Array.isArray(this.model)) {
341
+ return this.model.indexOf(value) >= 0;
342
+ } else {
343
+ return this.model === value;
344
+ }
345
+ },
346
+ select(value, event) {
347
+ if (this.tagClick) {
348
+ let item = this.getItem(value);
349
+ if (item) {
350
+ if (this.tagClick(item) === false) {
351
+ return;
352
+ }
353
+ }
354
+ }
355
+ if (this.isMulti()) {
356
+ let index = this.model.indexOf(value);
357
+ if (index >= 0) {
358
+ let newModel = [...this.model];
359
+ newModel.splice(index, 1);
360
+ this.model = newModel;
361
+ } else {
362
+ this.model = [...this.model, value];
363
+ }
364
+ } else {
365
+ this.model = value;
366
+ }
367
+ // Mobile usually doesn't need blur
368
+ },
369
+ openPicker() {
370
+ if (this.isDisabled) {
371
+ return;
372
+ }
373
+ if (this.isMulti()) {
374
+ this.tempModel = [...(this.model || [])];
375
+ } else {
376
+ this.pickerValue = [this.model];
377
+ }
378
+ this.pickerVisible = true;
379
+ },
380
+ onPickerConfirm(val) {
381
+ // TDesign Picker returns array of values
382
+ if (val && val.length > 0) {
383
+ this.model = val[0];
384
+ } else {
385
+ this.model = null; // or empty value
386
+ }
387
+ this.pickerVisible = false;
388
+ },
389
+ onMultiConfirm() {
390
+ this.model = [...this.tempModel];
391
+ this.pickerVisible = false;
392
+ },
393
+ onSlideRadioChange(item) {
394
+ // SlideRadioGroup 直接返回了 item 对象,或者如果没找到则是值
395
+ // 我们需要保持与 triggerChange 逻辑一致,最终调用 props.change
396
+ if (this.change) {
397
+ this.change(item, this.id);
398
+ } else {
399
+ this.$emit('change', item ? item[this.valueName] : item);
400
+ }
401
+ },
402
+ getText(value) {
403
+ let item = this.getItem(value);
404
+ return item ? item[this.textName] : undefined;
405
+ },
406
+ disableItem(itemValue, disabled, reverseOther) {
407
+ let values = Array.isArray(itemValue) ? itemValue : [itemValue];
408
+ if (this.items) {
409
+ for (let item of this.items) {
410
+ // 注意类型比较,这里假设 value 类型一致
411
+ let currentVal = item[this.valueName];
412
+ if (values.some(v => v === currentVal)) {
413
+ item.disabled = disabled;
414
+ // 如果禁用的选项已被选择,则需从已选择值中移除该选项的值
415
+ if (this.isMulti()) {
416
+ if (Array.isArray(this.model)) {
417
+ const idx = this.model.indexOf(currentVal);
418
+ if (idx > -1) {
419
+ const newModel = [...this.model];
420
+ newModel.splice(idx, 1);
421
+ this.model = newModel;
422
+ }
423
+ }
424
+ } else {
425
+ if (this.model === currentVal) {
426
+ this.model = null;
427
+ }
428
+ }
429
+ } else if (reverseOther) {
430
+ item.disabled = !disabled;
431
+ }
432
+ }
433
+ // 强制更新视图
434
+ this.groupKey = new Date().getTime();
435
+ }
436
+ },
437
+ }
438
+ }
439
+ </script>
440
+
441
+ <style>
442
+ .tnxtdm-tag-group {
443
+ display: flex;
444
+ flex-wrap: wrap;
445
+ gap: 8px;
446
+ }
447
+
448
+ .tnxtdm-text-button-group {
449
+ display: flex;
450
+ flex-wrap: wrap;
451
+ gap: 8px;
452
+ }
453
+
454
+ .tnxtdm-select-multi-popup {
455
+ background-color: var(--td-bg-color-container);
456
+ display: flex;
457
+ flex-direction: column;
458
+ max-height: 70vh;
459
+ }
460
+
461
+ .tnxtdm-select-multi-header {
462
+ display: flex;
463
+ justify-content: space-between;
464
+ align-items: center;
465
+ padding: 0 16px;
466
+ height: 48px;
467
+ border-bottom: 1px solid var(--td-border-level-1-color);
468
+ }
469
+
470
+ .tnxtdm-select-multi-title {
471
+ font-size: 16px;
472
+ font-weight: 600;
473
+ }
474
+
475
+ .tnxtdm-select-multi-body {
476
+ overflow-y: auto;
477
+ }
478
+ </style>