npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

lk-use-table-td-select

v1.1.7

Published

实现table下的td选取功能。

Readme

安装

# 首先确保安装了Vue 3
npm install vue@^3.0.0

# 然后安装本库
npm install lk-use-table-td-select

使用

import { useTableTdSelect } from 'lk-use-table-td-select'

具体使用环境与方法

<style lang="scss" scoped>
.lk-table {
  border-collapse: collapse;
  td,
  th {
    border: 1px solid #e2eaf6;
    min-width: 200px;
    min-height: 40px;
    font-size: 20px;
  }
  td {
    cursor: cell;
    user-select: none;
  }
}
.out-box {
  width: 800px;
  height: 800px;
  overflow: auto;
  margin: 50px;
  border: #e2eaf6 1px solid;
  padding: 20px;
  .single_selection {
    position: absolute;
    background: #ffffff00;
    width: 100px;
    height: 100px;
    border-color: rgba(16, 153, 104, 0.3);
    border-width: 0px 100px 100px 0px;
    box-shadow: rgb(16, 153, 104) 0px 0px 0px 2px;
    top: 0;
    left: 0;
    pointer-events: none;
    cursor: cell;
    //box-sizing: content-box;
    // 不可以这么使用,这么使用的话,box-shadow作用就失去了
    .single-dot {
      width: 6px;
      height: 6px;
      position: absolute;
      transform: translate(-3px, -3px);
      background: rgb(153, 71, 16);
      top: 0;
      left: 0;
      pointer-events: none;
    }
  }

  .single_selection_corner {
    width: 6px;
    height: 6px;
    position: absolute;
    transform: translate(-3px, -3px);
    background: rgb(16, 153, 104);
    top: 0;
    left: 0;
    pointer-events: none;
  }
  h1 {
    all: unset;
  }
}
</style>
<template>
  <div>
    <h1>USE</h1>
    <div class="out-box" ref="outBoxRef">
      <div style="position: relative">
        <table class="lk-table" ref="tableRef">
          <thead>
            <tr>
              <th v-for="(header, index) in table_header" :key="index">{{ header }}</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(row, y) in table_data" :key="y">
              <template v-for="(cell, x) in row">
                <td
                  :key="x"
                  align="center"
                  :colspan="cell.colspan || 1"
                  :rowspan="cell.rowspan || 1"
                  v-if="!cell.merged || cell.isMaster"
                >
                  <p>{{ cell.content || cell }}</p>
                </td>
              </template>
            </tr>
          </tbody>
        </table>
        <div class="single_selection" id="et_select_grid"></div>
        <div class="single_selection_corner"></div>
      </div>
    </div>
    <el-button type="primary" @click="mergeCells">合并单元格</el-button>
    <el-button type="primary" @click="splitCells">拆分单元格</el-button>
    <el-button type="primary" @click="clearRect">清除选中区域1</el-button>
  </div>
</template>
<script setup>
const table_header = [
  '培训日期',
  '培训时间',
  '培训岗位',
  '培训主题',
  '培训内容',
  '培训方式',
  '备注'
];

const table_data = ref(Array.from({ length: 77 }, () => Array.from({ length: 7 }, () => '数据')));

// 禁用table元素上面的右键
const table_ref = useTemplateRef('tableRef');
import { useTableTdSelect } from 'lk-use-table-td-select';
const { is_selecting, mousedown_dom_info, rect_select_elements, track_rect, clearRect } =
  useTableTdSelect(table_ref, {
    select_table_tbody: 'tbody',
    et_select_grid: '#et_select_grid',
    single_selection_corner: '.single_selection_corner'
  });

