!183 大屏设计多组件对齐和拖拽移动实现

Merge pull request !183 from JiangHH/dev
This commit is contained in:
Foming
2025-01-04 05:14:33 +00:00
committed by Gitee
15 changed files with 394 additions and 12 deletions

View File

@@ -0,0 +1,81 @@
## 多组件对齐和拖拽移动功能
注意前端版本 vue版本
1.多组件选中实现
2.对齐实现
3.拖拽实现
### 1、多组件选中
1. Ctrl键
2. 鼠标框选
3. 组合
#### 方式1 Ctrl键实现多选
说明:
1. 第一次单击组件,会默认把选中的组件加入到已选中的组件集合中.
2. 按住Ctrl键选中的组件会加入到已选中的组件集合中.
3. 按住Ctrl键选中的组件如果已选中的组件中包含该组件则该组件取消选中.
4. 点击大屏其他位置(非组件),会把选中的组件清空.
测试截图:
![](.\picture\img_01.png)
#### 方式2 鼠标框选实现多选
说明:
1. 鼠标 (按下,移动,释放)生成矩形框,跟矩形框相交的组件会被选中.
2. 组合方式多选的情况下,框选之前已选中的组件不会加入(除非框选也有).
3. 框选的组件也支持按住Ctrl键取消选中.
4. 点击大屏其他位置(非组件),会把选中的组件清空.
1. 鼠标按下,移动,释放,会生成一个矩形框,跟矩形框相交的组件爱你,会被选中.
2. 2.组合方式多选的情况下,框选之前已选中的组件不会加入(除非框选也有).
3. 3.框选的组件也支持按住Ctrl键取消选中.
4. 4.点击大屏其他位置(非组件),会把选中的组件清空.
- 1.鼠标按下,移动,释放,会生成一个矩形框,跟矩形框相交的组件爱你,会被选中.
测试截图:
![](.\picture\img_02.png)
![](.\picture\img_03.png)
![](.\picture\img_04.png)
### 2、多组件对齐
单选右键菜单
![](.\picture\img_05.png)
多选右键菜单
![](.\picture\img_06.png)
#### 左对齐/右对齐/居中对齐
选择左对齐 (以最上边的组件为标准对齐)---不合适自己可以修改代码
![](.\picture\img_07.png)
#### 上对齐/下对齐/居中对齐
选择上对齐(以最左边的组件为标准对齐)----不合适自己可以修改代码
![](.\picture\img_08.png)
![](.\picture\img_09.png)
### 3、多组件移动拖拽
#### 多选状态
![](.\picture\img_10.png)
#### 拖拽后
![](.\picture\img_11.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -258,6 +258,13 @@ const mixin = {
display: "block",
};
}
//设置多选和单选的菜单项展示
document.getElementsByName("singleSelect").forEach(e=>{
e.style.display= this.selectedWidgets.length >= 2 ?"none":"block";
});
document.getElementsByName("mulSelect").forEach(e=>{
e.style.display= this.selectedWidgets.length >= 2 ?"block":"none";
})
this.visibleContentMenu = true;
return false;
},
@@ -268,7 +275,11 @@ const mixin = {
},
// 删除
deletelayer() {
let _this = this;
this.widgets.splice(this.rightClickIndex, 1);
this.selectedWidgets.forEach(sw=>{
_this.widgets = _this.widgets.filter(w=>w.value.widgetId !== sw.value.widgetId);
})
},
// 锁定
lockLayer() {
@@ -329,6 +340,69 @@ const mixin = {
} else {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
},
//对齐
alignment(align) {
if(this.selectedWidgets.length <= 1) {
this.$message.error("请至少选择两个组件对齐");
return;
}
console.log("对齐方式:" + align);
let topWidget = this.selectedWidgets[0]; //最上组件(左右对齐使用)
let leftWidget = this.selectedWidgets[0]; //最左组件(上下对齐使用)
let minTop = this.selectedWidgets[0].value.position.top;
let minLeft = this.selectedWidgets[0].value.position.left;
//如果是框选的话,以【最上组件】【最左组件】为标准对齐;如果是Ctrl多选方式则以第一个选中的组件为标准对齐组合多选以框选逻辑为准
if(this.kuangSelectFlag){
for(let i = 0; i< this.selectedWidgets.length; i++){
let widget = this.selectedWidgets[i];
if( minTop > widget.value.position.top){
minTop = widget.value.position.top;
topWidget = widget;
}
if( minLeft > widget.value.position.left){
minLeft = widget.value.position.left;
leftWidget = widget;
}
}
}
for(let i = 0; i< this.selectedWidgets.length; i++){
let widget = this.selectedWidgets[i];
this.$refs.widgets.forEach(w=>{
if(w.value.widgetId === widget.value.widgetId){
w.$refs.draggable.setActive(false);
}
});
this.widgets.forEach(w=>{
if(w.value.widgetId === widget.value.widgetId){
switch (align){
case "left": //左对齐
w.value.position.left = topWidget.value.position.left;
break;
case "right": //右对齐
w.value.position.left = topWidget.value.position.left + topWidget.value.position.width - w.value.position.width;
break;
case "horizontal_center": //左右居中对齐
w.value.position.left = topWidget.value.position.left + topWidget.value.position.width/2 - w.value.position.width /2;
break;
case "top": //上对齐
w.value.position.top = leftWidget.value.position.top;
break;
case "bottom": //下对齐
w.value.position.top = leftWidget.value.position.top + leftWidget.value.position.height - w.value.position.height;
break;
case "vertical_center": //上下居中对齐
w.value.position.top = leftWidget.value.position.top + leftWidget.value.position.height/2 - w.value.position.height /2;
break;
}
}
});
}
this.selectedWidgets = [];
if(this.rect){
document.getElementById("workbench").removeChild(this.rect);
}
this.kuangSelectFlag = false;
}
}
}

