vue基于原生table鼠标拖拽选中单元格合并拆分原理,实现柜子结构设计器

1 篇文章 0 订阅
1 篇文章 0 订阅

一、使用场景

在业务中经常会遇到一些库房储物柜需求,这种需求中,我们可能会维护多个柜子,如果我们遇到的柜子是正常的3x3 、4x4这种,那么需求是简单的,但是在现实生活中,我们可能遇到的柜子是存在着部分格子占据了好几个小格子的位置,这种情况下正常的table将不是很容易满足需求。

二、效果展示

设计器展示

1.通过行列快速设计格子的形状请添加图片描述
2.根据现实需求对单元格进项合并
请添加图片描述
3.合并之后,柜子效果
请添加图片描述
4.对合并的单元格进进行拆分,还原柜格子原本的样子请添加图片描述
请添加图片描述

柜子渲染展示

本组件将提供渲染方法,页面可根据自身需求,可以自行设计成任意的风格 本例子只作为展示
在这里插入图片描述

三、设计原理

前端在设计完成格子的基本结构后,组件会生成一个结构的json结构将这个json以字符串的形式传输给后台数据库,用来作为柜子的基本结构,每个柜子的格子都有自己的唯一id,用来绑定每个格子的数据,真是的格子里的物品数据,将会通过组件方法自动绑定到各个格子中

四、设计器代码

CabinetLatticeSet.vue 设计器的主要代码

<template>
  <div class="cabinet_latte_contain">
    <div class="cabinet_latte_title">柜子设计器</div>

    <table
      border="1"
      id="cabinet_latte_table"
      align="center"
      width="100%"
      cellpadding="20"
      cellspacing="0"
      :key="renderTable"
    >
      <tr v-for="(row, index) in tableData" :key="index" :id="row.id">
        <td
          @mousedown="handleMouseDown"
          @mouseup="handleMouseUp"
          v-for="(col, j) in row.col"
          :key="j"
          :id="col.id"
          :rowspan="col.rowspan"
          :colspan="col.colspan"
          :initRow="col.initRow"
          :initCol="col.initCol"
          :col="JSON.stringify(col)"
          v-show="!col.isMerge"
          @click="cellClick(col)"
          @contextmenu="openContextMenu($event)"
        >
          <div class="cell_info">
            <slot name="cellCard" :cell="col.cell" :tableData="tableData"></slot>
          </div>
        </td>
      </tr>
    </table>
    <ContextMenuTool
      ref="contextMenuTool"
      @mergeCell="mergeCell"
      @splitCell="splitCell"
      @delRow="delRow"
      @delCol="delCol"
    ></ContextMenuTool>
  </div>
</template>
<script>
import guid from '@/utils/generator';
import ContextMenuTool from './ContextMenuTool.vue';

