Skip to content
On this page

Form 表单 0.2.0

用于数据录入、校验,支持输入框、单选框、复选框、文件上传等类型,常见的 form 表单为单元格形式的展示,即左侧为表单的标题描述,右侧为表单的输入。

其中,Input 输入框Textarea 输入框Picker 选择器Calendar 日历选择器ColPicker 多列选择器SelectPicker 单复选选择器Cell 单元格DatetimePicker 日期时间选择器具有单元格的展示形式,同时也支持 proprules 属性,我们称之为表单项组件,而 InputNumber 计数器Switch 开关Upload 上传 等组件则需要使用 Cell 单元格 进行包裹使用。

结合 wd-form 组件,可以实现对以上组件的规则校验。

对于表单组件,建议对 wd-cell-group 开启 border 属性,这样每条 cell 就会有边框线隔离开,这样表单的划分比较清晰。

基础用法

在表单中,使用 model 指定表单数据对象,每个 表单项组件 代表一个表单项,使用 prop 指定表单项字段 ,使用 rules 属性定义校验规则。

查看基础用法示例
html
<wd-form ref="form" :model="model">
  <wd-cell-group border>
    <wd-input
      label="用户名"
      label-width="100px"
      prop="value1"
      clearable
      v-model="model.value1"
      placeholder="请输入用户名"
      :rules="[{ required: true, message: '请填写用户名' }]"
    />
    <wd-input
      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
<script lang="ts" setup>
const { success: showSuccess } = useToast()

const model = reactive<{
  value1: string
  value2: string
}>({
  value1: '',
  value2: ''
})

const form = ref()

function handleSubmit1() {
  form.value
    .validate()
    .then(({ valid, errors }) => {
      if (valid) {
        showSuccess({
          msg: '校验通过'
        })
      }
    })
    .catch((error) => {
      console.log(error, 'error')
    })
}
</script>
css
.footer {
  padding: 12px;
}

校验规则

本章节演示四种自定义校验及提示规则:正则校验函数校验函数返回错误提示异步函数校验

查看校验规则示例
html
<wd-form ref="form2" :model="model">
  <wd-cell-group border>
    <wd-input
      label="校验"
      label-width="100px"
      prop="value1"
      clearable
      v-model="model.value1"
      placeholder="正则校验"
      :rules="[{ required: false, pattern: /\d{6}/, message: '请输入6位字符' }]"
    />
    <wd-input
      label="校验"
      label-width="100px"
      prop="value2"
      clearable
      v-model="model.value2"
      placeholder="函数校验"
      :rules="[
              {
                required: false,
                validator: validatorMessage,
                message: '请输入正确的手机号'
              }
            ]"
    />
    <wd-input
      label="校验"
      label-width="100px"
      prop="value3"
      clearable
      v-model="model.value3"
      placeholder="校验函数返回错误提示"
      :rules="[
              {
                required: false,
                message: '请输入内容',
                validator: validator
              }
            ]"
    />
    <wd-input
      label="校验"
      label-width="100px"
      prop="value4"
      clearable
      v-model="model.value4"
      placeholder="异步函数校验"
      :rules="[{ required: false, validator: asyncValidator, message: '请输入1234' }]"
    />
  </wd-cell-group>
  <view class="footer">
    <wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
  </view>
</wd-form>
typescript
<script lang="ts" setup>
const model = reactive<{
  value1: string
  value2: string
  value3: string
  value4: string
}>({
  value1: '',
  value2: '',
  value3: '',
  value4: ''
})

const { success: showSuccess } = useToast()

const form = ref()

const validatorMessage = (val) => {
  return /1\d{10}/.test(val)
}

const validator = (val) => {
  if (String(val).length >= 4) {
    return Promise.resolve()
  } else {
    return Promise.reject('长度不得小于4')
  }
}

// 校验函数可以返回 Promise,实现异步校验
const asyncValidator = (val) =>
  new Promise((resolve) => {
    showLoading('验证中...')

    setTimeout(() => {
      closeToast()
      resolve(val === '1234')
    }, 1000)
  })

function handleSubmit() {
  form.value
    .validate()
    .then(({ valid, errors }) => {
      if (valid) {
        showSuccess({
          msg: '提交成功'
        })
      }
    })
    .catch((error) => {
      console.log(error, 'error')
    })
}
</script>
css
.footer {
  padding: 12px;
}

动态表单

表单项动态增减。

