Vue 实现拖拽模块(三)自定义拖拽组件的样式

news2025/1/10 14:01:51

上文介绍了 自定义拖拽组件位置 的简单实现,本文将继续给大家分享如何自定义拖拽组件位置的简单实现,文中通过示例代码介绍,感兴趣的小伙伴们可以了解一下

本文主要介绍了 Vue 自定义拖拽组件的样式,具体如下:
支持通过右侧的属性配置去处理画布中不同元件的不同样式

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

实现过程

  1. 需要在每个元件上初始化一个 style 的集合,用来保存元件的基础样式,后续更改的样式也保存到对应的属性中
  2. 在 data 中定义一个保存当前选中元件的属性 curControl
  3. 并在渲染拖拽元素的列表时,将默认的属性通过 Vue 的动态绑定的方式绑定到每个元件身上
  4. 当用户选中画布中某个元件的时候,读取这个元件的属性,保存到 curControl 中
  5. 右侧样式配置根据 curControl 中元件的基础样式显示元件当前的样式配置
  6. 如果样式配置有更改的话,就会触发元素的回流 / 重绘去变成元素的样式

完整代码

<template>
  <div class="box">
    <!-- 左侧拖拽组件 -->
    <!-- v-if="false" -->
    <div class="drap">
      <!-- <p>元素</p> -->
      <!-- 
            @dragstart  < -- 是元素开始拖拽的时候触发
            draggable="true"  < -- 为了使元素可拖动,把 draggable 属性设置为 true :
            @dragover.prevent < -- 阻止浏览器默认行为,不然会显示一个叉叉,不好看, 加上会显示一个添加的符号
         -->
      <div
        v-for="(item, index) in drapLeftElList"
        class="drap-item"
        :key="index"
        @dragstart="handleDrapEvList($event, item)"
        @dragover.prevent
        draggable="true"
      >
        <img
          class="drap-item-img"
          draggable="false"
          :src="item.imgUrl"
          :alt="item.name"
        />
        <div class="drap-item-name">{{ item.name }}</div>
      </div>
    </div>
    <!-- 主体部分 -->
    <div
      class="drap-container"
      @dragover.prevent
      @mousedown="laryerMouseDown"
      @mousemove="laryerMouseMove"
      @mouseup="laryerMouseUp"
      @drop="handleDrap"
    >
      <h1>画布</h1>
      <div
        v-for="(component, index) in componentsList"
        class="drap-container-item"
        :class="{
          'drap-container-item-active':
            curControl && component.identifier == curControl.identifier,
        }"
        :key="index"
        :style="{
          top: `${component.position.y}px`,
          left: `${component.position.x}px`,
          width: `${component.position.w}px`,
          height: `${component.position.h}px`,
          'background-color': `${component.position.bg}`,
          borderWidth: component.style.borderWidth + 'px',
          borderStyle: component.style.borderStyle,
          borderColor: component.style.borderColor,
          borderRadius: component.style.radius + 'px',
        }"
        @mousedown.stop="handleMouseDown($event, component, index)"
      >
        <img
          class="drap-item-img"
          :src="component.imgUrl"
          draggable="false"
          :alt="component.name"
        />
        <div class="drap-item-name">{{ component.name }}</div>

 
      </div>
    </div>
    <!-- 属性配置 -->
    <div class="drap-right" style="width: 300px; height: 100%">
      <h2>属性配置</h2>
      <p>样式配置</p>
      <div v-if="curControl">
        <table>
          <tr>
            <td>宽度</td>
            <td>
              <el-input type="number" v-model="curControl.position.w" />
            </td>
          </tr>
          <tr>
            <td>高度</td>
            <td>
              <el-input type="number" v-model="curControl.position.h" />
            </td>
          </tr>
          <tr>
            <td>背景色</td>
            <td>
              <el-input type="color" v-model="curControl.position.bg" />
            </td>
          </tr>
          <tr>
            <td>边框大小</td>
            <td>
              <el-input type="number" v-model="curControl.style.borderWidth" />
            </td>
          </tr>
          <tr>
            <td>边框样式</td>
            <td>
              <el-select
                v-model="curControl.style.borderStyle"
                placeholder="请选择"
              >
                <el-option
                  label="solid"
                  value="solid"
                ></el-option>
                <el-option
                  label="dashed"
                  value="dashed"
                ></el-option>
                <el-option
                  label="dotted"
                  value="dotted"
                ></el-option>
              </el-select>
            </td>
          </tr>
          <tr>
            <td>边框颜色</td>
            <td>
              <el-input type="color" v-model="curControl.style.borderColor" />
            </td>
          </tr>
          <tr>
            <td>圆角大小</td>
            <td>
              <el-input type="number" v-model="curControl.style.radius" />
            </td>
          </tr>
        </table>
      </div>
      identifier: {{ identifier }}
      <br />
      curControl: {{ curControl }}
      <br />
      {{ containerMoveObj }}
    </div>
  </div>
