<template>
<div>
<table border="1" v-table-select="state">
<tr v-for="(rowData,rowIndex) of state.tableData" :key="rowIndex" >
<th>{{rowData.row}}</th>
<td v-for="(columnData,columnIndex) of rowData.data"
:key="columnData.id" :class="{'selected':columnData.selected}"
:data-row="rowIndex"
:data-column="columnIndex"
>
{{columnData.content}}
</td>
</tr>
</table>
</div>
</template>
<script setup>
import {reactive,watch} from 'vue'
import vTableSelect from './directives/tableSelect.js'
const data = [
{
row: "X",
data: [
{ id: 1, content: 1,selected:false },
{ id: 2, content: 2 },
{ id: 3, content: 3 },
{ id: 4, content: 4 },
],
},
{
row: "Y",
data: [
{ id: 5, content: 5 },
{ id: 6, content: 6 },
{ id: 7, content: 7 },
{ id: 8, content: 8 },
],
},
{
row: "Z",
data: [
{ id: 9, content: 9 },
{ id: 10, content: 10 },
{ id: 11, content: 11 },
{ id: 12, content: 12 },
],
},
];
const state=reactive({
tableData:data,
selectedData:null,
selectedAreaData:[]
})
watch(()=>state.selectedData,(newValue)=>{
console.log('selectedData',newValue);
} );
watch(()=>state.selectedAreaData,(newValue)=>{
console.log('selectedAreaData',newValue);
} );
</script>
<style>
body {
user-select: none;
margin: 0;
padding: 0;
}
table {
width: 800px;
border-collapse: collapse;
table-layout: fixed;
text-align: center;
}
td {
height: 33px;
cursor: pointer;
box-sizing: border-box;
position: relative;
}
td.selected {
background-color: orange;
}
</style>
let isMouseOver = false;
const vTableSelect = {
mounted(el, bindings) {
vTableSelect.el = el
bindEvent(bindings.value)
}
}
function bindEvent(state) {
const { el } = vTableSelect
el.addEventListener('click', handleTDClick.bind(el, state), false)
el.addEventListener('dblclick', handleTDDblClick.bind(el, state), false)
el.addEventListener('mousedown', handleTDMouseDown.bind(el, state), false)
window.addEventListener('click', handleWindowClick.bind(el, state), false)
}
function handleTDClick(...[state, e]) {
if (!isMouseOver) {
e.stopPropagation()
const { target } = e
restoreSelectedData(state)
if (target.tagName === 'TD') {
const { row, column } = getRowAndColumn(target)
const selectedTD = getTargetData(state.tableData, row, column)
if (state.selectedData !== selectedTD) {
state.selectedData = selectedTD
state.selectedData.selected = true
}
}
}
}
function handleTDDblClick(...[state, e]) {
e.stopPropagation()
const { target } = e
restoreSelectedData(state)
if (target.tagName === 'TD') {
const { row, column } = getRowAndColumn(target)
state.selectedData = getTargetData(state.tableData, row, column)
this.oInput = createInput(target, state.tableData, row, column)
}
}
function handleTDMouseDown(...[state, e]) {
e.stopPropagation()
isMouseOver = false
const { target } = e
const _handleTDMouseOver = handleTDMouseOver.bind(this, state)
restoreSelectedData(state)
document.addEventListener('mouseover', _handleTDMouseOver, false)
document.addEventListener('mouseup', handleTDMouseUp, false)
if (target.tagName === 'TD') {
const { row, column } = getRowAndColumn(target)
this.startRow = row;
this.startColumn = column
}
function handleTDMouseUp() {
document.removeEventListener('mouseover', _handleTDMouseOver, false)
document.removeEventListener('mouseup', handleTDMouseUp, false)
setTimeout(() => {
isMouseOver = false
})
}
}
function handleTDMouseOver(...[state, e]) {
e.stopPropagation()
isMouseOver = true
const { target } = e
if (target.tagName === 'TD') {
const { row, column } = getRowAndColumn(target)
this.endRow = row;
this.endColumn = column
state.selectedAreaData = getSelectedAreaData(
state.tableData,
this.startRow,
this.startColumn,
this.endRow,
this.endColumn
)
}
}
function handleWindowClick(...[state, e]) {
this.oInput && (state.selectedData.content = this.oInput.value)
!isMouseOver && restoreSelectedData(state)
}
function createInput(target, data, row, column) {
const { content } = getTargetData(data, row, column)
const oInput = document.createElement('input')
oInput.type = 'text'
oInput.value = content
oInput.onfocus = oInput.select;
target.appendChild(oInput)
oInput.focus()
oInput.style.cssText = `
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
box-sizing:border-box;
text-align:center;
font-size:16px;
`
return oInput
}
function getSelectedAreaData(data, startRow, startColumn, endRow, endColumn) {
const selectedAreaData = []
console.log(data, startRow, startColumn, endRow, endColumn);
if (startRow <= endRow) {
for (let i = startRow; i <= endRow; i++) {
setSelectedAreaData(data[i].data, startColumn, endColumn)
}
} else {
for (let i = startRow; i >= endRow; i--) {
setSelectedAreaData(data[i].data, startColumn, endColumn)
}
}
function setSelectedAreaData(rowData, startColumn, endColumn) {
if (startColumn <= endColumn) {
for (let j = startColumn; j <= endColumn; j++) {
pushColumnData(rowData[j])
}
} else {
for (let j = startColumn; j >= endColumn; j--) {
pushColumnData(rowData[j])
}
}
function pushColumnData(columnData) {
columnData.selected = true;
selectedAreaData.push(columnData)
}
}
return selectedAreaData
}
function restoreSelectedData(state) {
const { el } = vTableSelect
if (state.selectedData) {
state.selectedData.selected = false
}
if (state.selectedAreaData.length) {
state.selectedAreaData.forEach(data => {
data.selected = false
})
state.selectedAreaData = []
}
if (el.oInput) {
el.oInput.remove()
el.oInput = null
}
}
function getRowAndColumn(target) {
const { dataset } = target
return {
row: Number(dataset.row),
column: Number(dataset.column),
}
}
function getTargetData(data, row, column) {
let target = null;
data.forEach((_row, _rowIndex) => {
if (row === _rowIndex) {
_row.data.forEach((_column, _columnIndex) => {
if (column === _columnIndex) {
target = _column
}
})
}
})
return target
}
export default vTableSelect
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
2021-11-27 千分符分隔金额 number.toLocaleString("en-US")