function mergeCells() {
  // 检查是否有选中的单元格
  if (!rect_select_elements.value || rect_select_elements.value.length === 0) {
    console.warn('没有选中的单元格');
    return;
  }

  // 获取选中区域的边界
  const positions = rect_select_elements.value.map((item) => item.position);
  const minX = Math.min(...positions.map((pos) => pos.x));
  const minY = Math.min(...positions.map((pos) => pos.y));
  const maxX = Math.max(...positions.map((pos) => pos.x + pos.width));
  const maxY = Math.max(...positions.map((pos) => pos.y + pos.height));

  // 获取选中区域的行列范围
  const topLeftElement = rect_select_elements.value.find(
    (item) => item.position.x === minX && item.position.y === minY
  );
  const bottomRightElement = rect_select_elements.value.find(
    (item) =>
      item.position.x + item.position.width === maxX &&
      item.position.y + item.position.height === maxY
  );

  if (!topLeftElement || !bottomRightElement) {
    console.error('无法确定选中区域的边界');
    return;
  }

  // 获取左上角和右下角单元格的行列索引
  const topLeftPosition = getCellPosition(topLeftElement.element);
  const bottomRightPosition = getCellPosition(bottomRightElement.element);

  // 检查是否已经有合并的单元格在这个区域内
  let hasMergedCells = false;
  for (let i = topLeftPosition.row; i <= bottomRightPosition.row; i++) {
    for (let j = topLeftPosition.col; j <= bottomRightPosition.col; j++) {
      const cell = table_data.value[i][j];
      if (cell.merged && cell.isMaster) {
        hasMergedCells = true;
        break;
      }
    }
    if (hasMergedCells) break;
  }

  if (hasMergedCells) {
    console.warn('选中区域内已有合并单元格,无法再次合并');
    return;
  }

  // 计算合并的行列数
  const rowSpan = bottomRightPosition.row - topLeftPosition.row + 1;
  const colSpan = bottomRightPosition.col - topLeftPosition.col + 1;

  // 更新table_data数据结构以支持合并
  // 将table_data转换为支持合并的结构
  if (typeof table_data.value[0][0] === 'string') {
    // 如果是原始数据结构,转换为支持合并的结构
    for (let i = 0; i < table_data.value.length; i++) {
      for (let j = 0; j < table_data.value[i].length; j++) {
        table_data.value[i][j] = {
          content: table_data.value[i][j],
          colspan: 1,
          rowspan: 1,
          merged: false,
          isMaster: false
        };
      }
    }
  }

  // 设置左上角单元格为合并的主单元格
  const masterCell = table_data.value[topLeftPosition.row][topLeftPosition.col];
  masterCell.colspan = colSpan;
  masterCell.rowspan = rowSpan;
  masterCell.merged = true;
  masterCell.isMaster = true;

  // 隐藏被合并的其他单元格
  for (let i = topLeftPosition.row; i <= bottomRightPosition.row; i++) {
    for (let j = topLeftPosition.col; j <= bottomRightPosition.col; j++) {
      if (i !== topLeftPosition.row || j !== topLeftPosition.col) {
        // 标记为被合并的单元格
        table_data.value[i][j].merged = true;
        table_data.value[i][j].isMaster = false;
      }
    }
  }

  console.log('单元格合并完成', {
    topLeft: topLeftPosition,
    bottomRight: bottomRightPosition,
    rowSpan,
    colSpan
  });
}

