【sgExcelGrid】自定义组件:简单模拟Excel表格拖拽、选中单元格、横行、纵列、拖拽圈选等操作

简介: 【sgExcelGrid】自定义组件:简单模拟Excel表格拖拽、选中单元格、横行、纵列、拖拽圈选等操作


特性:

  1. 可以自定义拖拽过表格
  2. 可以点击某个表格,拖拽右下角小正方形进行任意方向选取单元格
  3. 支持选中某一行、列
  4. 支持监听@selectedGrids、@selectedDatas事件获取选中项的DOM对象和数据数组
  5. 支持props自定义显示label字段别名

sgExcelGrid源码

<template>
  <div :class="$options.name">
    <div class="ruler-corner"></div>
    <div class="horizontal-ruler" :style="{ left: `${-rulerPosition.x}px` }">
      <div
        class="tick"
        :hoverGrid="hoverGrid.x === A_Z[i]"
        @click="(hoverGrid = { x: A_Z[i] }), (mousedownGrid = {})"
        v-for="(a, i) in A_Z.slice(0, colCount_)"
        :key="i"
      >
        {{ a }}
      </div>
    </div>
    <div class="vertical-ruler" :style="{ top: `${-rulerPosition.y}px` }">
      <div
        class="tick"
        :hoverGrid="hoverGrid.y === i"
        @click="(hoverGrid = { y: i }), (mousedownGrid = {})"
        v-for="(a, i) in Math.ceil(pageSize / colCount_)"
        :key="i"
      >
        {{ i + 1 }}
      </div>
    </div>
    <div class="grids-scroll" ref="scrollContainer">
      <div class="grids" ref="dragContainer" :selectedGrids="selectedGrids.length > 0">
        <div
          class="grid"
          :hoverGridX="hoverGrid.x === gridsData[i].x"
          :hoverGridY="hoverGrid.y === gridsData[i].y"
          :mousedownGrid="
            mousedownGrid.x === gridsData[i].x && mousedownGrid.y === gridsData[i].y
          "
          :dragMove="isMouseDragMove"
          :type="a.strong ? 'primary' : ''"
          v-for="(a, i) in data"
          :key="i"
          @mouseover="hoverGrid = gridsData[i]"
          @click="clickGrid(i)"
          @mouseout="hoverGrid = {}"
        >
          <span :title="a[label]">{{ a[label] }}</span
          ><i class="el-icon-close" @click="del(a)" />
          <div
            class="position-text"
            :title="`点击复制`"
            @click="$g.copy($g.stripHTML(getPositionText(gridsData[i])), true)"
            v-html="getPositionText(gridsData[i])"
          ></div>
          <div class="drag-select-btn" @mousedown.stop="clickResizeHandle"></div>
        </div>
      </div>
    </div>
 
    <!-- 拖拽 -->
    <sgDragMoveTile :data="dragMoveTileData" @scroll="scroll" />
  </div>
