tang-ui-x 1.1.0 → 1.1.2

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 (89) hide show
  1. package/README.md +1003 -0
  2. package/components/TActionSheet/index.uvue +15 -2
  3. package/components/TCollapse/index.uvue +1 -1
  4. package/components/TCollapse/type.uts +3 -1
  5. package/components/TCollapseItem/index.uvue +22 -26
  6. package/components/TDialog/index.uvue +19 -4
  7. package/components/TEmpty/index.uvue +28 -14
  8. package/components/TForm/index.uvue +60 -26
  9. package/components/TForm/type.uts +4 -0
  10. package/components/TInput/index.uvue +24 -5
  11. package/components/TInput/type.uts +10 -0
  12. package/components/TPicker/index.uvue +26 -6
  13. package/components/TRadioButton/README.md +117 -0
  14. package/components/TRadioButton/index.uvue +69 -64
  15. package/components/TSearchBar/index.uvue +19 -4
  16. package/composables/i18n/error.uts +82 -0
  17. package/composables/i18n/index.uts +188 -0
  18. package/composables/i18n/manager-demo.uts +104 -0
  19. package/composables/i18n/manager.test.uts +182 -0
  20. package/composables/i18n/manager.uts +336 -0
  21. package/composables/i18n/register-demo.uts +125 -0
  22. package/composables/i18n/task22-verification.uts +198 -0
  23. package/composables/i18n/task23-verification.uts +343 -0
  24. package/composables/i18n/task8-demo.uts +93 -0
  25. package/composables/i18n/task8-verification.uts +98 -0
  26. package/composables/i18n/test-task23.uts +9 -0
  27. package/composables/i18n/types.uts +46 -0
  28. package/composables/i18n/useI18n-verification.uts +105 -0
  29. package/composables/i18n/validation-demo.uts +45 -0
  30. package/composables/i18n/validation-test.uts +106 -0
  31. package/composables/useI18n.uts +77 -0
  32. package/index.uts +23 -0
  33. package/locales/cross-platform-verification.uts +510 -0
  34. package/locales/en-US/actionSheet.json +3 -0
  35. package/locales/en-US/common.json +10 -0
  36. package/locales/en-US/dialog.json +5 -0
  37. package/locales/en-US/empty.json +5 -0
  38. package/locales/en-US/errorState.json +5 -0
  39. package/locales/en-US/examplePages.json +1236 -0
  40. package/locales/en-US/examples.json +218 -0
  41. package/locales/en-US/form.json +11 -0
  42. package/locales/en-US/input.json +3 -0
  43. package/locales/en-US/list.json +5 -0
  44. package/locales/en-US/loading.json +3 -0
  45. package/locales/en-US/navBar.json +4 -0
  46. package/locales/en-US/noticeBar.json +3 -0
  47. package/locales/en-US/picker.json +5 -0
  48. package/locales/en-US/searchBar.json +4 -0
  49. package/locales/en-US/textarea.json +3 -0
  50. package/locales/en-US/toast.json +6 -0
  51. package/locales/index.uts +79 -0
  52. package/locales/init-verification.uts +101 -0
  53. package/locales/loader.uts +251 -0
  54. package/locales/run-verification.uts +16 -0
  55. package/locales/zh-CN/actionSheet.json +3 -0
  56. package/locales/zh-CN/common.json +10 -0
  57. package/locales/zh-CN/dialog.json +5 -0
  58. package/locales/zh-CN/empty.json +5 -0
  59. package/locales/zh-CN/errorState.json +5 -0
  60. package/locales/zh-CN/examplePages.json +1236 -0
  61. package/locales/zh-CN/examples.json +218 -0
  62. package/locales/zh-CN/form.json +11 -0
  63. package/locales/zh-CN/input.json +3 -0
  64. package/locales/zh-CN/list.json +5 -0
  65. package/locales/zh-CN/loading.json +3 -0
  66. package/locales/zh-CN/navBar.json +4 -0
  67. package/locales/zh-CN/noticeBar.json +3 -0
  68. package/locales/zh-CN/picker.json +5 -0
  69. package/locales/zh-CN/searchBar.json +4 -0
  70. package/locales/zh-CN/textarea.json +3 -0
  71. package/locales/zh-CN/toast.json +6 -0
  72. package/locales/zh-TW/actionSheet.json +3 -0
  73. package/locales/zh-TW/common.json +8 -0
  74. package/locales/zh-TW/dialog.json +5 -0
  75. package/locales/zh-TW/empty.json +5 -0
  76. package/locales/zh-TW/errorState.json +5 -0
  77. package/locales/zh-TW/examplePages.json +705 -0
  78. package/locales/zh-TW/examples.json +218 -0
  79. package/locales/zh-TW/form.json +11 -0
  80. package/locales/zh-TW/input.json +3 -0
  81. package/locales/zh-TW/list.json +5 -0
  82. package/locales/zh-TW/loading.json +3 -0
  83. package/locales/zh-TW/navBar.json +4 -0
  84. package/locales/zh-TW/noticeBar.json +3 -0
  85. package/locales/zh-TW/picker.json +5 -0
  86. package/locales/zh-TW/searchBar.json +4 -0
  87. package/locales/zh-TW/textarea.json +3 -0
  88. package/locales/zh-TW/toast.json +6 -0
  89. package/package.json +49 -47
