在vue中通过js动态绘制table,并且合并连续相同内容的行,支持点击编辑单元格内容,新增单元行,删除单元行,以及还原被合并的单元格。

首先是vue代码

<template>
//这个是表格,后面的js方法会往这个div里面插入表格
<div class="box-container" >
        <div ref="tableContainer" class="lsb-table-box">
          <div class="table-container" id="lsb-table">
          </div>
        </div>
      </div>

//这个是弹窗
  <div class="tanchuang move-win1"
       style="width: 160px;height: 110px"
       id="tanchuang1">
    <el-button style="margin-left: 12px" data-action="above">在上方插入一行</el-button>
    <el-button style="margin-top: 5px" data-action="below">在下方插入一行</el-button>
    <el-button style="margin-top: 5px" data-action="remove">删除当前行</el-button>
  </div>

</template>

在这里插入图片描述

然后是js方法

/**
 * 渲染表格
 */
function tableRenderCs() {

    const data=[{
        "one": "测试1",
        "two": "测试2",
        "three": "测试3",
        "four": "测试4",
        "five": "测试5",
        "six": "测试6",
        "seven": "测试7",
        "eight": "测试8",
        "nine": "测试9"
    },{
        "one": "测试1",
        "two": "测试2",
        "three": "测试3",
        "four": "测试4",
        "five": "测试5",
        "six": "测试6",
        "seven": "测试7",
        "eight": "测试8",
        "nine": "测试9"
    },{
        "one": "测试1",
        "two": "测试2",
        "three": "测试3",
        "four": "测试4",
        "five": "测试5",
        "six": "测试6",
        "seven": "测试7",
        "eight": "测试8",
        "nine": "测试9"
    }]

    let html = '<table class="table-wj JZ-A" border="1" cellspacing="0">' +
        '<thead>' +
        '<tr>' +
        '<th rowspan="2" colspan="3">第一列</th>' +
        '<th rowspan="2" colspan="1" style="width: 50px;">第二列</th>' +
        '<th rowspan="2" colspan="1" style="width: 100px;">第三列</th>' +
        '<th rowspan="2" colspan="1" style="width: 50px;">第四列</th>' +
        '<th rowspan="2" colspan="1" style="width: 80px;">第五列</th>' +
        '<th rowspan="1" colspan="3">第六列</th>' +
        '</tr>' +
        '<tr>' +
        '<th colspan="1" style="width: 50px;">第六列的第一列</th>' +
        '<th colspan="1" style="width: 90px;">第六列的第二列</th>' +
        '</tr>' +
        '</thead>' +
        '<tbody>';

    for (let i = 0; i < data.length; i++) {
        const item = data[i];

        html +=
            `<tr>` +
            `<td>${item.one}</td>` +
            `<td>${item.two}</td>` +
            `<td>${item.three}</td>` +
            `<td>${item.four}</td>` +
            `<td>${item.five}</td>` +
            `<td>${item.six}</td>` +
            `<td>${item.seven}</td>` +
            `<td>${item.eight}</td>` +
            `<td>${item.nine}</td>` +
            `</tr>`;
    }

    html += '</tbody></table>';

    let _$ = $(".lsb-table-box .table-container");
    _$.append(html);

   //存储列名对应的字段值,方便后面计算
    let rowName = {
        '0': 'one',
        '1': 'two',
        '2': 'three',
        '3': 'four',
        '4': 'five',
        '5': 'six',
        '6': 'seven',
        '7': 'eight',
        '8': 'nine',
    }

       editInput(_$, data, "id", rowName);	//编辑单元格

    	addBindAction(24, data, rowName)	//为按钮绑定点击事件
}



const numCols这里给出了两种合并表格的逻辑,第一个是只会对前三列执行合并逻辑,而第二个会对所有列执行合并逻辑。下面是两种逻辑的合并效果图。
在这里插入图片描述
在这里插入图片描述
接下来是完整的js方法,实现了点击编辑单元格内容,新增单元行,删除单元行,以及还原被合并的单元格。
之所以要还原单元格是为了获取每一行的数据,而被合并的单元格会获取到空数据

let currentRow = null;

/**
 * 设置单元格可编辑
 * @param _$    表对象
 * @param data  表数据
 * @param fieldName 编辑后要获取的值对应的列名
 * @param rowName 列的索引与数据库字段相对应的集合
 */
