Vue2 + Element UI 封装 Table 递归多层级列表头动态

 注:此功能改为独立模块直接拿代码就可以使用。

1、在 components 中创建 HeaderTable 文件夹,在创建 ColumnItem.vue 和 index.vue。

如下:

2、index.vue 代码内容,如下:

<template>
  <div>
    <div class="searchBar">
      <el-form inline>
        <el-form-item label="时间选择">
          <el-date-picker
            v-model="queryParams.dataTime"
            type="date"
            value-format="yyyy-MM-dd"
            placeholder="选择年份">
            </el-date-picker>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="getList">搜索</el-button>
        </el-form-item>
        <el-form-item>
          <el-button @click="resetSearchForm">重置</el-button>
        </el-form-item>
        <el-form-item>
          <el-button @click="exportToExcel" type="warning">导出表格</el-button>
        </el-form-item>
        <el-form-item>
          <el-button @click="handleEditBut(1)" type="primary" v-if="!showAddBut">编辑表头</el-button>
          <el-button @click="handleEditBut('preserve')" type="primary" v-if="showAddBut">保存修改</el-button>
          <el-button @click="handleEditBut('quit')" type="danger" v-if="showAddBut">退出修改</el-button>
        </el-form-item>
      </el-form>
    </div>

    <el-table
      ref="table"
      :data="dataTableData"
      v-loading="vLoading"
      style="width: 100%"
      max-height="700"
      :cell-style="{
        'border-right': '1px solid #C4C6CB',
        'border-bottom': '1px solid #C4C6CB',
      }"
      :header-cell-style="{
        'background-color': '#f8f8f9',
        'border-right': '1px solid #C4C6CB',
        'border-bottom': '1px solid #C4C6CB',
      }"
    >
      <el-table-column
        prop="dataTime"
        label="时间"
        width="150"
        align="center"
        v-if="flag"
        >
      </el-table-column>
      <!-- 递归组件 -->
      <template v-if="flag">
        <ColumnItem
          v-for="(item,index) in dataHeaders"
          :key="item.label"
          :col="item"
          :maxLength="maxLength"
          :showAddBut="showAddBut"
          >
        </ColumnItem>
      </template>
      <el-table-column
        prop=""
        width="150"
        align="center"
        v-if="showAddBut && flag"
        >
        <template slot="header" slot-scope="scope">
          <i class="el-icon-plus" @click="getAdd({})"/>
        </template>
      </el-table-column>
    </el-table>

    <el-drawer
      :title="title"
      :visible.sync="drawer"
      :size="380"
      direction="ltr"
      :before-close="handleClose">
      <el-form
        :inline="true"
        :model="column"
        label-position="right"
        ref="ruleFormRef"
        :rules="rules"
        class="demo-form-inline form-padding"
      >
        <el-form-item label="名称" prop="label">
          <el-input v-model="column.label" placeholder="请输入名称" clearable />
        </el-form-item>
        
        <el-form-item label="宽度" prop="width">
          <el-input
            v-model="column.width"
            type="number"
            placeholder="请输入列宽度(不填为自适应)"
            clearable
          />
        </el-form-item>

        <el-form-item label="对齐方式" prop="align">
          <el-select
            v-model="column.align"
            placeholder="请选择对齐方式"
            clearable
          >
            <el-option label="左对齐" value="left" />
            <el-option label="居中对齐" value="center" />
            <el-option label="右对齐" value="right" />
          </el-select>
        </el-form-item>

        <el-form-item label="映射变量1" prop="prop">
          <el-cascader
            v-model="column.prop"
            :options="options"
            :props="optionProps"
            @change="handleChange">
          </el-cascader>
        </el-form-item>

        <el-form-item label="映射变量2" prop="propId" v-show="column.prop">
          <el-select v-model="column.propId" placeholder="请选择">
            <el-option
              v-for="item in optionsId"
              :key="item.id"
              :label="item.equipName"
              :value="item.id">
            </el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <div class="drawer-footer">
        <el-button @click="handleClose">取消</el-button>
        <el-button type="primary" :loading="loading" @click="handleSubmit">
          {{ submitText }}
        </el-button>
      </div>
    </el-drawer>
  </div>
</template>

