需求
合并行、合并标题、列宽可调整、列顺序可调整、可以控制列是否显示、列布局可保存、导出excel…
 
参考效果

 
代码
引入
npm i xlsx
npm install element-plus --save
table组件
<template>
  <div>
    <div class="table-btn">
      <el-tooltip content="高级筛选" placement="bottom" effect="light">
        <el-button :icon="Search" circle @click="dialogRulesVisible = true" />
      </el-tooltip>
      <el-tooltip content="修改table" placement="bottom" effect="light">
        <el-button :icon="EditPen" circle @click="dialogVisible = true" />
      </el-tooltip>
      <el-tooltip content="导出xlsx" placement="bottom" effect="light">
        <el-button :icon="Download" circle @click="exportBtn" />
      </el-tooltip>
    </div>
    <el-table :data="tableData" style="width: 100%" ref="exportTableRef">
      <template v-for="item in tablePropList">
        <el-table-column
          sortable
          :key="item.prop"
          :prop="item.prop"
          :label="item.label"
          :align="item.align"
          :width="item.width == 0 ? '' : item.width"
          v-if="item.isShow == 0"
          :fixed="item.fixed"
        >
          <template v-if="item.children.length > 0">
            <template v-for="itemChildren in item.children">
              <el-table-column
                :key="itemChildren.prop"
                sortable
                :prop="itemChildren.prop"
                :label="itemChildren.label"
                :align="itemChildren.align"
                :width="itemChildren.width == 0 ? '' : itemChildren.width"
                v-if="itemChildren.isShow == 0"
              >
              </el-table-column>
            </template>
          </template>
        </el-table-column>
      </template>
      <!-- 插槽 -->
      <slot />
    </el-table>
    <!-- 把这个改成表格 -->
    <el-dialog v-model="dialogVisible" title="修改表格" width="80%">
      <div class="over-height">
        <el-table :data="tablePropList" style="width: 100%" row-key="prop">
          <el-table-column prop="date" label="表格名">
            <template #default="scope">
              <el-input v-model="scope.row.label" />
            </template>
          </el-table-column>
          <el-table-column prop="date" label="序列">
            <template #default="scope">
              <el-input-number v-model="scope.row.index" :min="0" :max="100" />
            </template>
          </el-table-column>
          <el-table-column prop="date" label="宽度(0为自适应)">
            <template #default="scope">
              <el-input-number v-model="scope.row.width" :min="0" :max="1000" />
            </template>
          </el-table-column>
          <el-table-column prop="date" label="对齐方式">
            <template #default="scope">
              <el-select v-model="scope.row.align">
                <el-option label="左对齐" value="left" />
                <el-option label="居中" value="center" />
                <el-option label="右对齐" value="right" />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column prop="date" label="是否固定">
            <template #default="scope">
              <el-select v-model="scope.row.fixed">
                <el-option label="左侧固定" value="left" />
                <el-option label="右侧固定" value="right" />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column prop="date" label="是否显示">
            <template #default="scope">
              <el-select v-model="scope.row.isShow">
                <el-option label="显示" value="0" />
                <el-option label="隐藏" value="1" />
              </el-select>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" @click="sortTableData(0)"
            >保存并确定</el-button
          >
          <el-button type="primary" @click="sortTableData(1)">确定</el-button>
        </span>
      </template>
    </el-dialog>
    <el-dialog v-model="dialogRulesVisible" title="高级筛选" width="80%">
      <el-button-group class="ml-4">
        <el-button type="primary" plain @click="rulesTableData(0)"
          >保存方案</el-button
        >
        <el-button type="primary" plain>另存方案</el-button>
        <el-button type="primary" plain>重置条件</el-button>
      </el-button-group>
      <div class="over-height">
        <div v-for="item in tablePropList" :key="item.prop">
          <!-- 允许筛选且显示状态下出现筛选列表 -->
          <template v-if="item.isSwitch && item.isShow">
            <el-form :inline="true" :model="item" class="demo-form-inline">
              <el-form-item>
                <el-select v-model="item.rules.a" placeholder="表格">
                  <el-option
                    :label="propItem.label"
                    :value="propItem.prop"
                    v-for="propItem in tablePropList"
                    :key="propItem.prop"
                  />
                </el-select>
              </el-form-item>
              <el-form-item>
                <el-select v-model="item.rules.a" placeholder="并且">
                  <el-option label="并且" value="0" />
                  <el-option label="或" value="1" />
                </el-select>
              </el-form-item>
              <el-form-item>
                <el-select v-model="item.rules.b" placeholder="等于">
                  <template v-if="item.propType == 'string'">
                    <el-option label="等于" value="0" />
                    <el-option label="包含" value="1" />
                    <el-option label="大于" value="2" />
                    <el-option label="小于" value="3" />
                  </template>
                  <template v-else-if="item.propType == 'number'">
                    <el-option label="等于" value="0" />
                    <el-option label="大于" value="2" />
                    <el-option label="小于" value="3" />
                  </template>
                  <template v-else>
                    <el-option label="等于" value="0" />
                    <el-option label="大于" value="2" />
                    <el-option label="小于" value="3" />
                  </template>
                </el-select>
              </el-form-item>
              <el-form-item>
                <el-input v-model="item.rules.value" />
              </el-form-item>
              <el-form-item>
                <el-button :icon="Close" circle @click="exportBtn" />
              </el-form-item>
            </el-form>
          </template>
        </div>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" @click="rulesTableData(1)">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script lang="ts" setup>
