el-table + vue3-selecto 表格中选框

235 阅读2分钟

使用的库: vue3-selecto

文档地址: selecto文档

效果

QQ2025121-101710-HD.gif

下面是代码

// common.js

import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn'; // 引入中文语言包,用于显示中文星期

// 生成当月详细的天数
export const initDays = () => {
	dayjs.locale('zh-cn'); // 设置语言为中文
	const currentMonth = dayjs().month() + 1;
	// 获取当月的第一天
	const startOfMonth = dayjs().startOf('month');
	// 获取当月的最后一天
	const endOfMonth = dayjs().endOf('month');

	// 用于存储当月所有日期及星期的数组
	const datesWithWeek = [];

	// 循环生成当月所有日期及星期
	let currentDate = startOfMonth;
	while (currentDate <= endOfMonth) {
		const fullDate = currentDate.format('YYYY-MM-DD');
		const dateStr = currentDate.format('DD');
		const weekStr = currentDate.format('dddd'); // 获取星期几,中文显示
		const weekIndex = currentDate.day();
		datesWithWeek.push({
			date: parseFloat(dateStr),
			fullDate,
			week: weekStr,
			weekIndex,
		});
		currentDate = currentDate.add(1, 'day');
	}
	return { currentMonth, endOfMonth, datesWithWeek };
};

export const hasOwnProperty = (obj, key) => {
	return Object.prototype.hasOwnProperty.call(obj, key);
};

.vue文件

<template>
	<div class="personnel-scheduling">
		<el-table
			ref="tableRef"
			:data="tableData"
			@cell-dblclick="cellDblClick"
			:cell-class-name="cellClassName"
		>
			<el-table-column
				label="a"
				width="90"
				prop="windowName"
			></el-table-column>
			<template v-for="(item, index) in datesWithWeek" :key="index">
				<el-table-column
					:label-class-name="
						[0, 6].includes(item.weekIndex) ? 'is-week' : ''
					"
				>
					<template #header>
						<span v-if="item.date === 1">
							{{ `${currentMonth}月` }}
						</span>
						<span> {{ item.date }}</span>
					</template>
					<el-table-column
						prop="date"
						:label="item.week.replace('星期', '')"
						:label-class-name="
							[0, 6].includes(item.weekIndex) ? 'is-week' : ''
						"
					>
						<template #default="{ row }">
							<div
								:data-date="item.fullDate"
								:data-window-id="row.windowId"
							>
								{{
									row.date[index] &&
									row.date[index][item.fullDate]
								}}
							</div>
						</template>
					</el-table-column>
				</el-table-column>
			</template>
			<el-table-column label="操作">
				<el-button type="primary" link size="small">按钮</el-button>
			</el-table-column>
		</el-table>

		<Selecto
			ref="selectoRef"
			dragContainer=".el-table__body"
			:selectableTargets="['.is-selecto']"
			:hitRate="0"
			:selectByClick="true"
			:selectFromInside="true"
			:continueSelect="false"
			:continueSelectWithoutDeselect="true"
			toggleContinueSelect="shift"
			:toggleContinueSelectWithoutDeselect="[['ctrl'], ['meta']]"
			:ratio="0"
			:scrollOptions="{
				container: scrollerRef,
				getScrollPosition: () => {
					return [scrollerRef.scrollLeft, scrollerRef.scrollTop];
				},
				throttleTime: 30,
				threshold: 0,
			}"
			@dragStart="onDragStart"
			@selectEnd="onSelectEnd"
			@select="onSelect"
			@scroll="onScroll"
			@innerScroll="onInnerScroll"
		></Selecto>
	</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, toRaw } from 'vue';

import { ElMessage } from 'element-plus';

import Selecto from 'vue3-selecto';

import { initDays, hasOwnProperty } from '../common';

const emits = defineEmits(['dblclick']);

onMounted(() => {
	scrollerRef.value = tableRef.value.$el.querySelector(
		'.el-scrollbar__wrap '
	);
	getTableData();

	document.addEventListener('keydown', keydownHandler);
	document.addEventListener('paste', pasteHandler);
});

onUnmounted(() => {
	document.removeEventListener('keydown', keydownHandler);
	document.removeEventListener('paste', pasteHandler);
});

const { currentMonth, endOfMonth, datesWithWeek } = initDays();

const tableRef = ref(null);
const tableData = ref([]);

const cellDblClick = (row, column, cell, event) => {
	if (column.isSubColumn) {
		console.log('双击了子列');
	}
};
const cellClassName = (row, column, rowIndex, columnIndex) => {
	if (![0, 32].includes(row.columnIndex)) {
		return 'is-selecto';
	}
};

let selectData = [];
const selectoRef = ref(null);
const scrollerRef = ref(null);

