更新:登录功能
This commit is contained in:
207
uni_modules/wot-design-uni/components/wd-form/wd-form.vue
Normal file
207
uni_modules/wot-design-uni/components/wd-form/wd-form.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<view :class="`wd-form ${customClass}`" :style="customStyle">
|
||||
<slot></slot>
|
||||
<wd-toast v-if="props.errorType === 'toast'" selector="wd-form-toast" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'wd-form',
|
||||
options: {
|
||||
addGlobalClass: true,
|
||||
virtualHost: true,
|
||||
styleIsolation: 'shared'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import wdToast from '../wd-toast/wd-toast.vue'
|
||||
import { reactive, watch } from 'vue'
|
||||
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'
|
||||
|
||||
const { show: showToast } = useToast('wd-form-toast')
|
||||
const props = defineProps(formProps)
|
||||
|
||||
const { children, linkChildren } = useChildren(FORM_KEY)
|
||||
let errorMessages = reactive<Record<string, string>>({})
|
||||
|
||||
linkChildren({ props, errorMessages })
|
||||
|
||||
watch(
|
||||
() => props.model,
|
||||
() => {
|
||||
if (props.resetOnChange) {
|
||||
clearMessage()
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
/**
|
||||
* 表单校验
|
||||
* @param prop 指定校验字段或字段数组
|
||||
*/
|
||||
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 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: propName,
|
||||
message: rule.message
|
||||
})
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
if (rule.pattern && !rule.pattern.test(value)) {
|
||||
errors.push({
|
||||
prop: propName,
|
||||
message: rule.message
|
||||
})
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
const { validator, ...ruleWithoutValidator } = rule
|
||||
if (validator) {
|
||||
const result = validator(value, ruleWithoutValidator)
|
||||
if (isPromise(result)) {
|
||||
promises.push(
|
||||
result
|
||||
.then((res) => {
|
||||
if (typeof res === 'string') {
|
||||
errors.push({
|
||||
prop: propName,
|
||||
message: res
|
||||
})
|
||||
valid = false
|
||||
} else if (typeof res === 'boolean' && !res) {
|
||||
errors.push({
|
||||
prop: propName,
|
||||
message: rule.message
|
||||
})
|
||||
valid = false
|
||||
}
|
||||
})
|
||||
.catch((error?: string | Error) => {
|
||||
const message = isDef(error) ? (isString(error) ? error : error.message || rule.message) : rule.message
|
||||
errors.push({ prop: propName, message })
|
||||
valid = false
|
||||
})
|
||||
)
|
||||
} else {
|
||||
if (!result) {
|
||||
errors.push({
|
||||
prop: propName,
|
||||
message: rule.message
|
||||
})
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
showMessage(errors)
|
||||
|
||||
if (valid) {
|
||||
if (propsToValidate.length) {
|
||||
propsToValidate.forEach(clearMessage)
|
||||
} else {
|
||||
clearMessage()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid,
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
||||
// 合并子组件的rules到父组件的rules
|
||||
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]) {
|
||||
mergedRules[item.prop] = [...mergedRules[item.prop], ...item.rules]
|
||||
} else {
|
||||
mergedRules[item.prop] = item.rules
|
||||
}
|
||||
}
|
||||
})
|
||||
return mergedRules
|
||||
}
|
||||
|
||||
function showMessage(errors: ErrorMessage[]) {
|
||||
const childrenProps = children.map((e) => e.prop).filter(Boolean)
|
||||
const messages = errors.filter((error) => error.message && childrenProps.includes(error.prop))
|
||||
if (messages.length) {
|
||||
messages.sort((a, b) => {
|
||||
return childrenProps.indexOf(a.prop) - childrenProps.indexOf(b.prop)
|
||||
})
|
||||
if (props.errorType === 'toast') {
|
||||
showToast(messages[0].message)
|
||||
} else if (props.errorType === 'message') {
|
||||
messages.forEach((error) => {
|
||||
errorMessages[error.prop] = error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearMessage(prop?: string) {
|
||||
if (prop) {
|
||||
errorMessages[prop] = ''
|
||||
} else {
|
||||
Object.keys(errorMessages).forEach((key) => {
|
||||
errorMessages[key] = ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单项的验证提示
|
||||
*/
|
||||
function reset() {
|
||||
clearMessage()
|
||||
}
|
||||
|
||||
defineExpose<FormExpose>({ validate, reset })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
Reference in New Issue
Block a user