export function editInput(_$, data, fieldName, rowName) {
    // 获取所有表格单元格
    let cells = _$.find('td');

    // 为每个单元格添加点击事件
    cells.each(function (index) {
        let column = $(this).index();
        let row = $(this).closest('tr').index();

        let columnName = Object.keys(data[0])[column]; // 获取对应列的字段名

        $(this).data('columnName', columnName); // 存储列名为数据属性

        //添加双击击事件
        $(this).on('dblclick', function (event) {
            // 如果是鼠标右键点击,不进行操作
            if (event.which === 3) return;

            let $input = $(this).find('input'); // 检查单元格内是否已有输入框

            if ($input.length === 0) { // 如果没有输入框,则进行以下操作
                let currentValue = $(this).text();

                // 创建一个输入框元素,并将当前单元格内容设置为其值
                $input = $('<input type="text">');
                $input.val(currentValue);

                // 设置输入框的样式
                $input.css({
                    'width': '100%', // 设置宽度为100%,以占满<td>的宽度
                    'height': '90px', // 设置高度为100%,以占满<td>的高度
                    'box-sizing': 'border-box', // 确保边框和填充不会增加元素的尺寸
                    'padding': '0', // 移除内边距
                    'margin': '0', // 移除外边距
                    'background-color': 'green', // 设置背景色为透明
                    'color': 'white', // 设置文字颜色为白色
                    'border': 'none', // 移除边框
                    'outline': 'none', // 移除外边框
                });

                // 清空单元格并将输入框添加到单元格中
                $(this).empty().append($input);

                // 焦点定位到输入框
                $input.focus();

                // 阻止默认行为
                event.preventDefault();

                // 处理输入框失去焦点事件
                $input.on('blur', function () {
                    let newValue = $input.val();

                    let valueName = rowName[column];  // 获取列索引对应的字段名称
                    let columnValue = data[row][fieldName]; // 获取对应字段的值

                    $(this).parent('td').text(newValue);

                    console.log(`更改了${fieldName}为${columnValue}的字段${valueName}的值为${newValue}`)

                    let updateData = {};
                    updateData[valueName] = newValue; // 创建对象,属性名为 valueName,值为 newValue
                    restoreTable()
                    mergeColumns()
                });
            }
        });

        // 右键点击事件
        $(this).on('contextmenu', function(event) {
            event.preventDefault(); // 阻止默认的右键菜单

            // 获取当前行的引用
            currentRow =$(this).closest('tr');

            // 调用右键点击事件的方法
            showContextMenu(event);
        });

    });
}

//显示弹窗
function showContextMenu(event) {
    let tanchuang = document.getElementById('tanchuang1');
    // 设置弹窗的位置
    tanchuang.style.display = 'block';
    tanchuang.style.left = event.pageX + 'px';
    tanchuang.style.top = event.pageY + 'px';

    document.addEventListener('click', function(event) {
        if (event.target !== tanchuang && !tanchuang.contains(event.target)) {
            tanchuang.style.display = 'none'; // 隐藏弹窗
        }
    });
}

export function addBindAction(rows, data, rowName){
    // 为按钮绑定点击事件
    $(document).ready(function() {
        $(document).on('click', '[data-action]', function() {
            // 获取按钮的动作
            let action = $(this).data('action');
            // 处理行操作
            handleRowAction(action, currentRow, rows, data, rowName);
        });
    });
}

// 定义插入新行的方法
function handleRowAction(action, $currentRow, rows, data, rowName) {
    restoreTable()

    // 创建新的行
    let newRow = $('<tr>').append(
        $('<td style="height: 90px;"></td>')
    );

    // 使用for循环添加剩余的单元格
    for (let i = 0; i < rows; i++) {
        newRow.append($('<td style="height: 90px;"></td>'));
    }

    bindCellEvents(newRow);

    if (action === 'above') {
        $currentRow.before(newRow);
    } else if (action === 'below') {
        $currentRow.after(newRow);
    } else if (action === 'remove') {
        $currentRow.remove();
    }

    let _$ = $(".lsb-table-box .table-container");
    editInput(_$, data, "id", rowName);

    mergeColumns()
}

// 新插入行绑定点击事件
function bindCellEvents($row) {
    //添加双击击事件

    //右键点击事件
    $row.find('td').on('contextmenu', function(event) {
        event.preventDefault(); // 阻止默认的右键菜单

        // 获取当前行的引用
        currentRow = $(this).closest('tr');

        // 调用右键点击事件的方法
        showContextMenu(event);
    });
}