查看动态表单示例
html
<wd-form ref="form" :model="model">
  <wd-cell-group border>
    <wd-input
      label="用户名"
      label-width="100px"
      prop="name"
      clearable
      v-model="model.name"
      placeholder="请输入用户名"
      :rules="[{ required: true, message: '请填写用户名' }]"
    />
    <wd-input
      v-for="(item, index) in model.phoneNumbers"
      :key="item.key"
      :label="'联系方式' + index"
      :prop="'phoneNumbers.' + index + '.value'"
      label-width="100px"
      clearable
      v-model="item.value"
      placeholder="联系方式"
      :rules="[{ required: true, message: '请填写联系方式' + index }]"
    />

    <wd-cell title-width="0px">
      <view class="footer">
        <wd-button size="small" type="info" plain @click="addPhone">添加</wd-button>
        <wd-button size="small" type="info" plain @click="removePhone">删除</wd-button>
        <wd-button size="small" type="info" plain @click="reset">重置</wd-button>
        <wd-button type="primary" size="small" @click="submit">提交</wd-button>
      </view>
    </wd-cell>
  </wd-cell-group>
</wd-form>
typescript
<script lang="ts" setup>
import { useToast } from '@/uni_modules/wot-design-uni'
import { reactive, ref } from 'vue'

interface PhoneItem {
  key: number
  value: string
}

const model = reactive<{
  name: string
  phoneNumbers: PhoneItem[]
}>({
  name: '',
  phoneNumbers: [
    {
      key: Date.now(),
      value: ''
    }
  ]
})

const { success: showSuccess } = useToast()
const form = ref()

const removePhone = () => {
  model.phoneNumbers.splice(model.phoneNumbers.length - 1, 1)
}

const addPhone = () => {
  model.phoneNumbers.push({
    key: Date.now(),
    value: ''
  })
}

const reset = () => {
  form.value.reset()
}

const submit = () => {
  form.value.validate().then(({ valid, errors }) => {
    if (valid) {
      showSuccess('校验通过')
    }
  })
}
</script>
css
.footer {
  text-align: left;
  :deep(.wd-button) {
    &:not(:last-child) {
      margin-right: 12px;
    }
  }
}

指定字段校验

validate 方法可以传入一个 prop 参数,指定校验的字段,可以实现在表单组件的blurchange等事件触发时对该字段的校验。

查看指定字段校验示例
html
<wd-form ref="form" :model="model">
  <wd-cell-group border>
    <wd-input
      label="用户名"
      label-width="100px"
      prop="name"
      clearable
      v-model="model.name"
      placeholder="请输入用户名"
      @blur="handleBlur('name')"
      :rules="[{ required: true, message: '请填写用户名' }]"
    />
    <wd-input
      label="联系方式"
      prop="phoneNumber"
      label-width="100px"
      clearable
      @blur="handleBlur('phoneNumber')"
      v-model="model.phoneNumber"
      placeholder="联系方式"
      :rules="[{ required: true, message: '请填写联系方式' }]"
    />
  </wd-cell-group>
</wd-form>

<view class="footer">
  <wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
typescript
<script lang="ts" setup>
import { useToast } from '@/uni_modules/wot-design-uni'
import { reactive, ref } from 'vue'

const model = reactive<{
  name: string
  phoneNumber: string
}>({
  name: '',
  phoneNumber: ''
})

const { success: showSuccess } = useToast()
const form = ref()

function handleBlur(prop: string) {
  form.value.validate(prop)
}

function handleSubmit() {
  form.value
    .validate()
    .then(({ valid }) => {
      if (valid) {
        showSuccess('校验通过')
      }
    })
    .catch((error) => {
      console.log(error, 'error')
    })
}
</script>
css
.footer {
  padding: 12px;
}

复杂表单

结合Input 输入框Textarea 输入框Picker 选择器Calendar 日历选择器ColPicker 多列选择器SelectPicker 单复选选择器Cell 单元格DatetimePicker 日期时间选择器实现一个复杂表单。

