varletjs / varlet

A Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.
https://varletjs.org/#/en-US/index
MIT License
4.9k stars 618 forks source link

form扩展验证 #1747

Closed magnificent007 closed 1 week ago

magnificent007 commented 1 month ago

关于form扩展验证问题,文档内只有单个字段,我想扩展多个字段的验证,rules 和 validateData 该以什么样的形式传入进行验证呢?

chouchouji commented 1 month ago

可以参考这里 https://github.com/varletjs/varlet/blob/dev/packages/varlet-ui/src/form/example/index.vue

里面有个 VarCustomFormComponent 组件,拓展多个可以参考这个组件封装

haoziqaq commented 1 month ago

:rules="[() => model.name.length > 6 || 'Error', () => model.age > 10 || 'Error']" 也是一样的~

magnificent007 commented 1 month ago

:rules="[() => model.name.length > 6 || 'Error', () => model.age > 10 || 'Error']" 也是一样的~

可以确定rules是个数组函数,我就是不清楚验证的数据要用什么格式?对象数组形式还是什么其它的格式?

magnificent007 commented 1 month ago

可以参考这里 https://github.com/varletjs/varlet/blob/dev/packages/varlet-ui/src/form/example/index.vue

里面有个 VarCustomFormComponent 组件,拓展多个可以参考这个组件封装

查看了,但是还是不清楚,验证的数据需要什么格式?可以确定rules是个函数数组,验证的数据是否也要用对象数组?

haoziqaq commented 1 month ago

不对验证数据格式有要求呀。本质 rules 是一个验证函数数组,组件自动判断验证时机,验证的时候只是执行函数,用户怎么写的就怎么执行。

magnificent007 commented 4 weeks ago

不对验证数据格式有要求呀。本质 rules 是一个验证函数数组,组件自动判断验证时机,验证的时候只是执行函数,用户怎么写的就怎么执行。

不是的,您可能没理解我的意思。rules是个函数数组,我理解没有问题;我想知道的是要验证的数据 在多字段验证的情况下 需要传入什么格式?是对象数组还是其它形式?

haoziqaq commented 4 weeks ago

没有格式要求。input 绑定的是什么值就验证什么值。

