讲讲项目里的仪表盘编辑器(四)分页卡和布局容器组件

news2024/11/20 8:29:12

        讲讲两个经典布局组件的实现

① 布局容器组件

        

        配置面板是给用户配置布局容器背景颜色等属性。这里我们不需要关注

        定义文件

         规定了组件类的类型、标签、图标、默认布局属性、主文件等等。

// index.js
import Container from './container.vue';
class ContainerControl extends BaseControl {
     type = 'container';

     label = '布局容器';

     icon = 'tc-icon-layout';
    
     ...
        
     layout = {
        w: 30,
        h: 15,
        minH: 8,
     };
         
     // 组件实现的主文件
     DashboardComponent = Container;
}

export default new ContainerControl();

        入口文件会通过一系列逻辑生成【类型枚举类】,我们最后通过control['container'].DashboardComponent找到主体文件生成组件。这些我们简单了解就好啦。

具体来看看container.vue文件。

        组件主体

// container.vue
<template>
  <drag-container
    v-bind="fieldProps"
    @inChildComponent="$emit('inChildComponent', $event)"
    @add="handleAdd"
    @delete="handleDelete"
    @drop="syncDataToStore('add', $event)"
  >
    <drag-container-layout
      v-bind="fieldProps"
      :layout.sync="layout"
      :fields="fields"
      @resized="syncDataToStore('size', $event)"
      @moved="syncDataToStore('location', $event)"
      @edit="syncDataToStore('edit', $event)"
      @delete="syncDataToStore('delete', $event)"
      @select="handleSelect"
    />
  </drag-container>
</template>

        这里的drag-container其实长这样:

// drag-container
<template>
  <div
    @dragenter="dragenter"
    @dragover="dragover"
    @dragleave="dragleave"
    @drop="drop"
  >
    <slot />
  </div>
</template>

        是不是很熟悉?对,就是上一章讲的包裹着组件的drag事件层。用来触发inChildComponent事件的。

         drag-container-layout其实就是一个 grid-layout。有运行时和设计时两种情况(设计时可以拖拽组件进去,运行时只是纯展示)

// drag-container-layout.vue
<template>
  <grid-layout
    :layout.sync="layout"
    :col-num="60"
    :row-height="15"
    :isDraggable="!isRuntime"
    :isResizable="!isRuntime"
    :useCssTransforms="!isRuntime"
  >
    <template v-for="layoutItem in layout">
      <!-- 运行时 -->
      <component
        v-if="isRuntime"
        :is="Item"
        :key="layoutItem.i"
        v-bind="getComponentProps(layoutItem)"
      />
      <!-- 设计时 -->
      <grid-item
        v-else
        :key="layoutItem.i"
        v-bind="getLayoutProps(layoutItem)"
        @moved="$emit('moved', layoutItem)"
        @resized="$emit('moved', layoutItem)"
        @mousedown.native.stop="handlePointerDown"
        @mouseup.native.stop="handlePointerUp($event, layoutItem.i)"
      >
            <component
              :is="getComponent(layoutItem)"
              v-bind="getComponentProps(layoutItem)"
              @deleteComponent="handleDelete({ i: $event })"
            />
      </grid-item>
    </template>
  </grid-layout>
</template>

        添加组件

        上一节我们已经将过点击添加到布局组件内,所以这节主要展开讲讲拖拽。逻辑跟上一节会有一些不一样,上一节主要还是为了方便理解。

        拖拽组件进入布局组件内部时,drag-container层首先响应。触发dragenter事件

  /** @name 进入-有效目标 **/
  dragenter() {
    if (this.limit) return;
    this.$emit('inChildComponent', true);
  }

         当拖拽进来的组件是布局组件时,this.limit为true。这里的业务逻辑是不允许多层嵌套所以在这里做了阻断。此时不会给外界传递inChildComponent事件,仪表盘的gird-layout也不需要改变this.isInChildCom。这里跟上一节讲的不一样,是因为vue-grid-layout这个组件本身不允许组件之间重叠(组件是有碰撞体积的)。所以即使它进入到布局组件内,布局组件内不接管,也会被插件阻拦。

        同时触发dragover事件,为了定位拖拽的组件在布局组件内的位置

** @name 移动-有效目标 **/
dragover(e) {
    if (this.limit) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = 'copy';
    this._dragover(e);
}

@throttle(100, { trailing: false })
_dragover(e) {
    if (
      this.dragContext.clientX === e.clientX &&
      this.dragContext.clientY === e.clientY
    )
      return;
    // 时刻记录鼠标的位置
    this.dragContext.clientX = e.clientX;
    this.dragContext.clientY = e.clientY;
    this.updateInside(e);
    this.updateDrag(e);
}