</template>

<script>
export default {
  name: "drap",
  data() {
    return {
      // 保存拖拽的元素的列表
      componentsList: [
        {
          id: 11,
          name: "团队1",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 1,
          identifier: 666,
          position: {
            x: 100,
            y: 100,
            w: 180,
            h: 320,
            bg: "#ffffff",
          },
          // 
          style: {
            borderWidth: 0,
            borderStyle: "solid",
            borderColor: "#000",
            radius: 0,
          },
          temp: {
            position: {
              x: 100,
              y: 100,
            },
          },
        },
      ],
      //   元件库
      drapLeftElList: [
        {
          id: 11,
          name: "团队1",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 1,
          position: {
            x: 0,
            y: 0,
            w: 80,
            h: 120,
            bg: "#fff",
          },
          style: {
            borderWidth: 0,
            borderStyle: "solid",
            borderColor: "transparent",
            radius: 0,
          },
          temp: {
            position: {
              x: 0,
              y: 0,
            },
          },
        },
        {
          id: 13,
          name: "团队2",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 2,
          position: {
            x: 0,
            y: 0,
            w: 80,
            h: 120,
            bg: "#fff",
          },
          style: {
            borderWidth: 0,
            borderStyle: "solid",
            borderColor: "transparent",
            radius: 0,
          },
          temp: {
            position: {
              x: 0,
              y: 0,
            },
          },
        },
        {
          id: 14,
          name: "团队3",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 3,
          position: {
            x: 0,
            y: 0,
            w: 80,
            h: 120,
            bg: "#fff",
          },
          style: {
            borderWidth: 0,
            borderStyle: "solid",
            borderColor: "transparent",
            radius: 0,
          },
          temp: {
            position: {
              x: 0,
              y: 0,
            },
          },
        },
        {
          id: 15,
          name: "团队4",
          imgUrl:
            "http://192.168.0.103/admin/sys-file/ydcloud/4e4ab580ac7549bfb0b7b46db57b3bb4.png",
          sort: 3,
          position: {
            x: 0,
            y: 0,
            w: 80,
            h: 120,
            bg: "#fff",
          },
          style: {
            borderWidth: 0,
            borderStyle: "solid",
            borderColor: "transparent",
            radius: 0,
          },
          temp: {
            position: {
              x: 0,
              y: 0,
            },
          },
        },
      ],
      identifier: "", // 当前项的 唯一标识
      curControl: null, // 当前选择的组件
      flag: "", // 当前操作标识位
      containerMoveObj: {
        x: "",
        y: "",
      }, // 移动组件相关变量
      resizeItem: {
        startPx: 0,
        startPy: 0,
        x: 0,
        y: 0,
        w: 0,
        h: 0,
      }, //resize组件 相关变量
    };
  },
  methods: {
    resizeMousedown(component, ev, index, type) {
      console.log(component, ev, index, type);
      this.flag = type;

      this.handleClickTarget(component, index);
      this.resizeItem.startPx = ev.pageX;
      this.resizeItem.startPy = ev.pageY;

      //记录初始信息-resize
      this.resizeItem.x = component.position.x;
      this.resizeItem.y = component.position.y;
      this.resizeItem.w = component.position.w;
      this.resizeItem.h = component.position.h;
    },

    // 点击画布的时候, 取消选择组件
    laryerMouseDown() {
      console.log("laryerMouseDown");
      this.curControl = null;
    },

    // 给画布绑定的mousemove事件
    laryerMouseMove(ev) {
      // 判断是需要移动的类型
      if (this.flag == "move") {
        // 用当前移动的距离减去点击的位置
        let dx = ev.pageX - this.containerMoveObj.x,
          dy = ev.pageY - this.containerMoveObj.y;

        // 上次旧的位置加上 处理完的距离就得到当前位置
        let x = this.curControl.temp.position.x + dx,
          y = this.curControl.temp.position.y + dy;
        // 这里只是让元素跟着鼠标移动, 如果再这里直接赋值
        this.curControl.position.x = x;
        this.curControl.position.y = y;
        // 判断是需要改变元素大小
      } else if (this.flag.includes("resize")) {
        console.log("resize,---", this.flag);
        switch (this.flag) {
          case "resize-rt":
            const { pageX, pageY } = ev;
            let dx = pageX - this.resizeItem.startPx,
              dy = pageY - this.resizeItem.startPy;

            this.curControl.position.w = this.resizeItem.w + dx;
            this.curControl.position.h = this.resizeItem.h + dy;
            console.log(dx, dy);

            break;
        }
      }
    },

    // 给画布绑定的mouseup事件
    laryerMouseUp() {
      // 在鼠标抬起的时候判断是否
      if (this.flag == "") {
        return false;
      }
      if ((this.flag = "move")) {
        const x = this.curControl.position.x;
        const y = this.curControl.position.y;
        // 这里才是实际给元素位置赋值的地方!!!!
        // 查询是否有对应的模块然后, 对应的赋值
        this.componentsList.forEach((item) => {
          if (item.identifier == this.identifier) {
            console.log(item, "找到了");

            item.temp.position.x = x;
            item.temp.position.y = y;

            item.position.x = x;
            item.position.y = y;
          }
        });
      } else if (this.flag.includes("resize")) {
      }

      this.flag = "";
    },

    // 拖拽元素
    handleDrapEvList(event, value) {
      let { offsetX, offsetY } = event;
      var infoJson = JSON.stringify({
        ...value,
        position: {
          ...value.position,
          x: offsetX,
          y: offsetY,
        },
      });
      //   将数据绑定到dataTransfer身上
      event.dataTransfer.setData("drapData", infoJson);
      //   console.log(
      //     "🚀 ~ file: index.vue ~ line 58 ~ handleDrapEvList ~ ev, value",
      //     event,
      //     value
      //   );
    },

    // 监听拖拽元素结束
    handleDrap(event) {
      event.preventDefault();
      const value = event.dataTransfer.getData("drapData");
      //   获取绑定到拖拽元素身上的 drapData属性
      if (value) {
        let drapData = JSON.parse(value);
        const { position } = drapData;
        const identifier = Math.floor(Math.random() * 10000);
        this.componentsList.push({
          ...drapData,
          identifier,
          position: {
            ...position,
            x: event.offsetX - position.x,
            y: event.offsetY - position.y,
          },
          temp: {
            position: {
              x: event.offsetX - position.x,
              y: event.offsetY - position.y,
            },
          },
        });
      }
    },

    // 点击元素获取组件配置
    handleClickTarget(row, index) {
      console.log(row);
      this.identifier = row.identifier;
      this.curControl = row;
    },

    // 移动元素
    handleMouseDown(e, row, index) {
      this.flag = "move";
      // 获取组件配置, 为接下来的属性配置做准备
      this.handleClickTarget(row, index);
      e = e || window.event;

      // 记录下当前点击的位置

      this.containerMoveObj.x = e.pageX;
      this.containerMoveObj.y = e.pageY;
    },
  },
};
</script>

