大家好我是程序员阿晶啊,我又来了。
前言
在查询表格数据的时候,常常见到表格上方有一个表单区域,用于数据查询条件动态查询表格数据。如下图:

在此,本人查询资料结合Vue3开发了一个可以复用的组件。
可根据需要进行配置表单项(支持input,select,date类型的表单)和表格(支持表头配置)及动态表格数据等。
表单配置组件
表单配置组件:
QueryCondition.vue
<template>
<div style="width: 100%; height: 100%" class="form-condition">
<div class="form-content">
<el-form label-width="100px">
<el-row v-for="(formType, indexs) in formTypeArray" :key="indexs">
<el-col
:span="span"
v-for="(data, index) in formTypeArray[indexs]"
:key="index"
>
<el-form-item :label="data.label">
<!-- 输入框类型 -->
<el-input
v-if="data.type == 'input'"
v-model="formTypeArray[indexs][index].value"
:placeholder="data.placeholder"
></el-input>
<!-- 时间类型 -->
<el-date-picker
popper-class="date-style"
v-if="data.type == 'date'"
type="date"
:placeholder="data.placeholder"
v-model="formTypeArray[indexs][index].value"
></el-date-picker>
<!-- 下拉框类型 -->
<el-select
:popper-append-to-body="false"
v-if="data.type == 'select'"
v-model="formTypeArray[indexs][index].value"
:placeholder="data.placeholder"
>
<el-option
v-for="(item, i) in data.option"
:key="i"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<div v-if="data.type == 'searchAndClear'" id = "searchAndClear">
<div class="content-btn">
<div
class="btn-search"
style="float: left"
@click="getCondition"
>
<i class="el-icon-search"></i>搜索
</div>
<div
class="btn-clean"
style="float: left"
@click="cleanFormValue"
>
清空
</div>
</div>
</div>
</el-col>
</el-row>
</el-form>
</div>
</div>
</template>
<script lang="ts">
import { inject, ref } from "@vue/runtime-core";
import { watchEffect, defineComponent } from "vue";
export default defineComponent({
name: "QueryCondition",
emits: ["conditionParams"],
setup(props: any, context: any) {
let span = ref(6);
let formTypeArray = ref(new Array());
const formTypeConfig = inject("formTypeConfig");
/**
* 给父组件返回表单输入的内容
*/
const getCondition = () => {
context.emit("conditionParams", formTypeArray.value);
};
/**
* 清空表单内容
*/
const cleanFormValue = () => {
formTypeArray.value.forEach((e, index) => {
for (let i = 0; i < e.length; i++) {
formTypeArray.value[index][i].value = "";
}
});
};
// 监听表单配置数据 数据变化时, 更新配置
watchEffect(() => {
console.log("-------监听formTypeConfig--------");
console.log(formTypeConfig);
setFormType(formTypeConfig, 4);
});
/**
* 设置表单类型
* @param array 表单配置
* @param col 每行表单项个数
*/
function setFormType(array: any, col: any) {
if (col == null || col == "" || array == null || array == "") {
col = 4; // 默认分4列
}
span.value = 24 / col;
// 只有一行
if (col >= array.length) {
formTypeArray.value.push(array);
} else {
// 超过一行
let deb = col;
let i = 0;
while (i < array.length) {
let item = []; // null
while (i < deb) {
item.push(array[i]);
console.log(deb);
i++;
}
formTypeArray.value.push(item);
if (array.length - i < col) {
deb = array.length;
} else {
deb = i + col;
}
}
}
console.log("---------表单配置数据 SUCCESS----------");
}
return {
formTypeArray,
span,
getCondition,
cleanFormValue,
};
},
});
</script>
<style>
.form-condition .el-input__inner {
border: 2px solid #285267;
height: 35px;
background-color: #111d30;
}
.form-condition .el-select-dropdown {
background: #1f3758e0;
}
.form-condition .el-select-dropdown__item.hover,
.el-select-dropdown__item:hover {
background-color: #314665a1;
}
.date-style .el-picker__popper .date-style .el-popper .is-light .is-pure {
background: #1f3758e0;
}
.date-style .el-picker-panel {
background: #1f3758e0;
}
.search-third .el-input__inner {
height: 36px;
}
.search-third .el-input {
margin-top: -3px;
}
.search-header .el-input__inner {
border: 2px solid #285267;
height: 35px;
background-color: #111d30;
}
</style>
<style scoped>
.form-condition {
border-radius: 3px 3px 3px 3px;
background: rgba(8, 11, 21, 0.48);
padding-top: 16px;
margin-bottom: 8px;
}
.form-condition .el-dialog__header {
padding-top: 0px;
}
/*事件*/
.btn-search:hover {
color: #aa8383;
background: #2fce7c;
}
.btn-clean:hover {
color: #aa8383;
}
.content-btn > div {
margin-right: 16px;
}
.content-btn > div:first-child {
border: 2px solid #328597;
height: 28px;
width: 69px;
color: #d2cbcb;
text-align: center;
line-height: 28px;
border-radius: 3px 3px 3px 3px;
background-image: linear-gradient(#347489, #0e1d32);
font-size: 10px;
cursor: pointer;
}
.content-btn > div:last-child {
border: 2px solid #415770;
height: 28px;
width: 69px;
color: #d2cbcb;
text-align: center;
line-height: 28px;
border-radius: 3px 3px 3px 3px;
background-color: #1a2942;
font-size: 10px;
cursor: pointer;
}
.search-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
padding: 0 16px;
}
.search-first {
float: left;
border: 1px solid rgba(102, 226, 251, 0.24);
border-radius: 5px 5px 5px 5px;
}
/* 搜索框 */
.search-first > ul {
list-style: none;
display: flow-root;
}
.search-first > ul li {
float: left;
margin: 3px 9px;
line-height: 22px;
}
.search-first > ul li:not(:first-child)::after {
content: "";
width: 1px;
height: 14px;
background: linear-gradient(180deg, #b1d8ff 0%, rgba(177, 216, 255, 0) 100%);
display: inline-block;
position: absolute;
top: 10px;
margin: 0 -9px;
}
.search-first > ul li div {
/*border: 2px solid #328597;*/
height: 22px;
width: 54px;
color: #99bbdf;
text-align: center;
line-height: 27px;
/*border-radius: 3px 3px 3px 3px;*/
/*background-image: linear-gradient(#347489, #0E1D32);*/
font-size: 10px;
cursor: pointer;
}
/* 选中*/
.search-first > ul li:first-child div {
border: 1px solid #328597;
height: 22px;
width: 54px;
color: #d2cbcb;
text-align: center;
line-height: 22px;
border-radius: 2px;
background-image: linear-gradient(
180deg,
rgba(102, 226, 251, 0.4) 0%,
rgba(102, 226, 251, 0) 100%
);
font-size: 10px;
}
.header-right {
display: flex;
align-items: center;
}
.header-right-button {
padding: 5px 10px;
margin-right: 16px;
background: linear-gradient(
180deg,
rgba(102, 226, 251, 0.4) 0%,
rgba(102, 226, 251, 0) 100%
);
border-radius: 2px;
border: 1px solid;
font-size: 12px;
color: #99bbdf;
cursor: pointer;
}
.search-second {
display: flow-root;
float: left;
color: #d4cccc;
margin-top: 1px;
height: 26px;
border: 1px solid #21dde6;
box-shadow: 0 0 0 1px #5d4b4b;
border-radius: 4px 4px 4px 4px;
padding: 5px 4px 1px 7px;
margin-left: 12px;
margin-right: 12px;
cursor: pointer;
}
.search-second > div {
float: left;
}
.search-second div:first-child img {
width: 18px;
height: 19px;
}
.search-second div:last-child img {
width: 20px;
height: 21px;
}
.search-third {
float: left;
margin-top: 1px;
}
.form-content {
padding: 16px 16px 0;
}
#searchAndClear {
display: flex;
align-items: center;
height: 0px;
padding: 0 30px;
margin-left: 20px;
}
</style>
1、上面的formTypeConfig变量会接收从父组件传来的数据配置表单项,所以先从它入手
父组件:
App.vue
provide("formTypeConfig", [
{
label: "用户名称",
type: "input",
placeholder: "请输入. . .",
value: "",
},
{ label: "省份", type: "input", placeholder: "请输入. . .", value: "" },
{ label: "模式", type: "input", placeholder: "请输入. . .", value: "" },
{ label: "行业", type: "input", placeholder: "请输入. . .", value: "" },
{
label: "标签",
type: "select",
placeholder: "请选择",
option: [
{ label: "第一项", value: "1" },
{ label: "第二项", value: "2" },
],
value: "",
},
{
label: "分公司",
type: "select",
placeholder: "请选择",
option: [
{ label: "第一项", value: "1" },
{ label: "第二项", value: "2" },
],
value: "",
},
{
label: "受理员工号",
type: "input",
placeholder: "请输入. . .",
value: "",
},
{ label: "流水号", type: "input", placeholder: "请输入. . .", value: "" },
{ label: "时间", type: "date", placeholder: "请输入. . .", value: "" },
{ label: "", type: "searchAndClear", placeholder: "", value: "" },
]);
在父组件中配置好数据后,表单就能根据配置自己显示出来啦。
2、搜索和清空
搜索绑定getCondition
函数,它会把你输入到表单的数据发送给父组件,具体的搜索操作(数据接口)请在父组件中写,搜索出来的数据再次传给表单组件的formTypeConfig即可实现动态改变数据。
/**
* 给父组件返回表单输入的内容
*/
const getCondition = () => {
context.emit("conditionParams", formTypeArray.value);
};
/**
* 清空表单内容
*/
const cleanFormValue = () => {
formTypeArray.value.forEach((e, index) => {
for (let i = 0; i < e.length; i++) {
formTypeArray.value[index][i].value = "";
}
});
};
在组件中还有一个重要的方法setFormType。他是组件的一些逻辑,可不了解,直接copy,需要注意col参数,它是配置每行显示的表单项个数,可根据需要进行修改。
// 监听表单配置数据 数据变化时, 更新配置
watchEffect(() => {
console.log("-------监听formTypeConfig--------");
console.log(formTypeConfig);
setFormType(formTypeConfig, 4);
});
/**
* 设置表单类型
* @param array 表单配置
* @param col 每行表单项个数
*/
function setFormType(array: any, col: any) {
if (col == null || col == "" || array == null || array == "") {
col = 4; // 默认分4列
}
span.value = 24 / col;
// 只有一行
if (col >= array.length) {
formTypeArray.value.push(array);
} else {
// 超过一行
let deb = col;
let i = 0;
while (i < array.length) {
let item = []; // null
while (i < deb) {
item.push(array[i]);
console.log(deb);
i++;
}
formTypeArray.value.push(item);
if (array.length - i < col) {
deb = array.length;
} else {
deb = i + col;
}
}
}
console.log("---------表单配置数据 SUCCESS----------");
}
表格配置组件
TableDialog.vue
<template>
<div class="content-table" style="width: 100%">
<!-- 表头 -->
<el-table
:data="tableData"
row-class-name="row-style"
header-row-class-name="row-style"
:show-header="isShowHeader"
>
<!--<el-table-column type="selection" width="55"></el-table-column>-->
<el-table-column
v-for="(head, index) in tableHeader"
:key="index"
:label="head.label"
:sortable="head.sortable"
:prop="head.fieldName"
>
</el-table-column>
<el-table-column align="center" width="60px" label="操作">
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="page.currentPage"
:page-sizes="[10, 20, 30, 40]"
:page-size="page.size"
layout="total, sizes, prev, pager, next"
:total="page.total"
popper-class="select_bottom"
>
</el-pagination>
</div>
</div>
</template>
<script>
import {
watchEffect,
defineComponent,
inject,
ref,
reactive,
toRefs,
toRef,
} from "vue";
export default defineComponent({
name: "TableDialog",
emits: ["tableMessage"],
setup(props, context) {
// 数据及配置
let tableData = reactive([]);
let page = reactive({});
let isShowHeader = ref(true);
// 表头配置
let tableHeader = reactive([]);
const tableHeaderConfig = inject("tableHeaderConfig");
const tableDataConfig = inject("tableDataConfig");
// 监听表单配置数据
watchEffect(() => {
console.log("-------监听tableHeaderConfig,tableDataConfig--------");
tableHeader = tableHeaderConfig ? tableHeaderConfig : [];
tableData = tableDataConfig ? tableDataConfig.tableData : [];
page = tableDataConfig ? tableDataConfig.page : {};
});
function submitData(scope) {
context.emit("tableMessage", { page: page, scope: scope });
}
function handleSizeChange(val) {
console.log(`每页 ${val} 条`);
context.emit("tableMessage", { page: page, scope: null });
}
function handleCurrentChange(val) {
console.log(`当前页: ${val}`);
context.emit("tableMessage", { page: page, scope: null });
}
return {
tableHeader,
tableData,
submitData,
handleSizeChange,
handleCurrentChange,
page,
isShowHeader,
};
},
});
</script>
<style scoped>
.content-table :deep(.el-table) {
background: rgba(8, 11, 21, 0.48);
}
:deep(.row-style) {
background: rgba(8, 11, 21, 0.48);
}
:deep(.el-table::before) {
height: 0;
}
.content-table :deep(.el-table th.el-table__cell) {
background: rgba(8, 11, 21, 0.48);
border-bottom: rgba(8, 11, 21, 0.48);
}
.content-table .el-table tr {
background-color: #202c5d00;
font-size: 10px;
}
.content-table :deep(.el-table td.el-table__cell) {
border-bottom: rgba(8, 11, 21, 0.48);
}
.content-table
:deep(.el-table--enable-row-hover
.el-table__body
tr:hover
> td.el-table__cell) {
background-color: #131a2b;
border-bottom: rgba(8, 11, 21, 0.48);
}
.content-table :deep(.el-checkbox__inner) {
background-color: #12203c2b;
}
.pagination {
margin-top: 16px;
text-align: right;
}
.pagination :deep(.el-input__inner) {
border: 2px solid #285267;
height: 32px;
background-color: #111d30;
color: #b1d8ff;
}
.pagination :deep(.el-pager li) {
border: 1px solid #285267;
height: 30px;
color: #b1d8ff;
background-color: #111d30;
}
.pagination :deep(.el-pager li.active) {
background: linear-gradient(
180deg,
rgba(102, 226, 251, 0.4) 0%,
rgba(102, 226, 251, 0) 100%
);
}
.pagination :deep(.el-pagination .btn-prev) {
background-color: #111d30;
height: 30px;
color: #b1d8ff;
border: 1px solid rgba(177, 216, 255, 0.4);
}
.pagination :deep(.el-pagination .btn-next) {
background-color: #111d30;
height: 30px;
color: #b1d8ff;
border: 1px solid rgba(177, 216, 255, 0.4);
}
:deep(.el-pagination__total) {
color: #b1d8ff;
}
.select_bottom :deep(.el-select-dropdown__item.selected) {
color: #606266;
background: #1f3758e0;
border: 1px solid #517ca5;
}
.select_bottom :deep(.el-select-dropdown) {
background-color: #1f3758e0;
}
.btn-option {
font-size: 22px;
color: #78b7b1;
text-shadow: 0px 0px 1px #26a7ff;
cursor: pointer;
}
</style>
和表单类似,表格的配置也需要接收父组件传来的配置,负责接收的参数如下:
// 表头
const tableHeaderConfig = inject("tableHeaderConfig");
// 表格数据
const tableDataConfig = inject("tableDataConfig");
数据格式如下:
App.vue
<template>
<div>
<QueryCondition @conditionParams="getCondition"> </QueryCondition>
<TableDialog @tableMessage="getTableMessage"></TableDialog>
</div>
</template>
<script lang = "ts">
import { defineComponent, provide, ref, reactive } from "vue";
import QueryCondition from "@/components/QueryCondition.vue";
import TableDialog from "@/components/TableDialog.vue";
export default defineComponent({
components: {
QueryCondition,
TableDialog,
},
setup() {
provide("formTypeConfig", [
{
label: "用户名称",
type: "input",
placeholder: "请输入. . .",
value: "",
},
{ label: "省份", type: "input", placeholder: "请输入. . .", value: "" },
{ label: "模式", type: "input", placeholder: "请输入. . .", value: "" },
{ label: "行业", type: "input", placeholder: "请输入. . .", value: "" },
{
label: "标签",
type: "select",
placeholder: "请选择",
option: [
{ label: "第一项", value: "1" },
{ label: "第二项", value: "2" },
],
value: "",
},
{
label: "分公司",
type: "select",
placeholder: "请选择",
option: [
{ label: "第一项", value: "1" },
{ label: "第二项", value: "2" },
],
value: "",
},
{
label: "受理员工号",
type: "input",
placeholder: "请输入. . .",
value: "",
},
{ label: "流水号", type: "input", placeholder: "请输入. . .", value: "" },
{ label: "时间", type: "date", placeholder: "请输入. . .", value: "" },
{ label: "", type: "searchAndClear", placeholder: "", value: "" },
]);
const getCondition = (params: any) => {
// 获取表单数据
let result: Array<any> = [];
for (let i = 0; i < params.length; i++) {
for (let j = 0; j < params[i].length; j++) {
result.push(params[i][j]);
}
}
result = result.filter((e: any) => {
if (e.label) {
return e;
}
});
console.log(result);
};
let tableHeader: any = ref([]);
let tableDatas: any = ref([]);
// 这里的数据,在真实开发中,其实可以从后台获取,这样就实现了动态表头了。
tableHeader.value = [
{
fieldName: "id",
label: "序号",
width: "250px",
sortable: true,
},
{ fieldName: "name", label: "姓名", width: "200px", sortable: true },
{
fieldName: "award",
label: "奖项",
width: "200px",
sortable: true,
},
{
fieldName: "address",
label: "来自",
width: "200px",
sortable: true,
},
];
// 这里的数据,在真实开发中,是从后台获取的,结合之前的动态表头,组成了动态表格。
tableDatas.value = [
{
id: 1,
name: "李晶",
award: "金牌",
address: "江苏",
},
{
id: 2,
name: "苏炳添",
award: "银牌",
address: "广东",
},
];
// 分发给子组件
// 配置表头数据
provide("tableHeaderConfig", tableHeader);
// 配置单元格数据及属性/分页配置默认值
provide("tableDataConfig", {
page: { currentPage: 1, size: 10, total: 0 },
tableData: tableDatas,
});
return {
getCondition,
// getTableMessage,
};
},
});
</script>
<style scoped>
</style>
注:父组件中导入
表单:
<QueryCondition @conditionParams="getCondition"> </QueryCondition>
表格:
<TableDialog @tableMessage="getTableMessage"></TableDialog>
完整代码,其实上面已经给出
,结果图在前言
部分。
原文始发于微信公众号(程序员阿晶):Vue3动态表单
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/19751.html