效果如图
使用方法
参数说明
isDialogShow:控制dialog的展示与否
selectedList:该实例已有的内容,比如实例为阳光宅男组,成员有周杰伦和鲁仲连
requestDataFn:供封装的组件进行数据请求的函数
tableColumList:table中要显示哪些属性
@dialogConfirm:dialog点击确认后如何处理最新的已选内容
使用封装组件的父组件的示例代码
<template>
<div>
<el-button @click="showDialog">打开dialog</el-button>
<Dialog4SelectFromTable
ref="dialogRef"
v-if="isDialogShow"
v-model:visible="isDialogShow"
:selectedList="selectedList"
:requestDataFn="dialogRequestDataFn"
:tableColumList="tableColumList"
@dialogConfirm="ondialogConfirm"
></Dialog4SelectFromTable>
</div>
</template>
<script setup>
import { ref } from "vue";
import Dialog4SelectFromTable from "./components/Dialog4SelectFromTable.vue";
const dialogRef = ref();
const isDialogShow = ref(false);
const selectedList = ref([
{ id: 1, name: "周杰伦", qq: 123456 },
{ id: 10, name: "鲁仲连", qq: 198765 },
]);
const tableColumList = ref([
{ prop: "name", label: "姓名" },
{ prop: "qq", label: "QQ" },
]);
//. dialog确认时把最新的值赋给选定值
function ondialogConfirm(updatedSelectedList) {
selectedList.value = updatedSelectedList;
}
//. 打开dialog
const showDialog = () => {
isDialogShow.value = true;
};
async function dialogRequestDataFn() {
return {
/// 这里的数据要根据后端返回的格式区自定义
data: {
msg: "success",
data: {
list: testData,
pageInfo: {
total: 10,
},
},
},
};
}
const testData = [
{ id: 1, name: "周杰伦", qq: 123456 },
{ id: 2, name: "林俊杰", qq: 654321 },
{ id: 3, name: "陈奕迅", qq: 234567 },
{ id: 4, name: "王力宏", qq: 765432 },
{ id: 5, name: "薛之谦", qq: 345678 },
{ id: 6, name: "毛不易", qq: 876543 },
{ id: 7, name: "周兴哲", qq: 456789 },
{ id: 8, name: "李荣浩", qq: 987654 },
{ id: 9, name: "陶喆", qq: 567890 },
{ id: 10, name: "鲁仲连", qq: 198765 },
];
</script>
<style lang="scss" scoped></style>
封装组件完整代码(附详细注释)
<template>
<div>
<el-dialog v-model="isShow" title="选择列表" align="center">
<!-- -------- Tag ------------ -->
<div class="tag-container">
<el-tag
v-for="item in selectedList"
@close="handleTagClose(item.id)"
:key="item.id"
closable
type="success"
size="large"
effect="dark"
>
{{ item.name }}
</el-tag>
</div>
<!-- -------- Table ------------ -->
<el-table
:data="tableList"
@selection-change="onSelectionChange"
ref="tableRef"
v-loading="loading"
stripe
>
<el-table-column
type="selection"
header-align="center"
align="center"
width="100"
></el-table-column>
<el-table-column
v-for="item in tableColumList"
v-bind="item"
:key="item"
align="center"
/>
</el-table>
<!-- -------- 分页器 ------------ -->
<el-pagination
v-model:current-page="page.currentPage"
v-model:page-size="page.rowsPerPage"
@current-change="getData"
layout="prev, pager, next"
:total="page.total"
/>
<div class="menu-btn">
<el-button type="info" @click="isShow = false">取消</el-button>
<el-button type="primary" @click="onConfirm">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, nextTick, onMounted, computed } from "vue";
const props = defineProps({
selectedList: {
type: Array,
default: [],
},
requestDataFn: {
type: Function,
default: () => {},
},
tableColumList: {
type: Array,
default: [],
},
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible"]);
/// 控制dialog的显示
const isShow = computed({
get: () => props.visible,
set: (val) => {
emit("update:visible", val);
},
});
const selectedList = ref(props.selectedList); ///每次显示dialog,把已经选中的显示出来
const tableRef = ref();
const tableList = ref([]); ///查询到的要在table里显示的数据
const loading = ref(false); /// 控制table的loading
const page = reactive({
currentPage: 1,
rowsPerPage: 10,
});
//. 可直接从tag里移除,从选中项里删除并取消选中
function handleTagClose(id) {
selectedList.value = selectedList.value.filter((item) => item.id != id);
let row = tableRef.value.data.find((row) => row.id === id);
if (row) {
tableRef.value.toggleRowSelection(row, false);
}
}
//. 获取本页table数据
async function getData() {
//! props.data.requestData 请求数据的方法
const response = await props.requestDataFn(
{ currentPage: page.currentPage, rowsPerPage: page.rowsPerPage },
{
loading: true,
}
);
const { msg, data } = response.data;
if (msg == "success") {
const { list, pageInfo } = data;
tableList.value = list;
page.total = pageInfo.total;
/// 将本页本已选的选中
let originalSelectedIds = selectedList.value.map((item) => item.id);
nextTick(() => {
tableList.value.forEach((row) =>
tableRef.value.toggleRowSelection(
row,
originalSelectedIds.includes(row.id)
)
);
});
}
}
//. 选中项发生改变时
function onSelectionChange(selection) {
let originalSelectedIds = selectedList.value.map((item) => item.id); /// 原先已选择的ids
let currentPageSelectedIds = selection.map((i) => i.id); /// 最新的当前页选中的ids
let unselectedIds = tableList.value /// 最新的当前页未选中的ids
.filter((item) => !currentPageSelectedIds.includes(item.id))
.map((item) => item.id);
/// 当前页选中的原先没有,加进去
for (const id of currentPageSelectedIds) {
if (!originalSelectedIds.includes(id)) {
selectedList.value.push(tableList.value.find((item) => item.id == id));
}
}
/// 当前页未选中的原先有,移除
for (const id of unselectedIds) {
let index = selectedList.value.findIndex((item) => item.id == id);
if (index != -1) {
selectedList.value.splice(index, 1);
}
}
selectedList.value.sort((a, b) => a.id - b.id); /// 保持升序排序
}
//. 确认时传更新后的selectedList,供父组件处理
async function onConfirm() {
emit("dialogConfirm", selectedList.value);
isShow.value = false;
}
onMounted(() => {
getData();
});
</script>
<style lang="scss" scoped>
.tag-container {
margin: -10px auto 10px;
display: flex;
flex-wrap: wrap;
width: 80%;
gap: 5px;
}
:deep(.el-table__header) {
thead > tr th {
background-color: #a9c4ff;
color: black;
}
}
.el-pagination {
margin-top: 10px;
display: flex;
justify-content: center;
}
.menu-btn {
margin-top: 10px;
}
:deep(.el-dialog) {
width: 600px;
}
</style>