hy-app 0.5.11 → 0.5.13
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-button/hy-button.vue +289 -275
- package/components/hy-button/index.scss +24 -1
- package/components/hy-button/props.ts +143 -135
- package/components/hy-button/typing.d.ts +35 -30
- package/components/hy-coupon/hy-coupon.vue +183 -172
- package/components/hy-coupon/index.scss +20 -23
- package/components/hy-coupon/props.ts +108 -103
- package/components/hy-form/hy-form.vue +220 -220
- package/components/hy-input/hy-input.vue +333 -333
- package/components/hy-input/index.scss +1 -2
- package/components/hy-input/props.ts +186 -186
- package/components/hy-notice-bar/hy-row-notice.vue +121 -121
- package/components/hy-parse/node/node.vue +619 -422
- package/components/hy-parse/parser.js +1253 -1060
- package/components/hy-table/hy-table.vue +579 -358
- package/components/hy-table/index.scss +134 -159
- package/components/hy-table/props.ts +62 -47
- package/components/hy-table/typing.d.ts +29 -34
- package/components/hy-tabs/hy-tabs.vue +335 -335
- package/components/hy-tag/index.scss +1 -0
- package/components/hy-text/hy-text.vue +237 -237
- package/components/hy-text/props.ts +115 -115
- package/components/hy-textarea/hy-textarea.vue +198 -197
- package/components/hy-textarea/index.scss +2 -3
- package/components/hy-toast/hy-toast.vue +190 -190
- package/libs/css/theme.scss +1 -0
- package/libs/css/vars.scss +2 -2
- package/libs/typing/modules/common.d.ts +139 -139
- package/libs/utils/inside.ts +336 -350
- package/package.json +2 -2
- package/web-types.json +1 -1
|
@@ -1,103 +1,108 @@
|
|
|
1
|
-
import type { CSSProperties, PropType } from 'vue'
|
|
2
|
-
import type { HyButtonProps } from '../hy-button/typing'
|
|
3
|
-
|
|
4
|
-
const couponProps = {
|
|
5
|
-
/** 优惠券标题 */
|
|
6
|
-
title: {
|
|
7
|
-
type: String,
|
|
8
|
-
default: ''
|
|
9
|
-
},
|
|
10
|
-
/**
|
|
11
|
-
* 优惠券类型:moneyOff:满减券,discount:折扣券,fixedAmount:无门槛券
|
|
12
|
-
* @values moneyOff,discount,fixedAmount
|
|
13
|
-
* */
|
|
14
|
-
type: {
|
|
15
|
-
type: String,
|
|
16
|
-
default: ''
|
|
17
|
-
},
|
|
18
|
-
/** 金额底部优惠券类型文字描述 */
|
|
19
|
-
typeText: {
|
|
20
|
-
type: String,
|
|
21
|
-
default: ''
|
|
22
|
-
},
|
|
23
|
-
/** 优惠券状态 */
|
|
24
|
-
status: {
|
|
25
|
-
type: String,
|
|
26
|
-
default: ''
|
|
27
|
-
},
|
|
28
|
-
/** 优惠券禁用状态 */
|
|
29
|
-
disabledStatus: {
|
|
30
|
-
type: Array,
|
|
31
|
-
default: () => ['']
|
|
32
|
-
},
|
|
33
|
-
/** 优惠券描述备注 */
|
|
34
|
-
description: {
|
|
35
|
-
type: String,
|
|
36
|
-
default: ''
|
|
37
|
-
},
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
type: [String, Number],
|
|
41
|
-
default: ''
|
|
42
|
-
},
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
type: String,
|
|
46
|
-
default: ''
|
|
47
|
-
},
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
type: String,
|
|
51
|
-
default: ''
|
|
52
|
-
},
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
type: String,
|
|
56
|
-
default: ''
|
|
57
|
-
},
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
type: String,
|
|
61
|
-
default: '
|
|
62
|
-
},
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
type: String,
|
|
66
|
-
default: ''
|
|
67
|
-
},
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
type: String,
|
|
71
|
-
default: ''
|
|
72
|
-
},
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
type:
|
|
76
|
-
default:
|
|
77
|
-
},
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
type: String,
|
|
89
|
-
default: '
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
type: Object as PropType<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
import type { CSSProperties, PropType } from 'vue'
|
|
2
|
+
import type { HyButtonProps } from '../hy-button/typing'
|
|
3
|
+
|
|
4
|
+
const couponProps = {
|
|
5
|
+
/** 优惠券标题 */
|
|
6
|
+
title: {
|
|
7
|
+
type: String,
|
|
8
|
+
default: ''
|
|
9
|
+
},
|
|
10
|
+
/**
|
|
11
|
+
* 优惠券类型:moneyOff:满减券,discount:折扣券,fixedAmount:无门槛券
|
|
12
|
+
* @values moneyOff,discount,fixedAmount
|
|
13
|
+
* */
|
|
14
|
+
type: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: ''
|
|
17
|
+
},
|
|
18
|
+
/** 金额底部优惠券类型文字描述 */
|
|
19
|
+
typeText: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: ''
|
|
22
|
+
},
|
|
23
|
+
/** 优惠券状态 */
|
|
24
|
+
status: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: ''
|
|
27
|
+
},
|
|
28
|
+
/** 优惠券禁用状态 */
|
|
29
|
+
disabledStatus: {
|
|
30
|
+
type: Array,
|
|
31
|
+
default: () => ['']
|
|
32
|
+
},
|
|
33
|
+
/** 优惠券描述备注 */
|
|
34
|
+
description: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: ''
|
|
37
|
+
},
|
|
38
|
+
/** 描述省略行数,none不省略,1代表一行省略 */
|
|
39
|
+
desEllipsis: {
|
|
40
|
+
type: [String, Number],
|
|
41
|
+
default: 'none'
|
|
42
|
+
},
|
|
43
|
+
/** 优惠券金额 */
|
|
44
|
+
amount: {
|
|
45
|
+
type: [String, Number],
|
|
46
|
+
default: ''
|
|
47
|
+
},
|
|
48
|
+
/** 优惠券单位,没有就用默认值 */
|
|
49
|
+
unit: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: ''
|
|
52
|
+
},
|
|
53
|
+
/** 优惠券开始时间 */
|
|
54
|
+
startDate: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: ''
|
|
57
|
+
},
|
|
58
|
+
/** 优惠券结束时间 */
|
|
59
|
+
endDate: {
|
|
60
|
+
type: String,
|
|
61
|
+
default: ''
|
|
62
|
+
},
|
|
63
|
+
/** 时间格式 */
|
|
64
|
+
format: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: 'yyyy-MM-dd'
|
|
67
|
+
},
|
|
68
|
+
/** 日期描述,没有日期描述就用开始时间到结束时间 */
|
|
69
|
+
dateDesc: {
|
|
70
|
+
type: String,
|
|
71
|
+
default: ''
|
|
72
|
+
},
|
|
73
|
+
/** 背景色 */
|
|
74
|
+
bgColor: {
|
|
75
|
+
type: String,
|
|
76
|
+
default: ''
|
|
77
|
+
},
|
|
78
|
+
/** 是否显示阴影 */
|
|
79
|
+
boxShadow: {
|
|
80
|
+
type: Boolean,
|
|
81
|
+
default: false
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* 按钮类型
|
|
85
|
+
* @values test,button,none
|
|
86
|
+
* */
|
|
87
|
+
btnMode: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: 'button'
|
|
90
|
+
},
|
|
91
|
+
/** 按钮文字 */
|
|
92
|
+
btnText: {
|
|
93
|
+
type: String,
|
|
94
|
+
default: '立即领取'
|
|
95
|
+
},
|
|
96
|
+
buttonProp: {
|
|
97
|
+
type: Object as PropType<HyButtonProps>,
|
|
98
|
+
default: () => ({})
|
|
99
|
+
},
|
|
100
|
+
/** 定义需要用到的外部样式 */
|
|
101
|
+
customStyle: {
|
|
102
|
+
type: Object as PropType<CSSProperties>
|
|
103
|
+
},
|
|
104
|
+
/** 自定义外部类名 */
|
|
105
|
+
customClass: String
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default couponProps
|
|
@@ -1,220 +1,220 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<view class="hy-form">
|
|
3
|
-
<slot></slot>
|
|
4
|
-
</view>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
export default {
|
|
9
|
-
name: 'hy-form',
|
|
10
|
-
options: {
|
|
11
|
-
addGlobalClass: true,
|
|
12
|
-
virtualHost: true,
|
|
13
|
-
styleIsolation: 'shared'
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<script setup lang="ts">
|
|
19
|
-
import { provide, reactive, ref, toRefs } from 'vue'
|
|
20
|
-
import { clearVal, isArray } from '../../libs'
|
|
21
|
-
import formProps from './props'
|
|
22
|
-
import type { IFormEmits } from './typing'
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 表单组件父组件,需要搭配hy-form-item
|
|
26
|
-
* @displayName hy-form
|
|
27
|
-
*/
|
|
28
|
-
defineOptions({})
|
|
29
|
-
|
|
30
|
-
const props = defineProps(formProps)
|
|
31
|
-
const emit = defineEmits<IFormEmits>()
|
|
32
|
-
|
|
33
|
-
// 表单数据
|
|
34
|
-
const formData = reactive(props.model || {})
|
|
35
|
-
const formItems = ref<any[]>([])
|
|
36
|
-
const errors = reactive<Record<string, string>>({})
|
|
37
|
-
|
|
38
|
-
// 表单上下文
|
|
39
|
-
const formContext = {
|
|
40
|
-
...toRefs(props),
|
|
41
|
-
formData,
|
|
42
|
-
errors,
|
|
43
|
-
addFormItem: (item: any) => {
|
|
44
|
-
formItems.value.push(item)
|
|
45
|
-
},
|
|
46
|
-
removeFormItem: (item: any) => {
|
|
47
|
-
const index = formItems.value.indexOf(item)
|
|
48
|
-
if (index > -1) {
|
|
49
|
-
formItems.value.splice(index, 1)
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
validateField: (field: string, value: any, trigger?: 'blur' | 'change') => {
|
|
53
|
-
const fieldRules = props.rules?.[field]
|
|
54
|
-
if (!fieldRules) return true
|
|
55
|
-
|
|
56
|
-
const rules = isArray(fieldRules) ? fieldRules : [fieldRules]
|
|
57
|
-
let isValid = true
|
|
58
|
-
let errorMessage = ''
|
|
59
|
-
|
|
60
|
-
for (const rule of rules) {
|
|
61
|
-
// 检查触发时机
|
|
62
|
-
if (
|
|
63
|
-
(trigger && !rule.trigger) ||
|
|
64
|
-
(trigger && rule.trigger && !rule.trigger.includes(trigger))
|
|
65
|
-
) {
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 必填校验
|
|
70
|
-
if (rule.required && (!value || value === '')) {
|
|
71
|
-
errorMessage = rule.message || `${field} 是必填项`
|
|
72
|
-
isValid = false
|
|
73
|
-
break
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// 长度校验
|
|
77
|
-
if (rule.min && String(value).length < rule.min) {
|
|
78
|
-
errorMessage = rule.message || `${field} 长度不能少于 ${rule.min} 个字符`
|
|
79
|
-
isValid = false
|
|
80
|
-
break
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (rule.max && String(value).length > rule.max) {
|
|
84
|
-
errorMessage = rule.message || `${field} 长度不能超过 ${rule.max} 个字符`
|
|
85
|
-
isValid = false
|
|
86
|
-
break
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// 类型校验
|
|
90
|
-
if (rule.type === 'phone') {
|
|
91
|
-
const phoneRegex = /^1[3-9]\d{9}$/
|
|
92
|
-
if (!phoneRegex.test(String(value))) {
|
|
93
|
-
errorMessage = rule.message || '请输入正确的手机号'
|
|
94
|
-
isValid = false
|
|
95
|
-
break
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (rule.type === 'email') {
|
|
100
|
-
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/
|
|
101
|
-
if (!emailRegex.test(String(value))) {
|
|
102
|
-
errorMessage = rule.message || '请输入正确的邮箱格式'
|
|
103
|
-
isValid = false
|
|
104
|
-
break
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (rule.type === 'password') {
|
|
109
|
-
const passwordRegex =
|
|
110
|
-
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]).{8,}$/
|
|
111
|
-
if (!passwordRegex.test(String(value))) {
|
|
112
|
-
errorMessage = rule.message || '密码至少8个字符,包含大小写字母、数字和特殊符号'
|
|
113
|
-
isValid = false
|
|
114
|
-
break
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// 自定义校验
|
|
119
|
-
if (rule.validator) {
|
|
120
|
-
const result = rule.validator(value)
|
|
121
|
-
if (result === false || typeof result === 'string') {
|
|
122
|
-
errorMessage =
|
|
123
|
-
typeof result === 'string' ? result : rule.message || `${field} 校验失败`
|
|
124
|
-
isValid = false
|
|
125
|
-
break
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (isValid) {
|
|
131
|
-
delete errors[field]
|
|
132
|
-
} else {
|
|
133
|
-
errors[field] = errorMessage
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return isValid
|
|
137
|
-
},
|
|
138
|
-
setFieldValue: (field: string, value: any) => {
|
|
139
|
-
formData[field] = value
|
|
140
|
-
},
|
|
141
|
-
getFieldValue: (field: string) => {
|
|
142
|
-
return formData[field]
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 提供表单上下文给子组件
|
|
147
|
-
provide('formContext', formContext)
|
|
148
|
-
|
|
149
|
-
// 验证所有字段
|
|
150
|
-
const validate = () => {
|
|
151
|
-
return new Promise((resolve, reject) => {
|
|
152
|
-
let isValid = true
|
|
153
|
-
const allErrors: Record<string, string> = {}
|
|
154
|
-
|
|
155
|
-
formItems.value.forEach((item) => {
|
|
156
|
-
const field = item.props?.prop
|
|
157
|
-
if (field) {
|
|
158
|
-
const value = formData[field]
|
|
159
|
-
const fieldValid = formContext.validateField(field, value)
|
|
160
|
-
if (!fieldValid) {
|
|
161
|
-
isValid = false
|
|
162
|
-
allErrors[field] = errors[field]
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
emit('validate', isValid, allErrors)
|
|
168
|
-
if (isValid) {
|
|
169
|
-
resolve(isValid)
|
|
170
|
-
} else {
|
|
171
|
-
reject(allErrors)
|
|
172
|
-
}
|
|
173
|
-
})
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// 重置表单
|
|
177
|
-
const resetFields = () => {
|
|
178
|
-
clearVal(formData)
|
|
179
|
-
Object.keys(errors).forEach((key) => {
|
|
180
|
-
delete errors[key]
|
|
181
|
-
})
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 清除验证
|
|
185
|
-
const clearValidate = (fields?: string[]) => {
|
|
186
|
-
if (fields) {
|
|
187
|
-
fields.forEach((field) => {
|
|
188
|
-
delete errors[field]
|
|
189
|
-
})
|
|
190
|
-
} else {
|
|
191
|
-
Object.keys(errors).forEach((key) => {
|
|
192
|
-
delete errors[key]
|
|
193
|
-
})
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// 提交表单
|
|
198
|
-
const submit = async () => {
|
|
199
|
-
if (await validate()) {
|
|
200
|
-
emit('submit', { ...formData })
|
|
201
|
-
return formData
|
|
202
|
-
}
|
|
203
|
-
return false
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
defineExpose({
|
|
207
|
-
validate,
|
|
208
|
-
resetFields,
|
|
209
|
-
clearValidate,
|
|
210
|
-
submit,
|
|
211
|
-
formData,
|
|
212
|
-
errors
|
|
213
|
-
})
|
|
214
|
-
</script>
|
|
215
|
-
|
|
216
|
-
<style lang="scss" scoped>
|
|
217
|
-
.hy-form {
|
|
218
|
-
width: 100%;
|
|
219
|
-
}
|
|
220
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<view class="hy-form">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</view>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
export default {
|
|
9
|
+
name: 'hy-form',
|
|
10
|
+
options: {
|
|
11
|
+
addGlobalClass: true,
|
|
12
|
+
virtualHost: true,
|
|
13
|
+
styleIsolation: 'shared'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { provide, reactive, ref, toRefs } from 'vue'
|
|
20
|
+
import { clearVal, isArray } from '../../libs'
|
|
21
|
+
import formProps from './props'
|
|
22
|
+
import type { IFormEmits } from './typing'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 表单组件父组件,需要搭配hy-form-item
|
|
26
|
+
* @displayName hy-form
|
|
27
|
+
*/
|
|
28
|
+
defineOptions({})
|
|
29
|
+
|
|
30
|
+
const props = defineProps(formProps)
|
|
31
|
+
const emit = defineEmits<IFormEmits>()
|
|
32
|
+
|
|
33
|
+
// 表单数据
|
|
34
|
+
const formData = reactive(props.model || {})
|
|
35
|
+
const formItems = ref<any[]>([])
|
|
36
|
+
const errors = reactive<Record<string, string>>({})
|
|
37
|
+
|
|
38
|
+
// 表单上下文
|
|
39
|
+
const formContext = {
|
|
40
|
+
...toRefs(props),
|
|
41
|
+
formData,
|
|
42
|
+
errors,
|
|
43
|
+
addFormItem: (item: any) => {
|
|
44
|
+
formItems.value.push(item)
|
|
45
|
+
},
|
|
46
|
+
removeFormItem: (item: any) => {
|
|
47
|
+
const index = formItems.value.indexOf(item)
|
|
48
|
+
if (index > -1) {
|
|
49
|
+
formItems.value.splice(index, 1)
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
validateField: (field: string, value: any, trigger?: 'blur' | 'change') => {
|
|
53
|
+
const fieldRules = props.rules?.[field]
|
|
54
|
+
if (!fieldRules) return true
|
|
55
|
+
|
|
56
|
+
const rules = isArray(fieldRules) ? fieldRules : [fieldRules]
|
|
57
|
+
let isValid = true
|
|
58
|
+
let errorMessage = ''
|
|
59
|
+
|
|
60
|
+
for (const rule of rules) {
|
|
61
|
+
// 检查触发时机
|
|
62
|
+
if (
|
|
63
|
+
(trigger && !rule.trigger) ||
|
|
64
|
+
(trigger && rule.trigger && !rule.trigger.includes(trigger))
|
|
65
|
+
) {
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 必填校验
|
|
70
|
+
if (rule.required && (!value || value === '')) {
|
|
71
|
+
errorMessage = rule.message || `${field} 是必填项`
|
|
72
|
+
isValid = false
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 长度校验
|
|
77
|
+
if (rule.min && String(value).length < rule.min) {
|
|
78
|
+
errorMessage = rule.message || `${field} 长度不能少于 ${rule.min} 个字符`
|
|
79
|
+
isValid = false
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (rule.max && String(value).length > rule.max) {
|
|
84
|
+
errorMessage = rule.message || `${field} 长度不能超过 ${rule.max} 个字符`
|
|
85
|
+
isValid = false
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 类型校验
|
|
90
|
+
if (rule.type === 'phone') {
|
|
91
|
+
const phoneRegex = /^1[3-9]\d{9}$/
|
|
92
|
+
if (!phoneRegex.test(String(value))) {
|
|
93
|
+
errorMessage = rule.message || '请输入正确的手机号'
|
|
94
|
+
isValid = false
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (rule.type === 'email') {
|
|
100
|
+
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/
|
|
101
|
+
if (!emailRegex.test(String(value))) {
|
|
102
|
+
errorMessage = rule.message || '请输入正确的邮箱格式'
|
|
103
|
+
isValid = false
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (rule.type === 'password') {
|
|
109
|
+
const passwordRegex =
|
|
110
|
+
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]).{8,}$/
|
|
111
|
+
if (!passwordRegex.test(String(value))) {
|
|
112
|
+
errorMessage = rule.message || '密码至少8个字符,包含大小写字母、数字和特殊符号'
|
|
113
|
+
isValid = false
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 自定义校验
|
|
119
|
+
if (rule.validator) {
|
|
120
|
+
const result = rule.validator(value)
|
|
121
|
+
if (result === false || typeof result === 'string') {
|
|
122
|
+
errorMessage =
|
|
123
|
+
typeof result === 'string' ? result : rule.message || `${field} 校验失败`
|
|
124
|
+
isValid = false
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (isValid) {
|
|
131
|
+
delete errors[field]
|
|
132
|
+
} else {
|
|
133
|
+
errors[field] = errorMessage
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return isValid
|
|
137
|
+
},
|
|
138
|
+
setFieldValue: (field: string, value: any) => {
|
|
139
|
+
formData[field] = value
|
|
140
|
+
},
|
|
141
|
+
getFieldValue: (field: string) => {
|
|
142
|
+
return formData[field]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 提供表单上下文给子组件
|
|
147
|
+
provide('formContext', formContext)
|
|
148
|
+
|
|
149
|
+
// 验证所有字段
|
|
150
|
+
const validate = () => {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
let isValid = true
|
|
153
|
+
const allErrors: Record<string, string> = {}
|
|
154
|
+
|
|
155
|
+
formItems.value.forEach((item) => {
|
|
156
|
+
const field = item.props?.prop
|
|
157
|
+
if (field) {
|
|
158
|
+
const value = formData[field]
|
|
159
|
+
const fieldValid = formContext.validateField(field, value)
|
|
160
|
+
if (!fieldValid) {
|
|
161
|
+
isValid = false
|
|
162
|
+
allErrors[field] = errors[field]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
emit('validate', isValid, allErrors)
|
|
168
|
+
if (isValid) {
|
|
169
|
+
resolve(isValid)
|
|
170
|
+
} else {
|
|
171
|
+
reject(allErrors)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 重置表单
|
|
177
|
+
const resetFields = () => {
|
|
178
|
+
clearVal(formData)
|
|
179
|
+
Object.keys(errors).forEach((key) => {
|
|
180
|
+
delete errors[key]
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 清除验证
|
|
185
|
+
const clearValidate = (fields?: string[]) => {
|
|
186
|
+
if (fields) {
|
|
187
|
+
fields.forEach((field) => {
|
|
188
|
+
delete errors[field]
|
|
189
|
+
})
|
|
190
|
+
} else {
|
|
191
|
+
Object.keys(errors).forEach((key) => {
|
|
192
|
+
delete errors[key]
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 提交表单
|
|
198
|
+
const submit = async () => {
|
|
199
|
+
if (await validate()) {
|
|
200
|
+
emit('submit', { ...formData })
|
|
201
|
+
return formData
|
|
202
|
+
}
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
defineExpose({
|
|
207
|
+
validate,
|
|
208
|
+
resetFields,
|
|
209
|
+
clearValidate,
|
|
210
|
+
submit,
|
|
211
|
+
formData,
|
|
212
|
+
errors
|
|
213
|
+
})
|
|
214
|
+
</script>
|
|
215
|
+
|
|
216
|
+
<style lang="scss" scoped>
|
|
217
|
+
.hy-form {
|
|
218
|
+
width: 100%;
|
|
219
|
+
}
|
|
220
|
+
</style>
|