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

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

如下:

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

javascript 复制代码
<template>
  <div>
    <el-table
      :data="dataTableData"
      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="date"
        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"
          >
        </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="350"
      direction="ltr"
      :before-close="handleClose">
      <el-form
        :inline="true"
        :model="column"
        label-position="right"
        label-width="auto"
        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="映射变量" prop="prop">
          <el-cascader
            v-model="column.prop"
            :options="options"
            :props="optionProps"
            @change="handleChange">
          </el-cascader>
        </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 ColumnItem from './ColumnItem.vue';
import { apiEditHeaderTable, apiGetList, apiGetTreeselect } from "@/api/headerTable/index.js";
export default {
  name: 'CustomElTable',
  components: {
    ColumnItem
  },
  props: {
    // 表头数据
    headers: {
      type: Array,
      required: true
    },
    // 表格列表数据
    tableData: {
      type: Array,
      required: true
    },
    // 区分哪个页面下的列表
    tablekey: {
      type: String,
      required: true
    },
    // 限制新增最大列
    maxLength: {
      type: Number,
      required: false
    },
  },
  data() {
    return {
      dataHeaders: this.headers,
      dataTableData: this.tableData,
      dataNum: this.num,
      flag: true, // 为了更新子组件的状态
      drawer: false,
      loading: false,
      column: {
        label: "",
        width: "",
        align: "center",
        prop: "",
      },
      optionProps: { 
        expandTrigger: 'click',
        value: "id",
        label: "label",
        children: "children",
      },
      copyCol: {},
      rules: {
        label: [{ required: true, message: "请输入名称", trigger: "blur" }],
        align: [{ required: true, message: "请选择对齐方式", trigger: "blur" }],
      },
      title: '新增列',
      submitText: '新增',
      options: [],
    }
  },
  computed: {
    showAddBut() {
      return this.$store.state.common.headerEditBut
    }
  },
  watch: {
    headers: {
      handler(newValue,oldValue) {
        this.dataHeaders = newValue;
      }
    },
  },
  methods: {
    // 删除
    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;
    },
    // 编辑
    getEdit(col) {
      this.drawer = true;
      this.title = '编辑列',
      this.submitText = '保存',
      this.column.label = col.label;
      this.copyCol = col;
    },
    // 编辑,新增里的取消
    handleClose() {
      this.$confirm('内容未保存,确认关闭?','提示',{
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(_ => {
          this.drawer = false;
        })
        .catch(_ => {});
    },
    // 编辑,新增里的保存
    handleSubmit() {
      let col = this.copyCol
      this.loading = true;
      let item = {
        label: this.column.label,
        prop: "",
        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;
          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;
        })
      }
      this.loading = false;
      this.drawer = false;
      console.log("dataHeaders", this.dataHeaders);
      // this.handleApi();
    },
    // 请求接口,保存表头数据
    handleApi() {
      let data = {
        tableKey: this.tablekey,
        tableJson: JSON.stringify(this.dataHeaders)
      }
      apiEditHeaderTable(data).then(() => {
        this.loading = false;
        this.drawer = false;
        this.$message({
          message: '编辑成功',
          type: 'success'
        });
      }).catch(() => {
        this.loading = false;
        this.drawer = false;
        this.$message({
          message: '编辑失败',
          type: 'error'
        });
      })
    },
    handlePreserve() {
      console.log("保存");
      this.handleApi();
    },
    //
    handleChange(val) {
      let aa = val[val.length-1]
      apiGetList({
        deptId: aa
      }).then(res => {
        console.log(">>>>",res);
      })
    },
    handleList() {
      let data = {}
      apiGetTreeselect(data).then((res) => {
        this.options = res.data;
      })
    }
  },
  mounted() {
    this.handleList();
    console.log("tablekey",this.tablekey);
    EventBus.$on("getDelete",(col) => {
      this.getDelete(col)
    });
    EventBus.$on("getAdd",(col) => {
      this.getAdd(col)
    });
    EventBus.$on("getEdit",(col) => {
      this.getEdit(col)
    });
    EventBus.$on("handlePreserve",() => {
      this.handlePreserve()
    });
  }
}
</script>

