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

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

① 布局容器组件

        

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

        定义文件

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

// 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-containerv-bind="fieldProps"@inChildComponent="$emit('inChildComponent', $event)"@add="handleAdd"@delete="handleDelete"@drop="syncDataToStore('add', $event)"><drag-container-layoutv-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"><!-- 运行时 --><componentv-if="isRuntime":is="Item":key="layoutItem.i"v-bind="getComponentProps(layoutItem)"/><!-- 设计时 --><grid-itemv-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');// 添加到layoutthis.$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-layoutv-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,yconst 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.mzph.cn/news/94458.shtml

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

相关文章

ARMv8如何读取cache line中MESI 状态以及Tag信息(tag RAM dirty RAM)并以Cortex-A55示例

Cortex-A55 MESI 状态获取 一&#xff0c;系统寄存器以及读写指令二&#xff0c;Cortex-A55 Data cache的MESI信息获取&#xff08;AARCH 64&#xff09;2.1 将Set/way信息写入Data Cache Tag Read Operation Register2.2 读取Data Register 1和Data Register 0数据并解码 参考…

Linux嵌入式学习之Ubuntu入门(六)shell脚本详解

系列文章内容 Linux嵌入式学习之Ubuntu入门&#xff08;一&#xff09;基本命令、软件安装、文件结构、编辑器介绍 Linux嵌入式学习之Ubuntu入门&#xff08;二&#xff09;磁盘文件介绍及分区、格式化等 Linux嵌入式学习之Ubuntu入门&#xff08;三&#xff09;用户、用户组…

Java 基于 SpringBoot 的学生考勤系统

1 简介 本文讲解的是 Java基于 SpringBoot 的学生考勤系统。学生考勤管理系统能做到的不仅是大大简化管理员的信息管理工作&#xff0c;在提高学生考勤管理效率的同时还能缩减开支&#xff0c;更能在数字化的平面网络上将学生考勤管理最好的一面展示给客户和潜在客户&#xff…

swift加载h5页面空白

swift加载h5页面空白 problem 背景 xcode swift 项目&#xff0c;WebView方式加载h5页面本地h5地址是&#xff1a;http://localhost:5173/ 浏览器打开正常 Swift 加载h5&#xff1a; 百度官网 加载正常本地h5页面 加载空白&#xff0c;没有报错 override func viewDidLoad…

Netron【.pt转.torchscript模型展示】

Netron是一个模型的展示工具&#xff0c;它有网页版和app版&#xff1a; 网页版&#xff1a;Netron app版&#xff1a;GitHub - lutzroeder/netron: Visualizer for neural network, deep learning, and machine learning models 直接用网页版吧&#xff0c;还不用安装。 它可…

安装NodeJS并使用yarn下载前端依赖

文章目录 1、安装NodeJS1.1 下载NodeJS安装包1.2 解压并配置NodeJS1.3 验证是否安装成功2、使用yarn下载前端依赖2.1 安装yarn2.2 使用yarn下载前端依赖参考目标:在Windows下安装新版NodeJS,并使用yarn下载前端依赖,实现运行前端项目。 1、安装NodeJS 1.1 下载NodeJS安装包…

带你10分钟学会红黑树

前言&#xff1a; 我们都知道二叉搜索树&#xff0c;是一种不错的用于搜索的数据结构&#xff0c;如果二叉搜索树越接近完全二叉树&#xff0c;那么它的效率就会也高&#xff0c;但是它也存在的致命的缺陷&#xff0c;在最坏的情况下&#xff0c;二叉搜索树会退化成为单链表&am…

字典与数组第七讲:工作表数据计算时为什么要采用数组公式(一)

《VBA数组与字典方案》教程&#xff08;10144533&#xff09;是我推出的第三套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;字典是VBA的精华&#xff0c;我要求学员必学。7.1.3.9教程和手册掌握后&#xff0c;可以解决大多数工作中遇到的实际问题。…

谷歌地球引擎GEE账户注册的快速、百分百成功方法

