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.
- package/README.md +1003 -0
- package/components/TActionSheet/index.uvue +15 -2
- package/components/TCollapse/index.uvue +1 -1
- package/components/TCollapse/type.uts +3 -1
- package/components/TCollapseItem/index.uvue +22 -26
- package/components/TDialog/index.uvue +19 -4
- package/components/TEmpty/index.uvue +28 -14
- package/components/TForm/index.uvue +60 -26
- package/components/TForm/type.uts +4 -0
- package/components/TInput/index.uvue +24 -5
- package/components/TInput/type.uts +10 -0
- package/components/TPicker/index.uvue +26 -6
- package/components/TRadioButton/README.md +117 -0
- package/components/TRadioButton/index.uvue +69 -64
- package/components/TSearchBar/index.uvue +19 -4
- package/composables/i18n/error.uts +82 -0
- package/composables/i18n/index.uts +188 -0
- package/composables/i18n/manager-demo.uts +104 -0
- package/composables/i18n/manager.test.uts +182 -0
- package/composables/i18n/manager.uts +336 -0
- package/composables/i18n/register-demo.uts +125 -0
- package/composables/i18n/task22-verification.uts +198 -0
- package/composables/i18n/task23-verification.uts +343 -0
- package/composables/i18n/task8-demo.uts +93 -0
- package/composables/i18n/task8-verification.uts +98 -0
- package/composables/i18n/test-task23.uts +9 -0
- package/composables/i18n/types.uts +46 -0
- package/composables/i18n/useI18n-verification.uts +105 -0
- package/composables/i18n/validation-demo.uts +45 -0
- package/composables/i18n/validation-test.uts +106 -0
- package/composables/useI18n.uts +77 -0
- package/index.uts +23 -0
- package/locales/cross-platform-verification.uts +510 -0
- package/locales/en-US/actionSheet.json +3 -0
- package/locales/en-US/common.json +10 -0
- package/locales/en-US/dialog.json +5 -0
- package/locales/en-US/empty.json +5 -0
- package/locales/en-US/errorState.json +5 -0
- package/locales/en-US/examplePages.json +1236 -0
- package/locales/en-US/examples.json +218 -0
- package/locales/en-US/form.json +11 -0
- package/locales/en-US/input.json +3 -0
- package/locales/en-US/list.json +5 -0
- package/locales/en-US/loading.json +3 -0
- package/locales/en-US/navBar.json +4 -0
- package/locales/en-US/noticeBar.json +3 -0
- package/locales/en-US/picker.json +5 -0
- package/locales/en-US/searchBar.json +4 -0
- package/locales/en-US/textarea.json +3 -0
- package/locales/en-US/toast.json +6 -0
- package/locales/index.uts +79 -0
- package/locales/init-verification.uts +101 -0
- package/locales/loader.uts +251 -0
- package/locales/run-verification.uts +16 -0
- package/locales/zh-CN/actionSheet.json +3 -0
- package/locales/zh-CN/common.json +10 -0
- package/locales/zh-CN/dialog.json +5 -0
- package/locales/zh-CN/empty.json +5 -0
- package/locales/zh-CN/errorState.json +5 -0
- package/locales/zh-CN/examplePages.json +1236 -0
- package/locales/zh-CN/examples.json +218 -0
- package/locales/zh-CN/form.json +11 -0
- package/locales/zh-CN/input.json +3 -0
- package/locales/zh-CN/list.json +5 -0
- package/locales/zh-CN/loading.json +3 -0
- package/locales/zh-CN/navBar.json +4 -0
- package/locales/zh-CN/noticeBar.json +3 -0
- package/locales/zh-CN/picker.json +5 -0
- package/locales/zh-CN/searchBar.json +4 -0
- package/locales/zh-CN/textarea.json +3 -0
- package/locales/zh-CN/toast.json +6 -0
- package/locales/zh-TW/actionSheet.json +3 -0
- package/locales/zh-TW/common.json +8 -0
- package/locales/zh-TW/dialog.json +5 -0
- package/locales/zh-TW/empty.json +5 -0
- package/locales/zh-TW/errorState.json +5 -0
- package/locales/zh-TW/examplePages.json +705 -0
- package/locales/zh-TW/examples.json +218 -0
- package/locales/zh-TW/form.json +11 -0
- package/locales/zh-TW/input.json +3 -0
- package/locales/zh-TW/list.json +5 -0
- package/locales/zh-TW/loading.json +3 -0
- package/locales/zh-TW/navBar.json +4 -0
- package/locales/zh-TW/noticeBar.json +3 -0
- package/locales/zh-TW/picker.json +5 -0
- package/locales/zh-TW/searchBar.json +4 -0
- package/locales/zh-TW/textarea.json +3 -0
- package/locales/zh-TW/toast.json +6 -0
- 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">{{
|
|
106
|
+
<text class="t-action-sheet__cancel-text">{{ displayCancelText }}</text>
|
|
94
107
|
</view>
|
|
95
108
|
</view>
|
|
96
109
|
</TPopup>
|
|
@@ -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
|
-
<
|
|
92
|
-
<view
|
|
93
|
-
<
|
|
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
|
-
</
|
|
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.
|
|
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">{{
|
|
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">{{
|
|
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
|
-
<
|
|
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
|
-
{{
|
|
64
|
+
{{ displayTitle }}
|
|
56
65
|
</text>
|
|
57
66
|
|
|
58
|
-
<text v-if="
|
|
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
|
-
{{
|
|
71
|
+
{{ displayDescription }}
|
|
63
72
|
</text>
|
|
64
73
|
</view>
|
|
65
74
|
|
|
66
75
|
<view v-if="showAction" class="empty-action mt-6">
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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] =
|
|
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('
|
|
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
|
-
<
|
|
245
|
+
<view
|
|
226
246
|
v-for="opt in (item.componentProps?.options as FormOption[])"
|
|
227
247
|
:key="opt.value"
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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"
|
|
272
|
-
<button class="btn btn-reset" form-type="reset"
|
|
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;
|
|
@@ -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;
|
|
@@ -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">{{
|
|
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">{{
|
|
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">{{
|
|
183
|
+
<text class="t-picker__btn-text t-picker__btn-text--primary">{{ displayConfirmText }}</text>
|
|
164
184
|
</view>
|
|
165
185
|
</view>
|
|
166
186
|
|