vue实现拖拽(vuedraggable)

实现效果:

左侧往右侧拖动,右侧列表可以进行拖拽排序。

安装引用:

npm install vuedraggable
import draggable from 'vuedraggable'

使用:

data数据:

      componentList: [
        {
          groupName: '考试题型',
          children: [
            {
              componentType: 'danxuan',
              componentName: '单选题',
              componentIcon: 'icon-danxuan'
            },
            {
              componentType: 'duoxuan',
              componentName: '多选题',
              componentIcon: 'icon-duoxuan'
            },
            {
              componentType: 'panduan',
              componentName: '判断题',
              componentIcon: 'icon-panduan'
            }
          ]
        },
        {
          groupName: '信息题',
          children: [
            {
              componentType: 'message',
              componentName: '姓名',
              componentIcon: 'icon-xingming'
            },
            {
              componentType: 'message',
              componentName: '手机',
              componentIcon: 'icon-shouji'
            },
            {
              componentType: 'message',
              componentName: '邮箱',
              componentIcon: 'icon-youxiang'
            }
          ]
        }
      ],
questionList:[],

html代码:

左侧代码:   
<el-tabs type="border-card" class="tabs">
        <el-tab-pane label="题型">
          <div v-for="(item, index) in componentList" :key="index">
            <b class="fs-14">{{item.groupName}}</b>
            <draggable
              @end="end"
              :clone="cloneElement"
              class="group"
              v-model="item.children"
              :sort="false" //禁止排序
              :group="{
                name: 'component',
                pull: 'clone', 
                put: false  //不允许其他元素拖拽进此空间
              }">
              <div @click="pushComponent(_item)" class="component" v-for="(_item, _index) in item.children" :key="_index">
                <i class="iconfont mr-8" :class="_item.componentIcon"></i>
                <span>{{_item.componentName}}</span>
              </div>
            </draggable>
          </div>
        </el-tab-pane>
        <el-tab-pane label="题库">
          <el-tree
            ref="tree"
            highlight-current
            :data="treeList"
            node-key="id"
            :current-node-key="currentNodekey"
            @node-click="handleNodeClick"
            :load="loadNode"
            :props="props"
            lazy>
            <span slot-scope="{node}">
                <el-tooltip v-if="node.label.length>=8"  class="item" effect="dark" :content="node.label" placement="top">
                  <div class="text-ellipsis width-150">{{ node.label }}</div>
                </el-tooltip>
               <div v-else>{{ node.label }}</div>
            </span>
          </el-tree>
        </el-tab-pane>
      </el-tabs>
右侧代码:

    <div class="content">
        <el-scrollbar ref="scrollbar" style="height: calc(100vh - 220px)">
        {{questionList}}
          <draggable
            class="list"
            forceFallback
            :animation="200"
            ghostClass="ghost"
            handle=".el-icon-rank"
            v-model="questionList"
            :group="{
              name: 'component'
            }">
            <transition-group class="height-percent-100 display-block">
              <div
                class="item"
                :class="{active: item.active, error: item.error}"
                v-for="(item, index) in questionList"
                :key="item.uid">
                <div
                  class="display-flex ai-flex-start padding-20 pt-14"
                  @click="clickQuestion(item)"
                  :id="item.uid">
                  <div class="pt-6 width-40">
                    <b>{{index + 1}}</b>
                  </div>
                  <div class="flex-1">
                    <div class="display-flex ai-flex-start jc-space-between">
                      <b @click="editTitle(item)" class="width-percent-80 pt-6" style="min-height: 26px" v-if="!item.editTitle">{{item.title}}</b>
                      <el-input
                        type="textarea"
                        autosize
                        :ref="item.uid"
                        v-else
                        size="small"
                        class="width-percent-80"
                        @blur="item.editTitle = false"
                        v-model="item.title"></el-input>
                      <span v-if="item.componentType !== 'message'" class="color-info pt-6">( {{item.score}}分 )</span>
                    </div>
                    <div class="mt-12">
                      <el-input
                        v-if="item.componentType === 'message'"
                        readonly
                        placeholder="请输入"
                        type="textarea"
                        autosize
                        v-model="item.answer"
                        size="small"
                        class="width-percent-80"></el-input>
                      <draggable v-model="item.options" handle=".el-icon-d-caret">
                        <transition-group>
                          <div v-for="i in item.options" :key="i.value" class="display-flex ai-center jc-space-between pt-4 pb-4">
                            <div class="flex-1 display-flex ai-center">
                              <el-checkbox
                                v-if="item.componentType === 'duoxuan'"
                                v-model="item.answer" :label="i.value">
                                {{  }}
                              </el-checkbox>
                              <el-radio
                                v-else
                                v-model="item.answer"
                                :label="i.value" class="mr-0">{{  }}</el-radio>
                              <p @click="editOption(i)" v-if="!i.edit" class="margin-0 fs-14 width-percent-80 display-flex ai-center" style="min-height: 32px">{{i.label}}</p>
                              <el-input
                                type="textarea"
                                autosize
                                @blur="i.edit = false"
                                :ref="i.value"
                                v-else
                                v-model="i.label"
                                size="small"
                                class="width-percent-80"></el-input>
                            </div>
                            <div class="display-flex ai-center fd-row-reverse color-info width-130">
                              <i class="el-icon-d-caret ml-8 cursor-move"></i>
                              <i @click="delOption(item, i.value)" class="ml-10 el-icon-remove-outline cursor-pointer"></i>
                              <span class="color-success fs-14" v-if="item.answer.includes(i.value)">( 正确答案 )</span>
                            </div>
                          </div>
                        </transition-group>
                      </draggable>
                      <div v-if="['danxuan', 'duoxuan'].includes(item.componentType)">
                        <el-button class="pb-0" @click="addOption(item)" type="text" icon="el-icon-plus">添加选项</el-button>
                      </div>
                    </div>
                  </div>
                  <div class="display-flex ai-center color-info mt-8">
                    <i class="ml-14 el-icon-rank cursor-move"></i>
                    <i @click.stop="copyQuestion(item, index)" class="ml-14 el-icon-document-copy cursor-pointer"></i>
                    <i @click.stop="delQuestion(item)" class="ml-14 el-icon-delete cursor-pointer"></i>
                  </div>
                </div>
                <div class="errorMessage" v-if="item.error">
                  {{item.errorMessage}}
                </div>
              </div>
              <div key="empty" v-if="!questionList.length" class="height-percent-100 fd-column display-flex ai-center jc-center">
                <el-empty description="请点击右侧或拖入题型进行添加题目"></el-empty>
              </div>
            </transition-group>
          </draggable>
        </el-scrollbar>
      </div>

