这里对子组件进行了封装,若不需要去掉拖拽函数中的 $children[0]
即可。
// 例如
this.$refs.gridlayout.$children[this.layout.length].$children[0].style.display = "none";
// ==>
this.$refs.gridlayout.$children[this.layout.length].style.display = "none";
以下是完整案例:
<template>
<grid-item
style="padding: 15px 20px 20px 20px;background-color: #fafafa;"
:i="layout.i"
:x="layout.x"
:y="layout.y"
:w="layout.w"
:h="layout.h"
:minW="layout.minW"
:minH="layout.minH"
@moved="moved"
@resized="resized"
>
<!-- @formatter:off -->
<span class="vue-remove-handle" @click="remove"><a-icon type="close" /></span>
<!-- 折线图 -->
<echarts-line ref="line" v-if="layout.t==='line'" :i="layout.i"></echarts-line>
<!-- 柱状图 -->
<echarts-bar ref="bar" v-if="layout.t==='bar'" :i="layout.i"></echarts-bar>
<!-- 散点图 -->
<echarts-scatter ref="scatter" v-if="layout.t==='scatter'" :i="layout.i"></echarts-scatter>
<!-- K-线图 -->
<echarts-k-line ref="k-line" v-if="layout.t==='k-line'" :i="layout.i"></echarts-k-line>
<!-- 饼状图 -->
<echarts-pie ref="pie" v-if="layout.t==='pie'" :i="layout.i"></echarts-pie>
<!-- 雷达图 -->
<echarts-radar ref="radar" v-if="layout.t==='radar'" :i="layout.i"></echarts-radar>
<!-- 仪表盘 -->
<echarts-gauge ref="gauge" v-if="layout.t==='gauge'" :i="layout.i"></echarts-gauge>
<!-- 漏斗图 -->
<echarts-funnel ref="funnel" v-if="layout.t==='funnel'" :i="layout.i"></echarts-funnel>
<!-- 中国地图 -->
<echarts-map ref="map" v-if="layout.t==='map'" :i="layout.i"></echarts-map>
<!-- @formatter:on -->
</grid-item>
</template>
<script>
import {GridItem} from "vue-grid-layout"
import EchartsLine from "./EchartsLine"
import EchartsBar from "./EchartsBar"
import EchartsScatter from "./EchartsScatter"
import EchartsKLine from "./EchartsKLine"
import EchartsPie from "./EchartsPie"
import EchartsGauge from "./EchartsGauge"
import EchartsRadar from "./EchartsRadar"
import EchartsFunnel from "./EchartsFunnel"
import EchartsMap from "./EchartsMap"
export default {
name: "CustomGridItem",
props: {
layout: {
type: Object,
required: false
}
},
components: {
GridItem,
EchartsLine, // 折线图
EchartsBar, // 柱状图
EchartsScatter, // 散点图
EchartsKLine, // K-线图
EchartsPie, // 饼状图
EchartsRadar, // 雷达图
EchartsGauge, // 仪表盘
EchartsFunnel, // 漏斗图
EchartsMap, // 中国地图
},
methods: {
remove() {
this.$emit('remove', this.layout.i);
},
// 放大缩小结束时触发
resized(i, w, h) {
this.$refs[this.layout.t].resized();
this.$emit('resized', i, w, h);
},
// 移动结束触发
moved(i, x, y) {
this.$emit('moved', i, x, y);
}
}
}
</script>
<style scoped>
/* @formatter:off */
.vue-grid-item:not(.vue-grid-placeholder){background:#ffffff;}
.vue-draggable-handle{position:absolute;width:20px;height:20px;top:0;left:0;background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;background-position:bottom right;padding:0 8px 8px 0;background-repeat:no-repeat;background-origin:content-box;box-sizing:border-box;cursor:pointer;}
.vue-remove-handle{position:absolute;font-size:10px;width:20px;height:20px;top:0;right:0;background-position:100% 100%;background-repeat:no-repeat;background-origin:content-box;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer;}
.vue-remove-handle i{position:absolute;right:3px;top:3px;cursor:pointer;}
.vue-grid-item .resizing{opacity:0.9;}
.vue-grid-item .static{background:#cce;}
.vue-grid-item .text{font-size:24px;text-align:center;position:absolute;top:0;bottom:0;left:0;right:0;margin:auto;height:100%;width:100%;}
.vue-grid-item .no-drag{height:100%;width:100%;}
.vue-grid-item .minMax{font-size:12px;}
</style>
<template>
<div class="gutter">
<a-row :gutter="16">
<!-- 折叠面板 -->
<a-col class="gutter-row" :span="5">
<div class="gutter-box">
<a-collapse v-model="activeKey">
<a-collapse-panel key="1" header="统计图表">
<!-- 组件生成按钮(可点击与拖拽) -->
<a-button v-for="(item, index) in charts" :key="index"
:icon="item.icon"
draggable="true"
unselectable="on"
@drag="drag(item)"
@dragend="dragend(item)"
@click="addItem(item)"
>{{ item.n }}
</a-button>
</a-collapse-panel>
</a-collapse>
</div>
</a-col>
<!-- 拖拽布局容器 -->
<a-col class="gutter-row" :span="18">
<div class="gutter-box drag-content" id="content">
<grid-layout
ref="gridlayout"
:layout.sync="layout"
:col-num="grid.colNum"
:row-height="grid.rowHeight"
:max-rows="grid.maxRows"
:is-draggable="grid.draggable"
:is-resizable="grid.resizable"
:is-mirrored="grid.isMirrored"
:vertical-compact="grid.verticalCompact"
:margin="grid.margin"
:use-css-transforms="grid.useCssTransforms"
@layout-updated="layoutUpdatedEvent"
@layout-ready="layoutReadyEvent"
>
<!-- 布局组件 -->
<custom-grid-item
v-for="item in layout" :key="item.i"
:layout="item"
@moved="itemMoved"
@resized="itemResized"
@remove="removeItem"
></custom-grid-item>
</grid-layout>
</div>
</a-col>
</a-row>
</div>
</template>
<script>
import {GridLayout} from "vue-grid-layout"
import CustomGridItem from "./module/CustomGridItem"
export default {
components: {
GridLayout,
CustomGridItem
},
data() {
return {
mouseXY: {x: 0, y: 0}, // 拖拽鼠标定位
DragPos: {x: 0, y: 0}, // 拖拽组件定位
// 拖拽布局组件信息
layout: [
{i: this.getUUID(3), x: 0, y: 0, w: 3, h: 2, minW: 2, minH: 1, t: 'line'},
],
// 拖拽布局参数
grid: {
colNum: 6, // 网格列数
maxRows: 20, // 表示网格中最大行数
rowHeight: 150, // 网格每行的高度(以像素为单位)数字
margin: [10, 10], // 网格之间的边距
isMirrored: false, // RTL/LTR 的转换
useCssTransforms: true, // 是否使用 `css` 的 `transforms` 来排版
verticalCompact: true, // 垂直方向上是否应该紧凑布局
draggable: true, // 组件是否可以拖拽
resizable: true, // 组件是否可以调整大小
},
// 可拖拽图表整理
charts: [
{t: 'line', icon: 'line-chart', n: '折线图', w: 3, h: 2, minW: 2, minH: 1},
{t: 'bar', icon: 'bar-chart', n: '柱状图', w: 3, h: 2, minW: 2, minH: 1},
{t: 'scatter', icon: 'dot-chart', n: '散点图', w: 3, h: 2, minW: 2, minH: 1},
{t: 'k-line', icon: 'sliders', n: 'K-线图', w: 3, h: 2, minW: 2, minH: 1},
{t: 'pie', icon: 'pie-chart', n: '饼状图', w: 3, h: 2, minW: 2, minH: 1},
{t: 'radar', icon: 'radar-chart', n: '雷达图', w: 3, h: 2, minW: 2, minH: 1},
{t: 'gauge', icon: 'fund', n: '仪表盘', w: 3, h: 2, minW: 2, minH: 1},
{t: 'funnel', icon: 'heat-map', n: '漏斗图', w: 3, h: 2, minW: 2, minH: 1},
{t: 'map', icon: 'area', n: '中国地图', w: 6, h: 4, minW: 3, minH: 2},
],
activeKey: ['1'], // 折叠面板状态,默认展开
};
},
mounted() {
const _this = this;
// 监听拖拽事件,实时获取鼠标定位
document.addEventListener("dragover", function (e) {
_this.mouseXY.x = e.clientX;
_this.mouseXY.y = e.clientY;
}, false);
},
methods: {
// 点击添加新组件(空位添加新元素)
addItem(item) {
// 初始化元素
let newItem = {
i: this.getUUID(3),
x: 0,
y: 0,
w: item.w,
h: item.h,
minW: item.minW,
minH: item.minH,
t: item.t
}
// 确定边界
let Ys = [], maxX = 0, maxY = 0, edgeX = 0, edgeY = 0
this.layout.map(l => {
Ys.push(l.y + l.h)
})
maxY = Ys.length && Math.max.apply(null, Ys) || 1
edgeX = this.grid.colNum
edgeY = maxY
// 使用二维数组生成地图
let gridMap = []
for (let x = 0; x < edgeX; x++) {
gridMap[x] = []
for (let y = 0; y < edgeY; y++) {
gridMap[x][y] = 0
}
}
// 标记占位
this.layout.map(l => {
// 将layout中卡片所占区域标记为1
for (let x = l.x; x < (l.x + l.w); x++) {
for (let y = l.y; y < (l.y + l.h); y++) {
gridMap[x][y] = 1
}
}
})
// 遍历地图,申请位置
for (let y = 0; y < edgeY; y++) {
for (let x = 0; x < edgeX; x++) {
// 申请所需空间
if (edgeX - x >= item.w && edgeY - y >= item.h) {
let itemSignArr = []
for (let a = x; a < (x + item.w); a++) {
for (let b = y; b < (y + item.h); b++) {
itemSignArr.push(gridMap[x][y])
}
}
if (itemSignArr.indexOf(1) < 0) {
newItem.x = x
newItem.y = y
this.layout.push(newItem)
return
}
}
}
}
// 无满足条件
newItem.x = 0
newItem.y = edgeY + 1
this.layout.push(newItem)
},
// 组件更新完成生命周期
layoutReadyEvent(newLayout) {
// console.log("Ready", this.layout);
},
// 布局更新时间
layoutUpdatedEvent() {
// console.log("Updated", this.layout);
},
// 移除组件
removeItem(i) {
// console.log('Destroy ', i)
if (i) {
// 根据id移除组件
const layout = this.layout.filter(l => l.i === i);
const index = this.layout.indexOf(layout[0]);
index > -1 && this.layout.splice(index, 1);
}
},
// 组件放大缩小结束时触发
itemResized(i, w, h) {
console.log('Resized', i, 'layout:' + 'w:' + w + ', h:' + h)
},
// 组件移动结束触发
itemMoved(i, x, y) {
console.log('Moved', i, 'layout:' + 'x:' + x + ', y:' + y)
},
// 拖拽生产新组件 - 拖拽回调事件
drag(item) {
let mouseInGrid = false;
// 获取拖拽组件父容器定位数据
let parentRect = document.getElementById('content').getBoundingClientRect();
// 检测鼠标是否到容器内部
if (((this.mouseXY.x > parentRect.left) && (this.mouseXY.x < parentRect.right)) &&
((this.mouseXY.y > parentRect.top) && (this.mouseXY.y < parentRect.bottom))) {
mouseInGrid = true;
}
// 检测是否有临时拖拽演示组件,若无则添加到最后
let index = this.layout.findIndex(item => item.i === 'drop');
if (mouseInGrid === true && index === -1) {
this.layout.push({
i: 'drop',
x: (this.layout.length * 2) % (this.grid.colNum || 12),
y: this.layout.length + (this.grid.colNum || 12),
w: item.w,
h: item.h,
});
// 再次检测是否有临时拖拽演示组件
index = this.layout.findIndex(item => item.i === 'drop');
}
// 当检测临时拖拽演示组件不为空时,根据鼠标移动实时更新组件定位信息以达到拖拽演示效果
if (index !== -1) {
try {
// 拖拽时隐藏主体内容,只显示拖拽留影
this.$refs.gridlayout.$children[this.layout.length].$children[0].style.display = "none";
} catch {
}
// 获取临时拖拽演示组件对象
let el = this.$refs.gridlayout.$children[index];
// 拖拽定位变更
el.dragging = {"top": this.mouseXY.y - parentRect.top, "left": this.mouseXY.x - parentRect.left};
// 计算获取容器内定位
let new_pos = (el.calcXY ? el : el.$children[0]).calcXY(
this.mouseXY.y - parentRect.top - (parentRect.height / 4),
this.mouseXY.x - parentRect.left - (parentRect.width / 4),
);
// 鼠标移入容器时更新容器内组件定位
if (mouseInGrid === true) {
this.$refs.gridlayout.dragEvent('dragstart', 'drop', new_pos.x, new_pos.y, item.h, item.w);
this.DragPos.i = String(index);
this.DragPos.x = this.layout[index].x;
this.DragPos.y = this.layout[index].y;
}
// 鼠标移出容器时移除演示组件
if (mouseInGrid === false) {
this.$refs.gridlayout.dragEvent('dragend', 'drop', new_pos.x, new_pos.y, item.h, item.w);
this.layout = this.layout.filter(obj => obj.i !== 'drop');
}
}
},
// 拖拽生产新组件 - 拖拽结束回调事件
dragend(item) {
let mouseInGrid = false;
// 获取拖拽组件父容器定位数据
let parentRect = document.getElementById('content').getBoundingClientRect();
// 检测鼠标是否到容器内部
if (((this.mouseXY.x > parentRect.left) && (this.mouseXY.x < parentRect.right)) &&
((this.mouseXY.y > parentRect.top) && (this.mouseXY.y < parentRect.bottom))) {
mouseInGrid = true;
}
// 拖拽结束时鼠标在容器内部时
if (mouseInGrid === true) {
this.$refs.gridlayout.dragEvent('dragend', 'drop', this.DragPos.x, this.DragPos.y, item.h, item.w);
// 移除演示组件
this.layout = this.layout.filter(obj => obj.i !== 'drop');
// 添加一个正式的拖拽组件
this.layout.push({
i: this.getUUID(3),
x: this.DragPos.x,
y: this.DragPos.y,
w: item.w,
h: item.h,
minW: item.minW,
minH: item.minH,
t: item.t,
});
}
},
// 生成一个不重复的ID
getUUID(randomLength) {
return Number(Math.random().toString().substr(2, randomLength) + Date.now()).toString(36)
}
}
};
</script>
<style scoped>
/* @formatter:off */
*{touch-action:none;}
.gutter >>> .ant-row > div{background:transparent;border:0;}
.gutter-box{color:rgba(0,0,0,0.65);background-color:#fff;min-height:600px;border:1px solid #ccc;overflow:hidden;padding:5px;}
.ant-collapse-content .ant-btn{width:102px;margin:5px;cursor:move;}
.vue-grid-layout{background:#ffffff;}
</style>