const STATUS = {
  CREATE: 0,
  UPDATE: 1,
  DETAIL: 2,
};
export default {
  components: {
    ContextMenuTool,
  },
  props: {
    setConfig: {
      type: Object,
      default: () => {
        return {
          row: 0,
          col: 0,
        };
      },
    },
    dialogStatus: {
      type: Number,
      default: 0,
    },
    cellData: {
      type: Array,
      default: () => [],
    },
    cellConfigList: {
      type: Array,
      default: () => [],
    },
    entity: {
      type: Object,
      default: () => {
        return {};
      },
    },
  },
  data() {
    return {
      dragging: false,
      clickCellID: '',
      startCell: null,
      columns: [], // 您的列配置
      chooseCellsArea: [],
      chooseCellIds: [],
      cellConfig: [],
      renderTable: 0,
      tableData: [],
      endCell: {},
    };
  },
  mounted() {
    this.setCellConfigList();
  },
  methods: {
    setCellConfigList() {
      console.log('this.cellConfigList', this.cellConfigList);
      if (this.cellConfigList.length > 0) {
        this.tableData = this.cellConfigList;
        this.renderTable += 1;
        this.setCellDataList();
      }
    },
    getCellConfigList() {
      const localTableData = JSON.parse(JSON.stringify(this.tableData));
      localTableData.forEach(row => {
        row.col.forEach(col => {
          delete col.cell; // 删除cell对象
        });
      });
      return JSON.stringify(localTableData);
    },
    setCellDataList() {
      if (this.cellData.length > 0) {
        this.cellData.forEach(cell => {
          // 遍历 tableData 数组
          this.tableData.forEach(row => {
            // 在当前 row 的 col 数组中查找匹配的 cell
            const colItem = row.col.find(col => col.id === cell.id);
            if (colItem) {
              // 替换 cell 对象
              colItem.cell = cell;
            }
          });
        });

        this.renderTable += 1;
      }
      console.log('this.tableData12313', this.tableData);
    },
    updateCellData(cell) {
      this.tableData.forEach(row => {
        // 在当前 row 的 col 数组中查找匹配的 cell
        const colItem = row.col.find(col => col.id === cell.id);
        if (colItem) {
          // 替换 cell 对象
          colItem.cell = {
            ...colItem.cell,
            ...cell,
          };
        }
      });
      this.renderTable += 1;
    },
    getCellDataList() {
      const cells = this.tableData.reduce((acc, currentItem) => {
        // 对每个元素的 col 数组进行遍历,提取 cell 对象
        const cellsOfCurrentItem = currentItem.col.map(colItem => colItem.cell);
        // 将当前项的 cells 添加到累积器数组中
        return acc.concat(cellsOfCurrentItem);
      }, []); // 初始累积器是一个空数组
      console.log('cells', cells);
      return cells;
    },
    openContextMenu(event) {
      if (this.dialogStatus !== STATUS.CREATE) {
        // 不是新增就不可以打开右击菜单
        return;
      }
      this.$nextTick(() => {
        event.preventDefault();
        const rowspan = parseInt(event.currentTarget.getAttribute('rowspan'), 10);
        const colspan = parseInt(event.currentTarget.getAttribute('colspan'), 10);
        let flag = false;
        flag = (rowspan > 1 || colspan > 1) && this.chooseCellsArea.length === 1;
        if (flag) {
          // 拆分
          this.cellClick(event.currentTarget);
        }
        if (this.chooseCellsArea.length < 2 && !flag) {
          this.cellClick(event.currentTarget);
        }
        this.$refs.contextMenuTool.openContextMenu(event, flag, this.chooseCellsArea.length < 2 && !flag);
      });
    },

    mergeCell() {
      // 计算起始单元格位置
      console.log('行,列', this.getCellRowCol(this.chooseCellsArea[0]));
      const chooseIds = this.chooseCellsArea.map(item => {
        return item.id;
      });
      //   console.log(' chooseIds', chooseIds);
      // 计算合并单元格的大小
      const mergeDimensions = this.calculateMergeDimensions(this.chooseCellsArea);
      console.log(`合并后的单元格将占据 ${mergeDimensions.numRows} 行 和 ${mergeDimensions.numCols} 列。`);
      this.mergeCellMethod(chooseIds, this.startCell.cellId, mergeDimensions.numRows, mergeDimensions.numCols);
      //   this.mergeCellMethod();

      this.chooseCellsArea = [];
      console.log('this.tableData', this.tableData);
      this.renderTable += 1;
    },
    splitCell() {
      // 拆分单元格
      //   console.log(this.chooseCellsArea[0]);
      const cellId = this.clickCellID;

      let idsToUpdate = [cellId];
      this.tableData.forEach(row => {
        row.col.forEach(col => {
          if (col.id === cellId) {
            idsToUpdate = idsToUpdate.concat(col.mergeids);
          }
        });
      });

      // 更新tableData数组中的对象,如果它们的ID在idsToUpdate中
      this.tableData.forEach(row => {
        row.col.forEach(col => {
          if (idsToUpdate.includes(col.id)) {
            col.mergeids = [];
            col.rowspan = 1;
            col.colspan = 1;
            col.isMerge = false;
          }
        });
      });
      this.chooseCellsArea = [];
      console.log('this.tableData', this.tableData);
      this.renderTable += 1;
    },
    delRow() {
      const cellId = this.clickCellID;
      // 查找包含该col id的对象的索引
      const parentIndex = this.tableData.findIndex(item => item.col.some(col => col.id === cellId));
      if (parentIndex !== -1) {
        this.tableData.splice(parentIndex, 1);
      }
      this.chooseCellsArea = [];
      this.renderTable += 1;
    },
    delCol() {
      const cellId = this.clickCellID;
      let targetInitCol;

      // 第一步:找到对应ID的对象并记录其initCol
      for (const item of this.tableData) {
        const found = item.col.find(col => col.id === cellId);
        if (found) {
          targetInitCol = found.initCol;
          break;
        }
      }

      // 第二步:删除所有initCol等于targetInitCol的对象
      if (targetInitCol !== undefined) {
        for (const item of this.tableData) {
          item.col = item.col.filter(col => col.initCol !== targetInitCol);
        }
      }
      this.chooseCellsArea = [];
      this.renderTable += 1;
    },
    // 合并单元格
    // mergeCellMethod() {
    //   const chooseIds = this.chooseCellsArea.map(item => {
    //     return item.id;
    //   });

    //   this.chooseCellsArea.forEach(cell => {
    //     const colObj = JSON.parse(cell.getAttribute('col'));
    //     if (colObj.mergeids.length > 0) {
    //       chooseIds.push(...colObj.mergeids);
    //     }
    //   });
    //   const idsToMergeExceptFirst = chooseIds.slice(1);
    //   this.tableData.forEach(item => {
    //     item.col.forEach(colItem => {
    //       if (idsToMergeExceptFirst.includes(colItem.id)) {
    //         colItem.isMerge = true;
    //       }
    //       if (chooseIds[0] === colItem.id) {
    //         colItem.mergeids = [...colItem.mergeids, ...idsToMergeExceptFirst];
    //       }
    //     });
    //   });
    // },
    // // 合并单元格
    mergeCellMethod(chooseIds, id, rowspan, colspan) {
      // 获取被合并的id
      const idsToMergeExceptFirst = chooseIds.slice(1);
      console.log('被合并的id', idsToMergeExceptFirst);
      let endRowspan = 1;
      let endColspan = 1;
      this.tableData.forEach(row => {
        row.col.forEach((col, index) => {
          if (col.id === this.endCell.cellId) {
            endRowspan = col.rowspan;
            endColspan = col.colspan;
          }
        });
      });
      this.tableData.forEach(row => {
        row.col.forEach((col, index) => {
          if (col.id === id) {
            col.rowspan = rowspan;
            // col.colspan = col.colspan > 1 && colspan < col.colspan ? col.colspan : colspan;
            col.colspan = colspan;
          }
        });
      });
      this.tableData.forEach(item => {
        item.col.forEach(colItem => {
          if (idsToMergeExceptFirst.includes(colItem.id)) {
            colItem.isMerge = true;
          }
          if (chooseIds[0] === colItem.id) {
            colItem.mergeids = [...colItem.mergeids, ...idsToMergeExceptFirst];
          }
        });
      });
    },

    // 获取单元格位置信息
    getCellRowCol(cell) {
      const row = cell.parentNode.rowIndex;
      const column = cell.cellIndex;
      return {
        row,
        col: column,
      };
    },
    // 计算合并单元格的大小
    calculateMergeDimensions(chooseCellsArea) {
      let minRowIndex = Infinity;
      let maxRowIndex = -Infinity;
      let minCellIndex = Infinity;
      let maxCellIndex = -Infinity;

      chooseCellsArea.forEach(cell => {
        const rowIndex = parseInt(cell.getAttribute('initrow'), 10);
        const cellIndex = parseInt(cell.getAttribute('initcol'), 10);
        // const { rowIndex } = cell.parentNode; // 获取当前单元格的行索引
        // const { cellIndex } = cell; // 获取当前单元格的列索引

        // 更新行索引的最小和最大值
        minRowIndex = Math.min(minRowIndex, rowIndex);
        maxRowIndex = Math.max(maxRowIndex, rowIndex);

        // 更新列索引的最小和最大值
        minCellIndex = Math.min(minCellIndex, cellIndex);
        maxCellIndex = Math.max(maxCellIndex, cellIndex);
      });

      // 计算合并后单元格应占据的行数和列数
      const numRows = maxRowIndex - minRowIndex + 1;
      const numCols = maxCellIndex - minCellIndex + 1;

      return { numRows, numCols };
    },
    handleMouseDown(event) {
      this.$nextTick(() => {
        if (event.button === 0) {
          this.dragging = true;

          // 获取起始单元格信息,这里可能需要一些逻辑来确定单元格的行和列
          const target = event.currentTarget;
          console.log('摁下target', target);
          if (target) {
            const row = Number(target.attributes.initrow.value);
            const col = Number(target.attributes.initcol.value);
            const cellId = target.id;
            this.startCell = { cellId, row, col };

            //   console.log('初始单元格', this.startCell);
          }

          // 添加全局鼠标移动事件监听
          window.addEventListener('mousemove', this.handleMouseMove);
        }
      });
    },
    handleMouseUp(event) {
      this.dragging = false;

      // 移除全局鼠标移动事件监听
      window.removeEventListener('mousemove', this.handleMouseMove);
    },

    handleMouseMove(event) {
      //   if (!this.dragging) {
      //     return;
      //   }
      //   console.log('的高亮显示');
      // 这里可以根据需要添加逻辑,例如更新当前单元格的高亮显示
      this.$nextTick(() => {});
      if (event.button === 0) {
        const { target } = event;
        // console.log('target.parentNode', event);
        if (target) {
          const row = Number(target.attributes.initrow.value);
          const col = Number(target.attributes.initcol.value);

          // 根据 startCell 和当前单元格计算覆盖的单元格
          console.log(`Selected cells from (${this.startCell.row}, ${this.startCell.col}) to (${row}, ${col})`);
          // const cell = this.getTableCellByIdx('cabinet_latte_table', row, column);
          this.endCell = {
            cellId: target.id,
            row,
            col,
          };

          //   this.applyBorderStyles('cabinet_latte_table', this.startCell.row, this.startCell.col, row, col, target.id);
          this.getChooseCellRange('cabinet_latte_table', this.startCell.row, this.startCell.col, row, col, target.id);
        }
      }
    },
    // eslint-disable-next-line consistent-return
    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
      //  :span-method="objectSpanMethod"
      if (this.cellConfig.length > 0) {
        if (columnIndex === 2) {
          if (rowIndex === 2) {
            return [5, 2];
          }
        }
      }
    },
    cellClick(col) {
      this.clickCellID = col.id;
      const cell = document.getElementById(col.id);
      console.log(cell);
      this.chooseCellsArea = [cell];
      this.clearBorderStyles('cabinet_latte_table');
      cell.style.border = '4px solid #409eff';
    },

    clearBorderStyles(tableId) {
      const rowList = document.getElementById(tableId).querySelectorAll('tr');
      if (!rowList) return;

      // 遍历所有行和单元格,清除边框样式
      for (const row of rowList) {
        for (const cell of row.cells) {
          cell.style.borderTop = '';
          cell.style.borderBottom = '';
          cell.style.borderLeft = '';
          cell.style.borderRight = '';
          cell.style.backgroundColor = 'transparent';
        }
      }
    },
    getChooseCellRange(tableId, startRow, startCol, endRow, endCol, endId) {
      this.clearBorderStyles(tableId);
      const cellList = document.getElementById(tableId).querySelectorAll('td');
      const endRowNum = endRow;
      const endColNum = endCol;
      const cellsInRange = [];
      const cellsIdsRange = [];
      cellList.forEach(cell => {
        const initRow = parseInt(cell.getAttribute('initrow'), 10);
        const initCol = parseInt(cell.getAttribute('initcol'), 10);

        // if (cell.id === endId && (cell.rowSpan !== 1 || cell.rowCol !== 1)) {
        //   endRowNum = cell.rowSpan === 1 ? endRow : cell.rowSpan;
        //   endColNum = cell.colSpan === 1 ? endCol : cell.colSpan;
        // }
        // 判断单元格是否在指定范围内
        if (initRow >= startRow && initRow <= endRowNum && initCol >= startCol && initCol <= endColNum) {
          cellsInRange.push(cell);
          const endCell = this.getComputedStyleRanges(cellsInRange, tableId, startRow, startCol, endRow, endCol);
          //   this.chooseCellsArea = cellsInRange;
          //   cell.style.backgroundColor = '#c0bfbf';
          this.applyBorderStyles(tableId, startRow, startCol, endCell.endRow, endCell.endCol);
        }
      });
    },
    // 获取范围并且将改变边框颜色
    applyBorderStyles(tableId, startRow, startCol, endRow, endCol) {
      this.clearBorderStyles(tableId);
      const cellList = document.getElementById(tableId).querySelectorAll('td');
      //   let endRowNum = endRow;
      //   let endColNum = endCol;
      const cellsInRange = [];
      const cellsIdsRange = [];
      cellList.forEach(cell => {
        const initRow = parseInt(cell.getAttribute('initrow'), 10);
        const initCol = parseInt(cell.getAttribute('initcol'), 10);
        // 判断单元格是否在指定范围内
        if (initRow >= startRow && initRow <= endRow && initCol >= startCol && initCol <= endCol) {
          cellsInRange.push(cell);

          this.chooseCellsArea = cellsInRange;

          cell.style.backgroundColor = '#c0bfbf';
          // 应用顶部边框样式
          if (initRow === startRow) {
            cell.style.borderTop = '4px solid #409eff';
          }

          // 应用底部边框样式
          if (initRow === endRow) {
            cell.style.borderBottom = '4px solid #409eff';
          }

          // 应用左侧边框样式
          if (initCol === startCol) {
            cell.style.borderLeft = '4px solid #409eff';
          }

          // 应用右侧边框样式
          if (initCol === endCol) {
            cell.style.borderRight = '4px solid #409eff';
          }
        }
      });
      console.log('选中的单元格', this.chooseCellsArea);
    },
    findElementByInitPosition(row, col) {
      // 遍历tableData数组
      for (const rowData of this.tableData) {
        // 使用filter方法查找col数组中符合条件的元素
        const matchedElements = rowData.col.filter(element => element.initRow === row && element.initCol === col);

        // 如果找到匹配的元素,返回第一个匹配的元素
        if (matchedElements.length > 0) {
          return matchedElements[0]; // 返回找到的第一个匹配元素
        }
      }
      // 如果没有找到匹配的元素,返回null
      return null;
    },
    // 获取包含选中单元格的最大范围
    getComputedStyleRanges(cells, tableId, startRow, startCol, endRow, endCol) {
      let maxCol = 0;
      let maxRow = 0;
      cells.forEach(cell => {
        const colObj = JSON.parse(cell.getAttribute('col'));

        const colnum = colObj.initCol + (colObj.colspan - 1);
        const rownum = colObj.initRow + (colObj.rowspan - 1);
        if (colnum > maxCol) {
          maxCol = colnum;
        }
        if (rownum > maxRow) {
          maxRow = rownum;
        }
      });

      return {
        endCol: maxCol,
        endRow: maxRow,
      };
    },
    generate2DArray(setConfig) {
      // 创建一个空数组用于存储结果
      const result = [];

      // 遍历行数
      for (let i = 0; i < setConfig.row; i += 1) {
        // 为每一行创建一个对象,使用guid()函数生成id
        const rowObject = {
          id: guid(), // 假设guid()是你提到的用于生成唯一id的函数
          col: [],
        };

        // 遍历列数
        for (let j = 0; j < setConfig.col; j += 1) {
          // 为每一列创建一个对象,同样使用guid()函数生成id
          const cellId = guid();
          rowObject.col.push({
            id: cellId,
            rowspan: 1,
            colspan: 1,
            initRow: i,
            initCol: j,
            isMerge: false,
            mergeids: [],
            cell: {
              id: cellId,
              no: this.entity.containerName
                ? `${this.entity.containerName} ${i + 1} - ${j + 1}`
                : `${i + 1} - ${j + 1}`,
              storageLimit: this.entity.defaultStorageLimit,
              rowIndex: i + 1,
              columnIndex: j + 1,
            },
          });
        }

        // 将这一行的对象添加到结果数组中
        result.push(rowObject);
      }

      // 返回生成的二维数组
      return result;
    },
    // 设置柜子名称
    setContainerName(containerName) {
      this.tableData.forEach(row => {
        row.col.forEach(col => {
          col.cell.no = `${containerName + col.cell.rowIndex}-${col.cell.columnIndex}`; // 为每个cell对象添加containerName属性
        });
      });

      this.renderTable += 1;
    },
    SetCellProperty(propName, propValue) {
      this.tableData.forEach(row => {
        row.col.forEach(col => {
          col.cell[propName] = propValue; // 为每个cell对象添加containerName属性
        });
      });

      this.renderTable += 1;
    },
  },
  watch: {
    setConfig: {
      handler(val) {
        if (this.cellConfigList.length < 1) {
          if (val.row && val.col) {
            if (this.tableData.length > val.row) {
              this.tableData = this.tableData.splice(0, val.row);
            } else {
              this.tableData = this.generate2DArray(val);
            }
          }
        }
        console.log(' this.tableDatathis.cellConfigList', this.cellConfigList);
      },
      deep: true,
      immediate: true,
    },
    'entity.containerName': {
      handler(val) {
        if (this.tableData.length > 0 && this.cellConfigList.length < 1) {
          this.setContainerName(val);
        }
      },
      deep: true,
    },
    'entity.defaultStorageLimit': {
      handler(val) {
        if (this.tableData.length > 0 && this.cellConfigList.length < 1) {
          this.SetCellProperty('storageLimit', val);
        }
      },
      deep: true,
    },
  },
};
</script>
<style lang="scss" scoped>
.cabinet_latte_contain {
  .cabinet_latte_title {
    width: 100%;
    padding: 0 20px;
    font-family: KaiTi, Microsoft YaHei;
    font-size: 20px;
    color: #606266;
    line-height: 32px;
    font-weight: 600;
  }
  #cabinet_latte_table {
    margin-top: 20px;
    user-select: none;
    td {
      //   padding: 0;
      .cell_info {
        width: 100%;
        height: 100%;
      }
      .cell_btn {
        width: fit-content;
      }
    }
    ::v-deep .el-descriptions__body {
      background-color: transparent;
    }
  }
  ::v-deep .el-table thead {
    display: none;
    ::v-deep .el-table__row {
      border: 1px solid black;
      border-bottom: 0px;
    }
  }

  ::v-deep .el-table .el-table__body td,
  .el-table .el-table__header,
  .el-table .el-table__body tr,
  .el-table .el-table__header-wrapper {
    border: 1px solid black;
  }

  .el-table__body-wrapper {
    border: 1px solid rgb(0, 0, 0);
    border-bottom: 0px;
  }
  ::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
    background-color: transparent;
  }
}
</style>