View File

@@ -10,27 +10,45 @@
<div class="contentmenu__item" @click="deleteLayer">
<i class="iconfont iconguanbi"></i> 删除图层
</div>
<div class="contentmenu__item" @click="lockLayer">
<div class="contentmenu__item" @click="lockLayer" name="singleSelect">
<i class="iconfont iconfuzhi1"></i> 锁定图层
</div>
<div class="contentmenu__item" @click="noLockLayer">
<div class="contentmenu__item" @click="noLockLayer" name="singleSelect">
<i class="iconfont iconfuzhi1"></i> 解除锁定
</div>
<div class="contentmenu__item" @click="copyLayer">
<div class="contentmenu__item" @click="copyLayer" name="singleSelect">
<i class="iconfont iconfuzhi1"></i> 复制图层
</div>
<div class="contentmenu__item" @click="istopLayer">
<div class="contentmenu__item" @click="istopLayer" name="singleSelect">
<i class="iconfont iconjinlingyingcaiwangtubiao01"></i> 置顶图层
</div>
<div class="contentmenu__item" @click="setlowLayer">
<div class="contentmenu__item" @click="setlowLayer" name="singleSelect">
<i class="iconfont iconleft-copy"></i> 置底图层
</div>
<div class="contentmenu__item" @click="moveupLayer">
<div class="contentmenu__item" @click="moveupLayer" name="singleSelect">
<i class="iconfont iconjinlingyingcaiwangtubiao01"></i> 上移一层
</div>
<div class="contentmenu__item" @click="movedownLayer">
<div class="contentmenu__item" @click="movedownLayer" name="singleSelect">
<i class="iconfont iconleft-copy"></i> 下移一层
</div>
<div class="contentmenu__item" @click="alignment('left')" name="mulSelect">
<i class="iconfont iconzhankai"></i> 左对齐
</div>
<div class="contentmenu__item" @click="alignment('right')" name="mulSelect">
<i class="iconfont iconzhankai"></i> 右对齐
</div>
<div class="contentmenu__item" @click="alignment('horizontal_center')" name="mulSelect">
<i class="iconfont iconzhankai"></i> 左右居中对齐
</div>
<div class="contentmenu__item" @click="alignment('top')" name="mulSelect">
<i class="iconfont iconzhankai"></i> 上对齐
</div>
<div class="contentmenu__item" @click="alignment('bottom')" name="mulSelect">
<i class="iconfont iconzhankai"></i> 下对齐
</div>
<div class="contentmenu__item" @click="alignment('vertical_center')" name="mulSelect">
<i class="iconfont iconzhankai"></i> 上下居中对齐
</div>
</div>
</template>
<script>
@@ -96,6 +114,9 @@ export default {
movedownLayer() {
this.$emit("movedownLayer");
},
alignment(align){
this.$emit("alignment",align);
}
},
};
</script>

View File