@@ -1,6 +1,8 @@
1
1
  <script setup lang="uts">
2
+ import { computed } from 'vue'
2
3
  import TPopup from '../TPopup/index.uvue'
3
4
  import type { ActionSheetAction, TActionSheetProps } from './type.uts'
5
+ import { useI18n } from '../../composables/useI18n.uts'
4
6
 
5
7
  /**
6
8
  * TActionSheet 动作面板组件
@@ -14,7 +16,6 @@ const props = withDefaults(defineProps<Props>(), {
14
16
  actions: () => [] as ActionSheetAction[],
15
17
  title: '',
16
18
  description: '',
17
- cancelText: '取消',
18
19
  closeOnClickAction: true,
19
20
  closeOnClickOverlay: true,
20
21
  customClass: '',
@@ -30,6 +31,18 @@ const emit = defineEmits<{
30
31
  close: []
31
32
  }>()
32
33
 
34
+ // 使用 i18n
35
+ const { $t } = useI18n()
36
+
37
+ // 优先使用用户传入的值,否则使用翻译
38
+ const displayCancelText = computed(() => {
39
+ // 如果用户明确传入了 cancelText(即使是空字符串),使用用户的值
40
+ // 否则使用翻译
41
+ return props.cancelText !== undefined && props.cancelText !== ''
42
+ ? props.cancelText
43
+ : $t('actionSheet.cancelText')
44
+ })
45
+
33
46
  const handleSelect = (action: ActionSheetAction, index: number): void => {
34
47
  if (action.disabled || action.loading) return
35
48
 
@@ -90,7 +103,7 @@ const handleClose = (): void => {
90
103
 
91
104
  <!-- 取消按钮 -->
92
105
  <view class="t-action-sheet__cancel" @click="handleCancel">
93
- <text class="t-action-sheet__cancel-text">{{ cancelText }}</text>
106
+ <text class="t-action-sheet__cancel-text">{{ displayCancelText }}</text>
94
107
  </view>
95
108
  </view>
96
109
  </TPopup>
@@ -53,7 +53,7 @@ const toggle = (name: string): void => {
53
53
  * 提供给子组件的上下文
54
54
  */
55
55
  const collapseContext: TCollapseContext = {
56
- activeNames: activeNamesArray.value,
56
+ activeNames: activeNamesArray,
57
57
  accordion: props.accordion,
58
58
  toggle
59
59
  }
@@ -15,6 +15,8 @@ export type TCollapseProps = {
15
15
  border?: boolean
16
16
  }