function splitCells() {
  // 检查是否有选中的单元格
  if (!rect_select_elements.value || rect_select_elements.value.length === 0) {
    console.warn('没有选中的单元格');
    return;
  }

  // 用于跟踪已处理的主单元格,避免重复处理
  const processedMasterCells = new Set();

  // 遍历所有选中的单元格
  rect_select_elements.value.forEach((item) => {
    const cellElement = item.element;
    const position = getCellPosition(cellElement);
    console.log('🚀 ~ splitCells ~ position:', position);

    // 检查位置有效性
    if (
      position.row < 0 ||
      position.col < 0 ||
      position.row >= table_data.value.length ||
      position.col >= table_data.value[position.row].length
    ) {
      console.warn('无效的单元格位置:', position);
      return;
    }

    // 获取对应的单元格数据
    const cellData = table_data.value[position.row][position.col];

    // 如果是合并的主单元格,则拆分它
    if (cellData.isMaster && cellData.merged) {
      // 创建唯一标识符以避免重复处理
      const cellKey = `${position.row}-${position.col}`;
      if (processedMasterCells.has(cellKey)) {
        return; // 已经处理过这个主单元格
      }
      processedMasterCells.add(cellKey);

      // 保存原始的colspan和rowspan值用于计算影响范围
      const originalColspan = cellData.colspan || 1;
      const originalRowspan = cellData.rowspan || 1;

      // 重置主单元格属性
      cellData.colspan = 1;
      cellData.rowspan = 1;
      cellData.merged = false;
      cellData.isMaster = false;

      // 计算需要恢复显示的单元格范围
      const startRow = position.row;
      const endRow = Math.min(position.row + originalRowspan, table_data.value.length);
      const startCol = position.col;
      const endCol = Math.min(
        position.col + originalColspan,
        table_data.value[position.row].length
      );

      // 恢复被合并的单元格的显示
      for (let i = startRow; i < endRow; i++) {
        for (let j = startCol; j < endCol; j++) {
          // 跳过主单元格本身
          if (i === startRow && j === startCol) {
            continue;
          }

          // 检查数组边界
          if (i < table_data.value.length && j < table_data.value[i].length) {
            const targetCell = table_data.value[i][j];
            // 只有被标记为合并的单元格才需要恢复
            if (targetCell.merged) {
              targetCell.merged = false;
              targetCell.isMaster = false;
              // 保持原有的colspan和rowspan值(通常应该为1)
              targetCell.colspan = targetCell.colspan || 1;
              targetCell.rowspan = targetCell.rowspan || 1;
            }
          }
        }
      }

      console.log(`拆分单元格完成: 行${position.row}, 列${position.col}`, {
        originalColspan,
        originalRowspan,
        affectedRange: {
          startRow,
          endRow,
          startCol,
          endCol
        }
      });
    }
    // 如果是被合并的单元格(不是主单元格),我们需要找到它的主单元格并拆分
    else if (cellData.merged && !cellData.isMaster) {
      // 查找包含此单元格的主单元格
      const masterCellInfo = findMasterCellForPosition(position.row, position.col);

      if (masterCellInfo) {
        const { masterRow, masterCol } = masterCellInfo;
        const cellKey = `${masterRow}-${masterCol}`;

        // 避免重复处理
        if (processedMasterCells.has(cellKey)) {
          return;
        }
        processedMasterCells.add(cellKey);

        const masterCell = table_data.value[masterRow][masterCol];

        // 保存原始的colspan和rowspan值用于计算影响范围
        const originalColspan = masterCell.colspan || 1;
        const originalRowspan = masterCell.rowspan || 1;

        // 重置主单元格属性
        masterCell.colspan = 1;
        masterCell.rowspan = 1;
        masterCell.merged = false;
        masterCell.isMaster = false;

        // 计算需要恢复显示的单元格范围
        const startRow = masterRow;
        const endRow = Math.min(masterRow + originalRowspan, table_data.value.length);
        const startCol = masterCol;
        const endCol = Math.min(masterCol + originalColspan, table_data.value[masterRow].length);

        // 恢复被合并的单元格的显示
        for (let i = startRow; i < endRow; i++) {
          for (let j = startCol; j < endCol; j++) {
            // 跳过主单元格本身
            if (i === startRow && j === startCol) {
              continue;
            }

            // 检查数组边界
            if (i < table_data.value.length && j < table_data.value[i].length) {
              const targetCell = table_data.value[i][j];
              // 只有被标记为合并的单元格才需要恢复
              if (targetCell.merged) {
                targetCell.merged = false;
                targetCell.isMaster = false;
                // 保持原有的colspan和rowspan值(通常应该为1)
                targetCell.colspan = targetCell.colspan || 1;
                targetCell.rowspan = targetCell.rowspan || 1;
              }
            }
          }
        }

        console.log(`拆分单元格完成: 行${masterRow}, 列${masterCol}`, {
          originalColspan,
          originalRowspan,
          affectedRange: {
            startRow,
            endRow,
            startCol,
            endCol
          }
        });
      }
    }
  });

  console.log('所有选中单元格的拆分操作完成');
}

/**
 * 查找包含指定位置的主单元格
 * @param {number} row - 目标行索引
 * @param {number} col - 目标列索引
 * @returns {Object|null} 主单元格的位置信息 {masterRow, masterCol} 或 null
 */
function findMasterCellForPosition(row, col) {
  // 遍历所有单元格查找主单元格
  for (let i = 0; i < table_data.value.length; i++) {
    for (let j = 0; j < table_data.value[i].length; j++) {
      const cell = table_data.value[i][j];

      // 检查是否为主单元格且处于合并状态
      if (cell.isMaster && cell.merged) {
        const rowspan = cell.rowspan || 1;
        const colspan = cell.colspan || 1;

        // 检查目标位置是否在此主单元格的范围内
        if (row >= i && row < i + rowspan && col >= j && col < j + colspan) {
          return { masterRow: i, masterCol: j };
        }
      }
    }
  }

  return null; // 未找到主单元格
}

