本人是一个web前端开发工程师,主要是vue框架,整理了一些Vue常用的技术,一方面是分享,一方面是做总结,今后也会一直更新,有好建议的同学欢迎评论区分享 ;-)
Vue专栏:点击此处
Vue组件库专栏:点击此处
Vue2 vs Vue3 专栏:点击此处
Typescript专栏:点击此处
Vue组件库专栏会按顺序执行一下流程,不断完善组件库开发流程
环境状态
vue版本:vue3
是否使用 ts:是
后台管理系统的网站,一个页面无非就是4个常用业务块
那咋们是不是可以将其进行封装成组件呢?
只需要传入一个配置文件就可以了~
项目解构如下:
封装后的展示图如下:
效果图如下:
如果能做成这样,是不是页面就会整洁很多?
原理:通过传递配置文件searchConfig(数组),遍历里面的数据,每一项都是一个element-UI组件。等需要触发搜索请求的时候,通过ref 获取到该组件的实例以及所需的searchData。
<PenkSearch ref="refPenkSearch" :searchConfig="searchConfig">
<el-button type="primary" @click="getList">搜索el-button>
<el-button @click="handleAddItem">新增el-button>
PenkSearch>
可以看到,只有简单的searchConfig配置,就可以了,其中2个button,是通过插槽默认是在搜索后面追加,样式也在组件中编码了,可以省掉很多步骤。
调用的时候就是根据获取的ref实例,使用暴露出来的对象里面的数据即可,主要代码就是下面两句…
const refPenkSearch = ref();
…refPenkSearch.value.queryObj
// 搜索框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对象~
这边有的配置是可填可不填的,具体的配置项可以看封装组件里面的参数,这边大概看一下
type:类型,判断是什么UI组件
prop:很关键的,用来绑定该UI组件对应属性名!!!
data:用来存放可选的UI组件的配置,比如下拉框,级联之类...
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: "否",
},
],
},
];
<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>
主要用于存放按钮,比如添加,查询按钮…
<div class="penk-search-footer">
<slot>slot>
div>
由于使用了TS来编码,咋们可以在组件中interface中写清楚一些配置,方便使用者知道有什么配置 :)
// 这边主要是申明了是个对象数组,并且每个对象定义是按照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;
}
interface queryObj {
[index: string]: any;
}
// queryObj 就是一个搜索条件的对象,里面每一个属性就是一个搜索条件
// 设置私有属性,防止被修改~
const queryObj = reactive<queryObj>({});
// 将queryObj暴露出去,父组件才可以调用
defineExpose({
queryObj,
});
当然,你也可以实现双向绑定的功能,但是每一个父组件,是不是都要声明一个变量呢?这个变量好像也没什么其他的用处了...
<!--
* @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>