右击菜单组件ContextMenuTool.vue

<template>
  <div>
    <!-- <div class="context-menu-trigger">右击我试试</div> -->
    <div v-if="showContextMenu" :style="menuStyle" class="context-menu">
      <template v-if="cellOperate">
        <div v-if="isMarge" @click="mergeCell()">合并单元格</div>
        <div v-else @click="splitCell()">拆分单元格</div>
      </template>
      <div @click="delRow()">删除本行</div>
      <div @click="delCol()">删除本列</div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showContextMenu: false,
      menuStyle: {
        top: '0px',
        left: '0px',
      },
      isMarge: true,
      cellOperate: false,
    };
  },
  methods: {
    openContextMenu(event, flag, cellOperate) {
      this.isMarge = !flag;
      this.cellOperate = !cellOperate;
      this.menuStyle.top = `${event.clientY}px`;
      this.menuStyle.left = `${event.clientX}px`;
      this.menuStyle.position = 'fixed';
      this.menuStyle.zIndex = 9999999;
      this.showContextMenu = true;

      // 监听一次点击事件,用于关闭菜单
      window.addEventListener('click', this.closeContextMenu, { once: true });
    },
    mergeCell() {
      this.$emit('mergeCell');
    },
    splitCell() {
      this.$emit('splitCell');
    },
    delRow() {
      this.$emit('delRow');
    },
    delCol() {
      this.$emit('delCol');
    },
    closeContextMenu() {
      this.showContextMenu = false;
    },
    menuItemClicked(item) {
      console.log(`${item} 被点击了`);
      this.closeContextMenu();
    },
  },
};
</script>