方法:

     /**
     * 点击组件进行push
     * @param data
     * @param type
     */
    pushComponent (data, type = 0) {
      console.log(data)
      //type=1:后端给的题库项导入  0:题型项导入
      this.questionList.push(type ? data : this.cloneElement(data))
      const newDraggableIndex = this.questionList.length - 1
      const e = {
        to: {
          className: 'pushComponent'
        },
        newDraggableIndex
      }
      this.end(e)
    },



    /**
     * 拖拽结束
     * @param e
     */
    end (e) {
      console.log(e)
      if (e.to.className !== 'group') {
        for (const item of this.questionList) {
          item.active = false
        }
        this.questionList[e.newDraggableIndex].active = true
        this.$nextTick(() => {
          document.getElementById(this.questionList[e.newDraggableIndex].uid).scrollIntoView();
        })
      }
    },

    /**
     * 拖拽clone
     * @param item
     * @returns {any}
     */
    cloneElement (item) {
      const data = JSON.parse(JSON.stringify(item));
      console.log(data)
      data.uid = `${data.componentType}-${Math.floor(Math.random() * 1000000)}`
      data.title = data.componentName
      data.answer = ''
      data.active = false
      data.editTitle = false
      data.error = false
      data.errorMessage = ''
      switch (data.componentType) {
        case 'danxuan':
          data.scoreMethod = '1' // 得分方式
          data.options = [
            {
              edit: false,
              label: '选项1',
              value: `${data.componentType}-${Math.floor(Math.random() * 1000000)}`
            },
            {
              edit: false,
              label: '选项2',
              value: `${data.componentType}-${Math.floor(Math.random() * 1000000)}`
            }
          ] // 选项
          data.answer = data.options[0].value // 答案
          data.score = 10 // 分数
          data.description = '' // 解析
          break
        case 'duoxuan':
          data.scoreMethod = '1'
          data.options = [
            {
              edit: false,
              label: '选项1',
              value: `${data.componentType}-${Math.floor(Math.random() * 1000000)}`
            },
            {
              edit: false,
              label: '选项2',
              value: `${data.componentType}-${Math.floor(Math.random() * 1000000)}`
            }
          ]
          data.answer = [data.options[0].value]
          data.score = 10
          data.description = ''
          break
        case 'panduan':
          data.scoreMethod = '1'
          data.options = [
            {
              edit: false,
              label: '是',
              value: `${data.componentType}-true`
            },
            {
              edit: false,
              label: '否',
              value: `${data.componentType}-false`
            }
          ]
          data.answer = data.options[0].value
          data.score = 10
          data.description = ''
          break
      }
      return data
    },

css:

