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

news2024/11/19 14:47:17

一、案例效果

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

二、案例代码

  • 封装左侧抽屉
    DrawerSearch.vue
<template>
  <div>
    <mtd-form :model="formDrawerSearch" ref="formCustom" inline>
      <mtd-form-item>
        <mtd-input
          type="text"
          v-model="formDrawerSearch.hostname"
          placeholder="搜索已有图表"
          style="width: 130px"
        />
      </mtd-form-item>
      <mtd-form-item>
        <mtd-select
          v-model="formDrawerSearch.searchOrder"
          placeholder="按修改时间排序"
          style="width: 145px"
          clearable
          filterable
        >
          <mtd-option
            v-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-button
              v-if="item.isAdd"
              class="float-right"
              ghost
              type="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">
    <draggable
      class="drag-content"
      :list="rightList"
      :group="{ name: 'items', put: true }"
      @add="onAdd"
    >
      <div
        v-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-input
                type="text"
                v-model="formData.boardName"
                placeholder="请输入看板名称,最多128字符"
                :maxlength="128"
                style="width: 260px"
              />
            </mtd-form-item>
            <mtd-form-item label="归属风险域" prop="riskDomain">
              <mtd-select
                v-model="formData.riskDomain"
                placeholder="请选择"
                style="width: 160px"
              >
                <mtd-option
                  v-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-input
                type="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’设置样式

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

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

相关文章

美团一面:给定两棵二叉树 `A` 和 `B`,判断 `B` 是否是 `A` 的子结构?

目录标题 问题描述思路分析代码解释详细步骤复杂度分析 问题描述 给定两棵二叉树 A 和 B&#xff0c;判断 B 是否是 A 的子结构。所谓子结构是指 B 中任意节点在 A 中存在相同的结构和节点值。 例子1&#xff1a; 输入&#xff1a;tree1 [1,7,5], tree2 [6,1] 输出&#…

LeaferJS 动画、状态、过渡、游戏框架

LeaferJS 现阶段依然专注于绘图、交互和图形编辑场景。我们引入游戏场景&#xff0c;只是希望让 LeaferJS 被更多有需要的人看到&#xff0c;以充分发挥它的价值 LeaferJS 为你带来了全新的游戏、动画、状态和过渡功能&#xff0c;助你实现那些年少时的游戏梦想。我们引入了丰富…

NVIDIA TAO 工具套件5.3.0学习介绍及操作-01

什么是 NVIDIA TAO 工具套件&#xff1f; NVIDIA TAO 工具套件基于 TensorFlow 和 PyTorch 构建&#xff0c;是 NVIDIA TAO 框架的低代码版本&#xff0c;通过抽象出 AI/深度学习框架的复杂性来加速模型训练过程。TAO 工具套件让您利用迁移学习的强大功能和自己的数据对预训练 …

Remotion:使用前端技术开发视频

前言 最近做文章突然想到很多文章其实也可以用视频的方式来展现&#xff0c;以现在短视频的火爆程度&#xff0c;肯定能让更多的人看到。 恰巧最近看了很多关于动画的前端 js 库&#xff0c;那如果将这些动画帧连续起来&#xff0c;岂不是就成了一个视频吗&#xff1f; 而且…

集成Elasticsearch到django restful

文章目录 集成ES到django restful服务端项目安装haystack基本使用安装配置索引模型ORM模型中新增discount_json字段方法全文索引字段模板 索引序列化器全文搜索的索引视图路由手动构建es索引 集成ES到django restful服务端项目 如果直接在Django项目直接编写代码作为ElasticSe…