<style scoped>
.context-menu {
  position: absolute;
  border: 1px solid #ccc;
  background-color: #fff;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
}

.context-menu div {
  padding: 8px;
  cursor: pointer;
}

.context-menu div:hover {
  background-color: #f5f5f5;
}
</style>

五、设计器组件的使用

使用CabinetLatticeSet ,其中CellCard插槽表示自定义的格子内容,
组件的必要参数 和一些内置方法,会在下面进行细讲

<CabinetLatticeSet
          ref="cabinetLatticeSet"
          :cellConfigList="cellConfigList"
          :setConfig="setConfig"
          :entity.sync="entity"
          :dialogStatus="dialog.status"
          :cellData="cellData"
        >
          <template #cellCard="{ cell, tableData }">
            <CellCard
              :cell="cell"
              :tableData="tableData"
              @updateCell="updateCell"
              :dialogStatus="dialog.status"
            ></CellCard>
          </template>
        </CabinetLatticeSet>

CellCard.vue 自定义格子的内容卡片组件,可以按照自己需求修改


<template>
  <div class="cell_container">
    <el-descriptions
      class="margin-top"
      style="zindex: 0"
      :title="`编号:${cell.no} `"
      :column="1"
      direction="horizontal"
    >
      <el-descriptions-item label="容量">{{ cell.storageLimit }}</el-descriptions-item>
      <el-descriptions-item label="锁ID">{{ cell.lockId }}</el-descriptions-item>
    </el-descriptions>

    <div class="cell_btn">
      <el-button type="success" size="small" @click="setCell(cell)">{{ getBtnText }}</el-button>
    </div>
    <CellDialog ref="CellDialog" dialogFormDesignerName="柜格管理" @updateCell="updateCell"></CellDialog>
  </div>
