| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- <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>
|