Form 表单
用于组织表单项。
基础用法
可以控制 labelWidth
来设置 Label
宽度。
查看代码
vue
<template>
<demo-container>
<!-- 控制器 -->
<gov-form>
<gov-form-item label="Label宽度">
<gov-radio-group button v-model="labelWidth">
<gov-radio :value="80">80px</gov-radio>
<gov-radio :value="100">100px</gov-radio>
<gov-radio :value="120">120px</gov-radio>
</gov-radio-group>
</gov-form-item>
</gov-form>
<hr />
<!-- 表单内容 -->
<gov-form :labelWidth="labelWidth">
<gov-form-item label="姓名">
<gov-input v-model="formData.name" />
</gov-form-item>
<gov-form-item label="详细地址">
<gov-textarea v-model="formData.address" placeholder="请输入" />
</gov-form-item>
<gov-form-item>
<gov-button type="primary"> 提交 </gov-button>
</gov-form-item>
</gov-form>
</demo-container>
</template>
<script setup>
import { ref, reactive } from "vue";
const labelWidth = ref(80);
const formData = reactive({
name: null,
address: null,
});
</script>
对齐方式
控制台
labelPosition:right
查看代码
vue
<template>
<demo-container>
<!-- 控制器 -->
<gov-form>
<gov-form-item label="对齐方式">
<gov-radio-group button v-model="labelPosition">
<gov-radio value="left">左对齐</gov-radio>
<gov-radio value="right">右对齐</gov-radio>
<gov-radio value="top">顶部对齐</gov-radio>
</gov-radio-group>
</gov-form-item>
</gov-form>
<hr />
<!-- 表单内容 -->
<gov-form :labelPosition="labelPosition">
<gov-form-item label="姓名">
<gov-input v-model="formData.name" />
</gov-form-item>
<gov-form-item label="详细地址">
<gov-textarea v-model="formData.address" placeholder="请输入" />
</gov-form-item>
<gov-form-item>
<gov-button type="primary"> 提交 </gov-button>
</gov-form-item>
</gov-form>
<template #console> labelPosition:{{ labelPosition }} </template>
</demo-container>
</template>
<script setup>
import { ref, reactive } from "vue";
const labelPosition = ref("right");
const formData = reactive({
name: null,
address: null,
});
</script>
响应式布局
Form
和 FormItem
基于 Row
和 cal
组件,这在布局搜索项时非常有用,你可以在不同尺寸屏幕下控制表现形式,更多设置请参考Grid 栅格化。
查看代码
vue
<template>
<demo-container>
<!-- 控制器 -->
<gov-form>
<gov-form-item label="显示几列">
<gov-radio-group button v-model="formItemSpan">
<gov-radio :value="24">一列</gov-radio>
<gov-radio :value="12">两列</gov-radio>
<gov-radio :value="8">三列</gov-radio>
<gov-radio :value="6">四列</gov-radio>
</gov-radio-group>
</gov-form-item>
</gov-form>
<hr />
<!-- 表单内容 -->
<gov-form labelWidth="50px">
<gov-form-item label="标题" :span="formItemSpan">
<gov-input v-model="formData.title" />
</gov-form-item>
<gov-form-item label="地址" :span="formItemSpan">
<gov-input v-model="formData.address" />
</gov-form-item>
<gov-form-item label="产品" :span="formItemSpan">
<gov-input v-model="formData.product" />
</gov-form-item>
<gov-form-item label="其它" :span="formItemSpan">
<gov-input v-model="formData.other" />
</gov-form-item>
</gov-form>
<template>formItemSpan:{{ formItemSpan }}</template>
</demo-container>
</template>
<script setup>
import { ref, reactive } from "vue";
const formItemSpan = ref(12);
const formData = reactive({
title: null,
address: null,
product: null,
other: null,
});
</script>
表单校验
在防止用户犯错的前提下,尽可能让用户更早地发现并纠正错误。
更多高级用法可参考 async-validator。
控制台
validState:
invalidFields:
查看代码
vue
<template>
<demo-container>
<gov-form ref="ruleFormRef" :model="formData" :rules="formRules">
<gov-form-item prop="name" label="姓名">
<gov-input v-model="formData.name" />
</gov-form-item>
<gov-form-item prop="address" label="详细地址">
<gov-textarea v-model="formData.address" placeholder="请输入" />
</gov-form-item>
<gov-form-item>
<gov-button @click="handleSubmit" type="primary">
提交
</gov-button>
<gov-button @click="handleReset"> 重置 </gov-button>
<gov-button @click="handleClearValidate"> 清除验证 </gov-button>
</gov-form-item>
</gov-form>
<template #console>
validState: {{ validState }}<br />
invalidFields: {{ invalidFields }}
</template>
</demo-container>
</template>
<script setup>
import { ref, reactive } from "vue";
const ruleFormRef = ref();
const validState = ref(null);
const invalidFields = ref(null);
// 表单数据
const formData = reactive({
name: null,
address: null,
});
// 表单验证
const formRules = reactive({
name: [
{
required: true,
message: "请输入姓名!",
trigger: ["blur", "input", "change"],
},
{
min: 3,
max: 5,
message: "限制3-5个字符!",
trigger: ["blur", "input", "change"],
},
],
address: [
{
required: true,
message: "该项为必填项!",
trigger: ["blur", "input", "change"],
},
],
});
// 提交并验证
const handleSubmit = () => {
ruleFormRef.value.validate((valid, fields) => {
validState.value = valid;
invalidFields.value = fields;
});
};
// 重置
const handleReset = () => {
validState.value = null;
invalidFields.value = null;
ruleFormRef.value.resetFields();
};
// 清除表单验证
const handleClearValidate = () => {
validState.value = null;
invalidFields.value = null;
ruleFormRef.value.clearValidate();
};
</script>
动态表单项
除了在 Form 组件上一次性传递所有的验证规则外,还可以在单个的表单域上传递属性的验证规则。
控制台
validState:
invalidFields:
查看代码
注意:第 11
行,prop
属性允许引用表单数据中的深层值,只需按照对象路径使用点.
分隔各个层级,例如你可以使用 domains.0.value
来对应 formData.domains[0].value
的值。
vue
<template>
<demo-container>
<gov-form ref="ruleFormRef" :model="formData" :rules="formRules">
<gov-form-item prop="name" label="姓名">
<gov-input v-model="formData.name" />
</gov-form-item>
<gov-form-item
v-for="(domain, index) in formData.domains"
:label="'域名' + index"
:key="index"
:prop="'domains.' + index + '.value'"
:rules="{
required: true,
message: `域名${index}不能为空!`,
trigger: ['blur', 'input', 'change'],
}"
>
<div style="display: flex; justify-content: space-between">
<gov-input v-model="domain.value" />
<gov-button @click.prevent="removeDomain(domain)">
删除
</gov-button>
</div>
</gov-form-item>
<gov-form-item>
<gov-button @click="addDomain">新增域名</gov-button>
<gov-button @click="handleSubmit" type="primary">
提交
</gov-button>
<gov-button @click="handleReset"> 重置 </gov-button>
<gov-button @click="handleClearValidate"> 清除验证 </gov-button>
</gov-form-item>
</gov-form>
<template #console>
validState: {{ validState }}<br />
invalidFields: {{ invalidFields }}
</template>
</demo-container>
</template>
<script setup>
import { ref, reactive } from "vue";
const ruleFormRef = ref();
const validState = ref(null);
const invalidFields = ref(null);
// 表单数据
const formData = reactive({
name: null,
domains: [
{
value: "",
},
],
});
// 表单验证
const formRules = reactive({
name: [
{
required: true,
message: "请输入姓名!",
trigger: ["blur", "input", "change"],
},
{
min: 3,
max: 5,
message: "限制3-5个字符!",
trigger: ["blur", "input", "change"],
},
],
});
// 新增域名
const addDomain = () => {
formData.domains.push({
value: "",
key: Date.now(),
});
};
// 删除域名
const removeDomain = (item) => {
var index = formData.domains.indexOf(item);
if (index !== -1) {
formData.domains.splice(index, 1);
}
};
// 提交并验证
const handleSubmit = () => {
ruleFormRef.value.validate((valid, fields) => {
validState.value = valid;
invalidFields.value = fields;
});
};
// 重置
const handleReset = () => {
validState.value = null;
invalidFields.value = null;
ruleFormRef.value.resetFields();
};
// 清除表单验证
const handleClearValidate = () => {
validState.value = null;
invalidFields.value = null;
ruleFormRef.value.clearValidate();
};
</script>
完整表单
该表单包含了所有预定表单项、表单验证、尺寸、布局、是否可用等控制。
控制台
validState:
invalidFields:
查看代码
vue
<template>
<demo-container>
<!-- 控制器 -->
<div>
<gov-radio-group button v-model="formSize">
<gov-radio value="large">大尺寸</gov-radio>
<gov-radio value="default">默认尺寸</gov-radio>
<gov-radio value="small">小尺寸</gov-radio>
</gov-radio-group>
是否禁用:<gov-switch v-model="formDisabled" />
</div>
<hr />
<!-- 表单内容 -->
<gov-form
ref="ruleFormRef"
:model="formData"
:rules="formRules"
:size="formSize"
:disabled="formDisabled"
>
<gov-form-item prop="name" label="姓名">
<gov-input v-model="formData.name" />
</gov-form-item>
<gov-form-item prop="sex" label="性别">
<gov-radio-group v-model="formData.sex">
<gov-radio value="1">男生</gov-radio>
<gov-radio value="2">女生</gov-radio>
</gov-radio-group>
</gov-form-item>
<gov-form-item prop="hobby" label="爱好">
<gov-checkbox-group v-model="formData.hobby">
<gov-checkbox value="1">篮球</gov-checkbox>
<gov-checkbox value="2">唱歌</gov-checkbox>
<gov-checkbox value="3">跳舞</gov-checkbox>
</gov-checkbox-group>
</gov-form-item>
<gov-form-item prop="fruit" label="喜欢水果">
<gov-input-auto
v-model="formData.fruit"
:fetch="querySearch"
placeholder="请选择喜欢的水果"
/>
</gov-form-item>
<gov-form-item prop="orderTotal" label="订购">
<gov-input-number
v-model="formData.orderTotal"
controls
:step="500"
suffix="件"
placeholder="请填写"
/>
</gov-form-item>
<gov-form-item prop="deliveryType" label="配送">
<gov-select v-model="formData.deliveryType">
<gov-select-option label="货到付款" value="1" />
<gov-select-option label="先买后付" value="2" />
<gov-select-option label="在线支付" value="3" />
</gov-select>
</gov-form-item>
<gov-form-item prop="address" label="地址">
<gov-cascader
v-model="formData.address"
:options="locationTree"
/>
</gov-form-item>
<gov-form-item prop="addressInfo" label="详细地址">
<gov-textarea
v-model="formData.addressInfo"
placeholder="请输入"
/>
</gov-form-item>
<gov-form-item prop="deliveryDate" label="配送日期">
<gov-datepicker
v-model="formData.deliveryDate"
format="yyyy-MM-dd"
placeholder="请选择日期"
/>
</gov-form-item>
<gov-form-item prop="immediateDelivery" label="立即配送">
<gov-switch v-model="formData.immediateDelivery" />
</gov-form-item>
<gov-form-item prop="rateNum" label="评分">
<gov-rate v-model="formData.rateNum" />
</gov-form-item>
<gov-form-item prop="files" label="附件">
<gov-upload
v-model="formData.files"
:uploadRequest="simulateUpload"
append="上传文件最大 500KB"
/>
</gov-form-item>
<gov-form-item>
<gov-button @click="handleSubmit" type="primary">
提交
</gov-button>
<gov-button @click="handleReset"> 重置 </gov-button>
<gov-button @click="handleValidateFields">
验证部分表单
</gov-button>
<gov-button @click="handleClearValidate">
清除表单验证
</gov-button>
</gov-form-item>
</gov-form>
<template #console>
validState: {{ validState }}<br />
invalidFields: {{ invalidFields }}
</template>
</demo-container>
</template>
<script setup>
import { ref, reactive } from "vue";
import rules from "./rules.js";
import { fruits } from "./fruits.js";
import locationTree from "./locationTree.js";
// import uploadFile from "./axiosUpload.js";
import uploadFile from "./simulateUpload.js";
const ruleFormRef = ref();
const formSize = ref("default");
const formDisabled = ref(false);
const validState = ref(null);
const invalidFields = ref(null);
const formData = reactive({
name: null,
sex: null,
hobby: null,
fruit: "",
orderTotal: null,
deliveryType: null,
address: [],
addressInfo: null,
deliveryDate: null,
immediateDelivery: true,
rateNum: null,
files: [],
});
// 表单验证规则
const formRules = reactive(rules);
// 自动补全 fetch
const querySearch = (str = "") => {
return fruits.filter((el) => el.toLowerCase().includes(str.toLowerCase()));
};
// 上传请求
function simulateUpload(file, fileId, onProgress) {
return uploadFile({ myfile: file }, onProgress).then((response) => {
// 返回 url 预览图片;返回 response 后端数据。
return {
url: "/logo.png",
response,
};
});
}
// 提交并验证
const handleSubmit = () => {
ruleFormRef.value.validate((valid, fields) => {
validState.value = valid;
invalidFields.value = fields;
});
};
// 重置
const handleReset = () => {
validState.value = null;
invalidFields.value = null;
ruleFormRef.value.resetFields();
};
// 验证部分表单
const handleValidateFields = () => {
ruleFormRef.value.validateFields(["name", "sex"], (valid, fields) => {
validState.value = valid;
invalidFields.value = fields;
});
};
// 清除表单验证
const handleClearValidate = () => {
validState.value = null;
invalidFields.value = null;
ruleFormRef.value.clearValidate();
};
</script>
js
export default {
name: [
{
required: true,
message: "请输入姓名!",
trigger: ["blur", "input", "change"],
},
{
min: 3,
max: 5,
message: "限制3-5个字符!",
trigger: ["blur", "input", "change"],
},
],
sex: [
{
required: true,
message: "请选择性别!",
trigger: ["blur", "change"],
},
],
hobby: [
{
required: true,
message: "请选择爱好!",
trigger: ["blur", "change"],
},
],
fruit: [
{
required: true,
message: "请选择喜欢的水果!",
trigger: ["blur", "input", "change"],
},
],
orderTotal: [
{
required: true,
message: "请选择订购数!",
trigger: ["blur", "input", "change"],
},
],
deliveryType: [
{
required: true,
message: "请选择配送方式!",
trigger: ["blur", "input", "change"],
},
],
address: [
{
required: true,
message: "请选择配送方式!",
trigger: ["blur", "input", "change"],
},
],
addressInfo: [
{
required: true,
message: "该项为必填项!",
trigger: ["blur", "input", "change"],
},
],
deliveryDate: [
{
required: true,
message: "该项为必填项!",
trigger: ["blur", "input", "change"],
},
],
immediateDelivery: [
{
required: true,
type: "boolean",
// 自定义校验函数,确保值必须是 true
validator: (rule, value, callback) => {
if (value !== true) {
return callback(new Error("你必须打开此项!"));
}
callback();
},
trigger: ["blur", "change"],
},
],
rateNum: [
{
required: true,
message: "该项为必填项!",
trigger: "change",
},
],
files: [
{
required: true,
message: "该项为必填项!",
trigger: "change",
},
],
};
js
/**
* axios 插件上传文件的函数示例
* @param {Object} files - 文件对象,键为字段名,值为文件(File)对象
* @param {Function} onProgress - 进度回调函数
* @returns {Promise}
*/
import axios from "axios";
function uploadFile(files, onProgress) {
const formData = new FormData();
// 循环遍历文件对象,并将文件添加到FormData中
Object.keys(files).forEach((key) => {
formData.append(key, files[key]);
});
return axios({
method: "post",
url: "你的上传接口URL", // 注意替换成你的上传接口URL
data: formData,
headers: { "Content-Type": "multipart/form-data" },
onUploadProgress: ({ loaded, total }) => {
const percentCompleted = Math.round((loaded * 100) / total);
onProgress(percentCompleted);
},
})
.then((response) => {
if (response && response.data) {
return response.data;
} else {
throw new Error("Upload failed");
}
})
.catch((error) => {
throw error;
});
}
export default uploadFile;
js
/**
* 模拟上传文件的函数
* @param {Object} files - 文件对象,键为字段名,值为文件(File)对象
* @param {Function} onProgress - 进度回调函数
* @returns {Promise}
*/
function uploadFile(files, onProgress) {
const formData = new FormData();
// 循环遍历文件对象,并将文件添加到FormData中
Object.keys(files).forEach((key) => {
formData.append(key, files[key]);
});
return new Promise((resolve, reject) => {
// 模拟上传进度
let total = 0;
const interval = setInterval(() => {
if (total < 100) {
total += 10;
onProgress(total);
} else {
clearInterval(interval);
// 模拟随机的成功或失败
const success = Math.random() > 0.5 ? true : false;
if (success) {
// 设置预览地址 url;服务器数据 response
resolve({
message: "这是后端返回来的数据",
});
} else {
reject(new Error("Upload failed"));
}
}
}, 500);
});
}
export default uploadFile;
js
export const fruits = [
"Apple",
"Banana",
"Cherry",
"Date",
"Elderberry",
"Fig",
"Grape",
"Honeydew",
"Kiwi",
"Lemon",
"Mango",
"Nectarine",
"Orange",
"Papaya",
"Quince",
"Raspberry",
"Strawberry",
"Tomato",
"Ugli fruit",
"Vanilla",
"Watermelon",
"Xigua",
"Yumberry",
"Zucchini",
];
js
export default [
{
value: "shanghai",
label: "上海",
children: [
{
value: "shanghai_huangpu",
label: "黄浦区",
children: [
{
value: "shanghai_huangpu_street1",
label: "街道1",
},
{
value: "shanghai_huangpu_street2",
label: "街道2",
},
],
},
{
value: "shanghai_xuhui",
label: "徐汇区",
children: [
{
value: "shanghai_xuhui_street1",
label: "街道1",
},
{
value: "shanghai_xuhui_street2",
label: "街道2",
},
],
},
],
},
{
value: "jiangsu",
label: "江苏",
children: [
{
value: "nanjing",
label: "南京",
children: [
{
value: "nanjing_xuanwu",
label: "玄武区",
children: [
{
value: "nanjing_xuanwu_street1",
label: "街道1",
},
{
value: "nanjing_xuanwu_street2",
label: "街道2",
},
],
},
{
value: "nanjing_qinhuai",
label: "秦淮区",
children: [
{
value: "nanjing_qinhuai_street1",
label: "街道1",
},
{
value: "nanjing_qinhuai_street2",
label: "街道2",
},
],
},
],
},
{
value: "suzhou",
label: "苏州",
children: [
{
value: "suzhou_gusu",
label: "姑苏区",
children: [
{
value: "suzhou_gusu_street1",
label: "街道1",
},
{
value: "suzhou_gusu_street2",
label: "街道2",
},
],
},
{
value: "suzhou_industry",
label: "工业园区",
children: [
{
value: "suzhou_industry_street1",
label: "街道1",
},
{
value: "suzhou_industry_street2",
label: "街道2",
},
],
},
],
},
],
},
];
Form Attributes
属性名 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
model | 表单的数据对象 | Object | — | — |
rules | 表单验证规则 | Object | — | — |
size | 表单的尺寸 | String | large, default, small | default |
disabled | 是否禁用整个表单 | Boolean | true, false | false |
Form Methods
方法名 | 说明 | 参数 | 返回值 |
---|---|---|---|
validate | 触发表单验证 | (callback): Function(validateState,invalidFields) | 无 |
validateFields | 触发表单某个字段的验证 | (fields, callback): Array[String], Function | 无 |
resetFields | 重置表单字段的值 | — | 无 |
clearValidate | 清除表单验证信息 | — | 无 |
Form Slots
插槽名 | 说明 | 作用域插槽 | 内容 |
---|---|---|---|
default | 表单内容,用于放置表单控件 | 否 | — |
FormItem Attributes
属性名 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
prop | 表单域 model 字段 | String | — | — |
label | 标签文本 | String | — | — |
rules | 表单验证规则 | Object | — | — |
labelPosition | 标签位置 | String | — | — |
labelWidth | 标签宽度 | String/Number | — | — |
size | 尺寸 | String | — | — |
disabled | 是否禁用 | Boolean | true, false | false |
span | 栅格占位格数 | Number | 0-24 | 24 |
FormItem Methods
方法名 | 说明 | 回调参数 |
---|---|---|
validate | 触发表单验证 | (callback): Function(validateState,invalidFields) |
clearValidate | 清除表单验证 | — |
resetField | 重置表单域的值 | — |
FormItem Slots
插槽名 | 说明 | 作用域插槽 | 内容 |
---|---|---|---|
label | 自定义标签内容 | 否 | 可以放置自定义的标签内容 |
validate | 自定义验证错误信息 | 是 | 可以自定义验证错误信息的展示,提供了 validateMessage 作为插槽内容 |
default | 表单控件内容 | 否 | 放置表单控件,如 input、select 等 |
如何自定义表单控件?
预定义的表单项很可能并不满足项目的日常需求,例如:
- 1、富文本编辑器;
- 2、自定义控件;
- 3、组合式表单项,例如在线合同,某个表单项通常由多个表单项组合而成;
这些都需要和 Form
和 FormItem
控件交互,例如触发表单验证,控制 disabled
和 size
。