本项目依赖于https://www.npmjs.com/package/publicis-common
实现原理依赖于getClientRects() 方法返回一个指向客户端中每一个盒子的边界矩形的矩形集合,需要修改table数据源格式,即每个单元格都是一个小对象,通过单元格独一无二的name值实现定位
<template>
<div class="table-box active" @mousedown="handleMouseDown" v-contextmenu.dark="contextmenus">
<PocTable
v-loading="loading"
:data="tableData"
cell-class-name="week-data-td"
:cell-style="changeCell"
@cell-dblclick="handleEdit"
:current-page.sync="pageConfig.pageNum"
:total="pageConfig.total"
@size-change="pageSizeChange"
@page-current-change="pageChange">
<el-table-column
v-for="item in columnConfig"
:key="item.key"
:prop="item.key"
:label="item.name"
:fixed="item.fixed"
:sorter="item.sortable"
:width="item.width">
<template slot-scope="scope">
<div class="cell-key" :name="scope.row[item.key].key">
<span v-if="!scope.row[item.key].edit">{{scope.row[item.key].value}}</span>
<select @blur="handleCancelEdit(scope.row[item.key],scope.$index)" v-else type="text" v-model="scope.row[item.key].value">
<option label="111111111" value="1"></option>
<option label="1222222222" value="2"></option>
</select>
</div>
</template>
</el-table-column>
</PocTable>
<div
id="mask-background"
class="mask"
v-show="is_show_mask"
:style="'width:'+mask_width()+'left:'+mask_left()+'height:'+mask_height()+'top:'+mask_top()"
></div>
</div>
</template>
<script>
export default {
mixins: [$PCommon.TableMixin],
data() {
return {
loading: false,
columnConfig: [
{
key: 'a',
name: 'a',
dataType: 'number', // 字段的数值类型,字符串(string)、数字(number)、日期(date)、布尔(boolean)。
visible: true, // 是否展示在表格列中
sortable: true, // 是否可以排序
fixed: 'left', // 固定列的位置(left, right)
width: 200, // 默认宽度,像素
},
{
key: 'b',
name: 'b',
dataType: 'string',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'c',
name: 'c',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'd',
name: 'd',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'e',
name: 'e',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'f',
name: 'f',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'g',
name: 'g',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'h',
name: 'h',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'i',
name: 'i',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'j',
name: 'j',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'k',
name: 'k',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'l',
name: 'l',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'm',
name: 'm',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'n',
name: 'n',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'o',
name: 'o',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'p',
name: 'p',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'q',
name: 'q',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
{
key: 'r',
name: 'r',
dataType: 'number',
visible: true,
sortable: true,
fixed: null,
width: 200,
},
],
tableData: [],
start_x: 0,
start_y: 0,
end_x: 0,
end_y: 0,
box_screen_left: '60', //框选盒子距左边距离
box_screen_top: '60', //框选盒子距顶部距离
is_show_mask: false,
// rightmenus: false,
};
},
mounted() {
this.queryApi();
},
methods: {
// 右键菜单
contextmenus() {
// this.rightmenus = true;
return [
{
text: this.$t('nv:标记为未上刊'),
action: () => {this.handleSign(0)}
},
{
text: this.$t('nv:标记为上刊'),
action: () => {this.handleSign(1)}
},
]
},
handleSign(value) {
// this.rightmenus = false;
},
queryApi() {
this.loading = true;
this.tableData = [];
const list = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r']
window.setTimeout(() => {
for (let i = 0; i < 4; i++) {
const item = {};
list.forEach((v,index) => {
item[v] = {
key: `${v + Math.random() * 10}`,
value: `${v + Math.random() * 10}`,
edit: false,
}
})
this.tableData.push(item);
}
this.pageConfig.total = 1;
this.loading = false;
}, 20);
},
changeCell(row, column, rowIndex, columnIndex) {
if(row.row[row.column.property].checked) {
return "background: #3af";
}else {
return "background: #FFF";
}
},
handleEdit(row, column, rowIndex, columnIndex) {
row[column.property].edit = true;
row[column.property].checked = false;
},
handleCancelEdit(item) {
item.edit = false;
},
// 框选操作
mask_width() {
return `${Math.abs(this.end_x - this.start_x)}px;`;
},
mask_height() {
return `${Math.abs(this.end_y - this.start_y)}px;`;
},
mask_left() {
return `${
Math.min(this.start_x, this.end_x) - this.box_screen_left
}px;`;
},
mask_top() {
return `${
Math.min(this.start_y, this.end_y) - this.box_screen_top
}px;`;
},
handleMouseDown(e) {
this.is_show_mask = true;
this.start_x = e.pageX;
this.start_y = e.pageY;
this.end_x = e.pageX;
this.end_y = e.pageY;
document.body.addEventListener("mousemove", this.handleMouseMove);
document.body.addEventListener("mouseup", this.handleMouseUp);
},
handleMouseMove(e) {
this.end_x = e.pageX;
this.end_y = e.pageY;
},
handleMouseUp(e) {
if(Math.abs(this.end_x - this.start_x) < 5 && Math.abs(this.end_y - this.start_y) < 5) {
// 位移不够,按点击算
document.body.removeEventListener("mousemove", this.handleMouseMove);
document.body.removeEventListener("mouseup", this.handleMouseUp);
this.is_show_mask = false;
this.resetXY();
return false;
}
document.body.removeEventListener("mousemove", this.handleMouseMove);
document.body.removeEventListener("mouseup", this.handleMouseUp);
this.is_show_mask = false;
this.handleDomSelect();
this.resetXY();
},
resetXY() {
this.start_x = 0;
this.start_y = 0;
this.end_x = 0;
this.end_y = 0;
},
handleDomSelect() {
const dom_mask = window.document.querySelector("#mask-background");
// getClientRects() 方法返回一个指向客户端中每一个盒子的边界矩形的矩形集合
const rect_select = dom_mask.getClientRects()[0];
let selectKeys = [];
document.querySelectorAll(".week-data-td").forEach((node, index) => {
if(node.className.split(/\s+/).includes('is-hidden')) {
// 隐藏单元格不错处理
return false;
}
const rects = node.getClientRects()[0];
if (this.collide(rects, rect_select) === true) {
selectKeys.push(node.getElementsByClassName('cell-key')[0].getAttribute('name'));
}
});
let tableList = JSON.parse(JSON.stringify(this.tableData));
// 单选时
if (selectKeys.length === 1) {
tableList = tableList.map((item, key) => {
for(let k in item){
if (selectKeys.indexOf(item[k].key) > -1){
item[k].checked = !item[k].checked;
}else {
item[k].checked = false;
}
}
return item;
});
this.tableData = tableList;
return false;
};
tableList = tableList.map((item, key) => {
for(let k in item){
if (selectKeys.indexOf(item[k].key) > -1){
item[k].checked = true;
}else{
item[k].checked = false;
}
}
return item;
});
this.tableData = tableList;
},
collide(rect1, rect2) {
const maxX = Math.max(rect1.x + rect1.width, rect2.x + rect2.width);
const maxY = Math.max(rect1.y + rect1.height, rect2.y + rect2.height);
const minX = Math.min(rect1.x, rect2.x);
const minY = Math.min(rect1.y, rect2.y);
if (
maxX - minX <= rect1.width + rect2.width &&
maxY - minY <= rect1.height + rect2.height
) {
return true;
} else {
return false;
}
}
},
};
</script>
<style scoped lang="scss">
#mask-background {
position: absolute;
background: #409eff;
opacity: 0.4;
z-index: 10000000000000;
}
//禁止文本选中
.table-box {
&.active {
-moz-user-select:none;
-webkit-user-select:none;
user-select:none;
}
}
</style>