原生JS模拟Excel表格单元格合并,拆分,删除指定列示例

原生JS模拟Excel表格单元格合并,拆分,删除指定列示例,效果如下

原生JS模拟Excel表格单元格合并,拆分,删除指定列示例

<!doctype html>
<style>
    table {
        border-collapse: collapse
    }
        table td {
            border: solid 1px #ccc;
            padding: 10px 20px;
            word-break: break-all;
        }

    .cannotselect {
        -moz-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;
        -khtml-user-select: none;
        user-select: none;
    }

    td.selected {
        background: #0094ff;
        color: #fff
    }
</style>
<table border="1" id="tb1">
    <tr>
        <td colspan="2" id="t1">0-0,0-1</td>
        <td id="t2" rowspan="2">0-2<br />1-2</td>
        <td id="t3">0-3</td>
        <td id="t4">0-4</td>
    </tr>
    <tr>
        <td id="t5">1-0</td>
        <td id="t6">1-1</td>
        <td id="t7" colspan="2">1-3,1-4</td>
    </tr>
    <tr>
        <td rowspan="2" id="t8">2-0<br />3-0</td>
        <td id="t9">2-1</td>
        <td colspan="2" id="t10">2-2,2-3</td>
        <td id="t11">2-4</td>
    </tr>
    <tr>
        <td id="t12">3-1</td>
        <td colspan="3" id="t13">3-2,3-3,3-4</td>
    </tr>
