vue2 el-table使用

loading

 

1、table基础表格渲染(动态表头数据,排序)

<template>
  <div class="table-container">
    <!-- 动态表头表格 -->
    <el-table
      :data="tableData"
      border
      fit
      stripe
      @sort-change="handleChangeSort"
    >
      <!-- 循环渲染动态表头 -->
      <template v-for="(item, index) in dynamicColumns">
        <el-table-column
          v-if="item.isShow"
          :key="item.prop + index"
          :prop="item.prop"
          :label="item.label"
          :width="item.width"
          :show-overflow-tooltip="true"
          :align="item.align || 'center'"
          :sortable="item.sortable"
          :minWidth="item.minWidth"
          :formatter="columnFormatter"
        >
          <!-- 可选:自定义列内容(比如格式化、按钮等) -->
          <template slot-scope="scope">
            <!-- 示例:如果是操作列,渲染按钮 -->
            <template v-if="item.prop === 'operation'">
              <el-button
                type="primary"
                size="mini"
                @click="handleEdit(scope.row)"
                >编辑</el-button
              >
              <el-button
                type="danger"
                size="mini"
                @click="handleDelete(scope.row)"
                >删除</el-button
              >
            </template>
            <!-- 普通列直接显示值 -->
            <template v-else> {{ scope.row[item.prop] }} </template>
          </template>
        </el-table-column>
      </template>
    </el-table>
  </div>
</template>

<script>
export default {
  name: "DynamicTable",
  data() {
    return {
      // 表格数据源
      tableData: [
        { id: 1, name: "张三", age: 20, phone: "13800138000" },
        { id: 2, name: "李四", age: 22, phone: "13900139000" },
        { id: 3, name: "王五", age: 25, phone: "13700137000" },
      ],
      // 动态表头配置(核心)
      dynamicColumns: [
        {
          prop: "id",
          label: "ID",
          width: "80px",
          sortable: true,
          isShow: true,
          align: "center",
          minWidth: 500,
        },
        {
          prop: "name",
          label: "姓名",
          sortable: true,
          isShow: this.$route.name === "TestTest",
          width: "120px",
        },
        {
          prop: "age",
          label: "年龄",
          sortable: true,
          isShow: true,
          width: "80px",
        },
        {
          prop: "phone",
          label: "手机号",
          sortable: true,
          isShow: true,
          width: "180px",
        },
        {
          prop: "operation",
          label: "操作",
          isShow: true,
          width: "180px",
        },
      ],
    };
  },
  mounted() {
    console.log(this.$route.name);
    // 隐藏id - 通过请求接口类型控制
    const obj = this.getColumn("id");
    obj.isShow = false;
  },
  methods: {
    // 获取指定列的配置
    getColumn(prop) {
      return this.dynamicColumns.find((col) => col.prop === prop);
    },
    getSort(column) {
      const { order, prop } = column;
      let sortType = "";
      if (order === "ascending") {
        sortType = order.slice(0, 3);
      } else if (order === "descending") {
        sortType = order.slice(0, 4);
      }

      return {
        sortCol: sortType ? prop : "",
        sortType: sortType,
      };
    },
    handleChangeSort(column) {
     const {sortCol, sortType} = this.getSort(column)
     console.log(sortCol, sortType)
     // 请求接口
    },
    // 字段值转换
    columnFormatter(row, column, cellValue, index) {
      if (item.property === "systemSts") {
        return "转码";
        // return this.selectDictLabel(this.systemStsArr, row.systemSts)
      } else {
        return cellValue;
      }
    },

    // 编辑操作
    handleEdit(row) {
      console.log("编辑:", row);
      this.$message.success(`准备编辑 ${row.name}`);
    },
    // 删除操作
    handleDelete(row) {
      console.log("删除:", row);
      this.$message.warning(`准备删除 ${row.name}`);
    },
  },
};
</script>

<style scoped>
.table-container {
  padding: 20px;
}
</style>
View Code

 

 

 

2、table基础封装 通过父子组件、slot、多选功能

image

 

父组件部分 fatherTable.vue

<template>
  <div class="table-page">
      <!-- 操作按钮:配合复选框使用 -->
    <div class="table-toolbar" style="margin-bottom: 10px">
      <el-button type="primary" @click="handleGetSelected">获取选中行</el-button>
      <el-button type="warning" @click="handleClearSelected">清空选中</el-button>
      <el-button type="info" @click="handleToggleFirstRow">选中第一行</el-button>
    </div>
    <!-- 使用封装的动态表格 -->
    <DynamicTable
      ref="dynamicTable"
      :table-data="tableData"
      :columns="columns"
      :tableConfig="tableConfig"
      border
      :loading="loading"
       @selection-change="handleSelectionChange"   
       @row-click="handleRowClick"
    >
      <!-- 动态插槽:对应columns中slotName为operation的列 -->
      <template #operation="{ row, index}">
        <el-button type="primary" size="mini" @click="handleEdit(row)">编辑</el-button>
        <el-button type="danger" size="mini" @click="handleDelete(row, index)">删除</el-button>
      </template>

      <!-- 扩展:可自定义其他列的插槽(比如姓名列加样式) -->
      <template #name="{ row }">
        <span :style="{ color: row.age > 20 ? 'red' : 'black' }">
          {{ row.name }}({{ row.age }}岁)
        </span>
      </template>
    </DynamicTable>
  </div>
</template>

<script>
import DynamicTable from "./DynamicTable.vue"; // 引入封装的表格组件

export default {
  name: "TablePage",
  components: { DynamicTable },
  data() {
    return {
      loading: false,
      selectedRows: [],
      // 表格数据
      tableData: [
        { id: 1, name: "张三", age: 22, phone: "13800138000" },
        { id: 2, name: "李四", age: 19, phone: "13900139000" },
        { id: 3, name: "王五", age: 25, phone: "13700137000" }
      ],
      // 动态表头配置(指定插槽名)
      columns: [
        { prop: "id", label: "ID", width: "80px" },
        { prop: "name", label: "姓名", width: "150px", slotName: "name" }, // 自定义姓名列插槽
        { prop: "age", label: "年龄", width: "80px" },
        { prop: "phone", label: "手机号", width: "180px" },
        { prop: "operation", label: "操作", width: "200px", slotName: "operation" } // 操作列插槽
      ],
      // 配置项
      tableConfig:{
        selection: true,
        selectionWidth:'55',
        selectionFixed: true,
        talbeIndex: false
      }
    };
  },
  methods: {
    // 监听选中行变化
    handleSelectionChange(val) {
      this.selectedRows = val;
      console.log("当前选中行:", val);
    },
    // 获取选中行
    handleGetSelected() {
      // const selected = this.$refs.dynamicTable.getSelectedRows();
      const selected =  this.selectedRows
      this.$message.success(`选中了 ${selected.length} 行数据`);
      console.log("获取选中行:", selected);
    },
    // 清空选中行
    handleClearSelected() {
      this.$refs.dynamicTable.clearSelection();
      this.selectedRows = [];
      this.$message.warning("已清空所有选中行");
    },
    // 切换第一行的选中状态
    handleToggleFirstRow() {
      if (this.tableData.length > 0) {
        this.$refs.dynamicTable.toggleSelection(this.tableData[0]);
      }
    },
    // 行点击事件
    handleRowClick(row) {
      console.log("点击行:", row);
      // this.$refs.dynamicTable.toggleSelection(row);
    },
    // 编辑
    handleEdit(row) {
      this.$message.success(`编辑${row.name}`);
    },
    // 删除
    handleDelete(row, index) {
      this.$confirm("确定删除?", "提示", { type: "warning" }).then(() => {
        this.tableData.splice(index, 1);
        this.$message.success("删除成功");
      });
    }
  }
};
</script>
View Code