function getCellPosition(tdElement) {
  // 通过table_data数据来确认单元格位置

  // 获取DOM元素的行列信息
  const rowElement = tdElement.parentElement;
  const rowIndex = Array.from(rowElement.parentElement.children).indexOf(rowElement);
  const domColIndex = Array.from(rowElement.children).indexOf(tdElement);

  // 检查table_data的有效性
  if (rowIndex >= 0 && rowIndex < table_data.value.length) {
    const rowData = table_data.value[rowIndex];

    // 需要考虑到前面单元格的colspan影响
    // 计算实际的数据列索引
    let visibleCellCount = 0; // 已经遇到的可见单元格数量

    for (let colIndex = 0; colIndex < rowData.length; colIndex++) {
      const cellData = rowData[colIndex];

      // 如果当前单元格是可见的(不是被合并的单元格)
      if (!cellData.merged || cellData.isMaster) {
        // 如果我们已经到达目标DOM列索引
        if (visibleCellCount === domColIndex) {
          return { row: rowIndex, col: colIndex };
        }
        visibleCellCount++;
      }
    }
  }

  // 回退方案:如果无法通过table_data确定,则使用原有的DOM方式
  return { row: rowIndex, col: domColIndex };
}

// 渲染右键选择逻辑
import VNodeContext from './vNodeContext.vue';
import { render } from 'vue';
/**
 * 判断选中的单元格是否可以合并
 * @returns {boolean} 是否可以合并
 */
const isCanMergeCells = computed(() => {
  // 检查是否有选中的单元格
  if (!rect_select_elements.value || rect_select_elements.value.length === 0) {
    return false;
  }

  // 至少需要选中2个单元格才能合并
  if (rect_select_elements.value.length < 2) {
    return false;
  }

  try {
    // 获取选中区域的边界
    const positions = rect_select_elements.value.map((item) => item.position);
    const minX = Math.min(...positions.map((pos) => pos.x));
    const minY = Math.min(...positions.map((pos) => pos.y));
    const maxX = Math.max(...positions.map((pos) => pos.x + pos.width));
    const maxY = Math.max(...positions.map((pos) => pos.y + pos.height));

    // 获取选中区域的行列范围
    const topLeftElement = rect_select_elements.value.find(
      (item) => item.position.x === minX && item.position.y === minY
    );
    const bottomRightElement = rect_select_elements.value.find(
      (item) =>
        item.position.x + item.position.width === maxX &&
        item.position.y + item.position.height === maxY
    );

    if (!topLeftElement || !bottomRightElement) {
      return false;
    }

    // 获取左上角和右下角单元格的行列索引
    const topLeftPosition = getCellPosition(topLeftElement.element);
    const bottomRightPosition = getCellPosition(bottomRightElement.element);

    // 检查是否已经有合并的单元格在这个区域内
    for (let i = topLeftPosition.row; i <= bottomRightPosition.row; i++) {
      for (let j = topLeftPosition.col; j <= bottomRightPosition.col; j++) {
        // 检查数组边界
        if (i < table_data.value.length && j < table_data.value[i].length) {
          const cell = table_data.value[i][j];
          if (cell.merged && cell.isMaster) {
            return false; // 区域内已有合并单元格,无法再次合并
          }
        }
      }
    }

    // 检查选中的单元格是否构成一个连续的矩形区域
    const selectedCellPositions = rect_select_elements.value.map((item) =>
      getCellPosition(item.element)
    );

    // 检查是否所有选中的单元格都在矩形区域内
    const allSelectedInRect = selectedCellPositions.every(
      (pos) =>
        pos.row >= topLeftPosition.row &&
        pos.row <= bottomRightPosition.row &&
        pos.col >= topLeftPosition.col &&
        pos.col <= bottomRightPosition.col
    );

    if (!allSelectedInRect) {
      return false;
    }

    // 检查矩形区域内是否包含所有应该选中的单元格
    const expectedCellCount =
      (bottomRightPosition.row - topLeftPosition.row + 1) *
      (bottomRightPosition.col - topLeftPosition.col + 1);

    if (rect_select_elements.value.length !== expectedCellCount) {
      return false; // 选中的单元格不构成完整的矩形
    }

    return true;
  } catch (error) {
    console.error('判断合并条件时出错:', error);
    return false;
  }
});

