功能
-
Input输入框
-
autocomplete自动补齐输入框
-
radio 单选框
-
checkbox 复选框
-
date 日期选择框
-
select 下拉框
-
如需添加更多功能参考elementPlus或者根据业务需求自行
自定义组件
效果图
目录结构
input
ts
<template>
<el-input
v-bind="$attrs"
v-model="modelValue"
w-full
@blur="props.blur ? props.blur($event) : false"
@focus="props.focus ? props.focus($event) : false"
@change="props.change ? props.change($event) : false"
@input="props.input ? props.input($event) : false"
@clear="props.clear ? props.clear() : false"
/>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
modelValue: {
type: String,
default: () => "",
},
blur: {
type: Function,
default: () => () => {},
},
focus: {
type: Function,
default: () => () => {},
},
change: {
type: Function,
default: () => () => {},
},
input: {
type: Function,
default: () => () => {},
},
clear: {
type: Function,
default: () => () => {},
},
});
const modelValue = ref(props.modelValue);
//监听父组件的值
watch(
() => props.modelValue,
() => {
modelValue.value = props.modelValue;
},
);
// 通过emit将值传递给父组件
emit("update:modelValue", modelValue);
</script>
<style lang="scss" scoped></style>
select
ts
<template>
<el-select
v-model="modelValue"
v-bind="$attrs"
w-full
@change="props.change ? props.change($event) : false"
@visible-change="props.visibleChange ? props.visibleChange($event) : false"
@remove-tag="props.removeTag ? props.removeTag($event) : false"
@clear="props.clear ? props.clear() : false"
@blur="props.blur ? props.blur($event) : false"
@focus="props.focus ? props.focus($event) : false"
>
<el-option
v-for="item in options"
:key="item[valueFiled]"
:label="item[labelFiled]"
:value="item[valueFiled]"
></el-option>
</el-select>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
modelValue: {
type: [String, Array],
default: () => "",
},
options: {
type: Array as any,
default: () => [],
},
valueFiled: {
type: String,
default: "value",
},
labelFiled: {
type: String,
default: "label",
},
change: {
type: Function,
default: () => () => {},
},
visibleChange: {
type: Function,
default: () => () => {},
},
removeTag: {
type: Function,
default: () => () => {},
},
clear: {
type: Function,
default: () => () => {},
},
blur: {
type: Function,
default: () => () => {},
},
focus: {
type: Function,
default: () => () => {},
},
});
const modelValue = ref(props.modelValue);
//监听父组件的值
watch(
() => props.modelValue,
() => {
modelValue.value = props.modelValue;
},
);
// 通过emit将值传递给父组件
emit("update:modelValue", modelValue);
</script>
<style lang="scss" scoped></style>
autocomplete
ts
<template>
<el-autocomplete
v-bind="$attrs"
v-model="modelValue"
style="width: 100%"
@select="props.select ? props.select($event) : false"
@change="props.change ? props.change($event) : false"
/>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
modelValue: {
type: String,
default: () => "",
},
select: {
type: Function,
default: () => () => {},
},
change: {
type: Function,
default: () => () => {},
},
});
const modelValue = ref(props.modelValue);
//监听父组件的值
watch(
() => props.modelValue,
() => {
modelValue.value = props.modelValue;
},
);
// 通过emit将值传递给父组件
emit("update:modelValue", modelValue);
</script>
date
ts
<template>
<el-date-picker
v-model="Val"
v-bind="$attrs"
style="width: 100%"
@change="props.change ? props.change($event) : false"
@blur="props.blur ? props.blur($event) : false"
@focus="props.focus ? props.focus($event) : false"
@calendar-change="props.calendarChange ? props.calendarChange($event) : false"
@panel-change="(a, b, c) => (props.panelChange ? props.panelChange(a, b, c) : false)"
@visible-change="props.visibleChange ? props.visibleChange($event) : false"
></el-date-picker>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
modelValue: {
type: [String, Array, Date],
default: () => "",
},
change: {
type: Function,
default: () => () => {},
},
blur: {
type: Function,
default: () => () => {},
},
focus: {
type: Function,
default: () => () => {},
},
calendarChange: {
type: Function,
default: () => () => {},
},
panelChange: {
type: Function,
default: () => () => {},
},
visibleChange: {
type: Function,
default: () => () => {},
},
});
const Val = ref(props.modelValue);
//监听父组件的值
watch(
() => props.modelValue,
() => {
Val.value = props.modelValue;
},
);
// 通过emit将值传递给父组件
emit("update:modelValue", Val);
</script>
checkbox
ts
<template>
<el-checkbox-group
v-model="Val"
v-bind="$attrs"
@change="props.change ? props.change($event) : false"
>
<template v-if="props.cType === 'button'">
<el-checkbox-button
v-for="item in options"
:key="item[valueFiled]"
:value="item[valueFiled]"
>{{ item[labelFiled] }}</el-checkbox-button
>
</template>
<template v-else>
<el-checkbox
v-for="item in options"
:key="item[valueFiled]"
:value="item[valueFiled]"
:border="props.cType === 'border'"
>{{ item[labelFiled] }}</el-checkbox
>
</template>
</el-checkbox-group>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
modelValue: {
type: Array,
default: () => "",
},
options: {
type: Array as any,
default: () => [],
},
valueFiled: {
type: String,
default: "value",
},
labelFiled: {
type: String,
default: "label",
},
cType: {
type: String,
default: "",
},
change: {
type: Function,
default: () => () => {},
},
});
const Val = ref(props.modelValue);
//监听父组件的值
watch(
() => props.modelValue,
() => {
Val.value = props.modelValue;
},
);
// 通过emit将值传递给父组件
emit("update:modelValue", Val);
</script>
radio
ts
<template>
<el-radio-group
v-model="modelValue"
v-bind="$attrs"
@change="props.change ? props.change($event) : false"
>
<template v-if="props.cType === 'button'">
<el-radio-button v-for="item in options" :key="item[valueFiled]" :value="item[valueFiled]">{{
item[labelFiled]
}}</el-radio-button>
</template>
<template v-else>
<el-radio
v-for="item in options"
:border="props.cType === 'border'"
:key="item[valueFiled]"
:value="item[valueFiled]"
>{{ item[labelFiled] }}</el-radio
>
</template>
</el-radio-group>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
modelValue: {
type: [Number, String, Boolean],
default: () => "",
},
options: {
type: Array as any,
default: () => [],
},
valueFiled: {
type: String,
default: "value",
},
labelFiled: {
type: String,
default: "label",
},
cType: {
//radio类型:button/border
type: String,
default: "",
},
change: {
type: Function,
default: () => () => {},
},
});
const modelValue = ref(props.modelValue);
//监听父组件的值
watch(
() => props.modelValue,
() => {
modelValue.value = props.modelValue;
},
);
// 通过emit将值传递给父组件
emit("update:modelValue", modelValue);
</script>
cascader
ts
<template>
<el-cascader
v-bind="$attrs"
v-model="modelValue"
style="width: 100%"
@change="props.change ? props.change($event) : false"
@expand-change="props.expandChange ? props.expandChange($event) : false"
/>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
modelValue: {
type: Array,
default: () => [],
},
change: {
type: Function,
default: () => () => {},
},
expandChange: {
type: Function,
default: () => () => {},
},
});
const modelValue = ref(props.modelValue);
//监听父组件的值
watch(
() => props.modelValue,
() => {
modelValue.value = props.modelValue;
},
);
// 通过emit将值传递给父组件
emit("update:modelValue", modelValue);
</script>
types
ts
/*
* @Author: vhen
* @Date: 2024-03-24 00:36:03
* @LastEditTime: 2024-03-24 15:21:30
* @Description: 现在的努力是为了小时候吹过的牛逼!
* @FilePath: \vhen-vue3-admin-pro\src\components\SearchForm\types.ts
*
*/
export type FormType = "input" | "select" | "radio" | "cascader" | "autocomplete" | "date" | "daterange" | "checkbox";
export interface ItemOption {
label: string
value: string | number
disabled?: boolean
}
export interface FormItemVO {
type: FormType //输入框类型
label: string //输入框标题
disabled?: boolean//表单是否可修改 默认false
placeholder?: any //输入框默认显示内容
prop: string //表单校验
options?: ItemOption[] | (() => ItemOption[]) //选择器的可选子选项 select
span?: number // 表单栅格数
offset?: number // 表单栅格偏移
clearable?: boolean // 是否可清空
size?: string // 输入框大小
multiple?: boolean // 是否多选
collapseTags?: boolean // 是否折叠
collapseTagsThreshold?: number // 多选时标签的阈值,大于该阈值时折叠
filterable?: boolean // 是否可搜索
allowCreate?: boolean // 是否支持创建
radioType?: string // 单选框类型
}
export interface PropsVO {
formData: object // 表单数据
formItem: FormItemVO[] // 表单配置项
span?: number // 表单栅格数
isSeniorSearch?: boolean // 是否高级搜索
gutter?: number // 表单栅格间隔
showButton?: boolean // 是否显示查询按钮
}
index.vue
html
<template>
<section class="search-form">
<el-form :model="props.formData" v-bind="$attrs">
<el-row :gutter="props.gutter">
<el-col
v-for="column in useFormItem"
:key="column.prop"
:span="column.span"
:offset="column.offset"
>
<el-form-item :label="`${column.label}`" :prop="column.prop">
<component
:is="componentType[column.type]"
v-bind="column"
v-model="props.formData[column.prop]"
/>
</el-form-item>
</el-col>
<template v-if="$slots.default">
<slot />
</template>
<el-col :span="props.span" style="flex: 1; max-width: 100%" v-if="showButton">
<div flex justify="end" items-center w-full h-full>
<div
v-if="isSeniorSearch"
flex
items-center
mr-2
class="senior-search"
@click="isShow = !isShow"
cursor="pointer"
>
<div class="text">{{ isShow ? "收起" : "展开" }}</div>
<div class="flex m-left-4">
<el-icon>
<ArrowUp v-if="isShow" />
<ArrowDown v-else />
</el-icon>
</div>
</div>
<el-button @click="$emit('reset')" :icon="RefreshLeft">重置</el-button>
<el-button type="primary" class="m-bottom-12" @click="$emit('search')" :icon="Search"
>查询</el-button
>
</div>
</el-col>
</el-row>
</el-form>
</section>
</template>
<script lang="ts" setup>
import { RefreshLeft, Search } from "@element-plus/icons-vue";
import { FormInstance } from "element-plus";
import { computed, markRaw, ref } from "vue";
import VhenAutocomplete from "./src/VhenAutocomplete.vue";
import VhenCascader from "./src/VhenCascader.vue";
import VhenCheckbox from "./src/VhenCheckbox.vue";
import VhenDatePicker from "./src/VhenDatePicker.vue";
import VhenInput from "./src/VhenInput.vue";
import VhenRadio from "./src/VhenRadio.vue";
import VhenSelect from "./src/VhenSelect.vue";
import { PropsVO } from "./types";
const emit = defineEmits<{
(e: "reset"): void;
(e: "search"): void;
}>();
const props = withDefaults(defineProps<PropsVO>(), {
isSeniorSearch: false,
gutter: 12,
span: 8,
showButton: true,
});
const isShow = ref(false);
const useFormItem = computed(() => {
const isShowRow = props.isSeniorSearch && !isShow.value && props.showButton;
if (isShowRow) {
const num = Math.floor(24 / props.span) - 1;
return props.formItem.slice(0, num);
} else {
return props.formItem;
}
});
const componentType = markRaw({
input: VhenInput,
select: VhenSelect,
radio: VhenRadio,
cascader: VhenCascader,
autocomplete: VhenAutocomplete,
date: VhenDatePicker,
daterange: VhenDatePicker,
checkbox: VhenCheckbox,
});
const formRef = ref<FormInstance>();
defineExpose({ formRef });
</script>
<style lang="scss" scoped>
.senior-search {
color: var(--el-text-color-regular);
.text {
font-size: 14px;
}
}
</style>
组件案例
html
<template>
<div>
<SearchForm
:formData="formData"
:form-item="formItemList"
:rules="formRules"
:span="6"
label-position="top"
label-width="100px"
isSeniorSearch
@reset="resetData"
@search="handleSearch"
>
</SearchForm>
<pre>
{{ formData }}
</pre>
</div>
</template>
<script lang="ts" setup>
import SearchForm from "@/components/SearchForm/index.vue";
import { FormRules } from "element-plus";
import { reactive } from "vue";
const formData = reactive({
userName: "",
email: "843348394@qq.com",
sex: "1",
area: [],
city: "",
});
const formItemList = reactive([
{
type: "input",
label: "姓名",
prop: "userName",
clearable: true,
span: 6,
placeholder: "请输入姓名",
// disabled: true,
input: handleInput,
},
{
type: "autocomplete",
label: "邮箱",
prop: "email",
span: 6,
"fetch-suggestions": querySearch,
},
{
type: "daterange",
label: "出生日期",
prop: "birthday",
span: 6,
},
{
type: "radio",
label: "-",
prop: "sex",
cType: "button",
span: 6,
options: [
{
value: "0",
label: "男",
},
{
value: "1",
label: "女",
},
],
},
{
type: "checkbox",
label: "工作地点",
prop: "area",
span: 6,
options: [
{
label: "北京",
value: "beijing",
},
{
label: "上海",
value: "shanghai",
},
{
label: "深圳",
value: "shenzhen",
},
],
},
{
type: "select",
prop: "city",
label: "城市",
span: 6,
options: [
{
label: "北京",
value: "beijing",
},
{
label: "上海",
value: "shanghai",
},
{
label: "深圳",
value: "shenzhen",
},
],
},
]);
const resetData = () => {
console.log(formData);
};
const handleSearch = () => {
console.log(formData);
};
const formRules = reactive<FormRules>({
userName: [{ required: true, message: "请输入姓名", trigger: "blur" }],
email: [{ required: true, message: "请输入邮箱", trigger: "blur" }],
});
function handleInput(val: string | number) {
console.log(val);
}
function querySearch(query: string, cb: any) {
console.log(query);
cb([query]);
}
</script>
<style lang="scss" scoped></style>
结束语
简单二次封装form 表单组件,如大家有更好的方案,欢迎大家评论区讨论,一起学习一起成长....