【前端】表格合并如何实现?

ZSK6 2024-09-07 09:03:02 阅读 66

简言

介绍实现表格合并的一种方法。

表格合并

表格合并操作是一个比较复杂的操作,它主要分为以下步骤:

获取选中区域选择合并显示的单元格实现合并操作。

我们就逐一实现这三步,最后实现一个较完整的合并操作。(不考虑边界情况)

获取选中区域

选中区域这里相对来说比较难,它是第一步,也是最重要的一步,只要选的不对,白搭。

还有就是正常的选区,它可以有以下四种选中方向:

在这里插入图片描述

这里只考虑第3种,其他的可自行实现(利用x和y差值方向)。

另外,还有就是选区取消实现,例如我选中了2-3,2-4,然后我的鼠标又移回2-3区域了,那么2-4就应该取消选中。

思路

这里我选择的是利用鼠标按下、移动、抬起事件来实现长按选中操作,期间记录选中的节点和范围,以及最后选中节点的位置。

代码在示例。

选择合并显示的单元格

要选择合并显示的单元格,首先要判断你怎么选区的(选区方向)。

因为table元素中,一般都是靠前的td元素修改colspan和rowspan属性来执行合并操作。

示例代码 只考虑了 正向选区一种,即默认第一个为靠前td元素

代码在示例。

实现合并操作

合并操作这里主要处理选中区域的单元格,根据选中个数和合并情况来处理合并操作。

示例实现的是右键合并操作

在这里插入图片描述

示例

<code><!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<meta http-equiv="X-UA-Compatible" content="IE=edge">code>

<meta name="viewport" content="width=device-width, initial-scale=1.0">code>

<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">code>

<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>

</table>

<!-- 表格右键 -->

<div class="show-box">code>

<div>向下添加一行</div>

<div>向上添加一行</div>

<div>删除当前行行</div>

<div class="merge-cell">合并</div>code>

</div>

<script>

const table = document.querySelector('.zsk-table')

const showBox = document.querySelector('.show-box')

const mergeDiv = document.querySelector('.merge-cell')

const select = { -- --> // 选中单元格

value: [[]],

range: [[], []] // [start,end]范围

}

// 合并命令

mergeDiv.addEventListener('click', () => {

if (select.value.length === 0) return

console.log(select.range, 'range');

// 默认是正向选中,即结尾点比开始点的x和y都大

select.value.forEach((item, i) => {

item.forEach((v, k) => {

if (i === 0 && k === 0) {

console.log(v, '显示项');

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()

console.log(e.target, '右键', e)

showBox.style.left = e.clientX + 'px'

showBox.style.top = e.clientY + 'px'

})

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 = 0

table.addEventListener('mousedown', (e) => {

if (timer !== 0) {

clearTimeout(timer)

timer = 0

}

timer = setTimeout(() => {

// 先清空

clearSelect()

run = true

startRange = [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] = startRange

select.range[1] = [startRange[0] + e.target.offsetWidth, startRange[1] + e.target.offsetHeight]

}

}, 200)

})

// 移动

table.addEventListener('mousemove', (e) => {

if (run) {

end = [e.clientX, e.clientY]

console.log(`x: ${ end[0] - startRange[0]} y: ${ end[1] - startRange[1]} 范围:${ select.range[1][0] - select.range[0][0]}`);

// 计算范围 然后 判断是否修改选中dom数组

let x = end[0] - lastEnd[0]

let y = end[1] - lastEnd[1]

if (x > lastInfo[0]) {

console.log('横向超出,x扩展');

lastEnd = [select.range[1][0], lastEnd[1]]

lastInfo = [e.target.offsetWidth, lastInfo[1]]

// 每行横向添加一行

for (let i = 0; i < select.value.length; i++) {

// 查找最后一个节点元相邻td元素

console.log(select.value[i]);

let el = getNextElement(select.value[i][select.value[i].length - 1])

select.value[i].push(el)

}

// 更新选取范围 x

select.range[1] = [select.range[1][0] + e.target.offsetWidth, select.range[1][1]]

} else if (x < 0) {

if (select.value[0].length <= 1) return

console.log(select.value[0].length, '当前个数');

select.range[1] = [lastEnd[0], select.range[1][1]]

lastEnd = [lastEnd[0] - e.target.offsetWidth, lastEnd[1]]

lastInfo = [lastInfo[0], e.target.offsetHeight]

// 减去每行的最后一个

for (let i = 0; i < select.value.length; i++) {

if (select.value[i].length > 0) {

select.value[i][select.value[i].length - 1].classList.remove('select')

select.value[i].pop()

}

}

}

if (y > lastInfo[1]) {

console.log('纵向超出,y扩展', select.value[0].length);

lastEnd = [lastEnd[0], select.range[1][1]]

lastInfo = [lastInfo[0], e.target.offsetHeight]

const lastRow = []

for (let k = 0; k < select.value[0].length; k++) {

let el = select.value[select.value.length - 1][k]

lastRow.push(getNextRowXElement(el))

}

select.value.push(lastRow)

// 更新选区范围

select.range[1] = [select.range[1][0], select.range[1][1] + e.target.offsetHeight]

} else if (y < 0) {

if (select.value.length < 1) return

select.range[1] = [select.range[1][0], lastEnd[1]]

lastEnd = [lastEnd[0], lastEnd[1] - e.target.offsetHeight]

lastInfo = [lastInfo[0], e.target.offsetHeight]

// 去掉最后一行的class

select.value[select.value.length - 1].forEach(el => {

el.classList.remove('select')

})

select.value.pop()

}

// 选中元素添加class

for (let i = 0; i < select.value.length; i++) {

for (let k = 0; k < select.value[i].length; k++) {

select.value[i][k].classList.add('select')

}

}

// select.value.push(e.target)

// e.target.classList.add('select')

}

})

// 抬起

table.addEventListener('mouseup', (e) => {

run = false

if (timer !== 0) {

clearTimeout(timer)

timer = 0

}

})

}

/*

获取下一行当前横坐标相同位置元素

*/

function getNextRowXElement(currentElement) {

let nextElement = currentElement.parentElement.nextElementSibling.firstElementChild;

let currentLeft = currentElement.offsetLeft;

let nextElementLeft = nextElement.offsetLeft;

while (nextElement !== null && nextElementLeft !== currentLeft) {

nextElement = getNextElement(nextElement);

nextElementLeft = nextElement.offsetLeft;

}

return nextElement;

}

/**

* 获取下一个兄弟元素

**/

function getNextElement(element) {

if (element.nextElementSibling) {

return element.nextElementSibling;

} else {

return null

let parent = element.parentElement;

while (parent && parent.nextElementSibling === null) {

parent = parent.parentElement;

}

return parent ? parent.nextElementSibling.firstElementChild : null;

}

}

function clearSelect() {

select.value.forEach((item, index) => {

item.forEach(v => {

v.classList.remove('select')

})

})

Object.assign(select, {

value: [[]],

range: [[], []] // [start,end]范围

})

}

</script>

</body>

</html>

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

问题

选中区域方向问题选中节点信息没有处理colspan和rowspan属性,导致无法再次合并。无法再次合并。事件触发较频繁

结语

结束了。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。