vue实现拖拽(vuedraggable)

news2024/12/27 15:50:20

实现效果:

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

安装引用:

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方法通用的(传参不同),上面写的有。

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

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

相关文章

JVM内存随着服务器内存的升高而升高问题排查

一、故障描述 公司测试环境和线上环境&#xff0c;都会有&#xff1a;JVM内存随着服务器内存的升高而升高 这种问题 二、排查 1、linux服务器上使用htop查看java项目内存占比&#xff0c;给最大最小推内存300m&#xff0c;但是实际上超出一倍 2、排查方案 a、通过后面的学习…

Emlog博客网站快速搭建并结合内网穿透实现远程访问本地站点

文章目录 前言1. 网站搭建1.1 Emolog网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试总结 前言 博客作为使…

人工智能 — 图像滤波器

目录 一、图像噪声1、高斯噪声2、椒盐噪声3、泊松噪声4、乘性噪声5、瑞利噪声6、伽马噪声 二、图像滤波三、各种滤波器1、均值滤波2、中值滤波3、最大最小值滤波4、引导滤波 四、图像增强1、点处理1、线性变换2、分段线性变换3、对数变换4、幂律变换/伽马变换 2、领域处理3、图…

浏览器自动填充

input同时有多个 当input框的类型为typepassword&#xff0c;其上一个框为typetext&#xff0c;浏览器会自动填充保存过的账户密码、解决自动填充有以下几种处理方法&#xff1a; 1、在浏览器端设置 2、给出一组假页面存储&#xff0c;必须在同一个Form表单中&#xff0c;让填充…

解决SpringAMQP工作队列模型程序报错:WARN 48068:Failed to declare queue: simple.queue

这里写目录标题 1.运行环境2.报错信息3.解决方案4.查看解决之后的效果 1.运行环境 使用docker运行了RabbitMQ的服务器&#xff1a; 在idea中导入springAMQP的jar包&#xff0c;分别编写了子模块生产者publisher&#xff0c;消费者consumer&#xff1a; 1.在publisher中运行测试…

[AI]部署安装有道QanyThing

前提条件&#xff1a; 1、win10系统更新到最新的版本&#xff0c;系统版本最好为专业版本 winver 查看系统版本&#xff0c;内部版本要大于19045 2、CPU开启虚拟化 3、开启虚拟化功能&#xff0c;1、2、3每步完成后均需要重启电脑&#xff1b; 注&#xff1a;windows 虚拟…

农业四情在线监测站的应用

TH-Q3农业四情在线监测站可广泛应用于农田管理、作物种植、病虫害防治、气象灾害预警等领域。通过实时监测和数据分析&#xff0c;该系统可以帮助农民实现精准施肥、科学灌溉、合理调控作物生长等目标&#xff0c;提高农业生产效率和质量。同时&#xff0c;该系统还可以为政府决…

Pytorch学习(杂知识)

Mini-batch Mii-batch是一种在机器学习中常用的训练算法。它是将大的数据集分成一些小的数据集&#xff0c;每次只用一个小的数据集来训练模型。通常情况下&#xff0c;训练数据集中的数据越多&#xff0c;训练出的模型越准确&#xff0c;但是如果数据集太大&#xff0c;就会导…

【OpenFeign常用配置】

OpenFeign常用配置 快速入门&#xff1a;1、引入依赖2、启用OpenFeign 实践1、引入依赖2、开启连接池功能3、模块划分4、日志5、重试 快速入门&#xff1a; OpenFeign是一个声明式的http客户端&#xff0c;是spring cloud在eureka公司开源的feign基础上改造而来。其作用及时基于…

红日靶场3

靶场链接&#xff1a;漏洞详情 在虚拟机的网络编辑器中添加两个仅主机网卡 信息搜集 端口扫描 外网机处于网端192.168.1.0/24中&#xff0c;扫描外网IP端口&#xff0c;开放了80 22 3306端口 80端口http服务&#xff0c;可以尝试登录网页 3306端口mysql服务&#xff0c;可…

Megalinter 初体验

简介 MegaLinter 是一个多语言、多工具的集成代码检查工具&#xff0c;它能够通过一个统一的工作流来运行多个静态代码分析工具&#xff0c;从而提供全面的代码质量检查。 官网&#xff1a;https://megalinter.io/latest/ MegaLinter 的特点&#xff1a; 多语言支持&#x…

精品基于Spring Boot智能无人仓库管理-进销存储

《[含文档PPT源码等]精品基于Spring Boot智能无人仓库管理[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#xff1a;HTML5…

统计图玫瑰图绘制方法

统计图玫瑰图绘制方法 常用的统计图有条形图、柱形图、折线图、曲线图、饼图、环形图、扇形图。 前几类图比较容易绘制&#xff0c;饼图环形图绘制较难。 还有一种玫瑰图的绘制也较难&#xff0c;今提供玫瑰图的绘制方法供参考。 本方法采用C语言的最基本功能&#xff1a; &am…

ESP8266智能家居(1)——开发环境的搭建

1.前期介绍 本次打算使用esp8266的开发板——NodeMCU&#xff0c;进行物联网相关项目的学习。开发环境使用Arduino软件。 NodeMCU实物图为&#xff1a; 开发环境截图为&#xff1a; 2.软件下载 我使用的arduino版本为1.8.5&#xff0c;其安装包如下&#xff1a; 【免费】ar…

【鸿蒙 HarmonyOS 4.0】路由router

一、介绍 页面路由指在应用程序中实现不同页面之间的跳转和数据传递。HarmonyOS提供了Router模块&#xff0c;通过不同的url地址&#xff0c;可以方便地进行页面路由&#xff0c;轻松地访问不同的页面。 二、页面跳转 2.1、两种跳转模式&#xff1a; router.pushUrl()&…

浅谈兼容性测试的概念及目的

兼容性测试是软件测试中的一项重要工作&#xff0c;旨在验证系统在不同环境、不同设备和不同浏览器下的正常运行和稳定性。 1. 兼容性测试的概念 兼容性测试是指在多样化的硬件、操作系统、浏览器、网络环境等条件下&#xff0c;验证软件或应用程序的稳定性和一致性。这种测试是…

电商+支付双系统项目------电商系统中收货模块的开发

本篇文章是讲关于项目的收货地址模块的设计。这个就比较简单了&#xff0c;我就不像之前的文章讲的那么详细了&#xff0c;就简单讲讲就好。 首先先设计 DAO 层&#xff1a; package com.imooc.mall.dao;import com.imooc.mall.pojo.Shipping; import org.apache.ibatis.annot…

Linux-基础知识(黑马学习笔记)

硬件和软件 我们所熟知的计算机是由&#xff1a;硬件和软件组成。 硬件&#xff1a;计算机系统中电子&#xff0c;机械和光电元件等组成的各种物理装置的总称。 软件&#xff1a;是用户和计算机硬件之间的接口和桥梁&#xff0c;用户通过软件与计算机进行交流。 而操作系统…

研发流程图

1、需求评审流程 2、用例评审流程 3、代码评审流程 4、产品功能上线流程

信号通信与消息队列实现的通信:2024/2/23

作业1&#xff1a;将信号和消息队列的课堂代码敲一遍 1.1 信号 1.1.1 信号默认、捕获、忽略处理(普通信号) 代码&#xff1a; #include <myhead.h> void handler(int signo) {if(signoSIGINT){printf("用户键入 ctrlc\n");} } int main(int argc, const ch…