自组件  DynamicTable.vue

<template>
  <el-table
    ref="dynamicTableRef"
    :data="tableData"
    v-bind="$attrs"
    v-on="$listeners"
    style="width: 100%"
  >
    <!-- 1. 复选框列:配置showSelection为true时渲染 -->
    <el-table-column
      v-if="tableConfig.selection"
      type="selection"
       align="center"
      :width="tableConfig.selectionWidth || '55px'"
      :fixed="tableConfig.selectionFixed || true"
    />
    <el-table-column
      v-if="tableConfig.talbeIndex"
      type="index"
      label="序号"
       align="center"
      width="50"/>
    <!-- 循环渲染动态表头 -->
    <el-table-column
      v-for="(column, index) in columns"
      :key="column.prop || index"
      :prop="column.prop"
      :label="column.label"
      :width="column.width"
      :align="column.align || 'center'"
      :fixed="column.fixed"
    >
      <!-- 动态插槽:如果配置了slotName,则使用对应插槽渲染 -->
      <template slot-scope="scope">
        <!-- 优先使用自定义插槽 -->
        <slot
          v-if="column.slotName"
          :name="column.slotName"
          :row="scope.row"
          :index="scope.$index"
          :column="column"
        />
        <!-- 无插槽时默认渲染字段值 -->
        <span v-else>
          {{ scope.row[column.prop] || "-" }}
        </span>
      </template>
    </el-table-column>
  </el-table>
</template>

<script>
export default {
  name: "DynamicTable",
  props: {
    tableConfig:{
      type:Object,
      default:()=>({
        selection: false,
        selectionWidth:'55',
        selectionFixed: true,
        talbeIndex: false
      })
    },
    // 表格数据源(必传)
    tableData: {
      type: Array,
      required: true,
      default: () => [],
    },
    // 表头配置数组(必传)

    columns: {
      type: Array,
      required: true,
      default: () => [],
      /* 配置项示例:
      [
        { prop: 'id', label: 'ID', width: '80px' }, // 无插槽,默认渲染
        { prop: 'name', label: '姓名', width: '120px' },
        { prop: 'operation', label: '操作', width: '200px', slotName: 'operation' } // 自定义插槽
      ]
      */
    },
  },
  inheritAttrs: false, // 关闭属性继承,避免根元素继承非props属性
  methods: {
    // 暴露给父组件的方法:获取选中行
    getSelectedRows() {
      return this.$refs.dynamicTableRef?.getSelectionRows() || [];
    },
    // 暴露给父组件的方法:清空选中行
    clearSelection() {
      this.$refs.dynamicTableRef?.clearSelection();
    },
    // 暴露给父组件的方法:切换指定行的选中状态
    toggleSelection(row) {
      this.$refs.dynamicTableRef?.toggleRowSelection(row);
    },
  },
};
</script>
View Code

 

 

 

3、table基础封装\展开行\树形数据、懒加载

 

image

 

 

 

展开行  -通过配置项设置

  // 配置项
      tableConfig: {
        selection: false,
        selectionWidth: "55",
        selectionFixed: true,
        talbeIndex: false,
        isExpand: true, // 展开行
        expandSlotName:'expand'
      },

 

属性设置

    <!--type=expand 展开行-->
    <template v-if="tableConfig.isExpand">
      <el-table-column type="expand">
        <template slot-scope="scope">
          <slot
            :name="tableConfig.expandSlotName"
            :row="scope.row"
            :index="scope.$index"
          />
        </template>
      </el-table-column>
    </template>

 

页面插槽部分

    <!-- expand 插槽 -->
      <template #expand="{ row, index }">
       {{ index }} -{{row}}
      </template>

 

完整部分 -树形懒加载部分

父组件。index.vue 

<template>
  <div class="table-page">
    <!-- 操作按钮:配合复选框使用 -->
    <div class="table-toolbar" style="margin-bottom: 10px">
      <el-button type="primary" @click="handleGetSelected"
        >获取选中行</el-button
      >
      <el-button type="warning" @click="handleClearSelected"
        >清空选中</el-button
      >
      <el-button type="info" @click="handleToggleFirstRow"
        >选中第一行</el-button
      >
    </div>
    <!-- 使用封装的动态表格 -->
    <DynamicTable
      ref="dynamicTable"
      :table-data="tableData"
      :columns="columns"
      :tableConfig="tableConfig"
      row-key="id"
      :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
       lazy
       :load="load"
      border
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
      @sort-change="sortChange"
    >
      <!-- 动态插槽:对应columns中slotName为operation的列 -->
      <template #operation="{ row, index }">
        <el-button type="primary" size="mini" @click="handleEdit(row)"
          >编辑</el-button
        >
        <el-button type="danger" size="mini" @click="handleDelete(row, index)"
          >删除</el-button
        >
      </template>

      <!-- 扩展:可自定义其他列的插槽(比如姓名列加样式) -->
      <template #name="{ row }">
        <span :style="{ color: row.age > 20 ? 'red' : 'black' }">
          {{ row.name }}({{ row.age }}岁)
        </span>
      </template>

      <!-- expand 插槽 -->
      <template #expand="{ row, index }">
       {{ index }} -{{row}}
      </template>
    </DynamicTable>
  </div>
</template>

<script>
import DynamicTable from "./DynamicTable.vue"; // 引入封装的表格组件