YOLOv5白皮书-第Y2周:训练自己的数据集(云jupyter运行版 )

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](小团体&#xff5e;第八波) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊](K同学啊-CSDN博客)** 目录 前言 一、.xml文件里保存的是什么 二、准备好自己的数据 三、创建split_tr…

spring boot 3 + 虚拟线程 + MDC traceId

虚拟线程&#xff08;Virtual Thread&#xff09;也称协程或纤程&#xff0c;是一种轻量级的线程实现&#xff0c;与传统的线程以及操作系统级别的线程&#xff08;也称为平台线程&#xff09;相比&#xff0c;它的创建开销更小、资源利用率更高&#xff0c;是 Java 并发编程领…

ChatGPT-4模型镜像站对比和【软件开发人员】提示词

AI如今很强大&#xff0c;聊聊天、写论文、搞翻译、写代码、写文案、审合同等等&#xff0c;ChatGPT 真是无所不能~ 作为一款出色的大语言模型&#xff0c;ChatGPT 实现了人类般的对话交流&#xff0c;最主要是能根据上下文进行互动。 接下来&#xff0c;我将介绍 ChatGPT 在…

活动|华院计算参与《数字生态指数2024》报告发布并受邀主题分享

9月20-21日&#xff0c;“第二届数字生态与治理论坛暨数字生态指数2024发布会”在湖北省武汉市举办。华院计算作为研究团队一员受邀出席论坛&#xff0c;并发表“人工智能赋能基层社会治理”主题演讲。 《数字生态指数2024》报告为国家对外积极调整全球数字治理战略定位&#x…

UE学习篇ContentExample解读------Blueprint_Communication-下

文章目录 总览描述批次阅览2.1 Using an Event Dispatcher function to call an event in the level Blueprint2.2 Binding an Event Dispatcher function to a custom event2.3 Binding an Event Dispathcer to a custom event on spawn3.1 Basic communication using a Bluep…

Java Set类

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;Java 目录 &#x1f449;&#x1f3fb;set1. 接口与实现2. 特性3. 常用方法4. 示例代码5. 遍历6. 线程安全 &#x1f449;&#x1f3fb;set更多方法1. 其他常…

赛氪作媒体支持单位受邀参加首届科普翻译与跨学科专业学术研讨会

2024年9月22日&#xff0c;正值全国科普日之际&#xff0c;首届科普翻译与跨学科专业学术研讨会在上海健康与营养研究所信息中心励志厅成功举行并圆满结束。此次研讨会汇聚了来自全国各地的近60名专家学者、学界及企业界代表&#xff0c;共同探讨科普翻译与跨学科专业的发展。作…

封装一个vue3的文件上传组件(拖拽或点击选择文件)

1. 效果 选择文件后: 2. 代码 <template><divclass"drop-zone c-normal":class"{borderOutline: outline,}"dragover.preventdrop.prevent"handleDrop"click"chooseFiles"><div v-if"files.length < 1"…

FLStudio21Mac版flstudio v21.2.1.3430简体中文版下载(含Win/Mac)

给大家介绍了许多FL21版本&#xff0c;今天给大家介绍一款FL Studio21Mac版本&#xff0c;如果是Mac电脑的朋友请千万不要错过&#xff0c;当然我也不会忽略掉Win系统的FL&#xff0c;链接我会放在文章&#xff0c;供大家下载与分享&#xff0c;如果有其他问题&#xff0c;欢迎…

使用Vue.extend( ) 模仿 elementui 创建一个类似 message 消息提示框

提示&#xff1a;记录工作中遇到的需求及解决办法 文章目录 前言一、目录结构二、代码1. 创建 m-Toast.vue 文件2. 创建 global.js 文件3. 在 main.js 文件中导入 global.js 文件4. 在 App.vue 文件中使用 全局方法创建的 组件 前言 在此之前一直不明白Vue.extend( )干什么用的…

PCL 用八叉树完成空间变化检测

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1八叉树构建与变化检测 2.1.2检测变化的点云 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更…

快速排序(C语言实现)

目录 基本概念 Hoare版本 动图演示 思路 代码实现&#xff1a; 性能分析 取Key优化 三数取中法选择基准&#xff08;Median-of-Three Partitioning&#xff09; 实现步骤 代码实现 挖坑法 基本步骤 动图 示例说明 代码实现 前后指针法 动图示范 思路 代码实…

Linux操作系统中docker

1、docker概述 1、什么是docker Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux或Windows&#xff08;对于windows不是太友好&#xff09;操作系统的机器上&#xff0c;也可以…

Amazon EC2:权限设置指南,构建安全的云环境

在数字化转型的浪潮中&#xff0c;企业纷纷将业务迁移到云端&#xff0c;以提高灵活性和效率。Amazon Elastic Compute Cloud&#xff08;EC2&#xff09;作为 AWS 的核心服务之一&#xff0c;为企业提供了一个强大的云计算平台。然而&#xff0c;随着云环境的复杂性增加&#…

DHCP 中继器

在实际应用中可能会遇到一个比较大的物理网络中存在多个ip子网&#xff0c;而每个ip子网的主机都需要DHCP服务器来动态分配ip地址&#xff0c;实现的方法有两种: 第一种是在每一个子网中设置DHCP服务器&#xff0c;将其分别为每个子网分配ip地址&#xff0c;但此方法会增加开销…