封装从table里选择待选数据并能操作tag进行删除的dialog组件

24 阅读3分钟

效果如图

image.png

使用方法

参数说明

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>