</table>
<hr />
<input type="button" onclick="megerCells()" value="合并选中单元格" />
<input type="button" onclick="breakCells()" value="取消合并选中单元格" />
<input type="button" onclick="deleteColumn ()" value="删除指定列" />
<script>
    /**
     * 请保留作者(showbo)信息,有bug不迷路,哈哈~~。 https://www.w3dev.cn/
     */


    //获取单元格原始rowIndex和cellIndex
    function getRC(curTd/*:HTMLTableCellElement*/, totalCell/*:int*/) {
        let tbody = curTd.parentNode.parentNode;
        //从第一行计算总的td数量,最后一行也可以,其他行计算不准确
        let cellIndex = -1;
        let rowIndex = curTd.parentNode.rowIndex;
        if (curTd.parentNode.cells.length == totalCell) {//没有被rowspan,colspan影响到的单元格
            cellIndex = curTd.cellIndex;
        }
        else {
            //被rowspan影响,往上找rowspan的行
            cellIndex = curTd.cellIndex;
            for (let i = rowIndex - 1; i >= 0; i--) {
                for (let td of tbody.rows[i].cells) {
                    if (td.rowSpan > 1) {
                        if (td.parentNode.rowIndex + td.rowSpan > rowIndex && curTd.offsetLeft > td.offsetLeft) {//curTd所在行下标和当前rowspan重合,并且处于curTd前(使用位置来定位)
                            cellIndex += td.colSpan;//加上次单元格colSpan
                        }
                    }
                }
            }
            //同一行中td的colspan合并计算
            for (let i = curTd.cellIndex - 1; i >= 0; i--) {
                cellIndex += curTd.parentNode.cells[i].colSpan - 1;
            }
        }
        return JSON.stringify({ startRowIndex: rowIndex, startCellIndex: cellIndex, endRowIndex: rowIndex + curTd.rowSpan - 1, endCellIndex: cellIndex + curTd.colSpan - 1 });
    }
    //初始表格的原始rowIndex,cellIndex及跨行,跨列信息
    function initTdRc() {
        table.classList.add('cannotselect');
        let firstRow = table.rows[0]; //用第一行计算总的td数量,其他行计算不准确
        totalCell = 0;
        for (let td of firstRow.cells) {
            totalCell += td.colSpan;
        }
        for (let i = 0; i < table.rows.length; i++) {
            for (let j = 0; j < table.rows[i].cells.length; j++) {
                table.rows[i].cells[j].setAttribute('rc', getRC(table.rows[i].cells[j], totalCell));
            }
        }
    }
    function removeAllSelectedClass() {//删除td选中样式
        for (let tr of table.rows) {
            for (let td of tr.cells) {
                td.classList.remove('selected')
            }
        }
    }

    //在范围内td添加选中高亮样式
    function addSelectedClass() {
        for (let tr of table.rows) {
            for (let td of tr.cells) {
                let rc = JSON.parse(td.getAttribute('rc')/* as string*/);
                //在范围内加上高亮样式
                if (rc.startRowIndex >= MMRC.startRowIndex && rc.endRowIndex <= MMRC.endRowIndex && rc.startCellIndex >= MMRC.startCellIndex && rc.endCellIndex <= MMRC.endCellIndex) {
                    td.classList.add('selected')
                }
            }
        }
    }
    function checkMMRC() {//检查选中范围的rowspan和colspan
        let rangeChange = false;
        for (let tr of table.rows) {
            for (let td of tr.cells) {
                let rc = JSON.parse(td.getAttribute('rc')/* as string*/);
                //判断单元格4个顶点是否在范围内
                if (
                    (rc.startRowIndex >= MMRC.startRowIndex && rc.startRowIndex <= MMRC.endRowIndex && rc.startCellIndex >= MMRC.startCellIndex && rc.startCellIndex <= MMRC.endCellIndex) ||//左上
                    (rc.endRowIndex >= MMRC.startRowIndex && rc.endRowIndex <= MMRC.endRowIndex && rc.startCellIndex >= MMRC.startCellIndex && rc.startCellIndex <= MMRC.endCellIndex) ||//左下
                    (rc.startRowIndex >= MMRC.startRowIndex && rc.startRowIndex <= MMRC.endRowIndex && rc.endCellIndex >= MMRC.startCellIndex && rc.endCellIndex <= MMRC.endCellIndex) ||//右上
                    (rc.endRowIndex >= MMRC.startRowIndex && rc.endRowIndex <= MMRC.endRowIndex && rc.endCellIndex >= MMRC.startCellIndex && rc.endCellIndex <= MMRC.endCellIndex) //右下
                ) {//debugger
                    let startRowIndex = Math.min.call(null, MMRC.startRowIndex, rc.startRowIndex);
                    let endRowIndex = Math.max.call(null, MMRC.endRowIndex, rc.endRowIndex);
                    let startCellIndex = Math.min.call(null, MMRC.startCellIndex, rc.startCellIndex);
                    let endCellIndex = Math.max.call(null, MMRC.endCellIndex, rc.endCellIndex);
                    if (MMRC.startRowIndex > startRowIndex) {
                        MMRC.startRowIndex = startRowIndex;
                        rangeChange = true;
                    }
                    if (MMRC.startCellIndex > startCellIndex) {
                        MMRC.startCellIndex = startCellIndex;
                        rangeChange = true;
                    }
                    if (MMRC.endRowIndex < endRowIndex) {
                        MMRC.endRowIndex = endRowIndex;
                        rangeChange = true;
                    }
                    if (MMRC.endCellIndex < endCellIndex) {
                        MMRC.endCellIndex = endCellIndex;
                        rangeChange = true;
                    }
                }
            }
        }
        //范围有变化继续扩展
        if (rangeChange) {
            checkMMRC(table);
        }
    }
    function onMousemove(e/*:any*/) {//鼠标在表格单元格内移动事件
        let o = e.target;//as HTMLTableCellElement
        if (o.tagName == 'TD' && ((endTD != o && startTD != o) || (endTD && startTD == o))) {//不在开始td和结束td移动时再触发检查,优化下
            endTD = o;
            removeAllSelectedClass(table);

            let startRC = JSON.parse(startTD.getAttribute('rc')/* as string*/), endRC = JSON.parse(endTD.getAttribute('rc')/* as string*/);
            //求2个单元格的开始rowIndex,结束rowIndex,开始cellIndex和结束cellIndex
            let startRowIndex = Math.min.call(null, startRC.startRowIndex, endRC.startRowIndex);
            let endRowIndex = Math.max.call(null, startRC.endRowIndex, endRC.endRowIndex);
            let startCellIndex = Math.min.call(null, startRC.startCellIndex, endRC.startCellIndex);
            let endCellIndex = Math.max.call(null, startRC.endCellIndex, endRC.endCellIndex);

            MMRC = { startRowIndex, startCellIndex, endRowIndex, endCellIndex };

            checkMMRC(table);
            addSelectedClass(table);
        }
    }
    function onMouseup() {
        table.removeEventListener('mousemove', onMousemove);
        table.removeEventListener('mouseup', onMouseup);
    }
    function getText() {
        var text = [];
        for (let tr of table.rows) {
            let hit = false;
            for (let td of tr.cells) {
                if (td.classList.contains('selected')) {
                    text.push(td.innerHTML);
                }
            }
        }
        return text.join(',');
    }
    function megerCells(/*:HTMLTableElement*/) {//合并单元格
        if (startTD && endTD && startTD != endTD) {//开始结束td不相同确认合并
            let tds = Array.from(table.querySelectorAll('td.selected')), firstTD = tds[0]
                , html = getText();

            for (let i = 1; i < tds.length; i++)tds[i].parentNode.removeChild(tds[i]);
            firstTD.innerHTML = html;

            //更新合并的第一个单元格的缓存rc数据为所跨列和行
            firstTD.setAttribute('colspan', MMRC.endCellIndex - MMRC.startCellIndex + 1)
            firstTD.setAttribute('rowspan', MMRC.endRowIndex - MMRC.startRowIndex + 1);

            firstTD.setAttribute('rc', JSON.stringify(MMRC));

        }
        removeAllSelectedClass(table);
        MMRC = startTD = endTD = null;
    }
    function getInsertCellIndex(nextTr, offsetRight/*:int*/) {//找到拆分单元格时出现rowspan插入到新行中的单元格下标
        for (let td of nextTr.cells) {
            if (Math.abs(td.offsetLeft - offsetRight) < 2)//注意这里内容宽度会出现小数点,但是用offsetWidth取值是整数有舍入操作,所以要取差值
                return td.cellIndex;
        }
        return 0//找不到说明是在第一列合并的,返回0
    }
    function breakCells() {
        if (MMRC) {
            if (MMRC.startRowIndex == MMRC.endRowIndex && MMRC.startCellIndex == MMRC.endCellIndex) {
                alert('无法拆分!');
                return
            }

            var rows = Array.from(table.rows),
                cells;
            for (let tr of rows) {
                cells = Array.from(tr.cells);//拷贝到数组,而不是直接遍历tr.cells,cells会受cellspan,rowspan影响
                for (let td of cells) {
                    let rc = JSON.parse(td.getAttribute('rc')/* as string*/);
                    if (!rc) continue;//rowspan新增的单元格跳过
                    //在范围内
                    if (rc.startRowIndex >= MMRC.startRowIndex && rc.endRowIndex <= MMRC.endRowIndex && rc.startCellIndex >= MMRC.startCellIndex && rc.endCellIndex <= MMRC.endCellIndex) {

                        let colSpan = rc.endCellIndex - rc.startCellIndex;
                        if (colSpan > 0) {//跨列
                            for (let i = 0, j = colSpan; i < j; i++) {
                                tr.insertCell(td.cellIndex+1);//这个是在后面插入,前面插入+1
                            }
                            td.colSpan = 1;
                        }
                        let rowSpan = rc.endRowIndex - rc.startRowIndex;
                        if (rowSpan > 0) {//跨行
                            for (let k = 1; k <= rowSpan; k++) {
                                let nextTr = table.rows[rc.startRowIndex + k];
                                let cellIndex = getInsertCellIndex(nextTr, td.offsetLeft+td.offsetWidth);
                                for (let i = 0; i < colSpan + 1; i++) {
                                    nextTr.insertCell(cellIndex);
                                }
                            }
                            td.rowSpan = 1;                       
                        }
                    }
                }
            }
        }
        removeAllSelectedClass();
        initTdRc();//重新初始化过rc属性
    }
    function onMousedown(e/*:any*/) {//鼠标按下事件
        let o = e.target;
        if (o.tagName == 'TD') {
            removeAllSelectedClass(table);
            //绑定事件
            endTD = startTD = o;
            table.addEventListener('mousemove', onMousemove);
            table.addEventListener('mouseup', onMouseup);
            startTD.classList.add('selected');
            MMRC = JSON.parse(o.getAttribute('rc')/*as string*/);
        }
    }
    function deleteColumn() {
        let col = parseInt(prompt('请输入原始列下标!'));
        if (isNaN(col) || col >= totalCell) { alert(`列下标需要介于0~${totalCell - 1}之间!`); return; }
        for (let tr of table.rows) {
            for (let td of tr.cells) {
                var rc = JSON.parse(td.getAttribute('rc'));
                if (rc.startCellIndex <= col && col <= rc.endCellIndex) {
                    if (rc.startCellIndex == rc.endCellIndex) {//只有一个,删掉
                        td.parentNode.removeChild(td);
                    }
                    else {
                        td.colSpan -= 1;
                    }
                    break;//后续单元格不需要再遍历在,直接下一行
                }
            }
        }
        initTdRc();//重新初始化rc
    }
    let table = document.querySelector('#tb1'), startTD, endTD, MMRC = { startRowIndex: -1, startCellIndex: -1, endRowIndex: -1, endCellIndex: -1 }, totalCell=0;
    initTdRc(table)
    table.addEventListener('mousedown', onMousedown);


</script>

 

加支付宝好友偷能量挖...


原创文章,转载请注明出处:原生JS模拟Excel表格单元格合并,拆分,删除指定列示例

评论(0)Web开发网
阅读(1159)喜欢(0)JavaScript/Ajax开发技巧