查看复杂表单示例
html
<view>
  <wd-message-box />
  <wd-toast />
  <wd-form ref="form" :model="model" :rules="rules">
    <wd-cell-group custom-class="group" title="基础信息" border>
      <wd-input
        label="优惠券名称"
        label-width="100px"
        :maxlength="20"
        show-word-limit
        prop="couponName"
        required
        suffix-icon="warn-bold"
        clearable
        v-model="model.couponName"
        placeholder="请输入优惠券名称"
        @clicksuffixicon="handleIconClick"
      />
      <wd-select-picker
        label="推广平台"
        label-width="100px"
        prop="platform"
        v-model="model.platform"
        :columns="platformList"
        placeholder="请选择推广平台"
      />
      <wd-picker
        label="优惠方式"
        placeholder="请选择优惠方式"
        label-width="100px"
        prop="promotion"
        v-model="model.promotion"
        :columns="promotionlist"
      />
      <wd-cell prop="threshold" title="券面额" required title-width="100px" custom-value-class="cell-left">
        <view style="text-align: left">
          <view class="inline-txt" style="margin-left: 0"></view>
          <wd-input
            no-border
            custom-style="display: inline-block; width: 70px; vertical-align: middle"
            placeholder="请输入金额"
            v-model="model.threshold"
          />
          <view class="inline-txt"></view>
          <wd-input
            no-border
            custom-style="display: inline-block; width: 70px; vertical-align: middle"
            placeholder="请输入金额"
            v-model="model.price"
          />
        </view>
      </wd-cell>
    </wd-cell-group>
    <wd-cell-group custom-class="group" title="时间和地址" border>
      <wd-datetime-picker label="时间" label-width="100px" placeholder="请选择时间" prop="time" v-model="model.time" />
      <wd-calendar label="日期" label-width="100px" placeholder="请选择日期" prop="date" v-model="model.date" />

      <wd-col-picker
        label="地址"
        placeholder="请选择地址"
        label-width="100px"
        prop="address"
        v-model="model.address"
        :columns="area"
        :column-change="areaChange"
      />
    </wd-cell-group>
    <wd-cell-group custom-class="group" title="其他信息" border>
      <wd-textarea
        label="活动细则"
        label-width="100px"
        type="textarea"
        v-model="model.content"
        :maxlength="300"
        show-word-limit
        placeholder="请输入活动细则信息"
        clearable
        prop="content"
      />
      <wd-cell title="发货数量" title-width="100px" prop="count">
        <view style="text-align: left">
          <wd-input-number v-model="model.count" />
        </view>
      </wd-cell>
      <wd-cell title="开启折扣" title-width="100px" prop="switchVal" center>
        <view style="text-align: left">
          <wd-switch v-model="model.switchVal" />
        </view>
      </wd-cell>
      <wd-input label="卡号" label-width="100px" prop="cardId" suffix-icon="camera" placeholder="请输入卡号" clearable v-model="model.cardId" />
      <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-cell>
    </wd-cell-group>
    <view class="tip">
      <wd-checkbox v-model="model.read" prop="read" custom-label-class="label-class">
        已阅读并同意
        <text style="color: #4d80f0">《借款额度合同及相关授权》</text>
      </wd-checkbox>
    </view>
    <view class="footer">
      <wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
    </view>
  </wd-form>
</view>
typescript
<script lang="ts" setup>
import { useToast } from '@/uni_modules/wot-design-uni'
import { isArray } from '@/uni_modules/wot-design-uni/components/common/util'
import { FormRules } from '@/uni_modules/wot-design-uni/components/wd-form/types'
import { areaData } from '@/utils/area'
import { reactive, ref } from 'vue'

const model = reactive<{
  couponName: string
  platform: any[]
  promotion: string
  threshold: string
  price: string
  time: number | string
  date: null | number
  address: string[]
  count: number
  content: string
  switchVal: boolean
  cardId: string
  phone: string
  read: boolean
  fileList: Record<string, string>[]
}>({
  couponName: '',
  platform: [],
  promotion: '',
  threshold: '',
  price: '',
  date: null,
  time: '',
  address: [],
  count: 1,
  content: '',
  switchVal: true,
  cardId: '',
  phone: '',
  read: false,
  fileList: []
})

