HTML表格区域选中列选中-Vue & ElementUI

本文介绍了如何在HTML表格中实现区域选中功能,特别是列选中模式,通过Vue和ElementUI结合,利用CSS设置不可选中样式,并监听鼠标事件来实现任意选择。文章提到了两种方法,第一种依赖浏览器默认复制操作,存在复制到Excel和文本编辑器时格式不理想的问题。第二种方法则是通过自定义事件监听和剪切板操作,将选中内容以期望的格式写入剪切板。

HTML 元素(主要是文本)能否被选中,是由 user-select css 属性控制的,若设置为 none 则不可选中,更多属性值参考 MDN.

HTML 页面的默认选中方式是行选择模式,即鼠标从按下到释放中间经过的所有行都会被选中。若要实现列选中模式或是任意选中模式,基本思路是:将表格所有单元格设置为不可选中,在鼠标经过时,将对应的单元格设置可选中,即可实现任意选择的模式。 以上思路有几点需要注意的:

  1. 浏览器适配:完整的设置不可选中的样式为: -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;
  2. 不可选中的元素:不一定是给单元格 td 设置不可选中,而应该给直接包裹文字的元素设置(如下例中是 td 中 class 为 celldiv)。
  3. 框选模式:该思路只能直线涂抹选中,即鼠标经过的 cell 会被选中。若想实现画对角线进行框选,还需要添加逻辑。
  4. 事件:会涉及的事件:mousedown,mousemove,mouseup。若使用 jquery 则可以很方便的进行事件注册和 DOM操作,若使用 vue 则可以通过自定义指令 directives 得到需要操作的 DOM元素。

示例代码(Vue + elementUI):

const selectDisableStyle = `-webkit-user-select:none; -moz-user-select: none; -ms-user-select: none; user-select: none;`
...
directives: {
    areaSelect: { // 在需要自定义选择的元素上添加 v-areaSelect
        inserted: (el, binding, vnode) => {
            let randIds = new Map()
            let mouseDownFlag = false
            let mouseUpFlag = false
            let cells = []
            el.addEventListener('mousedown', function (event) {
                mouseDownFlag = true
                mouseUpFlag = false
                cells = []
                el.querySelectorAll('tr').forEach(tr => {
                    let row = tr.querySelectorAll('td div.cell')
                    row.length > 0 && cells.push(row)
                })
                cells.forEach((tdRow, idy) => {
                    tdRow.forEach((tdCol, idx) => {
                        const style = tdCol.getAttribute('style')
                        if (style.indexOf(selectDisableStyle) < 0) {
                            tdCol.setAttribute('style', style + selectDisableStyle)
                        }
                        // 若表格有 rowIndex ,cellIndex 则可不设 id
                        tdCol.setAttribute('id', `${idy + 1}_${idx + 1}`)
                    })
                })
                // 选中点击的 cell
                removeStyle(event)
            })

            function mouseMove(evt) {
                if (mouseUpFlag || !mouseDownFlag) {
                    return
                }
                // 缓存经过的 cell id
                randIds.set(evt.target.id, evt.target.id)
                // 选中
                removeStyle(evt)
            }

            el.addEventListener('mousemove', mouseMove)
            el.addEventListener('mouseup', function (evt) {
                mouseUpFlag = true
                mouseDownFlag = false
                // 框选逻辑
                let posList = Array.from(randIds).filter(v => v[0]).map(v => v[0]).map(v => v.split('_'))
                let posYList = posList.map(v => v[0])
                let posXList = posList.map(v => v[1])
                let minX = Math.min(...posXList), minY = Math.min(...posYList)
                let maxX = Math.max(...posXList), maxY = Math.max(...posYList)
                cells.forEach(cellRow => {
                    cellRow.forEach(cell => {
                        let [idy, idx] = cell.id.split('_').map(v => Number(v))
                        if (idx >= minX && idx <= maxX && idy >= minY && idy <= maxY) {
                            removeStyle(cell)
                        }
                    })
                })
                // 重置
                randIds = new Map()
                cells = []
            })
        }
    }
}