export default {
  name: "TablePage",
  components: { DynamicTable },
  data() {
    return {
      loading: false,
      selectedRows: [],
      // 表格数据
      tableData: [
        {
          id: 1,
          name: "张三",
          age: 22,
          phone: "13800138000",
          hasChildren: true
          // children: [
          //   {
          //     id: 31,
          //     age: 32,
          //     phone: "12800138000",
          //     name: "王小虎",
          //     children:[]
          //   },
          //   {
          //     id: 32,
          //     age: 33,
          //     phone: "14800138000",
          //     name: "王小虎",
          //     children:[]
          //   },
          // ],
        },
        { id: 2, name: "李四", age: 19, phone: "13900139000" ,hasChildren: true},
        { id: 3, name: "王五", age: 25, phone: "13700137000" },
      ],
       // 配置项
      tableConfig: {
        selection: false,
        selectionWidth: "55",
        selectionFixed: true,
        talbeIndex: false,
        isExpand: true, // 展开行
        expandSlotName:'expand'
      },
      // 动态表头配置(指定插槽名)
      columns: [
        { prop: "id",   label: "ID", width: "180px" },
        {
          prop: "name",  
          label: "姓名",
          width: "150px",
          sortable: true,
          slotName: "name",
        }, // 自定义姓名列插槽
        { prop: "age",  label: "年龄", width: "180px" },
        { prop: "phone", label: "手机号", width: "180px" },
        {
          prop: "operation",
          label: "操作",
          width: "200px",
          slotName: "operation",
        }, // 操作列插槽
      ],
     
    };
  },
  methods: {
    load(tree, treeNode, resolve) {
     console.log('treeNode',treeNode)
     const testData = [
      {
        id: 31,
        age: 32,
        phone: "12800138000",
        name: "王小虎",
        children:[]
      },
     ]
     resolve(testData)
    },
    // 监听选中行变化
    handleSelectionChange(val) {
      this.selectedRows = val;
      console.log("当前选中行:", val);
    },
    // 获取选中行
    handleGetSelected() {
      // const selected = this.$refs.dynamicTable.getSelectedRows();
      const selected = this.selectedRows;
      this.$message.success(`选中了 ${selected.length} 行数据`);
      console.log("获取选中行:", selected);
    },
    // 清空选中行
    handleClearSelected() {
      this.$refs.dynamicTable.clearSelection();
      this.selectedRows = [];
      this.$message.warning("已清空所有选中行");
    },
    // 切换第一行的选中状态
    handleToggleFirstRow() {
      if (this.tableData.length > 0) {
        this.$refs.dynamicTable.toggleSelection(this.tableData[0]);
      }
    },
    // 行点击事件
    handleRowClick(row) {
      console.log("点击行:", row);
      // this.$refs.dynamicTable.toggleSelection(row);
    },
    // 编辑
    handleEdit(row) {
      this.$message.success(`编辑${row.name}`);
    },
    // 删除
    handleDelete(row, index) {
      this.$confirm("确定删除?", "提示", { type: "warning" }).then(() => {
        this.tableData.splice(index, 1);
        this.$message.success("删除成功");
      });
    },
    sortChange(column) {
      const { prop, order } = column;
      // 分割 slice
      console.log(column);
    },
  },
};
</script>
<style scoped> 
/* 父组件样式 */
.el-table {
  --el-table-row-height: 40px; /* 行高足够 */
}
/* 确保树形箭头显示 */
.el-table__expand-icon {
  display: block !important;
  margin-right: 5px;
}
/* 复选框列宽度足够,避免遮挡箭头 */
.el-table-column--selection {
  width: 60px !important;
}
</style>
View Code

子组件部分 DynamicTable

<!-- 
1、 tableData  表格数据
2、 columns 字段数据
3、 tableConfig { 表格配置项
     selection 是否显示复选框 默认 false
     talbeIndex 表格序列号
     selectionWidth
     selectionFixed

     isExpand: false, expandSlotName 指定名字
     row-key 必须id, :tree-props={children: 'children'} 渲染树形数据时
  }
4、树形数据     
-->
<template>
  <el-table
    ref="dynamicTableRef"
    :data="tableData"
    v-bind="$attrs"
    v-on="$listeners"
    style="width: 100%"
  >
    <!--type=expand 展开行-->
    <template v-if="tableConfig.isExpand">
      <el-table-column type="expand">
        <template slot-scope="scope">
          <slot
            :name="tableConfig.expandSlotName"
            :row="scope.row"
            :index="scope.$index"
          />
        </template>
      </el-table-column>
    </template>

    <!-- 1. 复选框列:配置showSelection为true时渲染 -->
    <el-table-column
      v-if="tableConfig.selection"
      type="selection"
      align="center"
      :width="tableConfig.selectionWidth || '55px'"
      :fixed="tableConfig.selectionFixed || true"
    />
    <el-table-column
      v-if="tableConfig.talbeIndex"
      type="index"
      label="序号"
      align="center"
      width="50"
    />

    <!-- type="expand" -->
    <!-- 循环渲染动态表头 -->
    <el-table-column
      v-for="(column, index) in columns"
      :key="column.prop || index"
      :prop="column.prop"
      :label="column.label"
      :width="column.width"
      :align="column.align || 'center'"
      :sortable="column.sortable"
      :fixed="column.fixed"
    >
      <!-- 动态插槽:如果配置了slotName,则使用对应插槽渲染 -->
      <template slot-scope="scope">
        <!-- 优先使用自定义插槽 -->
        <slot
          v-if="column.slotName"
          :name="column.slotName"
          :row="scope.row"
          :index="scope.$index"
          :column="column"
        />
        <!-- 无插槽时默认渲染字段值 -->
        <span v-else>
          {{ scope.row[column.prop] || "-" }}
        </span>
      </template>
    </el-table-column>
  </el-table>
</template>

<script>
export default {
  name: "DynamicTable",
  props: {
    tableConfig: {
      type: Object,
      default: () => ({
        selection: false,
        selectionWidth: "55",
        selectionFixed: true,
        talbeIndex: false,
        isExpand: false,
        expandSlotName: "",
      }),
    },
    // 表格数据源(必传)
    tableData: {
      type: Array,
      required: true,
      default: () => [],
    },
    // 表头配置数组(必传)

    columns: {
      type: Array,
      required: true,
      default: () => [],
      /* 配置项示例:
      [
        { prop: 'id', label: 'ID', width: '80px' }, // 无插槽,默认渲染
        { prop: 'name', label: '姓名', width: '120px' },
        { prop: 'operation', label: '操作', width: '200px', slotName: 'operation' } // 自定义插槽
      ]
      */
    },
  },
  inheritAttrs: false, // 关闭属性继承,避免根元素继承非props属性
  methods: {
    // 暴露给父组件的方法:获取选中行
    getSelectedRows() {
      return this.$refs.dynamicTableRef?.getSelectionRows() || [];
    },
    // 暴露给父组件的方法:清空选中行
    clearSelection() {
      this.$refs.dynamicTableRef?.clearSelection();
    },
    // 暴露给父组件的方法:切换指定行的选中状态
    toggleSelection(row) {
      this.$refs.dynamicTableRef?.toggleRowSelection(row);
    },
  },
};
</script>
View Code

 

4、table基础封装\表格合并\根据单独字段合并\ 组合字段合并

 

image

 数据如下:

image

 

完整示例:

index.vue

<template>
  <div class="table-page">
    <!-- 操作按钮:配合复选框使用 -->
    <div class="table-toolbar" style="margin-bottom: 10px">
      <el-button type="primary" @click="handleGetSelected"
        >获取选中行</el-button
      >
      <el-button type="warning" @click="handleClearSelected"
        >清空选中</el-button
      >
      <el-button type="info" @click="handleToggleFirstRow"
        >选中第一行</el-button
      >
    </div>
    <!-- 使用封装的动态表格 -->
    <DynamicTable
      ref="dynamicTable"
      :table-data="tableData"
      :columns="columns"
      :tableConfig="tableConfig"
      row-key="id"
      :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      lazy
      :load="load"
      border
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
      @sort-change="sortChange"
      :span-method="cellSpanMethod"
    >
      <!-- 动态插槽:对应columns中slotName为operation的列 -->
      <template #operation="{ row, index }">
        <el-button type="primary" size="mini" @click="handleEdit(row)"
          >编辑</el-button
        >
        <el-button type="danger" size="mini" @click="handleDelete(row, index)"
          >删除</el-button
        >
      </template>

      <!-- 扩展:可自定义其他列的插槽(比如姓名列加样式) -->
      <template #name="{ row }">
        <span :style="{ color: row.age > 20 ? 'red' : 'black' }">
          {{ row.name }}({{ row.age }}岁)
        </span>
      </template>

      <!-- expand 插槽 -->
      <template #expand="{ row, index }">
        {{ index }} - {{ JSON.stringify(row) }}
      </template>
    </DynamicTable>
  </div>