17
17
 
18
+ import type { ComputedRef } from 'vue'
19
+
18
20
  /**
19
21
  * TCollapse 提供给子组件的上下文
20
22
  */
@@ -22,7 +24,7 @@ export type TCollapseContext = {
22
24
  /**
23
25
  * 当前展开的面板名称(数组)
24
26
  */
25
- activeNames: string[]
27
+ activeNames: ComputedRef<string[]>
26
28
 
27
29
  /**
28
30
  * 是否为手风琴模式
@@ -18,12 +18,24 @@ const props = withDefaults(defineProps<TCollapseItemProps>(), {
18
18
  // 从父组件注入上下文
19
19
  const collapseContext = inject<TCollapseContext>('TCollapse')
20
20
 
21
+ // 内容高度
22
+ const contentHeight = ref<number>(0)
23
+ const contentRef = ref<any>(null)
24
+
21
25
  // 计算是否展开
22
26
  const isActive = computed<boolean>(() => {
23
27
  if (collapseContext == null) {
24
28
  return false
25
29
  }
26
- return collapseContext.activeNames.indexOf(props.name) > -1
30
+ return collapseContext.activeNames.value.indexOf(props.name) > -1
31
+ })
32
+
33
+ // 计算内容样式
34
+ const contentStyle = computed<string>(() => {
35
+ if (isActive.value) {
36
+ return 'max-height: 2000px; opacity: 1; padding-top: 0; padding-bottom: 0;'
37
+ }
38
+ return 'max-height: 0; opacity: 0; padding-top: 0; padding-bottom: 0;'
27
39
  })
28
40
 
29
41
  /**
@@ -88,13 +100,11 @@ const arrowClass = computed<string>(() => {
88
100
  </view>
89
101
 
90
102
  <!-- 内容区域 -->
91
- <transition name="t-collapse">
92
- <view v-show="isActive" class="t-collapse-item__content">
93
- <view class="t-collapse-item__content-inner">
94
- <slot></slot>
95
- </view>
103
+ <view class="t-collapse-item__content" :style="contentStyle">
104
+ <view class="t-collapse-item__content-inner">
105
+ <slot></slot>
96
106
  </view>
97
- </transition>
107
+ </view>
98
108
  </view>
99
109
  </template>
100
110
 
@@ -147,7 +157,7 @@ const arrowClass = computed<string>(() => {
147
157
  display: flex;
148
158
  align-items: center;
149
159
  justify-content: center;
150
- transition: transform 0.3s;
160
+ transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
151
161
 
152
162
  &--active {
153
163
  transform: rotate(90deg);
@@ -164,31 +174,17 @@ const arrowClass = computed<string>(() => {
164
174
  &__content {
165
175
  background-color: #ffffff;
166
176
  overflow: hidden;
177
+ transition: max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1),
178
+ opacity 0.35s cubic-bezier(0.4, 0, 0.2, 1),
179
+ padding 0.35s cubic-bezier(0.4, 0, 0.2, 1);
167
180
 
168
181
  &-inner {
169
182
  padding: 16px 20px;
170
183
  font-size: 14px;
171
184
  color: #606266;
172
185
  line-height: 1.6;
186
+ transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
173
187
  }
174
188
  }
175
189
  }
176
-
177
- /* 折叠动画 */
178
- .t-collapse-enter-active,
179
- .t-collapse-leave-active {
180
- transition: all 0.3s ease;
181
- }
182
-
183
- .t-collapse-enter-from,
184
- .t-collapse-leave-to {
185
- opacity: 0;
186
- max-height: 0;
187
- }
188
-
189
- .t-collapse-enter-to,
190
- .t-collapse-leave-from {
191
- opacity: 1;
192
- max-height: 1000px;
193
- }
194
190
  </style>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="uts" >
2
2
  import { computed } from 'vue'
3
3
  import type { TDialogProps } from './type.uts'
4
+ import { useI18n } from '../../composables/useI18n.uts'
4
5
 
5
6
  /**
6
7
  * TDialog 对话框组件
@@ -18,8 +19,6 @@ const props = withDefaults(defineProps<Props>(), {
18
19
  maskClosable: true,
19
20
  showClose: true,
20
21
  showCancel: true,
21
- confirmText: '确定',
22
- cancelText: '取消',
23
22
  confirmType: 'primary'
24
23
  })
25
24
 
@@ -33,6 +32,22 @@ const emit = defineEmits<{
33
32
  close: []
34
33
  }>()
35
34
 
35
+ // 使用 i18n
36
+ const { $t } = useI18n()
37
+
38
+ // 优先使用用户传入的值,否则使用翻译
39
+ const displayConfirmText = computed(() => {
40
+ return props.confirmText !== undefined && props.confirmText !== ''
41
+ ? props.confirmText
42
+ : $t('dialog.confirmText')
43
+ })
44
+
45
+ const displayCancelText = computed(() => {
46
+ return props.cancelText !== undefined && props.cancelText !== ''
47
+ ? props.cancelText
48
+ : $t('dialog.cancelText')
49
+ })
50
+
36
51
  /**
37
52
  * 计算对话框样式
38
53
  */
@@ -129,10 +144,10 @@ const handleClose = (): void => {
129
144
  <view class="t-dialog__footer">
130
145
  <slot name="footer">
131
146
  <view v-if="showCancel" class="t-dialog__btn t-dialog__btn--cancel" @click="handleCancel">
132
- <text class="t-dialog__btn-text">{{ cancelText }}</text>
147
+ <text class="t-dialog__btn-text">{{ displayCancelText }}</text>
133
148
  </view>
134
149
  <view :class="confirmBtnClass" @click="handleConfirm">
135
- <text class="t-dialog__btn-text t-dialog__btn-text--confirm">{{ confirmText }}</text>
150
+ <text class="t-dialog__btn-text t-dialog__btn-text--confirm">{{ displayConfirmText }}</text>
136
151
  </view>
137
152
  </slot>
138
153
  </view>
@@ -1,4 +1,8 @@
1
1
  <script setup lang="uts" >
2
+ import { computed } from 'vue'
3
+ import TButton from '../TButton/index.uvue'
4
+ import { useI18n } from '../../composables/useI18n.uts'
5
+
2
6
  interface EmptyProps {
3
7
  /** 标题 */
4
8
  title ?: string;
@@ -15,11 +19,8 @@
15
19
  }
16
20
 
17
21
  const props = withDefaults(defineProps<EmptyProps>(), {
18
- title: '暂无数据',
19
- description: '当前没有相关内容',
20
22
  image: '/static/images/empty.png',
21
23
  showAction: false,
22
- actionText: '刷新试试',
23
24
  compact: false
24
25
  });
25
26
 
@@ -27,6 +28,14 @@
27
28
  actionClick : []
28
29
  }>();
29
30
 
31
+ // 使用 i18n
32
+ const { $t } = useI18n()
33
+
34
+ // 优先使用用户传入的值,否则使用翻译
35
+ const displayTitle = computed(() => props.title || $t('empty.title'))
36
+ const displayDescription = computed(() => props.description || $t('empty.description'))
37
+ const displayActionText = computed(() => props.actionText || $t('empty.actionText'))
38
+
30
39
  const handleActionClick = () => {
31
40
  emits('actionClick');
32
41
  };
@@ -43,7 +52,7 @@
43
52
  'empty-image',
44
53
  compact ? 'w-24 h-24 mb-3' : 'w-32 h-32 mb-4'
45
54
  ]">
