携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
基于element-ui封装弹窗表格,实现多选和单选功能。
为了解决以下问题:
- 日常场景需要用到翻页选择多条数据
- 数据量巨大,考虑性能问题,后端无法一次性返回所有数据
- 需要同时展示数据的编码、名称等内容
- 搜索条件通过接口进行数据筛选
以上问题无法通过简单的下拉多选框或下拉单选框实现,因此使用弹窗表格实现单选和多选功能。
弹窗效果
选中后效果
一、组件封装
1、弹窗框架
涉及属性和方法:
- title: 弹窗名称,
- @open: Dialog 打开的回调
- width: 弹窗宽度
- dialogTop: Dialog CSS 中的 margin-top 值
- model-value:是否显示 Dialog
- before-close: Dialog 关闭前的回调
2、弹窗搜索条件封装
涉及属性和方法:
- form: 搜索表单数据
- formList: 搜索条件
- getData: 获取列表数据
- resetInfo: 重置搜索条件
3、操作数据、确认数据
涉及属性和方法:
- isRadio: 判断是否单选
- tableData: 列表数据
- tableLoading: 获取列表数据加载loading
- tableList: 表头字段
- leftAdd:添加选中
- rightAdd:减少选中
- leftMIAnyJsonListItemAdd: 批量添加选中
- rightMIAnyJsonListItemAdd: 批量减少选中
- currentChange:单选情况下点击行为选中数据
- handleClose: 多选情况下确认或取消
完整代码
- 请求接口获取数据根据自身需求做处理、这里不多做添加
<template>
<el-dialog :title="title" @open="handleOpen" :width="dialogWidth" :top='dialogTop' :model-value="modelValue" :before-close="() => { handleClose('cancel') }">
<section class="page-searchbox" v-if="formList.length > 0">
<el-form ref="formRef" :inline="true" :model="form" @submit.prevent label-width="76px">
<div class="form-seacher-flex-wrap">
<el-form-item v-for="item in formList.filter((item) => item.isShow)" :key="item.key" :label="item.label" :prop="item.key">
<el-input v-model="form[item.key]" @keyup.enter="getData" clearable maxlength="32" />
</el-form-item>
<el-form-item class="search-tools">
<el-button type="primary" @click="getData">搜索</el-button>
<el-button @click="resetInfo">重置</el-button>
</el-form-item>
</div>
</el-form>
</section>
<div class="multiple-dialog" v-if="!isRadio">
<div class="content-dialog">
<div class="dialog-left" >
<el-table row-key="code" border :data="tableData" v-loading="tableLoading" stripe highlightCurrentRow height="400">
<el-table-column show-overflow-tooltip v-for="item in tableList.filter((item) => item.isShow)" :key="item.key" :prop="item.key" align="center" :label="item.label"></el-table-column>
<el-table-column show-ovexrflow-tooltip align="center" width="60" label="操作" >
<template v-slot="scope">
<span class="action-btn" @click="leftAdd(scope.row)">+</span>
</template>
</el-table-column>
</el-table>
<el-pagination popper-class="pagination-popper" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :pager-count="5" layout="total, prev, pager, next, sizes " :total="total" > </el-pagination>
</div>
<div class="dialog-middle">
<el-button @click="leftManyAdd" :style="{ margin: '10px 0 10px 0' }" >《《 </el-button>
<el-button @click="rightManyAdd"> 》》</el-button>
</div>
<div class="dialog-right">
<el-table row-key="code" border :data="selectTableData" v-loading="tableLoading" stripe highlightCurrentRow height="400" >
<el-table-column show-overflow-tooltip v-for="item in tableList.filter((item) => item.isShow)" :key="item.key" :prop="item.key" align="center" :label="item.label"></el-table-column>
<el-table-column show-ovexrflow-tooltip align="center" width="60" label="操作" >
<template v-slot="scope">
<span class="action-btn" @click="rightAdd(scope.row)">-</span>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
<div class="radio-main" v-else-if="isRadio">
<div class="dialog-table-content">
<div>
<el-table ref="multipleTable" row-key="code" border :data="tableData" v-loading="tableLoading" :row-class-name="({ row }) => row.disabled && 'disabled'" stripe height="400" @current-change="currentChange" >
<el-table-column show-overflow-tooltip v-for="(item) in tableList.filter((item) => item.isShow)" :key="item.key" :prop="item.key" align="center" :label="item.label"></el-table-column>
</el-table>
<div class="table-page" >
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :pager-count="5" layout="total, prev, pager, next, sizes " :total="total" />
</div>
</div>
</div>
</div>
<template #footer v-if="!isRadio">
<span class="dialog-footer">
<el-button type="primary" :loading="submitLoading" @click="handleClose('confirm')">确 定</el-button>
<el-button @click="handleClose('cancel')">取 消</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang='ts'>
import { defineComponent, reactive, ref, toRefs, watch, defineAsyncComponent, nextTick } from "vue";
import { funcApi } from "@/index/api";
// 组件初始化数据接口
interface ICompStateData {
submitLoading: boolean;
formList: Array<any>;
form: any;
tableData: Array<any>;
// 选择后的
selectTableData: Array<any>;
currentPage: number;
pageSize: number;
total: number;
formRef: any;
tableLoading: boolean;
tableList: Array<any>;
dialogWidth: number;
dialogTop:string;
}
export default defineComponent({
name: "DialogCombination",
emits: ['update:modelValue',"custHandle"],
props: {
modelValue: {
type: Boolean,
default: false,
},
title:{
type:String,
default:"",
},
dataContent:{
type:Object as () => any,
default:()=>({}),
},
showFormList:{
type:Object as () => any,
default:()=>({ code:true, name:true, }),
},
showTableList:{
type:Object as () => any,
default:()=>({ code:true, name:true, }),
},
isRadio: { // 是否单选 默认为单选
type: Boolean,
default:true
},
checkTable:{ // 回传选中的list,list中code是必须要有
type:Array as () => Array<any>,
default:()=>(null),
},
},
setup(props, { emit }) {
const state = reactive<ICompStateData>({
submitLoading: false,
formList: [
{ key: "code", label: "编码", isShow:props?.showFormList?.code? true :false },
{ key: "name", label: "名称", isShow:props?.showFormList?.name? true :false },
],
tableList: [
{ key: "code", label: "编码", isShow:props?.showTableList?.code? true :false },
{ key: "name", label: "名称", isShow:props?.showTableList?.name? true :false },
],
form: {
name: "",
code: "",
},
tableData: [],
selectTableData: [],
currentPage: 1,
pageSize: 10,
total: 0,
formRef: ref(null),
tableLoading: false,
dialogWidth: 920,
dialogTop: '8vh' ,
});
// 单选选中对应行
const currentChange = (rowData: any) => {
if(rowData && Object.keys(rowData).length > 0){// 这里只需要已存在的数据,清空的时候会调用这
state.selectTableData = [rowData]
emit("custHandle", { visible: false, list: state.selectTableData || [], rowData: state.selectTableData ? state.selectTableData[0] : {} });
emit('update:modelValue', false);
}
};
// 关闭弹窗
const handleClose = (action: string): void => {
if(action === "confirm"){
emit("custHandle", { visible: false, list: state.selectTableData || [] });
emit('update:modelValue', false);
}else{
emit('update:modelValue', false);
}
};
// 操作添加
const leftAdd = (data: any) => {
const handleIndexSelect = state.selectTableData.findIndex( (item) => data.code === item.code );
if (handleIndexSelect === -1) state.selectTableData.push(data);
};
// 操作减少
const rightAdd = (data: any) => {
const selectTableData = state.selectTableData;
const handleIndexSelect = selectTableData.findIndex( (item) => data.code === item.code );
handleIndexSelect !== -1 && state.selectTableData.splice(handleIndexSelect, 1);
};
// 操作添加
const leftManyAdd = (): void => {
const tableData = JSON.parse(JSON.stringify(state.tableData));
const selectTableData = state.selectTableData;
tableData.map((item: any) => {
const handleIndexSelect = selectTableData.findIndex( (st) => st.code === item.code );
handleIndexSelect !== -1 && selectTableData.splice(handleIndexSelect, 1); // 清空左边有存在相同的数据
});
state.selectTableData = selectTableData;
};
// 批量操作减少
const rightManyAdd = (): void => {
const selectTableData = state.selectTableData;
let hashRow ={};
const tableData = state.tableData.reduce((item:any,next:any)=>{
hashRow[`${next.id}${next.code}`] ? '' : hashRow[`${next.id}${next.code}`] = true && item.push(next)
return item
},[]);
tableData.map((item: any) => {
const handleIndexSelect = selectTableData.findIndex( (st) => st.code === item.code );
handleIndexSelect !== -1 && selectTableData.splice(handleIndexSelect, 1);
});
state.selectTableData = selectTableData.concat(tableData);
};
//每页条
const handleSizeChange = (sizeval: number) => {
state.pageSize = sizeval;
state.currentPage = 1;
getData();
};
//当前页
const handleCurrentChange = (currentVal: number) => {
state.currentPage = currentVal;
getData();
};
// 如果没有模板,取消弹窗并且提示错误
const errorShow = () => {
emit('update:modelValue', false);
console.log('请联系管理员的分配对应模板!');
}
// 获取表格数据
const getData = async (flag: any = false): Promise<void> => { // flag为了判断弹窗第一次获取数据
try {
let formData = state.form;
state.tableLoading = true;
const currentPage = state.currentPage-1;
// 请求数据
const beData = await funcApi({page_index: currentPage, page_size: state.pageSize, ...props.dataContent, ...formData}) ;
if(beData.error) {errorShow();return;}
const { rows, page_data: { count } } = beData;
if ( count !== 0 && Math.ceil(count / state.pageSize) < state.currentPage ) {
state.currentPage = 1;
await getData();
} else {
state.tableData = rows;
state.total = count;
}
} catch (error) {
console.log("%c error", "color: chartreuse", error);
}
state.tableLoading = false;
};
// 重置搜索条件
const resetInfo = () => {
state.formRef.resetFields();
state.currentPage = 1;
getData();
};
// 参数 data 传入要该表的数据 list 表示原有的数据
const getFirstList =(data:any, list :any [] )=>{
let lt:any[] = [] ;
list.map(item=>{
if(data[`${item.key}`]){
lt.push({ ...item, isShow:true })
}else{
lt.push({ ...item, isShow:false })
}
})
return lt
}
// 打开弹窗重置条件并获取数据
const handleOpen = ():void => {
state.formRef?.resetFields();
state.selectTableData = props.checkTable ? props.checkTable : [] ;
state.tableData = [];
state.currentPage = 1;
state.total = 0;
nextTick(()=>{ getData(true); })
}
watch(() => props.showFormList, () => {
state.formList = JSON.parse(JSON.stringify(getFirstList(props.showFormList, state.formList))) ;
// 初始form表单的信息
state.formList.map(item=>{
state.form[item.key] = '';
});
});
watch(() => props.showTableList, () => {
state.tableList = JSON.parse(JSON.stringify(getFirstList(props.showTableList, state.tableList)));
});
return {
...toRefs(state),
handleClose,
leftAdd,
rightAdd,
leftManyAdd,
rightManyAdd,
handleSizeChange,
handleCurrentChange,
getData,
resetInfo,
handleOpen,
currentChange,
};
},
});
</script>
<style lang="scss" scoped>
@import "@/Common/Dialog/Combination/index.scss";
.pagination-popper {
z-index:999999 !important;
}
</style>
@/Common/Dialog/Combination/index.scss
.multiple-dialog{
.el-table .success-row {
background: #f0f9eb;
}
.content-dialog {
width: 100%;
display: flex ;
.dialog-left{
width: 50%;
};
.dialog-middle {
width: 10%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
};
.dialog-right {
width: 40%;
};
.action-button-text{
width: 35px;
height: 25px;
font-size: 14px;
}
}
:deep(.page-searchbox .el-input){
width: 180px !important;
}
:deep(.el-form-item__content){
width: 180px !important;
}
.search-tools{
width: 144px;
}
}
.radio-main{
.el-table .success-row {
background: #f0f9eb;
}
.dialog-table-content{
width: 100% ;
.table-page{
display: flex;
justify-content: flex-end
}
}
:deep(.el-table__row:hover > td) {
background-color: #B0C4DE !important;
}
:deep(.el-table__body tr.current-row>td) {
background-color: #6CA6CD !important;
}
:deep(.page-searchbox .el-input){
width: 180px !important;
}
:deep(.el-form-item__content){
width: 180px !important;
}
.search-tools{
width: 144px;
}
}
二、组件使用
<el-input v-model="form[item.key]" maxlength="0" clearable @clear="()=>onSearchClear(item.clearData)">
<template v-slot:append>
<el-button icon="search" @click="onDialogShow(item.key)"></el-button>
</template>
</el-input>
<Combination v-model="dialogVisible" @cust-handle="custDialog" v-bind="{ ...searchData }" clearable />
interface StateType{
form: IAnyJsonListItem
dialogVisible: boolean
searchData: IAnyJsonListItem
dialogKey: string
}
export default defineComponent({
name: 'example',
setup() {
const state = reactive<StateType>({
form: {},
searchData: {},
dialogVisible: false,
dialogKey: '',
});
const onDialogShow = (key:string) => {
state.dialogKey = key;
switch(key){
case 'customerCode': {
state.searchData = { title:'选择客户', selectType: 'selectType', dataContent: { routineFilter: { level: 10 } } }
state.dialogVisible = true;
break;
}
default: break;
}
}
const custDialog = (data: IAnyJsonListItem) => {
const { list } = data;
if(state?.form){
let operaData = {}
switch(state.dialogKey){
case 'customerCode': {
operaData = {
customerCode: list.map((item: IAnyJsonListItem) => item.code),
customerIdList: list.map((item: IAnyJsonListItem) => item.id),
}
break;
}
default: break;
}
state.form = JSON.parse(
JSON.stringify({
...state.form,
...operaData
})
);
}
};
const onSearchClear =(arg:IAnyJsonListItem, type: string)=>{
if(arg){
Object.keys(arg).map((item)=>{
state.form[item] = arg[item];
});
}
}
return {
...toRefs(state),
onDialogShow,
custDialog,
}
}
})