</template>
<script>
import sgDragMoveTile from "@/vue/components/admin/sgDragMoveTile";
export default {
  name: "sgExcelGrid",
  components: {
    sgDragMoveTile,
  },
  data() {
    return {
      A_Z: [...Array(26)].map((v, i) => String.fromCharCode(i + 65)),
 
      hoverGrid: {}, //移入的宫格标记
      mousedownGrid: {}, //点击的宫格标记
      gridsData: [], //记录网格宫格状态
      selectedGrids: [], //被选中的网格宫格DOM
      selectedDatas: [], //被选中的网格宫格数据
      rulerPosition: { x: 0, y: 0 },
      dragMoveTileData: {},
      gridWidth: 200,
      gridHeight: 100,
      colCount_: 8,
      pageSize_: 100,
      isMouseDragMove: false, //鼠标拖拽选中移动
      label: `label`, //显示文本字段名
    };
  },
  props: [
    "value",
    "props",
    "data",
    "pageSize", //每页显示多少个宫格
    "colCount", //列数
  ],
  computed: {},
  watch: {
    props: {
      handler(newValue, oldValue) {
        if (newValue && Object.keys(newValue).length) {
          newValue.label && (this.label = newValue.label);
        }
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    pageSize: {
      handler(newValue, oldValue) {
        newValue && (this.pageSize_ = newValue);
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    data: {
      handler(newValue, oldValue) {
        this.init_gridsData();
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    colCount: {
      handler(newValue, oldValue) {
        newValue && (this.colCount_ = newValue);
        this.$nextTick(() => {
          this.$el.style.setProperty("--gridWidth", `${this.gridWidth}px`); //js往css传递局部参数
          this.$el.style.setProperty("--gridHeight", `${this.gridHeight}px`); //js往css传递局部参数
          this.$el.style.setProperty(
            "--gridsWidth",
            `${this.colCount_ * this.gridWidth}px`
          ); //js往css传递局部参数
        });
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    selectedGrids: {
      handler(newValue, oldValue) {
        this.$emit(`selectedGrids`, newValue || []);
      },
      deep: true, //深度监听
      // immediate: true, //立即执行
    },
    selectedDatas: {
      handler(newValue, oldValue) {
        this.$emit(`selectedDatas`, newValue || []);
      },
      deep: true, //深度监听
      // immediate: true, //立即执行
    },
  },
  created() {},
  mounted() {
    this.init_grid_view();
    this.addEvents();
  },
  destroyed() {
    this.removeEvents();
  },
  methods: {
    clickGrid(i) {
      (this.mousedownGrid = this.gridsData[i]),
        (this.hoverGrid = {}),
        this.resetSelectGrid();
      this.selectedGrids = [this.gridsData[i]];
      this.selectedDatas = [this.data[i]];
    },
    clickResizeHandle(e) {
      this.originRect = e.target.parentNode.getBoundingClientRect();
      this.originRect.bottomRightX = this.originRect.x + this.originRect.width; //右下角坐标.x
      this.originRect.bottomRightY = this.originRect.y + this.originRect.height; //右下角坐标.y
      this.__addWindowEvents();
    },
    __addWindowEvents() {
      this.__removeWindowEvents();
      addEventListener("mousemove", this.mousemove_window);
      addEventListener("mouseup", this.mouseup_window);
    },
    __removeWindowEvents() {
      removeEventListener("mousemove", this.mousemove_window);
      removeEventListener("mouseup", this.mouseup_window);
    },
    mousemove_window(e) {
      this.isMouseDragMove = true;
      let { x, y } = e;
      let minWidth = 0,
        minHeight = 0,
        maxWidth = innerWidth,
        maxHeight = innerHeight;
      x < 0 && (x = 0),
        y < 0 && (y = 0),
        x > maxWidth && (x = maxWidth),
        y > maxHeight && (y = maxHeight);
      let style = {};
      style.x = this.originRect.x;
      style.y = this.originRect.y;
      style.width = x - this.originRect.x;
      style.width <= minWidth &&
        ((style.width = Math.abs(style.width)),
        ((style.x = this.originRect.x - style.width),
        (style.width = style.width + this.originRect.width)));
      style.height = y - this.originRect.y;
      style.height <= minHeight &&
        ((style.height = Math.abs(style.height)),
        ((style.y = this.originRect.y - style.height),
        (style.height = style.height + this.originRect.height)));
      style.width > maxWidth && (style.width = maxWidth);
      style.height > maxHeight && (style.height = maxHeight);
      this.calcRectGrid(style);
    },
    mouseup_window(e) {
      this.isMouseDragMove = false;
      this.__removeWindowEvents();
    },
    resetAllGridStatus() {
      this.resetSelectGrid();
      this.mousedownGrid = {};
    },
    resetSelectGrid(d) {
      this.selectedGrids = [];
      this.selectedDatas = [];
      let grids = this.$refs.dragContainer.querySelectorAll(`.grid`);
      grids.forEach((v) => {
        v.removeAttribute("selected-left");
        v.removeAttribute("selected-top");
        v.removeAttribute("selected-right");
        v.removeAttribute("selected-bottom");
        v.removeAttribute("selected");
      });
    },
    // 计算是否选中格子
    calcRectGrid(rect) {
      this.resetSelectGrid();
      this.selectedGrids = this.getSelectedDoms({
        targetDoms: this.$refs.dragContainer.querySelectorAll(`.grid`),
        rect,
      });
      this.selectedGrids.forEach((grid) => {
        let grid_rect = grid.getBoundingClientRect();
        let gridRectScreenWidth = grid_rect.x + grid_rect.width;
        let gridRectScreenHeight = grid_rect.y + grid_rect.height;
        grid_rect.x <= rect.x &&
          rect.x < gridRectScreenWidth &&
          grid.setAttribute("selected-left", true);
        grid_rect.y <= rect.y &&
          rect.y < gridRectScreenHeight &&
          grid.setAttribute("selected-top", true);
        let rectScreenWidth = rect.x + rect.width;
        let rectScreenHeight = rect.y + rect.height;
        grid_rect.x < rectScreenWidth &&
          rectScreenWidth <= grid_rect.x + grid_rect.width &&
          grid.setAttribute("selected-right", true);
        grid_rect.y < rectScreenHeight &&
          rectScreenHeight <= grid_rect.y + grid_rect.height &&
          grid.setAttribute("selected-bottom", true);
        grid.setAttribute("selected", true);
      });
    },
    // 获取被选中的DOM
    getSelectedDoms({ targetDoms, rect } = {}) {
      this.selectedDatas = [];
      return [...targetDoms].filter((targetDom, i) => {
        if (this.$g.isCrash(targetDom, rect)) {
          this.selectedDatas.push(this.data[i]);
          return targetDom;
        }
      }); // 获取被圈选的内容
    },
    // ----------------------------------------
    del(d) {
      this.$emit(`del`, d);
    },
    addEvents(d) {
      this.removeEvents();
      this.__removeWindowEvents();
      addEventListener("resize", this.resize);
    },
    removeEvents(d) {
      removeEventListener("resize", this.resize);
    },
    getPositionText(gridData) {
      return `<span>第${gridData.y + 1}行</span>&nbsp;<span>第${gridData.x}列</span>`;
    },
    init_grid_view() {
      this.resize();
      this.$nextTick(() => {
        this.init_sgDragMoveTile();
      });
    },
    init_gridsData(d) {
      this.gridsData = [...Array(this.pageSize_)].map((v, i) => ({
        x: this.A_Z[i % this.colCount_],
        y: Math.floor(i / this.colCount_),
      }));
      this.$nextTick(() => {
        this.resetAllGridStatus();
      });
    },
    init_sgDragMoveTile() {
      this.dragMoveTileData = {
        scrollContainer: this.$refs.scrollContainer,
        dragContainer: this.$refs.dragContainer,
      };
    },
    resize(d) {
      let scrollContainer = this.$refs.scrollContainer;
      scrollContainer && this.scroll({ target: scrollContainer });
    },
    scroll(e) {
      this.rulerPosition = {
        x: e.target.scrollLeft,
        y: e.target.scrollTop,
      };
    },
  },
};
</script>
<style lang="scss" scoped>
.sgExcelGrid {
  /*禁止选中文本*/
  user-select: none;
 
  overflow: hidden;
 
  $tickDis: 40px;
  $gridWidth: var(--gridWidth);
  $gridHeight: var(--gridHeight);
  $gridsWidth: var(--gridsWidth);
  $scrollbarWidth: 14px;
  width: 100%;
  height: 100%;
  position: relative;
  .ruler-corner {
    position: absolute;
    z-index: 2;
    left: 0;
    top: 0;
    width: $tickDis;
    height: $tickDis;
    box-sizing: border-box;
    border: 1px solid #ebeef5;
    border-right: none;
    border-bottom: none;
    background-color: #eff2f755;
    /*遮罩模糊*/
    backdrop-filter: blur(5px);
  }
  .horizontal-ruler {
    position: absolute;
    z-index: 1;
    left: 0;
    top: 0;
    margin-left: $tickDis;
    display: flex;
    flex-wrap: nowrap;
    border-top: 1px solid #ebeef5;
    border-left: 1px solid #ebeef5;
    /*遮罩模糊*/
    backdrop-filter: blur(5px);
    // box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    .tick {
      display: flex;
      justify-content: center;
      align-items: center;
      flex-shrink: 0;
      width: $gridWidth;
      height: $tickDis;
      box-sizing: border-box;
      border-top: 1px solid transparent;
      border-left: 1px solid transparent;
      border-bottom: 1px solid #ebeef5;
      border-right: 1px solid #ebeef5;
      font-family: DIN-Black;
      background-color: #eff2f755;
      &[hoverGrid] {
        border-left: 1px solid #409eff;
        border-right: 1px solid #409eff;
        background-color: #b3d8ff99;
        color: #409eff;
      }
    }
  }
  .vertical-ruler {
    position: absolute;
    z-index: 1;
    left: 0;
    top: 0;
    margin-top: $tickDis;
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    border-top: 1px solid #ebeef5;
    border-left: 1px solid #ebeef5;
    /*遮罩模糊*/
    backdrop-filter: blur(5px);
    // box-shadow: 2px 0 12px 0 rgba(0, 0, 0, 0.1);
    .tick {
      display: flex;
      justify-content: center;
      align-items: center;
      flex-shrink: 0;
      width: $tickDis;
      height: $gridHeight;
      box-sizing: border-box;
      border-top: 1px solid transparent;
      border-left: 1px solid transparent;
      border-bottom: 1px solid #ebeef5;
      border-right: 1px solid #ebeef5;
      font-family: DIN-Black;
      background-color: #eff2f7;
      background-color: #eff2f755;
      &[hoverGrid] {
        border-top: 1px solid #409eff;
        border-bottom: 1px solid #409eff;
        background-color: #b3d8ff99;
        color: #409eff;
      }
    }
  }
  .grids-scroll {
    width: calc(100% - #{$tickDis});
    height: calc(100vh - 310px);
    box-sizing: border-box;
    overflow: auto;
    position: relative;
    margin: $tickDis 0 0 $tickDis;
    .grids {
      width: calc(#{$gridsWidth} + #{$scrollbarWidth});
      min-height: calc(#{$gridHeight} + #{$scrollbarWidth});
      overflow: auto;
      display: flex;
      flex-wrap: wrap;
      align-content: flex-start;
      box-sizing: border-box;
      border-top: 1px solid #ebeef5;
      border-left: 1px solid #ebeef5;
 
      .grid {
        display: flex;
        justify-content: center;
        align-items: center;
        width: $gridWidth;
        height: $gridHeight;
        padding: 20px;
        box-sizing: border-box;
        border-top: 1px solid transparent;
        border-left: 1px solid transparent;
        border-bottom: 1px solid #ebeef5;
        border-right: 1px solid #ebeef5;
        word-wrap: break-word;
        word-break: break-all;
        white-space: break-spaces;
        position: relative;
 
        span {
          /*多行省略号*/
          overflow: hidden;
          word-break: break-all;
          white-space: break-spaces;
          display: -webkit-box;
          -webkit-box-orient: vertical;
          max-height: min-content;
          -webkit-line-clamp: 3;
          line-height: 1.2;
        }
        // 坐标文本
        .position-text {
          position: absolute;
          height: 22px;
          z-index: 1;
          left: 0px;
          top: 0px;
          display: none;
          flex-wrap: nowrap;
          white-space: nowrap;
          align-items: center;
          color: white;
          background-color: #00000055;
          box-sizing: border-box;
          padding: 0 5px;
          border-radius: 0 0 8px 0;
          >>> span {
            font-size: 12px !important;
          }
          cursor: cell;
          &:hover {
            background-color: #409eff;
            color: white;
          }
        }
        // 删除
        i.el-icon-close {
          z-index: 1;
          display: none;
          position: absolute;
          right: 0;
          top: 0;
          font-size: 12px !important;
          justify-content: center;
          align-items: center;
          color: white;
          background-color: #409eff;
          box-sizing: border-box;
          padding: 5px;
          border-radius: 0 0 0 8px;
          cursor: pointer;
          &:hover {
            background-color: #f56c6c;
          }
        }
        // 拖拽选区
        .drag-select-btn {
          position: absolute;
          height: 9px;
          width: 9px;
          z-index: 1;
          right: -4.5px;
          bottom: -4.5px;
 
          display: none;
          box-sizing: border-box;
          border: 2px solid white;
          background-color: #f56c6c;
          cursor: crosshair;
        }
 
        &:nth-of-type(2n) {
          background-color: #eff2f755;
        }
 
        &[hoverGridX] {
          border-left: 1px solid #409eff;
          border-right: 1px solid #409eff;
          background-color: #f2f8fe;
        }
        &[hoverGridY] {
          border-top: 1px solid #409eff;
          border-bottom: 1px solid #409eff;
          background-color: #f2f8fe;
        }
        &[mousedownGrid] {
          z-index: 2; //让drag-select-btn在顶端
          border: 1px solid #f56c6c;
          background-color: #f56c6c22;
          .position-text {
            background-color: #f56c6c66;
            color: white;
            &:hover {
              background-color: #f56c6c;
            }
          }
          i.el-icon-close {
            background-color: #f56c6c66;
            &:hover {
              background-color: #f56c6c;
            }
          }
          .drag-select-btn {
            display: block;
          }
          &:hover:not([dragMove]) {
            background-color: #f56c6c66;
            i,
            .position-text {
              display: flex;
            }
          }
        }
        &[selected] {
          z-index: 2; //让drag-select-btn在顶端
          border-top: 1px solid transparent;
          border-left: 1px solid transparent;
          border-right: 1px solid #f56c6c22;
          border-bottom: 1px solid #f56c6c22;
          background-color: #f56c6c22;
          .position-text {
            background-color: #f56c6c66;
            color: white;
            &:hover {
              background-color: #f56c6c;
            }
          }
          i.el-icon-close {
            background-color: #f56c6c66;
            &:hover {
              background-color: #f56c6c;
            }
          }
          .drag-select-btn {
            display: none;
          }
          &:hover:not([dragMove]) {
            border: 1px solid #f56c6c;
            background-color: #f56c6c66 !important;
          }
        }
        &[selected-left] {
          border-left: 1px solid #f56c6c;
        }
        &[selected-top] {
          border-top: 1px solid #f56c6c;
        }
        &[selected-right] {
          border-right: 1px solid #f56c6c;
        }
        &[selected-bottom] {
          border-bottom: 1px solid #f56c6c;
        }
        &[selected-right][selected-bottom] {
          .drag-select-btn {
            display: block;
          }
        }
        &[mousedownGridX] {
          border-left: 1px solid #f56c6c;
          border-right: 1px solid #f56c6c;
          background-color: #f56c6c22;
        }
        &[mousedownGridY] {
          border-top: 1px solid #f56c6c;
          border-bottom: 1px solid #f56c6c;
          background-color: #f56c6c22;
        }
        &[dragMove] {
        }
        &:hover:not([mousedownGrid]):not([dragMove]) {
          background-color: #b3d8ff99;
          i,
          .position-text {
            display: flex;
          }
        }
      }
      &[selectedGrids] {
        .grid {
          &:hover:not([mousedownGrid]):not([dragMove]):not([selected]) {
            border: 1px solid #409eff;
          }
        }
      }
    }
  }
}
</style>

应用

<template>
  <sgExcelGrid
    :props="{ label: `MC` }"
    :data="gridDatas"
    :pageSize="pageSize"
    @del="delGrid"
    @selectedDatas="selectedDatas"
  />
</template>
<script>
import sgExcelGrid from "@/vue/components/admin/sgExcelGrid";
export default {
  components: {
    sgExcelGrid,
  },
  data() {
    return {
      gridDatas: [
        { ID: 1, value: 1, MC: "显示文本1" },
        { ID: 2, value: 2, MC: "显示文本2" },
        { ID: 3, value: 3, MC: "显示文本3" },
        { ID: 4, value: 4, MC: "显示文本4" },
        { ID: 5, value: 5, MC: "显示文本5" },
      ],
      pageSize: 100, //每页显示多少个单元格
      rectSelectIDS: [], //选中的ID数组
    };
  },
  props: ["value"],
  computed: {},
  watch: {},
  created() {},
  mounted() {},
  destroyed() {},
  methods: {
    selectedDatas(d) {
      this.rectSelectIDS = d.map((v) => v.ID); //获取选中项ID数组
    },
    delGrid(d) {
      //删除单元格
    },
  },
};
</script>

基于

image.png

相关文章
|
1月前
|
SQL 数据库连接 数据库
【SQL Server】2. 将数据导入导出到Excel表格当中
【SQL Server】2. 将数据导入导出到Excel表格当中
47 0
|
4天前
|
C#
C#NPOI操作Excel详解
C#NPOI操作Excel详解
8 0
|
14天前
|
数据挖掘 数据库连接 数据处理
精通Excel意味着熟练掌握基础及进阶操作
精通Excel意味着熟练掌握基础及进阶操作,如数据透视表、VBA编程和自定义公式。提升效率的技巧包括善用快捷键、自动化重复任务、巧用公式与函数(如SUM和VLOOKUP)、利用数据透视表分析数据、设置条件格式、建立数据库连接、编写自定义函数、创建数据图表、使用模板和进行分组汇总。这些方法能有效提升数据分析和处理能力,优化工作效率。
110 2
|
17天前
|
JavaScript 前端开发 BI
原生html—摆脱ps、excel 在线绘制财务表格加水印(html绘制表格js加水印)
原生html—摆脱ps、excel 在线绘制财务表格加水印(html绘制表格js加水印)
20 1
|
1月前
|
easyexcel
【EasyExcel】第二篇:导出excel文件,导出多个sheet工作空间
【EasyExcel】第二篇:导出excel文件,导出多个sheet工作空间
|
2月前
|
NoSQL 关系型数据库 MySQL
多人同时导出 Excel 干崩服务器?怎样实现一个简单排队导出功能!
业务诉求:考虑到数据库数据日渐增多,导出会有全量数据的导出,多人同时导出可以会对服务性能造成影响,导出涉及到mysql查询的io操作,还涉及文件输入、输出流的io操作,所以对服务器的性能会影响的比较大;结合以上原因,对导出操作进行排队; 刚开始拿到这个需求,第一时间想到就是需要维护一个FIFO先进先出的队列,给定队列一个固定size,在队列里面的人进行排队进行数据导出,导出完成后立马出队列,下一个排队的人进行操作;还考虑到异步,可能还需要建个文件导出表,主要记录文件的导出情况,文件的存放地址,用户根据文件列表情况下载导出文件。
多人同时导出 Excel 干崩服务器?怎样实现一个简单排队导出功能!
|
1月前
|
JavaScript 前端开发
【导出Excel】Vue实现导出下载Excel文件(blob文件流)--亲测可用
【导出Excel】Vue实现导出下载Excel文件(blob文件流)--亲测可用
【导出Excel】Vue实现导出下载Excel文件(blob文件流)--亲测可用
|
1天前
|
JavaScript
vue导出excel无法打开问题
vue导出excel无法打开问题
|
2天前
|
Java
java导出复杂excel
java导出复杂excel