Vue 实现拖拽模块(五) - 上 定义元件的触发事件

本文详细介绍了如何在Vue项目中实现拖拽组件的事件配置,包括选择元件后配置单击、双击、按下和抬起等事件类型,以及调用服务、打开链接等触发方式。文章还展示了具体的代码实现,包括元件位置的动态调整和参数传递,并提到了即将实现的预览功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上文介绍了 拖拽元件位置 的简单实现

本文主要介绍了选中元件后配置元件的事件,同时支持配置触发方式、事件类型、事件参数(演示参数,可拓展…),具体如下:

效果图

在这里插入图片描述

事件配置描述

  1. 事件类型支持单击、双击、按下、抬起操作(可拓展多种事件)
  2. 触发类型支持调用服务、打开链接操作(可拓展多种类型)
  3. 执行参数是在不同触发类型下需要传递的参数(可拓展配置多个)

实现过程

  1. 需要给元件的数据源上面定义保存事件配置的集合,事件配置里面的所有参数都保存在这个集合中
  2. 在预览页面(这个功能下期会讲到)给元件绑定这几种事件类型,根据不同的触发类型执行不同的操作并把执行参数带过去执行

因为之前的样式UI不好看所以这里对右侧的样式UI做了调整,里面的实际功能不变

完整代码

<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
          @mousedown.stop="
            resizeMousedown(component, $event, index, 'resize-lt')
          "
          v-show="curControl && component.identifier == curControl.identifier"
          class="resize-icon resize-left-top"
        ></div>
        <div
          @mousedown.stop="
            resizeMousedown(component, $event, index, 'resize-lc')
          "
          v-show="curControl && component.identifier == curControl.identifier"
          class="resize-icon resize-left-center"
        ></div>
        <div
          @mousedown.stop="
            resizeMousedown(component, $event, index, 'resize-lb')
          "
          v-show="curControl && component.identifier == curControl.identifier"
          class="resize-icon resize-left-bottom"
        ></div>
        <div
          @mousedown.stop="
            resizeMousedown(component, $event, index, 'resize-rt')
          "
          v-show="curControl && component.identifier == curControl.identifier"
          class="resize-icon resize-right-top"
        ></div>
        <div
          @mousedown.stop="
            resizeMousedown(component, $event, index, 'resize-rc')
          "
          v-show="curControl && component.identifier == curControl.identifier"
          class="resize-icon resize-right-center"
        ></div>
        <div
          @mousedown.stop="
            resizeMousedown(component, $event, index, 'resize-rb')
          "
          v-show="curControl && component.identifier == curControl.identifier"
          class="resize-icon resize-right-bottom"
        ></div>
        <div
          @mousedown.stop="
            resizeMousedown(component, $event, index, 'resize-ct')
          "
          v-show="curControl && component.identifier == curControl.identifier"
          class="resize-icon resize-center-top"
        ></div>
        <div
          @mousedown.stop="
            resizeMousedown(component, $event, index, 'resize-cb')
          "
          v-show="curControl && component.identifier == curControl.identifier"
          class="resize-icon resize-center-bottom"
        ></div>
      </div>
    </div>
    <!-- 属性配置 -->
    <div class="drap-right" style="width: 300px; height: 100%">
      <h2>元件配置</h2>
      <el-tabs v-model="activeName" v-if="curControl">
        <el-tab-pane label="样式配置" name="first">
          <div>
            <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>
              <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>
            </table>
          </div>
        </el-tab-pane>
        <el-tab-pane label="数据配置" name="third">数据配置</el-tab-pane>
        <el-tab-pane label="事件配置" name="second">
          <table>
            <tr>
              <td>事件类型</td>
              <td>
                <el-select v-model="curControl.action.fnType" placeholder="请选择">
                  <el-option label="单击" value="click"></el-option>
                  <el-option label="双击" value="dbclick"></el-option>
                  <el-option label="按下" value="mousedown"></el-option>
                  <el-option label="抬起" value="mouseup"></el-option>
                </el-select>
              </td>
            </tr>
            <tr>
              <td>触发类型</td>
              <td>
                <el-select v-model="curControl.action.fnService" placeholder="请选择">
                  <el-option label="调用服务" value="service"></el-option>
                  <el-option label="打开链接" value="link"></el-option>
                </el-select>
              </td>
            </tr>
            <tr>
              <td>执行参数</td>
              <td>
                <el-input type="number" v-model="curControl.action.fnParams" />
              </td>
            </tr>
          </table>
        </el-tab-pane>
      </el-tabs>
    </div>
  </div>
