目录
一、效果展示
二、代码分析
2.1、区域确定与坐标获取
2.2、单个点击与一次性点击
一、效果展示
主要借助自定义指令实现。在表格的"td们"上面进行移动框选,有一次性框选和单个框选,去掉自定义指令里的clearTargetNodes()会连续td,连续框选后会获取对应的值,可以再进行组装。
二、代码分析
<template>
<div>
<table border="1" v-cellSelect="weekdayTimeData">
<tr>
<th rowspan="2">星期 / 时间</th>
<th colspan="12">00:00 - 12:00</th>
<th colspan="12">12:00 - 24:00</th>
</tr>
<tr>
<th v-for="n in 24" :key="n">{{ n - 1 }}</th>
</tr>
<tr v-for="n in 7" :key="n" :data-weekday="n - 1">
<th class="weekday-title">{{ weekdayTitle[n - 1] }}</th>
<td v-for="m in 24" :key="m" :data-time="m - 1"></td>
</tr>
</table>
</div>
</template>
<script>
export default {
directives: {
cellSelect: {
bind(el, bindings) {
el.addEventListener("mousedown", handleMouseDown, false);
function handleMouseDown(e) {
const tar = e.target;
const tagName = tar.tagName.toLowerCase();
const els = this;
clearTargetNodes(els); //防止单个点击时"td"们连在一起。不加该函数,点击的"td"们会连在一起
if (tagName === "td") {
els.start = tar;
const rowIndex = Number(els.start.parentNode.dataset.weekday);
const columnIndex = Number(els.start.dataset.time);
// ===========拿到移动开始时的最终坐标
setWeekdayTimeData(els, rowIndex, columnIndex);
addTargetNode(els, tar); //情况一:单个点击移动框选
els.addEventListener("mousemove", handleMouseMove, false);
els.addEventListener("mouseup", handleMouseUp, false);
}
}
function handleMouseMove(e) {
const tar = e.target;
const tagName = tar.tagName.toLowerCase();
const els = this;
if (tagName === "td") {
const startTarget = els.start;
const endTarget = tar;
const startRow = Number(startTarget.parentNode.dataset.weekday);
const startColumn = Number(startTarget.dataset.time);
const endRow = Number(endTarget.parentNode.dataset.weekday);
const endColumn = Number(endTarget.dataset.time);
// ===========拿到移动停止时的最终坐标
const currentTargetNodes = getTargetNodes(
els,
startRow,
startColumn,
endRow,
endColumn
);
// ============拿到起始到结束的所有td数组
getTargetNodeDiff(els, els.targetNodes, currentTargetNodes);
}
}
function getTargetNodes(els, startRow, startColumn, endRow, endColumn) {
const { allRows } = els;
const startR = startRow > endRow ? endRow : startRow;
const startC = startColumn > endColumn ? endColumn : startColumn;
const endR = startRow > endRow ? startRow : endRow;
const endC = startColumn > endColumn ? startColumn : endColumn;
const targetNodes = new Set(); //防止来回移动,造成冗余数据
allRows.forEach((tr, rowIndex) => {
if (rowIndex >= startR && rowIndex <= endR) {
[...tr.querySelectorAll("td")].forEach((td, columnIndex) => {
if (columnIndex >= startC && columnIndex <= endC) {
targetNodes.add(td);
setWeekdayTimeData(els, rowIndex, columnIndex);
}
});
}
});
return targetNodes;
}
function getAllRows(el) {
const oAllRows = el.querySelectorAll("tr"); //[tr, tr, tr, tr, tr, tr, tr, tr, tr]
return [...oAllRows].reduce((prev, tr) => {
if (tr.dataset.weekday) {
prev.push(tr);
}
return prev;
}, []);
}
function handleMouseUp() {
const els = this;
els.removeEventListener("mousemove", handleMouseMove, false);
els.removeEventListener("mouseup", handleMouseUp, false);
}
el.allRows = getAllRows(el); //[tr, tr, tr, tr, tr, tr, tr],原本有9个,只取出带":data-weekday"的tr
el.targetNodes = new Set();
el.weekdayTimeData = bindings.value;
//情况二:"一气呵成"点击移动框选
function getTargetNodeDiff(els, targetNodes, currentTargetNodes) {
// currentTargetNodes里面有,而targetNodes没有,就要增加
currentTargetNodes.forEach((td) => {
!targetNodes.has(td) && addTargetNode(els, td);
});
// targetNodes里面有,而currentTargetNodes没有,就要减少
targetNodes.forEach((td) => {
!currentTargetNodes.has(td) && removeTargetNode(els, td);
});
}
function addTargetNode(el, target) {
//框选完继续扩大面积
el.targetNodes.add(target);
target.classList.add("target");
}
function removeTargetNode(el, target) {
//框选完缩小了面积
el.targetNodes.delete(target);
target.classList.remove("target");
}
function clearTargetNodes(el) {
el.targetNodes.forEach((target) => {
target.classList.remove("target");
});
el.targetNodes = new Set();
el.weekdayTimeData = [];
}
function setWeekdayTimeData(el, weekday, time) {
console.log(weekday, time, "el, weekday, time", el.weekdayTimeData);
//结果: [ {3: {0, 1, 2, 3}},{4:{0, 1, 2, 3}}]
//参考: {0:[0,1,2,3]}===>星期一:00:00,01:00,02:00,03:00
el.weekdayTimeData[weekday]
? el.weekdayTimeData[weekday].add(time)
: (el.weekdayTimeData[weekday] = new Set([time]));
}
},
},
},
data() {
return {
weekdayTimeData: [],
weekdayTitle: [
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六",
"星期日",
],
};
},
};
</script>
<style lang='less' scoped>
table {
border-collapse: collapse;
.weekday-title {
width: 100px;
}
tr {
height: 50px;
}
td {
width: 50px;
&.target {
background-color: aqua;
}
}
}
</style>
2.1、区域确定与坐标获取
(1)给表格绑定自定义指令拿到它本身,按下时取到所有的td(框选总面积),见handleMouseDown()函数。
(2)防止单个点击时"td"们连在一起。不加该函数,点击的"td"们会连在一起,见clearTargetNodes()函数
(3)拿到移动开始时的最终坐标:rowIndex、columnIndex;
拿到移动停止时的最终坐标:startRow、startColumn、endRow、endColumn
(4)拿到起始到结束的所有td数组,见getTargetNodes()函数,防止来回移动,造成冗余数据,原本有9个,只取出带":data-weekday"的tr
2.2、单个点击与一次性点击
情况一:单个点击移动框选
在handleMouseDown时,直接调用addTargetNode()函数
情况二:"一气呵成"点击移动框选
getTargetNodeDiff这个比较函数,就是要比较在原有框选区域上要扩大/缩小:
currentTargetNodes里面有,而targetNodes没有,就要增加==》框选完继续扩大面积addTargetNode()
targetNodes里面有,而currentTargetNodes没有,就要减少==》框选完缩小了面积removeTargetNode()