当前位置: 首页 > news >正文

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

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

① 布局容器组件

        

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

        定义文件

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

// 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)会让方法在在下一轮“事件循环”开始时执行。从而避免与父容器冲突。

 

② 分页卡

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

相关文章:

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

讲讲两个经典布局组件的实现 ① 布局容器组件 配置面板是给用户配置布局容器背景颜色等属性。这里我们不需要关注 定义文件 规定了组件类的类型、标签、图标、默认布局属性、主文件等等。 // index.js import Container from ./container.vue; class ContainerControl extends…...

Qt模块、Qt开发应用程序类型、Qt未来主要市场、Qt6功能普及

Qt模块、Qt开发应用程序类型、Qt未来主要市场、Qt6功能普及 文章目录 1.Qt核心模块2.Qt的功能拓展3.Qt未来主要市场4.Qt6功能普及5.弃用的功能&#xff1a; Qt是一个跨平台的应用程序开发框架&#xff0c;提供了丰富的模块和工具来开发各种类型的应用程序。以下是Qt目前已有的…...

nodejs+vue高校校图书馆elementui

管理员输入书籍所在的书架位置&#xff0c;借阅提醒系统&#xff1a;可以查看个人借阅信息和图书到期提醒、挂失、检索、虚拟借书证不仅为群众提供了服务&#xff0c;而且也推广了自己&#xff0c;让更多的群众了解自己。 管理员页面&#xff1a; 第三章 系统分析 10 3.1需求分…...

CUDA C编程权威指南:1.2-CUDA基础知识点梳理

主要整理了N多年前&#xff08;2013年&#xff09;学习CUDA的时候开始总结的知识点&#xff0c;好长时间不写CUDA代码了&#xff0c;现在LLM推理需要重新学习CUDA编程&#xff0c;看来出来混迟早要还的。 1.闭扫描和开扫描   对于一个二元运算符 ⊕ \oplus ⊕和一个 n n n元…...

C语言—位运算符

目录 &&#xff08;位与&#xff0c;AND&#xff09;&#xff1a; |&#xff08;位或&#xff0c;OR&#xff09;&#xff1a; 位取反(~)&#xff1a; 左移(<<)&#xff1a; 右移(>>)&#xff1a; &&#xff08;位与&#xff0c;AND&#xff09;&…...

怎么才能实现一个链接自动识别安卓.apk苹果.ipa手机和win电脑wac电脑

您想要实现的功能是通过检测用户代理&#xff08;User Agent&#xff09;来识别访问设备类型并根据设备类型展示相应的页面。您可以根据以下步骤进行实现&#xff1a; 选择后端语言和框架&#xff0c;例如&#xff1a;Node.js、Express。 创建一个新的Express项目。 编写一个…...

zookeeper选举机制

全新集群选举 zookeeper 全新集群选举机制网上资料很多说法很模糊&#xff0c;仔细思考了一下&#xff0c;应该是这样 得到票数最多的机器>机器总数半数 具体启动过程中的哪个节点成为 leader 与 zoo.cfg 中配置的节点数有关&#xff0c;下面以3个举例 选举过程如下 server…...

vcpkg切换 Visual Studio 版本

vcpkg切换 Visual Studio 版本 在使用vcpkg作为项目的包管理工具时&#xff0c;可能会遇到需要切换Visual Studio版本的情况。下面是一种简单的方法来实现这个目标&#xff0c;通过修改triplet文件来指定使用的Visual Studio版本。 步骤1: 创建或修改Triplet文件 首先&#…...

运算符重载

#include <iostream> using namespace std; class Num { private:int num1; //实部int num2; //虚部 public:Num(){}; //无参构造Num(int n1,int n2):num1(n1),num2(n2){}; //有参构造~Num(){}; //析构函数const Num operator(const Num &other)const //加号重载{Nu…...

Llama2-Chinese项目:7-外延能力LangChain集成