---原始邮件--- 发件人: @.> 发送时间: 2024年9月5日(周四) 下午4:47 收件人: @.>; 抄送: @.**@.>; 主题: Re: [varletjs/varlet] form扩展验证 (Issue #1747)

不对验证数据格式有要求呀。本质 rules 是一个验证函数数组,组件自动判断验证时机,验证的时候只是执行函数,用户怎么写的就怎么执行。

不是的,您可能没理解我的意思。rules是个函数数组,我理解没有问题;我想知道的是需要验证的数据需要传入什么格式?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

magnificent007 commented 4 weeks ago

没有格式要求。input 绑定的是什么值就验证什么值。

没有用input哦,里面是我自定义的多个输入操作,下面我提供下vue3代码片段和效果图

// 接入表单校验组件validate-form.tsx
import { Form } from '@varlet/ui'
import { nextTick, type SetupContext, type PropType } from 'vue'

type FComponentProps = {
  readonly?: Partial<boolean>,
  disabled?: Partial<boolean>,
  validateTrigger?: Partial<Array<string>>,
  rules?: Partial<Array<(v: any) => string | boolean>>,
  validateValues: Array<string>
}

type Events = {
  ['update:validateValues'](validateValues: Objectable): void,
  exposeFormInstance(i: Objectable): void
}

export type ExposeFormInstanceType = {
  reset: () => void, 
  trigger: () => void, 
  validate: () => Promise<boolean>, 
  resetValidation: () => void
}

export const ValidateForm = (props: FComponentProps, context: SetupContext<Events>) => {
  const { slots, emit, attrs } = context
  const { useForm, useValidation } = Form
  const { errorMessage, validateWithTrigger: _validateWithTrigger, validate: _validate, resetValidation } = useValidation()
  const { bindForm, form } = useForm()
  const validate = () => _validate(props.rules, props.validateValues)

  function reset() {
    emit('update:validateValues', [])
    resetValidation()
  }

  function validateWithTrigger(trigger: any) {
    nextTick(() => {
      const { validateTrigger, rules, validateValues } = props
      _validateWithTrigger(validateTrigger!, trigger, rules, validateValues)
    })
  }

  function trigger() {
    if (
      props.readonly ||
      props.disabled ||
      form?.readonly.value || 
      form?.disabled.value
    ) {
      return
    }
    emit('update:validateValues', props.validateValues)
    validateWithTrigger('onToggle')
  }

  emit('exposeFormInstance', { reset, trigger, validate, resetValidation })
  bindForm?.({ reset, validate, resetValidation })

  return (
    <>
      <var-space class="w-full z-99999" direction="column" size={[14, 0]}>
        { slots.main ? slots.main({ readonly: props.readonly, disabled: props.disabled, errorMessage: errorMessage.value }) : (<var-result type='empty' description='三无属性地带..!' />) }
        { slots.footer ? slots.footer({ readonly: props.readonly, disabled: props.disabled }) : (<></>) }
      </var-space>
    </>
  )
}

ValidateForm.props = {
  readonly: {
    type: Boolean as PropType<Partial<boolean>>,
    default: false
  },
  disabled: {
    type: Boolean as PropType<Partial<boolean>>,
    default: false
  },
  validateTrigger: {
    type: Array as PropType<Partial<Array<string>>>,
    default: () => ['onToggle']
  },
  rules: {
    type: Array as PropType<Partial<Array<(v: any) => string | boolean>>>,
    default: () => []
  },
  validateValues: {
    type: Array as PropType<Array<string>>,
    default: () => []
  }
}

export default ValidateForm
// 使用
<script lang='ts' setup>
import { ref } from 'vue'
import { ValidateForm, type ExposeFormInstanceType } from '@/components/basic/validate-form/validate-form'
import { pull } from 'lodash-es'

// ------------------ 验证从这里开始 ------------------

const { getFormInstance, validate } = useForm()
const { characteristic, checkedCharacter } = useSelectCharacter()

function useSelectCharacter() {
  const characteristic = ref<Array<string>>([])

  const checkedCharacter = (e: any, additional: { readonly: boolean, disabled: boolean }) => {
    if (additional.readonly || additional.disabled) return 
    const { id } = e.target.dataset
    if (characteristic.value.includes(id)) {
      pull(characteristic.value, id)
      return 
    }
    characteristic.value.push(id)
  }

  return {
    characteristic,
    checkedCharacter
  }
}

function useForm() {
  const formInstance = ref<ExposeFormInstanceType>()

  function getFormInstance(instance: ExposeFormInstanceType) {
    formInstance.value = instance
  }

  function validate() {
    formInstance.value?.validate()
  }

  return {
    formInstance,
    getFormInstance,
    validate
  }
}

const questions = [
  {
    title: '标题1',
    subtitle: '副标题1',
    list: ['积极', '好学', '勇敢', '阳光']
  },
  {
    title: '标题2',
    subtitle: '副标题2',
    list: ['消极', '懒惰', '胆小', '阴暗']
  }
]
</script>
<template>
  <ValidateForm 
    :readonly="false" 
    :disabled="false" 
    :rules="[v => v.length >= 1 || '至少选择1个', v => v.length >= 1 || '至少选择1个']" 
    v-model:validateValues="characteristic" 
    @exposeFormInstance="getFormInstance"
  >
    <template #main="{ readonly, disabled, errorMessage }">

      <div class="character-wrapper box-border" v-for="q in questions" :key="q.title">
        <span class="title block">{{ q.title }}</span>
        <span class="subtitle">{{ q.subtitle }}</span>
        <var-form-details :error-message="errorMessage" />
        <div class="flex flex-wrap justify-between gap-row-6 mt-4" @click="checkedCharacter($event, { readonly, disabled })">
          <var-paper 
            :class="['relative text-center', readonly || disabled ? 'disabled': '', characteristic.includes(i) ? 'active' : '']"
            :ripple="!(readonly || disabled)"
            width="30%" 
            height="4rem" 
            :elevation="2"
            v-for="i in q.list"
            :key="i"
            :data-id="i"
          >
            <span class="leading-16 text-coolGray font-bold" :data-id="i">{{ i }}</span>
          </var-paper>
        </div>
      </div>

    </template>
    <template #footer="{ readonly, disabled }">
      <slot name="footer" :readonly="readonly" :disabled="disabled"></slot>
    </template>
  </ValidateForm>
  <var-button 
    block 
    type="success" 
    loading-size="small" 
    loading-type="wave" 
    @click="validate"
  >
    验证
  </var-button>
</template>
<style lang='scss' scoped>
.character-wrapper {
  padding: 12px;

  .title {
    font-size: 18px;
    font-weight: bold;
    color: black;
  }

  .subtitle {
    font-size: 14px;
    color: gray;
  }
}

.disabled::before {
  content: '';
  box-sizing: border-box;
  background-color: rgba(0, 0, 0, 0.1);
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 101;
}

.active::after {
  content: '✔';
  box-sizing: border-box;
  background-color: green;
  color: white;
  font-size: 12px;
  line-height: 1rem;
  width: 1rem;
  height: 1rem;
  position: absolute;
  top: 0;
  right: 0;
  z-index: 100;
}
</style>

想实现的效果图

haoziqaq commented 3 weeks ago

image image 我这边没问题,你这边提供的 tsx 代码可能存在问题,可以尝试按照图2修复。