<style lang="scss">
.box {
  display: flex;
  flex-direction: row;
  align-items: center;
  position: relative;
  height: 500px;
  .drap {
    width: 300px;
    height: 500px;
    background: #f2f2f2;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    cursor: pointer;
    .drap-item {
      height: 120px;
      margin-right: 20px;
      .drap-item-img {
        display: block;
        width: 80px;
        height: 80px;
      }
      .drap-item-name {
        text-align: center;
      }
    }
  }
  .drap-container {
    flex: 1;
    height: 500px;
    background: #ccc;
    position: relative;

    .drap-container-item {
      -webkit-user-select: none;
      -moz-user-select: none;
      -o-user-select: none;
      user-select: none;
      position: absolute;
      user-select: none;
      cursor: pointer;
      border: 1px solid transparent;
      .drap-item-img {
        display: block;
        width: 100%;
        // height: 80px;
        user-select: none;
      }
      .drap-item-name {
        text-align: center;
      }

      .resize-icon {
        position: absolute;
        height: 10px;
        width: 10px;
        background-color: white;
        border: 1px solid #0cf;
        // cursor: nwse-resize;
        border-radius: 50%;
      }

      .resize-left-top {
        left: -15px;
        top: -15px;
        cursor: nwse-resize;
      }

      .resize-left-center {
        left: -15px;
        top: 50%;
        margin-top: -10px;
        cursor: ew-resize;
      }

      .resize-left-bottom {
        left: -15px;
        bottom: -15px;
        cursor: nesw-resize;
      }

      .resize-right-top {
        right: -15px;
        top: -15px;
        cursor: nesw-resize;
      }

      .resize-right-center {
        right: -15px;
        top: 50%;
        margin-top: -10px;
        cursor: ew-resize;
      }

      .resize-right-bottom {
        right: -15px;
        bottom: -15px;
        cursor: nwse-resize;
      }

      .resize-center-top {
        top: -15px;
        left: 50%;
        margin-left: -10px;
        cursor: ns-resize;
      }

      .resize-center-bottom {
        bottom: -15px;
        left: 50%;
        margin-left: -10px;
        cursor: ns-resize;
      }
    }
    .drap-container-item-active {
      border: 1px solid skyblue;
    }
  }
}
</style>