</template>
<script>
import CellDialog from './CellDialog.vue';

const STATUS = {
  CREATE: 0,
  UPDATE: 1,
  DETAIL: 2,
};
export default {
  props: {
    cell: {
      type: Object,
      default: () => {},
    },
    tableData: {
      type: Array,
      default: () => [],
    },
    dialogStatus: {
      type: Number,
      default: 0,
    },
  },
  components: {
    CellDialog,
  },
  computed: {
    getBtnText() {
      if (this.dialogStatus === STATUS.UPDATE || this.dialogStatus === STATUS.CREATE) {
        return '设置柜子';
      }
      return '查看柜子';
    },
  },
  data() {
    return {};
  },
  methods: {
    setCell(cell) {
      if (this.dialogStatus === STATUS.DETAIL) {
        this.$refs.CellDialog.showDialog(cell, 2, cell);
      } else {
        this.$refs.CellDialog.showDialog(cell, 1, cell);
      }
    },
    updateCell(cell) {
      this.$emit('updateCell', cell);
    },
  },
};
</script>
<style lang="scss" scoped>
// .cell_container {
//   width: 100%;
//   height: 100%;
//   background-image: url('board.png'); /* 设置背景图片 */
//   background-size: 100% 100%; /* 覆盖整个div,也可以用contain保持图片的比例 */
//   background-position: center; /* 图片居中显示 */
//   background-repeat: no-repeat; /* 防止图片重复 */
//   padding: 20px 20px;
// }
</style>