</template>

<script>
import DynamicTable from "./DynamicTable.vue"; // 引入封装的表格组件

export default {
  name: "TablePage",
  components: { DynamicTable },
  data() {
    return {
      loading: false,
      selectedRows: [],
      // 1. 定义合并规则:key=列prop,value=分组字段数组(单字段=单独合并,多字段=父级别合并)
      mergeRules: {
        id: ["id"], // 单独字段合并:仅按id
        name: ["name"], // 单独字段合并:仅按name
        age: ["name", "age"], // 父级别合并:按name+age组合
        phone:["parentd","phone"] // 父级别合并:按parentd+ phone组合
      },
      mergeObjCell: {
        // 2. 提前初始化合并列的键,避免清空逻辑跳过
        id: [],
        name: [],
        age: [],
      },
      // 表格数据
      tableData: [
        { id: '0', parentd: 11, name: "张三", age: 22, phone: "13800138000" },
        { id: 1, parentd: 11, name: "李四", age: 19, phone: "13800138000" },
        { id: 3, parentd: 22, name: "王五", age: 26, phone: "13700137000" },
        { id: 5, parentd: 22, name: "王五", age: 26, phone: "13700137000" },
        { id: 6, parentd: 33, name: "王五1", age: 26, phone: "13700137000" },
        { id: 7, parentd: 44, name: "王五1", age: 26, phone: "13700137000" },
      ],
      // 配置项
      tableConfig: {
        selection: true,
        selectionWidth: "55",
        selectionFixed: true,
        talbeIndex: false,
        isExpand: false, // 展开行
        expandSlotName: "expand",
      },
      // 动态表头配置(指定插槽名)
      columns: [
        { prop: "id", label: "ID", width: "180px", slotHeader: true },
        {
          prop: "name",
          label: "姓名",
          width: "150px",
          sortable: true,
          slotName: "name",
        }, // 自定义姓名列插槽
        { prop: "age", label: "年龄", width: "180px" },
        { prop: "phone", label: "手机号", width: "180px" },
        {
          prop: "operation",
          label: "操作",
          width: "200px",
          slotName: "operation",
        }, // 操作列插槽
      ],
    };
  },
  mounted() {
    // 数据处理:传入合并规则(支持单字段/父级别)
    this.getSpanArrCommon(this.tableData, this.mergeRules, this.mergeObjCell);
    console.log("合并配置:", this.mergeObjCell); // 验证配置是否生成
  },
  methods: {
    // 表格合并方法(修正参数取值 + 返回值格式)
    cellSpanMethod({ row, column, rowIndex, columnIndex }) {
      const colProp = column.property;
      // 仅处理配置了合并规则的列
      if (this.mergeObjCell[colProp] !== undefined) {
        const rowspan = this.mergeObjCell[colProp][rowIndex] || 0;
        return {
          rowspan: rowspan, // 合并行数(0=隐藏,>0=合并)
          colspan: 1, // 列不合并
        };
      }
      // 未配置的列:默认不合并
      return { rowspan: 1, colspan: 1 };
    },

    /**
     * 通用合并计算方法(支持单字段/父级别组合合并)
     * @param {Array} data - 表格数据
     * @param {Object} mergeRules - 合并规则 { 列prop: 分组字段数组 }
     * @param {Object} mergeObj - 输出的合并配置对象
     */
    getSpanArrCommon(data, mergeRules, mergeObj) {
      if (!data || !data.length || !mergeRules) {
        return;
      }

      // 清空旧的合并配置
      Object.keys(mergeObj).forEach((key) => {
        mergeObj[key] = [];
      });

      // 遍历每一列的合并规则
      Object.entries(mergeRules).forEach(([colProp, groupFields]) => {
        let count = 0; // 合并起始行索引
        mergeObj[colProp] = []; // 初始化当前列的合并数组

        // 遍历每行数据计算合并数
        data.forEach((item, index) => {
          if (index === 0) {
            // 第一行默认合并数为1
            mergeObj[colProp].push(1);
          } else {
            // 生成分组标识(支持多字段组合)
            const getGroupKey = (row) => {
              return groupFields
                .map((field) => {
                  const value = row[field];
                  return value === null || value === undefined ? "" : value;
                })
                .join("|");
            };
            const currentKey = getGroupKey(item);
            const prevKey = getGroupKey(data[index - 1]);

            // 判断是否属于同一分组
            if (currentKey === prevKey) {
              // 同一分组:起始行合并数+1,当前行标记为0
              mergeObj[colProp][count] += 1;
              mergeObj[colProp].push(0);
            } else {
              // 不同分组:重置起始索引,当前行合并数为1
              count = index;
              mergeObj[colProp].push(1);
            }
          }
        });
      });
    },

    load(tree, treeNode, resolve) {
      console.log("treeNode", treeNode);
      const testData = [
        {
          id: 31,
          age: 32,
          phone: "12800138000",
          name: "王小虎",
          children: [],
        },
      ];
      resolve(testData);
    },

    // 监听选中行变化
    handleSelectionChange(val) {
      this.selectedRows = val;
      console.log("当前选中行:", val);
    },
    // 获取选中行
    handleGetSelected() {
      const selected = this.selectedRows;
      this.$message.success(`选中了 ${selected.length} 行数据`);
      console.log("获取选中行:", selected);
    },
    // 清空选中行
    handleClearSelected() {
      this.$refs.dynamicTable.clearSelection();
      this.selectedRows = [];
      this.$message.warning("已清空所有选中行");
    },
    // 切换第一行的选中状态
    handleToggleFirstRow() {
      if (this.tableData.length > 0) {
        this.$refs.dynamicTable.toggleSelection(this.tableData[0]);
      }
    },
    // 行点击事件
    handleRowClick(row) {
      console.log("点击行:", row);
    },
    // 编辑
    handleEdit(row) {
      this.$message.success(`编辑${row.name}`);
    },
    // 删除
    handleDelete(row, index) {
      this.$confirm("确定删除?", "提示", { type: "warning" }).then(() => {
        this.tableData.splice(index, 1);
        // 删除后重新计算合并配置
        this.getSpanArrCommon(
          this.tableData,
          this.mergeRules,
          this.mergeObjCell,
        );
        this.$message.success("删除成功");
      });
    },
    sortChange(column) {
      const { prop, order } = column;
      console.log(column);
      // 排序后重新计算合并配置
      this.getSpanArrCommon(this.tableData, this.mergeRules, this.mergeObjCell);
    },
  },
};
</script>

<style scoped>
/* 父组件样式 */
.el-table {
  --el-table-row-height: 40px; /* 行高足够 */
}
/* 确保树形箭头显示 */
.el-table__expand-icon {
  display: block !important;
  margin-right: 5px;
}
/* 复选框列宽度足够,避免遮挡箭头 */
.el-table-column--selection {
  width: 60px !important;
}
</style>
View Code

 

