当前位置: 首页 > news >正文

vue实现左侧数据拖拽到右侧区域,且左侧数据保留且左侧数据不能互相拖拽改变顺序

一、案例效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、案例代码

  • 封装左侧抽屉
    DrawerSearch.vue
<template><div><mtd-form :model="formDrawerSearch" ref="formCustom" inline><mtd-form-item><mtd-inputtype="text"v-model="formDrawerSearch.hostname"placeholder="搜索已有图表"style="width: 130px"/></mtd-form-item><mtd-form-item><mtd-selectv-model="formDrawerSearch.searchOrder"placeholder="按修改时间排序"style="width: 145px"clearablefilterable><mtd-optionv-for="item in searchOrderList":key="item.value":label="item.label":value="item.value"/></mtd-select></mtd-form-item></mtd-form></div>
</template>
<script lang="ts" setup name="DrawerSearch">
import { ref, watch } from 'vue';const $emit = defineEmits(['formDrawerSearch']);const formDrawerSearch = ref({hostname: '',searchOrder: '',
});
const searchOrderList = ref([{value: 'tag1',label: '按照修改人',},{value: 'tag2',label: '按照风险域',},{value: 'tag3',label: '按照数据集',},
]);watch(() => formDrawerSearch.value,() => {$emit('formDrawerSearch', formDrawerSearch.value);},{ deep: true },
);
</script>

DrawerContent.vue