/**
 * 判断选中的单元格是否可以拆分
 * @returns {boolean} 是否可以拆分
 */
const isCanSplitCells = computed(() => {
  // 检查是否有选中的单元格
  if (!rect_select_elements.value || rect_select_elements.value.length === 0) {
    return false;
  }

  try {
    // 检查选中的单元格中是否包含已合并的单元格
    const hasMergedCells = rect_select_elements.value.some((item) => {
      const position = getCellPosition(item.element);
      // 检查位置有效性
      if (
        position.row < 0 ||
        position.col < 0 ||
        position.row >= table_data.value.length ||
        position.col >= table_data.value[position.row].length
      ) {
        return false;
      }

      const cellData = table_data.value[position.row][position.col];
      return cellData.merged;
    });

    if (!hasMergedCells) {
      return false; // 没有合并的单元格,无法拆分
    }

    // 检查选中的合并单元格是否可以被拆分
    const processedMasterCells = new Set();

    for (const item of rect_select_elements.value) {
      const position = getCellPosition(item.element);

      // 检查位置有效性
      if (
        position.row < 0 ||
        position.col < 0 ||
        position.row >= table_data.value.length ||
        position.col >= table_data.value[position.row].length
      ) {
        continue;
      }

      const cellData = table_data.value[position.row][position.col];

      // 如果是合并的主单元格
      if (cellData.isMaster && cellData.merged) {
        const cellKey = `${position.row}-${position.col}`;
        if (!processedMasterCells.has(cellKey)) {
          processedMasterCells.add(cellKey);
          // 主单元格可以直接拆分
          return true;
        }
      }
      // 如果是被合并的单元格(不是主单元格)
      else if (cellData.merged && !cellData.isMaster) {
        // 查找包含此单元格的主单元格
        const masterCellInfo = findMasterCellForPosition(position.row, position.col);
        if (masterCellInfo) {
          const { masterRow, masterCol } = masterCellInfo;
          const cellKey = `${masterRow}-${masterCol}`;
          if (!processedMasterCells.has(cellKey)) {
            processedMasterCells.add(cellKey);
            // 找到对应的主单元格,可以拆分
            return true;
          }
        }
      }
    }

    return processedMasterCells.size > 0;
  } catch (error) {
    console.error('判断拆分条件时出错:', error);
    return false;
  }
});
const outBoxRef = useTemplateRef('outBoxRef');

