实现效果:
左侧往右侧拖动,右侧列表可以进行拖拽排序。
安装引用:
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-treeref="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}}<draggableclass="list"forceFallback:animation="200"ghostClass="ghost"handle=".el-icon-rank"v-model="questionList":group="{name: 'component'}"><transition-group class="height-percent-100 display-block"><divclass="item":class="{active: item.active, error: item.error}"v-for="(item, index) in questionList":key="item.uid"><divclass="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-inputtype="textarea"autosize:ref="item.uid"v-elsesize="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-inputv-if="item.componentType === 'message'"readonlyplaceholder="请输入"type="textarea"autosizev-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-checkboxv-if="item.componentType === 'duoxuan'"v-model="item.answer" :label="i.value">{{ }}</el-checkbox><el-radiov-elsev-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-inputtype="textarea"autosize@blur="i.edit = false":ref="i.value"v-elsev-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 - 1const 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 = truethis.$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.componentNamedata.answer = ''data.active = falsedata.editTitle = falsedata.error = falsedata.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 = '' // 解析breakcase '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 = 10data.description = ''breakcase '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].valuedata.score = 10data.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-treeref="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方法通用的(传参不同),上面写的有。