1、背景
vue后台管理系统,会有很多表格页面,表格上方会有一些搜索选项。表格直接使用el-table即可,而搜索栏区域每次写起来都很繁琐,且多人开发情况下每个人写的样式都不相同,布局样式无法统一。所以要考虑对搜索栏做一个封装,统一配置引用,提升开发维护效率和界面统一。
完成后效果图:
2、分析 项目使用的是elementui框架,搜索栏这种表单提交,首先要使用el-form组件来封装,而复杂点就是表单项可能有很多种,例如input输入框、select选择框、日期时间选择框、日期时间范围选择框、cascader级联选择框等,每一项的字段名prop、名称label、绑定的属性方法都不尽相同。所以不能通过普通的绑定个别属性的方式来处理,而slot插槽的方式也无法简化,最终决定通过传递一个配置项数组的形式来解析生成相应的结构。
3、实现 目前实现的方式由两部分组成,一部分是form表单组件,接受父组件传递的配置项数组,一部分是封装一些常用的表单项组件,通过v-if来控制,form表单组件里引入该表单项组件,循环遍历,根据传递的表单项类型来匹配显示具体的表单项。 4、完整代码
(1)、form表单组件(searchForm.vue)代码
/**
* 表单匹配项
*/
v-if="isInput" v-model="currentVal" v-bind="bindProps" v-on="bindEvents" :placeholder="itemOptions.placeholder" >
v-if="isInputNumber" v-model="currentVal" v-bind="bindProps" v-on="bindEvents" :controls-position="itemOptions['controls-position'] || 'right'" >
v-if="isSelect" v-model="currentVal" v-bind="bindProps" v-on="bindEvents" clearable > v-for="item in itemOptions.options" :key="item.value" :label="item.label" :value="item.value" >
v-if="isDatePickerDateRange" v-model="currentVal" v-bind="bindProps" v-on="bindEvents" :type="itemOptions.type || 'datetimerange'" clearable :picker-options="pickerOptionsRange" start-placeholder="开始日期" range-separator="至" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" value-format="yyyy-MM-dd HH:mm:ss" >
v-if="isDatePickerMonthRange" v-model="currentVal" v-bind="bindProps" v-on="bindEvents" :type="itemOptions.type" clearable :picker-options="pickerOptionsRangeMonth" start-placeholder="开始日期" range-separator="至" end-placeholder="结束日期" value-format="yyyy-MM" >
v-if="isDatePickerOthers" v-model="currentVal" v-bind="bindProps" v-on="bindEvents" :type="itemOptions.type" clearable value-format="yyyy-MM-dd" placeholder="请选择日期" style="width: 300px" >
v-if="isCascader" v-model="currentVal" v-bind="bindProps" v-on="bindEvents" size="mini" clearable >
import tools from "@/utils/tools";
export default {
inheritAttrs: false,
props: {
value: {},
itemOptions: {
type: Object,
default() {
return {};
},
},
},
data() {
return {
pickerOptionsRange: tools.pickerOptionsRange,
pickerOptionsRangeMonth: tools.pickerOptionsRangeMonth,
};
},
computed: {
// 双向绑定数据值
currentVal: {
get() {
return this.value;
},
set(val) {
this.$emit("input", val);
},
},
// 绑定属性
bindProps() {
let obj = { ...this.itemOptions };
// 移除冗余属性
delete obj.label;
delete obj.prop;
delete obj.element;
delete obj.initValue;
delete obj.rules;
delete obj.events;
if (obj.element === "el-select") {
delete obj.options;
}
return obj;
},
// 绑定方法
bindEvents() {
return this.itemOptions.events || {};
},
// el-input
isInput() {
return this.itemOptions.element === "el-input";
},
// el-input-number
isInputNumber() {
return this.itemOptions.element === "el-input-number";
},
// el-select
isSelect() {
return this.itemOptions.element === "el-select";
},
// el-date-picker (type: datetimerange/daterange)
isDatePickerDateRange() {
const isDatePicker = this.itemOptions.element === "el-date-picker";
const isDateRange =
!this.itemOptions.type ||
this.itemOptions.type === "datetimerange" ||
this.itemOptions.type === "daterange";
return isDatePicker && isDateRange;
},
// el-date-picker (type: monthrange)
isDatePickerMonthRange() {
const isDatePicker = this.itemOptions.element === "el-date-picker";
const isMonthRange = this.itemOptions.type === "monthrange";
return isDatePicker && isMonthRange;
},
// el-date-picker (type: other)
isDatePickerOthers() {
const isDatePicker = this.itemOptions.element === "el-date-picker";
return (
isDatePicker &&
!this.isDatePickerDateRange &&
!this.isDatePickerMonthRange
);
},
// el-cascader
isCascader() {
return this.itemOptions.element === "el-cascader";
},
},
created() {},
methods: {},
components: {},
};
(2)、formItem表单项组件(formItem.vue)代码
v-for="(item, index) in formOptions" :key="newKeys[index]" :prop="item.prop" :label="item.label ? item.label + ':' : ''" :rules="item.rules" @keyup.enter.native="onSearch" >
v-if="btnItems.includes('search')" size="mini" type="primary" class="btn-search" @click="onSearch" >搜索
>
v-if="btnItems.includes('export')" size="mini" type="primary" class="btn-export" @click="onExport" >导出
>
>重置
>
import formItem from "./formItem";
import { tools } from "@/utils/tools";
export default {
props: {
/**
* 表单配置
* 示例:
* [{
* label: '用户名', // label文字
* prop: 'username', // 字段名
* element: 'el-input', // 指定elementui组件
* initValue: '阿黄', // 字段初始值
* placeholder: '请输入用户名', // elementui组件属性
* rules: [{ required: true, message: '必填项', trigger: 'blur' }], // elementui组件属性
* events: { // elementui组件方法
* input (val) {
* console.log(val)
* },
* ...... // 可添加任意elementui组件支持的方法
* }
* ...... // 可添加任意elementui组件支持的属性
* }]
*/
formOptions: {
type: Array,
required: true,
default() {
return [];
},
},
// 提交按钮项,多个用逗号分隔(search, export, reset)
btnItems: {
type: String,
default() {
return "search";
},
},
},
data() {
return {
formData: {},
};
},
computed: {
newKeys() {
return this.formOptions.map((v) => {
return this.createUniqueString();
});
},
},
created() {
console.log(this.tools);
this.addInitValue();
},
methods: {
createUniqueString() {
const timestamp = +new Date() + "";
const randomNum = parseInt((1 + Math.random()) * 65536) + "";
return (+(randomNum + timestamp)).toString(32);
},
// 校验
onValidate(callback) {
this.$refs.formRef.validate((valid) => {
if (valid) {
console.log("提交成功");
console.log(this.formData);
callback();
}
});
},
// 搜索
onSearch() {
this.onValidate(() => {
this.$emit("onSearch", this.formData);
});
},
// 导出
onExport() {
this.onValidate(() => {
this.$emit("onExport", this.formData);
});
},
onReset() {
this.$refs.formRef.resetFields();
},
// 添加初始值
addInitValue() {
const obj = {};
this.formOptions.forEach((v) => {
if (v.initValue !== undefined) {
obj[v.prop] = v.initValue;
}
});
this.formData = obj;
},
},
components: { formItem },
};
.search-form-box {
display: flex;
margin-bottom: 15px;
.btn-box {
padding-top: 5px;
display: flex;
button {
height: 28px;
}
}
.el-form {
/deep/ .el-form-item__label {
padding-right: 0;
margin-bottom: 10px;
}
.el-form-item {
margin-bottom: 0;
&.is-error {
margin-bottom: 25px;
}
}
// el-input宽度
/deep/ .form-item {
> .el-input:not(.el-date-editor) {
width: 300px;
}
}
/deep/ .el-select {
width: 300px;
}
/deep/ .el-cascader {
width: 200px;
}
}
}
(3)、组件使用
vue data里
formOptions: [
{
label: "学号", // label文字
prop: "XH", // 字段名
element: "el-input", // 指定elementui组件
},
{
label: "类型",
prop: "type",
element: "el-select",
options: [
{ label: "给点意见", value: "1" },
{ label: "售后问题", value: "2" },
],
},
{
label: "状态",
prop: "status",
element: "el-select",
options: [
{ label: "给点意见", value: "1" },
{ label: "售后问题", value: "2" },
],
},
{
label: "提交时间",
type: "date",
prop: "timeRange",
element: "el-date-picker",
},
{
label: "学生",
prop: "students",
element: "el-input",
},
],
vue methods里:
methods: {
// 获取搜索表单提交的数据
onSearch (val) {
console.log(val)
const this.queryParams = Object.assign(this.queryParams, val); // 搜索表格数据
}
}
好文链接
发表评论