Appearance
头部搜索栏
原理:通过传递配置文件searchConfig(数组),遍历里面的数据,每一项都是一个element-UI组件。等需要触发搜索请求的时候,通过ref 获取到该组件的实例以及所需的searchData。
1. 效果图
2. 父组件
2.1 父组件 template 中的调用
xml
<!-- 头部搜索栏 -->
<PenkSearch ref="refPenkSearch" :searchConfig="searchConfig">
<el-button type="primary" @click="getList">搜索</el-button>
<el-button @click="handleAddItem">新增</el-button>
</PenkSearch>
可以看到,只有简单的searchConfig配置,就可以了,其中2个button,是通过插槽默认是在搜索后面追加,样式也在组件中编码了,可以省掉很多步骤。
2.2. 父组件 script 中的调用
2.2.1 获取搜索组件中的查询对象queryObj
调用的时候就是根据获取的ref实例,使用暴露出来的对象里面的数据即可,主要代码就是下面两句...
const refPenkSearch = ref(); ...refPenkSearch.value.queryObj
typescript
// 搜索框ref实例
const refPenkSearch = ref();
// 表格数据
let tableData = reactive([]);
// 查找数据
async function getList() {
// 清空数据
tableData.length = 0;
console.log("tableData1:", tableData);
// 判断是否有对象,没有的话就自动弄
let res = await http.getList({
...refPenkSearch.value.queryObj,
pageNum: paginationData.pageNum,
pageSize: paginationData.pageSize,
});
// @ts-ignore
tableData.push(...res.rows);
paginationData.total = res.count;
console.log("tableData:", tableData);
}
tips:这边要注意reactive 创建的tabelData,不能使用直接赋值,因为是个proxy对象~
2.2.2 使用的配置
这边有的配置是可填可不填的,具体的配置项可以看封装组件里面的参数,这边大概看一下
type:类型,判断是什么UI组件
prop:很关键的,用来绑定该UI组件对应属性名!!!
data:用来存放可选的UI组件的配置,比如下拉框,级联之类...
- label:在UI组件前面的label
- width:UI组件的宽度
- hidden:是否隐藏,有些业务需求是根据不同角色,一些是不展示的
- clearabel:是否可清空数据
json
const searchConfig = [
{
type: "select",
label: "父级用户类型",
width: 150,
prop: "parentId",
hidden: false,
clearable: true,
data: [],
},
{
type: "input",
label: "类型名",
placeholder: "请输入类型名",
width: 150,
clearable: true,
prop: "typeName",
hidden: false,
},
{
type: "select",
label: "是否查找软删除",
placeholder: "请选择",
width: 150,
prop: "paranoid",
hidden: false,
clearable: true,
data: [
{
value: 0,
label: "是",
},
{
value: 1,
label: "否",
},
],
},
];
3. 组件的封装
3.1 封装组件template
3.1.1 请求组件的配置
- searchConfig:是个对象数组,用于遍历出一个个请求需要的组件,目前只列出了input跟select,可按需增加。
- searchConfig.data:用来给那些可选的组件,比如select或者级联...
- queryObj:是封装组件内的一个reactive对象,并且通过defineExpose 暴露给了父组件。
- searchConfig.props是每一项的键名,queryObj通过字符串索引的方式,将至设置成键名,并且通过v-model将键值绑定到该每一项的UI组件上。
xml
<template v-for="item in props.searchConfig" :key="item.prop">
<div
class="penk-search-item"
v-if="item.hidden != true"
>
<span>{{ item.label }}:</span>
<!-- 输入框 -->
<el-input
v-if="item.type == 'input'"
v-model="queryObj[item.prop]"
:placeholder="item.placeholder || ''"
:clearable="item.clearable"
:disabled="item.disabled"
:style="{ width: item.width + 'px' }"
/>
<!-- 下拉框 -->
<el-select
v-else-if="item.type == 'select'"
v-model="queryObj[item.prop]"
:placeholder="item.placeholder || ''"
:clearable="item.clearable"
:disabled="item.disabled"
filterable
:style="{ width: item.width + 'px' }"
>
<el-option
v-for="option in item.data"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
</template>
3.1.2 预留的插槽
主要用于存放按钮,比如添加,查询按钮...
xml
<!-- 默认尾部插槽 -->
<div class="penk-search-footer">
<slot></slot>
</div>
3.2 封装组件script
3.2.1 props属性的配置
由于使用了TS来编码,咋们可以在组件中interface中写清楚一些配置,方便使用者知道有什么配置 :)
typescript
// 这边主要是申明了是个对象数组,并且每个对象定义是按照searchItem这个接口来的
interface Props {
searchConfig: searchItem[];
}
// 每一个搜索条件对应的一些参数
interface searchItem {
// 必填
// 标签名
label: string;
// 组件所需类型,下拉框或者输入框
type: string;
// 对应的属性名 如: queryObj.name ~
prop: string;
// 选填-通用
// 宽度
width?: number;
// 占位符
placeholder?: string;
// 是否清空
clearable?: boolean;
// 是否使能
disabled?: boolean;
// 是否隱藏
hidden?: boolean;
// 选填-特殊
// 数据,一般是下拉框之类需要可选项的才用到
data?: any;
}
3.2.2 组件内的queryObj变量
typescript
interface queryObj {
[index: string]: any;
}
// queryObj 就是一个搜索条件的对象,里面每一个属性就是一个搜索条件
// 设置私有属性,防止被修改~
const queryObj = reactive<queryObj>({});
// 将queryObj暴露出去,父组件才可以调用
defineExpose({
queryObj,
});
总结
- 封装很简单,关联性不强,通过searchConfig传递给子组件,就可以生成相应的头部搜索栏视图。
- 搜索栏的操作,只会同步到组件内的queryObj对象。
- 父组件想要获取queryObj的时候,只需通过ref获取即可...
当然,你也可以实现双向绑定的功能,但是每一个父组件,是不是都要声明一个变量呢?这个变量好像也没什么其他的用处了...
源码
typescript
<!--
* @Author: Penk
* @LastEditors: Penk
* @LastEditTime: 2022-11-29 13:31:21
* @FilePath: \front-master\src\components\public\PenkSearch.vue
* @email: 492934056@qq.com
-->
<template lang="">
<div class="penk-search-box">
<!-- 头部插槽 -->
<slot name="header"></slot>
<template v-for="item in searchConfig" :key="item.prop">
<div
class="penk-search-item"
:style="{ display: item.hidden == true ? 'none' : '' }"
>
<span>{{ item.label }}:</span>
<!-- 输入框 -->
<el-input
v-if="item.type == 'input'"
v-model="queryObj[item.prop]"
:placeholder="item.placeholder || ''"
:clearable="item.clearable"
:disabled="item.disabled"
:style="{ width: item.width + 'px' }"
/>
<!-- 下拉框 -->
<el-select
v-else-if="item.type == 'select'"
v-model="queryObj[item.prop]"
:placeholder="item.placeholder || ''"
:clearable="item.clearable"
:disabled="item.disabled"
filterable
:style="{ width: item.width + 'px' }"
>
<el-option
v-for="option in item.data"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
</template>
<!-- 默认尾部插槽 -->
<div class="penk-search-footer">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from "vue";
// 每一个搜索条件的对象
interface queryObj {
[index: string]: any;
}
// 申明是个搜索条件的对象
interface searchItem {
// 必填
// 标签名
label: string;
// 组件所需类型,下拉框或者输入框
type: string;
// 对应的属性名 如: queryObj.name ~
prop: string;
// 选填-通用
// 宽度
width?: number;
// 占位符
placeholder?: string;
// 是否清空
clearable?: boolean;
// 是否使能
disabled?: boolean;
// 是否隱藏
hidden?: boolean;
// 选填-特殊
// 数据,一般是下拉框之类需要可选项的才用到
data?: any;
}
interface Props {
searchConfig: searchItem[];
}
// 定义props
let props = defineProps<Props>();
// 设置私有属性,防止被修改~
const queryObj = reactive<queryObj>({});
// 将queryObj暴露出去,父组件才可以调用
defineExpose({
queryObj,
});
</script>
<style lang="less" scoped>
.penk-search-box {
margin-bottom: 10px;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
.penk-search-item {
margin-right: 20px;
margin-bottom: 10px;
}
.penk-search-footer {
margin-bottom: 10px;
& > {
margin-right: 20px;
}
}
}
</style>