问题答疑

总结

以上就是今天要分享的内容,本文简单介绍了 自定义拖拽组件的样式 ( 如您发现本文代码的逻辑异常、或文章表述不清等问题,敬请留言或私信 ♥♥♥ )

接下来我们会逐步去实现针对拖拽组件的设置属性、绑定事件等操作

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

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

相关文章

番外-LogParser(IIS日志分析)

编写原因&#xff1a;在线的那个信息管理系统&#xff0c;总有人添加空白数据&#xff0c;一加就是很多条&#xff0c;用这个分析一下&#xff0c;再发现之后将其IP拉入黑名单 1&#xff0c;下载安装 网盘下载链接 提取码&#xff1a;229e 文件下载后解压&#xff0c;解压后为…

Lecture5:卷积层、池化层、全连接层

目录 1.卷积层、池化层、全连接层 1.1 全连接层 1.2 卷积层 1.3 池化层 1.卷积层、池化层、全连接层 1.1 全连接层 对全连接层而言&#xff0c;我们要做的就是在这些向量上进行操作&#xff0c;比如我们有一张RGB-D图片&#xff0c;它的大小为32*32*3&#xff0c;我们将所…

vue3 几款值得推荐的UI组件库

推荐几个比较流行的VUE3 UI框架&#xff0c;同时提供出色的开发人员体验&#xff0c;合理利用&#xff0c;又或者学习借鉴都是不错的选择&#xff0c;排名不分先后。 Ant Design Vue 官方网站&#xff1a;https://2x.antdv.com/components/overview/ Ant Design Vue 是一个非…

PingCAP 成为中国唯一入选 Forrester Wave 数据库厂商,被评为卓越表现者

2022 年 12 月 6 日&#xff0c;国际权威研究机构 Forrester 发布了「Forrester Wave™: Translytical Data Platforms, Q4 2022 」报告&#xff0c;企业级开源分布式数据库厂商 PingCAP 作为中国唯一入围的数据库厂商&#xff0c;首次参评该报告即获评“卓越表现者&#xff08…

微服务框架 SpringCloud微服务架构 29 ES 集群 29.3 集群职责及脑裂

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构29 ES 集群29.3 集群职责及脑裂29.3.1 ES 集群的节点角色29.3.2 ES集群的分…

RAID图解

RAID图解什么是RAID各种 RAID 详解RAID 0RAID 1RAID 2&#xff08;已淘汰&#xff09;RAID 3RAID 4RAID 5RAID 6RAID 7RAID 01RAID 10RAID 10和RAID 01有何区别&#xff1f;实操教程其他问题最近在涉及到服务器安装系统这块才了解到RAID这个知识点&#xff0c;上网了解该知识&a…

Nginx 动静分离相关配置解析

Nginx 动静分离相关配置解析 本篇主要介绍nginx 动静分离相关配置解析 概述 动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来&#xff0c;动静资源做好了拆分以后&#xff0c;我们就可以根据静态资源的特点将其做缓存操作&#xff0c;这就是…

基于jsp+mysql+ssm智慧仓库进销存系统-计算机毕业设计

项目介绍 企业仓库库存系统的设计在SSM的框架下&#xff0c;采用JAVA编程语言和JSP技术&#xff0c;&#xff0c;并使用Mysql作为系统的数据库。该系统设计了强大的功能模块&#xff0c;考虑了企业库存管理的各个方面。这些主要功能模块分别是系统用户管理、用户信息管理、商品…

CUDNN与CUDA的踩坑与记录

