适用场景:有若干个表格,前面几列格式不一致,但是后面几列格式皆为占一个单元格,所以需要封装表格,表格元素自动根据数据结构生成即可;并且用户可新增列数据。

分类
固定数据部分 就是根据数据结构传参设置table单元格内容及格式,数据结构由前端定义;
可新增删除部分 是由用户操作,格式统一为占一格,返回数据结构以列为单位,其中,删除列以判断对应列是否有表头为依据。
在这里插入图片描述

展示效果如下:

封装原生表格演示

固定表格部分

需要确定的是固定表格中的需要给单元格元素传哪些值:

  1. 单元格的内容;
  2. 单元格格式会有很多种情况,可能会占几行,也可能会占几列,所以就需要控制着每个单元格的colspan和rowspan;
  3. 单元格的内容长短不一致所以也需要控制着单元格的宽度;

其它参数可有可无,需要依照自身需求添加逻辑。

数据格式

数据格式有四大字段,分别控制着出表头以外的行数(tdRows),为了便于控制除表头的tr循环次数;表格名称(title);表头内容(ths)和表身内容(tds)。

SafetyTableData:
      {
        tdRows: 3,//除表头的tbody所占行数
        title: '表格名称表格名称表格名称表格名称表格名称',
        ths: [
          {
            thName: 'Safety/安全',
            colspan: 1,
            rowspan: 4,
            isEdit: false,
            width: 100,
          },
          {
            thName: '关键指标KPI',
            colspan: 2,
            rowspan: 1,
            isEdit: false,
            width: 100,
          },
          {
            thName: '输出部门',
            colspan: 1,
            rowspan: 1,
            isEdit: false,
            width: 100,
          },
          {
            thName: '公式',
            colspan: 1,
            rowspan: 1,
            isEdit: false,
            width: 180,
          },
          {
            thName: '单位',
            colspan: 1,
            rowspan: 1,
            isEdit: false,
            width: 100,
          },
        ],
        tds: [
          [
            { tdName: '伤害', colspan: 1, rowspan: 3, isEdit: false, width: 30, },
            { tdName: '占比%', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '游戏', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '(损失工时/ 投入工时)*100%', colspan: 1, rowspan: 1, isEdit: false, width: 180, },
            { tdName: '%', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
          ],
          [
            { tdName: '一些值', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '游戏', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: 'ABCDEFG', colspan: 1, rowspan: 1, isEdit: false, width: 180, },
            { tdName: 'absolute', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
          ],
          [
            { tdName: '实际占比%', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '游戏', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '(实际损失工时/实际投入工时)*100%', colspan: 1, rowspan: 1, isEdit: false, width: 180, },
            { tdName: '%', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
          ],
          // Repeat similar structure for other rows as needed
        ],
      },

元素设置

detailTableData为表格固定格式需要显示的数据,前面已设置了SafetyTableData的数据格式,可以直接将SafetyTableData赋值给detailTableData。若还有其他固定表格格式同理视情况赋值即可。

<table border="1">
        <tr ref="tableHeader">
          <th v-for="(thItem, index) in detailTableData.ths" :key="'th-' + index" :rowspan="thItem.rowspan" :colspan="thItem.colspan" :style="'width:'+thItem.width+'px'">
            {{thItem.thName}}
          </th>
        </tr>
        <tr v-for="rowIndex in detailTableData.tdRows" :key="rowIndex">
          <td v-for="(tdItem, colIndex) in detailTableData.tds[rowIndex-1]" :key="'td-' + rowIndex + '-' + colIndex" :rowspan="tdItem.rowspan" :colspan="tdItem.colspan" :style="'width:'+tdItem.width+'px'">
            {{tdItem.tdName}}
          </td>
        </tr>
</table>

可新增删除部分

既然可新增删除部分都是占一个单元格,那么可以确定的是colspan和rowspan都为1。

数据格式

全局参数(newTableData)用于存放所有用户新增的数据;newThObj为新增表头时的数据;newTdObj为新增表身时的数据。

newTableData: {
        ths: [],
        tds: [], // 初始包含一个空数组
},
newThObj: {
        thName: '',
        colspan: 1,
        rowspan: 1,
        isEdit: true,
},
newTdObj: {
        tdName: '',
        colspan: 1,
        rowspan: 1,
        isEdit: true,
},

元素设置

<table border="1">
        <tr ref="tableHeader">
          <th v-for="(newThItem,newindex) in newTableData.ths" :key="newindex" class="item" :id="'test'+newindex">
            <el-input v-if="newThItem.isEdit && editTableSate" v-model="newThItem.thName" placeholder="请输入时间"></el-input>
            <span v-else>{{newThItem.thName}}</span>
          </th>

        </tr>
        <tr v-for="rowIndex in detailTableData.tdRows" :key="rowIndex">
          <td v-for="(newTdItem, newColIndex) in newTableData.tds[rowIndex-1]" :key="newColIndex">
            <el-input v-if="newTdItem.isEdit && editTableSate" v-model="newTdItem.tdName" placeholder="请输入"></el-input>
            <span v-else>{{newTdItem.tdName}}</span>
          </td>
        </tr>
</table>

新增的方法

// 新增列的点击事件
    addTableColBtn() {
      // 向 newTableData.ths 添加一个新的表头对象
      this.newTableData.ths.push({ ...this.newThObj });

      // 如果 tds 为空,需要初始化它
      if (this.newTableData.tds.length === 0) {
        for (let i = 0; i < this.detailTableData.tdRows; i++) {
          this.newTableData.tds.push([]);
        }
      }

      // 遍历每一行,添加空单元格以匹配表头列数
      this.newTableData.tds.forEach(row => {
        row.push({ ...this.newTdObj });
      });
    },

删除的方法

// 反向遍历以避免删除元素时影响索引 -- 以表头为准,若表头为空,则提交后对应列为空
      for (let i = this.newTableData.ths.length - 1; i >= 0; i--) {
        if (!this.newTableData.ths[i].thName) {
          let shouldDeleteColumn = true;
          for (let row = 0; row < this.newTableData.tdRows; row++) {
            if (this.newTableData.tds[row][i].tdName) {
              shouldDeleteColumn = false;
              break;
            }
          }
          // 如果该列满足删除条件,则删除
          if (shouldDeleteColumn) {
            this.newTableData.ths.splice(i, 1);
            this.newTableData.tds.forEach(row => {
              row.splice(i, 1);
            });
          }
        }
      }

数据转换

在这里插入图片描述

将格式转化为以列

将newTableData{ths: [], tds: [],}转化为data[{SORT:‘’,TIME:‘’,TLLRGOAL:‘’,AF:‘’,ACTUALTLLR:‘’,}]格式

/**
     * 用于表格封装方法
     * @param {*object} newTableData:{ths:[],tds:[],} 
     * @param {*string} type
     * @returns {*array} array:[{SORT:'',TIME:'',Cells1:'',Cells2:'',Cells3:'',}]
     */
    transformTableData(newTableData, type) {
      const transformedData = [];
      newTableData.ths.forEach((th, index) => {
        const thName = th.thName;
        const colIndex = index;
        var transformedObj = {};
        switch (type) {
          case 'Safety'://安全
            transformedObj = {
              SORT: (colIndex + 1).toString(),//列数
              TIME: thName,//表头内容
              Cells1: newTableData.tds[0][colIndex].tdName,//单元格内容1
              Cells2: newTableData.tds[1][colIndex].tdName,//单元格内容2
              Cells3: newTableData.tds[2][colIndex].tdName//单元格内容3
            };
            break;
          default:
            break;
        }
        transformedData.push(transformedObj);
      });
      return transformedData;
    }

将格式转化为以表格

将data[{SORT:‘’,TIME:‘’,TLLRGOAL:‘’,AF:‘’,ACTUALTLLR:‘’,}]格式转化为newTableData{ths: [], tds: [],}

/**
     * 用于将获取数据返回至符合表格的封装方法
     * @param {*array} data:[{SORT:'',TIME:'',Cells1:'',Cells2:'',Cells3:'',}]
     * @returns {*object} newTableData:{ths:[],tds:[],} 
     */
    transformData(data) {
      // 初始化 newTableData 结构
      let newTableData = {
        ths: [],
        tds: []
      };
      // 提取所有的列名,除了 "SORT" 和 "TIME"、"ID",因为这三个是固定的
      const columns = Object.keys(data[0]).filter(key => key !== "SORT" && key !== "TIME" && key !== "ID");
      // 填充 ths 数组
      newTableData.ths = data.map(item => ({
        thName: item.TIME,
        colspan: 1,
        rowspan: 1,
        isEdit: true
      }));
      // 填充 tds 数组
      for (let column of columns) {
        let columnData = data.map(item =>
        (
          {
            tdName: item[column],
            colspan: 1,
            rowspan: 1,
            isEdit: true
          }
        )
        );
        // 将每个字段的值按顺序插入 tds 数组
        newTableData.tds.push([...columnData]);
      }
      return newTableData;
    }

全部代码

<template>
  <div class="testbox">
    <div class="bartype-class">
      <div class="verticalbar-class"></div>
      <h4>Details</h4>
      <el-button v-if="!editTableSate" @click="editTableBtn" size="mini" icon="el-icon-edit" style="margin-left:2%;">编 辑</el-button>
      <div v-else style="margin-left: auto;display:flex;">
        <el-button @click="addTableColBtn" size="mini" icon="el-icon-plus">新增列</el-button>
        <el-button @click="saveTableBtn" size="mini" icon="el-icon-upload">提 交</el-button>
      </div>
    </div>
    <div class="tabletitle-class" v-if="detailTableData.title">{{detailTableData.title}}</div>
    <div class="bar-class mytable" style="overflow: auto;">
      <table border="1">
        <tr ref="tableHeader">
          <th v-for="(thItem, index) in detailTableData.ths" :key="'th-' + index" :rowspan="thItem.rowspan" :colspan="thItem.colspan" :style="'width:'+thItem.width+'px'">
            {{thItem.thName}}
          </th>
          <th v-for="(newThItem,newindex) in newTableData.ths" :key="newindex" class="item" :id="'test'+newindex">
            <el-input v-if="newThItem.isEdit && editTableSate" v-model="newThItem.thName" placeholder="请输入时间"></el-input>
            <span v-else>{{newThItem.thName}}</span>
          </th>

        </tr>
        <tr v-for="rowIndex in detailTableData.tdRows" :key="rowIndex">
          <td v-for="(tdItem, colIndex) in detailTableData.tds[rowIndex-1]" :key="'td-' + rowIndex + '-' + colIndex" :rowspan="tdItem.rowspan" :colspan="tdItem.colspan" :style="'width:'+tdItem.width+'px'">
            {{tdItem.tdName}}
          </td>
          <td v-for="(newTdItem, newColIndex) in newTableData.tds[rowIndex-1]" :key="newColIndex">
            <el-input v-if="newTdItem.isEdit && editTableSate" v-model="newTdItem.tdName" placeholder="请输入"></el-input>
            <span v-else>{{newTdItem.tdName}}</span>
          </td>
        </tr>
      </table>
    </div>

  </div>
</template>

<script>

export default {
  components: {},
  data() {
    return {
      detailTableData: {},//表格显示的数据
      // 安全表格固定数据;ID: 6
      SafetyTableData:
      {
        tdRows: 3,//除表头的tbody所占行数
        title: '表格名称表格名称表格名称表格名称表格名称',
        ths: [
          {
            thName: 'Safety/安全',
            colspan: 1,
            rowspan: 4,
            isEdit: false,
            width: 100,
          },
          {
            thName: '关键指标KPI',
            colspan: 2,
            rowspan: 1,
            isEdit: false,
            width: 100,
          },
          {
            thName: '输出部门',
            colspan: 1,
            rowspan: 1,
            isEdit: false,
            width: 100,
          },
          {
            thName: '公式',
            colspan: 1,
            rowspan: 1,
            isEdit: false,
            width: 180,
          },
          {
            thName: '单位',
            colspan: 1,
            rowspan: 1,
            isEdit: false,
            width: 100,
          },
        ],
        tds: [
          [
            { tdName: '伤害', colspan: 1, rowspan: 3, isEdit: false, width: 30, },
            { tdName: '占比%', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '游戏', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '(损失工时/ 投入工时)*100%', colspan: 1, rowspan: 1, isEdit: false, width: 180, },
            { tdName: '%', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
          ],
          [
            { tdName: '一些值', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '游戏', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: 'ABCDEFG', colspan: 1, rowspan: 1, isEdit: false, width: 180, },
            { tdName: 'absolute', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
          ],
          [
            { tdName: '实际占比%', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '游戏', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
            { tdName: '(实际损失工时/实际投入工时)*100%', colspan: 1, rowspan: 1, isEdit: false, width: 180, },
            { tdName: '%', colspan: 1, rowspan: 1, isEdit: false, width: 100, },
          ],
          // Repeat similar structure for other rows as needed
        ],
      },
      newTableData: {
        ths: [],
        tds: [], // 初始包含一个空数组
      },
      newThObj: {
        thName: '',
        colspan: 1,
        rowspan: 1,
        isEdit: true,
      },
      newTdObj: {
        tdName: '',
        colspan: 1,
        rowspan: 1,
        isEdit: true,
      },
      editTableSate: false,//表格编辑状态
    };
  },
  created() {

  },
  computed: {

  },
  mounted() {
    this.detailTableData = this.SafetyTableData;
  },
  methods: {
    // 编辑表格按钮
    editTableBtn() {
      this.editTableSate = true;
    },
    // 新增列的点击事件
    addTableColBtn() {
      // 向 newTableData.ths 添加一个新的表头对象
      this.newTableData.ths.push({ ...this.newThObj });

      // 如果 tds 为空,需要初始化它
      if (this.newTableData.tds.length === 0) {
        for (let i = 0; i < this.detailTableData.tdRows; i++) {
          this.newTableData.tds.push([]);
        }
      }

      // 遍历每一行,添加空单元格以匹配表头列数
      this.newTableData.tds.forEach(row => {
        row.push({ ...this.newTdObj });
      });
    },
    // 保存表格的点击事件
    saveTableBtn() {
      // 反向遍历以避免删除元素时影响索引 -- 以表头为准,若表头为空,则提交后对应列为空
      for (let i = this.newTableData.ths.length - 1; i >= 0; i--) {
        if (!this.newTableData.ths[i].thName) {
          let shouldDeleteColumn = true;
          for (let row = 0; row < this.newTableData.tdRows; row++) {
            if (this.newTableData.tds[row][i].tdName) {
              shouldDeleteColumn = false;
              break;
            }
          }
          // 如果该列满足删除条件,则删除
          if (shouldDeleteColumn) {
            this.newTableData.ths.splice(i, 1);
            this.newTableData.tds.forEach(row => {
              row.splice(i, 1);
            });
          }
        }
      }
      this.saveTableDataFun();
    },
    // 保存表格数据函数
    async saveTableDataFun() {
      var data = [];
      if (this.newTableData.ths.length) {
        data = this.transformTableData(this.newTableData, 'Safety');
      }
      console.log('data:', data);
      this.editTableSate = false;
    },
    /**
     * 用于表格封装方法
     * @param {*object} newTableData:{ths:[],tds:[],} 
     * @param {*string} type
     * @returns {*array} array:[{SORT:'',TIME:'',Cells1:'',Cells2:'',Cells3:'',}]
     */
    transformTableData(newTableData, type) {
      const transformedData = [];
      newTableData.ths.forEach((th, index) => {
        const thName = th.thName;
        const colIndex = index;
        var transformedObj = {};
        switch (type) {
          case 'Safety'://安全
            transformedObj = {
              SORT: (colIndex + 1).toString(),//列数
              TIME: thName,//表头内容
              Cells1: newTableData.tds[0][colIndex].tdName,//单元格内容1
              Cells2: newTableData.tds[1][colIndex].tdName,//单元格内容2
              Cells3: newTableData.tds[2][colIndex].tdName//单元格内容3
            };
            break;
          default:
            break;
        }
        transformedData.push(transformedObj);
      });
      return transformedData;
    },
    /**
     * 用于将获取数据返回至符合表格的封装方法
     * @param {*array} data:[{SORT:'',TIME:'',Cells1:'',Cells2:'',Cells3:'',}]
     * @returns {*object} newTableData:{ths:[],tds:[],} 
     */
    transformData(data) {
      // 初始化 newTableData 结构
      let newTableData = {
        ths: [],
        tds: []
      };
      // 提取所有的列名,除了 "SORT" 和 "TIME"、"ID",因为这三个是固定的
      const columns = Object.keys(data[0]).filter(key => key !== "SORT" && key !== "TIME" && key !== "ID");
      // 填充 ths 数组
      newTableData.ths = data.map(item => ({
        thName: item.TIME,
        colspan: 1,
        rowspan: 1,
        isEdit: true
      }));
      // 填充 tds 数组
      for (let column of columns) {
        let columnData = data.map(item =>
        (
          {
            tdName: item[column],
            colspan: 1,
            rowspan: 1,
            isEdit: true
          }
        )
        );
        // 将每个字段的值按顺序插入 tds 数组
        newTableData.tds.push([...columnData]);
      }
      return newTableData;
    }
  },
  watch: {

  }
};
</script>

<style scoped>
.testbox {
  width: 100%;
  height: 100%;
}
.bartype-class {
  display: flex;
  align-items: center;
  height: 25px;
  margin: 10px 0;
}
.verticalbar-class {
  width: 4px;
  height: 100%;
  background: #555555;
  margin-right: 9px;
}

.mytable table {
  border-collapse: collapse;
  width: 100%;
  font-size: 12px;
}
.mytable table td:first-child,
th:first-child {
  /* font-weight: bold; */
  /* background-color: pink; */
  width: 15%;
}
.mytable table th,
td {
  border: 1px solid #ddd;
  text-align: center;
  padding: 8px;
}

.mytable table th {
  background-color: #f2f2f2;
}
.text-left {
  text-align: left;
}
.mytable .el-input {
  width: 80px;
}
.mytable .el-input__inner {
  padding: 0 5px;
}
.item {
  cursor: pointer;
}
.tabletitle-class {
  background: #0070c0;
  color: #fff;
  margin: 0.5% 0;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 30px;
  font-weight: bold;
  letter-spacing: 2px;
}
</style> 


点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部