本文介绍了Llama2模型集成LangChain框架的具体实现&#xff0c;这样可更方便地基于Llama2开发文档检索、问答机器人和智能体应用等。 1.调用Llama2类   针对LangChain[1]框架封装的Llama2 LLM类见examples/llama2_for_langchain.py&#xff0c;调用代码如下所示&#xff1a;…...

ES6中数组的扩展

1. 扩展运算符 用三个点(...)表示&#xff0c;它如同rest参数的逆运算&#xff0c;将数组转为用逗号分隔的参数序列。扩展就是将一个集合分成一个个的。 console.log(...[1, 2, 3]); // 1, 2, 3可以用于函数调用 扩展运算符后还可以放置表达式 ...(x > 0 ? [a] : [])如…...

计算机考研 | 2016年 | 计算机组成原理真题

文章目录 【计算机组成原理2016年真题44题-9分】【第一步&#xff1a;信息提取】【第二步&#xff1a;具体解答】 【计算机组成原理2016年真题45题-14分】【第一步&#xff1a;信息提取】【第二步&#xff1a;具体解答】 【计算机组成原理2016年真题44题-9分】 假定CPU主频为5…...

Web版Photoshop来了,用到了哪些前端技术?

经过 Adobe 工程师多年来的努力&#xff0c;并与 Chrome 等浏览器供应商密切合作&#xff0c;通过 WebAssembly Emscripten、Web Components Lit、Service Workers Workbox 和新的 Web API 的支持&#xff0c;终于在近期推出了 Web 版 Photoshop&#xff08;photoshop.adobe…...

FL Studio21.1.0水果中文官方网站

FL Studio 21.1.0官方中文版重磅发布纯正简体中文支持&#xff0c;更快捷的音频剪辑及素材管理器&#xff0c;多样主题随心换&#xff01;Mac版新增对苹果M2/1家族芯片原生支持。DAW界萌神&#xff01;极富二次元造型的水果娘FL chan通过FL插件Fruity Dance登场&#xff0c;为其…...

[BJDCTF2020]Mark loves cat

先用dirsearch扫一下&#xff0c;访问一下没有什么 需要设置线程 dirsearch -u http://8996e81f-a75c-4180-b0ad-226d97ba61b2.node4.buuoj.cn:81/ --timeout2 -t 1 -x 400,403,404,500,503,429使用githack python2 GitHack.py http://8996e81f-a75c-4180-b0ad-226d97ba61b2.…...

@SpringBootApplication注解的理解——如何排除自动装配 分布式情况下如何自动加载 nacos是怎么被发现的

前言 spring作为主流的 Java Web 开发的开源框架&#xff0c;是Java 世界最为成功的框架&#xff0c;持续不断深入认识spring框架是Java程序员不变的追求。 本篇博客介绍SpringBootApplicant注解的自动加载相关内容 其他相关的Spring博客文章列表如下&#xff1a; Spring基…...

HTTP的前世今生

史前时期 20 世纪 60 年代&#xff0c;美国国防部高等研究计划署&#xff08;ARPA&#xff09;建立了 ARPA 网&#xff0c;它有四个分布在各地的节点&#xff0c;被认为是如今互联网的“始祖”。 然后在 70 年代&#xff0c;基于对 ARPA 网的实践和思考&#xff0c;研究人员发…...

软件测试教程 自动化测试selenium篇(二)

掌握Selenium常用的API的使用 目录 一、webdriver API 1.1元素的定位 1.2 id定位 1.3name 定位 1.4tag name 定位和class name 定位 1.5CSS 定位 1.6XPath 定位 1.7link text定位 1.8Partial link text 定位 二、操作测试对象 2.1鼠标点击与键盘输入 2.2submit 提交…...

JavaSE入门--初始Java

文章目录 Java语言概述认识Java的main函数main函数示例运行Java程序认识注释认识标识符认识关键字 前言&#xff1a; 我从今天开始步入Java的学习&#xff0c;希望自己的博客可以带动小白学习&#xff0c;也能获得大佬的指点&#xff0c;日后能互相学习进步&#xff0c;都能如尝…...

leetcode做题笔记160. 相交链表

给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...