</template>

<script>
export default {
  name: "drap",
  data() {
    return {
      activeName: "first",
      // 保存拖拽的元素的列表
      componentsList: [
        {
          id: 11,
          name: "团队1",
          imgUrl:
            "http://img2.3png.com/68604d0c41a6cbc132055d03bbfade602ff7.png",
          sort: 1,
          identifier: 666,
          position: {
            x: 100,
            y: 100,
            w: 100,
            h: 200,
            bg: "#ffffff",
          },
          // 事件配置
          action: {
            fnType: "",
            fnService: "",
            fnParams: "",
          },
          style: {
            borderWidth: 0,
            borderStyle: "solid",
            borderColor: "#000",
            radius: 0,
          },
          temp: {
            position: {
              x: 100,
              y: 100,
            },
          },
        },
      ],
      //   元件库
      drapLeftElList: [
        {
          id: 11,
          name: "团队1",
          imgUrl:
            "http://img2.3png.com/68604d0c41a6cbc132055d03bbfade602ff7.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,
            },
          },
          // 事件配置
          action: {
            fnType: "",
            fnService: "",
            fnParams: "",
          },
        },
      ],
      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")) {
        // 鼠标移动的当前位置
        const { pageX, pageY } = ev;
        // 鼠标移动的当前位置减去按下时的位置等于移动的x,y距离
        let dx = pageX - this.resizeItem.startPx,
          dy = pageY - this.resizeItem.startPy;
        // console.log("resize,---", this.flag);
        // switch中的逻辑主要控制的是元素的位置
        switch (this.flag) {
          // 上左
          case "resize-lt":
            // x,y是元件的实际位置 用移动的距离加上元件本身的位置, 就等于移动后的位置
            this.curControl.position.y = this.resizeItem.y + dy;
            this.curControl.position.x = this.resizeItem.x + dx;
            dx = -dx;
            dy = -dy;
            break;

          // 上中
          case "resize-ct":
            console.log(dy);
            this.curControl.position.y = this.resizeItem.y + dy;
            dx = 0;
            dy = -dy;
            break;

          // 上右
          case "resize-rt":
            this.curControl.position.y = this.resizeItem.y + dy;
            dy = -dy;
            break;

          // 中左
          case "resize-lc":
            console.log(dy);
            this.curControl.position.x = this.resizeItem.x + dx;
            dy = 0;
            dx = -dx;
            break;

          // 中右
          case "resize-rc":
            console.log(dx);
            dy = 0;
            break;

          // 下中
          case "resize-cb":
            dx = 0;
            break;
          // 下中
          case "resize-lb":
            this.curControl.position.x = this.resizeItem.x + dx;
            dx = -dx;
            break;
        }
        // 如果元件的宽高/加上移动的距离大于20, 则给元件重新赋值宽高
        if (this.resizeItem.w + dx > 20) {
          this.curControl.position.w = this.resizeItem.w + dx;
        }
        if (this.resizeItem.h + dy > 20) {
          this.curControl.position.h = this.resizeItem.h + dy;
        }
      }
    },

    // 给画布绑定的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;
        height: 60%;
        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>

问题答疑

如有问题敬请留言或私信提问哦

总结

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

接下来我们会逐步去实现针对拖拽组件的预览功能、调用触发事件的配置事件

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值