DynamicTable.vue (无变化)

 

 

 

5、el-table 大数据展示卡顿问题 

     方案1  slice解决

 async loadMoreData() {
      // 避免重复加载(前置判断,快速返回)
      if (this.loading || this.noMore) return;
      this.loading = true;
      try {
        // 实际接口
        await this.mockApiRequest();

        // 计算数据索引(优化:边界处理,避免超出数组长度)
        const start = (this.currentPage - 1) * this.pageSize;
        const end = Math.min(start + this.pageSize, this.originData.length);

        const newData = this.originData.slice(start, end);

        this.tableData = [...this.tableData, ...newData];
        // 合并单元格
        getSpanArrCommon([...this.tableData],this.mergeRules,this.mergeObjCell);

        // 更新分页状态
        this.currentPage += 1;
        // 判断是否加载完毕(优化:精准判断,避免无效加载)
        this.noMore = end >= this.originData.length;
      } finally {
        // 无论成功失败,都解除加载锁(优化:避免加载状态卡死)
        this.loading = false;
      }

 

 监听加载滚动 ---el-table滚动 这里是设置el-table 高度 表头固定

/ 组件挂载后,获取内部滚动容器
    this.scrollContainer =
      this.$refs.dynamicTable.$refs.dynamicTableRef.$el.querySelector(
        ".el-table__body-wrapper",
      );
    // 绑定原生 scroll 事件
    if (this.scrollContainer) {
      this.scrollContainer.addEventListener("scroll", this.handleTableScroll);
    }

 

滚动加载更多

 handleTableScroll: _.throttle(function (scrollInfo) {
      const { scrollTop, scrollHeight, clientHeight } = scrollInfo.target;
      console.log(scrollTop, scrollHeight, clientHeight);
      // 滚动距离底部50px时触发加载(优化:增加边界判断,避免无效触发)
      const isNearBottom = scrollTop + clientHeight >= scrollHeight - 500;
      // 仅当接近底部、未加载中、还有数据时,才触发加载
      if (isNearBottom && !this.loading && !this.noMore) {
        console.log("scrollInfo", scrollInfo);
        this.loadMoreData();
      }
    }, 500),

 

 

完整代码--根据实际是否需要合并

index.vue

<template>
  <div class="table-page">
    <!-- 操作按钮:配合复选框使用 -->
    <div class="table-toolbar" style="margin-bottom: 10px">
      <el-button type="primary" @click="handleGetSelected"
        >获取选中行</el-button
      >
      <el-button type="warning" @click="handleClearSelected"
        >清空选中</el-button
      >
      <el-button type="info" @click="handleToggleFirstRow"
        >选中第一行</el-button
      >
    </div>
    <!-- 使用封装的动态表格 -->
    <DynamicTable
      ref="dynamicTable"
      :table-data="tableData"
      :columns="columns"
      :tableConfig="tableConfig"
      row-key="id"
      :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      lazy
      :load="load"
      border
      height="90vh"
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
      @sort-change="sortChange"
      :span-method="cellSpanMethod"
    >
      <!-- 动态插槽:对应columns中slotName为operation的列 -->
      <template #operation="{ row, index }">
        <el-button type="primary" size="mini" @click="handleEdit(row)"
          >编辑</el-button
        >
        <el-button type="danger" size="mini" @click="handleDelete(row, index)"
          >删除</el-button
        >
      </template>

      <!-- 扩展:可自定义其他列的插槽(比如姓名列加样式) -->
      <template #name="{ row }">
        <span :style="{ color: row.age > 20 ? 'red' : 'black' }">
          {{ row.name }}({{ row.age }}岁)
        </span>
      </template>

      <!-- expand 插槽 -->
      <template #expand="{ row, index }">
        {{ index }} - {{ JSON.stringify(row) }}
      </template>
    </DynamicTable>
  </div>
</template>

<script>
// 引入 lodash 节流(若项目未集成,可使用自定义节流函数)
import _ from "lodash";
import DynamicTable from "./DynamicTable.vue"; // 引入封装的表格组件
import { getSpanArrCommon } from "./utils.js";

export default {
  name: "TablePage",
  components: { DynamicTable },
  data() {
    return {
      originData: [], // 大数据量存储
      tableData: [], // 当前页面表格渲染
      pageSize: 10, // 每次加载100条(可根据性能调整,建议50-200)
      currentPage: 1, // 当前加载到第几页
      loading: false, // 是否正在加载
      noMore: false, // 是否已加载全部数据

      loading: false,
      selectedRows: [],
      // 1. 定义合并规则:key=列prop,value=分组字段数组(单字段=单独合并,多字段=父级别合并)
      mergeRules: {
        id: ["id"], // 单独字段合并:仅按id
        name: ["name"], // 单独字段合并:仅按name
        age: ["name", "age"], // 父级别合并:按name+age组合
        phone: ["parentd", "phone"], // 父级别合并:按parentd+ phone组合
      },
      mergeObjCell: {
        // 2. 提前初始化合并列的键,避免清空逻辑跳过
        id: [],
        name: [],
        age: [],
      },
      // 表格数据
      tableData: [
        { id: "0", parentd: 11, name: "张三", age: 22, phone: "13800138000" },
        { id: 1, parentd: 11, name: "李四", age: 19, phone: "13800138000" },
        { id: 3, parentd: 22, name: "王五", age: 26, phone: "13700137000" },
        { id: 5, parentd: 22, name: "王五", age: 26, phone: "13700137000" },
        { id: 6, parentd: 33, name: "王五1", age: 26, phone: "13700137000" },
        { id: 7, parentd: 44, name: "王五1", age: 26, phone: "13700137000" },
      ],
      // 配置项
      tableConfig: {
        selection: true,
        selectionWidth: "55",
        selectionFixed: true,
        talbeIndex: false,
        isExpand: false, // 展开行
        expandSlotName: "expand",
      },
      // 动态表头配置(指定插槽名)
      columns: [
        { prop: "id", label: "ID", width: "180px", slotHeader: true },
        {
          prop: "name",
          label: "姓名",
          width: "150px",
          sortable: true,
          slotName: "name",
        }, // 自定义姓名列插槽
        { prop: "age", label: "年龄", width: "180px" },
        { prop: "phone", label: "手机号", width: "180px" },
        {
          prop: "operation",
          label: "操作",
          width: "200px",
          slotName: "operation",
        }, // 操作列插槽
      ],
    };
  },
  mounted() {
    // test
    let arrTmep = [];
    for (let i = 0; i < 100; i++) {
      arrTmep.push({
        id: 100 + i,
        parentd: 44,
        name: "王五1",
        age: 26,
        phone: "13700137000",
      });
    }
    this.originData = [...arrTmep];
    this.loadMoreData();

    // 组件挂载后,获取内部滚动容器
    this.scrollContainer =
      this.$refs.dynamicTable.$refs.dynamicTableRef.$el.querySelector(
        ".el-table__body-wrapper",
      );
    // 绑定原生 scroll 事件
    if (this.scrollContainer) {
      this.scrollContainer.addEventListener("scroll", this.handleTableScroll);
    }

    // this.tableData = [...this.tableData, ...arrTmep]
    // console.log('arrTmep',arrTmep)
    // 数据处理:传入合并规则(支持单字段/父级别)
    // getSpanArrCommon([...this.tableData, ...arrTmep], this.mergeRules, this.mergeObjCell);
    // console.log("合并配置:", this.mergeObjCell); // 验证配置是否生成
  },
  methods: {
    handleTableScroll: _.throttle(function (scrollInfo) {
      const { scrollTop, scrollHeight, clientHeight } = scrollInfo.target;
      console.log(scrollTop, scrollHeight, clientHeight);
      // 滚动距离底部50px时触发加载(优化:增加边界判断,避免无效触发)
      const isNearBottom = scrollTop + clientHeight >= scrollHeight - 500;
      // 仅当接近底部、未加载中、还有数据时,才触发加载
      if (isNearBottom && !this.loading && !this.noMore) {
        console.log("scrollInfo", scrollInfo);
        this.loadMoreData();
      }
    }, 500),
    async loadMoreData() {
      // 避免重复加载(前置判断,快速返回)
      if (this.loading || this.noMore) return;
      this.loading = true;
      try {
        // 实际接口
        await this.mockApiRequest();

        // 计算数据索引(优化:边界处理,避免超出数组长度)
        const start = (this.currentPage - 1) * this.pageSize;
        const end = Math.min(start + this.pageSize, this.originData.length);

        const newData = this.originData.slice(start, end);

        this.tableData = [...this.tableData, ...newData];
        // 合并单元格
        getSpanArrCommon([...this.tableData],this.mergeRules,this.mergeObjCell);

        // 更新分页状态
        this.currentPage += 1;
        // 判断是否加载完毕(优化:精准判断,避免无效加载)
        this.noMore = end >= this.originData.length;
      } finally {
        // 无论成功失败,都解除加载锁(优化:避免加载状态卡死)
        this.loading = false;
      }
    },
    // 表格合并方法(修正参数取值 + 返回值格式)
    cellSpanMethod({ row, column, rowIndex, columnIndex }) {
      const colProp = column.property;
      // 仅处理配置了合并规则的列
      if (this.mergeObjCell[colProp] !== undefined) {
        const rowspan = this.mergeObjCell[colProp][rowIndex] || 0;
        return {
          rowspan: rowspan, // 合并行数(0=隐藏,>0=合并)····
          colspan: 1, // 列不合并·
        };
      }
      // 未配置的列:默认不合并
      return { rowspan: 1, colspan: 1 };
    },

    mockApiRequest() {
      return new Promise((resolve) => {
        setTimeout(resolve, 300); // 缩短延迟时间,提升用户体验
      });
    },

    /**
     * 通用合并计算方法(支持单字段/父级别组合合并)
     * @param {Array} data - 表格数据
     * @param {Object} mergeRules - 合并规则 { 列prop: 分组字段数组 }
     * @param {Object} mergeObj - 输出的合并配置对象
     */
    getSpanArrCommon(data, mergeRules, mergeObj) {
      if (!data || !data.length || !mergeRules) {
        return;
      }

      // 清空旧的合并配置
      Object.keys(mergeObj).forEach((key) => {
        mergeObj[key] = [];
      });

      // 遍历每一列的合并规则
      Object.entries(mergeRules).forEach(([colProp, groupFields]) => {
        let count = 0; // 合并起始行索引
        mergeObj[colProp] = []; // 初始化当前列的合并数组

        // 遍历每行数据计算合并数
        data.forEach((item, index) => {
          if (index === 0) {
            // 第一行默认合并数为1
            mergeObj[colProp].push(1);
          } else {
            // 生成分组标识(支持多字段组合)
            const getGroupKey = (row) => {
              return groupFields
                .map((field) => {
                  const value = row[field];
                  return value === null || value === undefined ? "" : value;
                })
                .join("|");
            };
            const currentKey = getGroupKey(item);
            const prevKey = getGroupKey(data[index - 1]);

            // 判断是否属于同一分组
            if (currentKey === prevKey) {
              // 同一分组:起始行合并数+1,当前行标记为0
              mergeObj[colProp][count] += 1;
              mergeObj[colProp].push(0);
            } else {
              // 不同分组:重置起始索引,当前行合并数为1
              count = index;
              mergeObj[colProp].push(1);
            }
          }
        });
      });
    },

    load(tree, treeNode, resolve) {
      console.log("treeNode", treeNode);
      const testData = [
        {
          id: 31,
          age: 32,
          phone: "12800138000",
          name: "王小虎",
          children: [],
        },
      ];
      resolve(testData);
    },

    // 监听选中行变化
    handleSelectionChange(val) {
      this.selectedRows = val;
      console.log("当前选中行:", val);
    },
    // 获取选中行
    handleGetSelected() {
      const selected = this.selectedRows;
      this.$message.success(`选中了 ${selected.length} 行数据`);
      console.log("获取选中行:", selected);
    },
    // 清空选中行
    handleClearSelected() {
      this.$refs.dynamicTable.clearSelection();
      this.selectedRows = [];
      this.$message.warning("已清空所有选中行");
    },
    // 切换第一行的选中状态
    handleToggleFirstRow() {
      if (this.tableData.length > 0) {
        this.$refs.dynamicTable.toggleSelection(this.tableData[0]);
      }
    },
    // 行点击事件
    handleRowClick(row) {
      console.log("点击行:", row);
    },
    // 编辑
    handleEdit(row) {
      this.$message.success(`编辑${row.name}`);
    },
    // 删除
    handleDelete(row, index) {
      this.$confirm("确定删除?", "提示", { type: "warning" }).then(() => {
        this.tableData.splice(index, 1);
        // 删除后重新计算合并配置
        getSpanArrCommon(this.tableData, this.mergeRules, this.mergeObjCell);
        this.$message.success("删除成功");
      });
    },
    sortChange(column) {
      const { prop, order } = column;
      console.log(column);
      // 排序后重新计算合并配置
      getSpanArrCommon(this.tableData, this.mergeRules, this.mergeObjCell);
    },
  },
};
</script>

<style scoped>
/* 父组件样式 */
.el-table {
  --el-table-row-height: 40px; /* 行高足够 */
}
/* 确保树形箭头显示 */
.el-table__expand-icon {
  display: block !important;
  margin-right: 5px;
}
/* 复选框列宽度足够,避免遮挡箭头 */
.el-table-column--selection {
  width: 60px !important;
}
</style>
View Code

 

DynamicTable.vue (原有的)

<!-- 
1、 tableData  表格数据
2、 columns 字段数据
3、 tableConfig { 表格配置项
     selection 是否显示复选框 默认 false
     talbeIndex 表格序列号
     selectionWidth
     selectionFixed

     isExpand: false, expandSlotName 指定名字
     row-key 必须id, :tree-props={children: 'children'} 渲染树形数据时
     headerSlot 自定义头部
  }
4、树形数据     
-->
<template>
  <el-table
    ref="dynamicTableRef"
    :data="tableData"
    v-bind="$attrs"
    v-on="$listeners"
    style="width: 100%"
  >
    <!--type=expand 展开行-->
    <template v-if="tableConfig.isExpand">
      <el-table-column type="expand">
        <template slot-scope="scope">
          <slot
            :name="tableConfig.expandSlotName"
            :row="scope.row"
            :index="scope.$index"
          />
        </template>
      </el-table-column>
    </template>

    <!-- 1. 复选框列:配置showSelection为true时渲染 -->
    <el-table-column
      v-if="tableConfig.selection"
      type="selection"
      align="center"
      :width="tableConfig.selectionWidth || '55px'"
      :fixed="tableConfig.selectionFixed || true"
    />
    <el-table-column
      v-if="tableConfig.talbeIndex"
      type="index"
      label="序号"
      align="center"
      width="50"
    />
    <!-- type="expand" -->
    <!-- 循环渲染动态表头 -->
    <el-table-column
      v-for="(column, index) in columns"
      :key="column.prop || index"
      :prop="column.prop"
      :label="column.label"
      :width="column.width"
      :align="column.align || 'center'"
      :sortable="column.sortable"
      :fixed="column.fixed"
    >
     <!-- 自定义头部 -->
      <template slot="header"  v-if="column.slotHeader">
        自定义ID
     </template>
    
      <!-- 动态插槽:如果配置了slotName,则使用对应插槽渲染 -->
      <template  slot-scope="scope"  >
        <!-- 优先使用自定义插槽 -->
        <slot
          v-if="column.slotName"
          :name="column.slotName"
          :row="scope.row"
          :index="scope.$index"
          :column="column"
        />
        <!-- 无插槽时默认渲染字段值 -->
        <span v-else>
          {{ scope.row[column.prop] || "-" }}
        </span>
      </template>
    </el-table-column>
  </el-table>
</template>

<script>
export default {
  name: "DynamicTable",
  props: {
    tableConfig: {
      type: Object,
      default: () => ({
        selection: false,
        selectionWidth: "55",
        selectionFixed: true,
        talbeIndex: false,
        isExpand: false,
        expandSlotName: "",
      }),
    },
    // 表格数据源(必传)
    tableData: {
      type: Array,
      required: true,
      default: () => [],
    },
    // 表头配置数组(必传)

    columns: {
      type: Array,
      required: true,
      default: () => [],
      /* 配置项示例:
      [
        { prop: 'id', label: 'ID', width: '80px' }, // 无插槽,默认渲染
        { prop: 'name', label: '姓名', width: '120px' },
        { prop: 'operation', label: '操作', width: '200px', slotName: 'operation' } // 自定义插槽
      ]
      */
    },
  },
  inheritAttrs: false, // 关闭属性继承,避免根元素继承非props属性
  methods: {
    // 暴露给父组件的方法:获取选中行
    getSelectedRows() {
      return this.$refs.dynamicTableRef?.getSelectionRows() || [];
    },
    // 暴露给父组件的方法:清空选中行
    clearSelection() {
      this.$refs.dynamicTableRef?.clearSelection();
    },
    // 暴露给父组件的方法:切换指定行的选中状态
    toggleSelection(row) {
      this.$refs.dynamicTableRef?.toggleRowSelection(row);
    },
  },
};
</script>
View Code

utils.js 

// utils/func.js
/**
 * 可配置防抖函数
 * @param {Function} fn - 要防抖的函数
 * @param {Number} delay - 延迟时间
 * @param {Boolean} immediate - 是否首次立即执行,默认false(延迟执行)
 * @returns {Function} 包装后的防抖函数
 */
export function debounce1(fn, delay = 500, immediate = false) {
  let timer = null;
  let isFirst = false;
  return function (...args) {
    // 清除之前的定时器
    if (timer) clearTimeout(timer);

    // 首次立即执行的逻辑
    if (immediate && !timer && !isFirst) {
      isFirst = true;
      fn.apply(this, args);
    } else if (isFirst) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

// 防抖函数
export function debounce(fn, delay = 500) {
  let timer = null;
  // 返回包装后的函数,保留 this 指向和参数
  return function (...args) {
    // 清除之前的定时器,重新计时
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args); // 执行原函数,传递参数和 this
      timer = null; // 执行后清空定时器
    }, delay);
  };
}

