在查询表格数据的时候,常常见到表格上方有一个表单区域, 用于数据查询条件动态查询表格数据.如下图:
在此, 我开发了一个可以复用的组件. 可根据需要进行配置表单项(支持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>
</el-col>
<el-col :span="span">
<el-form-item label="">
<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>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</div>
</template>
<script>
import {inject, ref} from "@vue/runtime-core";
import {watchEffect} from "vue";
export default {
name: "QueryCondition",
emits: ["conditionParams"],
setup(props, context) {
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, col) {
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;
}
</style>
1. 上面的formTypeConfig变量会接收从父组件传来的数据配置表单项, 所以先从它入手
const formTypeConfig = inject("formTypeConfig");
在父组件中配置表单信息, 必须按照这个数据格式(配置简单易懂, 不作详细说明)
父组件:
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: 'date', placeholder: '请选择', value: ''},
{label: '流水号', type: 'input', placeholder: '请输入. . .', value: ''},
{label: '流水号', type: 'input', placeholder: '请输入. . .', value: ''},
{label: '流水号', type: 'input', placeholder: '请输入. . .', value: ''},
{label: '流水号', type: 'input', 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参数, 它是配置每行显示的表单项个数, 可根据需要进行修改.
/**
* 设置表单类型
* @param array 表单配置
* @param col 每行表单项个数
*/
function setFormType(array, col) {
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">
<!--<el-table-column type="selection" width="55"></el-table-column>-->
<el-table-column v-for="(head, index) in tableHeader" :key="index" :label="head.label" :prop="head.fieldName"
:sortable="head.sortable">
<template #default="scope">
<div :style="scope.row[head.fieldName].style">
{{ scope.row[head.fieldName].value }}
</div>
</template>
</el-table-column>
<el-table-column align="center" width="60px" label="操作">
<template #default="scope">
<div class="btn-option" @click="submitData(scope)"><i class="el-icon-tickets"></i></div>
</template>
</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 {inject, reactive} from "@vue/runtime-core";
import {watchEffect} from "vue";
export default {
name: "TableDialog",
emits: ["tableMessage"],
setup(props, context) {
// 数据及配置
let tableData = reactive([])
let page = reactive({})
// 表头配置
let tableHeader = reactive([])
const tableHeaderConfig = inject("tableHeaderConfig");
const tableDataConfig = inject("tableDataConfig");
// 监听表单配置数据
watchEffect(() => {
console.log('-------监听tableHeaderConfig,tableDataConfig--------')
tableHeader = 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
}
}
}
</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");
数据格式如下:
// 设置表头 [fieldName字段名, label列名, width列宽, sortable排序]
tableHeader.value = [
{fieldName: 'test1', label: '客户名称', width: '250px', sortable: true},
{fieldName: 'test4', label: '省份', width: '200px', sortable: true},
{fieldName: 'test2', label: '客户服务模式', width: '200px', sortable: true},
{fieldName: 'test3', label: '客户行业', width: '200px', sortable: true},
]
// 表格数据
tableDatas.value = [
{
number: {value: 1, style: 'color: #dfe4e9;'},
name: {value: 'Huzz', style: 'color: #dfe4e9; font-weight: 600;'},
sex: {
value: 'A',
style: 'color: #F9A400;border: 2px solid #F9A400;width: 20%;text-align: center;border-radius: 5px 5px 5px 5px;'
},
option: {value: '金牌', style: 'color: #FFCE57;'},
test1: {value: '昆明', style: 'color: #dfe4e9;'},
test2: {value: '霍咏', style: 'color: #dfe4e9;'},
test3: {value: 18345681564, style: 'color: #dfe4e9;'}
},
{
number: {value: 2, style: 'color: #dfe4e9;'},
name: {value: 'Huzz', style: 'color: #dfe4e9;font-weight: 600;'},
sex: {
value: 'B',
style: 'color: #2DC277;border: 2px solid #2DC277;width: 20%;text-align: center;border-radius: 5px 5px 5px 5px;'
},
option: {value: '标准', style: 'color: #4CE2FF;'},
test1: {value: '昆明', style: 'color: #dfe4e9;'},
test2: {value: '霍咏', style: 'color: #dfe4e9;'},
test3: {value: 18345681564, style: 'color: #dfe4e9;'}
},
{
number: {value: 3, style: 'color: #dfe4e9;'},
name: {value: 'Huzz', style: 'color: #dfe4e9; font-weight: 600;'},
sex: {
value: 'C',
style: 'color: #0FD1FF;border: 2px solid #0FD1FF;width: 20%;text-align: center;border-radius: 5px 5px 5px 5px;'
},
option: {value: '银牌', style: 'color: #FFFFFF;'},
test1: {value: '昆明', style: 'color: #dfe4e9;'},
test2: {value: '霍咏', style: 'color: #dfe4e9;'},
test3: {value: 18345681564, style: 'color: #dfe4e9;'}
},
{
number: {value: 4, style: 'color: #dfe4e9;'},
name: {value: 'Huzz', style: 'color: #dfe4e9; font-weight: 600;'},
sex: {
value: 'D',
style: 'color: #FFFFFF;border: 2px solid #FFFFFF;width: 20%;text-align: center;border-radius: 5px 5px 5px 5px;'
},
option: {value: '铜牌', style: 'color: #FF9945;'},
test1: {value: '昆明', style: 'color: #dfe4e9;'},
test2: {value: '霍咏', style: 'color: #dfe4e9;'},
test3: {value: 18345681564, style: 'color: #dfe4e9;'}
},
])
// 配置表头数据
provide("tableHeaderConfig", tableHeader)
// 配置单元格数据及属性/ 分页配置默认值
provide("tableDataConfig", {page: {currentPage: 1, size: 10, total: 2}, tableData: tableDatas})
注 -- 父组件中导入
表单:<QueryCondition @conditionParams="getCondition"></QueryCondition>
表格:<TableDialog @tableMessage="getTableMessage"></TableDialog>