六、CabinetLatticeSet 组件的参数和方法

属性

参数说明类型可选值默认值
cellConfigList柜子结构基础jsonArray-[]
cellData柜子格子数据listArray-[]
setConfig控制生成行数列数Object-{row:0,col:0}
entity格子的基本类,可根据自身项目在组件中自行修改Object--
dialogStatus当前设计器状态Number新增: 0, 编辑: 1,查看: 2,0

方法

方法名说明参数
updateCellData更新某个格子数据,传入新的格子对象cell,用来更新结构cell
getCellConfigList获取柜子结构的json数据-
getCellDataList获取柜子中所有格子的实例数据-

七、展示渲染 柜子组件的代码演示

下面是代码 经过了精简直接使用可能会用问题,请注意使用,自行修改
将数据库中存在的柜子结构和数据渲染出来的页面

<template>
  <div class="ware-house-container" ref="warehouseContainer">
    <div class="lattice_content" v-if="chooseCabinetItem.isShowInfo" v-loading="loadingTable">
      <table border="1" id="cabinet_latte_table" align="center" cellpadding="20" cellspacing="0" :key="renderTable">
        <tr v-for="(row, index) in cellDesign" :key="index" :id="row.id">
          <td
            v-for="(col, j) in row.col"
            :key="j"
            :id="col.id"
            :rowspan="col.rowspan"
            :colspan="col.colspan"
            :initRow="col.initRow"
            :initCol="col.initCol"
            v-show="!col.isMerge"
          >
            <div
              class="lattice_card"
              :style="{ backgroundColor: getCabinetColor(j + 1) }"
              @mouseenter="mouseEnterHandler(col.cell, `${index},${j}`)"
              @mouseleave="mouseLeaveHandler(col.cell, `${index},${j}`)"
            >
              <div class="info_item_no">{{ col.cell.no }}</div>
              <div class="goods_sum">
                <template v-if="col.cell.usedStorage > 0"> {{ col.cell.usedStorage }}(件) </template>
                <template v-else> <div class="goods_tip"></div> </template>
              </div>
              <transition name="slide-up">
                <div class="card_box__hover" v-show="hoverXY === `${index},${j}`">
                  <template v-if="type">
                    <!-- <el-button class="big_btn" v-if="type === '存放位置'" type="primary" @click="saveLocation(col)"
                    >保存位置</el-button
                  > -->
                  </template>
                  <template v-else>
                    <!-- <el-button type="primary" class="big_btn" v-if="col.cell.usedStorage > 0" @click="goodsInfo(col)"
                    >查看物品</el-button
                  >

                  <el-button type="primary" class="big_btn" @click="btnDetailOnClick(item)">开柜</el-button> -->
                  </template>
                </div>
              </transition>
            </div>
          </td>
        </tr>
      </table>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isOverFlow: false,
      toggleFlag: false,
      cabinetContentTop: '',
      cabinetColor: ['#cfe7e9', '#e2f5fb', '#eae7f8', '#def1e0', '#fbfce0'],
      menustyle: {},
      menuHeight: '140px',
      chooseIndex: 0,
      chooseCabinetItem: {},
      chooseCellItem: {},
      hoverXY: null,
      cabinetList: [],
      cellDesign: [],
      cellData: [],
      cabinetContentStyle: {},
      loadingTable: false,
      renderTable: 0,
    };
  },

  methods: {
    async getCellInfoById(id) {
      // 获取柜子信息
      this.loadingTable = true;
      const res = await this.axios({
        url: 'xxxxxx',
        method: 'post',
        params: {
          id,
        },
      });
      this.cellDesign = res.data.conf ? JSON.parse(res.data.conf) : [];
      this.cellData = res.data.containerInfoList || [];

      this.setCellConfigList();
      this.loadingTable = false;
    },
    setCellConfigList() {
      if (this.cellDesign.length > 0) {
        // this.tableData = this.cellDesign;
        this.renderTable += 1;
        this.setCellDataList();
      }
    },
    // 解析结合数据渲染页面
    setCellDataList() {
      if (this.cellData.length > 0) {
        this.cellData.forEach(cell => {
          // 遍历 tableData 数组
          this.cellDesign.forEach(row => {
            // 在当前 row 的 col 数组中查找匹配的 cell
            const colItem = row.col.find(col => col.id === cell.id);
            if (colItem) {
              // 替换 cell 对象
              colItem.cell = cell;
            }
          });
        });

        this.renderTable += 1;
      }
    },

    getCabinetColor(col) {
      const num = col / this.cabinetColor.length;
      if (num > 1) {
        const q = col % this.cabinetColor.length;
        return this.cabinetColor[q];
      }
      return this.cabinetColor[col - 1];
    },

    mouseEnterHandler(item, index) {
      this.hoverXY = index;
    },
    mouseLeaveHandler(item, index) {
      if (this.hoverXY === index) {
        this.hoverXY = null;
      }
    },
  },
};
</script>
<style lang="scss" scoped>
.lattice_content {
  //   background-color: #cfe7e9;
  float: left;
  width: 100%;
  height: calc(100% - 40px);
  padding: 10px 15px 20px 15px;
  overflow: auto;
  #cabinet_latte_table {
    // border: 1px solid #eeeeee;
    border-color: #eeeeee;
    td {
      height: 200px;
      padding: 0;
      position: relative;

      .cell_info {
        width: 100%;
        height: 100%;
      }
      .lattice_card {
        // width: 100%;
        min-width: 200px;
        height: 100%;
        position: relative;
        padding: 10px 20px;
        .info_item_no {
          float: left;
          margin-left: 20px;
          font-size: 1vw;
          line-height: 40px;
          font-weight: 600;
        }
        .goods_sum {
          float: left;
          width: 100%;
          height: calc(100% - 60px);

          display: flex;
          justify-content: center;
          align-items: center;
          font-size: 2vw;
          font-weight: 600;
          .goods_tip {
            font-size: 4vw;
            font-weight: 800;
            color: #aaaaaa;
          }
        }
        .card_box__hover {
          border-radius: 10px;
          width: 100%;
          height: 100%;
          position: absolute;
          bottom: 0;
          left: 0;
          background-color: rgba(124, 203, 209, 0.5);
          display: flex;
          align-items: center;
          justify-content: center;
        }
      }
    }
  }
}
/* 自定义滚动条的宽度和背景颜色 */
::-webkit-scrollbar {
  width: 10px;
  background-color: #f5f5f5;
}

