实现效果:

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

安装引用:

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方法通用的(传参不同),上面写的有。
GitHub 加速计划 / vu / vue
207.53 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:1 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 3 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 4 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