hy-app 0.2.18 → 0.3.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/hy-form/README.md +301 -0
- package/components/hy-form/hy-form.vue +252 -533
- package/components/hy-form/index.ts +5 -0
- package/components/hy-form/typing.d.ts +57 -77
- package/components/hy-form-group/hy-form-group.vue +533 -0
- package/components/hy-form-group/typing.d.ts +77 -0
- package/components/hy-form-item/hy-form-item.vue +199 -0
- package/components/hy-form-item/index.scss +41 -0
- package/components/hy-form-item/typing.d.ts +43 -0
- package/components/hy-input/hy-input.vue +10 -5
- package/components/hy-login/TheUserLogin.vue +82 -90
- package/components/hy-swipe-action/hy-swipe-action.vue +13 -6
- package/components/hy-swipe-action/typing.d.ts +1 -1
- package/components/hy-textarea/hy-textarea.vue +9 -7
- package/components/hy-textarea/typing.d.ts +1 -1
- package/components/index.ts +7 -1
- package/global.d.ts +3 -1
- package/libs/css/mixin.scss +2 -1
- package/package.json +2 -2
- package/utils/inspect.ts +33 -33
- package/utils/utils.ts +1 -19
- package/web-types.json +1 -1
- /package/components/{hy-form → hy-form-group}/index.scss +0 -0
- /package/components/{hy-form → hy-form-group}/props.ts +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { FormColumnsType } from "../../typing";
|
|
2
|
+
import type HyInputProps from "../hy-input/typing";
|
|
3
|
+
import type HyTextareaProps from "../hy-textarea/typing";
|
|
4
|
+
import type HySwitchProps from "../hy-switch/typing";
|
|
5
|
+
import type HyPickerProps from "../hy-picker/typing";
|
|
6
|
+
import type HyRadioProps from "../hy-radio/typing";
|
|
7
|
+
import type HyCheckButtonProps from "../hy-check-button/typing";
|
|
8
|
+
|
|
9
|
+
export default interface HyFormProps {
|
|
10
|
+
/**
|
|
11
|
+
* @description 表单域提示文字的位置
|
|
12
|
+
* left - 左侧
|
|
13
|
+
* top - 上方
|
|
14
|
+
* */
|
|
15
|
+
labelPosition?: "left" | "top";
|
|
16
|
+
/**
|
|
17
|
+
* @description label宽度
|
|
18
|
+
* 数字 - 固定值
|
|
19
|
+
* auto - 自适应
|
|
20
|
+
* */
|
|
21
|
+
labelWidth?: string | number;
|
|
22
|
+
/**
|
|
23
|
+
* @description 是否右对齐
|
|
24
|
+
* */
|
|
25
|
+
right?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* @description label字体的对齐方式
|
|
28
|
+
* left - 左对齐
|
|
29
|
+
* center - 中间对齐
|
|
30
|
+
* right - 右对齐
|
|
31
|
+
* */
|
|
32
|
+
labelAlign?: HyApp.RowCenterType;
|
|
33
|
+
/**
|
|
34
|
+
* @description 显示冒号符号
|
|
35
|
+
* */
|
|
36
|
+
symbol?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* @description 显示底部下划线
|
|
39
|
+
* */
|
|
40
|
+
borderBottom?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* @description 当行内容高度
|
|
43
|
+
* */
|
|
44
|
+
itemHeight?: number | string;
|
|
45
|
+
/**
|
|
46
|
+
* @description 输入框属性api集合
|
|
47
|
+
* */
|
|
48
|
+
input?: Partial<HyInputProps>;
|
|
49
|
+
/**
|
|
50
|
+
* @description 文本域属性api集合
|
|
51
|
+
* */
|
|
52
|
+
textarea?: Partial<HyTextareaProps>;
|
|
53
|
+
/**
|
|
54
|
+
* @description 选择器属性api集合
|
|
55
|
+
* */
|
|
56
|
+
picker?: Partial<HyPickerProps>;
|
|
57
|
+
/**
|
|
58
|
+
* @description 开关属性api集合
|
|
59
|
+
* */
|
|
60
|
+
switchItem?: Partial<HySwitchProps>;
|
|
61
|
+
/**
|
|
62
|
+
* @description 单选属性api集合
|
|
63
|
+
* */
|
|
64
|
+
radio?: Partial<HyRadioProps>;
|
|
65
|
+
/**
|
|
66
|
+
* @description 选择按钮属性api集合
|
|
67
|
+
* */
|
|
68
|
+
checkButton?: Partial<HyCheckButtonProps>;
|
|
69
|
+
/**
|
|
70
|
+
* @description 表单配置
|
|
71
|
+
* */
|
|
72
|
+
columns: FormColumnsType[];
|
|
73
|
+
/**
|
|
74
|
+
* @description 表单值
|
|
75
|
+
* */
|
|
76
|
+
formData: Record<string, any>;
|
|
77
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view class="hy-form-item" :class="[`hy-form-item--${labelPosition}`]">
|
|
3
|
+
<view v-if="label" class="hy-form-item__label" :style="labelStyle">
|
|
4
|
+
<text v-if="isRequired" class="hy-form-item__label--required">*</text>
|
|
5
|
+
{{ label }}
|
|
6
|
+
</view>
|
|
7
|
+
<view class="hy-form-item__content">
|
|
8
|
+
<slot></slot>
|
|
9
|
+
<view v-if="errorMessage" class="hy-form-item__error">
|
|
10
|
+
{{ errorMessage }}
|
|
11
|
+
</view>
|
|
12
|
+
</view>
|
|
13
|
+
</view>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
export default {
|
|
18
|
+
name: 'hy-form-item',
|
|
19
|
+
options: {
|
|
20
|
+
addGlobalClass: true,
|
|
21
|
+
virtualHost: true,
|
|
22
|
+
styleIsolation: 'shared',
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { computed, inject, onMounted, onUnmounted, type PropType, provide, ref } from 'vue'
|
|
29
|
+
import type { FormContext } from './typing'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 表单组件子组件,需要搭配hy-form
|
|
33
|
+
* @displayName hy-form-item
|
|
34
|
+
*/
|
|
35
|
+
defineOptions({})
|
|
36
|
+
|
|
37
|
+
const props = defineProps({
|
|
38
|
+
/**
|
|
39
|
+
* 标签文本
|
|
40
|
+
*/
|
|
41
|
+
label: String,
|
|
42
|
+
/**
|
|
43
|
+
* 表单字段名
|
|
44
|
+
*/
|
|
45
|
+
prop: String,
|
|
46
|
+
/**
|
|
47
|
+
* 是否必填
|
|
48
|
+
*/
|
|
49
|
+
required: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
default: false,
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* 验证规则
|
|
55
|
+
*/
|
|
56
|
+
rules: Object as PropType<any>,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const emit = defineEmits<{
|
|
60
|
+
change: [value: any]
|
|
61
|
+
blur: [value: any]
|
|
62
|
+
}>()
|
|
63
|
+
|
|
64
|
+
// 注入表单上下文
|
|
65
|
+
const formContext = inject<FormContext>('formContext')
|
|
66
|
+
const formItem = {
|
|
67
|
+
// 处理子组件事件
|
|
68
|
+
handleChange(value: any) {
|
|
69
|
+
if (props.prop && formContext) {
|
|
70
|
+
formContext.setFieldValue(props.prop, value)
|
|
71
|
+
validate('change')
|
|
72
|
+
}
|
|
73
|
+
emit('change', value)
|
|
74
|
+
},
|
|
75
|
+
handleBlur(value: any) {
|
|
76
|
+
if (props.prop && formContext) {
|
|
77
|
+
validate('blur')
|
|
78
|
+
}
|
|
79
|
+
emit('blur', value)
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
provide('formItem', formItem)
|
|
83
|
+
|
|
84
|
+
// 当前组件的引用
|
|
85
|
+
const formItemRef = ref()
|
|
86
|
+
|
|
87
|
+
// 错误信息
|
|
88
|
+
const errorMessage = computed(() => {
|
|
89
|
+
if (!formContext || !props.prop) return ''
|
|
90
|
+
return formContext.errors[props.prop] || ''
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// 是否必填
|
|
94
|
+
const isRequired = computed(() => {
|
|
95
|
+
if (props.required) return true
|
|
96
|
+
if (!formContext || !props.prop) return false
|
|
97
|
+
|
|
98
|
+
const fieldRules = formContext.rules.value?.[props.prop]
|
|
99
|
+
if (!fieldRules) return false
|
|
100
|
+
|
|
101
|
+
const rules = Array.isArray(fieldRules) ? fieldRules : [fieldRules]
|
|
102
|
+
return rules.some((rule) => rule.required)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// 标签样式
|
|
106
|
+
const labelStyle = computed(() => {
|
|
107
|
+
if (!formContext) return {}
|
|
108
|
+
|
|
109
|
+
const style: Record<string, any> = {}
|
|
110
|
+
|
|
111
|
+
if (formContext.labelWidth.value !== 'auto') {
|
|
112
|
+
style.width =
|
|
113
|
+
typeof formContext.labelWidth.value === 'number'
|
|
114
|
+
? `${formContext.labelWidth.value}px`
|
|
115
|
+
: formContext.labelWidth.value
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (formContext.labelAlign.value) {
|
|
119
|
+
style.textAlign = formContext.labelAlign.value
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return style
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// 标签位置
|
|
126
|
+
const labelPosition = computed(() => {
|
|
127
|
+
return formContext?.labelPosition?.value || 'left'
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// 监听表单数据变化
|
|
131
|
+
// watch(
|
|
132
|
+
// () => formContext?.formData[props.prop],
|
|
133
|
+
// (newVal) => {
|
|
134
|
+
// if (props.prop && formContext) {
|
|
135
|
+
// formContext.setFieldValue(props.prop, newVal)
|
|
136
|
+
// validate('change')
|
|
137
|
+
// }
|
|
138
|
+
// },
|
|
139
|
+
// { immediate: true },
|
|
140
|
+
// )
|
|
141
|
+
|
|
142
|
+
// 验证字段
|
|
143
|
+
const validate = (trigger?: 'blur' | 'change') => {
|
|
144
|
+
if (!formContext || !props.prop) return true
|
|
145
|
+
|
|
146
|
+
const value = formContext.getFieldValue(props.prop)
|
|
147
|
+
return formContext.validateField(props.prop, value, trigger)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 重置字段
|
|
151
|
+
const resetField = () => {
|
|
152
|
+
if (!formContext || !props.prop) return
|
|
153
|
+
|
|
154
|
+
formContext.setFieldValue(props.prop, undefined)
|
|
155
|
+
formContext.validateField(props.prop, undefined)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 清除验证
|
|
159
|
+
const clearValidate = () => {
|
|
160
|
+
if (!formContext || !props.prop) return
|
|
161
|
+
|
|
162
|
+
delete formContext.errors[props.prop]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 组件挂载时注册到表单
|
|
166
|
+
onMounted(() => {
|
|
167
|
+
if (formContext) {
|
|
168
|
+
formContext.addFormItem({
|
|
169
|
+
props: props,
|
|
170
|
+
validate,
|
|
171
|
+
resetField,
|
|
172
|
+
clearValidate,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// 组件卸载时从表单中移除
|
|
178
|
+
onUnmounted(() => {
|
|
179
|
+
if (formContext) {
|
|
180
|
+
formContext.removeFormItem({
|
|
181
|
+
props: props,
|
|
182
|
+
validate,
|
|
183
|
+
resetField,
|
|
184
|
+
clearValidate,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// 暴露方法给父组件
|
|
190
|
+
defineExpose({
|
|
191
|
+
validate,
|
|
192
|
+
resetField,
|
|
193
|
+
clearValidate,
|
|
194
|
+
})
|
|
195
|
+
</script>
|
|
196
|
+
|
|
197
|
+
<style lang="scss" scoped>
|
|
198
|
+
@import './index.scss';
|
|
199
|
+
</style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
@use "../../theme.scss" as *;
|
|
2
|
+
@use "../../libs/css/mixin.scss" as *;
|
|
3
|
+
|
|
4
|
+
@include b(form-item) {
|
|
5
|
+
display: flex;
|
|
6
|
+
margin-bottom: 16px;
|
|
7
|
+
|
|
8
|
+
@include m(left) {
|
|
9
|
+
flex-direction: row;
|
|
10
|
+
align-items: flex-start;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@include m(top) {
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@include e(label) {
|
|
18
|
+
position: relative;
|
|
19
|
+
padding-right: 8px;
|
|
20
|
+
font-size: 14px;
|
|
21
|
+
color: #333;
|
|
22
|
+
line-height: 1.5;
|
|
23
|
+
|
|
24
|
+
@include m(required) {
|
|
25
|
+
color: #ff4d4f;
|
|
26
|
+
margin-right: 2px;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@include e(content) {
|
|
31
|
+
flex: 1;
|
|
32
|
+
min-width: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@include e(error) {
|
|
36
|
+
margin-top: 4px;
|
|
37
|
+
font-size: 12px;
|
|
38
|
+
color: #ff4d4f;
|
|
39
|
+
line-height: 1.5;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface FormContext {
|
|
2
|
+
formData: Record<string, any>
|
|
3
|
+
errors: Record<string, string>
|
|
4
|
+
rules: any
|
|
5
|
+
labelWidth: any
|
|
6
|
+
labelPosition: any
|
|
7
|
+
labelAlign: any
|
|
8
|
+
addFormItem: (item: any) => void
|
|
9
|
+
removeFormItem: (item: any) => void
|
|
10
|
+
validateField: (field: string, value: any, trigger?: 'blur' | 'change') => boolean
|
|
11
|
+
setFieldValue: (field: string, value: any) => void
|
|
12
|
+
getFieldValue: (field: string) => any
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FormItemContext {
|
|
16
|
+
/**
|
|
17
|
+
* 失去焦点触发表单校验
|
|
18
|
+
* */
|
|
19
|
+
handleBlur: (value: string | number) => void
|
|
20
|
+
/**
|
|
21
|
+
* 值改变触发表单校验
|
|
22
|
+
* */
|
|
23
|
+
handleChange: (value: string | number) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default interface HyFormItemProps {
|
|
27
|
+
/**
|
|
28
|
+
* 标签文本
|
|
29
|
+
*/
|
|
30
|
+
label?: string
|
|
31
|
+
/**
|
|
32
|
+
* 表单字段名
|
|
33
|
+
*/
|
|
34
|
+
prop?: string
|
|
35
|
+
/**
|
|
36
|
+
* 是否必填
|
|
37
|
+
*/
|
|
38
|
+
required?: boolean
|
|
39
|
+
/**
|
|
40
|
+
* 验证规则
|
|
41
|
+
*/
|
|
42
|
+
rules?: any
|
|
43
|
+
}
|
|
@@ -103,13 +103,15 @@ export default {
|
|
|
103
103
|
</script>
|
|
104
104
|
|
|
105
105
|
<script setup lang="ts">
|
|
106
|
-
import { computed, nextTick, ref, toRefs, watch, getCurrentInstance,
|
|
107
|
-
import type { CSSProperties } from 'vue'
|
|
106
|
+
import { computed, nextTick, ref, toRefs, watch, getCurrentInstance, inject } from 'vue'
|
|
107
|
+
import type { CSSProperties, PropType } from 'vue'
|
|
108
108
|
import HyIcon from '../hy-icon/hy-icon.vue'
|
|
109
109
|
import { addUnit, formatObject } from '../../utils'
|
|
110
110
|
import { IconConfig } from '../../config'
|
|
111
111
|
import type { IInputEmits } from './typing'
|
|
112
|
-
import HyIconProps from '
|
|
112
|
+
import type HyIconProps from '../hy-icon/typing'
|
|
113
|
+
import { FormItemContext } from '@/package/components/hy-form-item/typing'
|
|
114
|
+
import { TextAlign } from 'csstype'
|
|
113
115
|
|
|
114
116
|
/**
|
|
115
117
|
* 为一个输入框,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
|
|
@@ -236,7 +238,7 @@ const props = defineProps({
|
|
|
236
238
|
* @values left,center,right
|
|
237
239
|
* */
|
|
238
240
|
inputAlign: {
|
|
239
|
-
type: String
|
|
241
|
+
type: String as PropType<TextAlign>,
|
|
240
242
|
default: 'left',
|
|
241
243
|
},
|
|
242
244
|
/** 输入框字体的大小 */
|
|
@@ -303,6 +305,7 @@ const {
|
|
|
303
305
|
placeholderStyle,
|
|
304
306
|
} = toRefs(props)
|
|
305
307
|
const emit = defineEmits<IInputEmits>()
|
|
308
|
+
const formItem = inject<FormItemContext>('formItem')
|
|
306
309
|
|
|
307
310
|
const instance = getCurrentInstance()
|
|
308
311
|
// 清除操作
|
|
@@ -424,8 +427,9 @@ const onInput = (e: any) => {
|
|
|
424
427
|
/**
|
|
425
428
|
* @description 输入框失去焦点时触发
|
|
426
429
|
* */
|
|
427
|
-
const onBlur = (event:
|
|
430
|
+
const onBlur = (event: any) => {
|
|
428
431
|
emit('blur', event.detail.value)
|
|
432
|
+
formItem.handleBlur(event.detail.value)
|
|
429
433
|
// H5端的blur会先于点击清除控件的点击click事件触发,导致focused
|
|
430
434
|
// 瞬间为false,从而隐藏了清除控件而无法被点击到
|
|
431
435
|
setTimeout(() => {
|
|
@@ -466,6 +470,7 @@ const valueChange = (value: string | number, isOut = false) => {
|
|
|
466
470
|
// 标识value值的变化是由内部引起的
|
|
467
471
|
changeFromInner.value = true
|
|
468
472
|
emit('change', value)
|
|
473
|
+
formItem.handleChange(value)
|
|
469
474
|
|
|
470
475
|
emit('update:modelValue', value)
|
|
471
476
|
}
|