feat: Form 表单 validate 方法支持传入数组 (#829)

 Closes: #797
This commit is contained in:
不如摸鱼去 2025-01-13 23:55:07 +08:00 committed by GitHub
parent 8d528cb9c0
commit 8e6096ab74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 194 additions and 27 deletions

View File

@ -1,4 +1,4 @@
# Form 表单 <el-tag text style="vertical-align: middle;margin-left:8px;" effect="plain">0.2.0</el-tag>
# Form 表单
用于数据录入、校验,支持输入框、单选框、复选框、文件上传等类型,常见的 form 表单为`单元格`形式的展示,即左侧为表单的标题描述,右侧为表单的输入。
@ -394,7 +394,7 @@ const submit = () => {
## 指定字段校验
`validate` 方法可以传入一个 `prop` 参数,指定校验的字段,可以实现在表单组件的`blur``change`等事件触发时对该字段的校验。
`validate` 方法可以传入一个 `prop` 参数,指定校验的字段,可以实现在表单组件的`blur``change`等事件触发时对该字段的校验。`prop` 参数也可以是一个字段数组,指定多个字段进行校验。
::: details 查看指定字段校验示例
::: code-group
@ -424,6 +424,7 @@ const submit = () => {
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
<wd-button type="primary" size="large" @click="handleValidate" block>校验用户名和密码</wd-button>
</view>
</wd-form>
```
@ -459,6 +460,85 @@ function handleSubmit() {
console.log(error, 'error')
})
}
function handleValidate() {
form
.value!.validate(['value1', 'value2'])
.then(({ valid, errors }) => {
if (valid) {
showSuccess({
msg: '校验通过'
})
}
})
.catch((error) => {
console.log(error, 'error')
})
}
</script>
```
```css [css]
.footer {
padding: 12px;
}
```
:::
## 不对隐藏组件做校验
在表单中,如果某个组件使用 `v-if` 隐藏,则不会对该组件进行校验。
::: details 查看不对隐藏组件做校验示例
::: code-group
```html [vue]
<wd-form ref="form" :model="model" :rules="rules">
<wd-cell-group border>
<wd-input
label="用户名"
label-width="100px"
prop="value1"
clearable
v-model="model.value1"
placeholder="请输入用户名"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<wd-input
v-if="showPassword"
label="密码"
label-width="100px"
prop="value2"
show-password
clearable
v-model="model.value2"
placeholder="请输入密码"
:rules="[{ required: true, message: '请填写密码' }]"
/>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
</view>
</wd-form>
```
```typescript [typescript]
<script lang="ts" setup>
import { useToast } from '@/uni_modules/wot-design-uni'
import type { FormInstance } from '@/uni_modules/wot-design-uni/components/wd-form/types'
import { reactive, ref } from 'vue'
const { success: showSuccess } = useToast()
const model = reactive<{
value1: string
value2: string
}>({
value1: '',
value2: ''
})
})
}
</script>
```
@ -938,7 +1018,7 @@ function handleIconClick() {
| 事件名称 | 说明 | 参数 | 最低版本 |
| -------- | ------------------------------------------------------------------------------ | --------------- | -------- |
| validate | 验证表单,支持传入一个 prop 来验证单个表单项,不传入 prop 时,会验证所有表单项 | `prop?: string` | 0.2.0 |
| validate | 验证表单,支持传入一个 prop 来验证单个表单项,不传入 prop 时,会验证所有表单项$LOWEST_VERSION$ 版本起支持传入数组 | `prop?: string\|string[]` | 0.2.0 |
| reset | 重置校验结果 | - | 0.2.0 |
## 外部样式类

View File

@ -1,3 +1,12 @@
<!--
* @Author: weisheng
* @Date: 2024-11-09 12:35:25
* @LastEditTime: 2025-01-13 23:46:45
* @LastEditors: weisheng
* @Description:
* @FilePath: /wot-design-uni/src/pages/form/demo2.vue
* 记得注释
-->
<template>
<page-wraper>
<wd-form ref="form" :model="model" :reset-on-change="false">
@ -22,11 +31,23 @@
placeholder="玛卡巴卡单号"
:rules="[{ required: true, message: '请填写玛卡巴卡单号' }]"
/>
<wd-input
label="玛卡巴卡id"
prop="id"
label-width="100px"
clearable
@blur="handleBlur('id')"
v-model="model.id"
placeholder="玛卡巴卡id"
:rules="[{ required: true, message: '请填写玛卡巴卡id' }]"
/>
</wd-cell-group>
</wd-form>
<view class="footer">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
<wd-button type="primary" @click="handleSubmit">提交</wd-button>
<wd-button type="primary" @click="handleValidate">校验单号和ID</wd-button>
</view>
</page-wraper>
</template>
@ -38,9 +59,11 @@ import { reactive, ref } from 'vue'
const model = reactive<{
name: string
phoneNumber: string
id: string
}>({
name: '',
phoneNumber: ''
phoneNumber: '',
id: ''
})
const { success: showSuccess } = useToast()
@ -50,6 +73,19 @@ function handleBlur(prop: string) {
form.value!.validate(prop)
}
function handleValidate() {
form
.value!.validate(['phoneNumber', 'id'])
.then(({ valid }) => {
if (valid) {
showSuccess('校验通过')
}
})
.catch((error) => {
console.log(error, 'error')
})
}
function handleSubmit() {
form
.value!.validate()
@ -66,5 +102,6 @@ function handleSubmit() {
<style lang="scss" scoped>
.footer {
padding: 12px;
display: flex;
}
</style>

View File

@ -2,7 +2,6 @@
<view>
<page-wraper>
<wd-message-box />
<wd-toast />
<wd-form ref="form" :model="model" :rules="rules">
<wd-cell-group custom-class="group" title="基础信息" border>
<wd-input
@ -89,6 +88,15 @@
<wd-switch v-model="model.switchVal" />
</view>
</wd-cell>
<wd-input
label="折扣"
v-if="model.switchVal"
label-width="100px"
prop="discount"
placeholder="请输入优惠金额"
clearable
v-model="model.discount"
/>
<wd-input
label="歪比巴卜"
label-width="100px"
@ -100,7 +108,11 @@
/>
<wd-input label="玛卡巴卡" label-width="100px" prop="phone" placeholder="请输入玛卡巴卡" clearable v-model="model.phone" />
<wd-cell title="活动图片" title-width="100px" prop="fileList">
<wd-upload :file-list="model.fileList" action="https://ftf.jd.com/api/uploadImg" @change="handleFileChange"></wd-upload>
<wd-upload
:file-list="model.fileList"
action="https://mockapi.eolink.com/zhTuw2P8c29bc981a741931bdd86eb04dc1e8fd64865cb5/upload"
@change="handleFileChange"
></wd-upload>
</wd-cell>
</wd-cell-group>
<view class="tip">
@ -144,6 +156,7 @@ const model = reactive<{
phone: string
read: boolean
fileList: UploadFileItem[]
discount: number
}>({
couponName: '',
platform: [],
@ -159,7 +172,8 @@ const model = reactive<{
cardId: '',
phone: '',
read: false,
fileList: []
fileList: [],
discount: 1
})
const rules: FormRules = {
@ -319,6 +333,19 @@ const rules: FormRules = {
}
}
}
],
discount: [
{
required: true,
message: '请输入优惠金额',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject()
}
}
}
]
}
@ -397,6 +424,9 @@ function handleSubmit() {
form
.value!.validate()
.then(({ valid, errors }) => {
if (valid) {
toast.success('提交成功')
}
console.log(valid)
console.log(errors)
})

View File

@ -1,10 +1,10 @@
/*
* @Author: weisheng
* @Date: 2023-12-14 11:21:58
* @LastEditTime: 2024-03-18 12:50:41
* @LastEditTime: 2025-01-11 13:31:20
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-form\types.ts
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-form/types.ts
*
*/
import { type ComponentPublicInstance, type ExtractPropTypes, type InjectionKey, type PropType } from 'vue'
@ -72,7 +72,7 @@ export type FormExpose = {
*
* @param prop
*/
validate: (prop?: string) => Promise<{
validate: (prop?: string | Array<string>) => Promise<{
valid: boolean
errors: ErrorMessage[]
}>

View File

@ -19,7 +19,7 @@ export default {
<script lang="ts" setup>
import wdToast from '../wd-toast/wd-toast.vue'
import { reactive, watch } from 'vue'
import { deepClone, getPropByPath, isDef, isPromise, isString } from '../common/util'
import { deepClone, getPropByPath, isArray, isDef, isPromise, isString } from '../common/util'
import { useChildren } from '../composables/useChildren'
import { useToast } from '../wd-toast'
import { type FormRules, FORM_KEY, type ErrorMessage, formProps, type FormExpose } from './types'
@ -30,7 +30,7 @@ const props = defineProps(formProps)
const { children, linkChildren } = useChildren(FORM_KEY)
let errorMessages = reactive<Record<string, string>>({})
linkChildren({ props, errorMessages: errorMessages })
linkChildren({ props, errorMessages })
watch(
() => props.model,
@ -44,22 +44,33 @@ watch(
/**
* 表单校验
* @param prop 指定校验字段
* @param prop 指定校验字段或字段数组
*/
async function validate(prop?: string): Promise<{ valid: boolean; errors: ErrorMessage[] }> {
async function validate(prop?: string | string[]): Promise<{ valid: boolean; errors: ErrorMessage[] }> {
const errors: ErrorMessage[] = []
let valid: boolean = true
const promises: Promise<void>[] = []
const formRules: FormRules = getMergeRules()
const rulesToValidate: FormRules = prop ? { [prop]: formRules[prop] } : formRules
for (const prop in rulesToValidate) {
const rules = rulesToValidate[prop]
const value = getPropByPath(props.model, prop)
const propsToValidate = isArray(prop) ? prop : isDef(prop) ? [prop] : []
const rulesToValidate: FormRules =
propsToValidate.length > 0
? propsToValidate.reduce((acc, key) => {
if (formRules[key]) {
acc[key] = formRules[key]
}
return acc
}, {} as FormRules)
: formRules
for (const propName in rulesToValidate) {
const rules = rulesToValidate[propName]
const value = getPropByPath(props.model, propName)
if (rules && rules.length > 0) {
for (const rule of rules) {
if (rule.required && (!isDef(value) || value === '')) {
errors.push({
prop,
prop: propName,
message: rule.message
})
valid = false
@ -67,7 +78,7 @@ async function validate(prop?: string): Promise<{ valid: boolean; errors: ErrorM
}
if (rule.pattern && !rule.pattern.test(value)) {
errors.push({
prop,
prop: propName,
message: rule.message
})
valid = false
@ -82,13 +93,13 @@ async function validate(prop?: string): Promise<{ valid: boolean; errors: ErrorM
.then((res) => {
if (typeof res === 'string') {
errors.push({
prop,
prop: propName,
message: res
})
valid = false
} else if (typeof res === 'boolean' && !res) {
errors.push({
prop,
prop: propName,
message: rule.message
})
valid = false
@ -96,14 +107,14 @@ async function validate(prop?: string): Promise<{ valid: boolean; errors: ErrorM
})
.catch((error?: string | Error) => {
const message = isDef(error) ? (isString(error) ? error : error.message || rule.message) : rule.message
errors.push({ prop, message })
errors.push({ prop: propName, message })
valid = false
})
)
} else {
if (!result) {
errors.push({
prop,
prop: propName,
message: rule.message
})
valid = false
@ -119,8 +130,8 @@ async function validate(prop?: string): Promise<{ valid: boolean; errors: ErrorM
showMessage(errors)
if (valid) {
if (prop) {
clearMessage(prop)
if (propsToValidate.length) {
propsToValidate.forEach(clearMessage)
} else {
clearMessage()
}
@ -135,6 +146,15 @@ async function validate(prop?: string): Promise<{ valid: boolean; errors: ErrorM
// rulesrules
function getMergeRules() {
const mergedRules: FormRules = deepClone(props.rules)
const childrenProps = children.map((child) => child.prop)
// children
Object.keys(mergedRules).forEach((key) => {
if (!childrenProps.includes(key)) {
delete mergedRules[key]
}
})
children.forEach((item) => {
if (isDef(item.prop) && isDef(item.rules) && item.rules.length) {
if (mergedRules[item.prop]) {