本文介绍免费注册谷歌地球引擎&#xff08;Google Earth Engine&#xff0c;GEE&#xff09;账户的方便、快捷的最新方法&#xff1b;基于这一方法&#xff0c;只要我们创建一个谷歌Cloud Project&#xff0c;就可以直接访问GEE。 GEE在原本&#xff08;大概前几年的时候&#…

Redis-缓存穿透,缓存击穿,缓存雪崩

缓存穿透&#xff0c;缓存击穿&#xff0c;缓存雪崩 缓存穿透处理方案解决方案1 缓存空数据解决方案2 布隆过滤器 缓存击穿处理方案解决方案 1 互斥锁解决方案2 逻辑过期 缓存雪崩处理方案解决方案 1 给不同的key的过期时间设置添加一个随机值&#xff0c;降低同一个时段大量ke…

处理机调度的概念,层次联系以及七状态模型

1.基本概念 当有一堆任务要处理&#xff0c;但由于资源有限&#xff0c;这些事情没法同时处理。 这就需要确定某种规则来决定处理这些任务的顺序&#xff0c;这就是“调度”研究的问题。 2. 三个层次 1.高级调度&#xff08;作业调度&#xff09; 高级调度&#xff08;作业…

【10】c++设计模式——>依赖倒转原则

关于依赖倒转原则&#xff0c;对应的是两条非常抽象的描述&#xff1a; 1.高层模块不应该依赖低层模块&#xff0c;两个都应该依赖抽象。 2.抽象不应该依赖细节&#xff0c;细节应该依赖抽象。 先用人话解释一下这两句话中的一些抽象概念&#xff1a; 1.高层模块&#xff1a;可…

ROS(5)PX4仿真安装及运行

1、配置&#xff0c;提升下载速度 启动 $ cd clash-for-linux$ sudo bash start.sh$ source /etc/profile.d/clash.sh$ proxy_on 关闭 $ cd clash-for-linux$ sudo bash shutdown.sh$ proxy_off 2、安装PX4开源无人机 git clone https://github.com/PX4/PX4-Autopilot.git…

【软考】系统集成项目管理工程师(六)项目整体管理【6分】

一、 前言 1、项目管理三从四得 2、ITO共性总结 1、上一个过程的输出大部分是下-个过程的输入 2、计划和文件是不一样的 (每个输入都有计划和文件) 3、被批准的变更请求约等于计划 4、在执行和监控过程产生新的变更请求(变更请求包括变什么和怎么变&#xff0c;这是变更请求和…

Spring三大核心组件

Spring架构图 Spring三大核心组件分别为&#xff1a;Core、Beans和Context 1. Core&#xff08;核心&#xff09;&#xff1a; 思想&#xff1a;Core组件的核心思想是控制反转&#xff08;IoC&#xff09;和依赖注入&#xff08;DI&#xff09;。它将对象的创建、组装和管理的…

Junit的常用操作

注:本篇文章讲解的是junit5 目录 Juint是什么 Juint需要导入的依赖 Juint常用注解 Junit执行顺序 参数化 断言 测试套件 Juint是什么 Juint 是 Java 的一个单元测试框架. 也是回归测试框架. 使用 Junit 能让我们快速的完成单元测试。 注意&#xff1a;Junit 测试也是程序…

调用gethostbyname实现域名解析(附源码)

VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&a…

SystemUI导航栏

SystemUI导航栏 1、系统中参数项1.1 相关开关属性2.2 属性设置代码 2、设置中设置“三按钮”导航更新流程2.1 属性资源覆盖叠加2.2 SystemUI导航栏接收改变广播2.3 SystemUI导航栏布局更新2.4 时序图 android13-release 1、系统中参数项 1.1 相关开关属性 设置->系统->…

测试用例的编写(面试常问)

作者&#xff1a;爱塔居 专栏&#xff1a;软件测试 作者简介&#xff1a;不断总结&#xff0c;才能变得更好~踩过的坑&#xff0c;不能再踩~ 文章简介&#xff1a;常见的几个测试用例。 一、淘宝购物车 二、登录页面 三、三角形测试用例 abc结果346普通三角形333等边三角形334…

安装matplotlib_

安装pip 安装matplotlib 安装完毕 导入出现bug......