@truenewx/tnxvue3 3.4.4 → 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.
@@ -0,0 +1,392 @@
1
+ <template>
2
+ <div :class="containerClasses" :style="containerStyle" v-if="normalizedItems.length">
3
+ <div class="tnxtdm-slide-radio-group__glider" :style="gliderStyle"></div>
4
+ <div v-for="(item, index) in normalizedItems"
5
+ :key="item.value"
6
+ class="tnxtdm-slide-radio-group__item"
7
+ :class="{'is-active': modelValue === item.value}"
8
+ @click="onItemClick(item.value)"
9
+ :ref="el => { if (el) itemRefs[index] = el }">
10
+ <t-icon class="tnxtdm-slide-radio-group__item-icon" :name="item.icon" size="1rem" v-if="item.icon"/>
11
+ <span class="tnxtdm-slide-radio-group__item-text">{{ item.text }}</span>
12
+ </div>
13
+ </div>
14
+ </template>
15
+
16
+ <script>
17
+ export default {
18
+ name: 'TnxtdmSlideRadioGroup',
19
+ props: {
20
+ modelValue: [String, Number, Boolean],
21
+ items: {
22
+ type: Array,
23
+ default: () => [],
24
+ },
25
+ size: {
26
+ type: String,
27
+ default: 'medium',
28
+ validator: (value) => ['small', 'medium', 'large'].includes(value),
29
+ },
30
+ shape: {
31
+ type: String,
32
+ default: 'rectangle',
33
+ validator: (value) => ['capsule', 'rectangle'].includes(value),
34
+ },
35
+ theme: {
36
+ type: String,
37
+ default: 'default',
38
+ },
39
+ valueName: {
40
+ type: String,
41
+ default: 'value',
42
+ },
43
+ textName: {
44
+ type: String,
45
+ default: 'text',
46
+ },
47
+ iconName: {
48
+ type: String,
49
+ default: 'icon',
50
+ },
51
+ defaultValue: {
52
+ type: [String, Number, Boolean],
53
+ default: undefined,
54
+ },
55
+ width: {
56
+ type: String,
57
+ default: 'auto',
58
+ },
59
+ disabled: Boolean,
60
+ },
61
+ emits: ['update:modelValue', 'change'],
62
+ data() {
63
+ return {
64
+ gliderStyle: {
65
+ width: '0px',
66
+ transform: 'translateX(0px)',
67
+ },
68
+ formDisabled: false,
69
+ };
70
+ },
71
+ computed: {
72
+ isDisabled() {
73
+ return this.disabled || this.formDisabled;
74
+ },
75
+ normalizedItems() {
76
+ if (!this.items) {
77
+ return [];
78
+ }
79
+ return this.items.map(item => {
80
+ if (typeof item === 'object' && item !== null) {
81
+ return {
82
+ text: item[this.textName],
83
+ value: item[this.valueName],
84
+ icon: item[this.iconName],
85
+ };
86
+ }
87
+ return {
88
+ text: String(item),
89
+ value: item,
90
+ };
91
+ });
92
+ },
93
+ currentValue: {
94
+ get() {
95
+ return this.modelValue;
96
+ },
97
+ set(val) {
98
+ this.$emit('update:modelValue', val);
99
+ let selectedItem = val;
100
+ if (this.items && this.items.length > 0) {
101
+ const index = this.normalizedItems.findIndex(item => item.value === val);
102
+ if (index !== -1) {
103
+ selectedItem = this.items[index];
104
+ }
105
+ }
106
+ this.$emit('change', selectedItem);
107
+ }
108
+ },
109
+ containerClasses() {
110
+ return [
111
+ 'tnxtdm-slide-radio-group',
112
+ `tnxtdm-slide-radio-group--${this.shape}`,
113
+ `tnxtdm-slide-radio-group--${this.size}`,
114
+ `tnxtdm-slide-radio-group--theme-${this.theme}`,
115
+ {
116
+ 'tnxtdm-slide-radio-group--fixed': !['auto', 'fit-content'].includes(this.width),
117
+ 'tnxtdm-slide-radio-group--disabled': this.isDisabled,
118
+ }
119
+ ];
120
+ },
121
+ containerStyle() {
122
+ if (['auto', 'fit-content'].includes(this.width)) {
123
+ return {};
124
+ }
125
+ return {
126
+ width: this.width,
127
+ };
128
+ },
129
+ },
130
+ watch: {
131
+ modelValue: {
132
+ handler() {
133
+ this.ensureValue(); // 监听 modelValue 变化,确保其有效
134
+ this.$nextTick(this.updateGlider);
135
+ },
136
+ immediate: true,
137
+ },
138
+ items: {
139
+ handler() {
140
+ this.itemRefs = [];
141
+ this.ensureValue();
142
+ this.$nextTick(() => {
143
+ this.setupObserver();
144
+ this.updateGlider();
145
+ });
146
+ },
147
+ deep: true,
148
+ immediate: true, // items 初始化时也需要检查
149
+ },
150
+ },
151
+ mounted() {
152
+ this.updateGlider();
153
+ window.addEventListener('resize', this.updateGlider);
154
+ this.setupObserver();
155
+ // 兼容某些场景下初始渲染时宽度计算不准确的问题
156
+ setTimeout(this.updateGlider, 50);
157
+ setTimeout(this.updateGlider, 200);
158
+ this.initFormDisabled();
159
+ },
160
+ beforeUnmount() {
161
+ window.removeEventListener('resize', this.updateGlider);
162
+ if (this.resizeObserver) {
163
+ this.resizeObserver.disconnect();
164
+ }
165
+ },
166
+ methods: {
167
+ initFormDisabled() {
168
+ let parent = this.$parent;
169
+ while (parent) {
170
+ if (parent.$options && (parent.$options.name === 't-form' || parent.$options.name === 'TForm')) {
171
+ this.$watch(() => parent.disabled || (parent.$props && parent.$props.disabled), (val) => {
172
+ this.formDisabled = val;
173
+ }, {immediate: true});
174
+ break;
175
+ }
176
+ parent = parent.$parent;
177
+ }
178
+ },
179
+ setupObserver() {
180
+ if (this.$el instanceof Element && typeof ResizeObserver !== 'undefined') {
181
+ if (!this.resizeObserver) {
182
+ this.resizeObserver = new ResizeObserver(() => {
183
+ this.updateGlider();
184
+ });
185
+ }
186
+ this.resizeObserver.disconnect();
187
+ this.resizeObserver.observe(this.$el);
188
+ }
189
+ },
190
+ ensureValue() {
191
+ if (this.normalizedItems.length === 0) {
192
+ return;
193
+ }
194
+
195
+ const exists = this.normalizedItems.some(item => item.value === this.currentValue);
196
+ if (exists) {
197
+ return;
198
+ }
199
+
200
+ if (this.defaultValue !== undefined && this.defaultValue !== null) {
201
+ const defaultExists = this.normalizedItems.some(item => item.value === this.defaultValue);
202
+ if (defaultExists) {
203
+ this.currentValue = this.defaultValue;
204
+ return;
205
+ }
206
+ }
207
+
208
+ this.currentValue = this.normalizedItems[0].value;
209
+ },
210
+ onItemClick(value) {
211
+ if (this.isDisabled) {
212
+ return;
213
+ }
214
+ if (value !== this.currentValue) {
215
+ this.currentValue = value;
216
+ }
217
+ },
218
+ updateGlider() {
219
+ const index = this.normalizedItems.findIndex(item => item.value === this.currentValue);
220
+ if (index === -1 || !this.itemRefs[index]) {
221
+ this.gliderStyle = {
222
+ width: '0px',
223
+ opacity: 0,
224
+ left: '4px',
225
+ };
226
+ return;
227
+ }
228
+
229
+ const el = this.itemRefs[index];
230
+ if (this.size === 'small') {
231
+ const inset = 1;
232
+ const width = Math.max(el.offsetWidth - inset * 2, 0);
233
+ this.gliderStyle = {
234
+ width: `${width}px`,
235
+ transform: `translateX(${el.offsetLeft}px)`,
236
+ opacity: 1,
237
+ left: `${inset}px`,
238
+ };
239
+ } else {
240
+ const container = this.$el;
241
+ const paddingLeft = container ? parseFloat(getComputedStyle(container).paddingLeft || '0') : 0;
242
+ const width = el.offsetWidth;
243
+ this.gliderStyle = {
244
+ width: `${width}px`,
245
+ transform: `translateX(${el.offsetLeft - paddingLeft}px)`,
246
+ opacity: 1,
247
+ left: `${paddingLeft}px`,
248
+ };
249
+ }
250
+ },
251
+ },
252
+ }
253
+ </script>
254
+
255
+ <style>
256
+ .tnxtdm-slide-radio-group {
257
+ position: relative;
258
+ display: flex;
259
+ background-color: var(--td-gray-color-1);
260
+ padding: 0 0.25rem;
261
+ box-sizing: border-box;
262
+ user-select: none;
263
+ width: fit-content;
264
+ border: 1px solid var(--td-gray-color-2);
265
+ height: 2rem;
266
+ }
267
+
268
+ .tnxtdm-slide-radio-group--capsule {
269
+ border-radius: var(--td-radius-round);
270
+ }
271
+
272
+ .tnxtdm-slide-radio-group--rectangle {
273
+ border-radius: var(--td-radius-default);
274
+ }
275
+
276
+ .tnxtdm-slide-radio-group--small {
277
+ height: 1.5rem;
278
+ padding: 0;
279
+ }
280
+
281
+ .tnxtdm-slide-radio-group--small .tnxtdm-slide-radio-group__glider {
282
+ top: 1px;
283
+ bottom: 1px;
284
+ }
285
+
286
+ .tnxtdm-slide-radio-group__item {
287
+ padding: 0 12px;
288
+ font-size: 12px;
289
+ }
290
+
291
+ .tnxtdm-slide-radio-group--small .tnxtdm-slide-radio-group__item {
292
+ padding: 0 10px;
293
+ font-size: 10px;
294
+ }
295
+
296
+ .tnxtdm-slide-radio-group--medium .tnxtdm-slide-radio-group__glider {
297
+ top: 3px;
298
+ bottom: 3px;
299
+ }
300
+
301
+ .tnxtdm-slide-radio-group--large {
302
+ height: 2.5rem;
303
+ padding: 0 5px;
304
+ }
305
+
306
+ .tnxtdm-slide-radio-group--large .tnxtdm-slide-radio-group__item {
307
+ padding: 0 14px;
308
+ font-size: 14px;
309
+ }
310
+
311
+ .tnxtdm-slide-radio-group--large .tnxtdm-slide-radio-group__glider {
312
+ top: 5px;
313
+ bottom: 5px;
314
+ }
315
+
316
+ .tnxtdm-slide-radio-group--fixed .tnxtdm-slide-radio-group__item {
317
+ flex: 1;
318
+ white-space: normal;
319
+ }
320
+
321
+ .tnxtdm-slide-radio-group__glider {
322
+ position: absolute;
323
+ top: 2px;
324
+ bottom: 2px;
325
+ left: 0.25rem; /* Default left, will be overridden by style binding */
326
+ background-color: var(--td-bg-color-container);
327
+ transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
328
+ box-shadow: var(--td-shadow-1, 0 1px 2px rgba(0, 0, 0, 0.1));
329
+ z-index: 0;
330
+ }
331
+
332
+ .tnxtdm-slide-radio-group--capsule .tnxtdm-slide-radio-group__glider {
333
+ border-radius: var(--td-radius-round);
334
+ }
335
+
336
+ .tnxtdm-slide-radio-group--rectangle .tnxtdm-slide-radio-group__glider {
337
+ border-radius: var(--td-radius-default);
338
+ }
339
+
340
+ .tnxtdm-slide-radio-group--theme-primary .tnxtdm-slide-radio-group__glider {
341
+ background-color: var(--td-brand-color);
342
+ }
343
+
344
+ .tnxtdm-slide-radio-group--theme-success .tnxtdm-slide-radio-group__glider {
345
+ background-color: var(--td-success-color);
346
+ }
347
+
348
+ .tnxtdm-slide-radio-group--theme-warning .tnxtdm-slide-radio-group__glider {
349
+ background-color: var(--td-warning-color);
350
+ }
351
+
352
+ .tnxtdm-slide-radio-group--theme-danger .tnxtdm-slide-radio-group__glider {
353
+ background-color: var(--td-error-color);
354
+ }
355
+
356
+ .tnxtdm-slide-radio-group__item {
357
+ position: relative;
358
+ display: flex;
359
+ align-items: center;
360
+ justify-content: center;
361
+ font-weight: normal;
362
+ color: var(--td-text-color-secondary);
363
+ cursor: pointer;
364
+ z-index: 1;
365
+ padding: 0 1rem;
366
+ transition: color 0.2s;
367
+ text-align: center;
368
+ white-space: nowrap;
369
+ }
370
+
371
+ .tnxtdm-slide-radio-group__item.is-active {
372
+ color: var(--td-text-color-primary);
373
+ font-weight: normal;
374
+ }
375
+
376
+ .tnxtdm-slide-radio-group--theme-primary .tnxtdm-slide-radio-group__item.is-active,
377
+ .tnxtdm-slide-radio-group--theme-success .tnxtdm-slide-radio-group__item.is-active,
378
+ .tnxtdm-slide-radio-group--theme-warning .tnxtdm-slide-radio-group__item.is-active,
379
+ .tnxtdm-slide-radio-group--theme-danger .tnxtdm-slide-radio-group__item.is-active {
380
+ color: #ffffff;
381
+ }
382
+
383
+ .tnxtdm-slide-radio-group__item-icon {
384
+ margin-right: 4px;
385
+ font-size: 16px;
386
+ }
387
+
388
+ .tnxtdm-slide-radio-group--disabled {
389
+ opacity: 0.6;
390
+ cursor: not-allowed;
391
+ }
392
+ </style>
@@ -0,0 +1,132 @@
1
+ /**
2
+ * tnxtdm.css
3
+ */
4
+ :root {
5
+ --max-page-width: 442px; /* 手机像素的最大值,IPhone 17 Pro */
6
+ --td-tag-medium-padding: 1px 8px;
7
+ --td-tag-large-padding: 2px 10px;
8
+ }
9
+
10
+ .border-top-0,
11
+ .border-top-0::before,
12
+ .border-top-0 .t-cell-group--bordered::before {
13
+ border-top: none !important;
14
+ }
15
+
16
+ .border-bottom-0,
17
+ .border-bottom-0::after,
18
+ .border-bottom-0 .t-cell-group--bordered::after {
19
+ border-bottom: none !important;
20
+ }
21
+
22
+ .shadow {
23
+ box-shadow: var(--td-shadow-1);
24
+ }
25
+
26
+ .t-input.border {
27
+ padding: 11px 1rem;
28
+ border-radius: var(--td-radius-default);
29
+
30
+ &::after {
31
+ height: 0;
32
+ }
33
+ }
34
+
35
+ .t-input.bg {
36
+ padding: 12px 1rem;
37
+ border-radius: var(--td-radius-default);
38
+ background-color: var(--td-bg-color-page);
39
+
40
+ &::after {
41
+ height: 0;
42
+ }
43
+ }
44
+
45
+ .t-input.rounded {
46
+ border-radius: var(--td-radius-round);
47
+ padding: 0.75rem 1rem;
48
+ }
49
+
50
+ .t-input__control {
51
+ height: 24px;
52
+ padding-bottom: 2px;
53
+ }
54
+
55
+ .t-input__wrap--suffix {
56
+ border-left: 1px solid var(--td-border-level-2-color);
57
+ margin-left: var(--td-spacer-1, 12px);
58
+ }
59
+
60
+ /* 修复密码输入框占位符间距过大的问题 */
61
+ .t-input__control[type="password"] {
62
+ letter-spacing: normal;
63
+ }
64
+
65
+ .t-input[size="small"] {
66
+ padding: 0.5rem 0.75rem;
67
+ }
68
+
69
+ .t-input[size="small"] .t-input__icon--prefix {
70
+ font-size: 20px;
71
+ }
72
+
73
+ .t-input[size="small"] .t-input__icon--prefix:not(:empty) + .t-input__label:empty {
74
+ margin-right: var(--td-spacer-1, 12px);
75
+ }
76
+
77
+ .t-input[size="small"] .t-input__control {
78
+ height: 20px;
79
+ font-size: 14px;
80
+ }
81
+
82
+ .t-dialog__body,
83
+ .t-drawer__sidebar {
84
+ overflow-y: auto;
85
+ }
86
+
87
+ .t-dialog__header + .t-dialog__body {
88
+ margin-top: 1rem;
89
+ }
90
+
91
+ .t-drawer__sidebar-item-icon {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ }
96
+
97
+ .t-form__label--top {
98
+ padding-right: 0;
99
+ }
100
+
101
+ .t-popover__wrapper {
102
+ display: flex;
103
+ align-items: center;
104
+ }
105
+
106
+ .t-button-group {
107
+ display: flex;
108
+ flex-direction: row;
109
+ align-items: center;
110
+ border-radius: var(--td-button-border-radius, var(--td-radius-default, 6px));
111
+ }
112
+
113
+ .t-button-group .t-button {
114
+ width: fit-content;
115
+ }
116
+
117
+ .t-button-group .t-button:first-child:not(:last-child),
118
+ .t-button-group .t-button:first-child:not(:last-child)::after {
119
+ border-top-right-radius: 0;
120
+ border-bottom-right-radius: 0;
121
+ border-right: 1px solid var(--td-bg-color-component-active);
122
+ }
123
+
124
+ .t-button-group .t-button:last-child:not(:first-child),
125
+ .t-button-group .t-button:last-child:not(:first-child)::after {
126
+ border-top-left-radius: 0;
127
+ border-bottom-left-radius: 0;
128
+ }
129
+
130
+ .t-dialog__body {
131
+ text-align: unset;
132
+ }