tang-ui-x 1.0.5 → 1.1.0
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/components/TForm/README.md +644 -0
- package/components/TForm/index.uvue +379 -0
- package/components/TForm/type.uts +80 -0
- package/index.uts +108 -92
- package/package.json +3 -3
- package/components/VbenFrom/index.uvue +0 -392
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
<script setup lang="uts" >
|
|
2
|
-
|
|
3
|
-
import RadioButton from '../RadioButton/index'
|
|
4
|
-
import CheckboxButton from '../CheckboxButton/index'
|
|
5
|
-
|
|
6
|
-
type FormOption = { label : string; value : string | number }
|
|
7
|
-
type FormSchema = {
|
|
8
|
-
field : string
|
|
9
|
-
label : string
|
|
10
|
-
component : 'input' | 'textarea' | 'select' | 'date' | 'radio' | 'checkbox' | 'switch'
|
|
11
|
-
placeholder ?: string
|
|
12
|
-
required ?: boolean
|
|
13
|
-
options ?: FormOption[]
|
|
14
|
-
type ?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type Props = {
|
|
18
|
-
schemas : FormSchema[]
|
|
19
|
-
activeColor ?: string
|
|
20
|
-
inactiveColor ?: string
|
|
21
|
-
backgroundColor ?: string
|
|
22
|
-
hideButtons ?: boolean // 是否隐藏默认按钮
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
26
|
-
activeColor: '#00bba7',
|
|
27
|
-
inactiveColor: '#666666',
|
|
28
|
-
backgroundColor: '#ffffff',
|
|
29
|
-
hideButtons: false,
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
const emit = defineEmits(['submit', 'reset'])
|
|
33
|
-
|
|
34
|
-
/** 使用 defineModel 实现双向数据绑定 */
|
|
35
|
-
const model = defineModel<Record<string, any>>({ default: () => ({}) })
|
|
36
|
-
|
|
37
|
-
/** 验证错误状态 */
|
|
38
|
-
const errors = reactive<Record<string, string>>({})
|
|
39
|
-
|
|
40
|
-
/** 动态 CSS 变量 */
|
|
41
|
-
const cssVars = computed(() => ({
|
|
42
|
-
'--hover-active-color': props.activeColor,
|
|
43
|
-
'--hover-inactive-color': props.inactiveColor,
|
|
44
|
-
'--hover-bg-color': props.backgroundColor,
|
|
45
|
-
}))
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/** placeholder */
|
|
49
|
-
|
|
50
|
-
const hanldePlaceholder=(item : FormSchema): string => {
|
|
51
|
-
return item.placeholder || `请输入${item.label}`
|
|
52
|
-
}
|
|
53
|
-
/** 处理范围选择器选项 */
|
|
54
|
-
const hanldeRange = (options) => {
|
|
55
|
-
return options?.map(o => o.label) || []
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/** select 选中项文字 */
|
|
60
|
-
const getSelectLabel = (item : FormSchema) => {
|
|
61
|
-
const opt = item.options?.find(o => o.value === model.value[item.field])
|
|
62
|
-
return opt?.label || ''
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** 选择器变化 */
|
|
66
|
-
const onSelectChange = (e : any, item : FormSchema) => {
|
|
67
|
-
const index = e.detail.value as number
|
|
68
|
-
const value = item.options?.[index]?.value ?? ''
|
|
69
|
-
model.value[item.field] = value
|
|
70
|
-
validateField(item)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
/** 选择时间 */
|
|
75
|
-
const onTimeChange = (e : any, item : FormSchema) => {
|
|
76
|
-
model.value[item.field] = e.detail.value
|
|
77
|
-
validateField(item)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** 验证单个字段 */
|
|
81
|
-
const validateField = (item : FormSchema) => {
|
|
82
|
-
if (item.required && !model.value[item.field]) {
|
|
83
|
-
errors[item.field] = `请输入${item.label}`
|
|
84
|
-
} else {
|
|
85
|
-
delete errors[item.field]
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** 单选变化 */
|
|
90
|
-
const onRadioChange = (value : string | number, item : FormSchema) => {
|
|
91
|
-
model.value[item.field] = value
|
|
92
|
-
validateField(item)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** 多选变化 */
|
|
96
|
-
const onCheckboxChange = (values : (string | number)[], item : FormSchema) => {
|
|
97
|
-
model.value[item.field] = values
|
|
98
|
-
validateField(item)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** 提交表单 */
|
|
102
|
-
const onFormSubmit = async (e: UniFormSubmitEvent): Promise<{ valid: boolean, values: any }> => {
|
|
103
|
-
// 清空所有错误
|
|
104
|
-
Object.keys(errors).forEach(k => delete errors[k])
|
|
105
|
-
|
|
106
|
-
// 验证所有必填项
|
|
107
|
-
let hasError = false
|
|
108
|
-
for (const item of props.schemas) {
|
|
109
|
-
validateField(item)
|
|
110
|
-
if (item.required && !model.value[item.field]) {
|
|
111
|
-
hasError = true
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 如果有错误,阻止提交
|
|
116
|
-
if (hasError) {
|
|
117
|
-
console.warn('表单验证失败,存在必填项未填写')
|
|
118
|
-
return { valid: false, values: null }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ✅ 返回异步提交结果
|
|
122
|
-
try {
|
|
123
|
-
await emit('submit', model.value)
|
|
124
|
-
return { valid: true, values: model.value}
|
|
125
|
-
} catch (err) {
|
|
126
|
-
console.error('表单提交失败:', err)
|
|
127
|
-
return { valid: false, values: null }
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
/** 重置表单 */
|
|
133
|
-
const onFormReset = () => {
|
|
134
|
-
Object.keys(model.value).forEach(k => (model.value[k] = ''))
|
|
135
|
-
Object.keys(errors).forEach(k => delete errors[k])
|
|
136
|
-
emit('reset')
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 对外暴露的方法
|
|
140
|
-
defineExpose({
|
|
141
|
-
/** 提交表单 */
|
|
142
|
-
submit:async () => {
|
|
143
|
-
return await onFormSubmit({} as UniFormSubmitEvent)
|
|
144
|
-
},
|
|
145
|
-
/** 重置表单 */
|
|
146
|
-
reset: () => {
|
|
147
|
-
onFormReset()
|
|
148
|
-
},
|
|
149
|
-
/** 验证所有字段 */
|
|
150
|
-
validate: () => {
|
|
151
|
-
Object.keys(errors).forEach(k => delete errors[k])
|
|
152
|
-
let hasError = false
|
|
153
|
-
for (const item of props.schemas) {
|
|
154
|
-
validateField(item)
|
|
155
|
-
if (item.required && !model.value[item.field]) {
|
|
156
|
-
hasError = true
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return !hasError
|
|
160
|
-
},
|
|
161
|
-
/** 获取表单数据 */
|
|
162
|
-
getFormData: () => ({ ...model.value }),
|
|
163
|
-
/** 获取错误信息 */
|
|
164
|
-
getErrors: () => ({ ...errors })
|
|
165
|
-
})
|
|
166
|
-
</script>
|
|
167
|
-
|
|
168
|
-
<template>
|
|
169
|
-
<view class="vform-container" :style="cssVars">
|
|
170
|
-
<form @submit="onFormSubmit" @reset="onFormReset">
|
|
171
|
-
<view v-for="(item, index) in schemas" class="form-item-box" :key="index">
|
|
172
|
-
<view class="form-item">
|
|
173
|
-
<view class="label">
|
|
174
|
-
<text>{{ item.label }}</text>
|
|
175
|
-
<text v-if="item.required" class="required">*</text>
|
|
176
|
-
</view>
|
|
177
|
-
|
|
178
|
-
<view class="components">
|
|
179
|
-
<input v-if="item.component === 'input'" v-model="model[item.field]" :placeholder="hanldePlaceholder(item)"
|
|
180
|
-
:type="item.type || 'text'" :name="item.field" class="input" @input="validateField(item)" />
|
|
181
|
-
|
|
182
|
-
<!-- 文本域 -->
|
|
183
|
-
<textarea v-if="item.component === 'textarea'" v-model="model[item.field]"
|
|
184
|
-
:placeholder="hanldePlaceholder(item)" :name="item.field" class="textarea"
|
|
185
|
-
@input="validateField(item)"></textarea>
|
|
186
|
-
|
|
187
|
-
<!-- 下拉选择 -->
|
|
188
|
-
<picker v-else-if="item.component === 'select'" mode="selector" :name="item.field"
|
|
189
|
-
:range="hanldeRange(item.options)" @change="onSelectChange($event, item)">
|
|
190
|
-
<view class="select">
|
|
191
|
-
{{ getSelectLabel(item) || '请选择' }}
|
|
192
|
-
</view>
|
|
193
|
-
</picker>
|
|
194
|
-
|
|
195
|
-
<!-- 日期选择 -->
|
|
196
|
-
<picker v-else-if="item.component === 'date'" mode="date" :name="item.field" :value="model[item.field]"
|
|
197
|
-
@change="onTimeChange($event, item)">
|
|
198
|
-
<view class="select">
|
|
199
|
-
{{ model[item.field] || '请选择日期' }}
|
|
200
|
-
</view>
|
|
201
|
-
</picker>
|
|
202
|
-
</view>
|
|
203
|
-
</view>
|
|
204
|
-
<!-- 错误提示 -->
|
|
205
|
-
<view v-if="errors[item.field]" class="error-message">
|
|
206
|
-
<text>{{ errors[item.field] }}</text>
|
|
207
|
-
</view>
|
|
208
|
-
</view>
|
|
209
|
-
|
|
210
|
-
<!-- 默认按钮区域(可通过hideButtons隐藏) -->
|
|
211
|
-
<view v-if="!hideButtons" class="form-footer">
|
|
212
|
-
<button class="btn btn-submit" form-type="submit" type="primary">提交</button>
|
|
213
|
-
<button class="btn btn-reset" form-type="reset" type="default">重置</button>
|
|
214
|
-
</view>
|
|
215
|
-
</form>
|
|
216
|
-
</view>
|
|
217
|
-
</template>
|
|
218
|
-
|
|
219
|
-
<style lang="scss" scoped>
|
|
220
|
-
$color-primary: #14b8a6;
|
|
221
|
-
$color-secondary: #2dd4bf;
|
|
222
|
-
$color-accent: #5eead4;
|
|
223
|
-
$color-error: #ff6b6b;
|
|
224
|
-
$color-warning: #feca57;
|
|
225
|
-
|
|
226
|
-
$color-text: #333;
|
|
227
|
-
$color-border: rgba(0, 0, 0, 0.05);
|
|
228
|
-
$color-bg-light: rgba(255, 255, 255, 0.8);
|
|
229
|
-
$color-disabled: #e0e0e0;
|
|
230
|
-
$color-disabled-text: #999;
|
|
231
|
-
|
|
232
|
-
// 深色模式
|
|
233
|
-
$dark-bg: rgba(30, 30, 30, 0.95);
|
|
234
|
-
$dark-bg-sub: rgba(40, 40, 40, 0.8);
|
|
235
|
-
$dark-bg-item: rgba(50, 50, 50, 0.8);
|
|
236
|
-
$dark-border: rgba(255, 255, 255, 0.1);
|
|
237
|
-
$dark-text: #fff;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
.vform-container {
|
|
241
|
-
/* 移除默认背景和内边距,完全由父组件控制 */
|
|
242
|
-
background-color: transparent;
|
|
243
|
-
padding: 40rpx 32rpx 32rpx;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.form-item-box {
|
|
247
|
-
/* 移除默认布局,完全由自定义样式控制 */
|
|
248
|
-
align-items: flex-start;
|
|
249
|
-
width: 100%;
|
|
250
|
-
margin: 10rpx 0;
|
|
251
|
-
padding: 16rpx 24rpx;
|
|
252
|
-
border-radius: 16rpx;
|
|
253
|
-
border: 1rpx solid $color-border;
|
|
254
|
-
transition: all 0.3s ease;
|
|
255
|
-
|
|
256
|
-
&:focus-within {
|
|
257
|
-
background: #fff;
|
|
258
|
-
border-color: $color-primary ;
|
|
259
|
-
box-shadow: 0 0 0 4rpx rgba($color-primary, 0.1);
|
|
260
|
-
transform: translateY(-2rpx);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.form-item {
|
|
265
|
-
/* 移除默认布局,完全由自定义样式控制 */
|
|
266
|
-
flex-direction: row;
|
|
267
|
-
align-items: flex-start;
|
|
268
|
-
width: 100%;
|
|
269
|
-
|
|
270
|
-
border-radius: 16rpx;
|
|
271
|
-
background: $color-bg-light;
|
|
272
|
-
transition: all 0.3s ease;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
.label {
|
|
278
|
-
flex-direction: row;
|
|
279
|
-
margin-bottom: 12rpx;
|
|
280
|
-
color: $color-text ;
|
|
281
|
-
font-weight: 500;
|
|
282
|
-
font-size: 28rpx;
|
|
283
|
-
padding: 10rpx 0;
|
|
284
|
-
margin-right: 12rpx;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.required {
|
|
288
|
-
color: $color-error ;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
.components {
|
|
292
|
-
flex: 1;
|
|
293
|
-
justify-content: center
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.input,
|
|
297
|
-
.textarea,
|
|
298
|
-
.select {
|
|
299
|
-
/* 移除所有默认样式,完全由自定义样式控制 */
|
|
300
|
-
display: flex;
|
|
301
|
-
justify-content: center;
|
|
302
|
-
border: none;
|
|
303
|
-
padding: 0;
|
|
304
|
-
margin: 0;
|
|
305
|
-
font-size: inherit;
|
|
306
|
-
color: inherit;
|
|
307
|
-
border-radius: 0;
|
|
308
|
-
outline: none;
|
|
309
|
-
resize: none;
|
|
310
|
-
min-height: 34px;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/* 确保输入框样式完全被自定义样式覆盖 */
|
|
314
|
-
.input:focus,
|
|
315
|
-
.textarea:focus,
|
|
316
|
-
.select:focus {
|
|
317
|
-
width: 100%;
|
|
318
|
-
background: transparent;
|
|
319
|
-
border: none;
|
|
320
|
-
padding: 12rpx 0;
|
|
321
|
-
font-size: 30rpx;
|
|
322
|
-
color: $color-text ;
|
|
323
|
-
transition: all 0.3s ease;
|
|
324
|
-
border-bottom: 2rpx solid transparent;
|
|
325
|
-
text-align: left;
|
|
326
|
-
|
|
327
|
-
&:focus {
|
|
328
|
-
border-bottom-color: $color-primary ;
|
|
329
|
-
outline: none;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.radio-button-container,
|
|
334
|
-
.checkbox-button-container,
|
|
335
|
-
uni-picker {
|
|
336
|
-
width: 100%;
|
|
337
|
-
background: transparent;
|
|
338
|
-
border: none;
|
|
339
|
-
padding: 0;
|
|
340
|
-
text-align: left;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
uni-picker {
|
|
345
|
-
/* 移除默认样式 */
|
|
346
|
-
width: auto;
|
|
347
|
-
text-align: left;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
.form-footer {
|
|
351
|
-
flex-direction: row;
|
|
352
|
-
gap: 8px;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
.btn {
|
|
356
|
-
/* 移除默认按钮样式 */
|
|
357
|
-
flex: 1;
|
|
358
|
-
transition: all 0.3s ease;
|
|
359
|
-
|
|
360
|
-
&:active {
|
|
361
|
-
opacity: 0.7;
|
|
362
|
-
transform: scale(0.98);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
.btn-submit {
|
|
367
|
-
background-color: $color-primary;
|
|
368
|
-
color: $dark-text
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
.btn-reset {
|
|
372
|
-
border-width: 1px;
|
|
373
|
-
border-style: solid;
|
|
374
|
-
border-color: $color-primary;
|
|
375
|
-
color: $color-primary;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
.error-message {
|
|
379
|
-
margin-top: 8rpx;
|
|
380
|
-
text-align: left;
|
|
381
|
-
color: $color-error ;
|
|
382
|
-
font-size: 24rpx;
|
|
383
|
-
line-height: 1.4;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.error-message text {
|
|
387
|
-
color: $color-error;
|
|
388
|
-
font-size: 24rpx;
|
|
389
|
-
line-height: 1.4;
|
|
390
|
-
text-align: right;
|
|
391
|
-
}
|
|
392
|
-
</style>
|