const rules: FormRules = {
  couponName: [
    {
      required: true,
      pattern: /\d{6}/,
      message: '优惠券名称6个字以上',
      validator: (value) => {
        if (value) {
          return Promise.resolve()
        } else {
          return Promise.reject('请输入优惠券名称')
        }
      }
    }
  ],
  content: [
    {
      required: true,
      message: '请输入活动细则信息',
      validator: (value) => {
        if (value && value.length > 2) {
          return Promise.resolve()
        } else {
          return Promise.reject('请输入活动细则信息')
        }
      }
    }
  ],
  threshold: [
    {
      required: true,
      message: '请输入满减金额',
      validator: (value) => {
        if (value && model.price) {
          return Promise.resolve()
        } else {
          return Promise.reject()
        }
      }
    }
  ],
  platform: [
    {
      required: true,
      message: '请选择推广平台',
      validator: (value) => {
        if (value && isArray(value) && value.length) {
          return Promise.resolve()
        } else {
          return Promise.reject('请选择推广平台')
        }
      }
    }
  ],
  promotion: [
    {
      required: true,
      message: '请选择推广平台',
      validator: (value) => {
        if (value) {
          return Promise.resolve()
        } else {
          return Promise.reject('请选择推广平台')
        }
      }
    }
  ],
  time: [
    {
      required: true,
      message: '请选择时间',
      validator: (value) => {
        if (value) {
          return Promise.resolve()
        } else {
          return Promise.reject('请选择时间')
        }
      }
    }
  ],
  date: [
    {
      required: true,
      message: '请选择日期',
      validator: (value) => {
        if (value) {
          return Promise.resolve()
        } else {
          return Promise.reject()
        }
      }
    }
  ],
  address: [
    {
      required: true,
      message: '请选择地址',
      validator: (value) => {
        if (isArray(value) && value.length) {
          return Promise.resolve()
        } else {
          return Promise.reject('请选择地址')
        }
      }
    }
  ],
  count: [
    {
      required: true,
      message: '发货数量需要大于1',
      validator: (value) => {
        if (Number(value) > 1) {
          return Promise.resolve()
        } else {
          return Promise.reject('发货数量需要大于1')
        }
      }
    }
  ],
  cardId: [
    {
      required: true,
      message: '请输入卡号',
      validator: (value) => {
        if (value) {
          return Promise.resolve()
        } else {
          return Promise.reject('请输入卡号')
        }
      }
    }
  ],
  phone: [
    {
      required: true,
      message: '请输入手机号',
      validator: (value) => {
        if (value) {
          return Promise.resolve()
        } else {
          return Promise.reject()
        }
      }
    }
  ],
  fileList: [
    {
      required: true,
      message: '请选择活动图片',
      validator: (value) => {
        if (isArray(value) && value.length) {
          return Promise.resolve()
        } else {
          return Promise.reject()
        }
      }
    }
  ]
}

const platformList = ref<any>([
  {
    value: '1',
    label: '京东'
  },
  {
    value: '2',
    label: '开普勒'
  },
  {
    value: '3',
    label: '手Q'
  },
  {
    value: '4',
    label: '微信'
  },
  {
    value: '5',
    label: '1号店'
  },
  {
    value: '6',
    label: '十元街'
  },
  {
    value: '7',
    label: '京东极速版'
  }
])
const promotionlist = ref<any[]>([
  {
    value: '1',
    label: '满减'
  },
  {
    value: '2',
    label: '无门槛'
  }
])

const area = ref<any[]>([
  Object.keys(areaData[86]).map((key) => {
    return {
      value: key,
      label: areaData[86][key]
    }
  })
])
const areaChange = ({ selectedItem, resolve, finish }) => {
  if (areaData[selectedItem.value]) {
    resolve(
      Object.keys(areaData[selectedItem.value]).map((key) => {
        return {
          value: key,
          label: areaData[selectedItem.value][key]
        }
      })
    )
  } else {
    finish()
  }
}
const toast = useToast()
const form = ref()

function handleFileChange({ fileList }) {
  model.fileList = fileList
}

function handleSubmit() {
  form.value
    .validate()
    .then(({ valid, errors }) => {
      console.log(valid)
      console.log(errors)
    })
    .catch((error) => {
      console.log(error, 'error')
    })
}

function handleIconClick() {
  toast.info('优惠券提示信息')
}
</script>
css
.inline-txt {
  display: inline-block;
  font-size: 14px;
  margin: 0 8px;
  color: rgba(0, 0, 0, 0.45);
  vertical-align: middle;
}
:deep(.group) {
  margin-top: 12px;
}
.tip {
  margin: 10px 15px 21px;
  color: #999;
  font-size: 12px;
}
.footer {
  padding: 0 25px 21px;
}
:deep(.label-class) {
  color: #999 !important;
  font-size: 12px !important;
}

Attributes

参数说明类型可选值默认值最低版本
model表单数据对象Record<string, any>--0.2.0
rules表单验证规则FormRules--0.2.0
resetOnChange表单数据变化时是否重置表单提示信息(设置为false时需要开发者单独对变更项进行校验)boolean-true0.2.16

FormItemRule 数据结构

键名说明类型
required是否为必选字段boolean
message错误提示文案string
validator通过函数进行校验,可以返回一个 Promise 来进行异步校验(value, rule) => boolean | Promise
pattern通过正则表达式进行校验,正则无法匹配表示校验不通过RegExp

Events

事件名称说明参数最低版本
validate验证表单,支持传入一个 prop 来验证单个表单项,不传入 prop 时,会验证所有表单项prop?: string0.2.0
reset重置校验结果-0.2.0

外部样式类

类名说明最低版本
custom-class根节点样式0.2.0

Released under the MIT License.

Released under the MIT License.