CUDNN与CUDA的踩坑与记录 CUDA的安装 nvidia-smi看显卡驱动能够安装的最高版本的CUDA下载CUDA–>点击安装过程去掉显卡驱动安装完成的结果如下&#xff1a;Summary Driver: Not Selected Toolkit: Installed in /usr/local/cuda-10.2/ Samples: Installed in /home/g…

【世界杯中的安全思考】工控设备

目录 1、总述 2、半自动越位技术&#xff1a;人工智能辅助裁判 3、卡塔尔的智能道路 4、体育场的冷却技术 5、医疗援助的可穿戴设备 6、实时导航 7、照明系统 8、感官观察室 1、总述 从世界杯带来的科技感&#xff0c;可以看出大到球场&#xff0c;小到足球&#xff0c;…

厨房装修竟然有这么多你不知道的事

每个家庭的厨房大小、形状不同&#xff0c;厨房的建筑结构在房子中往往也是比较复杂的&#xff0c;橱柜等设施如何安置&#xff0c;都要因地制宜&#xff0c;很难说怎样做最好。但这并不意味着厨房的布局没有规律可循。在规划厨房布局时&#xff0c;最简单的办法就是按照做饭的…

微服务框架 SpringCloud微服务架构 29 ES 集群 29.1 集群结构介绍

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构29 ES 集群29.1 集群结构介绍29.1.1 ES集群结构29 ES 集群 29.1 集群结构…

银行软件测试简历模板,找工作的小伙伴看过来了

目录 个人简历 教育背景 工作经历 自我评价 专业技能 总结 重点&#xff1a;配套学习资料和视频教学 个人简历 姓  名&#xff1a; 性  别&#xff1a; 学  历&#xff1a; 经  验&#xff1a; 手  机&#xff1a; 邮  箱&#xff1a; 政治面貌&#…

水滴公司Q3财报引股价增长,保险业复苏“第一枪”打响了?

2020年以来&#xff0c;保险行业整体都笼罩在发展失速的低迷中。 2020年&#xff0c;在保险中介监管信息系统执业登记的保险销售从业人员有971.2万人&#xff0c;而2022年6月&#xff0c;这项数据为570.7万人&#xff0c;接近腰斩。公开数据显示&#xff0c;2022前三季度&…

DDR4时序标准规范(二)

DDR4时序标准规范编码模式寄存器(MRx)模式寄存器0(MR0)CAS延迟测试模式TM写恢复(WR)/读预充DLL(延时锁相环)复位模式寄存器1(MR1)DLL(延时锁相环)使能/禁用ODT RTT&#xff08;标称&#xff09;值AL(附加延迟)写入均衡终端数据选通(TDQS)模式寄存器2(MR2)CAS写延迟低功耗自动自…

在ros中创建yolov5-onnxruntime功能包

文章目录 工作目录依赖库ROS其他依赖库Yolov5-onnxruntimeCMakeLists.txt编译可能存在的问题工作目录 在vscode中新建功能包apple_detect 依赖库 ROS cv_bridgegeometry_msgsimage_transportmessage_generationroscpprospysensor_msgsstd_msgs其他依赖库 opencv4.6.0onnxru…

计算机毕业设计ssm+vue基本微信小程序的心理咨询服务系统 uniapp 小程序

项目介绍 随着计算机技术的发展,带来社会各行业的进步,信息化逐渐运用到人们的生活中。传统模式的会员心理健康管理满足不了现代人的生活追求,服务质量、服务速度,之前的很多网站由于功能、或者框架设计等原因,无法完美的展现它的特色,优势,浪费了很多资源。使用管理系统进行管…

绿色荧光试剂210236-90-1,Fitc-TSA,Fluorescein TSA,Fitc-Tyramide荧光素酪胺

试剂基团反应特点&#xff08;Reagent group reaction characteristics&#xff09;&#xff1a; Fluorescein Tyramide&#xff08;荧光素酪胺&#xff09;是一种绿色荧光试剂&#xff0c;广泛用于 IHC、ICC、FISH 和多色 FISH 中的酪胺信号放大 (TSA)。 HRP 催化多个酪胺分子…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java垃圾回收系统j16l0

这个选题的话其实有很多的&#xff0c;就看你自己能接受怎么样的&#xff0c;比如可以做网站类、系统类、小程序类、安卓app、大数据类等等&#xff0c;这个也要看你个人能力和技术问题&#xff0c;如果技术小白或者有一点点基础的话建议选择网站类和系统类的&#xff0c;如果有…

用HTML+CSS做一个漂亮简单大学生校园班级网页

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…