onMounted(() => {
  document.addEventListener('contextmenu', (e) => {
    // 检查是否在表格区域内
    if (!table_ref.value.contains(e.target)) return;

    e.preventDefault();

    // 获取outBoxRef的滚动数据
    const scrollLeft = outBoxRef.value.scrollLeft;
    const scrollTop = outBoxRef.value.scrollTop;

    // 使用outBoxRef的getBoundingClientRect()数据进行位置校验
    const outBoxRect = outBoxRef.value.getBoundingClientRect();

    // 获取选择框的边界数据
    const selectionBoxRect = track_rect.value;

    // 检查是否在选择框内 - 基于outBoxRef的位置数据进行校验
    let isInSelectionBox = false;
    if (selectionBoxRect && selectionBoxRect.length > 0) {
      // 获取选择框的四个角点
      const topLeft = selectionBoxRect.find((item) => item.corner === 'top-left');
      const topRight = selectionBoxRect.find((item) => item.corner === 'top-right');
      const bottomLeft = selectionBoxRect.find((item) => item.corner === 'bottom-left');
      const bottomRight = selectionBoxRect.find((item) => item.corner === 'bottom-right');

      if (topLeft && topRight && bottomLeft && bottomRight) {
        // 基于outBoxRef的位置计算选择框的实际位置
        const selectionBoxX = outBoxRect.left + topLeft.x - scrollLeft;
        const selectionBoxY = outBoxRect.top + topLeft.y - scrollTop;
        const selectionBoxWidth = topRight.x - topLeft.x;
        const selectionBoxHeight = bottomLeft.y - topLeft.y;

        // 检查鼠标是否在选择框内(基于outBoxRef的绝对位置)
        isInSelectionBox =
          e.clientX >= selectionBoxX &&
          e.clientX <= selectionBoxX + selectionBoxWidth &&
          e.clientY >= selectionBoxY &&
          e.clientY <= selectionBoxY + selectionBoxHeight;
        if (isInSelectionBox) {
          const vNode = h(VNodeContext, {
            isShow: true,
            style: {
              top: `${e.clientY}px`,
              left: `${e.clientX}px`
            },
            onMergeCells: () => {
              if (isCanMergeCells.value) {
                mergeCells();
                render(h(VNodeContext, { isShow: false }), document.body);
              }
            },
            onSplitCells: () => {
              if (isCanSplitCells.value) {
                splitCells();
                render(h(VNodeContext, { isShow: false }), document.body);
              }
            },
            isCanMergeCells: isCanMergeCells.value,
            isCanSplitCells: isCanSplitCells.value
          });
          render(vNode, document.body);
        }
      }
    }
  });

  document.addEventListener('click', (e) => {
    if (table_ref.value.contains(e.target)) {
      const vNode = h(VNodeContext);
      render(vNode, document.body);
    }
  });

  const tbody_target = table_ref.value.querySelector('tbody');

  tbody_target.addEventListener('mousemove', (event) => {
    if (!is_selecting.value) return;
    // 自动滚动功能 - 当鼠标靠近容器边界时
    handleAutoScroll(event, '.out-box', 100, 60);
  });
});

/**
 * 处理容器自动滚动功能 - 当鼠标靠近容器边界时自动滚动
 * @param {MouseEvent} event - 鼠标移动事件对象
 * @param {string|HTMLElement} rectScrollDom - 滚动容器的选择器或DOM元素
 * @param {number} scrollThreshold - 触发滚动的边界距离(像素)
 * @param {number} scrollSpeed - 滚动速度(像素)
 */
function handleAutoScroll(
  event,
  rectScrollDom = '.out-box',
  scrollThreshold = 100,
  scrollSpeed = 100
) {
  // 自动滚动功能 - 当鼠标靠近容器边界时
  const outBox =
    typeof rectScrollDom === 'string' ? document.querySelector(rectScrollDom) : rectScrollDom;

  if (!outBox) return;

  const rect = outBox.getBoundingClientRect();

  const mouseX = event.clientX;
  const mouseY = event.clientY;

  let scrollX = 0;
  let scrollY = 0;

  // 检测左右边界
  if (mouseX < rect.left + scrollThreshold) {
    scrollX = -scrollSpeed; // 向左滚动
  } else if (mouseX > rect.right - scrollThreshold) {
    scrollX = scrollSpeed; // 向右滚动
  }

  // 检测上下边界
  if (mouseY < rect.top + scrollThreshold) {
    scrollY = -scrollSpeed; // 向上滚动
  } else if (mouseY > rect.bottom - scrollThreshold) {
    scrollY = scrollSpeed; // 向下滚动
  }

  // 执行滚动
  if (scrollX !== 0 || scrollY !== 0) {
    outBox.scrollBy({
      left: scrollX,
      top: scrollY,
      behavior: 'smooth'
    });
  }
}
</script>

更新信息

1.0.0

  • 版本发布

1.0.1

  • 模块环境中使用问题

1.1.0

  • 选择框选中数组问题解决
  • 重新绘制选择框数据

1.1.1

  • 重写calculateUnionRectangle工具函数
  • 新增generateCornerPoints函数
  • 解决选择框选中的节点尾部不包含问题:1.1.0问题

1.1.2

  • 忘记构建。。。。。

1.1.3

  • 添加mouseMove事件监听,使其更加美观。并使用requestAnimationFrame减少事件触发。

1.1.4

  • 限制只能左键点击选中功能

1.1.5

  • 优化选择框滑到选择区域外逻辑,使用debounce校验,1s钟后回来可以继续操作。
  • 限制鼠标移动逻辑,只有左键按住移动才可以绘制矩阵。

1.1.6

  • handleMouseUp方法优化

1.1.7

  • 暴露出来clearRect清空选择框,为了合并单元格和拆分单元格时候使用。