/* 自定义滚动条滑块的颜色、边框和滑动时的颜色 */
::-webkit-scrollbar-thumb {
  background-color: #555;
  border-radius: 5px;
  border: 2px solid #f5f5f5;
}

/* 自定义滚动条轨道的颜色 */
::-webkit-scrollbar-track {
  background-color: #f0f0f0;
}
.slide-up-enter-active {
  animation: height 0.5s;
}
::v-deep .big_btn {
  font-size: 23px !important;
}

@keyframes height {
  from {
    height: 0%;
  }
  to {
    height: 100%;
  }
}
</style>

八、柜子结构json demo展示

下面将根据图片所示的柜子结构给出对应代码生成的结构json,并对json的各个参数做解释
在这里插入图片描述

row对象
id:行id 唯一值
col:本行的列数组
col数组下的cell格子对象
id:每个格子的唯一id,数据库中要跟格子中的物品做关联使用!!!很重要
rowspan:本单元格占据的行数
colspan:本单元格占据的列数
initRow:初始单元格的行位置
initCol: 初始单元格的列位置
isMerge:这个单元格是否被合并掉了false表示页面需要显示 true表示这个单元格被合并了,不再做显示
mergeids:被合并的单元格的id集合

[
    {
        "id": "e255d896-d62f-4501-9946-cf38c395111f",
        "col": [
            {
                "id": "5d40900a-791b-4add-a815-08c16ece6e08",
                "rowspan": 2, 
                "colspan": 2,
                "initRow": 0, 
                "initCol": 0,
                "isMerge": false,
                "mergeids": [
                    "d1b08873-0c3a-41af-b48e-c5365ca51fad",
                    "304d1ea2-c550-4864-b3ad-0b0599fe3ca9",
                    "decf8dd7-5b24-437f-848a-78ebc31ad0b7"
                ]
            },
            {
                "id": "d1b08873-0c3a-41af-b48e-c5365ca51fad",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 0,
                "initCol": 1,
                "isMerge": true,
                "mergeids": []
            },
            {
                "id": "b9901b3b-ab19-4eac-ae6e-61b70f0e16ba",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 0,
                "initCol": 2,
                "isMerge": false,
                "mergeids": []
            },
            {
                "id": "9635c3ab-4594-4969-a00f-45c7ad4a3b4a",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 0,
                "initCol": 3,
                "isMerge": false,
                "mergeids": []
            }
        ]
    },
    {
        "id": "9ce8ff5e-5125-41ee-bc6e-7de9bb4980aa",
        "col": [
            {
                "id": "304d1ea2-c550-4864-b3ad-0b0599fe3ca9",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 1,
                "initCol": 0,
                "isMerge": true,
                "mergeids": []
            },
            {
                "id": "decf8dd7-5b24-437f-848a-78ebc31ad0b7",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 1,
                "initCol": 1,
                "isMerge": true,
                "mergeids": []
            },
            {
                "id": "b632f4fb-4ec0-4776-b007-dc9e124faa69",
                "rowspan": 2,
                "colspan": 1,
                "initRow": 1,
                "initCol": 2,
                "isMerge": false,
                "mergeids": [
                    "7f30a380-c8dc-467f-b849-13c6b0c94ab6"
                ]
            },
            {
                "id": "8d7eed53-d0e7-4329-b50b-a936b445de62",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 1,
                "initCol": 3,
                "isMerge": false,
                "mergeids": []
            }
        ]
    },
    {
        "id": "981b8507-0e78-4a3e-bf48-4eafadefa5e7",
        "col": [
            {
                "id": "5acfc116-1fa2-4b45-a445-25b7700381b8",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 2,
                "initCol": 0,
                "isMerge": false,
                "mergeids": []
            },
            {
                "id": "5a4448dd-d968-4300-bdc3-d899bffbe214",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 2,
                "initCol": 1,
                "isMerge": false,
                "mergeids": []
            },
            {
                "id": "7f30a380-c8dc-467f-b849-13c6b0c94ab6",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 2,
                "initCol": 2,
                "isMerge": true,
                "mergeids": []
            },
            {
                "id": "5cee411f-6761-437b-8ebc-2763ef3752d8",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 2,
                "initCol": 3,
                "isMerge": false,
                "mergeids": []
            }
        ]
    },
    {
        "id": "19acc767-a559-4fad-b5f5-047c142b9549",
        "col": [
            {
                "id": "ef369fda-de70-4e37-8ab5-20f509af1c31",
                "rowspan": 1,
                "colspan": 2,
                "initRow": 3,
                "initCol": 0,
                "isMerge": false,
                "mergeids": [
                    "657a7c99-a10c-49e1-a179-997a708b42ac"
                ]
            },
            {
                "id": "657a7c99-a10c-49e1-a179-997a708b42ac",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 3,
                "initCol": 1,
                "isMerge": true,
                "mergeids": []
            },
            {
                "id": "efa6f4eb-7353-4582-bed9-e2b50ccc8cbc",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 3,
                "initCol": 2,
                "isMerge": false,
                "mergeids": []
            },
            {
                "id": "787a7e5d-96d3-46c1-8fea-220c675c05cd",
                "rowspan": 1,
                "colspan": 1,
                "initRow": 3,
                "initCol": 3,
                "isMerge": false,
                "mergeids": []
            }
        ]
    }
]
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Ant Design Vue的a-table组件默认不支持单元格合并,但是可以通过自定义渲染函数来实现单元格合并。以下是一个简单的示例代码: ```html <template> <a-table :columns="columns" :data-source="dataSource"></a-table> </template> <script> export default { data() { return { columns: [ { title: "姓名", dataIndex: "name", customRender: (text, row, index) => { const rowspan = row.age < 30 ? 2 : 1; // 根据条件设定行合并数 if (index % rowspan === 0) { return { children: text, attrs: { rowSpan: rowspan } }; } else { return { children: "", attrs: { rowSpan: 0 } }; } } }, { title: "年龄", dataIndex: "age" }, { title: "地址", dataIndex: "address" } ], dataSource: [ { name: "张三", age: 28, address: "北京市海淀区" }, { name: "李四", age: 32, address: "上海市浦东新区" }, { name: "王五", age: 25, address: "广州市天河区" }, { name: "赵六", age: 31, address: "深圳市福田区" } ] }; } }; </script> ``` 在上面的代码中,我们通过自定义渲染函数 `customRender` 来实现单元格合并。在渲染姓名这一列时,根据条件设定行合并数,然后判断当前行是否是合并行的第一行,如果是就返回一个包含 `children` 和 `attrs` 属性的对象,其中 `children` 属性设置单元格显示的文本,`attrs` 属性设置单元格的 `rowspan` 属性。如果不是合并行的第一行,就返回一个空字符串和 `rowspan` 为 0 的 `attrs` 属性,表示该单元格不需要显示。 这样就能实现 Ant Design Vue 的 a-table 表格单元格合并了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值