<script>
import { EventBus } from "../../utils/event-bus.js";
import * as XLSX from "xlsx";
import ColumnItem from './ColumnItem.vue';
import { apiEditHeaderTable, apiGetList, apiGetTreeselect, listTableReport, listYearReport, listMonthReport, listDayReport } from "@/api/headerTable/index.js";
export default {
  name: 'CustomElTable',
  components: {
    ColumnItem
  },
  props: {
    // 区分哪个页面下的列表
    tablekey: {
      type: String,
      required: true
    },
    // 限制新增最大列
    maxLength: {
      type: Number,
      required: false
    },
  },
  data() {
    let today = new Date();
    let todayString = `${today.getFullYear()}-${
      today.getMonth() + 1
    }-${today.getDate()}`;
    return {
      dataHeaders: [], // 表头数据
      dataTableData: [], // 表格列表数据
      // 查询参数
      queryParams: {
        dataTime: todayString,
        ids: [],
      },
      vLoading: false,
      showAddBut: false, // 控制按钮状态
      flag: true, // 为了更新子组件的状态
      drawer: false,
      loading: false,
      column: {
        label: "",
        width: "",
        align: "center",
        prop: "",
        propId: "",
      },
      optionProps: { 
        expandTrigger: 'click',
        value: "id",
        label: "label",
        children: "children",
      },
      copyCol: {},
      rules: {
        label: [{ required: true, message: "请输入名称", trigger: "blur" }],
        align: [{ required: true, message: "请选择对齐方式", trigger: "blur" }],
        prop: [{ required: true, message: "请选择映射变量1", trigger: ["blur","change"] }],
        propId: [{ required: true, message: "请选择映射变量2", trigger: ["blur","change"] }],
      },
      title: '新增列',
      submitText: '新增',
      options: [],
      optionsId: [],
    }
  },
  methods: {
    // 立即执行获取数据 搜索
    getTableList() {
      this.vLoading = true;
      this.showTable = false;
      listTableReport(this.tablekey).then(res => {
        if(res.code == 200) {
          let tableJson = res.data.tableJson ? JSON.parse(res.data.tableJson) : [];
          console.log("tableJson",tableJson);
          this.getForRecursion(tableJson);
          this.dataHeaders = tableJson;
          this.getList();
        }
      })
    },
    // 获取列表数据
    async getList() {
      console.log("this.queryParams",this.queryParams);
      if(this.queryParams.ids) {
        this.vLoading = true;
        let data = {
          dataTime: this.queryParams.dataTime,
          ids: this.queryParams.ids.length == 0 ? [] : this.queryParams.ids
        }
        let res = null;
        if(this.tablekey === 'year') {
          res = await listYearReport(data);
        }else if(this.tablekey === 'month') {
          res = await listMonthReport(data);
        }else {
          res = await listDayReport(data);
        }
        this.dataTableData = res.data;
        this.showTable = true;
        this.vLoading = false;
      }
    },
    // 递归遍历出 propId
    getForRecursion(list) {
      console.log("list",list);
      if(list?.length > 0) {
        list.map(item => {
          if (item.children && item.children.length > 0) {
            this.getForRecursion(item.children);
          }else {
            this.queryParams.ids.push(item.propId);
          }
        })
      }else {
        this.queryParams.ids = [];
      }
    },
    // 请求接口,保存表头数据
    handleSave() {
      console.log("保存");
      let data = {
        tableKey: this.tablekey,
        tableJson: JSON.stringify(this.dataHeaders)
      }
      apiEditHeaderTable(data).then(() => {
        this.loading = false;
        this.drawer = false;
        this.$message({
          message: '编辑成功',
          type: 'success'
        });
        this.getTableList();
      }).catch(() => {
        this.loading = false;
        this.drawer = false;
        this.$message({
          message: '编辑失败',
          type: 'error'
        });
      })
    },
    // 重置
    resetSearchForm() {
      let today = new Date();
      let todayString = `${today.getFullYear()}-${
        today.getMonth() + 1
      }-${today.getDate()}`;
      this.queryParams.dataTime = todayString;
      this.getList();
    },
    // 操作列表头
    handleEditBut(val) {
      this.showAddBut = !this.showAddBut;
      if(val === 'preserve') {
        this.handleSave();
      }else if(val === 'quit') {
        this.getTableList();
      }
    },
    // 删除
    getDelete(col) {
      this.$confirm('是否删除?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(_ => {
          let newHeaders = this.getRecursion(this.dataHeaders,col);
          console.log("处理完数据的", newHeaders);
          this.dataHeaders = newHeaders;
          // 数据更新,更新子组件状态
          this.flag = false;
          this.$nextTick(() => {
            this.flag = true;
          })
          // EventBus.$emit("getHeaders",this.headers)
        }).catch(() => {});
    },
    // 递归删除
    getRecursion(arr,col) {
      arr.map((item,index) => {
        if(item.label === col.label && item.deep === col.deep) {
          arr.splice(index,1);
          return;
        }
        if(item.children && item.children.length > 0) {
          this.getRecursion(item.children,col)
        }
      })
      return arr;
    },
    // 新增
    getAdd(col) {
      this.drawer = true;
      this.title = '新增列';
      this.submitText = '新增';
      this.column.label = '新增列';
      this.copyCol = col;
      this.emptyColumn();
    },
    // 编辑
    getEdit(col) {
      console.log("col",col);
      this.drawer = true;
      this.title = '编辑列',
      this.submitText = '保存',
      this.column.label = col.label;
      this.column.width = col.width;
      this.column.align = col.align;
      this.column.prop = col.prop.split(",").map(item => Number(item));
      if(col?.prop) {
        this.handleChange(this.column.prop);
      }
      console.log("this.column.prop",this.column.prop);
      this.column.propId = col.propId;
      this.copyCol = col;
    },
    // 编辑,新增里的取消
    handleClose() {
      this.$confirm('内容未保存,确认关闭?','提示',{
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(( )=> {
          this.emptyColumn();
          this.drawer = false;
        })
        .catch(() => {});
    },
    // 编辑,新增里的保存
    handleSubmit() {
      this.$refs['ruleFormRef'].validate((valid) => {
        if(!valid) {
          return false;
        }
        console.log("this.column",this.column);
        let col = this.copyCol
        this.loading = true;
        let item = {
          label: this.column.label,
          prop:  this.column.prop.toString(),
          propId: this.column.propId,
          width: this.column.width,
          align: this.column.align,
          deep: 1,
          selected: false,
          children: []
        }
        if(this.title === '新增列') {
          if(!Object.keys(col).length == 0){
            item.deep = col.deep + 1;
            if(!col.children) {
              col.children = [];
            }
            col.children.push(item);
          }else {
            this.dataHeaders.push(item);
          }
        }else {
          this.flag = false;
          this.$nextTick(() => {
            this.flag = true;
            col.label = this.column.label;
            col.width = this.column.width;
            col.align = this.column.align;
            col.prop = this.column.prop.toString();
            col.propId = this.column.propId;
          })
        }
        this.loading = false;
        this.drawer = false;
      })
    },
    // 清空 column 的数据
    emptyColumn() {
      this.column.width = "";
      this.column.align = "center";
      this.column.prop = "";
      this.column.propId = "";
    },
    // 映射变量更改获取数据 ID
    handleChange(val) {
      let aa = val[val.length-1]
      apiGetList({
        deptId: aa
      }).then(res => {
        if(res.code == 200) {
          this.optionsId = res.rows;
        }
      })
    },
    // 映射变量下拉数据
    handleList() {
      let data = {}
      apiGetTreeselect(data).then((res) => {
        this.options = res.data;
      })
    },
    // 导出表格
    exportToExcel() {
      const table = this.$refs.table.$el;
      const workbook = XLSX.utils.table_to_book(table, { raw: true });
      const wbout = XLSX.write(workbook, { bookType: "xlsx", type: "array" });
      let tableName = '';
      if(this.tablekey === 'year') {
        tableName = '年报表';
      }else if(this.tablekey === 'month') {
        tableName = '月报表';
      }else {
        tableName = '日报表';
      }
      tableName ? tableName + ".xlsx" : "表格.xlsx";
      this.downloadExcel(wbout, tableName);
    },
    downloadExcel(buffer, filename) {
      const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
      const link = document.createElement("a");
      const url = URL.createObjectURL(blob);
      link.href = url;
      link.setAttribute("download", filename);
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(url);
    },
  },
  mounted() {
    console.log("this.tablekey",this.tablekey);
    this.getTableList();
    this.handleList();
    // 接收子组件传递过来的事件
    EventBus.$on("getDelete",(col) => {
      this.getDelete(col) // 删除
    });
    EventBus.$on("getAdd",(col) => {
      this.getAdd(col) // 新增
    });
    EventBus.$on("getEdit",(col) => {
      this.getEdit(col) // 编辑
    });
  }
}
</script>

<style lang="scss" scoped>
.searchBar {
  padding: 15px;
  padding-bottom: 0;
}
.scope_p {
  margin: 0;
}
.i_margin {
  margin: 0 10px;
}

.content-main {
  .table-title {
    display: flex;
    justify-content: space-around;
    margin-bottom: 10px;
    > p {
      flex: 1;
      font-weight: bold;
      font-size: 18px;
    }
  }
  .el-input {
    width: 250px;
  }
  .el-select {
    width: 280px;
  }
}
.form-padding {
  padding: 0 20px;
  ::v-deep .el-form-item__label {
    width: 88px;
  }
}
.drawer-footer {
  display: flex;
  align-items: center;
  justify-content: center;
}

.header-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  .el-icon-plus {
    margin-left: 10px;
    cursor: pointer;
  }
  .el-icon-edit {
    margin-left: 10px;
    cursor: pointer;
  }
  .el-icon-delete {
    margin-left: 10px;
    cursor: pointer;
  }
}

