一、思路
1.每次push到list数组的时候,新增一个z用来管理图层的叠放,z应该有个默认值0,如果有z值去多个z值中比较获取最大的,这样拖拽的时候,会是最高层级的的
2.样式的话z-index因为我们用的依赖,所以直接赋值就可以了
二、置顶和置底
1.首先右击的时候获取当前图层的焦点传一个item过来,然后设置一个focused状态,遍历list中的id,跟当前点击的id是否一样,一样就true,否则为flase
2.先找到数组中被选中的项,通过list中多个z值比较,最大最小,从而实现置底和置顶
//封装 左键点击,让当前项获取焦点,其他项取消焦点
clickDrageeronFoucus(currentItem) {
currentItem.focused = true;
this.list.forEach((item)=>{
item.focused = item.id === currentItem.id
})
},
//右键菜单-打开的时候获取当前焦点
onContextMenuOpen(e, item) {
this.$refs.contextMenu.open(e);//打开右键菜单
this.clickDrageeronFoucus(item);//右击的时候获取焦点
},
//封装去找最小值
findBottomLayer(currentItem) {
const minZ = Math.min(...this.list.map(item => item.z)) || 0//获取最小值
if (currentItem.z === minZ) {
alert('已经是最底层了')
return false;//因为还有一个最上层,所以改为false
}
return minZ
},
//封装去找最大值
findTopLayer(currentItem) {
const maxZ = Math.max(...this.list.map(item => item.z)) || 0//获取最大值
if (currentItem.z === maxZ) {
alert('已经是最顶层了')
return;
}
return maxZ
},
//右键菜单项-置顶
onLayerTop() {
//先找到数组当中被选中的那项
const currentItem = this.list.find(item => item.focused)
const maxZ = this.findTopLayer(currentItem)//找最大值
if (!maxZ) {//判断最大值是否存在,注意0的时候是不存在的
return;
}
currentItem.z = maxZ + 1;//
// this.sortList()//排序
// this.record()//记录
},
//右键菜单项-置底
onLayerBottom() {
//先找到数组当中被选中的那项
const currentItem = this.list.find(item => item.focused)
const minZ = this.findBottomLayer(currentItem)//找最小值
if (minZ === false) {//0是一个默认值 // 如果 !minZ 相当于!0 就为true就不对了所以要 写===false
return;
}
// 因为显示不了负数比如-1,最小只能为0,所以要做逻辑处理
if (minZ - 1 < 0) {
this.list = this.list.map(item => {
item.z -= minZ - 1;
return item
})
currentItem.z = 0
} else {
currentItem.z = minZ - 1
}
// this.sortList()//排序
// this.record()//记录
},
三、全部代码
<template>
<div id="app">
<!-- 图层列表 -->
<el-tabs v-model="activeName" class="sidebar">
<el-tab-pane label="图层列表" name="layer">
<!-- 图层列表 -->
<div class="layer" v-for="item in list" :key="item.id">
{{ item.label }}
</div>
</el-tab-pane>
<el-tab-pane label="组件列表" name="widget">
<Widget-List :list="widgetList" @onWidgetMouseDown="onWidgetMouseDown" />
</el-tab-pane>
</el-tabs>
<!-- 操作面板 -->
<div class="panel"
@dragover.prevent
@drop="onDrop" >
<Drageer v-for="(item, i) in list"
:key="item.id"
ref="widget"
class="box"
:x="item.x"
:y="item.y"
:z="item.z"
:w="item.w"
:h="item.h"
@contextmenu.native.prevent="onContextMenuOpen($event, item)"
>
<component :value="item.value" class="inner-widget" :is="item.component"
:styles="item.styles" />
</Drageer>
</div>
<!-- 样式配置区域 -->
<!-- <style-sider></style-sider> -->
<!-- 右键菜单 -->
<Context-menu ref="contextMenu">
<li>
<a href="#" @click.prevent="onLayerTop">置顶</a>
</li>
<li>
<a href="#" @click.prevent="onLayerBottom">置底</a>
</li>
<li>
<a href="#" @click.prevent="onLayerUp">上移图层</a>
</li>
<li>
<a href="#" @click.prevent="onLayerDown">下移图层</a>
</li>
<li>
<a href="#" @click.prevent="onLayerRemove">删除</a>
</li>
<!-- 可能会冒泡加stop -->
</Context-menu>
<!-- 对齐线 纵向 -->
<!-- <div></div> -->
</div>
</template>
<script>
let currentId = 0; //id计数器
let widgetX = 0;//差值
let widgetY = 0;//差值
let currentWidget = null;//获取当前点击的图层数据
//右键菜单
import ContextMenu from 'vue-context';
//出现在panel上的组件
import BarChart from '@/components/bar-chart'
import AreaChart from '@/components/area-chart'
import CustomText from '@/components/custom-text'
import CustomVideo from '@/components/custom-video'
import MianjiChart from '@/components/mianji-chart'
//左侧小组件列表
import WidgetList from '@/components/widget-list/'
//静态配置
import * as CONFIG from '@/components/constants/config'
export default {
name: 'App',
components: {
WidgetList,
BarChart,
AreaChart,
CustomText,
CustomVideo,
MianjiChart,
ContextMenu,
},
data(){
return{
activeName:'widget',
list:[],
widgetList: CONFIG.WIDGET_LTST,//组件的数据结构
}
},
methods:{
//放置组件
onDrop(e,i){
//放置的距离左侧的距离-鼠标落下鼠标距离左侧的距离=当前box的x轴位置
let x = e.offsetX - widgetX;
let y = e.offsetY - widgetY;
if (i !== undefined) {
x += this.list[i].x;
y += this.list[i].y;
}
const newItem = ({
id: currentId++,//key绑定id
x,
y,
z: !this.list.length ? 0 : Math.max(...this.list.map(item => item.z)) + 1,//因为一开是空 所以给个默认值0
...currentWidget.default,
// w:this.currentWidget.w,//盒子初始值宽
// h:this.currentWidget.h,
label: currentWidget.label,//文字
component: currentWidget.component, // 新增的组件名
type: currentWidget.type,//新增组件的类型
styles: currentWidget.styles,//新增组件的样式
});
this.list.push(newItem)
// this.clickDrageeronFoucus(newItem)
this.$refs.contextMenu.close()//关闭右键菜单
},
//在小组件鼠标落下的时候
onWidgetMouseDown(e, widget) {
//获取 鼠标距离左侧的距离
widgetX = e.offsetX;
widgetY = e.offsetY;
currentWidget = widget;//当前点击的图层数据赋值
},
//右键菜单打开事件
onContextMenuOpen(e, item) {
this.$refs.contextMenu.open(e);//打开右键菜单
this.clickDrageeronFoucus(item);//右击的时候获取焦点
},
//封装去找最小值
findBottomLayer(currentItem) {
const minZ = Math.min(...this.list.map(item => item.z)) || 0//获取最小值
if (currentItem.z === minZ) {
alert('已经是最底层了')
return false;//因为还有一个最上层,所以改为false
}
return minZ
},
//封装去找最大值
findTopLayer(currentItem) {
const maxZ = Math.max(...this.list.map(item => item.z)) || 0//获取最大值
if (currentItem.z === maxZ) {
alert('已经是最顶层了')
return;
}
return maxZ
},
//右键菜单项-置顶
onLayerTop() {
//先找到数组当中被选中的那项
const currentItem = this.list.find(item => item.focused)
const maxZ = this.findTopLayer(currentItem)//找最大值
if (!maxZ) {//判断最大值是否存在,注意0的时候是不存在的
return;
}
currentItem.z = maxZ + 1;//
// this.sortList()//排序
// this.record()//记录
},
//右键菜单项-置底
onLayerBottom() {
//先找到数组当中被选中的那项
const currentItem = this.list.find(item => item.focused)
const minZ = this.findBottomLayer(currentItem)//找最小值
if (minZ === false) {//0是一个默认值 // 如果 !minZ 相当于!0 就为true就不对了所以要写===false
return;
}
// 因为显示不了负数比如-1,最小只能为0,所以要做逻辑处理
if (minZ - 1 < 0) {
this.list = this.list.map(item => {
item.z -= minZ - 1;
return item
})
currentItem.z = 0
} else {
currentItem.z = minZ - 1
}
// this.sortList()//排序
// this.record()//记录
},
// 左键点击,让当前项获取焦点,其他项取消焦点
clickDrageeronFoucus(currentItem) {
currentItem.focused = true;
this.list.forEach((item)=>{
item.focused = item.id === currentItem.id
})
},
}
}
</script>
<style>
body {
margin: 0;
}
#app {
display: flex;
width: 100vw;
height: 100vh;
/* 可视高度的多少1vh=视窗高度的1% */
}
.sidebar {
width: 200px;
background: #e9e9e9;
}
.panel {
flex: 1;
background: #f6f6f6;
position: relative;
/* 给相对定位主要是因为 要根据panel盒子的左上角来进行绝对定位*/
}
.widget {
width: 100px;
height: 100px;
outline: 1px solid red;
font-size: 24px;
text-align: center;
line-height: 100px;
margin: 24px;
}
.box {
/* width: 100px;
height: 100px; */
/* 插件自带宽高 */
/* outline: 1px solid blue; */
outline: 1px solid rgba(0, 0, 0, 0);
position: absolute;
}
.inner-widget {
width: 100%;
height: 100%;
}
.layer {
width: 100%;
height: 50px;
line-height: 50px;
background: #e9e9e9;
}
.layer:hover {
background: #fff;
}
.currentbgm {
background: #fff;
}
.sider {
width: 200px;
background: #e9e9e9;
}
.sider.right {
width: 300px;
}
.standard-line {
width: 2px;
height: 100%;
/* background: rgba(31, 29, 29, 1); */
border-left: 2px #0f0f0f dashed;
position: absolute;
left: 200px;
}
.standard-line.correnct {
/* background: red; */
border-left: 2px red dashed;
}
#frame {
position: absolute;
outline: 2px dashed red;
}
</style>
6.效果展示