赞
踩
一款专注可视化平台工具,功能强大,高可扩展的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">
- <div
- v-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.mouse
-
- export 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不能大于maxWidth
- if (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 = 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()
-
- // 优化:取消选中的行为优先绑定在父节点上
- const parentElement = this.$el.parentNode
- addEvent(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.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) {
- 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.$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()
- }
- 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.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 handles
- if (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()
-
- 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) === 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.$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 = left
- this.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 = top
- this.bottom = this.parentHeight - this.height - top
- },
- // 控制柄移动
- handleResize (e) {
- let left = this.left
- let top = this.top
- let right = this.right
- let bottom = this.bottom
-
- const mouseClickPosition = this.mouseClickPosition
- const lockAspectRatio = this.lockAspectRatio
- const 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.$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.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) {
- 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.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 (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 = false
- await this.conflictCheck()
- this.$emit('refLineParams', refLine)
- this.$emit('resizestop', this.left, this.top, this.width, this.height)
- }
- if (this.dragging) {
- this.dragging = false
- await 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.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与right
- let [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
- this.$emit('resizing', this.left, this.top, this.width, this.height)
- }
- }
- }
- }
- },
- // 检测对齐元素
- 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 // 对齐目标right
- const b = t + h // 对齐目标的bottom
-
- const 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 = t
- this.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 = b
- this.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 = l
- this.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 = r
- this.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 = 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与top
- formatTransformVal (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) 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 = 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>
- <!--选择素材-->
- <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-select
- placeholder="请选择素材分组"
- :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-radio
- border
- :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-pagination
- style="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、multiple
- ids: {
- 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>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。