【前端】实现表格简单操作

简言

表格合并基础篇
本篇是在上一章的基础上实现,实现了的功能有添加行、删除行、逆向选区、取消合并功能。
在这里插入图片描述
在这里插入图片描述

功能实现

添加行

添加行分为在上面添加和在下面追加行。
利用 insertAdjacentElement 方法实现,该方法可以实现从前插入元素和从后插入元素。
在这里插入图片描述

删除行

删除当前行就是利用元素remove()方法,从dom树种删除元素。
在这里插入图片描述

逆向选区

逆向选区是指选区从下往上选。
解决思路:记录当前选区时鼠标移动方向,往左上移动则为负,往右下移动则为正。负时在首位插入选中节点,正时从尾部追加选中节点,这样合并只需取第一个选中节点即可。
在这里插入图片描述

取消合并

获取当前元素的rowspan和colspan属性值,然后遍历后面的和下面包含行节点,删除节点的hide类,然后删除当前元素rowspan和colspan属性即可。
在这里插入图片描述

代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>表格合并</title><style>.zsk-table {border-collapse: collapse;border: 1px solid;font-family: inherit;user-select: none;}.zsk-table tr {height: 32px;}.zsk-table td {border: 1px solid;height: 32px;padding: 16px;}.amount {width: 100px;}.show-box {position: absolute;top: -200px;left: -200px;width: 200px;background-color: #eee;}.show-box>div {width: 200px;height: 50px;line-height: 50px;border-bottom: 1px solid #000;}.show-box>div:hover {background-color: #ccc;cursor: pointer;}.select {color: #fff;background-color: #3987cf;}.hide {display: none;}</style>
</head><body><h1>表格合并</h1><table tabindex="1" class="zsk-table"><tr><td>1-1</td><td>1-2</td><td>1-3</td><td>1-4</td><td>1-5</td></tr><tr><td>2-1</td><td>2-2</td><td>2-3</td><td>2-4</td><td>2-5</td></tr><tr><td>3-1</td><td>3-2</td><td>3-3</td><td>3-4</td><td>3-5</td></tr><tr><td>4-1</td><td>4-2</td><td>4-3</td><td>4-4</td><td>4-5</td></tr><tr><td>5-1</td><td>5-2</td><td>5-3</td><td>5-4</td><td>5-5</td></tr></table><!-- 表格右键 --><div class="show-box"><div class="add-down">向下添加一行</div><div class="add-up">向上添加一行</div><div class="delete-cell">删除当前行行</div><div class="merge-cell">合并</div><div class="split-cell">取消合并</div></div><script>const table = document.querySelector('.zsk-table')const showBox = document.querySelector('.show-box')const mergeDiv = document.querySelector('.merge-cell')const addDownDiv = document.querySelector('.add-down')const addUpDiv = document.querySelector('.add-up')const deleteCellDiv = document.querySelector('.delete-cell')const cancelDiv = document.querySelector('.split-cell')const select = {  // 选中单元格value: [[]],range: [[], []] //  [start,end]范围}//  向下添加一行addDownDiv.addEventListener('click', (e) => {let node = select.value[0][0].parentElementlet newNode = node.cloneNode(true)node.insertAdjacentElement('afterend', newNode)clearSelect()})//  向上添加一行addUpDiv.addEventListener('click', (e) => {let node = select.value[0][0].parentElementlet newNode = node.cloneNode(true)node.insertAdjacentElement('beforebegin', newNode)clearSelect()})//  删除当前行deleteCellDiv.addEventListener('click', () => {let node = select.value[0][0].parentElementnode.remove()clearSelect()})//  取消合并cancelDiv.addEventListener('click', () => {let node = select.value[0][0]let rowspan = node.getAttribute('rowspan')let colspan = node.getAttribute('colspan')if (!colspan || !rowspan) returncolspan = Number.parseInt(colspan)rowspan = Number.parseInt(rowspan)let index = getChildIndex(node)let nextNode = nodefor (let i = 0; i < rowspan; i++) {let col = colspanlet temp = nextNodewhile (col--) {temp.classList.remove('hide')temp = temp.nextElementSibling}nextNode = getRowXElement(nextNode, 1)}node.removeAttribute('colspan')node.removeAttribute('rowspan')})//  合并命令mergeDiv.addEventListener('click', () => {if (select.value.length === 0) return//  默认是正向选中,即结尾点比开始点的x和y都大select.value.forEach((item, i) => {item.forEach((v, k) => {if (i === 0 && k === 0) {v.setAttribute('colspan', item.length || '1')v.setAttribute('rowspan', select.value.length || '1')} else {v.classList.add('hide')}})})clearSelect()})//  右键table.addEventListener('click', (e) => {e.target.focus()})table.addEventListener("contextmenu", (e) => {e.preventDefault()showBox.style.left = e.clientX + 'px'showBox.style.top = e.clientY + 'px'if (!select.value[0][0]) {select.value[0][0] = e.target}})table.addEventListener('blur', (e) => {setTimeout(() => {showBox.style.left = -1000 + 'px'showBox.style.top = -1000 + 'px'}, 150)})/***  选中逻辑* **/selectLogic(table, select)function selectLogic(table, select) {let lastEnd = [0, 0] // 最后选中的单元格位置let lastInfo = [0, 0]  //  最后选中单元格的宽高let endUp = [0, 0]let startRange = [0.0]let endRange = [0, 0]let run = false//  按下let timer = 0table.addEventListener('mousedown', (e) => {if (timer !== 0) {clearTimeout(timer)timer = 0}timer = setTimeout(() => {//  先清空clearSelect()run = truestartRange = [e.clientX - e.offsetX, e.clientY - e.offsetY]lastEnd = [startRange[0], startRange[1]]lastInfo = [e.target.offsetWidth, e.target.offsetHeight]e.target.classList.add('select')if (e.target.tagName === 'TD') {select.value[0].push(e.target)select.range[0] = startRangeselect.range[1] = [startRange[0] + e.target.offsetWidth, startRange[1] + e.target.offsetHeight]}}, 200)})//  移动table.addEventListener('mousemove', (e) => {if (run) {end = [e.clientX, e.clientY]//  计算范围 然后 判断是否修改选中dom数组let x = end[0] - lastEnd[0]let y = end[1] - lastEnd[1]let xDirection = end[0] - startRange[0] > 0 ? 1 : -1  //  x方向let yDirection = end[1] - startRange[1] > 0 ? 1 : -1  //  y方向console.log(`x: ${x} y: ${y}  方向x:${xDirection}  方向y:${yDirection}`);if ((xDirection === 1 && x >= lastInfo[0]) || (xDirection === -1 && x <= 0)) {console.log('横向超出,x扩展');//  更新选取范围 xif (xDirection === 1) {lastEnd = [select.range[1][0], lastEnd[1]]lastInfo = [e.target.offsetWidth, lastInfo[1]]select.range[1] = [select.range[1][0] + e.target.offsetWidth, select.range[1][1]]} else {select.range[0] = [select.range[0][0] - e.target.offsetWidth, select.range[0][1]]lastEnd = [select.range[0][0], lastEnd[1]]lastInfo = [e.target.offsetWidth, lastInfo[1]]}//  每行横向添加一行for (let i = 0; i < select.value.length; i++) {//  查找最后一个节点元相邻td元素console.log(select.value[i]);if (xDirection === 1) {let el = getElement(select.value[i][select.value[i].length - 1], xDirection)select.value[i].push(el)} else {let el = getElement(select.value[i][0], xDirection)select.value[i].unshift(el)}}} else if ((xDirection === 1 && x <= 0) || (xDirection === -1 && x >= lastInfo[0])) {if (select.value[0].length <= 1) returnconsole.log(select.value[0].length, '当前个数');if (xDirection === 1) {select.range[1] = [lastEnd[0], select.range[1][1]]lastEnd = [lastEnd[0] - e.target.offsetWidth, lastEnd[1]]lastInfo = [e.target.offsetWidth, lastInfo[1]]} else {select.range[0] = [lastEnd[0] + e.target.offsetWidth, select.range[0][1]]lastEnd = [select.range[0][0], lastEnd[1]]lastInfo = [e.target.offsetWidth, lastInfo[1]]}//  减去每行的最后一个for (let i = 0; i < select.value.length; i++) {if (select.value[i].length > 0) {if (xDirection === 1) {select.value[i][select.value[i].length - 1].classList.remove('select')select.value[i].pop()} else {select.value[i][0].classList.remove('select')select.value[i].shift()}}}}if ((yDirection === 1 && y > lastInfo[1]) || (yDirection === -1 && y <= 0)) {console.log('纵向超出,y扩展', select.value[0].length);if (yDirection === 1) {lastEnd = [lastEnd[0], select.range[1][1]]lastInfo = [lastInfo[0], e.target.offsetHeight]select.range[1] = [select.range[1][0], select.range[1][1] + e.target.offsetHeight]} else {select.range[0] = [select.range[0][0], select.range[0][1] - e.target.offsetHeight]lastEnd = [lastEnd[0], select.range[0][1]]lastInfo = [lastInfo[0], e.target.offsetHeight]}const lastRow = []for (let k = 0; k < select.value[0].length; k++) {let el = yDirection === 1 ? select.value[select.value.length - 1][k] : select.value[0][k]lastRow.push(getRowXElement(el, yDirection))}if (yDirection === 1) {select.value.push(lastRow)} else {select.value.unshift(lastRow)}//  更新选区范围} else if ((yDirection === 1 && y <= 0) || (yDirection === -1 && y >= lastInfo[1])) {if (select.value.length < 1) returnif (yDirection === 1) {select.range[1] = [select.range[1][0], lastEnd[1]]lastEnd = [lastEnd[0], lastEnd[1] - e.target.offsetHeight]lastInfo = [lastInfo[0], e.target.offsetHeight]} else {select.range[0] = [select.range[0][0], lastEnd[1] + e.target.offsetHeight]lastEnd = [lastEnd[0], lastEnd[1] + e.target.offsetHeight]lastInfo = [lastInfo[0], e.target.offsetHeight]}//  去掉最后一行的classif (yDirection === 1) {select.value[select.value.length - 1].forEach(el => {el.classList.remove('select')})select.value.pop()} else {select.value[0].forEach(el => {el.classList.remove('select')})select.value.shift()}}//  选中元素添加classfor (let i = 0; i < select.value.length; i++) {for (let k = 0; k < select.value[i].length; k++) {select.value[i][k] && select.value[i][k].classList.add('select')}}}})//  抬起table.addEventListener('mouseup', (e) => {run = falseif (timer !== 0) {clearTimeout(timer)timer = 0}console.log(select.value);})}/*获取下一行当前横坐标相同位置元素*/function getRowXElement(currentElement, direction = 1) {if (!currentElement.parentElement.nextElementSibling && direction == 1 || !currentElement.parentElement.previousElementSibling && direction !== 1) return nulllet nextElement = direction === 1 ? currentElement.parentElement.nextElementSibling.firstElementChild : currentElement.parentElement.previousElementSibling.firstElementChild;let childIndex = getChildIndex(currentElement)  //  获取当前元素在父元素中的索引if (childIndex !== -1) {return nextElement.parentElement.children[childIndex]} else {let currentLeft = currentElement.offsetLeft;let nextElementLeft = nextElement.offsetLeft;while (nextElement !== null && nextElementLeft !== currentLeft) {nextElement = getElement(nextElement, 1);nextElementLeft = nextElement.offsetLeft;}return nextElement;}}/***  获取下一个兄弟元素* direction === 1时默认查找下一个兄弟元素,否则上一个**/function getElement(element, direction = 1) {if (direction === 1) {if (element.nextElementSibling) {return element.nextElementSibling;} else {let parent = element.parentElement;while (parent && parent.nextElementSibling === null) {parent = parent.parentElement;}return parent ? parent.nextElementSibling.firstElementChild : null;}} else {if (element.previousElementSibling) {return element.previousElementSibling;} else {let parent = element.parentElement;while (parent && parent.previousElementSibling === null) {parent = parent.parentElement;}return parent ? parent.previousElementSibling.firstElementChild : null;}}}function clearSelect() {select.value.forEach((item, index) => {item.forEach(v => {v.classList.remove('select')})})Object.assign(select, {value: [[]],range: [[], []] //  [start,end]范围})}/*** 获取子元素的索引*/function getChildIndex(child) {var parent = child.parentNode;for (var i = 0; i < parent.children.length; i++) {if (parent.children[i] === child) {return i;}}return -1; // 如果找不到子元素,则返回-1}</script>
</body></html>

结语

合并没有做多次合并处理。
添加列没有实现。
边界情况未处理。

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

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

相关文章

两个开关控制一盏灯

1.PLC控制电路 1.1双联控制 开关控制灯最为普遍、简单的做法是用一个开关控制一盏灯&#xff0c;除此之外&#xff0c;在某个位置需要用两个开关控制一盏灯&#xff0c;这就是双联控制。 灯的双联控制&#xff0c;在传统上要用到2个单刀双联开关&#xff0c;其电气原理图如下图…

【CTS :testExtensionAvailability】

【CTS】android.hardware.camera2.cts.CameraExtensionCharacteristicsTest#testExtensionAvailability 报错&#xff1a; java.lang.AssertionError: Extensions system property : true does not match with the advertised extensions: false expected: but was: 通过对这…

暗区突围端游海外版预下载教程 暗区突围端游海外版怎么注册 下载

暗区突围端游海外版预下载教程 暗区突围端游海外版怎么注册 下载 想必最近暗区突围PC版本的上线对于热爱这款游戏的玩家们是一件喜事&#xff0c;这款游戏自从手游上线之初就在全世界范围内引起了不小的轰动&#xff0c;作为逃离塔科夫这款游戏的竞品&#xff0c;刚上线时自然…

嵌入式学习——C语言基础——day14

1. 共用体 1.1 定义 union 共用名 { 数据类型1 成员变量1; 数据类型2 成员变量2; 数据类型3 成员变量3; .. }; 1.2 共用体和结构体的区别 1. 结构体每个成员变量空间独立 2. 共用体每个成员变量空间共享 1.3 判断内存大小端 1. 内存大端…

doris 启动be报错

doris版本是1.2.4 java版本是&#xff1a;1.8 刚开始我以为是版本不兼容问题&#xff0c;后面发现思路错了&#xff0c;版本是兼容的&#xff0c;报以下错我的原因是操作系统没有达到安装要求 以下是博主在部署doris x64(avx2)版本中遇到的小bug 在大家使用doris的时候应该…

翻译《The Old New Thing》- Does Windows have a limit of 2000 threads per process?

Does Windows have a limit of 2000 threads per process? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20050729-14/?p34773 Raymond Chen 2005年07月29日 Windows 是否有一个每个进程2000线程的限制&#xff1f; 简要 文章解释了在 W…

javascript中的DOM和BOM

目录 JavaScript中的对象 简介&#xff1a; js对象的基本用法&#xff1a; 创建对象&#xff1a; 访问对象的属性&#xff1a; 设置修改对象的属性&#xff1a; 删除对象的属性&#xff1a; DOM&#xff08;文档对象模型&#xff09; 简介&#xff1a; DOM对象的属性…

探索未来居住新纪元:公寓商场综合楼可视化引领潮流

在繁忙的都市生活中&#xff0c;我们都渴望找到一处既能满足居住需求&#xff0c;又能提供便捷购物体验的理想之地。如今&#xff0c;这一梦想已不再是遥不可及&#xff0c;随着科技的飞速发展&#xff0c;3D可视化技术正逐渐走进我们的生活&#xff0c;为我们带来前所未有的居…

用脚本写一个日期样式的字符

现在想要诸如此类样式的语句&#xff1a;&#xff08;过去三个月的&#xff09; 可以用python脚本写&#xff1a; from datetime import date, timedelta# 获取当前日期 current_date date.today()# 定义过去三个月的时间间隔 three_months_ago current_date - timedelta(da…

第二证券|集合竞价915到920能成交吗?

不能&#xff0c;买卖日9点15分到9点20分是集合竞价申报时刻&#xff0c;还没有发生集合竞价。 在这个时刻段内&#xff0c;投资者能够申报&#xff0c;也能够撤单&#xff0c;但这些挂单并不会立即成交。9:25时&#xff0c;系统会对一切收到的挂单进行一次性促成处理&#xf…

python 12实验

1.导入数据。 2.清洗数据&#xff0c;将缺失值或“NAN”替换为“无”&#xff0c;并将文本数据转换为数值型数据。 3.使用聚类算法&#xff08;如KMeans&#xff09;对数据进行聚类&#xff0c;并计算样本到簇中心的平均距离以确定最佳的簇数量。 4.对数据进行PCA降维&#xff…

酷开科技AI技术支持,酷开系统根据你的喜好量身定制节目

在当今数字化时代&#xff0c;个性化推荐已成为提升消费者体验的关键因素。酷开科技的智慧AI&#xff0c;为消费者提供了精彩的内容推荐服务&#xff0c;更大地丰富了消费者的娱乐生活。 酷开系统中的AI推荐引擎通过学习消费者的观看习惯和偏好&#xff0c;能够快速识别其兴趣…

「51媒体」企业单位新闻稿件考核,怎么发布

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 电力税务企事业单位部门等单位提供了新闻稿件&#xff0c;如何在一些重点媒体进行宣发呢&#xff1a; 精准锁定发布媒体 了解考核要求&#xff1a;仔细阅读宣传任务名单&#xff0c;了解…

Leetcode167两数之和

题目链接&#xff1a; 167两数之和 解题思路: 缩减空间法 // 167 两数之和 缩减搜索空间方法 vector<int> twoSum(vector<int>& numbers, int target) {int i 0;int j numbers.size() - 1;while (i < j){int tmp numbers[i] numbers[j];if (tmp tar…

迈向语音大模型的平权之路

什么样的事情最有价值&#xff1f;难且正确的事情。把 1B 级别的大模型优化到和 200M级别模型相同水平的 RTF 同时 WER 维持代差&#xff0c;难道不是一件该令人亢奋的事情吗&#xff1f; -- 向前看&#xff0c;别回头 在两个月前的年度总结中&#xff0c;WeNet 社区已经开始向…

有哪些岗位适合持有CISAW证书的人去申请?有哪些优势?

持有CISAW证书的人&#xff0c;原来有这么多高薪岗位可以选择&#xff01; CISAW证书是信息安全领域的权威认证&#xff0c;持有该证书的人具备较高的专业能力和水平&#xff0c;可以胜任多种信息安全相关岗位。 马老师&#xff1a;13521730416 信息安全工程师是其中一类&…

【Qt】深入理解QWidget常用控件: enable属性、geometry属性和window frame属性

文章目录 前言&#xff1a;1. 什么是控件2. Qt中QWidget控件的常用属性及元编程QWidget 核心属性enable属性&#xff1a;geometry 属性 :window frame 窗口框架 总结: 前言&#xff1a; 图形化界面的开发常常需要使用各种控件&#xff0c;而Qt作为一个强大的跨平台GUI应用程序…

【iOS逆向与安全】网上gw如何自动登录与签到SM2,SM3,SM4算法加解密

1.下载 app 2.frida 调试 3.抓包查看接口 4.分析加密数据 5.易语言编写代码 1 .开始下载 下载好发现有越狱检测&#xff0c;检测点为&#xff1a; -[AppDelegate isJailBreak]; 于是编写插件xm代码 : %hook AppDelegate- (void)isJailBreak{NSLog("AppDelegate is…

【cpp题解】动态规划之爬楼梯 (70)

目录 前言我的思路思路一思路二 我的代码 前言 今天我来学一学动态规划&#xff0c;大二的时候学算法分析与设计&#xff0c;觉得算法是真难&#xff0c;有些高不可攀。现在大四了&#xff0c;其实今天稍微学了一下&#xff0c;简单的动态规划问题就和递归差不多&#xff0c;没…

linux虚拟机配置环境

1.配置虚拟机 在VMware中安装CentOS7&#xff08;超详细的图文教程&#xff09;_在vmware上安装centos-CSDN博客https://blog.csdn.net/qq_45743985/article/details/121152504 2.固定虚拟机ip地址 Vmware虚拟机Linux配置固定IP地址&#xff08;详细版&#xff09;_虚拟机固…