// 节流函数(时间戳版)
export function throttle(fn, interval = 500) {
  let lastTime = 0; // 上一次执行的时间
  return function (...args) {
    const now = Date.now();
    // 距离上次执行超过间隔时间,才执行
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now; // 更新上次执行时间
    }
  };
}

export function getSpanArrCommon(data, mergeRules, mergeObj) {
  if (!data || !data.length || !mergeRules) {
    return;
  }

  // 清空旧的合并配置
  Object.keys(mergeObj).forEach((key) => {
    mergeObj[key] = [];
  });

  // 遍历每一列的合并规则
  Object.entries(mergeRules).forEach(([colProp, groupFields]) => {
    let count = 0; // 合并起始行索引
    mergeObj[colProp] = []; // 初始化当前列的合并数组

    // 遍历每行数据计算合并数
    data.forEach((item, index) => {
      if (index === 0) {
        // 第一行默认合并数为1
        mergeObj[colProp].push(1);
      } else {
        // 生成分组标识(支持多字段组合)
        const getGroupKey = (row) => {
          return groupFields
            .map((field) => {
              const value = row[field];
              return value === null || value === undefined ? "" : value;
            })
            .join("|");
        };
        const currentKey = getGroupKey(item);
        const prevKey = getGroupKey(data[index - 1]);

        // 判断是否属于同一分组
        if (currentKey === prevKey) {
          // 同一分组:起始行合并数+1,当前行标记为0
          mergeObj[colProp][count] += 1;
          mergeObj[colProp].push(0);
        } else {
          // 不同分组:重置起始索引,当前行合并数为1
          count = index;
          mergeObj[colProp].push(1);
        }
      }
    });
  });
}

