【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

相关文章
|
14天前
|
Java API Apache
Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
【10月更文挑战第29天】Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
67 5
|
8天前
|
SQL 数据可视化 数据挖掘
想让Excel表格设计更美观?试试这几款好用工具!
Excel表格设计在项目管理和数据分析中至关重要。本文推荐四款辅助工具:板栗看板、Excel自动图表助手、Think-Cell Chart 和 Power BI,分别在任务管理、图表生成、数据可视化等方面表现突出,帮助你设计出更专业、美观的表格。
24 2
|
21天前
|
JavaScript 前端开发 数据处理
Vue导出el-table表格为Excel文件的两种方式
Vue导出el-table表格为Excel文件的两种方式
|
30天前
|
前端开发 JavaScript API
前端基于XLSX实现数据导出到Excel表格,以及提示“文件已经被损坏,无法打开”的解决方法
前端基于XLSX实现数据导出到Excel表格,以及提示“文件已经被损坏,无法打开”的解决方法
123 0
|
1月前
|
Java Apache
Apache POI java对excel表格进行操作(读、写) 有代码!!!
文章提供了使用Apache POI库在Java中创建和读取Excel文件的详细代码示例,包括写入数据到Excel和从Excel读取数据的方法。
34 0
|
1月前
|
数据采集 存储 JavaScript
自动化数据处理:使用Selenium与Excel打造的数据爬取管道
本文介绍了一种使用Selenium和Excel结合代理IP技术从WIPO品牌数据库(branddb.wipo.int)自动化爬取专利信息的方法。通过Selenium模拟用户操作,处理JavaScript动态加载页面,利用代理IP避免IP封禁,确保数据爬取稳定性和隐私性。爬取的数据将存储在Excel中,便于后续分析。此外,文章还详细介绍了Selenium的基本设置、代理IP配置及使用技巧,并探讨了未来可能采用的更多防反爬策略,以提升爬虫效率和稳定性。
|
3月前
|
关系型数据库 MySQL Shell
不通过navicat工具怎么把查询数据导出到excel表中
不通过navicat工具怎么把查询数据导出到excel表中
44 0
|
1月前
|
数据处理 Python
Python实用记录(十):获取excel数据并通过列表的形式保存为txt文档、xlsx文档、csv文档
这篇文章介绍了如何使用Python读取Excel文件中的数据,处理后将其保存为txt、xlsx和csv格式的文件。
45 3
Python实用记录(十):获取excel数据并通过列表的形式保存为txt文档、xlsx文档、csv文档
|
1月前
|
easyexcel Java UED
SpringBoot中大量数据导出方案:使用EasyExcel并行导出多个excel文件并压缩zip后下载
在SpringBoot环境中,为了优化大量数据的Excel导出体验,可采用异步方式处理。具体做法是将数据拆分后利用`CompletableFuture`与`ThreadPoolTaskExecutor`并行导出,并使用EasyExcel生成多个Excel文件,最终将其压缩成ZIP文件供下载。此方案提升了导出效率,改善了用户体验。代码示例展示了如何实现这一过程,包括多线程处理、模板导出及资源清理等关键步骤。
|
2月前
|
数据采集 存储 数据挖掘
使用Python读取Excel数据
本文介绍了如何使用Python的`pandas`库读取和操作Excel文件。首先,需要安装`pandas`和`openpyxl`库。接着,通过`read_excel`函数读取Excel数据,并展示了读取特定工作表、查看数据以及计算平均值等操作。此外,还介绍了选择特定列、筛选数据和数据清洗等常用操作。`pandas`是一个强大且易用的工具,适用于日常数据处理工作。

热门文章

最新文章