// 清除禁止选中的样式,同时选中
function removeStyle(evt) {
    let target = evt.target || evt
    let style = target.getAttribute('style') || selectDisableStyle
    let reg = new RegExp(selectDisableStyle, 'g')
    target.setAttribute('style', style.replace(reg, ''))
}

该方法虽然可实现任意区域框选,但复制的操作仍然不理想,复制到 Excel 中仍然会复制整行(可能是 ElementUI 的行为),复制到文本编辑器,多行多列的内容也会被合并为一列(单元格内容被换行或制表符分割)。

第二种思路 不去依赖浏览器的默认复制操作,而是自动将被复制内容写入剪切板。依然可以借鉴上一方法中对各种事件的监听,以及区域框选算法。只是对鼠标经过的单元格,不是设置 user-select:none 之类的样式,而是将单元格添加边框,以示选中。执行区域选中之后,程序是可以知道哪些单元格被选中的,此时可以将这些单元格的内容以想要的格式写入剪切板。

写入剪切板的思路:利用一个不可见 input 元素(若需要多行内容可以使用 textarea),将要复制的文本写入,再执行 setSelectionRange 选中,然后执行 document.execCommand('copy'),将 value 写入系统剪切板。

操作方式:按住 Ctrl 再使用鼠标选择,鼠标释放时自动框选,并将内容复制到剪切板。若不按 Ctrl 则仍旧可以使用浏览器自身的行选择模式。

const selectStyle = 'border: 1px solid rgb(51,144,255); box-shadow: 0px 0px 5px 1px rgb(51,144,255);'
...
mounted() {
    // 按下 control 键
    // isCtrlPressed => this.ctrlPress > 0
    document.onkeydown = (e) => {
        if (e.keyCode === 17) {
            this.ctrlPress += 1
        }
    }
    document.onkeyup = (e) => {
        if (e.keyCode === 17) {
            this.ctrlPress = 0
        }
    }
},
directives: {
    areaSelect: {
        inserted: (el, binding, vnode) => {
            let randIds = new Map()
            let mouseDownFlag = false
            let mouseUpFlag = false
            const vm = vnode.context // 获取当前组件的 Vue 实例
            let cells = []    // 表格中所有 cell
            let selectedCells = [] // 最终选中的 cell

            // 复制之后清除选中样式,单击会与现有事件冲突,改为双击
            document.addEventListener('dblclick', function () {
                if (!el) { // 该事件不好注销,故加此判断
                    return
                }
                el.querySelectorAll('tr').forEach(tr => {
                    let row = tr.querySelectorAll('td div.cell')
                    row.forEach(tdCol => {
                        tdCol.setAttribute('style', "")
                    })
                })
            })

            el.addEventListener('mousedown', function (event) {
                if (!vm.isCtrlPressed) {
                    return
                }
                mouseDownFlag = true
                mouseUpFlag = false
                cells = []
                el.querySelectorAll('tr').forEach(tr => {
                    let row = tr.querySelectorAll('td div.cell')
                    row.length > 0 && cells.push(row)
                })
                cells.forEach((tdRow, idy) => {
                    tdRow.forEach((tdCol, idx) => {
                        const style = tdCol.getAttribute('style')
                        // 为了界面简洁明了,选择过程中仍然禁止浏览器自身选中行为
                        if (style.indexOf(selectDisableStyle) < 0) {
                            tdCol.setAttribute('style', style + selectDisableStyle)
                        }
                        tdCol.setAttribute('id', `${idy + 1}_${idx + 1}`)
                    })
                })
                // 选中点击的 cell
                selectCell(event)
            })

            el.addEventListener('mousemove', function mouseMove(evt) {
                if (!vm.isCtrlPressed) {
                    return
                }
                if (mouseUpFlag || !mouseDownFlag) {
                    return
                }
                // 缓存经过的 cell id
                randIds.set(evt.target.id, evt.target.id)
                selectCell(evt)
            })

            el.addEventListener('mouseup', function (evt) {
                if (!vm.isCtrlPressed) {
                    return
                }
                mouseUpFlag = true
                mouseDownFlag = false
                let posList = Array.from(randIds).filter(v => v[0]).map(v => v[0]).map(v => v.split('_'))
                let posYList = posList.map(v => v[0])
                let posXList = posList.map(v => v[1])
                let minX = Math.min(...posXList), minY = Math.min(...posYList)
                let maxX = Math.max(...posXList), maxY = Math.max(...posYList)
                cells.forEach(cellRow => {
                    let selectedRow = []
                    cellRow.forEach(cell => {
                        let [idy, idx] = cell.id.split('_').map(v => Number(v))
                        if (idx >= minX && idx <= maxX && idy >= minY && idy <= maxY) {
                            selectCell(cell)
                            selectedRow.push(cell)
                        }
                        // 去除禁止选择的样式,仍然支持浏览器自身的行选择模式
                        removeStyle(cell)
                    })
                    selectedRow.length > 0 && selectedCells.push(selectedRow)
                })
                // WPS 默认单元格以 \t 分割,行以 \n 分割
                copyToClipboard(selectedCells.map(v => v).map(row => row.map(cell => cell.innerText).join("\t")).join("\n"))
                vm.$message.success("内容已复制到剪切板!")
                selectedCells = []
                randIds = new Map()
                cells = []
            })
        }
    }
}

