vue3-表单查询封装(快速实现一个搜索查询)

背景

在日常的管理系统开发中,我们会经常实现 form搜索+table展示的场景,通过elementUI,我们也可以快速实现自己的需求,但是如果遇见大量类似的页面,不断地去复制,粘贴el-form和el-table,确实是一件让人烦躁的事情,那么有什么办法可以让我们从这代码搬运的环境中解脱出来嘛,这就是我本次想要分享的主题,通过封装一个搜索组件,通过数据去动态渲染我们的搜索组件,同时抛出我们需要的各种搜索事件,实现效果如图所示

1.组件需求分析

  1. 组件接收一个filter参数【对象】(v-model形式的,目的:可以实时的将用户的操作数据告诉父组件)
  2. 组件接收一个formItemArr【数组】(目的:根据传入的formItemArr可以动态的渲染组件,展示界面)
  3. 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... 需要查看效果的小伙伴可以克隆一份去看看

相关推荐
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
2401_8576009511 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_8576009511 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL11 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js
轻口味11 小时前
Vue.js 核心概念:模板、指令、数据绑定
vue.js
2402_8575834911 小时前
基于 SSM 框架的 Vue 电脑测评系统:照亮电脑品质之路
前端·javascript·vue.js
java_heartLake12 小时前
Vue3之性能优化
javascript·vue.js·性能优化
ddd君3177413 小时前
组件的声明、创建、渲染
vue.js
前端没钱14 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js