<template><div class="drawer-content pr-5"><draggable:list="cardList":move="onMove":group="{ name: 'items', pull: 'clone', put: false }":clone="checkAndCloneItem":sort="false"item-key="id"><div v-for="(item, index) in cardList" :key="index"><mtd-card class="card-box"><div><span class="text-[13px] font-semibold">{{ item.title }}</span><mtd-buttonv-if="item.isAdd"class="float-right"ghosttype="primary"size="small">已添加</mtd-button></div><div class="mt-3 card-content"><div>可视化类型:{{ item.chartType }}</div><div>数据集:<a @click="goChartDetail()">{{ item.dataSet }}</a></div><div>修改:{{ item.modify }}</div></div></mtd-card></div></draggable></div>
</template>
<script lang="ts" setup name="DrawerContent">
import { BoardDrawerItemType } from '@/type/aggregateAnalysis';
import { Message } from '@ss/mtd-vue';
import { ref } from 'vue';
import Draggable from 'vuedraggable';const $emit = defineEmits(['cloneItem']);
const props = defineProps(['rightList']);const cardList = ref<BoardDrawerItemType[]>([{id: 1,title: '12133场景',isAdd: true,chartType: '折线图',dataSet: '全量数据集',modify: '2024-03-01/jinlidan',},{id: 2,title: 'asdasd场景',isAdd: true,chartType: '柱形图',dataSet: '全量数据集',modify: '2024-05-06/jinlidan',},{id: 3,title: '88999场景',isAdd: true,chartType: '饼图',dataSet: '全量数据集',modify: '2024-08-09/jinlidan',},{id: 4,title: 'dfaaaa场景',isAdd: true,chartType: '饼图',dataSet: '全量数据集',modify: '2024-08-09/jinlidan',},{id: 5,title: '333场景',isAdd: true,chartType: '饼图',dataSet: '全量数据集',modify: '2024-08-09/jinlidan',},{id: 6,title: '66666场景',isAdd: true,chartType: '饼图',dataSet: '全量数据集',modify: '2024-08-09/jinlidan',},{id: 7,title: '7777场景',isAdd: true,chartType: '饼图',dataSet: '全量数据集',modify: '2024-08-09/jinlidan',},
]);
const checkAndCloneItem = (item: any) => {console.log('props.rightList', props.rightList, 'item', item);const isDuplicate = props.rightList.some((rightItem: any) => rightItem.id === item.id,);if (isDuplicate) {Message.warning('已有重复项目!');return false; // 返回 false 或 null 来阻止拖动}$emit('cloneItem', { ...item });return { ...item }; // 返回一个新对象,避免修改原始数据
};const onMove = (evt: any) => {// evt.dragged: 当前被拖拽的元素// evt.related: 当前拖拽元素下的DOM元素const draggedEl = evt.dragged;const relatedEl = evt.related;// 给被拖拽的元素添加样式draggedEl.classList.add('dragging-item');
};const goChartDetail = () => {};
</script>
<style lang="less" scoped>
.drawer-content {overflow: auto;height: calc(100vh - 272px);
}
.card-box {margin-bottom: 5px;margin-left: 0px;/deep/.mtd-card-body {padding: 10px;}.card-content {font-size: 12px;color: #6b7280c4;}
}
.dragging-item {width: 100%;padding: 8px;margin: 5px;border: 2px dashed #409eff; /* 示例样式:蓝色虚线边框 */opacity: 0.7; /* 透明度降低,增加拖拽感 */
}
</style>
  • 封装右侧区域

RightContent.vue

<template><div class="right-content"><draggableclass="drag-content":list="rightList":group="{ name: 'items', put: true }"@add="onAdd"><divv-for="(item, index) in filteredRightList":key="index"class="drag-content-item"><mtd-card class="card-box"><div><span class="text-[13px] font-semibold">{{ item.title }}</span></div></mtd-card></div></draggable></div>
</template>
<script lang="ts" setup name="RightContent">
import { BoardDrawerItemType } from '@/type/aggregateAnalysis';
import { getCloneItemHandle } from '@/utils/aggregateAnalysis';
import { ref, computed } from 'vue';
import Draggable from 'vuedraggable';const $emit = defineEmits(['rightList']);const rightList = ref<BoardDrawerItemType[]>([]);const filteredRightList = computed(() => {return rightList.value.filter((item) => item.title);
});const onAdd = (evt: any) => {console.log('onAdd triggered', evt);let newItem = evt.item;// 检查是否存在 _underlying_vm_ 属性if (newItem && newItem._underlying_vm_) {newItem = newItem._underlying_vm_;}console.log('New item:', newItem);const arr = JSON.parse(JSON.stringify(rightList.value));rightList.value = arr.filter((item: any) => item !== false && item.id);console.log('==rightList.value', rightList.value);$emit('rightList', rightList.value);if (newItem) {const clonedItem = getCloneItemHandle(newItem);// 检查是否已存在相同的项const existingIndex = rightList.value.findIndex((item) => item.id === clonedItem.id,);if (existingIndex === -1) {// 如果不存在,则添加新项rightList.value.splice(evt.newIndex, 0, clonedItem);}} else {console.error('无法从事件中获取新项');}
};
</script>
<style lang="less" scoped>
.right-content {height: 100%;.drag-content {display: flex;height: 100%;flex-wrap: wrap;white-space: nowrap;.drag-content-item {margin: 10px;width: calc(33.33% - 20px);height: 350px;background: #fff;.card-box {/deep/.mtd-card-body {padding: 10px;}}}}
}
</style>
  • 组件组合
    在这里插入图片描述
<template><div><AggregateAnalysisTab :activeTabType="'board'"><div slot="tabBoardContent"><BackTitle href="aggregateAnalysis">新建看板<div slot="buttonHandle"><mtd-button class="mr-3">取消</mtd-button><mtd-button type="primary">保存</mtd-button></div></BackTitle><div><mtd-form :model="formData" ref="formData" :rules="ruleData" inline><LabelModule :title="'定义看板'" /><mtd-form-item label="看板名称" prop="boardName"><mtd-inputtype="text"v-model="formData.boardName"placeholder="请输入看板名称,最多128字符":maxlength="128"style="width: 260px"/></mtd-form-item><mtd-form-item label="归属风险域" prop="riskDomain"><mtd-selectv-model="formData.riskDomain"placeholder="请选择"style="width: 160px"><mtd-optionv-for="item in riskDomainList":key="item.value":label="item.label":value="item.value"/></mtd-select></mtd-form-item><mtd-form-item label="看板说明" prop="boardRemark"><mtd-inputtype="text"v-model="formData.boardRemark"placeholder="请对看板的数据的业务含义说明,最多1024字符":maxlength="1024"style="width: 560px"/></mtd-form-item></mtd-form></div><LabelModule :title="'配置看板'"><template slot="buttonHandle"><mtd-button>高级配置</mtd-button></template></LabelModule><div class="config-box flex clear-both"><div class="left-drawer flex"><DragDrawer ref="drawer" mode="left"><template slot="content"><DrawerSearch @formDrawerSearch="getFormDrawerSearch" /><DrawerContent :rightList="rightList" /></template></DragDrawer></div><div class="right-content-box"><RightContent @rightList="getRightList" /></div></div></div></AggregateAnalysisTab></div>
</template>
<script lang="ts">
import { debounce } from '@/common/index';
import AggregateAnalysisTab from '@/components/aggregateAnalysis/AggregateAnalysisTab.vue';
import DragDrawer from '@/components/aggregateAnalysis/widgets/DragDrawer.vue';
import BackTitle from '@/components/base/BackTitle.vue';
import LabelModule from '@/components/base/LabelModule.vue';
import { DrawerBoardType } from '@/type/aggregateAnalysis';
import { Component, Vue } from 'vue-property-decorator';
import DrawerContent from './components/DrawerContent.vue';
import DrawerSearch from './components/DrawerSearch.vue';
import RightContent from './components/RightContent.vue';@Component({name: 'AggregateBoardInfo',components: {AggregateAnalysisTab,BackTitle,LabelModule,DragDrawer,DrawerSearch,DrawerContent,RightContent,},
})
export default class AggregateBoardInfo extends Vue {private formData = {boardName: '',riskDomain: '',boardRemark: '',};private ruleData = {boardName: [{required: true,message: '请输入',},],riskDomain: [{required: true,message: '请选择',},],};private riskDomainList = [{value: 'tag1',label: '标签1',},{value: 'tag2',label: '标签2',},{value: 'tag3',label: '标签3',},];private rightList: any = [];/*** 查询图表列表*/private getChartList() {}/*** 获取图表查询参数*/private getFormDrawerSearch(params: DrawerBoardType) {console.log('128--params', params);debounce(this.getChartList);}private getRightList(params: any) {this.rightList = [...params];}
}
</script>
<style lang="less" scoped>
.back-title {/deep/span {line-height: 33px;}
}
.config-box {height: 100%;.right-content-box {background: #0c3fa6de;flex: 1;margin-left: 30px;overflow: auto;height: calc(100vh - 272px);}
}
</style>
  • utils/index
export const getCloneItemHandle = (params: any) => {return { ...params };
};

三、文件目录

  • views
    在这里插入图片描述
  • components
    在这里插入图片描述

四、总结

  • 采用 vuedraggable 插件进行功能实现
  • 保留左侧数据 主要是 :group="{ name: 'items', pull: 'clone', put: false }" 中 ‘clone’
  • 保持左侧数据不能互相拖拽改变顺序 主要是配置 :sort="false" item-key="id"
  • 左侧拖拽到右侧数据去重主要采用 :clone="checkAndCloneItem"实现
  • 拖动到右侧区域之前可以改变的样式采用 :move="onMove"添加 'dragging-item’设置样式

相关文章:

  • 注册中心Eureka
  • 面试-2024年9月13号
  • I2C通信中的当前地址指针(CADDR)工作原理
  • 【韩顺平Java笔记】第3章:变量
  • Spring Boot 配置全流程 总结
  • 【linux 多进程并发】linux进程状态与生命周期各阶段转换,进程状态查看分析,助力高性能优化
  • 51单片机和ARM单片机的区别
  • “领航猿1号” 正式更名为 “AGI舰长”
  • 代码随想录冲冲冲 Day59 图论Part10
  • 数据结构 ——— C语言实现无哨兵位单向不循环链表
  • Linux基础命令lsblk详解
  • vue限定类型上传文件 最简单实践(单个可文件、可图片)
  • Hive数仓操作(五)
  • STM32--GPIO点亮LED灯(手把手,超详细)
  • @antv/x6 动态的修改attr与prop,以及动态改变节点的大小
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • extjs4学习之配置
  • JS字符串转数字方法总结
  • Python十分钟制作属于你自己的个性logo
  • Vultr 教程目录
  • 从PHP迁移至Golang - 基础篇
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 每天10道Java面试题,跟我走,offer有!
  • 你真的知道 == 和 equals 的区别吗?
  • 嵌入式文件系统
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 通过npm或yarn自动生成vue组件
  • 异常机制详解
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • postgresql行列转换函数
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • ​渐进式Web应用PWA的未来
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • #if #elif #endif
  • #数据结构 笔记一
  • ${factoryList }后面有空格不影响
  • (007)XHTML文档之标题——h1~h6
  • (13):Silverlight 2 数据与通信之WebRequest
  • (16)Reactor的测试——响应式Spring的道法术器
  • (19)夹钳(用于送货)
  • (2)STM32单片机上位机
  • (c语言+数据结构链表)项目:贪吃蛇
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (Java)【深基9.例1】选举学生会
  • (第二周)效能测试
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (完整代码)R语言中利用SVM-RFE机器学习算法筛选关键因子
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转)scrum常见工具列表
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转)菜鸟学数据库(三)——存储过程
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?