可拖拽编辑页面

作者:陈佳鑫
时间:2023-2-13

  • 轮播图

  • 商品

  • 图片

页面标题

可拖拽编辑界面组件

本章节开发使用npm vuedraggableopen in new window
开发思路:将主界面分 3 个模块进行,left 模块使用dragstartopen in new windowdragendopen in new window 进行组件拖拽控制

<section class="l">
  <ul @dragstart="dragStart" @dragend="dragEnd">
    <li v-for="(val, key, index) in typeList" draggable :data-type="key" :key="index + 1">
      <span :class="val.icon"></span>
      <p>{{ val.name }}</p>
    </li>
  </ul>
</section>

当组件 div 被拖拽至 content 组件(中间内容显示组件)时,因在 content 组件中使用了dragoveropen in new window 事件会每隔几百秒毫秒触发

<section class="c">
  <div class="top-nav" @click="selectType(0)">
    <img :src="http://rw8irwnr8.hn-bkt.clouddn.com/topNavBlack.png" />
    <span class="tit">{{ info.title }}</span>
  </div>
  <div class="view-content" @drop="drog" @dragover="dragOver" :style="{ backgroundColor: info.backgroundColor }">
    <Draggable v-model="view" draggable=".item">
      <template v-for="(item, index) in view">
        <div v-if="index > 0" :data-index="index" :key="index" class="item" @click="selectType(index)">
          <!-- waiting -->
          <template v-if="item.status && item.status == 2">
            <div class="wait"> {{ item.type }} </div>
          </template>
          <template v-else>
            <component :is="typeList[item.type]['com']" :data="item" :className="className[item.tabType]">
            </component>
          </template>
          <i @click="deleteItem($event, index)" class="el-icon-error"></i>
        </div>
      </template>
    </Draggable>
  </div>
</section>

此时我们可以根据当前 div 移动位置进行判断,dragover event 中可以获取到当前 div 在 content 组件中位置event.target.className。 我们为主 div 创建了一个 className 为view-content, Draggable 组件中 className 为item,此时我们可以通过 name 判断当前 div 位置,移动位置为view-content时,我们 可以在view数组中插入一条 status 为 2 的占位数据(因为此 div 当前所在位置为组件最底部),当移动位置为item说明我们在view已经插入过一条数据,此时我们需要判断单曲 div 相对于item的高度, 如果高度小于item的一半我们将占位数据插入至item之前,如果高度大于一半将数据插入至item之后,当拖拽结束时触发dragEnd事件,删除掉viewstatus这样就会根据当前组件 渲染,到此整个拖拽思路完成。

dragOver 移动中监控事件

// 移动中
dragOver(e) {
  if (!this.type) { // 内容拖拽
    return
  }
  e.preventDefault()
  e.stopPropagation()
  let className = e.target.className
  let name = className !== 'view-content' ? 'item' : 'view-content'

  const defaultData = {
    type: this.type,    // 组件类型
    status: 2,          // 默认状态
    data: [],           // 数据
    options: {},        // 选项操作
    tabType: 1
  }
  if (name == 'view-content') {
    if (!this.isPush) {
      this.index = this.view.length
      this.isPush = true
      this.view.push(defaultData)
    }
  } else if (name == 'item') {

    let target = e.target
    let [y, h, curIndex] = [e.offsetY, target.offsetHeight, target.dataset.index]

    let direction = y < (h / 2)
    if (!this.isPush) {
      // Push to Top or Bottom
      if (direction) {
        if (curIndex == 0) {
          this.view.unshift(defaultData)
        } else {
          this.view.splice(curIndex, 0, defaultData)
        }
      } else {
        curIndex = +curIndex + 1
        this.view.splice(curIndex, 0, defaultData)
      }
    } else {
      // Moving
      if (direction) {
        var i = curIndex == 0 ? 0 : curIndex - 1
        var result = this.view[i]['status'] == 2
      } else {
        var i = +curIndex + 1
        var result = this.view.length > i && this.view[i]['status'] == 2
      }
      if (result) return
      const temp = this.view.splice(this.index, 1)
      this.view.splice(curIndex, 0, temp[0])
    }
    this.index = curIndex
    this.isPush = true
  }
}
Last Updated:
Contributors: 1102166242@qq.com, cjx