const myName = () => {
  return "zs====";
};

export { myName };

export default {
  name: "zs",
  age: 22,
};
View Code

 

 

6、el-table 数据扁平-处理成嵌套

 

看工具类

 

 

 

 

 

 

 

 ===================以下更乱😂===============================

多级表头--静态
 <el-table
      :data="tableData"
      highlight-current-row
      @current-change="handleCurrentChange"
      style="width: 100%"
    >
      <el-table-column prop="date" label="日期" width="150"> </el-table-column>
      <el-table-column label="配送信息">
        <el-table-column prop="name" label="姓名" width="120">
        </el-table-column>
        <el-table-column label="地址">
          <el-table-column prop="province" label="省份" width="120">
          </el-table-column>
          <el-table-column prop="city" label="市区" width="120">
          </el-table-column>
          <el-table-column prop="address" label="地址" width="300">
          </el-table-column>
          <el-table-column prop="zip" label="邮编" width="120">
          </el-table-column>
        </el-table-column>
      </el-table-column>
    </el-table>
View Code

 

 

image

  

2、多级表头--递归
<h1>多级表头--动态-组件递归</h1>
    选择id:{{ tabSelIds }} -
    <el-button type="primary" @click="selcetAll">全选</el-button>
    <el-button type="primary" @click="cancelAll">取消全选</el-button>
    <el-button type="primary" @click="selectedIdsFn">默认勾选【1,3】</el-button>
    <el-table
      ref="myTable"
      :data="tableDataSyn"
      highlight-current-row
      @selection-change="handleSelectionChange"
      @row-click="currentSelect"
      :default-selection-keys="[1]"
      border
      style="width: 100%"
    >
      <el-table-column type="selection" width="55" :selectable="selectable">
      </el-table-column>
      <template v-for="(item, index) in tableColumns">
        <template v-if="item.children && item.children.length">
          <table-column
            v-if="item.children && item.children.length"
            :key="index"
            :coloumn-header="item"
          ></table-column>
        </template>
        <el-table-column
          v-else-if="item.prop == 'operation'"
          :key="index"
          :label="item.label"
          :prop="item.prop"
        >
          <template slot-scope="scope">
            <custom-button
              content="修改"
              @click.native.stop="handleTest(scope.row)"
              name="recording"
            />
          </template>
        </el-table-column>

        <el-table-column
          v-else
          :key="index"
          :label="item.label"
          :prop="item.prop"
        ></el-table-column>

        <!-- -->

        <!-- <table-column
          v-if="item.children && item.children.length"
          :key="index"
          :coloumn-header="item"
        ></table-column>
        <el-table-column
          v-else
          :key="index"
          :label="item.label"
          :prop="item.prop"
        ></el-table-column> -->
      </template>
    </el-table>
