背景
在日常的管理系统开发中,我们会经常实现 form搜索+table展示的场景,通过elementUI,我们也可以快速实现自己的需求,但是如果遇见大量类似的页面,不断地去复制,粘贴el-form和el-table,确实是一件让人烦躁的事情,那么有什么办法可以让我们从这代码搬运的环境中解脱出来嘛,这就是我本次想要分享的主题,通过封装一个搜索组件,通过数据去动态渲染我们的搜索组件,同时抛出我们需要的各种搜索事件,实现效果如图所示
1.组件需求分析
- 组件接收一个filter参数【对象】(v-model形式的,目的:可以实时的将用户的操作数据告诉父组件)
- 组件接收一个formItemArr【数组】(目的:根据传入的formItemArr可以动态的渲染组件,展示界面)
- elColSpan 一行展示几个搜索组件 gutter 搜索组件的间距 isSeniorSearch 是否展示展开收起
2. 代码实现
vue3
<script setup lang="ts">
import { DefineComponent, computed, ref } from 'vue';
import { SearchItemVO } from '../constants/enum';
import CustomInput from './CustomInput.vue';
import CustomSelect from './CustomSelect.vue';
import CustomRadio from './CustomRadio.vue';
type FilterVo = string | number | undefined | null | unknown[];
interface PropsVO {
filter: Record<string, FilterVo>; // 查询参数
formItemArr: SearchItemVO[]; // 表单数据
elColSpan?: number; // 一行展示几个表单控件
gutter?: number; // 表单控件距离
isSeniorSearch?: boolean; // 是否需要展示,收起
}
const props = withDefaults(defineProps<PropsVO>(), {
elColSpan: 6,
gutter: 12,
isSeniorSearch: true,
});
const emit = defineEmits<{
(e: 'update:filter', filter: Record<string, FilterVo>): void;
(e: 'init'): void;
(e: 'search'): void;
}>();
const isShow = ref(false);
const useFormItemArr = computed(() => {
const isshowLess = props.isSeniorSearch && !isShow.value;
if (isshowLess) {
const num = Math.floor(24 / props.elColSpan) - 1;
return props.formItemArr.slice(0, num);
} else {
return props.formItemArr;
}
});
const filter = computed({
get: () => props.filter,
set: (val) => emit('update:filter', val),
});
const componentsMap = new Map([
['input', CustomInput],
['select', CustomSelect],
['radio', CustomRadio],
]);
const getComponent: (type: string) => DefineComponent = (type: string) => {
return componentsMap.get(type);
};
</script>
<template>
<section class="bg-white container br-4 overflow-hidden">
<el-form :model="filter">
<el-row class="search-wrap" :gutter="props.gutter">
<el-col v-for="item in useFormItemArr" :key="item.prop" :span="item.elColSpan || elColSpan">
<el-form-item :label="`${item.label}:`" :prop="item.prop">
// 根据组件类型动态的渲染组件
<component :is="getComponent(item.type)" v-model:value="filter[item.prop]" :item="item" />
</el-form-item>
</el-col>
<el-col :span="elColSpan" style="flex: 1; max-width: 100%">
<div class="flex justify-content-end align-items-center">
<div
v-if="isSeniorSearch"
class="cursor-pointer m-bottom-12 senior-search"
@click="isShow = !isShow"
>
<span>{{ isShow ? '关闭筛选' : '展开筛选' }}</span>
<div class="flex align-items-center m-left-4">
<el-icon>
<ArrowUpBold v-if="isShow" />
<ArrowDownBold v-else />
</el-icon>
</div>
</div>
<el-button class="m-left-10 m-bottom-12" @click="$emit('init')">重置</el-button>
<el-button type="primary" class="m-bottom-12" @click="$emit('search')">查询</el-button>
</div>
</el-col>
</el-row>
</el-form>
</section>
</template>
<style lang="less" scoped>
.container {
padding: 12px 12px 0 12px;
.search-wrap {
display: flex;
flex-wrap: wrap;
}
.senior-search {
line-height: 32px;
display: flex;
justify-content: center;
}
}
</style>
补充一下ts类型
ts
export type SearchType = 'input' | 'select' | 'radio';
export interface SearchItemOptionsVO {
label: string;
value: number | string;
disabled?: boolean;
}
export interface ChangeParamsVO {
prop: string;
val: unknown;
}
export interface SearchItemVO {
label: string;
prop: string;
type: SearchType;
elColSpan?: number; // 展示列的长度,默认一行 24
options?: SearchItemOptionsVO[] | (() => SearchItemOptionsVO[]); // select的选项
onChange?: (changeParams: ChangeParamsVO) => void; // 搜索框更改事件
config?: Record<string, unknown>;
}
自定义 input select radio (如果有其他需求,可以自己添加)
CustomInput(vue3)
<script setup lang="ts">
import { computed, reactive } from 'vue';
import { SearchItemVO } from '../constants/enum';
export type ValueVO = unknown;
const prop = defineProps<{
value: ValueVO;
item: SearchItemVO;
}>();
const emit = defineEmits<{
(e: 'update:value', value: ValueVO): void;
}>();
const value = computed({
get: () => prop.value as string,
set: (value: ValueVO) => emit('update:value', value),
});
const config = reactive({
placeholder: `请输入${prop.item.label}`,
...(prop.item.config || {}),
});
const changeValue = (val: unknown) => {
if (prop.item.onChange) {
prop.item.onChange({
prop: prop.item.prop,
val,
});
}
};
</script>
<template>
<el-input v-model="value" v-bind="config" @change="(val) => changeValue(val)" />
</template>
<style lang="less" scoped></style>
CustomSelect(vue3)
<script setup lang="ts">
import { computed, reactive } from 'vue';
import { SearchItemVO } from '../constants/enum';
export type ValueVO = unknown;
const prop = defineProps<{
value: ValueVO;
item: SearchItemVO;
}>();
const emit = defineEmits<{
(e: 'update:value', value: ValueVO): void;
}>();
const value = computed({
get: () => prop.value as string,
set: (value: ValueVO) => emit('update:value', value),
});
const config = reactive({
placeholder: `请输入${prop.item.label}`,
...(prop.item.config || {}),
});
const changeValue = (val: unknown) => {
if (prop.item.onChange) {
prop.item.onChange({
prop: prop.item.prop,
val,
});
}
};
const options = computed(() => {
if (prop.item.options) {
if (Array.isArray(prop.item.options)) {
return prop.item.options;
} else {
return prop.item.options();
}
} else {
return [];
}
});
</script>
<template>
<el-select v-model="value" v-bind="config" @change="(val) => changeValue(val)">
<el-option
v-for="v in options"
:key="v.value"
:label="v.label"
:value="v.value"
:disabled="v.disabled || false"
>
</el-option>
</el-select>
</template>
<style lang="less" scoped></style>
CustomRadio(vue3)
<script setup lang="ts">
import { computed, reactive } from 'vue';
import { SearchItemVO } from '../constants/enum';
export type ValueVO = unknown;
const prop = defineProps<{
value: ValueVO;
item: SearchItemVO;
}>();
const emit = defineEmits<{
(e: 'update:value', value: ValueVO): void;
}>();
const value = computed({
get: () => prop.value as string,
set: (value: ValueVO) => emit('update:value', value),
});
const config = reactive({
placeholder: `请输入${prop.item.label}`,
...(prop.item.config || {}),
});
const options = computed(() => {
if (prop.item.options) {
if (Array.isArray(prop.item.options)) {
return prop.item.options;
} else {
return prop.item.options();
}
} else {
return [];
}
});
const changeValue = (val: unknown) => {
if (prop.item.onChange) {
prop.item.onChange({
prop: prop.item.prop,
val,
});
}
};
</script>
<template>
<el-radio-group v-model="value" v-bind="config" @change="(val) => changeValue(val)">
<el-radio v-for="v in options" :key="v.value" :label="v.value">{{ v.label }}</el-radio>
</el-radio-group>
</template>
<style lang="less" scoped></style>
3.使用
vue3
<script lang="ts">
export default {
name: 'FormShow',
};
</script>
<script setup lang="ts">
import { reactive } from 'vue';
import SearchContainer from './components/SearchContainer.vue';
import { ChangeParamsVO, SearchItemVO } from './constants/enum';
const stateFilter = {
name: '',
gender: 'male',
phone: '',
address: '',
productType: '',
};
const filter = reactive({ ...stateFilter });
const initFilter = () => {
Object.assign(filter, { ...stateFilter });
};
const searchFilter = () => {
console.log('filter', filter);
};
const changeValue = (params: ChangeParamsVO): void => {
console.log('1111', params);
};
const formItemArr: SearchItemVO[] = [
{
label: '姓名',
prop: 'name',
type: 'input',
config: {
// placeholder: '1111', 设置一些组件的默认值
},
onChange: (changeParams) => changeValue(changeParams),
},
{
label: '性别',
prop: 'gender',
type: 'radio',
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' },
],
onChange: (changeParams) => changeValue(changeParams),
},
{
label: '产品类型',
prop: 'productType',
type: 'select',
options: () => {
return [
{ label: 'A', value: 'a' },
{ label: 'B', value: 'b', disabled: true },
{ label: 'C', value: 'c' },
];
},
onChange: (changeParams) => changeValue(changeParams),
},
{
label: '电话',
prop: 'phone',
type: 'input',
onChange: (changeParams) => changeValue(changeParams),
},
{
label: '地址',
prop: 'address',
type: 'input',
onChange: (changeParams) => changeValue(changeParams),
},
];
</script>
<template>
<section class="h-full bg-gray p-12">
{{ filter }}
<search-container
v-model:filter="filter"
:form-item-arr="formItemArr"
@init="initFilter"
@search="searchFilter"
></search-container>
</section>
</template>
<style lang="less" scoped></style>
4.代码仓库
github.com/maqianbo-we... 需要查看效果的小伙伴可以克隆一份去看看