Appearance
表单
版本说明
时间 | 修改人 | 备注 |
---|---|---|
2025-04-25 | YG | 补充文档 |
表单设计规范
1. 布局与结构
- 采用垂直布局,每个表单项独占一行
- 相关表单项进行分组,使用卡片或分隔线区分不同分组
- 重要字段放在前面,非必填项放在后面
- 过长表单应考虑分步填写,减轻用户认知负担
2. 标签与输入框
- 标签文字简洁明了,使用名词而非动词
- 对齐方式采用顶部对齐
- 输入框宽度占满剩余空间
- 必填项使用星号(*)标记
- 输入框获取焦点时有明显的视觉反馈
3. 输入引导
- 使用placeholder提示输入格式
- 复杂输入提供示例或说明文字
- 特殊格式输入(如日期、手机号)提供格式化输入控件
- 密码输入提供显示/隐藏切换按钮
表单验证规范
1. 验证时机
- 输入过程中实时验证(失焦验证)
- 提交表单时进行全面验证
2. 验证规则
- 手机号验证:11位数字,以1开头
/^1[3-9]\d{9}$/
- 邮箱验证:符合邮箱格式规范
/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
- 密码验证:长度、复杂度要求明确提示(8-20位,包含数字和字母)
/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,20}$/
- 身份证验证:18位,最后一位可能是X
/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
- 必填项不能为空
- 数字输入范围限制
- 文本长度限制
3. 错误提示
- 错误提示文字清晰具体,指出错误原因
- 错误提示位置靠近对应输入框,通常在下方
- 使用醒目但不刺眼的颜色(如红色)
- 同时只显示一个错误,优先显示第一个错误项
表单交互规范
1. 提交操作
- 提交按钮醒目,通常使用主题色
- 提交按钮文字明确表达动作,如"提交"、"注册"
- 提交前进行表单验证,提交过程中显示loading状态,防止重复提交
- 提交成功后给予明确反馈,如成功页面或提示
- 退出页面后使用 resetFields() 方法重置表单
2. 特殊交互
- 验证码获取后60秒倒计时,倒计时期间禁用获取按钮
- 倒计时过程中不允许修改手机号
- 敏感操作(如删除账号)需二次确认
uniapp 组件使用
1. 表单组件结构
- 使用 uni-forms 作为表单容器组件
- 每个表单项使用 uni-forms-item 包裹
- 表单项内可嵌套 uni-easyinput 、 uni-data-checkbox 等组件或原生表单组件
2. 表单布局与样式
- 通过 label-position 设置标签位置:top(顶部)、left(左侧,默认)
- 使用 label-width 设置标签宽度,默认75px
- 设置 label-align 控制标签对齐方式:left(左对齐)、center(居中)、right(右对齐)
vue
<uni-forms :modelValue="formData" label-position="top">
<uni-forms-item label="姓名" name="name">
<uni-easyinput v-model="formData.name" placeholder="请输入姓名" />
</uni-forms-item>
</uni-forms>
3. 表单验证规范
- 定义 rules 对象设置验证规则
- 支持多种验证类型:required(必填)、format(格式)、range(范围)、length(长度)等
- 可通过 validateFunction 自定义验证函数
vue
<template>
<uni-forms ref="form" :rules="rules" :modelValue="formData">
<!-- 表单项 -->
</uni-forms>
</template>
<script>
export default {
data() {
return {
formData: {
name: '',
age: '',
email: ''
},
rules: {
name: {
rules: [
{ required: true, errorMessage: '请输入姓名' },
{ minLength: 2, maxLength: 10, errorMessage: '姓名长度在2-10个字符' }
]
},
email: {
rules: [
{ required: true, errorMessage: '请输入邮箱' },
{ format: 'email', errorMessage: '邮箱格式不正确' }
]
}
}
}
}
}
</script>
4. 表单验证方法
- 使用 validate() 方法进行表单整体验证
- 使用 validateField(name) 验证单个表单项
- 使用 clearValidate([names]) 清除验证结果
js
// 表单验证示例
submitForm() {
this.$refs.form.validate().then(res => {
// 验证通过
console.log('表单数据信息:', res);
}).catch(err => {
// 验证失败
console.log('表单错误信息:', err);
})
}
5. 表单项特性
- 使用 required 属性显示必填星号
- 设置 err-show-type 控制是否显示错误信息,undertext/toast/modal
- 通过插槽自定义表单项内容:label(标签)、default(默认)
vant 组件使用
- 表单的保存和删除等按钮,一般需要虚浮在底部;
- 表单的必填项需要添加验证和红点标记;
- 表单验证使用公共方法:
/src/libs/common/WxValidate.js
。 - 表单项必须使用van-field保持样式一致(实际使用是根据UI统一调整样式),如果有其他输入方式使用van-field的slot:input;
- 表单的布局:
- form > van-field
- form > van-field > template #input > ...
1. 表单示例
vue
<template>
<view class="content">
<form @submit="onSubmit">
<view class="form_box">
<!-- 输入框 -->
<van-field
name="name"
type="text"
label="名称"
placeholder="请输入名称"
:value="formModel.name"
:required="formRules.name.required"
:error-message="objValidateErr.name"
@input="onInput($event, 'name')"
></van-field>
<van-field
name="desc"
type="text"
label="描述"
placeholder="请输入描述"
:value="formModel.desc"
:required="formRules.desc.required"
:error-message="objValidateErr.desc"
@input="onInput($event, 'desc')"
></van-field>
<van-field
name="phone"
type="phone"
label="手机号"
placeholder="请输入手机号"
:value="formModel.phone"
:required="formRules.phone.required"
:error-message="objValidateErr.phone"
@input="onInput($event, 'phone')"
></van-field>
<van-field
name="password"
type="phone"
label="密码"
placeholder="请输入密码"
:password="!controlItem.showPassword"
:value="formModel.password"
:required="formRules.password.required"
:error-message="objValidateErr.password"
@input="onInput($event, 'password')"
>
<template #right-icon>
<view
@click.stop="controlItem.showPassword = !controlItem.showPassword"
>
<image
v-if="controlItem.showPassword"
class="showPawIcon"
src="https://pro-core.babycdn.com/2022/09/zsh/xcx/images/home/show.png"
></image>
<image
v-else
class="showPawIcon"
src="https://pro-core.babycdn.com/2022/09/zsh/xcx/images/home/not_show.png"
></image>
</view>
</template>
</van-field>
<!-- 开关 -->
<!-- 小程序样式问题,高度比size多2px -->
<van-field name="open" label="是否开启">
<template #input>
<van-switch
:checked="formModel.open"
style="height:20px"
size="18"
@change="onInput($event, 'open')"
/>
</template>
</van-field>
<!-- 单选框 -->
<van-field
name="sex"
label="性别"
:required="formRules.sex.required"
:error-message="objValidateErr.sex"
>
<template #input>
<van-radio-group
:value="formModel.sex.toString()"
direction="horizontal"
@change="onInput($event, 'sex')"
>
<template v-for="(option, option_i) in formOption.sex">
<van-radio :key="option_i" :name="option.key" shape="round">
{{ option.text }}
</van-radio>
</template>
</van-radio-group>
</template>
</van-field>
<!-- 复选框 -->
<van-field
name="likes"
label="爱好"
:required="formRules.likes.required"
:error-message="objValidateErr.likes"
>
<template #input>
<van-checkbox-group
:value="formModel.likes"
direction="horizontal"
@change="onInput($event, 'likes')"
>
<template v-for="(option, option_i) in formOption.likes">
<van-checkbox :key="option_i" :name="option.key" shape="square">
{{ option.text }}
</van-checkbox>
</template>
</van-checkbox-group>
</template>
</van-field>
<!-- 日期选择器 -->
<view @click="controlItem.showDatePopup = true">
<van-field
name="date"
label="日期"
:value="formModel.date"
readonly
placeholder="请选择日期"
:error-message="objValidateErr.date"
>
</van-field>
</view>
<van-popup
:show="controlItem.showDatePopup"
position="bottom"
round
@close="controlItem.showDatePopup = false"
>
<van-datetime-picker
type="date"
title="请选择日期"
:value="new Date(formModel.date).getTime()"
:min-date="formRules.date.minDate"
:max-date="formRules.date.maxDate"
@cancel="controlItem.showDatePopup = false"
@confirm="onInputDate($event, 'date', 'showDatePopup')"
/>
</van-popup>
<!-- 评分 -->
<van-field
name="score"
label="评分"
:required="formRules.score.required"
:error-message="objValidateErr.score"
>
<template #input>
<van-rate
:value="formModel.score"
size="18"
gutter="2"
count="10"
allow-half
@change="onInput($event, 'score')"
/>
</template>
</van-field>
<!-- 步进器-->
<van-field
name="num"
label="数量"
:required="formRules.num.required"
:error-message="objValidateErr.num"
>
<template #input>
<van-stepper
:value="formModel.num"
step="1"
@change="onInput($event, 'num')"
/>
</template>
</van-field>
<!-- 进度条 -->
<van-field
name="speed"
label="进度"
:required="formRules.speed.required"
:error-message="objValidateErr.speed"
>
<template #input>
<view class="slider_box">
<van-slider
:value="formModel.speed"
step="1"
:use-button-slot="true"
@drag="onInputValue($event, 'speed')"
>
<view class="slider_btn" slot="button">
{{ formModel.speed }}
</view>
</van-slider>
</view>
</template>
</van-field>
<!-- 图片上传 -->
<van-field
name="image"
label="图片base64上传"
:required="formRules.image.required"
:error-message="objValidateErr.image"
>
<template #input>
<van-uploader
:file-list="formModel.image"
:max-count="4"
accept="image"
:imageFitL="'center'"
@after-read="onInputImg($event, 'image')"
@delete="onDelImg($event, 'image')"
/>
</template>
</van-field>
<!-- oss图片上传 -->
<van-field
name="oss_img"
label="图片oss上传"
:border="false"
:required="formRules.oss_img.required"
:error-message="objValidateErr.oss_img"
>
<template #input>
<van-uploader
:file-list="formModel.oss_img"
:max-count="4"
accept="image"
:imageFitL="'center'"
@after-read="onInputImg($event, 'oss_img')"
@delete="onDelImg($event, 'oss_img')"
/>
</template>
</van-field>
</view>
<view class="operation_box">
<template v-for="(btn, btn_i) in operation">
<template v-if="btn.type == 'submit'">
<van-button
:key="btn_i"
size="normal"
block
round
:type="btn.btnType"
form-type="submit"
:class="[btn.customClass]"
>
{{ btn.text }}
</van-button>
</template>
<template v-else>
<van-button
:key="btn_i"
size="normal"
block
round
:type="btn.btnType"
:class="[btn.customClass]"
>
{{ btn.text }}
</van-button>
</template>
</template>
</view>
</form>
</view>
</template>
<script>
import addData from "./add.js";
export default {
components: {},
data() {
return {
formModel: JSON.parse(JSON.stringify(addData.formModel)),
formRules: addData.formRules,
formOption: addData.formOption,
validate: addData.validate,
objValidateErr: addData.objValidateErr,
controlItem: addData.controlItem,
operation: addData.operation,
};
},
onLoad() {},
methods: {
//input、switch、rate、field、radio、checkbox
onInput(e, key) {
console.log(e);
let value = e.detail;
this.formModel[key] = value;
this.checkField(key);
},
//slider
onInputValue(e, key) {
console.log(e);
let value = e.detail.value;
this.formModel[key] = value;
this.checkField(key);
},
//datetime-picker
onInputDate(e, key, controlLey) {
let date = new Date(e.detail);
this.formModel[key] = `${date.getFullYear()}-${
date.getMonth() + 1 >= 10
? date.getMonth() + 1
: "0" + (date.getMonth() + 1)
}-${date.getDate() >= 10 ? date.getDate() : "0" + date.getDate()}`;
this.checkField(key);
//关闭对应弹出框
if (controlLey) {
this.controlItem[controlLey] = false;
}
},
//图片
onInputImg(e, key) {
this.formModel[key].push(e.detail.file);
},
onDelImg(e, key) {
this.formModel[key].splice(e.detail.index, 1);
},
//验证单项
checkField(key) {
if (this.validate.checkField(key, this.formModel)) {
delete this.objValidateErr[key];
} else {
this.objValidateErr[key] = this.validate.errorList[0].msg;
}
},
onSubmit(e) {
let submit = this.operation.filter((it) => it.type == "submit")[0];
submit.fun(this);
},
touchFun(fun, item) {
fun(item);
},
},
};
</script>
2. 校验文件的使用
官方文档:https://github.com/wux-weapp/wx-extend/blob/master/docs/components/validate.md
- 引入文件
js
import WxValidate from "/src/libs/common/WxValidate.js";
- 使用
js
//表单对应的字段名
const formModel = {
name: "",
desc: "",
phone: "",
password: "",
open: true,
sex: "",
likes: [],
date: "",
score: null,
num: 1,
speed: 0,
image: [],
oss_img: [],
oss_file: [],
};
//表单对应字段的校验规则
const formRules = {
name: { required: true, maxlength: 5 },
desc: { required: true, maxlength: 50 },
phone: { required: true, tel: true },
password: { required: true, password: /^[0-9|a-z|A-Z]{6,16}$/ },
open: { required: true },
sex: { required: true },
likes: { required: true, maxlength: 3 },
date: {
required: true,
minDate: new Date().getTime(),
maxDate: new Date("2023-12-12").getTime(),
},
score: { required: true },
num: { required: true, max: 10 },
speed: { required: true, min: 1 },
image: { required: true },
oss_img: { required: true },
};
//校验规则的错误提示
const validateMessage = {
name: {
required: "请输入名称",
maxLength: "不能多于5个字符",
},
desc: {
required: "请输入描述",
maxLength: "不能多于50个字符",
},
phone: {
required: "请输入手机号",
tel: "请输入正确的手机号码",
},
password: {
required: "请输入密码",
},
open: {},
sex: {
required: "请选择性别",
},
likes: {
required: "请选择爱好",
maxlength: "最多选择3个",
},
date: {
required: "请选择日期",
},
score: {
required: "请选择评分",
},
num: {
required: "请选择数量",
max: "数量不能大于10",
},
speed: {
required: "请选择进度",
max: "进度不能小于1",
},
image: { required: "请上传图片" },
oss_img: { required: "请上传图片" },
};
const validate = new WxValidate(formRules, validateMessage);
// 自定义验证规则
validate.addMethod(
"password",
(value, param) => {
return param.test(value);
},
"请输入6~16位由数字、大小字母组成的密码"
);
校验全部
js
validate.checkForm(formModel);
校验单项
js
validate.checkField(key, data);
- 内置的校验规则
序号 | 规则 | 描述 |
---|---|---|
1 | required: true | 这是必填字段。 |
2 | email: true | 请输入有效的电子邮件地址。 |
3 | tel: true | 请输入 11 位的手机号码。 |
4 | url: true | 请输入有效的网址。 |
5 | date: true | 请输入有效的日期。 |
6 | dateISO: true | 请输入有效的日期(ISO),例如:2009-06-23,1998/01/22。 |
7 | number: true | 请输入有效的数字。 |
8 | digits: true | 只能输入数字。 |
9 | idcard: true | 请输入 18 位的有效身份证。 |
10 | equalTo: 'field' | 输入值必须和 field 相同。 |
11 | contains: 'ABC' | 输入值必须包含 ABC。 |
12 | minlength: 5 | 最少要输入 5 个字符。 |
13 | maxlength: 10 | 最多可以输入 10 个字符。 |
14 | rangelength: [5, 10] | 请输入长度在 5 到 10 之间的字符。 |
15 | min: 5 | 请输入不小于 5 的数值。 |
16 | max: 10 | 请输入不大于 10 的数值。 |
17 | range: [5, 10] | 请输入范围在 5 到 10 之间的数值。 |
- 方法
名称 | 返回类型 | 描述 |
---|---|---|
checkForm(formModel) | boolean | 验证所有字段的规则,返回验证是否通过。 |
checkField(key,data) | boolean | 验证所有字段的规则,返回验证是否通过。 |
valid() | boolean | 返回验证是否通过。 |
size() | number | 返回错误信息的个数。 |
validationErrors() | array | 返回所有错误信息。 |
addMethod(name, method, message) | boolean | 添加自定义验证方法。 |
3. 示例项目 SVN 地址
uni-app 项目:svn://39.107.13.238:12580/base_digi
示例
/_uniapp/src/pages/demo/add/wxapp/add.vue