View Code

 

 

组件部分。

 TableHeader.vue
<template>
  <el-table-column
    :key="column.key || column.prop"
    :label="column.label"
    :prop="column.prop"
    :width="column.width"
    :min-width="column.minWidth || 80"
    :align="column.align || 'center'"
    :fixed="column.fixed"
  >
    <!-- 递归渲染子表头:有children则继续调用自身 -->
    <template v-if="column.children && column.children.length > 0">
      <table-header
        v-for="(child, childIndex) in column.children"
        :key="childIndex"
        :column="child"
      />
    </template>
  </el-table-column>
</template>

<script>
export default {
  name: "TableHeader", // 必须命名,支持递归调用
  props: {
    column: {
      type: Object,
      required: true,
      default: () => ({})
    }
  }
};
</script>

 

 ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- -------

image

 

完整代码,tableData 数据返回格式 扁平返回,表头可以有子集children
 
<template>
  <div class="large-table-container">
    <!-- 开启虚拟滚动 + 懒加载,优化大量列渲染性能 -->
    <el-table
      :data="tableData"
      border
      style="width: 100%"
      :header-cell-style="{ background: '#f5f7fa' }"
      :virtual-scroll="true"
      :virtual-scroll-item-size="40"
      lazy
      :load="loadMoreData"
      :loading="tableLoading"
    >
      <!-- 引入递归表头组件,批量渲染所有表头 -->
      <table-header
        v-for="(col, idx) in dynamicHeaders"
        :key="idx"
        :column="col"
      />
    </el-table>
  </div>
</template>

<script>
import TableHeader from "./TableHeader.vue"; // 引入递归组件

export default {
  components: { TableHeader },
  data() {
    return {
      tableLoading: false,
      // 模拟大量多级表头配置(可从接口获取)
      dynamicHeaders: [],
      // 表格数据
      tableData: [],
    };
  },
  created() {
    // 初始化:模拟从接口获取大量表头配置
    this.getLargeHeaderConfig();
    // 初始化表格数据
    this.getTableData();
  },
  methods: {
    // 模拟获取大量多级表头配置(支持任意层级)
    getLargeHeaderConfig() {
      // 实际项目中替换为真实接口请求
      this.dynamicHeaders = [
        {
          label: "基础信息",
          key: "base",
          children: [
            {
              label: "个人信息",
              prop: "personal",
              children: [
                { label: "姓名", prop: "name", width: 100 },
                { label: "年龄", prop: "age", width: 80 },
                { label: "性别", prop: "gender", width: 80 },
                { label: "身份证号", prop: "idCard", width: 180 },
              ],
            },
            {
              label: "联系方式",
              prop: "contact",
              children: [
                { label: "手机号", prop: "phone", width: 120 },
                { label: "邮箱", prop: "email", width: 200 },
                {
                  label: "地址",
                  prop: "address",
                  children: [
                    {
                      label: "",
                      prop: "province",
                      width: 100,
                      children: [
                        { label: "省1", prop: "province1", width: 200 },
                        { label: "省2", prop: "province2", width: 200 },
                      ],
                    },
                    { label: "", prop: "city", width: 100 },
                    { label: "", prop: "area", width: 100 },
                    { label: "详细地址", prop: "detail", minWidth: 200 },
                  ],
                },
              ],
            },
          ],
        },
        {
          label: "学业信息",
          key: "study",
          children: [
            // 可无限嵌套,递归组件自动渲染
            {
              label: "小学",
              key: "primary",
              children: [
                { label: "学校名称", prop: "name", width: 150 },
                { label: "入学时间", prop: "entryTime", width: 120 },
                { label: "毕业时间", prop: "gradTime", width: 120 },
              ],
            },
            {
              label: "初中",
              key: "middle",
              children: [
                { label: "学校名称", prop: "name", width: 150 },
                { label: "入学时间", prop: "entryTime", width: 120 },
                { label: "毕业时间", prop: "gradTime", width: 120 },
              ],
            },
            // 可继续添加高中、大学等层级...
          ],
        },
        // 可继续添加更多大列(如家庭信息、奖惩信息等)...
      ];
    },

    // 模拟获取表格数据
    getTableData() {
      this.tableData = [
        {
          name: "张三",
          age: 20,
          gender: "",
          idCard: "110101199901011234",
          phone: "13800138000",
          email: "zhangsan@example.com",
          city: "北京市",
          area: "朝阳区",
          detail: "XX小区1号楼1单元101",
          province1: "北京市1",
          province2: "北京市2",
          name: "XX小学",
          entryTime: "2010-09-01",
          gradTime: "2016-06-30",
          name: "XX中学",
          entryTime: "2016-09-01",
          gradTime: "2019-06-30",
        },
        // 模拟多条数据...
      ];
    },

    // 懒加载加载更多数据(数据量大时用)
    loadMoreData(tree, treeNode, resolve) {
      this.tableLoading = true;
      // 模拟接口请求
      setTimeout(() => {
        const newData = [
          /* 新数据 */
        ];
        this.tableData = [...this.tableData, ...newData];
        resolve();
        this.tableLoading = false;
      }, 500);
    },

  },
};
</script>

<style scoped>
.large-table-container {
  padding: 20px;
  overflow-x: auto; /* 列数过多时横向滚动 */
}

/* 优化多级表头样式,避免层级过深导致文字挤压 */
::v-deep .el-table th {
  font-weight: 600;
  white-space: nowrap; /* 表头文字不换行 */
}
::v-deep .el-table td {
  padding: 8px 0;
}
/* 虚拟滚动优化 */
::v-deep .el-table__body-wrapper {
  overflow: auto;
}
</style>
View Code

 

 

 
 
 

 

posted on 2026-02-11 11:33  Mc525  阅读(27)  评论(0)    收藏  举报

导航