vue实现H5拖拽可视化编辑器

一款专注可视化平台工具,功能强大,高可扩展的HTML5可视化编辑器,致力于提供一套简单易用、高效创新、无限可能的解决方案。技术栈采用vue和typescript开发, 专注研发创新工具。 

<template><div:style="style":class="[{[classNameActive]: enabled,[classNameDragging]: dragging,[classNameResizing]: resizing,[classNameDraggable]: draggable,[classNameResizable]: resizable}, className]"@click="$emit('click')"@mousedown="elementMouseDown"@touchstart="elementTouchDown"@contextmenu="onContextMenu"><divv-for="handle in actualHandles":key="handle":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.mouseexport default {replace: true,name: 'draggable-resizable',props: {rotateZ: {type: Number,default: 0},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}}}},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}},created: function () {// eslint-disable-next-line 无效的prop:minWidth不能大于maxWidthif (this.maxWidth && this.minWidth > this.maxWidth) console.warn('[Vdr warn]: Invalid prop: minWidth cannot be greater than maxWidth')// eslint-disable-next-line 无效prop:minHeight不能大于maxHeight'if (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 = parentWidththis.parentHeight = parentHeightconst [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 : widththis.height = this.h !== 'auto' ? this.h : heightthis.right = this.parentWidth - this.width - this.leftthis.bottom = this.parentHeight - this.height - this.topthis.settingAttribute()// 优化:取消选中的行为优先绑定在父节点上const parentElement = this.$el.parentNodeaddEvent(parentElement || document.documentElement, 'mousedown', this.deselect)addEvent(parentElement || 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: {// 右键菜单onContextMenu (e) {this.$emit('contextmenu', e)},// 重置边界和鼠标状态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.leftthis.bottom = newParentHeight - this.height - this.topthis.parentWidth = newParentWidththis.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.touchthis.elementDown(e)},elementMouseDown (e) {eventsFor = events.mousethis.elementDown(e)},// 元素按下elementDown (e) {if (e instanceof MouseEvent && e.which !== 1) {return}const target = e.target || e.srcElementif (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 = falsereturn}if (!this.enabled) {this.enabled = truethis.$emit('activated')this.$emit('update:active', true)}if (this.draggable) {this.dragging = true}this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageXthis.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageYthis.mouseClickPosition.left = this.leftthis.mouseClickPosition.right = this.rightthis.mouseClickPosition.top = this.topthis.mouseClickPosition.bottom = this.bottomthis.mouseClickPosition.w = this.widththis.mouseClickPosition.h = this.heightif (this.parent) {this.bounds = this.calcDragLimits()}addEvent(document.documentElement, eventsFor.move, this.move)addEvent(document.documentElement, eventsFor.stop, this.handleUp)}},// 计算移动范围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.srcElementconst regex = new RegExp(this.className + '-([trmbl]{2})', '')if (!this.$el.contains(target) && !regex.test(target.className)) {if (this.enabled && !this.preventDeactivation) {this.enabled = falsethis.$emit('deactivated')this.$emit('update:active', false)}removeEvent(document.documentElement, eventsFor.move, this.handleResize)}this.resetBoundsAndMouseState()},// 控制柄触摸按下handleTouchDown (handle, e) {eventsFor = events.touchthis.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 = truethis.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageXthis.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageYthis.mouseClickPosition.left = this.leftthis.mouseClickPosition.right = this.rightthis.mouseClickPosition.top = this.topthis.mouseClickPosition.bottom = this.bottomthis.mouseClickPosition.w = this.widththis.mouseClickPosition.h = this.heightthis.bounds = this.calcResizeLimits()addEvent(document.documentElement, eventsFor.move, this.handleResize)addEvent(document.documentElement, eventsFor.stop, this.handleUp)},// 计算调整大小范围calcResizeLimits () {let minW = this.minWlet minH = this.minHlet maxW = this.maxWlet maxH = this.maxHconst aspectFactor = this.aspectFactorconst [gridX, gridY] = this.gridconst width = this.widthconst height = this.heightconst left = this.leftconst top = this.topconst right = this.rightconst bottom = this.bottomif (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 % gridXlimits.maxLeft = left + Math.floor((width - minW) / gridX) * gridXlimits.minTop = top % gridYlimits.maxTop = top + Math.floor((height - minH) / gridY) * gridYlimits.minRight = right % gridXlimits.maxRight = right + Math.floor((width - minW) / gridX) * gridXlimits.minBottom = bottom % gridYlimits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridYif (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 = nulllimits.maxLeft = left + Math.floor((width - minW) / gridX) * gridXlimits.minTop = nulllimits.maxTop = top + Math.floor((height - minH) / gridY) * gridYlimits.minRight = nulllimits.maxRight = right + Math.floor((width - minW) / gridX) * gridXlimits.minBottom = nulllimits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridYif (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.axisconst grid = this.gridconst bounds = this.boundsconst mouseClickPosition = this.mouseClickPositionconst tmpDeltaX = axis && axis !== 'y' ? mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX) : 0const tmpDeltaY = axis && axis !== 'x' ? mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY) : 0const [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) === false) {return}const right = restrictToBounds(mouseClickPosition.right + deltaX, bounds.minRight, bounds.maxRight)const bottom = restrictToBounds(mouseClickPosition.bottom + deltaY, bounds.minBottom, bounds.maxBottom)this.left = leftthis.top = topthis.right = rightthis.bottom = bottomawait this.snapCheck()this.$emit('dragging', {left: this.left, top: this.top})},moveHorizontally (val) {const [deltaX, _] = snapToGrid(this.grid, val, this.top, this.scale)const left = restrictToBounds(deltaX, this.bounds.minLeft, this.bounds.maxLeft)this.left = leftthis.right = this.parentWidth - this.width - left},moveVertically (val) {const [_, deltaY] = snapToGrid(this.grid, this.left, val, this.scale)const top = restrictToBounds(deltaY, this.bounds.minTop, this.bounds.maxTop)this.top = topthis.bottom = this.parentHeight - this.height - top},// 控制柄移动handleResize (e) {let left = this.leftlet top = this.toplet right = this.rightlet bottom = this.bottomconst mouseClickPosition = this.mouseClickPositionconst lockAspectRatio = this.lockAspectRatioconst aspectFactor = this.aspectFactorconst 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 = leftthis.top = topthis.right = rightthis.bottom = bottomthis.width = widththis.height = heightthis.$emit('resizing', {left: this.left, top: this.top, width: this.width, height: this.height})},changeWidth (val) {const [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.bottomif (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 = rightthis.bottom = bottomthis.width = widththis.height = height},changeHeight (val) {const [_, 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.rightif (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 = rightthis.bottom = bottomthis.width = widththis.height = height},// 从控制柄松开async handleUp (e) {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 = falseawait this.conflictCheck()this.$emit('refLineParams', refLine)this.$emit('resizestop', this.left, this.top, this.width, this.height)}if (this.dragging) {this.dragging = falseawait this.conflictCheck()this.$emit('refLineParams', refLine)this.$emit('dragstop', this.left, this.top)}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.topconst left = this.leftconst width = this.widthconst height = this.heightif (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.offsetWidthconst 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.topthis.left = this.mouseClickPosition.leftthis.right = this.mouseClickPosition.rightthis.bottom = this.mouseClickPosition.bottomthis.width = this.mouseClickPosition.wthis.height = this.mouseClickPosition.hthis.$emit('resizing', this.left, this.top, this.width, this.height)}}}}},// 检测对齐元素async snapCheck () {let width = this.widthlet height = this.heightif (this.snap) {let activeLeft = this.leftlet activeRight = this.left + widthlet activeTop = this.toplet 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.childNodeslet tem = {value: { x: [[], [], []], y: [[], [], []] },display: [],position: []}const { groupWidth, groupHeight, groupLeft, groupTop, bln } = await this.getActiveAll(nodes)if (!bln) {width = groupWidthheight = groupHeightactiveLeft = groupLeftactiveRight = groupLeft + groupWidthactiveTop = groupTopactiveBottom = 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.offsetWidthconst h = item.offsetHeightconst [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]// fix:中线自动对齐,元素可能超过父元素边界的问题if (ts) {if (bln) {this.top = Math.max(t - height, this.bounds.minTop)this.bottom = this.parentHeight - this.top - height}tem.value.y[0].push(l, r, activeLeft, activeRight)}if (bs) {if (bln) {this.top = tthis.bottom = this.parentHeight - this.top - height}tem.value.y[0].push(l, r, activeLeft, activeRight)}if (TS) {if (bln) {this.top = Math.max(b - height, this.bounds.minTop)this.bottom = this.parentHeight - this.top - height}tem.value.y[1].push(l, r, activeLeft, activeRight)}if (BS) {if (bln) {this.top = bthis.bottom = this.parentHeight - this.top - height}tem.value.y[1].push(l, r, activeLeft, activeRight)}if (ls) {if (bln) {this.left = Math.max(l - width, this.bounds.minLeft)this.right = this.parentWidth - this.left - width}tem.value.x[0].push(t, b, activeTop, activeBottom)}if (rs) {if (bln) {this.left = lthis.right = this.parentWidth - this.left - width}tem.value.x[0].push(t, b, activeTop, activeBottom)}if (LS) {if (bln) {this.left = Math.max(r - width, this.bounds.minLeft)this.right = this.parentWidth - this.left - width}tem.value.x[1].push(t, b, activeTop, activeBottom)}if (RS) {if (bln) {this.left = rthis.right = this.parentWidth - this.left - width}tem.value.x[1].push(t, b, activeTop, activeBottom)}if (hc) {if (bln) {this.top = Math.max(t + h / 2 - height / 2, this.bounds.minTop)this.bottom = this.parentHeight - this.top - height}tem.value.y[2].push(l, r, activeLeft, activeRight)}if (vc) {if (bln) {this.left = Math.max(l + w / 2 - width / 2, this.bounds.minLeft)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 = originrefLine[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 = 0let groupHeight = 0let groupLeft = 0let groupTop = 0for (let item of nodes) {if (item.className !== undefined && item.className.includes(this.classNameActive)) {activeAll.push(item)}}const AllLength = activeAll.lengthif (AllLength > 1) {for (let i of activeAll) {const l = i.offsetLeftconst r = l + i.offsetWidthconst t = i.offsetTopconst b = t + i.offsetHeightXArray.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 === 1return { groupWidth, groupHeight, groupLeft, groupTop, bln }},// 正则获取left与topformatTransformVal (string) {let [left, top] = string.replace(/[^0-9\-,]/g, '').split(',')if (top === undefined) top = 0return [+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) rotateZ(${this.rotateZ}deg)`,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 = valif (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><!--选择素材--><el-dialog@close="$emit('cancel')":title="$t('plugin.selectFootage')"append-to-body:close-on-click-modal="false":visible.sync="visible"><el-form inline ref="queryForm" :model="material" size="small"><el-form-item label="分组" prop="groupId"><tree-selectplaceholder="请选择素材分组":data="groupOptions":props="defaultProps":clearable="true":accordion="true"@getValue="getValue"/></el-form-item><el-form-item label="名称" prop="name"><el-input circle v-model="material.name" placeholder="请输入素材名称"></el-input></el-form-item><el-form-item><el-button icon="el-icon-search" @click="getListMaterial" type="primary">{{ $t('plugin.search') }}</el-button><el-button icon="el-icon-refresh" @click="resetQuery">{{ $t('plugin.rest') }}</el-button></el-form-item></el-form><el-radio-group@change="changeType"v-if="typeList.length > 1"style="margin-bottom: 8px;"v-model="material.type" size="small"><el-radioborder:label="item"style="margin-right: 5px;"v-for="item in typeList"><span>{{ item | filterType }}</span></el-radio></el-radio-group><el-row :gutter="20" v-loading="material.loading"><el-col :span="6" v-for="(item, index) in material.list"><label><div style="background-color: #fff; width: 167px; font-weight: 400; border: 2px solid transparent;":class="{'active-material': currentIndex.some(s => s.id == item.id)}" class="choose-file"@click="rowClick(item)"><span v-show="Number(item.duration)" class="duration">{{ item.duration }} {{ $t('plugin.second') }}</span><span class="resolution">{{ item.resolution }}</span><img v-if="item.type === 'image'"style="width: 100%; height: 120px;":src="filterUrl(item)"/><img v-if="item.type === 'file'"style="width: 100%; height: 120px;":src="filterUrl(item)"/><div v-if="item.type === 'audio'" style="height: 120px; width: 100%; background-color: #ecf4ff;"><svg-icon style="color: #86baff; font-size: 36px; margin: 10px;" icon-class="audio"/></div><video v-if="item.type === 'media'"style="height: 120px; width: 100%; object-fit: fill; vertical-align: bottom;":disabled="true":autoplay="false":controls="false":src="filterUrl(item)">{{ $t('tips.canvas') }}</video><div class="bottom line-clamp1"><span style="margin: 0 5px;">{{ item.name }}</span></div></div></label></el-col></el-row><el-paginationstyle="margin-top: 16px;"@size-change="getListMaterial"@current-change="getListMaterial":current-page.sync="material.current":page-size="material.size"layout="total, prev, pager, next":total="material.total"></el-pagination><div style="text-align: right; margin-top: 16px;"><el-button size="small" @click="$emit('cancel')">{{ $t('tips.cancel') }}</el-button><el-button size="small" :disabled="!currentIndex.length" type="primary" @click="confirm"> {{$t('tips.confirm')}}</el-button></div></el-dialog>
</template><script>
import treeSelect from "./TreeSelect/index"
import {request} from "@/config";
import Cookies from "js-cookie";const {getMaterialList, groupTree} = request;
export default {name: "material",inject: ['equipment'],components: {treeSelect},watch: {visible: {handler() {this.currentIndex = [];},deep: true,immediate: true}},props: {mode: {type: String,default: "single"}, // single、multipleids: {type: Array,default() {return []}},title: {type: String,default: "选择素材"},visible: {type: Boolean,default: false},typeList: {type: Array,default() {return ["image"]}}},filters: {filterType(data) {const typeList = [{label: "图片", name: "image"},{label: "视频", name: "media"},{label: "音频", name: "audio"}];const vo = typeList.find(item => data === item.name);const {label, name} = vo;const language = Cookies.get('language') || "zh"return language === 'zh' ? label : name;}},computed: {currentType() {return this.typeList.length ? this.typeList[0] : ""}},data() {const type = this.typeList[0]return {defaultProps: {value: 'id',label: 'name',children: 'children'},groupOptions: [],empty: require("@/assets/images/empty-img.png"),currentIndex: [],material: {name: "",groupId: "",type: type,list: [],current: 1,total: 0,size: 20,loading: false,data: []},baseUrl: sessionStorage.getItem('baseUrl')}},methods: {getValue(value) {this.material.groupId = value;this.getListMaterial();},getTree() {groupTree({type: '0'}).then(response => {this.groupOptions = response.data})},changeType() {this.material.current = 1;this.getListMaterial();},filterUrl(data) {const {decodedUrl, originalUrl} = data;return data ? `${this.baseUrl}${decodedUrl || originalUrl}` : this.empty;},rowClick(data) {if (this.mode === "multiple") {if (this.currentIndex.some(item => item.id == data.id)) {this.currentIndex = this.currentIndex.filter(item => item.id !== data.id);} else {this.currentIndex.push(data)}} else {this.currentIndex = [data]}},confirm() {let array = JSON.parse(JSON.stringify(this.currentIndex));this.material.data = [];let flag = false;array.forEach(data => {const {decodedUrl, originalUrl} = data;data.url = `${this.baseUrl}${decodedUrl || originalUrl}`if (data.addition) {data.addition = data.addition.split(",").map(item => this.baseUrl + item);} else {flag = true;}this.material.data.push(data);})if (flag && this.currentType === 'file') {return this.$notify.warning("当前文档未转换成功")}if (this.mode === "multiple") {this.$emit("confirm", this.material.data);} else {const data = this.material.data;this.$emit("confirm", data.length ? data[0] : {});}},getListMaterial() {this.material.loading = true;if (!Number(this.material.groupId)) {this.material.groupId = "";}getMaterialList({name: this.material.name,groupId: this.material.groupId,type: this.material.type,current: this.material.current,size: this.material.size}).then(response => {const {total, data} = response;if (data) {data.forEach((item, index) => {if (item.type === 'file') {const list = item.addition ? item.addition.split(",") : [""]data[index].decodedUrl = list[0];}})this.material.list = data;this.material.total = total;}this.material.loading = false;})},resetQuery() {this.$refs.queryForm.resetFields();this.material.current = 1;this.material.groupId = "";this.getListMaterial();}},created() {if (!this.equipment) {this.getListMaterial();this.getTree();}}
}
</script><style>
.active-material {transition: .3s;background-color: #ecf4ff !important;border: 3px solid #409eff !important;
}
</style>

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/581094.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MYSQL数据库的安全管理-数据库实验六

Mysql数据库实验及练习题相关 MySQL 数据库和表的管理-数据库实验一 MySQL连接查询、索引、视图-数据库实验二、实验三 MySQL约束、触发器-数据库实验四 MYSQL存储过程和存储函数-数据库实验五 MySQL批量随机生成name、TEL、idNumber MYSQL数据库的安全管理-数据库实验六 MYSQ…

<JavaEE> TCP 的通信机制(二) -- 连接管理(三次握手和四次挥手)

目录 TCP的通信机制的核心特性 三、连接管理 1&#xff09;什么是连接管理&#xff1f; 2&#xff09;“三次握手”建立连接 1> 什么是“三次握手”&#xff1f; 2> “三次握手”的核心作用是什么&#xff1f; 3&#xff09;“四次挥手”断开连接 1> 什么是“…

第2课 用FFmpeg读取rtmp流并显示视频

这节课我们开始利用ffmpeg和opencv来实现一个rtmp播放器。播放器的最基本功能其实就两个:显示画面和播放声音。在实现这两个功能前&#xff0c;我们需要先用ffmpeg连接到rtmp服务器&#xff0c;当然也可以打开一个文件。 1.压缩备份上节课工程文件夹为demo.rar&#xff0c;并修…

ZETA落地合肥、宜城南方水泥,纵行科技携手中才邦业助力水泥企业数智化管理

近日&#xff0c;合肥南方水泥、宜城南方水泥落地ZETA预测性维护方案&#xff0c;通过在水泥厂内搭建ZETA网络&#xff0c;并在B类及C类主辅机设备上安装ZETA系列端智能传感器&#xff0c;进行数据采集和监测设备运行状态、以及早期故障警报和诊断&#xff0c;实现水泥生产设备…

web期末大作业--网页设计 HTML+CSS+JS(附源码)

目录 一&#xff0c;作品介绍 二.运用知识 三.作品详情 四.部分作品效果图 我的&#xff1a;​编辑 五.部分源代码 六.文件目录 七.源码 一&#xff0c;作品介绍 作品介绍&#xff1a;该作品是一个是一个关于影视作品的网页&#xff0c;一共有五个页面&#xff0c;主页&a…

文件IO

文章目录 文章目录 前言 一 . 文件 文件路径 文件类型 Java中操作文件 File 概述 属性 构造方法 方法 createNewFile mkdir 二 . 文件内容的读写 - IO InputStream 概述 FileInputStream 概述 利用 Scanner 进行字符读取 OutputStream 概述 PrintWriter封装O…

gin框架使用系列之五——表单校验

系列目录 《gin框架使用系列之一——快速启动和url分组》《gin框架使用系列之二——uri占位符和占位符变量的获取》《gin框架使用系列之三——获取表单数据》《gin框架使用系列之四——json和protobuf的渲染》 一 、表单验证的基本理论 在第三篇中&#xff0c;我们介绍了如何…

Flink1.17实战教程(第四篇:处理函数)

系列文章目录 Flink1.17实战教程&#xff08;第一篇&#xff1a;概念、部署、架构&#xff09; Flink1.17实战教程&#xff08;第二篇&#xff1a;DataStream API&#xff09; Flink1.17实战教程&#xff08;第三篇&#xff1a;时间和窗口&#xff09; Flink1.17实战教程&…

C# LINQ

一、前言 学习心得&#xff1a;C# 入门经典第8版书中的第22章《LINQ》 二、LINQ to XML 我们可以通过LINQ to XML来创造xml文件 如下示例&#xff0c;我们用LINQ to XML来创造。 <Books><CSharp Time"2019"><book>C# 入门经典</book><…

uniapp 输入手机号并且正则校验

1.<input input“onInput” :value“phoneNum” type“number” maxlength“11”/> 3. method里面写 onInput(e){ this.phoneNum e.detail.value }, 4.调用接口时候校验正则 if (!/^1[3456789]\d{9}$/.test(this.phoneNum)) {uni.showToast({title: 请输入正确的手机号…

对于c++的总结与思考

笔者觉得好用的学习方法&#xff1a;模板法 1.采用原因&#xff1a;由于刚从c语言面向过程的学习中解脱出来&#xff0c;立即把思路从面向过程转到面向对象肯定不现实&#xff0c;加之全新的复杂语法与操作&#xff0c;着实给新手学习这门语言带来了不小的困难。所以&#xff…

【动画视频生成】

转自&#xff1a;机器之心 动画视频生成这几天火了&#xff0c;这次 NUS、字节的新框架不仅效果自然流畅&#xff0c;还在视频保真度方面比其他方法强了一大截。 最近&#xff0c;阿里研究团队构建了一种名为 Animate Anyone 的方法&#xff0c;只需要一张人物照片&#xff0…

数据结构与算法教程,数据结构C语言版教程!(第一部分、数据结构快速入门,数据结构基础详解)二

第一部分、数据结构快速入门&#xff0c;数据结构基础详解 数据结构基础&#xff0c;主要研究数据存储的方式。 本章作为数据结构的入门课程&#xff0c;主要让读者明白&#xff0c;数据结构到底是什么&#xff0c;常用的数据存储结构有哪些&#xff0c;数据结构和算法之间到底…

钉钉机器人接入定时器(钉钉API+XXL-JOB)

钉钉机器人接入定时器&#xff08;钉钉APIXXL-JOB&#xff09; 首先需要创建钉钉内部群 在群设置中找到机器人选项 选择“自定义”机器人 通过Webhook接入自定义服务 创建完成后会生成一个send URL和一个加签码 下面就是干货 代码部分了 DingDingUtil.sendMessageByText(webho…

【Python】ubuntu python>3.9编译安装,及多个Python版本并存的使用方法

【Python】ubuntu python3.9编译安装&#xff0c;及多个Python版本并存的使用方法 1. 安装依赖2. 编译与安装2.1 依赖与源获取2.2 配置2.3 编译2.4 安装2.5 链接动态库 3. 多版本兼容 1. 安装依赖 更新系统软件 在正式开始之前&#xff0c;建议首先检查系统软件是否均为最新&a…

构建高效数据中台:集群规划与搭建的最佳实践指南

架构设计 Rack(机架)配置建议 大数据集群规划 安装细节见配套文档 YARN资源管理平台队列调度策略 Capacity Scheduler 默认配置下,Capacity Scheduler 将尝试保证每个队列在其分配的容量内公平地使用资源。 然而,Hadoop 也支持通过调整队列的权重和使用抢占策略来优化资…

《Spring Cloud学习笔记:微服务保护Sentinel》

Review 解决了服务拆分之后的服务治理问题&#xff1a;Nacos解决了服务治理问题OpenFeign解决了服务之间的远程调用问题网关与前端进行交互&#xff0c;基于网关的过滤器解决了登录校验的问题 流量控制&#xff1a;避免因为突发流量而导致的服务宕机。 隔离和降级&#xff1a…

微信小程序开发系列-04获取用户图像和昵称

这个功能的实现对于我这个新手来说可谓是一波三折。该功能的实现经历了三个“版本”的迭代&#xff0c;我的运气不是很好&#xff0c;从第一个“版本”开始尝试&#xff0c;这篇文章也是记录下这个过程&#xff0c;以便其他新手能快速找到解决方案。 Gen1-getUserInfo 第一个…

音视频学习(二十二)——rtmp发流(tcp方式)

前言 本文主要介绍自研的RtmpStreamSender.dll&#xff0c;rtmp库提供接口接收裸流数据&#xff0c;支持将裸流数据封装为flv格式并通过rtmp协议发流。 关于rtmp协议基础介绍可查看&#xff1a;https://blog.csdn.net/www_dong/article/details/131026072 关于rtmp收流介绍可…

可视化云监控/安防监控系统EasyCVR视频管理平台播流失败的原因(端口篇)

安防视频监控EasyCVR平台兼容性强&#xff0c;可支持的接入协议众多&#xff0c;包括国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议与SDK&#xff0c;如&#xff1a;海康ehome、海康sdk、大华sdk、宇视sdk、华为sdk、萤石云sdk、乐橙sdk等。平台能将接入的视频…