/** @name 拖拽上下文,用于记录鼠标位置 */
dragContext = {
    clientX: 0,
    clientY: 0,
};

         updateInside是为了在拖动的时候更新布局组件内的布局,让拖动元素在布局组件内部形成占位符。这一点在之前几章我都没讲过,是因为vue-grid-layout这个组件对拖拽效果已经做了很好的处理了,此时加上拖拽时占位,只不过是锦上添花的效果罢了。

  /** @name 判断拖动元素是否在拖动区域内,是则添加一项(占位符),否则删除一项 **/
  updateInside(ev) {
    // 获取布局组件内部区域位置大小
    const rect = this.$el.getBoundingClientRect();
    // 容错率
    const errorRate = 10;
    // 判断拖动元素是否在拖动区域内
    const inside =
      ev.clientX > rect.left + errorRate &&
      ev.clientX < rect.right - errorRate &&
      ev.clientY > rect.top + errorRate &&
      ev.clientY < rect.bottom - errorRate;
    if (this.dragLayout) {
      if (inside) {
        this.$emit('add', deepClone(this.dragLayout));
      } else {
        this.$emit('delete', deepClone(this.dragLayout));
      }
    }
  }

        add和delete最终指向是操作drag-container-layout.vue里的this.layout这个属性,也就是布局容器内的布局(add操作会查找this.layout是否重复存在这个拖拽元素)。可以理解为dragover操控更新了布局容器内的布局,而一旦dragleave,则会:

        ①取消接管仪表盘layout层的拖拽事件。恢复到仪表盘layout层进行接管

        ②更新布局组件内部

  /** @name 离开-有效目标 **/
dragleave(e) {
   if (this.limit) return;
   this.$emit('inChildComponent', false);
   this.updateInside(e);
}

        那么最最最关键的一环,无非是drop事件了。它的核心思路是把布局容器当前的layout里的draglayout拿出来,将它的位置属性记录在生成的拖拽组件属性中。并抛出到vuex仓库里进行存储。如果失败,也只需要删除视图层layout里的dragLayout组件罢了。

 /** @name 放置-有效目标 **/
  async drop() {
    if (this.limit) return;
    const dragLayout = deepClone(this.dragLayout);
    try {
      let field = createDashboardField(this.dragType);
      // 标记组件为子组件
      field.parentId = this.field.pkId;
      // 布局
      field.widget.layout = pick(dragLayout, 'x', 'y', 'w', 'h');
      // 添加到layout
      this.$emit(
        'add',
        {
          ...field.widget.layout,
          i: field.pkId,
        },
        dragLayout.i,
      );
      this.$emit('drop', field);
    } catch (e) {
      this.$emit('delete', dragLayout);
      throw e;
    }
  }
<drag-container
    ...
    @drop="syncDataToStore('add', $event)"
>
</drag-container>

           这个syncDataToStore方法会吧数据同步到vuex仓库,包括了新增/删除/变化。我们最后再讲。到这一步,我们已经把视图层关于新增的步骤完成了。

          删除组件

// drag-container.vue
/** @name 删除 **/
handleDelete(layout) {
    this.$emit('delete', layout);
}
// container.vue    
<drag-container-layout
      ...
      @delete="syncDataToStore('delete', $event)"
 />

       放大缩小组件/ 改变位置     

        vue-grid-layout负责抛出

<template v-for="layoutItem in layout">
    <grid-item
       ...
        @moved="$emit('moved', layoutItem)"
        @resized="$emit('sized', layoutItem)"
    >
        ...
    </grid-item>
</template >

          这里很巧妙的运用了this.layout属性,vue-grid-layout的官方示例用法是这样的:

        可以理解为这两个响应事件是返回了新的位置信息。而项目里的写法是利用了vue-grid-layout在moved或resized之后自身的this.layout也会随着改变,里面的layout-item也会跟随动态变化,所以直接把layout-item当做参数传出

// container.vue

    <drag-container-layout
      v-bind="fieldProps"
      :layout.sync="layout"
      :fields="fields"
      @resized="syncDataToStore('size', $event)"
      @moved="syncDataToStore('location', $event)"
      @delete="syncDataToStore('delete', $event)"
    />

        和添加组件一样,视图层逻辑到此结束,等待数据层处理

        数据层处理

        每个项目都有自己的处理方式,到这里视图层已经完成了自己的使命,把数据教辅给数据层进行存储变更。所以参考一下就行啦     

        

  /**
   * @name 同步到store
   * @param { String } type: 添加-add、删除-delete、大小变化-size、位置变化-moved
   * @param { Object } value: field、layout
   **/
  async syncDataToStore(type, value) {
    this.updateFields(fields => {
      const currentField = fields.find(field => field.pkId === this.field.pkId);
      const currentWidget = currentField.widget;
      if (type === 'add') {
        // 布局组件里面存储普通组件的字段
        currentWidget.fields.push(value);
      } else if (type === 'moved' || type === 'size') {
        // 移动会改变其他元素的位置, 所以整体要重复赋值x,y
        const layoutMap = generateMap(this.layout, 'i', layout => layout);
        currentWidget.fields.forEach(field => {
          field.widget.layout = pick(layoutMap[field.pkId], 'x', 'y', 'w', 'h');
        });
      } else if (type === 'delete') {
        const index = currentWidget.fields.findIndex(
          item => item.pkId === value.i,
        );
        currentWidget.fields.splice(index, 1);
      }
      return fields;
    });
    if (type === 'delete') {
      await this.$nextTick();
      // 记得更新视图,add就不用了,因为在dragover的时候已经更新了this.layout了
      this.syncLayout();
    }
  }

        特别注意的是,移动位置或 更改大小需要更新容器内所有组件的位置,因为可能会发生挤压或换行。

        区分父容器和布局容器里的点击事件