46
- <LazyImage :src="image" class="w-full h-full" mode="aspectFit" :show-loading="false" />
55
+ <image :src="image" class="w-full h-full" mode="aspectFit" :show-loading="false" />
47
56
  </view>
48
57
  </slot>
49
58
 
@@ -52,21 +61,25 @@
52
61
  'empty-title block text-gray-700 dark:text-gray-300 text-center',
53
62
  compact ? 'text-base font-medium' : 'text-lg font-medium',
54
63
  ]">
55
- {{ title }}
64
+ {{ displayTitle }}
56
65
  </text>
57
66
 
58
- <text v-if="description" :class="[
67
+ <text v-if="displayDescription" :class="[
59
68
  'empty-description block mt-2 text-gray-500 dark:text-gray-400',
60
69
  compact ? 'text-xs' : 'text-sm',
61
70
  ]">
62
- {{ description }}
71
+ {{ displayDescription }}
63
72
  </text>
64
73
  </view>
65
74
 
66
75
  <view v-if="showAction" class="empty-action mt-6">
67
- <ButtonX size='small' :block="true" @tap="handleActionClick">
68
- {{ actionText }}
69
- </ButtonX>
76
+ <TButton
77
+ type="primary"
78
+ size="medium"
79
+ @click="handleActionClick"
80
+ >
81
+ {{ displayActionText }}
82
+ </TButton>
70
83
  </view>