// 获取所有行内容的函数
export function getAllRowContents(tableId, type) {

    let table = restoreTable();

    // let table = document.getElementById(tableId);


    let rows = table.rows; // 获取所有行
    let list = []; // 创建一个空列表来存储行数据

    for (let i = 0; i < rows.length; i++) {
        let row = rows[i]; // 获取行
        let rowData = []; // 创建一个空数组来存储当前行的数据

        // 遍历行中的所有单元格
        for (let j = 0; j < row.cells.length; j++) {
            let cell = row.cells[j];
            let cellValue = cell.textContent || cell.innerText; // 获取单元格的内容
            rowData.push(cellValue); // 将单元格内容添加到行数据数组中
        }

        // 将行数据添加到列表中
        list.push(rowData);
    }

    console.log(list)

    let data = {
        lsbList: list,
        stationId: localStorage.getItem('stationId'),
        type: type,
    }

    saveLcJlLSBList(data).then(res => {
        if (res.code === 200){
            ElMessage.success('保存成功')
        }else {
            ElMessage.error('保存失败')
        }
    })

    mergeColumns();
}

/**
 * 合并方法只有前三列执行合并逻辑
 */
export function mergeColumns() {
    let $table = $('table.table-wj');
    let $rows = $table.find('tbody tr');

    const numCols = Math.min(3, $rows.eq(0).find('td').length); // 仅考虑前三列
    //const numCols = $rows.eq(0).find('td').length; // 考虑所有列

    // 遍历每列
    for (let col = 1; col <= numCols; col++) {
        let $currentColumn = $table.find(`td:nth-child(${col})`);
        let prevContent = null;
        let rowspan = 1;

        for (let i = 0; i < $currentColumn.length; i++) {
            let $currentCell = $($currentColumn[i]);
            let currentContent = $currentCell.text();

            if (currentContent === prevContent) {
                rowspan++;
                $currentCell.addClass('hidden');
            } else {
                if (rowspan > 1) {
                    $currentColumn.eq(i - rowspan).attr('rowspan', rowspan);
                }
                prevContent = currentContent;
                rowspan = 1;
            }
        }

        if (rowspan > 1) {
            $currentColumn.eq($currentColumn.length - rowspan).attr('rowspan', rowspan);
        }
    }

    // 清除被隐藏的单元格
    $table.find('.hidden').remove();
}

//还原被合并的单元格
export function restoreTable() {
    // 获取到包含表格的foreignObject元素
    const foreignObject = document.getElementById('publicSvg');
    if (!foreignObject){
        return '';
    }
    // 获取到表格元素
    let oldTable = foreignObject.querySelector('table');
    if (!oldTable){
        return '';
    }
    // 复制表格元素
    // let newTable = oldTable.cloneNode(true);
    let newTable = oldTable
    // 将新表格添加到页面中
    // foreignObject.appendChild(newTable);
    //以下方法将合并后的表格还原为未合并的状态
    let rows = newTable.rows;

    for (let i = 2; i < rows.length; i++) {
        let cells = rows[i].cells;
        for (let j = 0; j < cells.length; j++) {
            let cell = cells[j];
            let rowspan = cell.rowSpan;
            let colspan = cell.colSpan;

            cell.setAttribute('data-original-rowspan', rowspan);
            cell.setAttribute('data-original-colspan', colspan);

            // 如果单元格有 rowspan 或 colspan
            if (rowspan > 1 || colspan > 1) {
                // 记录单元格的内容
                let cellContent = cell.innerHTML;

                // 还原 rowspan
                for (let r = 0; r < rowspan; r++) {
                    for (let c = 0; c < colspan; c++) {
                        if (r !== 0 || c !== 0) {
                            // 创建一个新的单元格元素,保持原有的类型
                            let newCell = document.createElement(cell.tagName.toLowerCase());
                            newCell.innerHTML = cellContent;
                            newCell.setAttribute('data-remove', true);

                            // 在新位置插入单元格
                            let targetRow = rows[i + r];
                            if (targetRow) { // 确保目标行存在
                                targetRow.insertBefore(newCell, targetRow.cells[j + c]);
                            }
                        }
                    }
                }

                // 重置原单元格的 rowspan 和 colspan
                cell.rowSpan = 1;
                cell.colSpan = 1;
            }
        }
    }

    // 检查表格元素是否存在
    if (newTable) {
        // 删除表格元素
        // foreignObject.removeChild(newTable);
    }
    return newTable;
}








实现效果如下,同时还获取了当前单元格所在行里面指定的某一列的数据内容(比如可以获取当前行的id,以此来给后端修改数据库中的数据),注意行是从表头下面开始的,行和列的下标都是从0开始
在这里插入图片描述

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值