function removeStyle(evt) {
    let target = evt.target || evt
    let style = target.getAttribute('style') || selectDisableStyle
    let reg = new RegExp(selectDisableStyle, 'g')
    target.setAttribute('style', style.replace(reg, ''))
}

function selectCell(evt) {
    let target = evt.target || evt
    // 可能会有其他元素进入,导致样式不美观
    if (target.getAttribute('class').indexOf('cell') < 0) {
        return
    }
    const style = target.getAttribute('style')
    if (style.indexOf(selectStyle) < 0) {
        target.setAttribute('style', style + ';' + selectStyle)
    }
}

function copyToClipboard(text) {
    const input = document.createElement('TEXTAREA');
    input.style.opacity = 0;
    input.style.position = 'absolute';
    input.style.left = '-100000px';
    document.body.appendChild(input);

    input.value = text;
    input.select();
    input.setSelectionRange(0, text.length);
    document.execCommand('copy');
    document.body.removeChild(input);
}

效果:
效果

Vue.js和Element UI中,如果你想在`el-table`组件中实现全选整页数据的功能,你可以这样做: 首先,你需要在`el-table`组件上添加一个全局的复选框(通常是放在表头),并绑定一个计算属性来管理当前选中的状态。然后,通过监听这个全局的复选框的状态变化,同步所有分页数据的选择。 以下是一个简单的示例: ```html &lt;template&gt; &lt;div&gt; &lt;el-checkbox v-model=&quot;selectAll&quot; @change=&quot;toggleAllPages&quot;&gt;&lt;/el-checkbox&gt; &lt;el-table :data=&quot;tableData&quot; :key=&quot;index&quot;&gt; &lt;!-- ... 表格定义 --&gt; &lt;template slot-scope=&quot;{ row }&quot;&gt; &lt;!-- ... 表格行内容 --&gt; &lt;el-checkbox v-model=&quot;row.selected&quot; @change=&quot;toggleRow(row)&quot;&gt;&lt;/el-checkbox&gt; &lt;/template&gt; &lt;/el-table&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; export default { data() { return { selectAll: false, tableData: [] // 这里需要是你实际的表格数据,可以包含selected属性 }; }, methods: { toggleAllPages(event) { if (event.target.checked) { this.tableData.forEach(row =&gt; row.selected = true); } else { this.tableData.forEach(row =&gt; row.selected = false); } }, toggleRow(row) { if (this.selectAll) { row.selected = true; } else { this.selectAll = !this.selectAll &amp;&amp; row.selected; // 只有当全选开关关闭时,才切换单行选中状态 } } } }; &lt;/script&gt; ``` 在这个例子中,`toggleAllPages`方法会更新整个表格的所有行,而`toggleRow`则会根据全选的状态来调整每一行的选中状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值