简述
在后台管理中最常用的是表格,而表格的数据一般是需要一定的条件,因此封装这个条件搜索,条件搜索可以是输入框、选择框、时间选择、日期选择等等。
既然包含输入框、选择框、时间选择、日期选择组件,那么我们就需要简单封装一下输入框、选择框、时间选择、日期选择组件,需要对组件添加label
输入框
开发一个兼容element plus所有的属性并添加label的功能
简单布局
首先显编写输入框的布局
<script setup name="eInp" lang="ts">
import { ref } from "vue";
let input = ref("");
</script>
<template>
<div class="eInp">
<div class="label">label</div>
<el-input v-model="input" style="width: 240px" placeholder="Please input" />
</div>
</template>
<style lang="scss" scoped>
.eInp {
display: flex;
align-items: center;
width: 100%;
.inpRow {
flex: 1;
}
}
</style>
设置传递的参数
需要自定义label并且需要设置是否显示:、label的宽度以及输入框中的占位符,并且需要再父组件传入,因此在组件中需要通过props接受
<script setup name="eInp" lang="ts">
import { ref } from "vue";
defineProps({
// label
label: {
type: String,
default: "label",
},
// 是否显示冒号
isColon: {
type: Boolean,
default: true,
},
// label width
labelWidth: {
type: Number,
default: 60,
},
placeholder: {
type: String,
default: "请输入",
},
});
let input = ref("");
</script>
<template>
<div class="eInp">
<div class="label" :style="{ width: labelWidth + 'px !important' }">
{{ label }}{{ isColon ? ":" : "" }}
</div>
<el-input v-model="input" :placeholder />
</div>
</template>
<style lang="scss" scoped>
.eInp {
display: flex;
align-items: center;
width: 100%;
.inpRow {
flex: 1;
}
}
</style>
设置双向绑定
接下来对组件添加v-model
<script setup name="eInp" lang="ts">
import { ref } from "vue";
defineProps({
modelValue: {
type: String,
default: "",
},
// label
label: {
type: String,
default: "label",
},
// 是否显示冒号
isColon: {
type: Boolean,
default: true,
},
// label width
labelWidth: {
type: Number,
default: 60,
},
placeholder: {
type: String,
default: "请输入",
},
});
let emit = defineEmits(["update:modelValue"]);
const changeData = val => {
emit("update:modelValue", val);
};
</script>
<template>
<div class="eInp">
<div class="label" :style="{ width: labelWidth + 'px !important' }">
{{ label }}{{ isColon ? ":" : "" }}
</div>
<el-input :model-value="modelValue" :placeholder @input="changeData" />
</div>
</template>
<style lang="scss" scoped>
.eInp {
display: flex;
align-items: center;
width: 100%;
.inpRow {
flex: 1;
}
}
</style>
添加失焦、获焦、清除事件
通过emit将事件传递给父组件,通过v-bind="$attrs"接受props中没有接受的参数
<script setup name="eInp" lang="ts">
import { ref } from "vue";
defineProps({
modelValue: {
type: String,
default: "",
},
// label
label: {
type: String,
default: "label",
},
// 是否显示冒号
isColon: {
type: Boolean,
default: true,
},
// label width
labelWidth: {
type: Number,
default: 60,
},
placeholder: {
type: String,
default: "请输入",
},
});
let emit = defineEmits(["update:modelValue", "blur", "focus", "clear"]);
// 绑定的数据
let inpText = ref<any>(null);
const changeData = val => {
emit("update:modelValue", val);
};
const blurFn = () => {
emit("blur", inpText.value);
};
const focusFn = () => {
emit("focus", inpText.value);
};
const clearFn = () => {
emit("clear", inpText.value);
};
</script>
<template>
<div class="eInp">
<div class="label" :style="{ width: labelWidth + 'px !important' }">
{{ label }}{{ isColon ? ":" : "" }}
</div>
<el-input
v-bind="$attrs"
:model-value="modelValue"
:placeholder
@input="changeData"
@blur="blurFn"
@focus="focusFn"
@clear="clearFn"
/>
</div>
</template>
<style lang="scss" scoped>
.eInp {
display: flex;
align-items: center;
width: 100%;
.inpRow {
flex: 1;
}
}
</style>
暴漏方法到父组件
有时候需要再父组件调用子组件的方法
<script setup name="eInp" lang="ts">
import { ref } from "vue";
defineProps({
modelValue: {
type: [String, Number],
default: "",
},
// label
label: {
type: String,
default: "label",
},
// 是否显示冒号
isColon: {
type: Boolean,
default: true,
},
// label width
labelWidth: {
type: Number,
default: 60,
},
placeholder: {
type: String,
default: "请输入",
},
});
let emit = defineEmits(["update:modelValue", "blur", "focus", "clear"]);
// 绑定的数据
let inpText = ref<any>(null);
const changeData = val => {
emit("update:modelValue", val);
};
const blurFn = () => {
emit("blur", inpText.value);
};
const focusFn = () => {
emit("focus", inpText.value);
};
const clearFn = () => {
emit("clear", inpText.value);
};
// 绑定的ref
let inpRef = ref();
// 组件获取失焦
const blur = () => {
inpRef.value.blur();
};
// 组件清除数据
const clear = () => {
inpRef.value.clear();
};
// 组件获取焦点
const focus = () => {
inpRef.value.focus();
};
// 选择组件的文本
const select = () => {
inpRef.value.select();
};
// 暴漏方法
defineExpose({ blur, clear, focus, select });
</script>
<template>
<div class="eInp">
<div class="label" :style="{ width: labelWidth + 'px !important' }">
{{ label }}{{ isColon ? ":" : "" }}
</div>
<el-input
ref="inpRef"
v-bind="$attrs"
:model-value="modelValue"
:placeholder
@input="changeData"
@blur="blurFn"
@focus="focusFn"
@clear="clearFn"
/>
</div>
</template>
<style lang="scss" scoped>
.eInp {
display: flex;
align-items: center;
width: 100%;
.inpRow {
flex: 1;
}
}
</style>
选择器
接下来开发选择器组件,同样需要设置label、label的宽度等等,同时还需要传入下拉列表以及下拉列表的配置
<script setup name="eSelect" lang="ts">
import { ref } from "vue";
defineProps({
modelValue: {
type: [String, Number, Array, Object],
default: "",
},
// label
label: {
type: String,
default: "label",
},
// 是否显示冒号
isColon: {
type: Boolean,
default: true,
},
// label width
labelWidth: {
type: Number,
default: 60,
},
placeholder: {
type: String,
default: "请输入",
},
// 列表
options: {
type: Array as () => any[],
default: () => [],
},
// 选项对象
optionObject: {
type: Object,
default: () => ({
label: "label",
value: "value",
key: "key",
}),
},
});
let emit = defineEmits(["update:modelValue", "blur", "focus", "clear"]);
// 绑定的数据
let selectData = ref<any>(null);
const changeData = val => {
selectData.value = val;
emit("update:modelValue", val);
};
const blurFn = () => {
emit("blur", selectData.value);
};
const focusFn = () => {
emit("focus", selectData.value);
};
const clearFn = () => {
emit("clear", selectData.value);
};
let eSelectRef = ref();
const focus = () => {
eSelectRef.value.focus();
};
const blur = () => {
eSelectRef.value.blur();
};
defineExpose({ blur, focus });
</script>
<template>
<div class="eSelect">
<div class="label" :style="{ width: labelWidth + 'px !important' }">
{{ label }}{{ isColon ? ":" : "" }}
</div>
<el-select
:model-value="modelValue"
:placeholder
v-bind="$attrs"
@focus="focusFn"
@change="changeData"
@blur="blurFn"
@clear="clearFn"
ref="eSelectRef"
class="selectRow"
>
<el-option
v-for="item in options"
:key="item[optionObject.key]"
:label="item[optionObject.label]"
:value="item[optionObject.value]"
/>
</el-select>
</div>
</template>
<style lang="scss" scoped>
.eSelect {
display: flex;
align-items: center;
width: 100%;
.selectRow {
flex: 1;
}
}
</style>
时间选择
开发时间选择组件,配置和上面的基本上一样
<script setup name="timeSelect" lang="ts">
import { ref } from "vue";
defineProps({
modelValue: {
type: [String, Number, Array, Object],
default: "",
},
// label
label: {
type: String,
default: "label",
},
// 是否显示冒号
isColon: {
type: Boolean,
default: true,
},
// label width
labelWidth: {
type: Number,
default: 60,
},
placeholder: {
type: String,
default: "请输入",
},
});
let timeData = ref<any>(null);
let emit = defineEmits(["change", "blur", "focus", "clear"]);
const change = val => {
timeData.value = val;
emit("change", val);
};
const blurFn = () => {
emit("blur", timeData.value);
};
const focusFn = () => {
emit("focus", timeData.value);
};
const clearFn = () => {
emit("clear", timeData.value);
};
let timeSelect = ref<any>(null);
const blur = () => {
timeSelect.value.blur();
};
const focus = () => {
timeSelect.value.focus();
};
const handleOpen = () => {
timeSelect.value.handleOpen();
};
const handleClose = () => {
timeSelect.value.handleClose();
};
defineExpose({
blur,
focus,
handleOpen,
handleClose,
});
</script>
<template>
<div class="timeSelect">
<div class="label" :style="{ width: labelWidth + 'px !important' }">
{{ label }}{{ isColon ? ":" : "" }}
</div>
<el-time-picker
:model-value="modelValue"
v-bind="$attrs"
:placeholder
@change="change"
@blur="blurFn"
@focus="focusFn"
@clear="clearFn"
ref="timeSelect"
class="timeSelectRow"
/>
</div>
</template>
<style lang="scss" scoped>
.timeSelect {
display: flex;
align-items: center;
// justify-content: space-between;
width: 100%;
.timeSelectRow {
flex: 1;
}
}
</style>
日期选择
<script setup name="dateSelect" lang="ts">
import { ref, watch } from "vue";
let props = defineProps({
modelValue: {
type: [String, Number, Array, Object],
default: "",
},
// label
label: {
type: String,
default: "label",
},
// 是否显示冒号
isColon: {
type: Boolean,
default: true,
},
// label width
labelWidth: {
type: Number,
default: 60,
},
placeholder: {
type: String,
default: "请输入",
},
type: {
type: String,
default: "date",
},
});
let dateSelect = ref<any>();
watch(
() => props.modelValue,
() => {
dateSelect.value = props.modelValue;
},
);
let emit = defineEmits(["update:modelValue", "blur", "focus", "clear"]);
const changeData = () => {
emit("update:modelValue", dateSelect.value);
};
const blurFn = () => {
emit("blur", dateSelect.value);
};
const focusFn = () => {
emit("focus", dateSelect.value);
};
const clearFn = () => {
emit("clear", dateSelect.value);
};
let dateSelectRef = ref();
const focus = () => {
dateSelectRef.value.focus();
};
const handleOpen = () => {
dateSelectRef.value.handleOpen();
};
const handleClose = () => {
dateSelectRef.value.handleClose();
};
defineExpose({ focus, handleOpen, handleClose });
</script>
<template>
<div class="dateSelect">
<div class="label" :style="{ width: labelWidth + 'px !important' }">
{{ label }}{{ isColon ? ":" : "" }}
</div>
<el-date-picker
v-model="dateSelect"
:placeholder
:type
@change="changeData"
@focus="focusFn"
@blur="blurFn"
@clear="clearFn"
ref="dateSelectRef"
class="dateSelectRow"
/>
</div>
</template>
<style lang="scss" scoped>
.dateSelect {
display: flex;
align-items: center;
width: 100%;
.dateSelectRow {
flex: 1;
}
}
</style>
条件搜索组件
组件配置
在这个组件中我们需要通过一个配置项来生成条件搜索组件,在这个配置中通过某个字段来决定渲染那个组件
配置代码如下:
let dispositionList = ref([
{
type: "eInp", // 组件名称 组件类型
field: "name", // 需要设置的字段
// 其他参数 子组件的参数
attribute: {
placeholder: "请输入账号",
label: "账号",
},
},
{
type: "eSelect",
field: "sex",
label: "性别",
attribute: {
placeholder: "请选择性别",
label: "性别",
},
// 下拉选项
options: [
{
label: "男",
value: "1",
},
{
label: "女",
value: "2",
},
],
// 下拉选项配置
optionObject: {
label: "label",
value: "value",
key: "value",
},
},
{
type: "timeSelect",
field: "time",
attribute: {
label: "时间",
placeholder: "请选择时间",
},
},
{
type: "dateSelect",
field: "dateSelect",
attribute: {
label: "日期",
placeholder: "请选择日期",
},
},
]);
动态渲染组件
使用动态组件渲染
<template v-for="(item, index) in dispositionList" :key="index">
<component
class="item"
:is="item.type"
v-model="formData[item.field]"
:options="item.options"
:optionObject="item.optionObject"
v-bind="item.attribute"
:style="{ width: itemWidth + 'px !important', height: itemHeight + 'px !important' }"
></component>
</template>
这样基本上就完成了 需要注意的是配置项要和组件进行匹配
插槽
既然是搜索组件,那么就需要显示搜索和取消按钮,但是也有可能是需要在使用的时候传递,因此接下来编写插槽部分
如果没有传递内容,就使用插槽
<div class="operateRow">
<!-- 使用插槽 -->
<slot>
<div class="operateRowDefault" v-if="isModelValue">
<el-button v-if="searchBtn" v-bind="searchOpt" @click="search">{{
searchText
}}</el-button>
<el-button v-if="cancelBtn" v-bind="cancelOpt" @click="cancel">{{
cancelText
}}</el-button>
</div>
</slot>
</div>
事件
接下来开始编写事件,点击按钮的时候需要将数据返回到父组件或清除数据,还有一种形式是不需要按钮触发,在数据发生变化的时候直接触发,那么就需要监听绑定的数据,当数据发生变化的时候就返回到父组件
let emit = defineEmits(["update:modelValue", "search", "cancel"]);
let formData = ref({});
watch(
() => formData.value,
val => {
// console.log("双向绑定", val);
emit("update:modelValue", val);
},
{
deep: true,
},
);
// 搜索按钮
const search = () => {
// console.log("查询", formData.value);
emit("search", JSON.parse(JSON.stringify(formData.value)));
};
// 取消按钮
const cancel = () => {
formData.value = {};
// console.log("取消", formData.value);
emit("cancel", {});
};
// 清除数据
const clear = () => {
formData.value = {};
emit("cancel", {});
};
总结
经过以上条件搜索组件就开发完成了,如果需要筛选项添加对应的组件就好了
搜索组件代码
<script setup name="conditionalSearch">
import { ref, watch } from "vue";
defineProps({
itemWidth: {
type: Number,
default: 200,
},
itemHeight: {
type: Number,
default: 32,
},
// 搜索按钮
searchBtn: {
type: Boolean,
default: true,
},
searchText: {
type: String,
default: "搜索",
},
// 搜索按钮配置
searchOpt: {
type: Object,
default: () => ({
type: "primary",
}),
},
// 取消按钮
cancelBtn: {
type: Boolean,
default: true,
},
cancelText: {
type: String,
default: "取消",
},
// 取消按钮配置
cancelOpt: {
type: Object,
default: () => ({}),
},
// 组件配置
dispositionList: {
type: Array,
default: () => [],
},
// 是否双向绑定
isModelValue: {
type: Boolean,
default: true,
},
});
let emit = defineEmits(["update:modelValue", "search", "cancel"]);
let formData = ref({});
watch(
() => formData.value,
val => {
// console.log("双向绑定", val);
emit("update:modelValue", val);
},
{
deep: true,
},
);
// 搜索按钮
const search = () => {
// console.log("查询", formData.value);
emit("search", JSON.parse(JSON.stringify(formData.value)));
};
// 取消按钮
const cancel = () => {
formData.value = {};
// console.log("取消", formData.value);
emit("cancel", {});
};
// 清除数据
const clear = () => {
formData.value = {};
emit("cancel", {});
};
// 暴漏方法
defineExpose({ clear });
</script>
<template>
<div class="conditionalSearch">
<template v-for="(item, index) in dispositionList" :key="index">
<component
class="item"
:is="item.type"
v-model="formData[item.field]"
:options="item.options"
:optionObject="item.optionObject"
v-bind="item.attribute"
:style="{ width: itemWidth + 'px !important', height: itemHeight + 'px !important' }"
></component>
</template>
<div class="operateRow">
<!-- 使用插槽 -->
<slot>
<div class="operateRowDefault" v-if="isModelValue">
<el-button v-if="searchBtn" v-bind="searchOpt" @click="search">{{
searchText
}}</el-button>
<el-button v-if="cancelBtn" v-bind="cancelOpt" @click="cancel">{{
cancelText
}}</el-button>
</div>
</slot>
</div>
</div>
</template>
<style lang="scss" scoped>
.conditionalSearch {
display: flex;
align-items: stretch;
justify-content: flex-start;
flex-wrap: wrap;
position: relative;
.item {
margin: 5px;
display: flex;
align-items: center;
justify-content: space-between;
text-align: center;
}
.operateRow {
display: flex;
align-items: center;
justify-content: center;
padding: 5px;
margin: 5px;
box-sizing: border-box;
position: absolute;
right: 0;
bottom: 0;
margin-bottom: 0;
}
}
</style>