.tabs {
  width: 240px;
  box-shadow: none;
  border: none;
  height: 100%;
  .group {
    display: grid;
    grid-gap: 12px;
    grid-template-columns: repeat(2, 1fr);
    font-size: 14px;
    padding: 12px 0;
  }
  .component {
    color: #666666;
    border-radius: 4px;
    border: 1px solid #D8D8D8;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 8px 0;
    cursor: pointer;
    &:hover {
      color: #1774FF;
      border-color: #1774FF;
    }
  }
}
.content {
  flex: 1;
  background-color: #EFF2F4;
  border-left: 1px solid #DCDFE6;
  border-right: 1px solid #DCDFE6;
  padding: 20px 4px 0 20px;

  .list {
    height: 100%;
    margin-right: 16px;
    .item {
      padding: 0;
      border: 1px solid transparent;
      border-radius: 4px;
      box-shadow: 0 2px 4px 0 rgba(0,0,0,0.1);
      background-color: #fff;
      margin-bottom: 20px;
      .el-icon-delete,.el-icon-remove-outline {
        &:hover {
          color: #F56C6C;
        }
      }
      .el-icon-document-copy {
        &:hover {
          color: #3377FF;
        }
      }
    }
    .errorMessage {
      color: #FFFFFF;
      background-color: #F56C6C;
      padding: 10px 20px;
      font-size: 14px;
      border-radius: 0 0 4px 4px;
    }
    .active {
      border-color: #2A5EFF;
    }
    .error {
      border-color: #F56C6C;
    }
  }
  .ghost {
    background-color: #499BFF;
    border-radius: 4px;
    padding: 20px;
    margin-bottom: 20px;
    .iconfont {
      display: none;
    }
    span {
      color: #FFFFFF;
    }
  }
}

扩展:

点击题库中的题进行导入:

代码:

<el-tab-pane label="题库">
  <el-tree
    ref="tree"
    highlight-current
    :data="treeList"
    node-key="id"
    :current-node-key="currentNodekey"
    @node-click="handleNodeClick"
    :load="loadNode"
    :props=" {
       label: 'name',
       value: 'id',
       isLeaf: 'isLeaf'
      },"
    lazy>
    <span slot-scope="{node}">
        <el-tooltip v-if="node.label.length>=8"  class="item" effect="dark" :content="node.label" placement="top">
          <div class="text-ellipsis width-150">{{ node.label }}</div>
        </el-tooltip>
       <div v-else>{{ node.label }}</div>
    </span>
  </el-tree>
</el-tab-pane>

方法:

handleNodeClick (node) {
  if (node.level === 2) {
    //点击子节点(叶子节点)
    this.$nextTick(() => {
      this.$refs.tree.setCurrentKey(node.id)
    })
    const data = JSON.parse(JSON.stringify(node))
    data.answer = JSON.parse(node.answer)
    data.uid = `${data.componentType}-${Math.floor(Math.random() * 1000000)}`
    this.pushComponent(data, 1)
  } else {
    this.$nextTick(() => {
      this.$refs.tree.setCurrentKey()
    })
  }
},
loadNode (node, resolve) {
  if (node.level === 0) {
    this.$api.pxExam.getExamSetList({ name: this.fuzzy }).then(res => {
      this.treeList = res.map(res => {
        return {
          name: res.name,
          id: res.id,
          isLeaf: false,
          level: 1
        }
      })
      return resolve(this.treeList)
    })
  }
pushComponent方法通用的(传参不同),上面写的有。
  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue3和Vue2一样都可以使用vuedraggable插件进行不同组件的拖拽。以下是使用vuedraggable插件实现不同组件拖拽的步骤: 1. 安装vuedraggable插件 在终端中执行以下命令: ``` npm install vuedraggable ``` 2. 引入vuedraggable插件 在需要使用vuedraggable插件的组件中引入: ```javascript import draggable from 'vuedraggable' ``` 3. 使用draggable组件 在模板中使用draggable组件,并设置相应的属性: ```html <draggable v-model="list" :element="'ul'" :options="{group:'items'}"> <li v-for="(item, index) in list" :key="item.id">{{ item.name }}</li> </draggable> ``` 其中,v-model绑定了一个数组list,用于记录拖拽后的顺序;:element指定了拖拽的元素类型,这里为ul;:options指定了拖拽的选项,这里设置了group为items,表示不同的组件可以互相拖拽。 注意:v-model绑定的数组中的元素必须要有一个唯一的标识符,用于区分不同的元素。 4. 处理拖拽事件 当拖拽完成后,可以通过监听dragend事件来处理拖拽后的逻辑: ```html <draggable v-model="list" :element="'ul'" :options="{group:'items'}" @dragend="onDragEnd"> <li v-for="(item, index) in list" :key="item.id">{{ item.name }}</li> </draggable> ``` ```javascript methods: { onDragEnd(event) { console.log(event) // 处理拖拽完成后的逻辑 } } ``` 在onDragEnd方法中,可以获取到拖拽完成后的事件对象,通过事件对象可以获取到拖拽前后的元素顺序,从而进行相应的操作。 以上就是使用vuedraggable插件实现不同组件拖拽的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值