// npm i xlsx
// npm install element-plus --save
import { EditPen, Download, Search, Close } from "@element-plus/icons-vue";
import { ref } from "vue";
import { toRefs, defineProps, defineEmits } from "vue";
const emit = defineEmits(["tableRules"]);
// 弹窗布尔值
const dialogVisible = ref(false);
const dialogRulesVisible = ref(false);
// 接收值
const props = defineProps({
  //子组件接收父组件传递过来的值
  tableData: Array,
  tablePropsList: Object,
});
//使用父组件传递过来的值
const { tableData, tablePropsList } = toRefs(props);
// 获取当前页的配置表
const localTable = JSON.parse(localStorage.getItem("table"));
var configuration = [];
var configurationRules = [];
if (localTable) {
  configuration = localTable[tablePropsList.value.id].configuration;
  configurationRules = localTable[tablePropsList.value.id].rules;
} else {
  // 没有的话就新建一个保存
  let obj = {
    [tablePropsList.value.id]: {
      configuration: [],
      rules: [],
    },
  };
  localStorage.setItem("table", JSON.stringify(obj));
}
const tablePropList = ref([]);
// 处理数据(原始传入数据,本地存储的数据,导出的数据)
const switchTableData = (tableData, localStorageData, addData) => {
  // prop: 表格绑定字段, label:当前名, index:表格当前序列,width:表格当前宽度,align: 对齐方式 left左 center中 right右,isShow:是否显示 0显示,1隐藏
  tableData.forEach((e, index) => {
    if (localStorageData && localStorageData.length > 0) {
      localStorageData.forEach((ec) => {
        if (ec.prop == e.prop) {
          let obj = {
            prop: ec.prop,
            label: ec.label,
            index: ec.index,
            width: ec.width,
            isShow: ec.isShow,
            align: ec.align,
            fixed: ec.fixed,
            children: [],
            rules: ec.rules,
            propType: ec.propType,
            isSwitch: ec.isSwitch,
          };
          addData.push(obj);
          if (e.children) {
            switchTableData(e.children, ec.children, obj.children);
          }
        }
      });
    } else {
      let obj = {
        prop: e.prop,
        label: e.label,
        index: index,
        width: e.width ? e.width : 0,
        isShow: e.isShow ? e.isShow : "0",
        align: e.align ? e.align : "center",
        fixed: e.fixed ? e.fixed : "",
        children: [],
        rules: {},
        propType: e.propType ? e.propType : "string",
        isSwitch: e.isSwitch ? e.isSwitch : true,
      };
      addData.push(obj);
      if (e.children) {
        switchTableData(e.children, null, obj.children);
      }
    }
  });
};
switchTableData(tablePropsList.value.props, configuration, tablePropList.value);
// 给数组排序
const sortTableData = (type) => {
  // type 0 保存并确定 1 确定
  tablePropList.value.sort(function (a, b) {
    return a.index - b.index; // 根据升序排序
  });
  dialogVisible.value = false;
  if (type == 0) {
    let arr = JSON.parse(localStorage.getItem("table")) || {};
    arr[tablePropsList.value.id].configuration = tablePropList.value;
    localStorage.setItem("table", JSON.stringify(arr));
  }
};
// 数据添加筛选规则
const rulesTableData = (type) => {
  // type 0 保存并确定 1 确定
  dialogRulesVisible.value = false;
  let rulesList = [];
  tablePropList.value.forEach((e) => {
    rulesList.push(e.rules);
  });
  if (type == 0) {
    let arr = JSON.parse(localStorage.getItem("table")) || {};
    arr[tablePropsList.value.id].rules = configurationRules;
    localStorage.setItem("table", JSON.stringify(arr));
  }
  //传递给父组件
  emit("tableRules", rulesList);
};
// 导出功能
import * as XLSX from "xlsx";
const exportTableRef = ref(null);
const exportBtn = () => {
  const tableDom = exportTableRef.value?.$el;
  if (!tableDom) {
    return;
  }
  const wb = XLSX.utils.table_to_book(tableDom);
  XLSX.writeFile(wb, "表格数据.xlsx");
};
</script>
<style scoped>
.over-height {
  max-height: 60vh;
  overflow-y: auto;
}
.el-select,
.el-input {
  width: 200px;
}
.table-btn {
  float: right;
  margin-bottom: 2px;
}
.last-table {
  width: 94%;
  margin: 0 auto;
}
</style>
demo
<template>
  <div>
    <h2>直接使用</h2>
    <!-- 
      tableData 是渲染表单的原始数据,
      tablePropsList是表单的规则数据,
      tableRules 是表单返回的筛选数据,数据在tableRules(val)方法中取返回值 val
      -->
    <tableVue
      :tableData="tableData"
      :tablePropsList="tablePropsList"
      @tableRules="tableRules"
    />
    <h2>添加操作等栏目</h2>
    <tableVue :tableData="tableData" :tablePropsList="tablePropsList">
      <!-- 该组件可继续添加表格数据 -->
      <!-- 如果需要增加操作栏列等,可以按照以下格式添加 -->
      <el-table-column fixed="right" label="Operations" width="120">
        <template #default="scope">
          <el-button link type="primary" size="small">
            Remove{{ scope.$index }}
          </el-button>
        </template>
      </el-table-column>
    </tableVue>
  </div>