.selected {
  color: #409eff;
}

.header-scroll::v-deep {
  .el-table__header-wrapper {
    overflow-x: auto;
  }

  /* 修改滚动条样式 */
  ::-webkit-scrollbar {
    height: 8px; /* 设置滚动条宽度 */
  }

  ::-webkit-scrollbar-track {
    background-color: #ffffff; /* 设置滚动条轨道背景色 */
  }

  ::-webkit-scrollbar-thumb {
    background-color: #ececec; /* 设置滚动条滑块颜色 */
    border-radius: 4px; /* 设置滚动条滑块圆角 */
  }

  ::-webkit-scrollbar-thumb:hover {
    background-color: #e2e2e2; /* 设置滚动条滑块鼠标悬停时颜色 */
  }
}
.error_label {
  color: #f56c6c !important;
}
</style>

3、ColumnItem.vue 代码内容,如下:

<template>
  <el-table-column
    :prop="`column_${col.propId}`"
    :width="col.width"
    :min-width="col.width ? '' : '100'"
    :align="col.align"
    min-width="150"
    >
    <template slot="header" slot-scope="scope">
        <p class="scope_p">{{col.label}}</p>
        <div v-if="showAddBut">
          <i class="el-icon-plus" v-if="col.deep < maxLength" @click="Add(col)"/>
          <i class="el-icon-edit-outline i_margin" @click="Edit(col)"/>
          <i class="el-icon-delete" @click="Delete(col)"/>
        </div>
    </template>
    <!-- 遍历递归 -->
    <template v-for="(item,index) in col.children">
      <ColumnItem v-if="item.children" :col="item" :key="`${item.label}-${item.propId}`" :maxLength="maxLength" :showAddBut="showAddBut"/>
    </template>
  </el-table-column>
</template>

<script>
import { EventBus } from "../../utils/event-bus.js";
export default {
  name: "ColumnItem",
  props: {
    col: {
      type: Object,
      required: true
    },
    maxLength: {
      type: Number,
      required: false
    },
    showAddBut: {
      type: Boolean,
    },
  },
  methods: {
    Add(col) {
      EventBus.$emit("getAdd",col)
    },
    Edit(col) {
      EventBus.$emit("getEdit",col)
    },
    Delete(col) {
      console.log(col);
      EventBus.$emit("getDelete",col)
    }
  },
  mounted() {
  },
}
</script>

<style lang="scss" scoped>
.scope_p {
  margin: 0;
}
i {
  cursor: pointer;
}
.i_margin {
  margin: 0 10px;
}
</style>

4、在 .vue 文件中使用和数据,如下: 

<HeaderTable tablekey="year" :maxLength="4" />

5、效果图,如下:

 

6、引入事件总线 EventBus 

// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值