@@ -54,7 +54,7 @@
:key="'item' + index"
class="tools-item"
:class="widgetIndex == index ? 'is-active' : ''"
@click="layerClick(index)"
@click="layerClick($event,index)"
>
<span class="tools-item-icon">
<i class="iconfont" :class="item.icon"></i>
@@ -275,6 +275,9 @@
@click.self="setOptionsOnClickScreen"
@drop="widgetOnDragged($event)"
@dragover="dragOver($event)"
@mousedown.self="downEvent($event)"
@mouseup="upEvent($event)"
@mousemove="moveEvent($event)"
>
<div v-if="grade" class="bg-grid"></div>
<widget
@@ -288,7 +291,7 @@
:bigscreen="{ bigscreenWidth, bigscreenHeight }"
@onActivated="setOptionsOnClickWidget"
@contextmenu.prevent.native="rightClick($event, index)"
@mousedown.prevent.native="widgetsClick(index)"
@mousedown.prevent.native="widgetsClick($event, index)"
@mouseup.prevent.native="grade = false"
/>
</div>
@@ -351,6 +354,7 @@
@setlowLayer="setlowLayer"
@moveupLayer="moveupLayer"
@movedownLayer="movedownLayer"
@alignment="alignment($event)"
/>
</div>
</template>
@@ -416,6 +420,18 @@ export default {
currentSizeRangeIndex: -1, // 当前是哪个缩放比分比,
currentWidgetTotal: 0,
widgetParamsConfig: [], // 各组件动态数据集的参数配置情况
selectedWidgets: [], //多选组件集合
moveTimes: 0, //鼠标移动次数
selectFlag: false, //选择标识
kuangSelectFlag: false, //框选标识
downX: 0, //移动开始X坐标
downY: 0, //移动开始Y坐标
downX2: 0, //移动结束X坐标
downY2: 0, //移动结束Y坐标
rect : null, //框选矩形对象
openMulDrag: false, //批量拖拽开关
moveWidgets:{}, //记录移动的组件的起始left和top属性
};
},
computed: {
@@ -621,12 +637,20 @@ export default {
});
}
},
layerClick(index) {
layerClick(event,index) {
this.widgetIndex = index;
this.widgetsClick(index);
this.widgetsClick(event,index);
},
// 如果是点击大屏设计器中的底层,加载大屏底层属性
setOptionsOnClickScreen() {
console.log("setOptionsOnClickScreen");
if(this.selectedWidgets.length > 0 && this.kuangSelectFlag){
//如果Ctrl多选过程中点击了大屏底层就清空 this.selectedWidgets
return;
}
this.selectFlag = false;
this.kuangSelectFlag = false;
this.selectedWidgets = [];
this.screenCode = "screen";
// 选中不同的组件 右侧都显示第一栏
this.activeName = "first";
@@ -653,7 +677,42 @@ export default {
});
this.widgetOptions = this.deepClone(this.widgets[obj.index]["options"]);
},
widgetsClick(index) {
widgetsClick(event,index) {
console.log("widgetsClick");
//判断是否按住了Ctrl按钮表示Ctrl多选
let _this = this;
let eventWidget = event.currentTarget.__vue__.$parent;//vue3已经弃用__vue__
if(event.ctrlKey){ //Ctrl左键选中或者取消选中
if(this.selectedWidgets.includes(eventWidget)){
this.selectedWidgets = this.selectedWidgets.filter(w=> w!== eventWidget);
this.$refs.widgets.forEach(w=>{
if(eventWidget.value.widgetId === w.value.widgetId){
setTimeout(function (){
_this.$refs.widgets[index].$refs.draggable.setActive(false);
console.log("触发取消选中, eventWidget.value.widgetId = " + eventWidget.value.widgetId +", w.value.widgetId= "+ w.value.widgetId);
},200); //设置超时,防止效果被覆盖
}
})
return;
}
this.widgetsClickAndCtrl(event, index);
return;
}
if(this.selectedWidgets.includes(eventWidget)){ //右键点击菜单的时候 批量拖拽的时候
this.openMulDrag = true;
this.moveWidgets = {};
for (let i = 0; i < this.$refs.widgets.length; i++) {
let widget = {
left: this.$refs.widgets[i].value.position.left,
top: this.$refs.widgets[i].value.position.top
};
this.moveWidgets[this.$refs.widgets[i].value.widgetId] = widget;
}
this.calculateMousePosition(event, true);
return;
}
this.selectedWidgets = []; //单选的时候需要清空
this.selectedWidgets.push(this.$refs.widgets[index]); //确保第一个选中的组件添加到集合不需要按住Ctrl键
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
if (i == index) {
@@ -665,6 +724,15 @@ export default {
this.setOptionsOnClickWidget(index);
this.grade = true;
},
//Ctrl鼠标点击事件
widgetsClickAndCtrl(event, index) {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
if (i === index && ! this.selectedWidgets.includes(this.$refs.widgets[i])) {
this.selectedWidgets.push(this.$refs.widgets[i]); //选中的添加到集合
}
}
},
handleMouseDown() {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
@@ -734,6 +802,144 @@ export default {
evt.preventDefault();
this.widgets = this.swapArr(this.widgets, evt.oldIndex, evt.newIndex);
},
//计算鼠标坐标
calculateMousePosition(event, isStart){
let workbenchPosition = this.getDomTopLeftById("workbench");
let widgetTopInWorkbench = event.clientY - workbenchPosition.top;
let widgetLeftInWorkbench = event.clientX - workbenchPosition.left;
const targetScale =
this.currentSizeRangeIndex === this.defaultSize.index
? this.bigscreenScaleInWorkbench
: this.sizeRange[this.currentSizeRangeIndex] / 100;
const x = widgetLeftInWorkbench / targetScale;
const y = widgetTopInWorkbench / targetScale;
if(isStart){
this.downX = x;
this.downY = y;
}else{
this.downX2 = x;
this.downY2 = y;
}
},
//鼠标按下事件
downEvent(event){
console.log("downEvent")
this.moveTimes = 0;
this.selectedWidgets = [];
this.openMulDrag = false;
this.selectFlag = true;
this.kuangSelectFlag = false; //框选标志
//鼠标位置
this.calculateMousePosition(event, true)
if(this.rect != null){
document.getElementById("workbench").removeChild(this.rect);
this.rect = null;
}
},
//鼠标移动事件
moveEvent(event){
console.log("moveEvent");
//测试的时候发现每次点击组件再次点击大屏的时候偶尔会触发一次moveEvent,导致会生成rect所以加了移动次数moveTimes 变量控制一下,只有移动多次的情况下,才能说明是框选多选
if(this.selectFlag && this.selectedWidgets.length <= 1 && this.moveTimes >= 1){
if(this.rect === null){
//这里说明一下为啥不在downEvent方法中创建是因为
this.rect = document.createElement("div");
this.rect.style.cssText = "position:absolute; width:0px; height:0px; font-size:0px; margin:0px; border: 1px dashed #0099FF; background-color: #C3D5ED";
this.rect.id = "selectedDiv";
this.rect.style.left = this.downX +"px";
this.rect.style.top = this.downY+"px";
this.rect.style.left = this.downX;
this.rect.style.top = this.downY;
}
document.getElementById("workbench").appendChild(this.rect);
this.calculateMousePosition(event, false);
if(this.rect.style.display === "none"){
this.rect.style.display = "";
}
this.rect.style.left = Math.min(this.downX, this.downX2) + "px";
this.rect.style.top = Math.min(this.downY, this.downY2) + "px";
this.rect.style.width = Math.abs(this.downX - this.downX2) + "px";
this.rect.style.height = Math.abs(this.downY - this.downY2) + "px";
if(this.downX2 < this.downX && this.downY2 < this.downY){
this.rect.style.left = this.downX2;
this.rect.style.top = this.downY2;
}
if(this.downX2 > this.downX2 && this.downY2 < this.downY){
this.rect.style.left = this.downX;
this.rect.style.top = this.downY2;
}
if(this.downX2 < this.downX && this.downY2 > this.downY){
this.rect.style.left = this.downX2;
this.rect.style.top = this.downY;
}
if(this.downX2 > this.downX2 && this.downY2 > this.downY){
this.rect.style.left = this.downX;
this.rect.style.top = this.downY;
}
}
if (this.openMulDrag) {
this.mulWidgetMove(event);
}
this.moveTimes++;
},
//批量拖拽移动
mulWidgetMove(event){
let _this = this;
if(this.openMulDrag && this.selectedWidgets.length >= 2){
this.calculateMousePosition(event, false);
setTimeout(function (){
_this.selectedWidgets.forEach(sw=>{
for (let i = 0; i < _this.$refs.widgets.length; i++) {
if(sw.value.widgetId === _this.$refs.widgets[i].value.widgetId){
_this.$refs.widgets[i].value.position.left = _this.moveWidgets[_this.$refs.widgets[i].value.widgetId].left + (_this.downX2 - _this.downX);
_this.$refs.widgets[i].value.position.top = _this.moveWidgets[_this.$refs.widgets[i].value.widgetId].top + (_this.downY2 - _this.downY);
}
}
});
},50);
}
},
upEvent(event){
console.log("upEvent")
if(this.selectFlag && this.selectedWidgets.length === 0 && this.rect !== null){
this.calculateMousePosition(event, false);
//计算选择框内的组件
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
//判断组件是否在选择框内
let widget = this.$refs.widgets[i];
if(this.intersection(widget)){
this.selectedWidgets.push(widget);
widget.$refs.draggable.setActive(true);
}
}
this.selectFlag = false;
this.kuangSelectFlag = true; //框选结束的时候
}
if(this.rect){
document.getElementById("workbench").removeChild(this.rect);
this.rect = null;
}
if(this.openMulDrag){
this.mulWidgetMove(event);
this.openMulDrag = false;
}
},
//判断矩形框与组件是否相交
intersection(widget){
return (
(widget.value.position.left - this.downX) * (widget.value.position.left - this.downX2) < 0 ||
(widget.value.position.left + widget.value.position.width - this.downX) * (widget.value.position.left + widget.value.position.width- this.downX2) < 0
)
&&
(
(widget.value.position.top - this.downY) * (widget.value.position.top - this.downY2) < 0 ||
(widget.value.position.top + widget.value.position.height - this.downY) * (widget.value.position.top + widget.value.position.height- this.downY2) < 0
);
}
},
};
</script>