<style lang="scss" scoped>
.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;
}
.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 代码内容,如下:

javascript 复制代码
<template>
  <el-table-column
    :prop="col.prop"
    :width="col.width"
    :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" :maxLength="maxLength"/>
    </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
    },
  },
  methods: {
    Add(col) {
      EventBus.$emit("getAdd",col)
    },
    Edit(col) {
      EventBus.$emit("getEdit",col)
    },
    Delete(col) {
      console.log(col);
      EventBus.$emit("getDelete",col)
    }
  },
  computed: {
    showAddBut() {
      return this.$store.state.common.headerEditBut
    }
  },
  mounted() {
  },
}
</script>

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

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

javascript 复制代码
<HeaderTable :headers="headers" :tableData="tableData" :tablekey="tablekey" :maxLength="4"/>
javascript 复制代码
data() {
  return {
     headers: [
        {
          label: "1",
          prop: "name",
          width: "150",
          align: "center",
          deep: 1,
          selected: false,
          children: [
            {
              label: "1-1",
              prop: "",
              width: "",
              align: "center",
              deep: 2,
              selected: false,
              children: [
                {
                  label: "1-1-1",
                  prop: "",
                  width: "",
                  align: "center",
                  deep: 3,
                  selected: false,
                  children: []
                }
              ]
            },
            {
              label: "1-2",
              prop: "",
              width: "",
              align: "center",
              deep: 2,
              selected: false,
              children: []
            }
          ]
        },
        {
          label: "2",
          prop: "name",
          width: "150",
          align: "center",
          deep: 1,
          selected: false,
          children: [
            {
              label: "2-1",
              prop: "",
              width: "",
              align: "center",
              deep: 2,
              selected: false,
              children: [
                {
                  label: "2-1-1",
                  prop: "",
                  width: "",
                  align: "center",
                  deep: 3,
                  selected: false,
                  children: []
                },
                {
                  label: "2-1-2",
                  prop: "",
                  width: "",
                  align: "center",
                  deep: 3,
                  selected: false,
                  children: []
                }
              ]
            },
            {
              label: "2-2",
              prop: "",
              width: "",
              align: "center",
              deep: 2,
              selected: false,
              children: []
            }
          ]
        }
      ],
      tableData: [
      {
        date: '2016-05-03',
      }, {
        date: '2016-05-02',
      }, {
        date: '2016-05-04',
      }, {
        date: '2016-05-01',
      }, {
        date: '2016-05-08',
      }, {
        date: '2016-05-06',
      }, {
        date: '2016-05-07', 
      }
      ],
  }
}

5、效果图,如下:

相关推荐
前端没钱1 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
顽疲2 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
羊小猪~~2 小时前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·javascript·css·vue.js·vscode·ajax·html5
摸鱼了2 小时前
🚀 从零开始搭建 Vue 3+Vite+TypeScript+Pinia+Vue Router+SCSS+StyleLint+CommitLint+...项目
前端·vue.js
2401_857600953 小时前
基于 SSM 框架 Vue 电脑测评系统:赋能电脑品质鉴定
前端·javascript·vue.js
天之涯上上3 小时前
Pinia 是一个专为 Vue.js 3 设计的状态管理库
前端·javascript·vue.js
晓纪同学4 小时前
QT创建一个模板槽和信号刷新UI
开发语言·qt·ui
CodeChampion5 小时前
60.基于SSM的个人网站的设计与实现(项目 + 论文)
java·vue.js·mysql·spring·elementui·node.js·mybatis
Elena_Lucky_baby5 小时前
实现路由懒加载的方式有哪些?
前端·javascript·vue.js
王解6 小时前
Vue CLI 脚手架创建项目流程详解 (2)
前端·javascript·vue.js