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

news2024/11/16 3:35:03

一款专注可视化平台工具,功能强大,高可扩展的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>

 

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

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

相关文章

计算机毕业设计 基于SpringBoot的高校竞赛管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

2024美赛数学建模思路A题B题C题D题E题F题思路汇总 选题分析

文章目录 1 赛题思路2 美赛比赛日期和时间3 赛题类型4 美赛常见数模问题5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 美赛比赛日期和时间 比赛开始时间&#xff1a;北京时间2024年2月2日&#xff08;周五&#xff…

TiDB 7.1 多租户在中泰证券中的应用

本文详细介绍了中泰证券在系统国产化改造项目中采用 TiDB 多租户技术的实施过程。文章分析了中泰证券数据库系统现状以及引入 TiDB 资源管控技术的必要性&#xff0c;探讨了 TiDB 多租户的关键特性&#xff0c;并阐述了在实际应用中的具体操作步骤。通过该技术的应用&#xff0…

MariaDB单机多实例的配置方法

1、什么是数据库的单机多实例 数据库的单机多实例是指在一台物理服务器上运行多个数据库实例。这种部署方式允许多个数据库实例共享相同的物理资源&#xff0c;如CPU、内存和存储&#xff0c;从而提高硬件利用率并降低成本。每个数据库实例可以独立运行&#xff0c;处理不同的…

ElementUI的Table组件行合并上手指南

ElementUI的Table组件行合并 &#xff0c;示例用官网vue3版的文档 <el-table :data"tableData" :span-method"objectSpanMethod" border style"width: 100%; margin-top: 20px"><el-table-column prop"id" label"ID&qu…

uniapp项目如何引用安卓原生aar插件(避坑指南三)

官方文档说明&#xff1a;uni小程序SDK 1.第一步在uniapp项目下&#xff0c;创建一个nativeplugins目录&#xff0c;目录下面创建插件名称&#xff0c;这里以abcModule为例子&#xff0c;在此目录下创建android目录&#xff0c;把aar放androidd目录下&#xff0c;同时创建libs…

低代码选型注意事项

凭借着革命性的生产力优势&#xff0c;低代码技术火爆了整个IT圈。面对纷繁复杂的低代码和无代码产品&#xff0c;开发者该如何选择&#xff1f; 在研究低代码平台的年数上&#xff0c;本人已有3年&#xff0c;也算是个低代码资深用户了&#xff0c;很多企业面临低代码选型上的…

探究Android DreamService的梦幻世界

探究Android DreamService的梦幻世界 引言 DreamService的概述 在Android开发中&#xff0c;DreamService是一种特殊类型的服务&#xff0c;它可以用于创建梦幻世界的屏保应用。梦幻世界是一种用户界面显示模式&#xff0c;当设备进入空闲状态时&#xff0c;系统会自动启动D…

【网络安全 | 指纹识别工具】WhatWeb使用详析

前言 WhatWeb 是一款用于识别 Web 应用程序和 Web 服务器的开源工具。它可以识别网站使用的编程语言、Web 框架、Web 服务器软件、Web 应用程序等信息&#xff0c;从而帮助安全测试人员快速了解目标网站的技术特征&#xff0c;发现可能存在的漏洞。 本文将对 WhatWeb 的使用方法…

获取Android和iOS崩溃日志的方法

文章目录 一、Android崩溃日志1、获取方法1.1 通过adb logcat获取1.2 通过adb shell dumpsys dropbox命令获取 2、导出设备Crash日志3、导出设备ANR日志4、常见日志类别 二、iOS崩溃日志1、获取方法1.1 xcode中打开1.2 手机上直接获取 2、Crash 头部信息 一、Android崩溃日志 …

redis中根据通配符删除key

redis中根据通配符删除key 我们是不是在redis中keys user:*可以获取所有key&#xff0c;但是 del user:*却不行这里我提供的命令主要是SCANSCAN 0 MATCH user:* COUNT 100使用lua保证原子性 SCAN参数描述 在示例中&#xff0c;COUNT 被设置为 100。这是一个防止一次性获取大…

Linux安装GitLab教程

Linux安装GitLab教程 1、配置yum源 相当于新建一个文件&#xff0c;通过这个文件来安装gitlab vim /etc/yum.repos.d/gitlab-ce.repo 把这些配置粘进去 [gitlab-ce] nameGitlab CE Repository baseurlhttps://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el$releasever/ gp…

Ubuntu安装K8S的dashboard(管理页面)

原文网址&#xff1a;Ubuntu安装k8s的dashboard&#xff08;管理页面&#xff09;-CSDN博客 简介 本文介绍Ubuntu安装k8s的dashboard&#xff08;管理页面&#xff09;的方法。 Dashboard的作用有&#xff1a;便捷操作、监控、分析、概览。 相关网址 官网地址&#xff1a;…

Redis6.0 Client-Side缓存是什么

前言 Redis在其6.0版本中加入了Client-side caching的支持&#xff0c;开启该功能后&#xff0c;Redis可以将指定的key-value缓存在客户端侧&#xff0c;这样当客户端发起请求时&#xff0c;如果客户端侧存在缓存&#xff0c;则无需请求Redis Server端。 Why Client-side Cac…

C语言实验1:C程序的运行环境和运行C程序的方法

一、算法原理 这是学C语言的入门&#xff0c;并不需要很高深的知识&#xff0c;一个hello world 或者一个简单的加法即可 二、实验要求 了解所用的计算机系统的基本操作方法&#xff0c;学会独立使用该系统。 了解在该系统上如何编辑、编译、连接和运行一个C程序。 通过运…

macOS系统下载安装PyCharm社区版本的流程(详细)

第一步 进入PyCharm官网 链接&#xff1a;Get Your Educational Tool - JetBrains 第二步 选择下拉框&#xff0c;根据自己的电脑芯片选择下载版本 电脑芯片的查看位置&#xff1a;设置-通用-关于本机。选择完版本再点击Download按钮 -- 第三步 下载完以后在右上角打开文件&…

AGV智能搬运机器人-替代人工工位让物流行业降本增效

在当今快速发展的世界中&#xff0c;物流业面临着巨大的挑战&#xff0c;包括提高效率、降低成本和优化工作流程。为了应对这些挑战&#xff0c;一种新型的自动化设备——智能搬运机器人正在崭露头角。本文将通过一个具体的案例来展示富唯智能转运机器人在实际应用中的价值。 案…

uniApp中uView组件库的丰富布局方法

目录 基本使用 #分栏间隔 #混合布局 #分栏偏移 #对齐方式 API #Row Props #Col Props #Row Events #Col Events UniApp的uView组件库是一个丰富的UI组件库&#xff0c;提供了各种常用的UI组件和布局方法&#xff0c;帮助开发者快速构建美观、灵活的界面。下面给你写一…

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

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

网站显示不安全警告怎么办?消除网站不安全警告超全指南

网站显示不安全警告怎么办&#xff1f;当用户访问你的网站&#xff0c;而您的网站没有部署SSL证书实现HTTPS加密时&#xff0c;网站就会显示不安全警告&#xff0c;这种警告&#xff0c;不仅有可能阻止用户继续浏览网站&#xff0c;影响网站声誉&#xff0c;还有可能影响网站在…