const onDragStart = (e) => {
	return !(e.inputEvent.target.nodeName === 'BUTTON');
};
const onSelect = (e) => {
	e.added.forEach((el) => {
		el.classList.add('selected');
	});
	e.removed.forEach((el) => {
		el.classList.remove('selected');
	});
};
const onScroll = ({ direction }) => {
	scrollerRef.value.scrollBy(direction[0] * 10, direction[1] * 10);
};
const onInnerScroll = ({ container, direction }) => {
	container.scrollBy(direction[0] * 10, direction[1] * 10);
};
const onSelectEnd = (e) => {
	selectData = [];
	const els = selectoRef.value.getSelectedTargets();

	els.forEach((el) => {
		const { date, windowId } = el.children[0].children[0].dataset;
		selectData.push({ date, windowId });
	});
};

const getTableData = () => {
	let data = [
		{
			date: [{ '2025-01-01': '张三,王五' }, { '2025-01-02': '李四' }],
			windowName: 'a1',
			windowId: '1',
		},
		{
			date: [],
			windowName: 'a2',
			windowId: '2',
		},
		{
			date: [],
			windowName: 'a3',
			windowId: '3',
		},
		{
			date: [],
			windowName: 'a4',
			windowId: '4',
		},
		{
			date: [],
			windowName: 'a5',
			windowId: '5',
		},
		{
			date: [],
			windowName: 'a1',
			windowId: '6',
		},
		{
			date: [],
			windowName: 'a2',
			windowId: '7',
		},
		{
			date: [],
			windowName: 'a3',
			windowId: '8',
		},
		{
			date: [],
			windowName: 'a4',
			windowId: '9',
		},
		{
			date: [],
			windowName: 'a5',
			windowId: '10',
		},
	];

	data.forEach((item) => {
		if (item.date.length !== endOfMonth) {
			const days = datesWithWeek.map((item) => item.fullDate);
			if (!Object.keys(item.date).length) {
				days.forEach((citem) => {
					item.date.push({ [citem]: '' });
				});
			} else {
				const dates = [];
				Object.values(item.date).forEach((dItem) => {
					dates.push(...Object.keys(dItem));
				});

				days.forEach((dItem) => {
					if (!dates.includes(dItem)) {
						item.date.push({ [dItem]: '' });
					}
				});
			}
		}
	});

	tableData.value = data;
};

const keydownHandler = (event) => {
	const { ctrlKey, keyCode } = event;

	if (ctrlKey && keyCode === 67) {
		const els = selectoRef.value.getSelectedTargets();
		let strArr = [];
		els.forEach((el) => {
			if (el.children[0].children[0].innerText) {
				strArr.push(el.children[0].children[0].innerText);
			}
		});

		if (navigator.clipboard) {
			navigator.clipboard.writeText(strArr.join(',')).then(() => {
				console.log('复制成功');
				strArr = [];
				ElMessage.success('复制成功');
			});
		}
	}

	if (keyCode === 8) {
		selectData.forEach((item) => {
			tableData.value.forEach((cItem) => {
				if (cItem.windowId === item.windowId) {
					cItem.date.forEach((dItem) => {
						console.log(dItem, item.date);
						if (hasOwnProperty(dItem, item.date)) {
							dItem[item.date] = '';
						}
					});
				}
			});
		});
	}
};

const pasteHandler = (event) => {
	// 阻止默认行为,如果需要的话
	event.preventDefault();

	// 获取粘贴板的数据
	var clipboardData = event.clipboardData || window.clipboardData;
	var pastedData = clipboardData.getData('Text');

	// if (!pastedData || pastedData === 'pastedData') {
	// 	return false;
	// }

	selectData.forEach((item) => {
		tableData.value.forEach((cItem) => {
			if (cItem.windowId === item.windowId) {
				if (!cItem.date.length) {
					cItem.date[0] = { [item.date]: pastedData };
				} else {
					cItem.date.forEach((dItem) => {
						if (!dItem[item.date]) {
							dItem[item.date] = pastedData;
						} else {
							if (hasOwnProperty(dItem, item.date)) {
								dItem[item.date] = pastedData;
							}
						}
					});
				}
			}
		});
	});
};
</script>
<style lang="less" scoped>
.personnel-scheduling {
	:deep(.el-table) {
		.el-table__inner-wrapper {
			.el-table__header-wrapper {
				.el-table__header {
					.el-table__cell {
						&.is-week {
							color: #f56c6c;
						}
					}
				}
			}

			.el-table__body-wrapper {
				.el-table__body {
					.el-table__cell {
						&.selected {
							color: #fff;
							background: #409eff;
						}
					}
				}
			}
		}
	}
}
</style>