<grid-item
@mousedown.native.stop="handlePointerDown"></grid-item>

handlePointerDown(ev) {
    // 防止和父级选中冲突
    setTimeout(() => {
      this._pointerContext = {
        x: ev.clientX,
        y: ev.clientY,
      };
    });
  }

        settimeout(fn,0)会让方法在在下一轮“事件循环”开始时执行。从而避免与父容器冲突。

 

② 分页卡

        跟布局容器一样,只是数据存储多了一层嵌套

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

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

相关文章

六、vpp 流表+负载均衡

草稿&#xff01;&#xff01;&#xff01; vpp node其实就是三个部分 1、plugin init 2、set command 3、function 实现功能&#xff0c;比如这里的流表 今天我们再用VPP实现一个流表的功能 一、流表 1.1流表----plugin init VLIB_REGISTER_NODE 注册流表节点 // 注册流…

15-自动化测试——理论知识

目录 1.什么是自动化测试&#xff1f; 2.常见的自动化测试分类 2.1.单元测试&#xff08;Java、Python&#xff09; 2.2.接口测试&#xff08;Java、Python&#xff09; 2.3.UI测试&#xff08;移动端、网站&#xff09; 3.如何实施自动化测试&#xff1f; 4.自动化测试…

测开 | Vue速查知识点

文章目录 Vue知识1. Vue 概述2. Vue 代码格式3. Vue 指令3.1 v-bind & v-model3.2 v-on3.3 v-if和v-show3.4 v-for 4. 生命周期 Vue知识 1. Vue 概述 简介&#xff1a; Vue.js&#xff08;读音 /vjuː/, 类似于 view&#xff09; 是一套构建用户界面的 渐进式框架。与其他…

[QT编程系列-44]: Windows + QT软件闪退的检测方法

目录 一、Windows程序闪退的问题定位方法 1.1 Windows程序闪退 1.2 要找到Windows程序的crash点 1.3 当Windows程序崩溃时&#xff0c;可以尝试以下方法获取出错信息&#xff1a; 二、关键工具的进一步分析 2.1 Windows事件查看器&#xff08;Event Viewer&#xff09; …

openGauss学习笔记-87 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用简单概述

文章目录 openGauss学习笔记-87 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用简单概述87.1 授予用户权限87.2 创建/删除MOT87.3 为MOT创建索引 openGauss学习笔记-87 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用简单概述 使用…

【python的输入】sys.stdin与sys.argv

在老师的课堂里碰到了sys.stdin与sys.argv&#xff0c;虽然是很简单的东西&#xff0c;还是花了大半天的时间才勉强理解。在这里记录一下学习过程&#xff0c;方便以后用到复习。 一、sys.stdin 根据python3 library里的解释&#xff0c; sys.stdin可用于所有交互式的输入。 …

Vscode爆红Delete `␍`eslintprettier/prettier

一、先看报错 文件中爆红&#xff0c;提示 Delete ␍eslintprettier/prettier 二、解决方案 项目根目录下&#xff0c;.prettierrc.js 文件中&#xff1a; endOfLine: auto,三、重启VsCode 此时不在爆红&#xff0c;问题完美解决

STM32CubeMX学习笔记-USB接口使用(HID按键)

STM32CubeMX学习笔记-USB接口使用&#xff08;HID按键&#xff09; 一、USB简介1.1 USB HID简介 二、新建工程1. 打开 STM32CubeMX 软件&#xff0c;点击“新建工程”2. 选择 MCU 和封装3. 配置时钟4. 配置调试模式 三、USB3.1 参数配置3.2 引脚配置3.3 配置时钟3.4 USB Device…

数据分析与挖掘: 红楼梦人物关系(Python)词云图

一: 角色剧本 第一代&#xff1a;水字辈祖宗创下基业 贾源、贾演兄弟二人帮先帝打江山立下战功&#xff0c;贾演被封为宁国公&#xff08;大约有平定江山安宁天下之意&#xff09;&#xff0c;贾源被封荣国公&#xff08;大约有强国富民之功&#xff09;。贾源贾演二兄弟皆是一…

