- 画尺寸ruler
 

background: repeating-linear-gradient(90deg, #000 0px, transparent 1px, transparent 4px), repeating-linear-gradient(90deg, #000 0px, transparent 1px, transparent 20px), repeating-linear-gradient(#000 0px, transparent 1px, transparent 4px), repeating-linear-gradient(#000 0px, transparent 1px, transparent 20px), #e6e6e6;background-size: 100% 10px, 100% 15px, 10px 100%, 15px 100%;background-position: 40px 0, 40px 0, 0 40px, 0 40px;background-repeat: no-repeat;
- 画格子
 

background: linear-gradient(-90deg, #eee 1px, transparent 1px), linear-gradient(#eee 1px, transparent 1px), #fff;background-size: 8px 8px;background-repeat: repeat;background-position: 1px 1px, 4px 4px;
- 代码展示
 
monaco-editor
- 导出代码 ```css import JSZip from ‘jszip’; import FileSaver from ‘file-saver’; import { CodeFile } from ‘@/types/code-editor’;
 
export function exportZip(fileList: CodeFile[]) { // const blobs = codeStrArray.map(codeStr => new Blob([codeStr], {type: ‘text/plain’})); const blobs = fileList.map((file: CodeFile) => { const code = Object.values(file.codeMap).join(‘\n’); return { blob: new Blob([code], {type: ‘text/plain’}), name: file.name, }; }); const zip = JSZip(); blobs.forEach((file) => { zip.file(file.name, file.blob); }); zip.generateAsync({type: ‘blob’}).then(zipFile => { const fileName = ‘combined.zip’; return FileSaver.saveAs(zipFile, fileName); }); }
5. 组件拖拽和resize```css<template><div:style="style":class="[{[classNameActive]: enabled,[classNameDragging]: dragging,[classNameResizing]: resizing,[classNameDraggable]: draggable,[classNameResizable]: resizable,},className,]"@mousedown="elementMouseDown"@touchstart="elementTouchDown"@click="selectCurComponent"><divv-for="(handle, index) in actualHandles":key="handle + index":class="[classNameHandle, classNameHandle + '-' + handle]":style="handleStyle(handle)"@mousedown.stop.prevent="handleDown(handle, $event)"@touchstart.stop.prevent="handleTouchDown(handle, $event)"><slot :name="handle"></slot></div><slot></slot></div></template><script>import { matchesSelectorToParentElements, getComputedSize, addEvent, removeEvent } from '../utils/dom';import { computeWidth, computeHeight, restrictToBounds, snapToGrid } from '../utils/fns';const events = {mouse: {start: 'mousedown',move: 'mousemove',stop: 'mouseup'},touch: {start: 'touchstart',move: 'touchmove',stop: 'touchend'}};// 禁止用户选取const userSelectNone = {userSelect: 'none',MozUserSelect: 'none',WebkitUserSelect: 'none',MsUserSelect: 'none'};// 用户选中自动const userSelectAuto = {userSelect: 'auto',MozUserSelect: 'auto',WebkitUserSelect: 'auto',MsUserSelect: 'auto'};let eventsFor = events.mouse;export default {replace: true,name: 'vue-draggable-resizable',props: {className: {type: String,default: 'vdr'},classNameDraggable: {type: String,default: 'draggable'},classNameResizable: {type: String,default: 'resizable'},classNameDragging: {type: String,default: 'dragging'},classNameResizing: {type: String,default: 'resizing'},classNameActive: {type: String,default: 'active'},classNameHandle: {type: String,default: 'handle'},disableUserSelect: {type: Boolean,default: true},enableNativeDrag: {type: Boolean,default: false},preventDeactivation: {type: Boolean,default: false},active: {type: Boolean,default: false},draggable: {type: Boolean,default: true},resizable: {type: Boolean,default: true},// 锁定宽高比lockAspectRatio: {type: Boolean,default: false},w: {type: [Number, String],default: 200,validator: (val) => {if (typeof val === 'number') {return val > 0;}return val === 'auto';}},h: {type: [Number, String],default: 200,validator: (val) => {if (typeof val === 'number') {return val > 0;}return val === 'auto';}},minWidth: {type: Number,default: 0,validator: (val) => val >= 0},minHeight: {type: Number,default: 0,validator: (val) => val >= 0},maxWidth: {type: Number,default: null,validator: (val) => val >= 0},maxHeight: {type: Number,default: null,validator: (val) => val >= 0},x: {type: Number,default: 0},y: {type: Number,default: 0},z: {type: [String, Number],default: 'auto',validator: (val) => (typeof val === 'string' ? val === 'auto' : val >= 0)},handles: {type: Array,default: () => ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'],validator: (val) => {const s = new Set(['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml']);return new Set(val.filter(h => s.has(h))).size === val.length;}},dragHandle: {type: String,default: null},dragCancel: {type: String,default: null},axis: {type: String,default: 'both',validator: (val) => ['x', 'y', 'both'].includes(val)},grid: {type: Array,default: () => [1, 1]},parent: {type: [Boolean, String],default: false},onDragStart: {type: Function,default: () => true},onDrag: {type: Function,default: () => true},onResizeStart: {type: Function,default: () => true},onResize: {type: Function,default: () => true},// 冲突检测isConflictCheck: {type: Boolean, default: false},// 元素对齐snap: {type: Boolean, default: false},// 当调用对齐时,用来设置组件与组件之间的对齐距离,以像素为单位snapTolerance: {type: Number,default: 5,validator: function (val) {return typeof val === 'number';}},// 缩放比例scaleRatio: {type: Number,default: 1,validator: (val) => typeof val === 'number'},// handle是否缩放handleInfo: {type: Object,default: () => {return {size: 8,offset: -5,switch: true};}},componentName: {type: String,default: ''},componentKey: {type: String,default: ''},},data: function () {return {left: this.x,top: this.y,right: null,bottom: null,width: null,height: null,widthTouched: false,heightTouched: false,aspectFactor: null,parentWidth: null,parentHeight: null,minW: this.minWidth,minH: this.minHeight,maxW: this.maxWidth,maxH: this.maxHeight,handle: null,enabled: this.active,resizing: false,dragging: false,zIndex: this.z,hasMove: false,hasResize: false,};},created: function () {// eslint-disable-next-lineif (this.maxWidth && this.minWidth > this.maxWidth) console.warn('[Vdr warn]: Invalid prop: minWidth cannot be greater than maxWidth')// eslint-disable-next-lineif (this.maxWidth && this.minHeight > this.maxHeight) console.warn('[Vdr warn]: Invalid prop: minHeight cannot be greater than maxHeight')this.resetBoundsAndMouseState();},mounted: function () {if (!this.enableNativeDrag) {this.$el.ondragstart = () => false;}const [parentWidth, parentHeight] = this.getParentSize();this.parentWidth = parentWidth;this.parentHeight = parentHeight;const [width, height] = getComputedSize(this.$el);this.aspectFactor = (this.w !== 'auto' ? this.w : width) / (this.h !== 'auto' ? this.h : height);this.width = this.w !== 'auto' ? this.w : width;this.height = this.h !== 'auto' ? this.h : height;this.right = this.parentWidth - this.width - this.left;this.bottom = this.parentHeight - this.height - this.top;this.settingAttribute();addEvent(document.documentElement, 'mousedown', this.deselect);addEvent(document.documentElement, 'touchend touchcancel', this.deselect);addEvent(window, 'resize', this.checkParentSize);},beforeDestroy: function () {removeEvent(document.documentElement, 'mousedown', this.deselect);removeEvent(document.documentElement, 'touchstart', this.handleUp);removeEvent(document.documentElement, 'mousemove', this.move);removeEvent(document.documentElement, 'touchmove', this.move);removeEvent(document.documentElement, 'mouseup', this.handleUp);removeEvent(document.documentElement, 'touchend touchcancel', this.deselect);removeEvent(window, 'resize', this.checkParentSize);},methods: {// 重置边界和鼠标状态resetBoundsAndMouseState () {this.mouseClickPosition = { mouseX: 0, mouseY: 0, x: 0, y: 0, w: 0, h: 0 };this.bounds = {minLeft: null,maxLeft: null,minRight: null,maxRight: null,minTop: null,maxTop: null,minBottom: null,maxBottom: null};},// 检查父元素大小checkParentSize () {if (this.parent) {const [newParentWidth, newParentHeight] = this.getParentSize();// 修复父元素改变大小后,组件resizing时活动异常this.right = newParentWidth - this.width - this.left;this.bottom = newParentHeight - this.height - this.top;this.parentWidth = newParentWidth;this.parentHeight = newParentHeight;}},// 获取父元素大小getParentSize () {if (this.parent === true) {const style = window.getComputedStyle(this.$el.parentNode, null);return [parseInt(style.getPropertyValue('width'), 10),parseInt(style.getPropertyValue('height'), 10)];}if (typeof this.parent === 'string') {const parentNode = document.querySelector(this.parent);if (!(parentNode instanceof HTMLElement)) {throw new Error(`The selector ${this.parent} does not match any element`);}return [parentNode.offsetWidth, parentNode.offsetHeight];}return [null, null];},// 元素触摸按下elementTouchDown (e) {eventsFor = events.touch;this.elementDown(e);},elementMouseDown (e) {eventsFor = events.mouse;this.elementDown(e);},// 元素按下elementDown (e) {if (e instanceof MouseEvent && e.which !== 1 && e.which !== 3) {return;}const target = e.target || e.srcElement;if (this.$el.contains(target)) {if (this.onDragStart(e) === false) {return;}if ((this.dragHandle && !matchesSelectorToParentElements(target, this.dragHandle, this.$el)) ||(this.dragCancel && matchesSelectorToParentElements(target, this.dragCancel, this.$el))) {this.dragging = false;return;}if (!this.enabled) {this.enabled = true;this.$emit('activated', this.componentKey);this.$emit('update:active', true);}if (this.draggable) {this.dragging = true;}this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX;this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY;this.mouseClickPosition.left = this.left;this.mouseClickPosition.right = this.right;this.mouseClickPosition.top = this.top;this.mouseClickPosition.bottom = this.bottom;this.mouseClickPosition.w = this.width;this.mouseClickPosition.h = this.height;if (this.parent) {this.bounds = this.calcDragLimits();}this.hasMove = false;addEvent(document.documentElement, eventsFor.move, this.move);addEvent(document.documentElement, eventsFor.stop, this.handleUp);}},selectCurComponent (e) {// 阻止向父组件冒泡e.stopPropagation();e.preventDefault();this.$store.commit('hideContextMenu');},// 计算移动范围calcDragLimits () {return {minLeft: this.left % this.grid[0],maxLeft: Math.floor((this.parentWidth - this.width - this.left) / this.grid[0]) * this.grid[0] + this.left,minRight: this.right % this.grid[0],maxRight: Math.floor((this.parentWidth - this.width - this.right) / this.grid[0]) * this.grid[0] + this.right,minTop: this.top % this.grid[1],maxTop: Math.floor((this.parentHeight - this.height - this.top) / this.grid[1]) * this.grid[1] + this.top,minBottom: this.bottom % this.grid[1],maxBottom: Math.floor((this.parentHeight - this.height - this.bottom) / this.grid[1]) * this.grid[1] + this.bottom};},// 取消deselect (e) {const target = e.target || e.srcElement;const regex = new RegExp(this.className + '-([trmbl]{2})', '');if (!this.$el.contains(target) && !regex.test(target.className)) {if (this.enabled && !this.preventDeactivation) {this.enabled = false;this.$emit('deactivated');this.$emit('update:active', false);}removeEvent(document.documentElement, eventsFor.move, this.handleResize);}this.resetBoundsAndMouseState();},// 控制柄触摸按下handleTouchDown (handle, e) {eventsFor = events.touch;this.handleDown(handle, e);},// 控制柄按下handleDown (handle, e) {if (e instanceof MouseEvent && e.which !== 1) {return;}if (this.onResizeStart(handle, e) === false) {return;}if (e.stopPropagation) e.stopPropagation();// Here we avoid a dangerous recursion by faking// corner handles as middle handlesif (this.lockAspectRatio && !handle.includes('m')) {this.handle = 'm' + handle.substring(1);} else {this.handle = handle;}this.resizing = true;this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX;this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY;this.mouseClickPosition.left = this.left;this.mouseClickPosition.right = this.right;this.mouseClickPosition.top = this.top;this.mouseClickPosition.bottom = this.bottom;this.mouseClickPosition.w = this.width;this.mouseClickPosition.h = this.height;this.bounds = this.calcResizeLimits();this.hasResize = false;addEvent(document.documentElement, eventsFor.move, this.handleResize);addEvent(document.documentElement, eventsFor.stop, this.handleUp);},// 计算调整大小范围calcResizeLimits () {let minW = this.minW;let minH = this.minH;let maxW = this.maxW;let maxH = this.maxH;const aspectFactor = this.aspectFactor;const [gridX, gridY] = this.grid;const width = this.width;const height = this.height;const left = this.left;const top = this.top;const right = this.right;const bottom = this.bottom;if (this.lockAspectRatio) {if (minW / minH > aspectFactor) {minH = minW / aspectFactor;} else {minW = aspectFactor * minH;}if (maxW && maxH) {maxW = Math.min(maxW, aspectFactor * maxH);maxH = Math.min(maxH, maxW / aspectFactor);} else if (maxW) {maxH = maxW / aspectFactor;} else if (maxH) {maxW = aspectFactor * maxH;}}maxW = maxW - (maxW % gridX);maxH = maxH - (maxH % gridY);const limits = {minLeft: null,maxLeft: null,minTop: null,maxTop: null,minRight: null,maxRight: null,minBottom: null,maxBottom: null};if (this.parent) {limits.minLeft = left % gridX;limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX;limits.minTop = top % gridY;limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY;limits.minRight = right % gridX;limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX;limits.minBottom = bottom % gridY;limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY;if (maxW) {limits.minLeft = Math.max(limits.minLeft, this.parentWidth - right - maxW);limits.minRight = Math.max(limits.minRight, this.parentWidth - left - maxW);}if (maxH) {limits.minTop = Math.max(limits.minTop, this.parentHeight - bottom - maxH);limits.minBottom = Math.max(limits.minBottom, this.parentHeight - top - maxH);}if (this.lockAspectRatio) {limits.minLeft = Math.max(limits.minLeft, left - top * aspectFactor);limits.minTop = Math.max(limits.minTop, top - left / aspectFactor);limits.minRight = Math.max(limits.minRight, right - bottom * aspectFactor);limits.minBottom = Math.max(limits.minBottom, bottom - right / aspectFactor);}} else {limits.minLeft = null;limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX;limits.minTop = null;limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY;limits.minRight = null;limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX;limits.minBottom = null;limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY;if (maxW) {limits.minLeft = -(right + maxW);limits.minRight = -(left + maxW);}if (maxH) {limits.minTop = -(bottom + maxH);limits.minBottom = -(top + maxH);}if (this.lockAspectRatio && (maxW && maxH)) {limits.minLeft = Math.min(limits.minLeft, -(right + maxW));limits.minTop = Math.min(limits.minTop, -(maxH + bottom));limits.minRight = Math.min(limits.minRight, -left - maxW);limits.minBottom = Math.min(limits.minBottom, -top - maxH);}}return limits;},// 移动move (e) {if (this.resizing) {this.handleResize(e);} else if (this.dragging) {this.handleDrag(e);}},// 元素移动async handleDrag (e) {const axis = this.axis;const grid = this.grid;const bounds = this.bounds;const mouseClickPosition = this.mouseClickPosition;const tmpDeltaX = axis && axis !== 'y' ? mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX) : 0;const tmpDeltaY = axis && axis !== 'x' ? mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY) : 0;const [deltaX, deltaY] = snapToGrid(grid, tmpDeltaX, tmpDeltaY, this.scaleRatio);const left = restrictToBounds(mouseClickPosition.left - deltaX, bounds.minLeft, bounds.maxLeft);const top = restrictToBounds(mouseClickPosition.top - deltaY, bounds.minTop, bounds.maxTop);if (this.onDrag(left, top, this.componentKey) === false) {return;}const right = restrictToBounds(mouseClickPosition.right + deltaX, bounds.minRight, bounds.maxRight);const bottom = restrictToBounds(mouseClickPosition.bottom + deltaY, bounds.minBottom, bounds.maxBottom);this.left = left;this.top = top;this.right = right;this.bottom = bottom;await this.snapCheck();this.hasMove = true;this.$emit('dragging', this.left, this.top, this.componentKey);},moveHorizontally (val) {// eslint-disable-next-lineconst [deltaX, _] = snapToGrid(this.grid, val, this.top, this.scale)const left = restrictToBounds(deltaX, this.bounds.minLeft, this.bounds.maxLeft);this.left = left;this.right = this.parentWidth - this.width - left;},moveVertically (val) {// eslint-disable-next-lineconst [_, deltaY] = snapToGrid(this.grid, this.left, val, this.scale)const top = restrictToBounds(deltaY, this.bounds.minTop, this.bounds.maxTop);this.top = top;this.bottom = this.parentHeight - this.height - top;},// 控制柄移动handleResize (e) {console.log('handleResize-------');let left = this.left;let top = this.top;let right = this.right;let bottom = this.bottom;const mouseClickPosition = this.mouseClickPosition;// eslint-disable-next-lineconst lockAspectRatio = this.lockAspectRatioconst aspectFactor = this.aspectFactor;const tmpDeltaX = mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX);const tmpDeltaY = mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY);if (!this.widthTouched && tmpDeltaX) {this.widthTouched = true;}if (!this.heightTouched && tmpDeltaY) {this.heightTouched = true;}const [deltaX, deltaY] = snapToGrid(this.grid, tmpDeltaX, tmpDeltaY, this.scaleRatio);if (this.handle.includes('b')) {bottom = restrictToBounds(mouseClickPosition.bottom + deltaY,this.bounds.minBottom,this.bounds.maxBottom);if (this.lockAspectRatio && this.resizingOnY) {right = this.right - (this.bottom - bottom) * aspectFactor;}} else if (this.handle.includes('t')) {top = restrictToBounds(mouseClickPosition.top - deltaY,this.bounds.minTop,this.bounds.maxTop);if (this.lockAspectRatio && this.resizingOnY) {left = this.left - (this.top - top) * aspectFactor;}}if (this.handle.includes('r')) {right = restrictToBounds(mouseClickPosition.right + deltaX,this.bounds.minRight,this.bounds.maxRight);if (this.lockAspectRatio && this.resizingOnX) {bottom = this.bottom - (this.right - right) / aspectFactor;}} else if (this.handle.includes('l')) {left = restrictToBounds(mouseClickPosition.left - deltaX,this.bounds.minLeft,this.bounds.maxLeft);if (this.lockAspectRatio && this.resizingOnX) {top = this.top - (this.left - left) / aspectFactor;}}const width = computeWidth(this.parentWidth, left, right);const height = computeHeight(this.parentHeight, top, bottom);if (this.onResize(this.handle, left, top, width, height) === false) {return;}this.left = left;this.top = top;this.right = right;this.bottom = bottom;this.width = width;this.height = height;this.hasResize = true;this.$emit('resizing', this.left, this.top, this.width, this.height, this.componentKey);},changeWidth (val) {// eslint-disable-next-lineconst [newWidth, _] = snapToGrid(this.grid, val, 0, this.scale)let right = restrictToBounds((this.parentWidth - newWidth - this.left),this.bounds.minRight,this.bounds.maxRight);let bottom = this.bottom;if (this.lockAspectRatio) {bottom = this.bottom - (this.right - right) / this.aspectFactor;}const width = computeWidth(this.parentWidth, this.left, right);const height = computeHeight(this.parentHeight, this.top, bottom);this.right = right;this.bottom = bottom;this.width = width;this.height = height;},changeHeight (val) {// eslint-disable-next-lineconst [_, newHeight] = snapToGrid(this.grid, 0, val, this.scale)let bottom = restrictToBounds((this.parentHeight - newHeight - this.top),this.bounds.minBottom,this.bounds.maxBottom);let right = this.right;if (this.lockAspectRatio) {right = this.right - (this.bottom - bottom) * this.aspectFactor;}const width = computeWidth(this.parentWidth, this.left, right);const height = computeHeight(this.parentHeight, this.top, bottom);this.right = right;this.bottom = bottom;this.width = width;this.height = height;},// 从控制柄松开async handleUp () {this.handle = null;// 初始化辅助线数据const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' });const refLine = { vLine: [], hLine: [] };for (let i in refLine) { refLine[i] = JSON.parse(JSON.stringify(temArr)); }if (this.resizing) {this.resizing = false;await this.conflictCheck();this.$emit('refLineParams', refLine);this.$emit('resizestop', this.left, this.top, this.width, this.height, this.componentKey);this.hasResize && this.$store.commit('recordSnapshot');}if (this.dragging) {this.dragging = false;await this.conflictCheck();this.$emit('refLineParams', refLine);this.$emit('dragstop', this.left, this.top, this.componentKey);this.hasMove && this.$store.commit('recordSnapshot');}this.resetBoundsAndMouseState();removeEvent(document.documentElement, eventsFor.move, this.handleResize);},// 新增方法 ↓↓↓// 设置属性settingAttribute () {// 设置冲突检测this.$el.setAttribute('data-is-check', `${this.isConflictCheck}`);// 设置对齐元素this.$el.setAttribute('data-is-snap', `${this.snap}`);},// 冲突检测conflictCheck () {const top = this.top;const left = this.left;const width = this.width;const height = this.height;if (this.isConflictCheck) {const nodes = this.$el.parentNode.childNodes; // 获取当前父节点下所有子节点for (let item of nodes) {if (item.className !== undefined && !item.className.includes(this.classNameActive) && item.getAttribute('data-is-check') !== null && item.getAttribute('data-is-check') !== 'false') {const tw = item.offsetWidth;const th = item.offsetHeight;// 正则获取left与rightlet [tl, tt] = this.formatTransformVal(item.style.transform);// 左上角与右下角重叠const tfAndBr = (top >= tt && left >= tl && tt + th > top && tl + tw > left) || (top <= tt && left < tl && top + height > tt && left + width > tl);// 右上角与左下角重叠const brAndTf = (left <= tl && top >= tt && left + width > tl && top < tt + th) || (top < tt && left > tl && top + height > tt && left < tl + tw);// 下边与上边重叠const bAndT = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw);// 上边与下边重叠(宽度不一样)const tAndB = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw);// 左边与右边重叠const lAndR = (left >= tl && top >= tt && left < tl + tw && top < tt + th) || (top > tt && left <= tl && left + width > tl && top < tt + th);// 左边与右边重叠(高度不一样)const rAndL = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left + width > tl);// 如果冲突,就将回退到移动前的位置if (tfAndBr || brAndTf || bAndT || tAndB || lAndR || rAndL) {this.top = this.mouseClickPosition.top;this.left = this.mouseClickPosition.left;this.right = this.mouseClickPosition.right;this.bottom = this.mouseClickPosition.bottom;this.width = this.mouseClickPosition.w;this.height = this.mouseClickPosition.h;}}}}},// 检测对齐元素async snapCheck () {let width = this.width;let height = this.height;if (this.snap) {let activeLeft = this.left;let activeRight = this.left + width;let activeTop = this.top;let activeBottom = this.top + height;// 初始化辅助线数据const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' });const refLine = { vLine: [], hLine: [] };for (let i in refLine) { refLine[i] = JSON.parse(JSON.stringify(temArr)); }// 获取当前父节点下所有子节点const nodes = this.$el.parentNode.childNodes;let tem = {value: { x: [[], [], []], y: [[], [], []] },display: [],position: []};const { groupWidth, groupHeight, groupLeft, groupTop, bln } = await this.getActiveAll(nodes);if (!bln) {width = groupWidth;height = groupHeight;activeLeft = groupLeft;activeRight = groupLeft + groupWidth;activeTop = groupTop;activeBottom = groupTop + groupHeight;}for (let item of nodes) {if (item.className !== undefined && !item.className.includes(this.classNameActive) && item.getAttribute('data-is-snap') !== null && item.getAttribute('data-is-snap') !== 'false') {const w = item.offsetWidth;const h = item.offsetHeight;const [l, t] = this.formatTransformVal(item.style.transform);const r = l + w; // 对齐目标rightconst b = t + h; // 对齐目标的bottomconst hc = Math.abs((activeTop + height / 2) - (t + h / 2)) <= this.snapTolerance; // 水平中线const vc = Math.abs((activeLeft + width / 2) - (l + w / 2)) <= this.snapTolerance; // 垂直中线const ts = Math.abs(t - activeBottom) <= this.snapTolerance; // 从上到下const TS = Math.abs(b - activeBottom) <= this.snapTolerance; // 从上到下const bs = Math.abs(t - activeTop) <= this.snapTolerance; // 从下到上const BS = Math.abs(b - activeTop) <= this.snapTolerance; // 从下到上const ls = Math.abs(l - activeRight) <= this.snapTolerance; // 外左const LS = Math.abs(r - activeRight) <= this.snapTolerance; // 外左const rs = Math.abs(l - activeLeft) <= this.snapTolerance; // 外右const RS = Math.abs(r - activeLeft) <= this.snapTolerance; // 外右tem['display'] = [ts, TS, bs, BS, hc, hc, ls, LS, rs, RS, vc, vc];tem['position'] = [t, b, t, b, t + h / 2, t + h / 2, l, r, l, r, l + w / 2, l + w / 2];if (ts) {if (bln) {this.top = t - height;this.bottom = this.parentHeight - this.top - height;}tem.value.y[0].push(l, r, activeLeft, activeRight);}if (bs) {if (bln) {this.top = t;this.bottom = this.parentHeight - this.top - height;}tem.value.y[0].push(l, r, activeLeft, activeRight);}if (TS) {if (bln) {this.top = b - height;this.bottom = this.parentHeight - this.top - height;}tem.value.y[1].push(l, r, activeLeft, activeRight);}if (BS) {if (bln) {this.top = b;this.bottom = this.parentHeight - this.top - height;}tem.value.y[1].push(l, r, activeLeft, activeRight);}if (ls) {if (bln) {this.left = l - width;this.right = this.parentWidth - this.left - width;}tem.value.x[0].push(t, b, activeTop, activeBottom);}if (rs) {if (bln) {this.left = l;this.right = this.parentWidth - this.left - width;}tem.value.x[0].push(t, b, activeTop, activeBottom);}if (LS) {if (bln) {this.left = r - width;this.right = this.parentWidth - this.left - width;}tem.value.x[1].push(t, b, activeTop, activeBottom);}if (RS) {if (bln) {this.left = r;this.right = this.parentWidth - this.left - width;}tem.value.x[1].push(t, b, activeTop, activeBottom);}if (hc) {if (bln) {this.top = t + h / 2 - height / 2;this.bottom = this.parentHeight - this.top - height;}tem.value.y[2].push(l, r, activeLeft, activeRight);}if (vc) {if (bln) {this.left = l + w / 2 - width / 2;this.right = this.parentWidth - this.left - width;}tem.value.x[2].push(t, b, activeTop, activeBottom);}// 辅助线坐标与是否显示(display)对应的数组,易于循环遍历const arrTem = [0, 1, 0, 1, 2, 2, 0, 1, 0, 1, 2, 2];for (let i = 0; i <= arrTem.length; i++) {// 前6为Y辅助线,后6为X辅助线const xory = i < 6 ? 'y' : 'x';const horv = i < 6 ? 'hLine' : 'vLine';if (tem.display[i]) {const { origin, length } = this.calcLineValues(tem.value[xory][arrTem[i]]);refLine[horv][arrTem[i]].display = tem.display[i];refLine[horv][arrTem[i]].position = tem.position[i] + 'px';refLine[horv][arrTem[i]].origin = origin;refLine[horv][arrTem[i]].lineLength = length;}}}}this.$emit('refLineParams', refLine);}},calcLineValues (arr) {const length = Math.max(...arr) - Math.min(...arr) + 'px';const origin = Math.min(...arr) + 'px';return { length, origin };},async getActiveAll (nodes) {const activeAll = [];const XArray = [];const YArray = [];let groupWidth = 0;let groupHeight = 0;let groupLeft = 0;let groupTop = 0;for (let item of nodes) {if (item.className !== undefined && item.className.includes(this.classNameActive)) {activeAll.push(item);}}const AllLength = activeAll.length;if (AllLength > 1) {for (let i of activeAll) {const l = i.offsetLeft;const r = l + i.offsetWidth;const t = i.offsetTop;const b = t + i.offsetHeight;XArray.push(t, b);YArray.push(l, r);}groupWidth = Math.max(...YArray) - Math.min(...YArray);groupHeight = Math.max(...XArray) - Math.min(...XArray);groupLeft = Math.min(...YArray);groupTop = Math.min(...XArray);}const bln = AllLength === 1;return { groupWidth, groupHeight, groupLeft, groupTop, bln };},// 正则获取left与topformatTransformVal (string) {let [left, top] = string.replace(/[^0-9\-,]/g, '').split(',');if (top === undefined) top = 0;return [+left, +top];}},computed: {handleStyle () {return (stick) => {if (!this.handleInfo.switch) return { display: this.enabled ? 'block' : 'none' };const size = (this.handleInfo.size / this.scaleRatio).toFixed(2);const offset = (this.handleInfo.offset / this.scaleRatio).toFixed(2);const center = (size / 2).toFixed(2);const styleMap = {tl: {top: `${offset}px`,left: `${offset}px`},tm: {top: `${offset}px`,left: `calc(50% - ${center}px)`},tr: {top: `${offset}px`,right: `${offset}px`},mr: {top: `calc(50% - ${center}px)`,right: `${offset}px`},br: {bottom: `${offset}px`,right: `${offset}px`},bm: {bottom: `${offset}px`,right: `calc(50% - ${center}px)`},bl: {bottom: `${offset}px`,left: `${offset}px`},ml: {top: `calc(50% - ${center}px)`,left: `${offset}px`}};const stickStyle = {width: `${size}px`,height: `${size}px`,top: styleMap[stick].top,left: styleMap[stick].left,right: styleMap[stick].right,bottom: styleMap[stick].bottom};stickStyle.display = this.enabled ? 'block' : 'none';return stickStyle;};},style () {return {transform: `translate(${this.left}px, ${this.top}px)`,width: this.computedWidth,height: this.computedHeight,zIndex: this.zIndex,...(this.dragging && this.disableUserSelect ? userSelectNone : userSelectAuto)};},// 控制柄显示与否actualHandles () {if (!this.resizable) return [];return this.handles;},computedWidth () {if (this.w === 'auto') {if (!this.widthTouched) {return 'auto';}}return this.width + 'px';},computedHeight () {if (this.h === 'auto') {if (!this.heightTouched) {return 'auto';}}return this.height + 'px';},resizingOnX () {return (Boolean(this.handle) && (this.handle.includes('l') || this.handle.includes('r')));},resizingOnY () {return (Boolean(this.handle) && (this.handle.includes('t') || this.handle.includes('b')));},isCornerHandle () {return (Boolean(this.handle) && ['tl', 'tr', 'br', 'bl'].includes(this.handle));}},watch: {active (val) {this.enabled = val;if (val) {this.$emit('activated');} else {this.$emit('deactivated');}},z (val) {if (val >= 0 || val === 'auto') {this.zIndex = val;}},x (val) {if (this.resizing || this.dragging) {return;}if (this.parent) {this.bounds = this.calcDragLimits();}this.moveHorizontally(val);},y (val) {if (this.resizing || this.dragging) {return;}if (this.parent) {this.bounds = this.calcDragLimits();}this.moveVertically(val);},lockAspectRatio (val) {if (val) {this.aspectFactor = this.width / this.height;} else {this.aspectFactor = undefined;}},minWidth (val) {if (val > 0 && val <= this.width) {this.minW = val;}},minHeight (val) {if (val > 0 && val <= this.height) {this.minH = val;}},maxWidth (val) {this.maxW = val;},maxHeight (val) {this.maxH = val;},w (val) {if (this.resizing || this.dragging) {return;}if (this.parent) {this.bounds = this.calcResizeLimits();}this.changeWidth(val);},h (val) {if (this.resizing || this.dragging) {return;}if (this.parent) {this.bounds = this.calcResizeLimits();}this.changeHeight(val);}}};</script><template><div:style="style":class="[{[classNameActive]: enabled,[classNameDragging]: dragging,[classNameResizing]: resizing,[classNameDraggable]: draggable,[classNameResizable]: resizable,},className,]"@mousedown="elementMouseDown"@touchstart="elementTouchDown"@click="selectCurComponent"><divv-for="(handle, index) in actualHandles":key="handle + index":class="[classNameHandle, classNameHandle + '-' + handle]":style="handleStyle(handle)"@mousedown.stop.prevent="handleDown(handle, $event)"@touchstart.stop.prevent="handleTouchDown(handle, $event)"><slot :name="handle"></slot></div><slot></slot></div></template><script>import { matchesSelectorToParentElements, getComputedSize, addEvent, removeEvent } from '../utils/dom';import { computeWidth, computeHeight, restrictToBounds, snapToGrid } from '../utils/fns';const events = {mouse: {start: 'mousedown',move: 'mousemove',stop: 'mouseup'},touch: {start: 'touchstart',move: 'touchmove',stop: 'touchend'}};// 禁止用户选取const userSelectNone = {userSelect: 'none',MozUserSelect: 'none',WebkitUserSelect: 'none',MsUserSelect: 'none'};// 用户选中自动const userSelectAuto = {userSelect: 'auto',MozUserSelect: 'auto',WebkitUserSelect: 'auto',MsUserSelect: 'auto'};let eventsFor = events.mouse;export default {replace: true,name: 'vue-draggable-resizable',props: {className: {type: String,default: 'vdr'},classNameDraggable: {type: String,default: 'draggable'},classNameResizable: {type: String,default: 'resizable'},classNameDragging: {type: String,default: 'dragging'},classNameResizing: {type: String,default: 'resizing'},classNameActive: {type: String,default: 'active'},classNameHandle: {type: String,default: 'handle'},disableUserSelect: {type: Boolean,default: true},enableNativeDrag: {type: Boolean,default: false},preventDeactivation: {type: Boolean,default: false},active: {type: Boolean,default: false},draggable: {type: Boolean,default: true},resizable: {type: Boolean,default: true},// 锁定宽高比lockAspectRatio: {type: Boolean,default: false},w: {type: [Number, String],default: 200,validator: (val) => {if (typeof val === 'number') {return val > 0;}return val === 'auto';}},h: {type: [Number, String],default: 200,validator: (val) => {if (typeof val === 'number') {return val > 0;}return val === 'auto';}},minWidth: {type: Number,default: 0,validator: (val) => val >= 0},minHeight: {type: Number,default: 0,validator: (val) => val >= 0},maxWidth: {type: Number,default: null,validator: (val) => val >= 0},maxHeight: {type: Number,default: null,validator: (val) => val >= 0},x: {type: Number,default: 0},y: {type: Number,default: 0},z: {type: [String, Number],default: 'auto',validator: (val) => (typeof val === 'string' ? val === 'auto' : val >= 0)},handles: {type: Array,default: () => ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'],validator: (val) => {const s = new Set(['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml']);return new Set(val.filter(h => s.has(h))).size === val.length;}},dragHandle: {type: String,default: null},dragCancel: {type: String,default: null},axis: {type: String,default: 'both',validator: (val) => ['x', 'y', 'both'].includes(val)},grid: {type: Array,default: () => [1, 1]},parent: {type: [Boolean, String],default: false},onDragStart: {type: Function,default: () => true},onDrag: {type: Function,default: () => true},onResizeStart: {type: Function,default: () => true},onResize: {type: Function,default: () => true},// 冲突检测isConflictCheck: {type: Boolean, default: false},// 元素对齐snap: {type: Boolean, default: false},// 当调用对齐时,用来设置组件与组件之间的对齐距离,以像素为单位snapTolerance: {type: Number,default: 5,validator: function (val) {return typeof val === 'number';}},// 缩放比例scaleRatio: {type: Number,default: 1,validator: (val) => typeof val === 'number'},// handle是否缩放handleInfo: {type: Object,default: () => {return {size: 8,offset: -5,switch: true};}},componentName: {type: String,default: ''},componentKey: {type: String,default: ''},},data: function () {return {left: this.x,top: this.y,right: null,bottom: null,width: null,height: null,widthTouched: false,heightTouched: false,aspectFactor: null,parentWidth: null,parentHeight: null,minW: this.minWidth,minH: this.minHeight,maxW: this.maxWidth,maxH: this.maxHeight,handle: null,enabled: this.active,resizing: false,dragging: false,zIndex: this.z,hasMove: false,hasResize: false,};},created: function () {// eslint-disable-next-lineif (this.maxWidth && this.minWidth > this.maxWidth) console.warn('[Vdr warn]: Invalid prop: minWidth cannot be greater than maxWidth')// eslint-disable-next-lineif (this.maxWidth && this.minHeight > this.maxHeight) console.warn('[Vdr warn]: Invalid prop: minHeight cannot be greater than maxHeight')this.resetBoundsAndMouseState();},mounted: function () {if (!this.enableNativeDrag) {this.$el.ondragstart = () => false;}const [parentWidth, parentHeight] = this.getParentSize();this.parentWidth = parentWidth;this.parentHeight = parentHeight;const [width, height] = getComputedSize(this.$el);this.aspectFactor = (this.w !== 'auto' ? this.w : width) / (this.h !== 'auto' ? this.h : height);this.width = this.w !== 'auto' ? this.w : width;this.height = this.h !== 'auto' ? this.h : height;this.right = this.parentWidth - this.width - this.left;this.bottom = this.parentHeight - this.height - this.top;this.settingAttribute();addEvent(document.documentElement, 'mousedown', this.deselect);addEvent(document.documentElement, 'touchend touchcancel', this.deselect);addEvent(window, 'resize', this.checkParentSize);},beforeDestroy: function () {removeEvent(document.documentElement, 'mousedown', this.deselect);removeEvent(document.documentElement, 'touchstart', this.handleUp);removeEvent(document.documentElement, 'mousemove', this.move);removeEvent(document.documentElement, 'touchmove', this.move);removeEvent(document.documentElement, 'mouseup', this.handleUp);removeEvent(document.documentElement, 'touchend touchcancel', this.deselect);removeEvent(window, 'resize', this.checkParentSize);},methods: {// 重置边界和鼠标状态resetBoundsAndMouseState () {this.mouseClickPosition = { mouseX: 0, mouseY: 0, x: 0, y: 0, w: 0, h: 0 };this.bounds = {minLeft: null,maxLeft: null,minRight: null,maxRight: null,minTop: null,maxTop: null,minBottom: null,maxBottom: null};},// 检查父元素大小checkParentSize () {if (this.parent) {const [newParentWidth, newParentHeight] = this.getParentSize();// 修复父元素改变大小后,组件resizing时活动异常this.right = newParentWidth - this.width - this.left;this.bottom = newParentHeight - this.height - this.top;this.parentWidth = newParentWidth;this.parentHeight = newParentHeight;}},// 获取父元素大小getParentSize () {if (this.parent === true) {const style = window.getComputedStyle(this.$el.parentNode, null);return [parseInt(style.getPropertyValue('width'), 10),parseInt(style.getPropertyValue('height'), 10)];}if (typeof this.parent === 'string') {const parentNode = document.querySelector(this.parent);if (!(parentNode instanceof HTMLElement)) {throw new Error(`The selector ${this.parent} does not match any element`);}return [parentNode.offsetWidth, parentNode.offsetHeight];}return [null, null];},// 元素触摸按下elementTouchDown (e) {eventsFor = events.touch;this.elementDown(e);},elementMouseDown (e) {eventsFor = events.mouse;this.elementDown(e);},// 元素按下elementDown (e) {if (e instanceof MouseEvent && e.which !== 1 && e.which !== 3) {return;}const target = e.target || e.srcElement;if (this.$el.contains(target)) {if (this.onDragStart(e) === false) {return;}if ((this.dragHandle && !matchesSelectorToParentElements(target, this.dragHandle, this.$el)) ||(this.dragCancel && matchesSelectorToParentElements(target, this.dragCancel, this.$el))) {this.dragging = false;return;}if (!this.enabled) {this.enabled = true;this.$emit('activated', this.componentKey);this.$emit('update:active', true);}if (this.draggable) {this.dragging = true;}this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX;this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY;this.mouseClickPosition.left = this.left;this.mouseClickPosition.right = this.right;this.mouseClickPosition.top = this.top;this.mouseClickPosition.bottom = this.bottom;this.mouseClickPosition.w = this.width;this.mouseClickPosition.h = this.height;if (this.parent) {this.bounds = this.calcDragLimits();}this.hasMove = false;addEvent(document.documentElement, eventsFor.move, this.move);addEvent(document.documentElement, eventsFor.stop, this.handleUp);}},selectCurComponent (e) {// 阻止向父组件冒泡e.stopPropagation();e.preventDefault();this.$store.commit('hideContextMenu');},// 计算移动范围calcDragLimits () {return {minLeft: this.left % this.grid[0],maxLeft: Math.floor((this.parentWidth - this.width - this.left) / this.grid[0]) * this.grid[0] + this.left,minRight: this.right % this.grid[0],maxRight: Math.floor((this.parentWidth - this.width - this.right) / this.grid[0]) * this.grid[0] + this.right,minTop: this.top % this.grid[1],maxTop: Math.floor((this.parentHeight - this.height - this.top) / this.grid[1]) * this.grid[1] + this.top,minBottom: this.bottom % this.grid[1],maxBottom: Math.floor((this.parentHeight - this.height - this.bottom) / this.grid[1]) * this.grid[1] + this.bottom};},// 取消deselect (e) {const target = e.target || e.srcElement;const regex = new RegExp(this.className + '-([trmbl]{2})', '');if (!this.$el.contains(target) && !regex.test(target.className)) {if (this.enabled && !this.preventDeactivation) {this.enabled = false;this.$emit('deactivated');this.$emit('update:active', false);}removeEvent(document.documentElement, eventsFor.move, this.handleResize);}this.resetBoundsAndMouseState();},// 控制柄触摸按下handleTouchDown (handle, e) {eventsFor = events.touch;this.handleDown(handle, e);},// 控制柄按下handleDown (handle, e) {if (e instanceof MouseEvent && e.which !== 1) {return;}if (this.onResizeStart(handle, e) === false) {return;}if (e.stopPropagation) e.stopPropagation();// Here we avoid a dangerous recursion by faking// corner handles as middle handlesif (this.lockAspectRatio && !handle.includes('m')) {this.handle = 'm' + handle.substring(1);} else {this.handle = handle;}this.resizing = true;this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX;this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY;this.mouseClickPosition.left = this.left;this.mouseClickPosition.right = this.right;this.mouseClickPosition.top = this.top;this.mouseClickPosition.bottom = this.bottom;this.mouseClickPosition.w = this.width;this.mouseClickPosition.h = this.height;this.bounds = this.calcResizeLimits();this.hasResize = false;addEvent(document.documentElement, eventsFor.move, this.handleResize);addEvent(document.documentElement, eventsFor.stop, this.handleUp);},// 计算调整大小范围calcResizeLimits () {let minW = this.minW;let minH = this.minH;let maxW = this.maxW;let maxH = this.maxH;const aspectFactor = this.aspectFactor;const [gridX, gridY] = this.grid;const width = this.width;const height = this.height;const left = this.left;const top = this.top;const right = this.right;const bottom = this.bottom;if (this.lockAspectRatio) {if (minW / minH > aspectFactor) {minH = minW / aspectFactor;} else {minW = aspectFactor * minH;}if (maxW && maxH) {maxW = Math.min(maxW, aspectFactor * maxH);maxH = Math.min(maxH, maxW / aspectFactor);} else if (maxW) {maxH = maxW / aspectFactor;} else if (maxH) {maxW = aspectFactor * maxH;}}maxW = maxW - (maxW % gridX);maxH = maxH - (maxH % gridY);const limits = {minLeft: null,maxLeft: null,minTop: null,maxTop: null,minRight: null,maxRight: null,minBottom: null,maxBottom: null};if (this.parent) {limits.minLeft = left % gridX;limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX;limits.minTop = top % gridY;limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY;limits.minRight = right % gridX;limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX;limits.minBottom = bottom % gridY;limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY;if (maxW) {limits.minLeft = Math.max(limits.minLeft, this.parentWidth - right - maxW);limits.minRight = Math.max(limits.minRight, this.parentWidth - left - maxW);}if (maxH) {limits.minTop = Math.max(limits.minTop, this.parentHeight - bottom - maxH);limits.minBottom = Math.max(limits.minBottom, this.parentHeight - top - maxH);}if (this.lockAspectRatio) {limits.minLeft = Math.max(limits.minLeft, left - top * aspectFactor);limits.minTop = Math.max(limits.minTop, top - left / aspectFactor);limits.minRight = Math.max(limits.minRight, right - bottom * aspectFactor);limits.minBottom = Math.max(limits.minBottom, bottom - right / aspectFactor);}} else {limits.minLeft = null;limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX;limits.minTop = null;limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY;limits.minRight = null;limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX;limits.minBottom = null;limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY;if (maxW) {limits.minLeft = -(right + maxW);limits.minRight = -(left + maxW);}if (maxH) {limits.minTop = -(bottom + maxH);limits.minBottom = -(top + maxH);}if (this.lockAspectRatio && (maxW && maxH)) {limits.minLeft = Math.min(limits.minLeft, -(right + maxW));limits.minTop = Math.min(limits.minTop, -(maxH + bottom));limits.minRight = Math.min(limits.minRight, -left - maxW);limits.minBottom = Math.min(limits.minBottom, -top - maxH);}}return limits;},// 移动move (e) {if (this.resizing) {this.handleResize(e);} else if (this.dragging) {this.handleDrag(e);}},// 元素移动async handleDrag (e) {const axis = this.axis;const grid = this.grid;const bounds = this.bounds;const mouseClickPosition = this.mouseClickPosition;const tmpDeltaX = axis && axis !== 'y' ? mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX) : 0;const tmpDeltaY = axis && axis !== 'x' ? mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY) : 0;const [deltaX, deltaY] = snapToGrid(grid, tmpDeltaX, tmpDeltaY, this.scaleRatio);const left = restrictToBounds(mouseClickPosition.left - deltaX, bounds.minLeft, bounds.maxLeft);const top = restrictToBounds(mouseClickPosition.top - deltaY, bounds.minTop, bounds.maxTop);if (this.onDrag(left, top, this.componentKey) === false) {return;}const right = restrictToBounds(mouseClickPosition.right + deltaX, bounds.minRight, bounds.maxRight);const bottom = restrictToBounds(mouseClickPosition.bottom + deltaY, bounds.minBottom, bounds.maxBottom);this.left = left;this.top = top;this.right = right;this.bottom = bottom;await this.snapCheck();this.hasMove = true;this.$emit('dragging', this.left, this.top, this.componentKey);},moveHorizontally (val) {// eslint-disable-next-lineconst [deltaX, _] = snapToGrid(this.grid, val, this.top, this.scale)const left = restrictToBounds(deltaX, this.bounds.minLeft, this.bounds.maxLeft);this.left = left;this.right = this.parentWidth - this.width - left;},moveVertically (val) {// eslint-disable-next-lineconst [_, deltaY] = snapToGrid(this.grid, this.left, val, this.scale)const top = restrictToBounds(deltaY, this.bounds.minTop, this.bounds.maxTop);this.top = top;this.bottom = this.parentHeight - this.height - top;},// 控制柄移动handleResize (e) {console.log('handleResize-------');let left = this.left;let top = this.top;let right = this.right;let bottom = this.bottom;const mouseClickPosition = this.mouseClickPosition;// eslint-disable-next-lineconst lockAspectRatio = this.lockAspectRatioconst aspectFactor = this.aspectFactor;const tmpDeltaX = mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX);const tmpDeltaY = mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY);if (!this.widthTouched && tmpDeltaX) {this.widthTouched = true;}if (!this.heightTouched && tmpDeltaY) {this.heightTouched = true;}const [deltaX, deltaY] = snapToGrid(this.grid, tmpDeltaX, tmpDeltaY, this.scaleRatio);if (this.handle.includes('b')) {bottom = restrictToBounds(mouseClickPosition.bottom + deltaY,this.bounds.minBottom,this.bounds.maxBottom);if (this.lockAspectRatio && this.resizingOnY) {right = this.right - (this.bottom - bottom) * aspectFactor;}} else if (this.handle.includes('t')) {top = restrictToBounds(mouseClickPosition.top - deltaY,this.bounds.minTop,this.bounds.maxTop);if (this.lockAspectRatio && this.resizingOnY) {left = this.left - (this.top - top) * aspectFactor;}}if (this.handle.includes('r')) {right = restrictToBounds(mouseClickPosition.right + deltaX,this.bounds.minRight,this.bounds.maxRight);if (this.lockAspectRatio && this.resizingOnX) {bottom = this.bottom - (this.right - right) / aspectFactor;}} else if (this.handle.includes('l')) {left = restrictToBounds(mouseClickPosition.left - deltaX,this.bounds.minLeft,this.bounds.maxLeft);if (this.lockAspectRatio && this.resizingOnX) {top = this.top - (this.left - left) / aspectFactor;}}const width = computeWidth(this.parentWidth, left, right);const height = computeHeight(this.parentHeight, top, bottom);if (this.onResize(this.handle, left, top, width, height) === false) {return;}this.left = left;this.top = top;this.right = right;this.bottom = bottom;this.width = width;this.height = height;this.hasResize = true;this.$emit('resizing', this.left, this.top, this.width, this.height, this.componentKey);},changeWidth (val) {// eslint-disable-next-lineconst [newWidth, _] = snapToGrid(this.grid, val, 0, this.scale)let right = restrictToBounds((this.parentWidth - newWidth - this.left),this.bounds.minRight,this.bounds.maxRight);let bottom = this.bottom;if (this.lockAspectRatio) {bottom = this.bottom - (this.right - right) / this.aspectFactor;}const width = computeWidth(this.parentWidth, this.left, right);const height = computeHeight(this.parentHeight, this.top, bottom);this.right = right;this.bottom = bottom;this.width = width;this.height = height;},changeHeight (val) {// eslint-disable-next-lineconst [_, newHeight] = snapToGrid(this.grid, 0, val, this.scale)let bottom = restrictToBounds((this.parentHeight - newHeight - this.top),this.bounds.minBottom,this.bounds.maxBottom);let right = this.right;if (this.lockAspectRatio) {right = this.right - (this.bottom - bottom) * this.aspectFactor;}const width = computeWidth(this.parentWidth, this.left, right);const height = computeHeight(this.parentHeight, this.top, bottom);this.right = right;this.bottom = bottom;this.width = width;this.height = height;},// 从控制柄松开async handleUp () {this.handle = null;// 初始化辅助线数据const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' });const refLine = { vLine: [], hLine: [] };for (let i in refLine) { refLine[i] = JSON.parse(JSON.stringify(temArr)); }if (this.resizing) {this.resizing = false;await this.conflictCheck();this.$emit('refLineParams', refLine);this.$emit('resizestop', this.left, this.top, this.width, this.height, this.componentKey);this.hasResize && this.$store.commit('recordSnapshot');}if (this.dragging) {this.dragging = false;await this.conflictCheck();this.$emit('refLineParams', refLine);this.$emit('dragstop', this.left, this.top, this.componentKey);this.hasMove && this.$store.commit('recordSnapshot');}this.resetBoundsAndMouseState();removeEvent(document.documentElement, eventsFor.move, this.handleResize);},// 新增方法 ↓↓↓// 设置属性settingAttribute () {// 设置冲突检测this.$el.setAttribute('data-is-check', `${this.isConflictCheck}`);// 设置对齐元素this.$el.setAttribute('data-is-snap', `${this.snap}`);},// 冲突检测conflictCheck () {const top = this.top;const left = this.left;const width = this.width;const height = this.height;if (this.isConflictCheck) {const nodes = this.$el.parentNode.childNodes; // 获取当前父节点下所有子节点for (let item of nodes) {if (item.className !== undefined && !item.className.includes(this.classNameActive) && item.getAttribute('data-is-check') !== null && item.getAttribute('data-is-check') !== 'false') {const tw = item.offsetWidth;const th = item.offsetHeight;// 正则获取left与rightlet [tl, tt] = this.formatTransformVal(item.style.transform);// 左上角与右下角重叠const tfAndBr = (top >= tt && left >= tl && tt + th > top && tl + tw > left) || (top <= tt && left < tl && top + height > tt && left + width > tl);// 右上角与左下角重叠const brAndTf = (left <= tl && top >= tt && left + width > tl && top < tt + th) || (top < tt && left > tl && top + height > tt && left < tl + tw);// 下边与上边重叠const bAndT = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw);// 上边与下边重叠(宽度不一样)const tAndB = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw);// 左边与右边重叠const lAndR = (left >= tl && top >= tt && left < tl + tw && top < tt + th) || (top > tt && left <= tl && left + width > tl && top < tt + th);// 左边与右边重叠(高度不一样)const rAndL = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left + width > tl);// 如果冲突,就将回退到移动前的位置if (tfAndBr || brAndTf || bAndT || tAndB || lAndR || rAndL) {this.top = this.mouseClickPosition.top;this.left = this.mouseClickPosition.left;this.right = this.mouseClickPosition.right;this.bottom = this.mouseClickPosition.bottom;this.width = this.mouseClickPosition.w;this.height = this.mouseClickPosition.h;}}}}},// 检测对齐元素async snapCheck () {let width = this.width;let height = this.height;if (this.snap) {let activeLeft = this.left;let activeRight = this.left + width;let activeTop = this.top;let activeBottom = this.top + height;// 初始化辅助线数据const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' });const refLine = { vLine: [], hLine: [] };for (let i in refLine) { refLine[i] = JSON.parse(JSON.stringify(temArr)); }// 获取当前父节点下所有子节点const nodes = this.$el.parentNode.childNodes;let tem = {value: { x: [[], [], []], y: [[], [], []] },display: [],position: []};const { groupWidth, groupHeight, groupLeft, groupTop, bln } = await this.getActiveAll(nodes);if (!bln) {width = groupWidth;height = groupHeight;activeLeft = groupLeft;activeRight = groupLeft + groupWidth;activeTop = groupTop;activeBottom = groupTop + groupHeight;}for (let item of nodes) {if (item.className !== undefined && !item.className.includes(this.classNameActive) && item.getAttribute('data-is-snap') !== null && item.getAttribute('data-is-snap') !== 'false') {const w = item.offsetWidth;const h = item.offsetHeight;const [l, t] = this.formatTransformVal(item.style.transform);const r = l + w; // 对齐目标rightconst b = t + h; // 对齐目标的bottomconst hc = Math.abs((activeTop + height / 2) - (t + h / 2)) <= this.snapTolerance; // 水平中线const vc = Math.abs((activeLeft + width / 2) - (l + w / 2)) <= this.snapTolerance; // 垂直中线const ts = Math.abs(t - activeBottom) <= this.snapTolerance; // 从上到下const TS = Math.abs(b - activeBottom) <= this.snapTolerance; // 从上到下const bs = Math.abs(t - activeTop) <= this.snapTolerance; // 从下到上const BS = Math.abs(b - activeTop) <= this.snapTolerance; // 从下到上const ls = Math.abs(l - activeRight) <= this.snapTolerance; // 外左const LS = Math.abs(r - activeRight) <= this.snapTolerance; // 外左const rs = Math.abs(l - activeLeft) <= this.snapTolerance; // 外右const RS = Math.abs(r - activeLeft) <= this.snapTolerance; // 外右tem['display'] = [ts, TS, bs, BS, hc, hc, ls, LS, rs, RS, vc, vc];tem['position'] = [t, b, t, b, t + h / 2, t + h / 2, l, r, l, r, l + w / 2, l + w / 2];if (ts) {if (bln) {this.top = t - height;this.bottom = this.parentHeight - this.top - height;}tem.value.y[0].push(l, r, activeLeft, activeRight);}if (bs) {if (bln) {this.top = t;this.bottom = this.parentHeight - this.top - height;}tem.value.y[0].push(l, r, activeLeft, activeRight);}if (TS) {if (bln) {this.top = b - height;this.bottom = this.parentHeight - this.top - height;}tem.value.y[1].push(l, r, activeLeft, activeRight);}if (BS) {if (bln) {this.top = b;this.bottom = this.parentHeight - this.top - height;}tem.value.y[1].push(l, r, activeLeft, activeRight);}if (ls) {if (bln) {this.left = l - width;this.right = this.parentWidth - this.left - width;}tem.value.x[0].push(t, b, activeTop, activeBottom);}if (rs) {if (bln) {this.left = l;this.right = this.parentWidth - this.left - width;}tem.value.x[0].push(t, b, activeTop, activeBottom);}if (LS) {if (bln) {this.left = r - width;this.right = this.parentWidth - this.left - width;}tem.value.x[1].push(t, b, activeTop, activeBottom);}if (RS) {if (bln) {this.left = r;this.right = this.parentWidth - this.left - width;}tem.value.x[1].push(t, b, activeTop, activeBottom);}if (hc) {if (bln) {this.top = t + h / 2 - height / 2;this.bottom = this.parentHeight - this.top - height;}tem.value.y[2].push(l, r, activeLeft, activeRight);}if (vc) {if (bln) {this.left = l + w / 2 - width / 2;this.right = this.parentWidth - this.left - width;}tem.value.x[2].push(t, b, activeTop, activeBottom);}// 辅助线坐标与是否显示(display)对应的数组,易于循环遍历const arrTem = [0, 1, 0, 1, 2, 2, 0, 1, 0, 1, 2, 2];for (let i = 0; i <= arrTem.length; i++) {// 前6为Y辅助线,后6为X辅助线const xory = i < 6 ? 'y' : 'x';const horv = i < 6 ? 'hLine' : 'vLine';if (tem.display[i]) {const { origin, length } = this.calcLineValues(tem.value[xory][arrTem[i]]);refLine[horv][arrTem[i]].display = tem.display[i];refLine[horv][arrTem[i]].position = tem.position[i] + 'px';refLine[horv][arrTem[i]].origin = origin;refLine[horv][arrTem[i]].lineLength = length;}}}}this.$emit('refLineParams', refLine);}},calcLineValues (arr) {const length = Math.max(...arr) - Math.min(...arr) + 'px';const origin = Math.min(...arr) + 'px';return { length, origin };},async getActiveAll (nodes) {const activeAll = [];const XArray = [];const YArray = [];let groupWidth = 0;let groupHeight = 0;let groupLeft = 0;let groupTop = 0;for (let item of nodes) {if (item.className !== undefined && item.className.includes(this.classNameActive)) {activeAll.push(item);}}const AllLength = activeAll.length;if (AllLength > 1) {for (let i of activeAll) {const l = i.offsetLeft;const r = l + i.offsetWidth;const t = i.offsetTop;const b = t + i.offsetHeight;XArray.push(t, b);YArray.push(l, r);}groupWidth = Math.max(...YArray) - Math.min(...YArray);groupHeight = Math.max(...XArray) - Math.min(...XArray);groupLeft = Math.min(...YArray);groupTop = Math.min(...XArray);}const bln = AllLength === 1;return { groupWidth, groupHeight, groupLeft, groupTop, bln };},// 正则获取left与topformatTransformVal (string) {let [left, top] = string.replace(/[^0-9\-,]/g, '').split(',');if (top === undefined) top = 0;return [+left, +top];}},computed: {handleStyle () {return (stick) => {if (!this.handleInfo.switch) return { display: this.enabled ? 'block' : 'none' };const size = (this.handleInfo.size / this.scaleRatio).toFixed(2);const offset = (this.handleInfo.offset / this.scaleRatio).toFixed(2);const center = (size / 2).toFixed(2);const styleMap = {tl: {top: `${offset}px`,left: `${offset}px`},tm: {top: `${offset}px`,left: `calc(50% - ${center}px)`},tr: {top: `${offset}px`,right: `${offset}px`},mr: {top: `calc(50% - ${center}px)`,right: `${offset}px`},br: {bottom: `${offset}px`,right: `${offset}px`},bm: {bottom: `${offset}px`,right: `calc(50% - ${center}px)`},bl: {bottom: `${offset}px`,left: `${offset}px`},ml: {top: `calc(50% - ${center}px)`,left: `${offset}px`}};const stickStyle = {width: `${size}px`,height: `${size}px`,top: styleMap[stick].top,left: styleMap[stick].left,right: styleMap[stick].right,bottom: styleMap[stick].bottom};stickStyle.display = this.enabled ? 'block' : 'none';return stickStyle;};},style () {return {transform: `translate(${this.left}px, ${this.top}px)`,width: this.computedWidth,height: this.computedHeight,zIndex: this.zIndex,...(this.dragging && this.disableUserSelect ? userSelectNone : userSelectAuto)};},// 控制柄显示与否actualHandles () {if (!this.resizable) return [];return this.handles;},computedWidth () {if (this.w === 'auto') {if (!this.widthTouched) {return 'auto';}}return this.width + 'px';},computedHeight () {if (this.h === 'auto') {if (!this.heightTouched) {return 'auto';}}return this.height + 'px';},resizingOnX () {return (Boolean(this.handle) && (this.handle.includes('l') || this.handle.includes('r')));},resizingOnY () {return (Boolean(this.handle) && (this.handle.includes('t') || this.handle.includes('b')));},isCornerHandle () {return (Boolean(this.handle) && ['tl', 'tr', 'br', 'bl'].includes(this.handle));}},watch: {active (val) {this.enabled = val;if (val) {this.$emit('activated');} else {this.$emit('deactivated');}},z (val) {if (val >= 0 || val === 'auto') {this.zIndex = val;}},x (val) {if (this.resizing || this.dragging) {return;}if (this.parent) {this.bounds = this.calcDragLimits();}this.moveHorizontally(val);},y (val) {if (this.resizing || this.dragging) {return;}if (this.parent) {this.bounds = this.calcDragLimits();}this.moveVertically(val);},lockAspectRatio (val) {if (val) {this.aspectFactor = this.width / this.height;} else {this.aspectFactor = undefined;}},minWidth (val) {if (val > 0 && val <= this.width) {this.minW = val;}},minHeight (val) {if (val > 0 && val <= this.height) {this.minH = val;}},maxWidth (val) {this.maxW = val;},maxHeight (val) {this.maxH = val;},w (val) {if (this.resizing || this.dragging) {return;}if (this.parent) {this.bounds = this.calcResizeLimits();}this.changeWidth(val);},h (val) {if (this.resizing || this.dragging) {return;}if (this.parent) {this.bounds = this.calcResizeLimits();}this.changeHeight(val);}}};</script>
.vdr {
  touch-action: none;
  position: absolute;
  box-sizing: border-box;
  border: 1px dashed #d6d6d6;
}
.vdr.active {
  z-index: 1!important;
}
.handle {
  box-sizing: border-box;
  position: absolute;
  width: 8px;
  height: 8px;
  background: #ffffff;
  border: 1px solid #333;
  box-shadow: 0 0 2px #bbb;
  z-index: 2;
}
.handle-tl {
  top: -5px;
  left: -5px;
  cursor: nw-resize;
}
.handle-tm {
  top: -5px;
  left: calc(50% - 4px);
  /*margin-left: -5px;*/
  cursor: n-resize;
}
.handle-tr {
  top: -5px;
  right: -5px;
  cursor: ne-resize;
}
.handle-ml {
  top: calc(50% - 4px);
  /*margin-top: -5px;*/
  left: -5px;
  cursor: w-resize;
}
.handle-mr {
  top: calc(50% - 4px);
  /*margin-top: -5px;*/
  right: -5px;
  cursor: e-resize;
}
.handle-bl {
  bottom: -5px;
  left: -5px;
  cursor: sw-resize;
}
.handle-bm {
  bottom: -5px;
  left: calc(50% - 4px);
  /*margin-left: -5px;*/
  cursor: s-resize;
}
.handle-br {
  bottom: -5px;
  right: -5px;
  cursor: se-resize;
}
/*旋转handle*/
/*.handle-rot {
  top: 0;
  left: 50%;
  margin-top: -20px;
  margin-left: -1px;
  border-radius: 50%;
  width: 10px;
  height: 10px;
  cursor: grab;
  transform: translate(-50%, 0);
}
.handle-rot:before {
  content: '';
  position: absolute;
  top: 8px;
  left: 50%;
  width: 0;
  height: 7px;
  border-left: 1px solid #000000;
  transform: translate(-50%, 0);
}*/
.ref-line{
  position: absolute;
  background-color: rgb(255, 0, 204);
  z-index: 9999;
}
.v-line{
  width: 1px;
}
.h-line{
  height: 1px;
}
@media only screen and (max-width: 768px) {
  [class*="handle-"]:before {
    content: '';
    left: -10px;
    right: -10px;
    bottom: -10px;
    top: -10px;
    position: absolute;
  }
}
                    