</template>
<script setup>
import { ref } from "vue";
import tableVue from "./components/table.vue";
// 定义的数据
const tableData = ref([
  {
    id:1,
    date: "2016-05-03",
    name: "Aom1",
    address: "No. 189, Grove St, Los Angeles",
    count: 1,
    address3: "address3",
    address4: "address4",
  },
  {
    id:2,
    date: "2016-05-02",
    name: "BTom2",
    address: "No. 189, Grove St, Los Angeles",
    count: 1,
    address3: "address3",
    address4: "address4",
  },
  {
    id:3,
    date: "2016-05-04",
    name: "CTom",
    address: "No. 189, Grove St, Los Angeles",
    count: 1,
    address3: "address3",
    address4: "address4",
  },
  {
    id:4,
    date: "2016-05-01",
    name: "DTom",
    address: "No. 189, Grove St, Los Angeles",
    count: 1,
    address3: "address3",
    address4: "address4",
  },
]);
// 需要定义关键字和关键字的label
const tablePropsList = ref({
  // id 唯一值
  id: "text-table",
  /**
   * 必填项
   * label为表格名
   * prop对应表格的参数
   * 
   * 选填
   * width 表格宽度,0自适应,可填数字,默认自适应
   * isShow 是否显示 0 显示,1不显示,默认0
   * align 对其方式 left 左对齐, center居中对其,right:右对齐,默认center
   * fixed 是否固定 left 固定左侧,right 固定右侧,空值不固定,默认空
   * propType 数据类型,默认为空
   * isSwitch 是否可筛选 默认true
   */
  props: [
    { label: "姓名", prop: "name", propType: "string", isSwitch: true, isShow: "0"},
    { label: "时间", prop: "date", propType: "string", isSwitch: true, isShow: "0" },
    { label: "地址", prop: "address", propType: "string" , isSwitch: true, isShow: "0"},
    { label: "数量", prop: "count", propType: "number", isSwitch: true, isShow: "0" },
    {
      label: "其他地址",
      prop: "address2",
      // children 为子数据
      children: [
        { label: "地址3", prop: "address3" , propType: "string", isSwitch: true, isShow: "0" },
        { label: "地址4", prop: "address4" , propType: "string", isSwitch: false , isShow: "0"},
      ],
    },
  ],
});
// 接收子组件返回的筛选规则
const tableRules = (val) => {
  console.log("rules", val);
};
</script>
<style scoped>
</style>



