71
84
  </view>
72
85
  </template>
@@ -105,6 +118,11 @@
105
118
  max-width: 80vw;
106
119
  }
107
120
 
121
+ .empty-action {
122
+ width: 100%;
123
+ max-width: 200px;
124
+ }
125
+
108
126
  // 动画效果
109
127
  .empty-image {
110
128
  animation: fadeIn 0.5s ease;
@@ -121,8 +139,4 @@
121
139
  transform: translateY(0);
122
140
  }
123
141
  }
124
-
125
- :deep(.btn-small) {
126
- padding: 0 1rem
127
- }
128
142
  </style>
@@ -1,10 +1,12 @@
1
1
  <script setup lang="uts">
2
+ import { computed } from 'vue'
2
3
  import TRadioButton from '../TRadioButton/index.uvue'
3
4
  import TCheckbox from '../TCheckbox/index.uvue'
4
5
  import TSwitch from '../TSwitch/index.uvue'
5
6
  import TRate from '../TRate/index.uvue'
6
7
  import TSlider from '../TSlider/index.uvue'
7
8
  import type { FormOption, FormSchema, TFormProps, ComponentProps } from './type.uts'
9
+ import { useI18n } from '../../composables/useI18n.uts'
8
10
 
9
11
  const props = withDefaults(defineProps<TFormProps>(), {
10
12
  labelWidth: '160rpx',
@@ -16,8 +18,27 @@
16
18
  const model = defineModel<Record<string, any>>({ default: () => ({}) })
17
19
  const errors = reactive<Record<string, string>>({})
18
20
 
21
+ // 使用 i18n
22
+ const { $t } = useI18n()
23
+
24
+ // 按钮文本
25
+ const submitButtonText = computed(() => {
26
+ return props.submitText !== undefined && props.submitText !== ''
27
+ ? props.submitText
28
+ : $t('form.submitButton')
29
+ })
30
+
31
+ const resetButtonText = computed(() => {
32
+ return props.resetText !== undefined && props.resetText !== ''
33
+ ? props.resetText
34
+ : $t('form.resetButton')
35
+ })
36
+
19
37
  const handlePlaceholder = (item: FormSchema): string => {
20
- return item.componentProps?.placeholder || `请输入${item.label}`
38
+ if (item.componentProps?.placeholder) {
39
+ return item.componentProps.placeholder
40
+ }
41
+ return $t('form.inputPlaceholder', { label: item.label })
21
42
  }
22
43
 
23
44
  const handleRange = (item: FormSchema) => {
@@ -46,7 +67,7 @@
46
67
 
47
68
  const validateField = (item: FormSchema) => {
48
69
  if (item.required && !model.value[item.field]) {
49
- errors[item.field] = `请输入${item.label}`
70
+ errors[item.field] = $t('form.requiredError', { label: item.label })
50
71
  } else {
51
72
  delete errors[item.field]
52
73
  }
@@ -89,7 +110,7 @@
89
110
  }
90
111
 
91
112
  if (hasError) {
92
- console.warn('表单验证失败')
113
+ console.warn($t('form.validationFailed'))
93
114
  return { valid: false, values: null }
94
115
  }
95
116
 
@@ -97,7 +118,7 @@
97
118
  await emit('submit', model.value)
98
119
  return { valid: true, values: model.value }
99
120
  } catch (err) {
100
- console.error('表单提交失败:', err)
121
+ console.error($t('form.submitFailed'), err)
101
122
  return { valid: false, values: null }
102
123
  }
103
124
  }
@@ -180,7 +201,7 @@
180
201
  v-bind="item.componentProps || {}"
181
202
  @change="onSelectChange($event, item)">
182
203
  <view class="picker">
183
- {{ getSelectLabel(item) || '请选择' }}
204
+ {{ getSelectLabel(item) || $t('form.selectPlaceholder') }}
184
205
  </view>
185
206
  </picker>
186
207
 
@@ -192,7 +213,7 @@
192
213
  v-bind="item.componentProps || {}"
193
214
  @change="onTimeChange($event, item)">
194
215
  <view class="picker">
195
- {{ model[item.field] || '请选择日期' }}
216
+ {{ model[item.field] || $t('form.datePlaceholder') }}
196
217
  </view>
197
218
  </picker>
198
219
 
@@ -204,7 +225,7 @@
204
225
  v-bind="item.componentProps || {}"
205
226
  @change="onTimeChange($event, item)">
206
227
  <view class="picker">
207
- {{ model[item.field] || '请选择时间' }}
228
+ {{ model[item.field] || $t('form.timePlaceholder') }}
208
229
  </view>
209
230
  </picker>
210
231
 
@@ -213,32 +234,33 @@
213
234
  <TRadioButton
214
235
  v-for="opt in (item.componentProps?.options as FormOption[])"
215
236
  :key="opt.value"
237
+ v-model="model[item.field]"
216
238
  :label="opt.label"
217
239
  :value="opt.value"
218
- :checked="model[item.field] === opt.value"
219
- v-bind="item.componentProps || {}"
220
240
  @change="onRadioChange(opt.value, item)" />
221
241
  </view>
222
242
 
223
243
  <!-- 多选 -->
224
244
  <view v-else-if="item.component === 'Checkbox'" class="checkbox-group">
225
- <TCheckbox
245
+ <view
226
246
  v-for="opt in (item.componentProps?.options as FormOption[])"
227
247
  :key="opt.value"
228
- :label="opt.label"
229
- :value="opt.value"
230
- :checked="(model[item.field] || []).includes(opt.value)"
231
- v-bind="item.componentProps || {}"
232
- @change="(checked) => {
233
- const values = model[item.field] || []
234
- if (checked) {
235
- values.push(opt.value)
236
- } else {
237
- const index = values.indexOf(opt.value)
238
- if (index > -1) values.splice(index, 1)
239
- }
240
- onCheckboxChange(values, item)
241
- }" />
248
+ class="checkbox-item">
249
+ <TCheckbox
250
+ :checked="(model[item.field] || []).includes(opt.value)"
251
+ v-bind="item.componentProps || {}"
252
+ @change="(checked) => {
253
+ const values = model[item.field] || []
254
+ if (checked) {
255
+ values.push(opt.value)
256
+ } else {
257
+ const index = values.indexOf(opt.value)
258
+ if (index > -1) values.splice(index, 1)
259
+ }
260
+ onCheckboxChange(values, item)
261
+ }" />
262
+ <text class="checkbox-label">{{ opt.label }}</text>
263
+ </view>
242
264
  </view>
