Skip to content

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>

响应式布局

FormFormItem 基于 Rowcal 组件,这在布局搜索项时非常有用,你可以在不同尺寸屏幕下控制表现形式,更多设置请参考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" />&nbsp;&nbsp;
					<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>
			&nbsp; 是否禁用:<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表单的尺寸Stringlarge, default, smalldefault
disabled是否禁用整个表单Booleantrue, falsefalse

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是否禁用Booleantrue, falsefalse
span栅格占位格数Number0-2424

FormItem Methods

方法名说明回调参数
validate触发表单验证(callback): Function(validateState,invalidFields)
clearValidate清除表单验证
resetField重置表单域的值

FormItem Slots

插槽名说明作用域插槽内容
label自定义标签内容可以放置自定义的标签内容
validate自定义验证错误信息可以自定义验证错误信息的展示,提供了 validateMessage 作为插槽内容
default表单控件内容放置表单控件,如 input、select 等

如何自定义表单控件?

预定义的表单项很可能并不满足项目的日常需求,例如:

  • 1、富文本编辑器;
  • 2、自定义控件;
  • 3、组合式表单项,例如在线合同,某个表单项通常由多个表单项组合而成;

这些都需要和 FormFormItem 控件交互,例如触发表单验证,控制 disabledsize

查看如何自定义一个表单项 Editor

Released under the MIT License.