手把手教你实现一个可编辑的Table

news2024/12/26 22:48:32

手把手教你实现一个可编辑的Table

需求背景

  • 我们最近在做一些商品的备案工作,历史有很多的备案记录,不能很好的利用。所以想做一个提效工具。

  • 备案人员,在网页通过搜索历史知识库的数据,进行备案编辑,他们平时都在使用Excel,所以要求样式和使用体验求尽量向Excel 靠近,当然本篇主要讲table在线编辑的一些实现思路

主要实现功能

  • 在线编辑
  • 支持键盘导航
  • 支持批量复制粘贴

效果

editF.gif

具体实现细节

表格搭建

  • html 采用了 table,thead, tbody,tr,td,th 数据结构采用了二维数组

template

 <table>
    <thead>
      <tr>
        <th v-for="(th, i) in headers" :key="i" @click="go">{{ th }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(row, rowIndex) in rowsList" :key="rowIndex">
        <td v-for="(cellValue, columnIndex) in row" :key="columnIndex">
          <div>{{ cellValue }}</div>
        </td>
      </tr>
    </tbody>
  </table>

数据结构

const headers = ref(["Name", "Age", "Gender"]);
const rowsList = ref([
  ["Alice", 20, "Female"],
  ["Bob", 30, "Male"]
]);

table可编辑

  1. api选择

可编辑提供了一些方式,input,textarea,还有h5新增的contentEditable。最终由于是多行文本编辑,并且要提供给用户一些换行等功能,所以最后选择了contentEditable

  1. 交互

交互想了两种,一种是提供一个按钮,用户点击按钮,当前行变为可编辑。

按钮编辑.gif

另一种是直接可以输入

按钮.gif

但是在我这个场景下,主要就是操作便捷,按钮无疑是增加了操作的复杂度。可是使用直接输入,这么多输入框,都是可编辑状态,性能堪忧啊。但是最后采用了直接输入,并且动态设置可编辑,只有鼠标聚焦的时候,才是可编辑

实现逻辑

  • 给每一个单元格设置一个ref,(方便做聚焦)
  • 通过点击事件,获取当前单元格的index,做contenteditable的动态编辑
  • 给可编辑的单元格做聚焦

template

++
:contenteditable="currentRow === rowIndex && currentCol === columnIndex"
:ref="setRef(rowIndex, columnIndex)"
@click="focusCell(rowIndex, columnIndex, $event)"
++

steup

++
// 获取每个单元格的ref
const setRef = (rowIndex, columnIndex) => {
  return (el) => {
    if (!refs[rowIndex]) {
      refs[rowIndex] = {};
    }
    refs[rowIndex][columnIndex] = el;
    console.log("el", el);
  };
};
const selectedRange = reactive({ start: null, end: null });
// 单击事件,获取焦点并且设置index
const focusCell = (rowIndex, columnIndex, $event) => {
    selectedRange.start = { rowIndex, columnIndex };
    selectedRange.end = { rowIndex, columnIndex };
    currentRow.value = rowIndex;
    currentCol.value = columnIndex;
  setTimeout(() => {
    refs[rowIndex][columnIndex].focus();
  });
}; 
++

🌟 到这里,可编辑就做完了,但是编辑会有一个问题,contenteditable 输入的是元素本身的值,并没有和vue 做响应式数据,那赋值就可以了

template

++
@blur="setCell(rowIndex, columnIndex, $event.target.textContent)"
++

steup

++
// 失焦后,设置数据,这样的好处是减少频繁的数据更新,不然contenteditable会出现光标不准确的bug
const setCell = (rowIndex, columnIndex, value) => {
  rowsList.value[rowIndex][columnIndex] = value;
};
++

table支持键盘导航

⭐️ 功能描述

可以通过键盘的上下左右键盘,做单元的切换

(其实就是根据键盘事件,获取当前的索引,做可编辑可focus)

template

++
@keydown="handleKeydown(rowIndex, columnIndex, $event)"
++

steup

++
const handleKeydown = (rowIndex, columnIndex, event) => {
  const { key } = event;
  // 双击之后,锁住上下左右键的切换
  if (keyFlag.value) {
    return;
  }
  console.log("event", event);
  switch (key) {
    case "ArrowUp":
      if (rowIndex > 0) {
        keyDownFunc(rowIndex - 1, columnIndex);
      }
      break;
    case "ArrowDown":
      if (rowIndex < rowsList.value.length - 1) {
        keyDownFunc(rowIndex + 1, columnIndex);
      }
      break;
    case "ArrowLeft":
      if (columnIndex > 0) {
        keyDownFunc(rowIndex, columnIndex - 1);
      }
      break;
    case "ArrowRight":
      if (columnIndex < headers.value.length - 1) {
        keyDownFunc(rowIndex, columnIndex + 1);
      }
      break;
  }
};
// 上下左右键,去设置index,并且使当前单元格聚焦
const keyDownFunc = (rowIndex, columnIndex) => {
  currentRow.value = rowIndex;
  currentCol.value = columnIndex;
  setTimeout(() => {
    refs[rowIndex][columnIndex].focus();
    console.log("refs[rowIndex][columnIndex]", refs[rowIndex][columnIndex]);
    const el = refs[rowIndex][columnIndex];
    // 此方式只针对于 input ,textarea
    // const len = refs[rowIndex][columnIndex].innerText.length

    // refs[rowIndex][columnIndex].setSelectionRange(0, len); // 设置光标到最后
    const range = document.createRange();
    range.selectNodeContents(el);
    range.collapse(false);
    const sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
  });
};
++

但是会出现一个问题,就是永远只会上线左右,无法仅在当前单元格编辑。所以设计了双击解除编辑状态

template

++
@dblclick="handleDoubleClick"
++

steup

++
const handleDoubleClick = () => {
  keyFlag.value = true;

  selectedRange.start = null;
  selectedRange.end = null;
};
++

table支持批量复制粘贴

⭐️ 功能描述

CV 复制粘贴

这个有点东西,你得先体验一下Excel里面的复制粘贴
比如我先复制一个Excel

image.png

粘贴出来

image.png
你会发现他是带格式的。

但是我们自己实现的table,复制下来没格式,这个时候,我们就需要自己去处理换行和空格

  1. 复制

    template

++
@copy="handleCopy($event)"
++

steup

++
const focusCell = (rowIndex, columnIndex, $event) => {
  console.log("$event", $event);
  if ($event.shiftKey) { // shift
    selectedRange.end = { rowIndex, columnIndex };
  }

function handleCopy($event) {
  currentRow.value = null;
   // 自己处理格式,空格\t ,换行\n
  const selectedValues = getSelectedValues();
  if (selectedValues) {
    ElMessage({
      message: "复制成功",
      type: "success"
    });
  }

  $event.clipboardData.setData("text", selectedValues);
  $event.preventDefault();
}
function getSelectedValues() {
  const { rowIndex: startRow, columnIndex: startCol } = selectedRange.start;
  const { rowIndex: endRow, columnIndex: endCol } = selectedRange.end;
  let rowData = "";
  for (let i = startRow; i <= endRow; i++) {
    for (let j = startCol; j <= endCol; j++) {
      rowData += rowsList.value[i][j] + "\t";
    }
    rowData += "\n";
  }

  return rowData;
}
++

  1. 粘贴

    template

++
@paste="handlePaste($event)"
++

steup

++
function handlePaste($event) {
  const clipboardData = $event.clipboardData;
  if (clipboardData) {
    ElMessage({
      message: "粘贴成功",
      type: "success"
    });
  }
  const pastedData = clipboardData.getData("text");

  setSelectedValues(pastedData);
  $event.preventDefault();
}
function setSelectedValues(pastedData) {
  console.log("pastedData", pastedData);
  // 如果复制数据的大小与所选单元格不匹配,则退出
  const { rowIndex: startRow, columnIndex: startCol } = selectedRange.start;
  const { rowIndex: endRow, columnIndex: endCol } = selectedRange.end;
  const rows = pastedData
    .trim()
    .split("\n")
    .map((row) => row.split("\t"));
  for (let i = 0; i < rows.length && startRow + i <= endRow; i++) {
    const row = rows[i];
    for (let j = 0; j < row.length && startCol + j <= endCol; j++) {
      rowsList.value[startRow + i][startCol + j] = row[j];
    }
  }
}
++

  1. shift 复制粘贴的样式

就是一个函数,根据单元格的范围,是就return true。否就返回false

++
:class="{
         selected: isSelected(rowIndex,columnIndex)
       }"
++

steup

++
const isSelected = (rowIndex, columnIndex) => {
  const { start, end } = selectedRange;

  if (!start || !end) return false;
  if (
    rowIndex >= start.rowIndex &&
    rowIndex <= end.rowIndex &&
    columnIndex >= start.columnIndex &&
    columnIndex <= end.columnIndex
  ) {
    return true;
  }
  if (
    rowIndex >= end.rowIndex &&
    rowIndex <= start.rowIndex &&
    columnIndex >= end.columnIndex &&
    columnIndex <= start.columnIndex
  ) {
    return true;
  }

  return false;
};
++

市面上也有很多其他的可编辑table
比如ant https://procomponents.ant.design/components/editable-table

vxe https://vxetable.cn/#/table/edit/popupForm

但是都不满足需求,所以自己造了一个轮子。
下面是github地址,欢迎start

https://gitee.com/Big_Cat-AK-47/edit-table.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/954072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

亚马逊美国站上半年“日本商店”畅销品类了解一下吧!

近日&#xff0c;亚马逊美国站公布了2023年上半年“日本商店”&#xff08;JAPAN STORE&#xff09;的热门品类以及各品类销量排名前三的商品。据了解&#xff0c;亚马逊与日本贸易振兴机构 (JETRO) 合作&#xff0c;于2021年11月推出“日本商店”&#xff0c;支持日本卖家出海…

Vue安装过程的困惑解答——nodejs和vue关系、vue的项目结构

文章目录 一、为什么在使用vue前要下载nodejs&#xff1f;二、为什么安装nodejs后就能使用NPM包管理工具&#xff1f;三、为什么是V8引擎并且使用C实现&#xff1f;四、为什么会安装淘宝镜像&#xff1f;五、什么是webpack模板&#xff0c;为什么需要他&#xff1f;六、vue项目…

2023年中国电商销售额将达2.2万亿美元领跑全球

根据Global Data的数据&#xff0c;中国将继续保持在全球电子商务格局中的主导地位。预计中国电子商务市场将增长9.9%&#xff0c;到2023年将增长22亿美元。 2018年至2022年间&#xff0c;中国的在线销售额复合年增长率为11.2%。电商销售额在2022年达到了2万亿美元。预计2023年…

Python 实践之Pandas 时间数据处理方法详解

概要 表格数据中常见的数据类型是数值型&#xff08;包括整数、浮点数&#xff09;和字符型&#xff0c;除了这两种数据&#xff0c;时间类型数据也是常见数据的重要组成部分&#xff0c;同时也是数据分析中极其重要的信息。无论是金融领域的股票交易数据&#xff0c;还是企业注…

恒运资本:a股总市值怎么看?

跟着经济开展和金融商场的壮大&#xff0c;投资者越来越重视股市的行情。而对于A股商场来说&#xff0c;总市值是一个非常重要的目标。那么&#xff0c;A股总市值怎么看&#xff1f;以下是本文的详细剖析。 一、A股总市值界说与核算方法 首要需要了解A股总市值的界说和核算方法…

深度了解BSP452芯片特性与应用|百能云芯

在现代科技领域&#xff0c;芯片是推动各种电子设备发展的核心。BSP452作为一款重要的芯片&#xff0c;具有广泛的应用领域和独特的特性&#xff0c;为多个领域的创新提供了支持。百能云芯将深入探讨BSP452芯片的特点、应用领域以及未来发展前景。 BSP452芯片是一款N沟道MOS场效…

详细对比超融合服务器硬件平滑升级方案:新建集群 VS 滚动升级

作者&#xff1a;深耕行业的金融团队 刘慧敏 在企业 IT 基础架构运维中&#xff0c;经常会遇到以下问题&#xff0c;从而需要对服务器硬件进行更换或升级&#xff1a; 服务器达到维护期限&#xff1a;通常在金融行业中&#xff0c;生产环境的服务器维护期限在 5 年左右&#…

【进程间通信】管道

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

基于RabbitMQ的模拟消息队列需求文档

文章目录 一、项目背景二、需求分析1.核心概念2.BrokerServer核心组件3.核心API4.交换机类型5.持久化6.网络通信7.消息应答 三、消息队列模块划分 一、项目背景 什么是消息队列&#xff1f; 消息队列就是&#xff0c;基于阻塞队列&#xff0c;封装成一个独立的服务器程序&#…

springboot实战(一)之项目搭建

环境准备 ideajdk1.8springboot版本 2.7.15 项目开始 1.打开idea&#xff0c;点击new project 2.选择spring initillizr 核对&#xff1a;Server Url是否是&#xff1a;start.spring.io&#xff0c;然后根据自己依次设置项目名称、存储位置和包名&#xff0c;如下&#xff…

洛朗展式求留数方法计算超越函数f(x)=e^(cosx)的定积分

https://math.stackexchange.com/questions/2468863/what-is-the-integral-of-e-cos-x https://tieba.baidu.com/p/6881253594 例子 https://www.zhihu.com/question/441124046/answer/1697123609?utm_id0

《Kubernets证书篇:kubernetes1.24.17证书修改时间限制》

一、背景 Kubernetes 默认的证书有效期只有1年,因此需要每年手动更新一次节点上面的证书,特别麻烦而且更新过程中可能会出现问题,因此我们要对 Kubernetes 的 SSL 证书有效期进行修改,这里将证书的时间限制修改为100年。 环境信息如下: 操作系统内核版本K8S版本Ubuntu 20.…

百度的AI画图和讯飞的AI画图目前就这样了

今天接到百度的短信&#xff0c;说给我一个搜索AI的测试权限&#xff0c;心血来潮&#xff0c;让AI给我做个画&#xff0c;百度和讯飞来做个PK&#xff0c;结果都不敢恭维哈&#xff0c;双方都有待提高&#xff0c;加油&#xff01;

代码解读 FCOS网络(基于mmrotate框架)

文章目录 1. rotated_fcos_head.py1.1 __init__函数1.2 forward_single 函数1.2.1 父类的forward_single 函数&#xff08;anchor_free_head.py&#xff09;1.2.2 _init_layers 函数 1.3 loss 函数1.3.1 get_targets 函数1.3.1.1 _get_target_single 函数 1.3.2 prior_generato…

vr城乡规划建筑设计元宇宙平台提高工作协同效率

随着科技的发展&#xff0c;元宇宙正逐渐渗透到各个行业领域中&#xff0c;特别是建筑设计&#xff0c;那么在建筑设计中&#xff0c;利用元宇宙平台有哪些特点及优势呢? 面对建筑行业日益突出的高消耗、高风险、高投入、低利润的问题&#xff0c;如何提升企业经营管理水平&am…

Python Pyecharts 制图

基本图表 - pyecharts - A Python Echarts Plotting Library built with love. from pyecharts import options as opts from pyecharts.charts import Pie from pyecharts.faker import Fakerc (Pie().add("",[list(z) for z in zip(["7室1厅", "5…

基于KNN算法的鸢尾花种类预测

K近邻法算法思想 K近邻法&#xff08;K-Nearest Neighbor&#xff0c;KNN&#xff09;是一种基本的分类和回归方法&#xff0c;是监督学习方法里的一种常用方法。K近邻算法用一句通俗的古语来说就是&#xff1a;“物以类聚&#xff0c;人以群分”。有人说看一个人什么样&#…

【Unity3D游戏魔坦之争】游戏结束流程封装实现【七】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

虹科分享 | 温度边缘效应对冻干成品含水量的影响(下)——优化和总结

上一篇文章中介绍到借助虹科Ellab的温度记录仪观察到由于冻干机壁面温度的影响&#xff0c;形成的边缘效应导致同一隔板的不同区域冻干饼块的干燥程度不均匀&#xff0c;含水量不同。 06 初次试验结果&#xff1a; 二次干燥中的产品温度显示&#xff1a; 放置在搁板中间的产品…

Linux内核源码分析 (A)常见内核面试题

Linux内核源码分析 (A)常见内核面试题 文章目录 Linux内核源码分析 (A)常见内核面试题调用 schedule() 进行进程切换的方式有几种CFS调度器vruntime的计算方式 网站收集面试题集合1 调用 schedule() 进行进程切换的方式有几种 系统调用 do_fork()&#xff1a;copy_process()定…