效果
<table class="precipitation-table">
<thead>
<tr>
<th class="time-headerzero"> </th>
<th class="time-headerone">{{ 1 }} </th>
<th class="time-headerone" v-for="n in 9" :key="n">{{ n+1 }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(group, groupIndex) in groupedRainMinuteKeys" :key="groupIndex">
<td class="time-column">{{ `${groupIndex + '1'}-${groupIndex * 10 +
10}min` }}</td>
<td v-for="(min, index) in group" :key="index"
@mousedown="startSelect(groupIndex, index)"
@mousemove="selectCells(groupIndex, index)"
@mouseup="endSelect"
:class="{ 'selected-cell': selectedCells.has(`${groupIndex}-${index}`) }">
<el-input v-number class="table_value-input "
size="small"
@input="val => applyBatchValue(val)"
v-model="form.rainMinuteMap[min].v"
placeholder="-"></el-input>
</td>
</tr>
</tbody>
</table>
行列动态生成:
表头通过v-for="n in 9"
循环生成1-10列的标题;表体通过groupedRainMinuteKeys
分组数据动态渲染行,每行对应一个时间段(如"1-10min"),单元格内嵌套el-input
输入框。
表头通过v-for="n in 9"
循环生成1-10列的标题;
<thead>
<tr>
<th class="time-headerzero"> </th>
<th class="time-headerone">{{ 1 }} </th>
<th class="time-headerone" v-for="n in 9" :key="n">{{ n+1 }}</th>
</tr>
</thead>
要为每一个表格绑定对应的数据,他的数据元素一共有六十个
进行分组分批。把元素分为六组,一行一组进行渲染
/**
* 计算属性:将 `rainMinuteKeys` 数组按每 10 个元素为一组进行分组。
*
* 该函数通过遍历 `rainMinuteKeys` 数组,将其分割成多个子数组,每个子数组包含最多 10 个元素。
* 最终返回一个包含这些子数组的数组。
*
* @returns {Array<Array>} 返回一个二维数组,每个子数组包含最多 10 个元素。
*/
const groupedRainMinuteKeys = computed(() => {
const result = [];
// 遍历 `rainMinuteKeys` 数组,每 10 个元素为一组进行切片
for (let i = 0; i < rainMinuteKeys.value.length; i += 10) {
这段代码的功能是将rainMinuteKeys.value数组从索引i开始,截取10个元素,并将这10个元素作为一个子数组推入result数组中。
result.push(rainMinuteKeys.value.slice(i, i + 10));
}
return result;
});
先生成分钟区间 groupindex是 0 1 2 3 4 5
group是具体的 值
然后生成具体的表格内容 从
遍历
<tbody>
<tr v-for="(group, groupIndex) in groupedRainMinuteKeys" :key="groupIndex">
<td class="time-column">{{ `${groupIndex + '1'}-${groupIndex * 10 +
10}min` }}</td>
<td v-for="(min, index) in group" :key="index"
@mousedown="startSelect(groupIndex, index)"
@mousemove="selectCells(groupIndex, index)"
@mouseup="endSelect"
:class="{ 'selected-cell': selectedCells.has(`${groupIndex}-${index}`) }">
<el-input v-number class="table_value-input "
size="small"
@input="val => applyBatchValue(val)"
v-model="form.rainMinuteMap[min].v"
placeholder="-"></el-input>
</td>
</tr>
</tbody>
交互逻辑设计
- 框选功能实现:
- 事件监听:通过
@mousedown
、@mousemove
、@mouseup
实现框选交互。 - 选区计算:
selectCells
函数根据起始和结束位置计算矩形选区,动态更新selectedCells
集合中的单元格坐标(格式为groupIndex-index
)。 - 状态管理:
selectedCells
通过Vue的ref
管理选区状态,选中单元格通过:class="{ 'selected-cell': ... }
高亮显示。
- 事件监听:通过
- 批量修改数值:
输入框的@input
事件触发applyBatchValue
,遍历selectedCells
集合,将当前输入值同步到所有选中单元格对应的数据字段。
当鼠标按下时,会记录表格的行数据和列数据对应的索引传入方法中,去记录对应的值
/**
* 开始框选操作,初始化框选状态并设置起始单元格。
*
* @param {number} groupIndex - 单元格所在组的索引。
* @param {number} index - 单元格在组内的索引。
* @returns {void} 无返回值。
*/
function startSelect(groupIndex, index) {
// 标记当前正在进行框选操作
isSelecting.value = true;
// 记录框选的起始单元格
startCell = { groupIndex, index };
// 清空已选中的单元格集合
selectedCells.value.clear();
// 将起始单元格添加到已选中的单元格集合中
selectedCells.value.add(`${groupIndex}-${index}`);
}
/**
* 在框选过程中,根据起始单元格和当前单元格的位置,计算并更新选中的单元格范围。
*
* @param {number} groupIndex - 当前单元格的组索引。
* @param {number} index - 当前单元格在组内的索引。
*
* @returns {void} 该函数没有返回值,但会更新 `selectedCells` 集合。
*/
function selectCells(groupIndex, index) {
// 如果当前未处于框选状态或没有起始单元格,则直接返回
if (!isSelecting.value || !startCell) return;
// 获取起始单元格的组索引和组内索引
const { groupIndex: startGroupIndex, index: startIndex } = startCell;
// 计算当前框选范围的组索引和组内索引的最小值和最大值
const minGroupIndex = Math.min(startGroupIndex, groupIndex);
const maxGroupIndex = Math.max(startGroupIndex, groupIndex);
const minIndex = Math.min(startIndex, index);
const maxIndex = Math.max(startIndex, index);
// 清空当前选中的单元格集合
selectedCells.value.clear();
// 遍历框选范围内的所有单元格,并将其添加到选中的单元格集合中
for (let i = minGroupIndex; i <= maxGroupIndex; i++) {
for (let j = minIndex; j <= maxIndex; j++) {
selectedCells.value.add(`${i}-${j}`);
}
}
}
/**
* 结束框选操作
* 该函数用于结束当前的框选状态,将框选标志设置为 false,并清空起始单元格的引用。
* 无参数
* 无返回值
*/
function endSelect() {
// 结束框选状态
isSelecting.value = false;
// 清空起始单元格的引用
startCell = null;
}
批量修改数值:
输入框的@input
事件触发applyBatchValue
,遍历selectedCells
集合,将当前输入值同步到所有选中单元格对应的数据字段。
<!--
el-input 组件用于输入框的渲染和交互。
该输入框具有以下特性:
- 使用 v-number 指令,限制输入内容为数字。
- 应用了 table_value-input 样式类,用于自定义样式。
- 设置 size 为 small,表示输入框的尺寸为小号。
- 监听 input 事件,当输入内容发生变化时,调用 applyBatchValue 函数,并将当前输入值作为参数传递。
- 使用 v-model 双向绑定 form.rainMinuteMap[min].v,将输入框的值与表单数据中的 rainMinuteMap[min].v 属性进行绑定。
- 设置 placeholder 为 "-",表示输入框为空时的占位符。
-->
<el-input v-number class="table_value-input "
size="small"
@input="val => applyBatchValue(val)" 这段代码是Vue中的事件监听器,当输入框的值发生变化时,会触发applyBatchValue函数,并将当前输入值作为参数传递给该函数。
v-model="form.rainMinuteMap[min].v"
placeholder="-">
</el-input>
高效批量操作
-
框选多单元格:
用户可通过鼠标拖拽选择连续区域的单元格,支持跨行跨列操作(如选择5-15min的第2-5列)。 -
一键批量赋值:
选中区域后,在任意选中单元格输入数值,所有选中单元格自动同步更新,大幅提升数据录入效率。
/**
* 批量修改选中的单元格的值
*
* 该函数遍历所有选中的单元格,并根据传入的值 `val` 更新这些单元格的值。
* 单元格的键值通过 `selectedCells` 获取,每个键值表示单元格在分组中的位置。
* 函数会根据键值找到对应的 `rainMinuteMap` 中的条目,并将其值更新为 `val`。
*
* @param {any} val - 要应用到选中单元格的新值
*/
function applyBatchValue(val) {
// 遍历所有选中的单元格
selectedCells.value.forEach(cellKey => {
// 解析单元格键值,获取分组索引和单元格索引
const [groupIndex, index] = cellKey.split('-').map(Number);
// 获取对应分组中的最小分钟值
const min = groupedRainMinuteKeys.value[groupIndex][index];
// 更新 `rainMinuteMap` 中对应条目的值
form.value.rainMinuteMap[min].v = val;
});
}
.selected-cell {
background-color: #93aeb9; /* 高亮选中单元格 */
}
在Vue中,冒号
:
是v-bind
的简写,用于动态绑定属性或类名。class
前面的冒号表示class
的值是一个表达式,而不是静态字符串。这里通过判断selectedCells
集合是否包含当前单元格的索引,动态决定是否添加selected-cell
类名。
在css中加入下面代码可以让他不选中文字
user-select:none;
加上之后就不会出现选中文字的情况了