讲讲项目里的仪表盘编辑器(四)分页卡和布局容器组件
讲讲两个经典布局组件的实现
① 布局容器组件

配置面板是给用户配置布局容器背景颜色等属性。这里我们不需要关注
定义文件
规定了组件类的类型、标签、图标、默认布局属性、主文件等等。
// 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.弃用的功能: Qt是一个跨平台的应用程序开发框架,提供了丰富的模块和工具来开发各种类型的应用程序。以下是Qt目前已有的…...
nodejs+vue高校校图书馆elementui
管理员输入书籍所在的书架位置,借阅提醒系统:可以查看个人借阅信息和图书到期提醒、挂失、检索、虚拟借书证不仅为群众提供了服务,而且也推广了自己,让更多的群众了解自己。 管理员页面: 第三章 系统分析 10 3.1需求分…...
CUDA C编程权威指南:1.2-CUDA基础知识点梳理
主要整理了N多年前(2013年)学习CUDA的时候开始总结的知识点,好长时间不写CUDA代码了,现在LLM推理需要重新学习CUDA编程,看来出来混迟早要还的。 1.闭扫描和开扫描 对于一个二元运算符 ⊕ \oplus ⊕和一个 n n n元…...
C语言—位运算符
目录 &(位与,AND): |(位或,OR): 位取反(~): 左移(<<): 右移(>>): &(位与,AND)&…...
怎么才能实现一个链接自动识别安卓.apk苹果.ipa手机和win电脑wac电脑
您想要实现的功能是通过检测用户代理(User Agent)来识别访问设备类型并根据设备类型展示相应的页面。您可以根据以下步骤进行实现: 选择后端语言和框架,例如:Node.js、Express。 创建一个新的Express项目。 编写一个…...
zookeeper选举机制
全新集群选举 zookeeper 全新集群选举机制网上资料很多说法很模糊,仔细思考了一下,应该是这样 得到票数最多的机器>机器总数半数 具体启动过程中的哪个节点成为 leader 与 zoo.cfg 中配置的节点数有关,下面以3个举例 选举过程如下 server…...
vcpkg切换 Visual Studio 版本
vcpkg切换 Visual Studio 版本 在使用vcpkg作为项目的包管理工具时,可能会遇到需要切换Visual Studio版本的情况。下面是一种简单的方法来实现这个目标,通过修改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框架的具体实现,这样可更方便地基于Llama2开发文档检索、问答机器人和智能体应用等。 1.调用Llama2类 针对LangChain[1]框架封装的Llama2 LLM类见examples/llama2_for_langchain.py,调用代码如下所示:…...
ES6中数组的扩展
1. 扩展运算符 用三个点(...)表示,它如同rest参数的逆运算,将数组转为用逗号分隔的参数序列。扩展就是将一个集合分成一个个的。 console.log(...[1, 2, 3]); // 1, 2, 3可以用于函数调用 扩展运算符后还可以放置表达式 ...(x > 0 ? [a] : [])如…...
计算机考研 | 2016年 | 计算机组成原理真题
文章目录 【计算机组成原理2016年真题44题-9分】【第一步:信息提取】【第二步:具体解答】 【计算机组成原理2016年真题45题-14分】【第一步:信息提取】【第二步:具体解答】 【计算机组成原理2016年真题44题-9分】 假定CPU主频为5…...
Web版Photoshop来了,用到了哪些前端技术?
经过 Adobe 工程师多年来的努力,并与 Chrome 等浏览器供应商密切合作,通过 WebAssembly Emscripten、Web Components Lit、Service Workers Workbox 和新的 Web API 的支持,终于在近期推出了 Web 版 Photoshop(photoshop.adobe…...
FL Studio21.1.0水果中文官方网站
FL Studio 21.1.0官方中文版重磅发布纯正简体中文支持,更快捷的音频剪辑及素材管理器,多样主题随心换!Mac版新增对苹果M2/1家族芯片原生支持。DAW界萌神!极富二次元造型的水果娘FL chan通过FL插件Fruity Dance登场,为其…...
[BJDCTF2020]Mark loves cat
先用dirsearch扫一下,访问一下没有什么 需要设置线程 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 开发的开源框架,是Java 世界最为成功的框架,持续不断深入认识spring框架是Java程序员不变的追求。 本篇博客介绍SpringBootApplicant注解的自动加载相关内容 其他相关的Spring博客文章列表如下: Spring基…...
HTTP的前世今生
史前时期 20 世纪 60 年代,美国国防部高等研究计划署(ARPA)建立了 ARPA 网,它有四个分布在各地的节点,被认为是如今互联网的“始祖”。 然后在 70 年代,基于对 ARPA 网的实践和思考,研究人员发…...
软件测试教程 自动化测试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程序认识注释认识标识符认识关键字 前言: 我从今天开始步入Java的学习,希望自己的博客可以带动小白学习,也能获得大佬的指点,日后能互相学习进步,都能如尝…...
leetcode做题笔记160. 相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中不存在环。 注意,函数返回结果后&…...
从GUI到Tcl命令:Vivado Report Timing Summary配置选项的完整对照手册(附常用命令模板)
Vivado时序报告GUI与Tcl命令深度对照手册:打造自动化分析工作流 在FPGA设计流程中,时序分析是确保设计满足性能要求的关键环节。Vivado IDE提供了直观的GUI界面用于配置时序报告,但对于追求高效自动化的工程师而言,掌握底层Tcl命令…...
从“变速齿轮”到“创新引擎”:解码阿里“大中台、小前台”战略的演进与实战
1. 中台战略的起源与本质 第一次听说"大中台、小前台"这个概念时,我正坐在杭州一家咖啡馆里和几位阿里P8的技术专家聊天。他们用了一个特别形象的比喻:"现在的互联网公司就像一辆老式自行车,前台是拼命蹬车的双腿,…...
企业邮箱安全必看:SPF、DKIM、DMARC 三件套配置实战(附常见错误排查)
企业邮箱安全必看:SPF、DKIM、DMARC 三件套配置实战(附常见错误排查) 当一封伪造CEO签名的钓鱼邮件成功进入财务部门邮箱时,企业面临的不仅是数据泄露风险——根据Verizon《2023年数据泄露调查报告》,83%的商务邮件入侵…...
专利数据挖掘与商业价值转化:开源工具驱动的技术创新与决策变革
专利数据挖掘与商业价值转化:开源工具驱动的技术创新与决策变革 【免费下载链接】patents-public-data Patent analysis using the Google Patents Public Datasets on BigQuery 项目地址: https://gitcode.com/gh_mirrors/pa/patents-public-data 在数字化转…...
避坑指南:HuggingFace本地数据集加载常见的5个报错及解决方法
HuggingFace本地数据集加载实战:5类典型报错深度解析与解决方案 当你第一次尝试将本地数据集加载到HuggingFace生态系统中时,可能会遇到各种令人困惑的错误信息。这些报错往往隐藏着数据格式、特征定义或路径处理等关键问题。本文将剖析开发者最常遇到的…...
终极PrimeVue Toast组件交互事件回调指南:从基础到高级应用
终极PrimeVue Toast组件交互事件回调指南:从基础到高级应用 【免费下载链接】primevue Next Generation Vue UI Component Library 项目地址: https://gitcode.com/GitHub_Trending/pr/primevue PrimeVue是一款功能强大的Vue UI组件库,其中Toast组…...
【CDA干货】三个部门三个营收数:1200 万、1150 万、1280 万?企业指标口径不一致,三步破局
财务部报的Q3营收是1200万,运营部那边却是1150万,更离谱的是CEO给投资人看的PPT上写着1280万。这种事儿听起来是不是很离谱?但实际上,数据对不上,这事儿太常见了。表面看是数字打架,实际上是人跟人较劲——…...
进阶篇第5节:共享内存(三)——实战:优化矩阵乘法(Tiling技术)
第二篇进阶篇第5节:共享内存(三)——实战:优化矩阵乘法(Tiling技术) 从朴素到分块,从分块到极致——矩阵乘法的优化之路,就是CUDA性能优化的缩影 写在前面 矩阵乘法是CUDA优化中最经典的案例,没有之一。在筑基篇,我们实现了朴素版本和基础分块版本,性能从 252 GFLO…...
SEO_2024年最新SEO策略与趋势深度解析(352 )
<h2>2024年最新SEO策略与趋势深度解析</h2> <p>在数字化时代,搜索引擎优化(SEO)依然是网站流量和品牌影响力的核心驱动力。2024年,随着互联网技术的不断进步,SEO策略和趋势也在不断演变。本文将详细…...
2026论文降重神器盘点!毕业论文“AIGC痕迹”怎么破?
【CSDN技术引言:拒绝“开源背调”式的学术翻车】 哈喽各位同行和科研圈的战友们。最近后台私信快炸了,今年这届硕博生仿佛遭遇了“灭顶之灾”。某985高校前天出炉的抽检结果直接把大家看傻了:明明自己逐字逐句手敲的论文,知网查重…...