项目进展(五)-修复PCB电路板,学习32位ADC芯片ADS1285

一、前言 上个月29号放假了&#xff0c;和朋友一起去了南京(人是真滴多)&#xff0c;师兄晚放假几天&#xff0c;结果在测试时不小心把12V和GND碰触到一起了&#xff0c;导致12V短路&#xff0c;电路板几乎瘫痪了。 今天下午到学校之后就开始着手寻找问题和修复&#xff0c;最…

Kafka日志索引详解以及生产常见问题分析与总结

文章目录 1、Kafka的Log日志梳理1.1、Topic下的消息是如何存储的&#xff1f;1.1.1、 log文件追加记录所有消息1.1.2、 index和timeindex加速读取log消息日志。 1.2、文件清理机制1.2.1、如何判断哪些日志文件过期了1.2.2、过期的日志文件如何处理 1.3、Kafka的文件高效读写机制…

腾讯云服务器南京地域详细介绍、测试IP和Ping值测速

腾讯云服务器南京地域怎么样&#xff1f;南京地域很不错&#xff0c;正好处于中间的位置&#xff0c;南方北方用户均可以选择&#xff0c;网络延迟更低速度更快&#xff0c;并且目前南京地域有活动&#xff0c;南京地域可用区可选南京一区、南京二区和南京三区&#xff0c;腾讯…

软件设计模式系列之二十四——模板方法模式

在软件设计领域&#xff0c;设计模式是一组被反复使用、多次实践验证的经典问题解决方案。其中&#xff0c;模板方法模式是一种行为型设计模式&#xff0c;用于定义一个算法的骨架&#xff0c;将算法中的某些步骤延迟到子类中实现&#xff0c;从而使子类可以重新定义算法的某些…

互联网Java工程师面试题·Zookeeper 篇·第二弹

目录 13. 服务器角色 14. Zookeeper 下 Server 工作状态 15. 数据同步 16. zookeeper 是如何保证事务的顺序一致性的&#xff1f; 17. 分布式集群中为什么会有 Master&#xff1f; 18. zk 节点宕机如何处理&#xff1f; 19. zookeeper 负载均衡和 nginx 负载均衡区别 20…

代码随想录算法训练营第五十七天 | 392.判断子序列 115.不同的子序列

1. 判断子序列 392. 判断子序列 - 力扣&#xff08;LeetCode&#xff09; dp[i][j] 表示以下标i-1为结尾的字符串s&#xff0c;和以下标j-1为结尾的字符串t&#xff0c;相同子序列的长度。 class Solution {public boolean isSubsequence(String s, String t) {//dp[i][j] 表示…

Spring MVC:数据绑定

Spring MVC 数据绑定数据类型转换数据格式化数据校验 附 数据绑定 数据绑定&#xff0c;指 Web 页面上请求和响应的数据与 Controller 中对应处理方法上的对象绑定&#xff08;即是将用户提交的表单数据绑定到 Java 对象中&#xff09;。 过程如下&#xff1a; ServletRequest…

代数科学计算:LiveMath Maker v3.6.0 cRACK

LiveMath Maker 是一个数学计算机程序&#xff0c;您可以使用它来制作(并探索、实验和创建)LiveMath。您和LiveMath Maker制作的任何LiveMath都可以通过互联网与世界共享。 用LiveMath Maker 来进行数学研究&#xff0c;函数学习&#xff0c;深入探索、实验和创建你的LiveMath…

通用收藏管理器Koillection

什么是 Koillection &#xff1f; Koillection 是一个自托管的收藏管理器&#xff0c;旨在跟踪任何类型的物理&#xff08;主要&#xff09;收藏&#xff0c;如书籍、DVD、邮票、游戏……&#xff0c;由于 Koillection 旨在用于任何类型的收藏&#xff0c;它不支持自动下载元数…

计算机网络-计算机网络体系结构-概述,模型

目录 一、计算机网络概述 二、性能指标 速率 带宽 吞吐量 时延 往返时延RTT 利用率 三、计算机网络体系结构 分层结构 IOS模型 应用层-> 表示层-> 会话层-> 传输层-> 网络层-> 数据链路层-> 物理层-> TCP/IP模型 一、计算机网络概述 计…

用向量数据库Milvus Cloud搭建检索知识库机器人

检索知识库 Milvus 中已经存储了文本块向量,现在可以进行向量查询了。 以下函数创建了 1 个查询 pipeline。注意,这是本教程中最为关键的一个步骤! ops.ann_search.osschat_milvus(host=MILVUS_HOST, port=MILVUS_PORT, **{metric_type: IP, limit: 3, output_fields: [text…