243
265
 
244
266
  <!-- 开关 -->
@@ -268,8 +290,8 @@
268
290
 
269
291
  <!-- 按钮区域 -->
270
292
  <view v-if="!hideButtons" class="form-footer">
271
- <button class="btn btn-submit" form-type="submit">提交</button>
272
- <button class="btn btn-reset" form-type="reset">重置</button>
293
+ <button class="btn btn-submit" form-type="submit">{{ submitButtonText }}</button>
294
+ <button class="btn btn-reset" form-type="reset">{{ resetButtonText }}</button>
273
295
  </view>
274
296
  </form>
275
297
  </view>
@@ -338,6 +360,18 @@
338
360
  gap: 16rpx;
339
361
  }
340
362
 
363
+ .checkbox-item {
364
+ display: flex;
365
+ flex-direction: row;
366
+ align-items: center;
367
+ gap: 16rpx;
368
+ }
369
+
370
+ .checkbox-label {
371
+ font-size: 28rpx;
372
+ color: #333;
373
+ }
374
+
341
375
  .error-message {
342
376
  margin-top: 8rpx;
343
377
  font-size: 24rpx;
@@ -74,6 +74,10 @@ export type TFormProps = {
74
74
  labelWidth?: string
75
75
  /** 是否隐藏默认按钮 */
76
76
  hideButtons?: boolean
77
+ /** 提交按钮文本 */
78
+ submitText?: string
79
+ /** 重置按钮文本 */
80
+ resetText?: string
77
81
  }
78
82
 
79
83
  // 导出所有类型
@@ -16,6 +16,8 @@ const props = withDefaults(defineProps<TInputProps>(), {
16
16
  clearable: false,
17
17
  showCount: false,
18
18
  maxlength: -1,
19
+ prefix: '',
20
+ suffix: '',
19
21
  prefixIcon: '',
20
22
  suffixIcon: '',
21
23
  rows: 3,
@@ -70,13 +72,13 @@ const inputWrapperClasses = computed((): string => {
70
72
  classes.push('t-input--focused')
71
73
  }
72
74
 
73
- // 前缀图标
74
- if (props.prefixIcon) {
75
+ // 前缀文本或图标
76
+ if (props.prefix || props.prefixIcon) {
75
77
  classes.push('t-input--prefix')
76
78
  }
77
79
 
78
- // 后缀图标或清除按钮
79
- if (props.suffixIcon || props.clearable) {
80
+ // 后缀文本、图标或清除按钮
81
+ if (props.suffix || props.suffixIcon || props.clearable) {
80
82
  classes.push('t-input--suffix')
81
83
  }
82
84
 
@@ -142,8 +144,13 @@ const handleConfirm = () => {
142
144
 
143
145
  <template>
144
146
  <view class="t-input" :class="inputWrapperClasses" :style="customStyle">
147
+ <!-- 前缀文本 -->
148
+ <view v-if="prefix" class="t-input__prefix">
149
+ <text class="t-input__prefix-text">{{ prefix }}</text>
150
+ </view>
151
+
145
152
  <!-- 前缀图标 -->
146
- <view v-if="prefixIcon" class="t-input__prefix">
153
+ <view v-else-if="prefixIcon" class="t-input__prefix">
147
154
  <text class="t-input__icon">{{ prefixIcon }}</text>
148
155
  </view>
149
156
 
@@ -189,6 +196,11 @@ const handleConfirm = () => {
189
196
  <text class="t-input__icon">✕</text>
190
197
  </view>
191
198
 
199
+ <!-- 后缀文本 -->
200
+ <view v-else-if="suffix" class="t-input__suffix">
201
+ <text class="t-input__suffix-text">{{ suffix }}</text>
202
+ </view>
203
+
192
204
  <!-- 后缀图标 -->
193
205
  <view v-else-if="suffixIcon" class="t-input__suffix">
194
206
  <text class="t-input__icon">{{ suffixIcon }}</text>
@@ -242,6 +254,13 @@ const handleConfirm = () => {
242
254
  font-size: 16px;
243
255
  }
244
256
 
257
+ &__prefix-text,
258
+ &__suffix-text {
259
+ font-size: 14px;
260
+ color: #606266;
261
+ white-space: nowrap;
262
+ }
263
+
245
264
  &__inner,
246
265
  &__textarea {
247
266
  flex: 1;
@@ -63,6 +63,16 @@ export interface TInputProps {
63
63
  */
64
64
  maxlength?: number
65
65
 
66
+ /**
67
+ * 前缀文本
68
+ */
69
+ prefix?: string
70
+
71
+ /**
72
+ * 后缀文本
73
+ */
74
+ suffix?: string
75
+
66
76
  /**
67
77
  * 前缀图标
68
78
  */
@@ -1,6 +1,7 @@
1
1
  <script setup lang="uts">
2
2
  import { computed, ref, watch } from 'vue'
3
3
  import type { TPickerProps, TPickerOption } from './type.uts'
4
+ import { useI18n } from '../../composables/useI18n.uts'
4
5
 
5
6
  /**
6
7
  * TPicker 通用选择器组件
@@ -11,11 +12,8 @@ import type { TPickerProps, TPickerOption } from './type.uts'
11
12
  type Props = Omit<TPickerProps, 'visible'>
12
13
 
13
14
  const props = withDefaults(defineProps<Props>(), {
14
- title: '请选择',
15
15
  options: () => [] as TPickerOption[],
16
16
  columns: () => [] as TPickerOption[][],
17
- confirmText: '确定',
18
- cancelText: '取消',
19
17
  itemHeight: '44px',
20
18
  visibleItemCount: 5,
21
19
  showToolbar: true
@@ -34,6 +32,28 @@ const emit = defineEmits<{
34
32
  change: [value: any, index: number | number[], columnIndex: number]
35
33
  }>()
36
34
 
35
+ // 使用 i18n
36
+ const { $t } = useI18n()
37
+
38
+ // 优先使用用户传入的值,否则使用翻译
39
+ const displayTitle = computed(() => {
40
+ return props.title !== undefined && props.title !== ''
41
+ ? props.title
42
+ : $t('picker.title')
43
+ })
44
+
45
+ const displayConfirmText = computed(() => {
46
+ return props.confirmText !== undefined && props.confirmText !== ''
47
+ ? props.confirmText
48
+ : $t('picker.confirmText')
49
+ })
50
+
51
+ const displayCancelText = computed(() => {
52
+ return props.cancelText !== undefined && props.cancelText !== ''
53
+ ? props.cancelText
54
+ : $t('picker.cancelText')
55
+ })
56
+
37
57
  // 当前选中的索引(单列为 number,多列为 number[])
38
58
  const selectedIndex = ref<number[]>([0])
39
59
 
@@ -154,13 +174,13 @@ const handleChange = (e: any): void => {
154
174
  <!-- 工具栏 -->
155
175
  <view v-if="showToolbar" class="t-picker__toolbar">
156
176
  <view class="t-picker__btn t-picker__btn--cancel" @click="handleCancel">
157
- <text class="t-picker__btn-text">{{ cancelText }}</text>
177
+ <text class="t-picker__btn-text">{{ displayCancelText }}</text>
158
178
  </view>
159
179
  <view class="t-picker__title">
160
- <text class="t-picker__title-text">{{ title }}</text>
180
+ <text class="t-picker__title-text">{{ displayTitle }}</text>
161
181
  </view>
162
182
  <view class="t-picker__btn t-picker__btn--confirm" @click="handleConfirm">
163
- <text class="t-picker__btn-text t-picker__btn-text--primary">{{ confirmText }}</text>
183
+ <text class="t-picker__btn-text t-picker__btn-text--primary">{{ displayConfirmText }}</text>
164
184
  </view>
165
185
  </view>
166
186