使用 Next.js、 Prisma 和 PostgreSQL 全栈开发视频网站

news2024/11/24 20:40:24

通过上一篇文章,我们对乔巴乐高海报平台的整体架构有了初步的了解。今天我们深入到编辑器部分,对其中的难点和实现细节进行分析。

这是目前生产的编辑器页面:

不难看出和市面上大部分低代码平台一样,由三部分组成:左侧组件列表、中间画布区域、右侧属性区域。

大致操作流程就是拖动左侧的组件到中间的画布,选中组件,右侧属性面板就会展示与该组件关联的属性。编辑右侧属性,画布中对应的组件样式就会同步更新。页面拼接完成。

从中看出组件串联其中,在前面一篇文章中,我们大致分析了整体页面和组件的数据结构,但没有细化。抽取一下文字、图片、素材组件的通用特性:

  • 尺寸属性(Size)* 宽度(width)* 高度(height)
  • 填充属性(Padding)* 上填充(padding-top)* 右填充(padding-right)* 下填充(padding-bottom)* 左填充(padding-left)
  • 视觉格式属性* 指定如何定位元素(position)* 指定所定位元素的上边缘的位置(top)* 指定所定位元素的右边缘的位置(right)* 指定所定位元素底边的位置(bottom)* 指定定位元素左边缘的位置(left)* 将一个或多个阴影应用于元素的框(box-shadow)
  • 颜色属性(Color)* 透明度(opacity)
  • 边框属性(Border)* 设置元素所有四个侧面的边框颜色(border-color)* 设置元素所有四个侧面的边框宽度(border-width)* 在元素的所有四个面上设置边框的样式(border-style)* 定义元素边界角的形状(border-radius)

除此之外,文字组件还具有以下属性:

  • 字体属性(Fonts)* 定义元素的字体列表(font-family)* 定义文本的字体大小(font-size)* 定义文本的字体样式(font-style)* 指定文本的字体粗细(font-weight)
  • 文字属性(Text)* 设置内联内容的水平对齐方式(text-align)* 指定添加到文本的装饰(text-decoration)* 设置文本行之间的高度(line-height)

图片组件还具有:

  • 图片属性(Image)* 图片链接(src)

素材组件还具有:

  • 背景属性(Background)* 定义元素的背景色(background-color)

我们将上面的操作流程拆解为三步:

  • 1⃣️ 拖动左侧的组件到中间的画布
  • 2⃣️ 选中组件,右侧属性面板就会展示与该组件关联的属性
  • 3⃣️ 编辑右侧属性,画布中对应的组件样式就会同步更新

添加组件到画布

通过上一篇文章,我们知道编辑器整体的数据结构是这么设计的:

state:{ 
// 所有添加到画布中的组件数据 
componentData:[], 
} 

reducers:{ 
// 添加组件到componentData 
addComponentData(){}, 
// 编辑组件,更新componentData及curComponent 
editComponentData(){}, 
// 删除组件 
delComponentData(){} 
} 

那么从左侧组件列表添加组件到画布的操作其实就是向componentDatapush一条组件数据。

这里主要是关注下组件列表要怎么设计:为了便于用户快速创建活动,组件列表最好是预设一些模板,其实就是针对文字、图片和素材分别提供一些已有的元素。这样当对应组件点击添加到画布时,对应就会commit一个mutation来修改store中的componentData

这里组件列表底层渲染也是用的组件库,只是不同模板的props不同。

选中组件展示其关联属性

当在画布中选中具体组件时,我们需要知道此刻是哪个组件被选中了,意味着需要一个变量来存储当前高亮的组件。那么在store中添加setActivegetCurrentElement

const editorModule = {state: {componentData: [],currentElement: "",},mutations: { addComponentData(state, component) { }, setActive(state, id) {state.currentElement = id; },},getters: {getCurrentElement: (state) => {return state.componentData.find((component) => component.id === state.currentElement);},}
} 

当在画布中选中组件时,就会触发setActive,更新currentElement。(通过getCurrentElement可以获取到当前正在被操作的组件)。

这个时候,怎么在右侧属性区域动态展示不同组件的不同属性呢?

对于单独的组件来说,属性面板应该是语义化的,无论是开发还是非开发同学,通过属性面板的操作区,就可以直观的知道一个组件的属性是什么,应该如何使用和编辑。

那么属性面板应该包含哪些内容呢?

1、label:属性名称。这个可以显式的告诉具体的属性的作用,比如元素的宽高、边框、背景颜色等。

2、description:属性的描述信息。对于一些特殊属性,可能第一下通过label并不能直观的识别属性的含义,添加描述信息可以进行详细的阐述。

3、content:属性渲染器。用户可以基于此实现对属性的修改。最常见的有 textarea、input、select 等。

4、error:属性校验信息。当用户输入了不合法的或者类型不匹配时,可给予适当的错误提示信息。

通过以上描述,我们会发现,这其实就是我们常用的表单。

对应上面组件的props信息,我们可以对这些属性做一些归类,那归类的标准又是什么呢?我认为应该把属性与js中的数据类型做一下映射,然后在具体的分类下选用合适的渲染器。

我们知道在JavaScript中,一共有七种数据类型,字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol和对象(Object)。其中对象类型包括:数组(Array)、函数(Function)、还有两个特殊的对象:正则(RegExp)和日期(Date)。

这里面的空(Null)、未定义(Undefined)、Symbol和正则(RegExp)在渲染器中基本用不到。

我们先来看一下字符串(String)、数字(Number)、布尔(Boolean)和日期(Date)可能渲染的方式:

字符串(String

渲染器类型组件
input
textarea

数字(Number)

渲染器类型组件
input-number
slider

布尔(Boolean)

渲染器类型组件
switch

日期(Date

渲染器类型组件
date

除了这几种,还有对象(Object)、数组(Array)、函数(Function)。

对象和数组属于较复杂的类型,不过我们可以把它抽象为多层级(可以理解为嵌套)的基础数据类型:

渲染器类型组件
array

像数组一般是用下拉框的形式来展现。

至于函数(Function),可以采用预定义的形式:

渲染器类型组件
function

到这里,不难想到,我们要维护一个属性和表单组件的对应关系。属性对应上面的key,像borderColortextwidthfontFamily这些,那组件呢?组件其实就是对属性的具体呈现,像width可以用数字输入框、text可以用普通输入框,但是对于一些比较复杂的特性,我们自己去实现这些组件,就显得捉襟见肘了,这个时候我们就可以考虑和现有的组件库做一下结合了(这里我采用的是Ant Design Vue)。

那么这样,属性propcomponent基础的对应关系就有了:

const mapPropsToComponents = {text: { component: "a-input",},width: { component: "a-input-number",},borderWidth: { component: "a-slider",},// ...
} 

但这只是满足了常规的基础组件设计,像一些独有的属性或者基础组件不能满足的情况,我们需要对其做一定扩展:

渲染器类型组件
upload
color-picker

上面提到的上传组件和颜色选择组件是需要我们单独去实现的。

编辑属性,画布同步更新

上面只是初步建立了属性和组件的对应关系,组件初始值的展示、复杂组件的展示以及表单值更新后,画布如何同步更新,这些问题我们还都没有解决。

其实把问题简化,这就是表单的回显和更新问题。

以我以往的经验来看:表单组件在设计时,有两点是必须的:

  • 表单初始值(默认value),供初始展示使用
  • 表单属性更改的事件(默认为 change

对于不同的表单,初始值和属性更改后,参数的处理是不一样的:

  • 像高度、宽度这种数字类型的,传入表单时应保证是number(24)类型,属性更改后,事件参数应该是string(24px)类型的
  • 字体加粗与否、倾斜与否、加下划线与否,传入表单时应保证是boolean(true/false)类型,属性更改后,事件参数应该是string(bold/normal)类型的

所以给每一个属性在传入表单和事件更改后都要加一个额外的转化函数去处理值:

  • initialValueConvert
  • eventChangeValueConvert

还有对属性进行赋值时,不是所有的表单控件接收的都是value,像checkbox就是checked,这种单独抽一个属性valueProp去控制即可。

其次,像上面提到的复杂组件(我们这里是指父子层级)的渲染,除了component还要多加一个subComponent

完善后,属性propcomponent的对应关系为:

const mapPropsToComponents = {text: {component: "textarea-fix",eventName: "change",valueProp: "value",initialValueConvert: (v: any) => v,eventChangeValueConvert: (e: any) => e.target.value,text: "文本",},width: {component: "a-input-number",eventName: "change",valueProp: "value",initialValueConvert: (v: string) => (v ? parseInt(v) : ""),eventChangeValueConvert: (e: number) => (e ? `${e}px` : ""),text: "宽度",},textDecoration: {component: "icon-switch",eventName: "change",initialValueConvert: (v: string) => v === "underline",eventChangeValueConvert: (e: boolean) => (e ? "underline" : "none"),valueProp: "checked",},backgroundSize: {component: "a-select",eventName: "change",initialValueConvert: (v: any) => v,eventChangeValueConvert: (e: any) => e,subComponent: "a-select-option",text: "背景大小",options: [{ value: "contain", text: "自动缩放" },{ value: "cover", text: "自动填充" },{ value: "", text: "默认" },], }, // ...
} 

我们的数据始终保持自上而下的顺序,也就是说表单更新最终要反射回到总体的 store 当中去。这个时候我们在对应的组件当中发射出一个事件(change),当 change 发生的时候,我们能够知道是哪个元素的哪个属性,以及新的值是什么,我们就用这些信息更新这个值,这样 store完成更新,元素的 props 发生更新,那么整个数据流动就完成了。

画布区域交互设计实现

上面说了这么多,基本都是围绕左侧组件区域、中间画布区域、右侧属性区域相互之间的数据流动来讲的。最后来说一下画布区域本身一些比较复杂的交互实现。

我大概整理了这几种:

1.拖拽(组件在画布中移动)
2.组件图层
3.放大/缩小
4.撤销/重做

拖拽(组件在画布中移动)

这个相对比较简单,就是mousedownmousemovemouseup事件的结合使用:在组件上按下鼠标后,记录组件当前位置,也就是x、y坐标(对应的是css中的left和top);每次鼠标移动时用当前最新的xy坐标减去最开始的xy坐标,计算出移动的距离,然后更新组件位置;鼠标抬起时结束移动。

组件图层

图层面板主要是控制组件的显示/隐藏、不同组件的层级关系以及点击选中。

这里主要说一下层级关系吧,正常情况下,我们会选择使用z-index来控制层级。但是这里我没有使用z-index,而是利用了层叠领域黄金准则的第二条。

层叠领域黄金准则:1、谁大谁上: 当具有明显的层叠水平标示的时候,如识别的z-indx值,在同一个层叠上下文领域,层叠水平值大的那一个覆盖小的那一个。2、后来居上: 当元素的层叠水平一致、层叠顺序相同的时候,在DOM流中处于后面的元素会覆盖前面的元素。

为什么选择第二个而没有选择最常见的第一条呢?首先,我们需要一个图层列表可以对每个组件对应的图层进行排序,其实就是对store中的components进行排序,也就是数组排序了,那么在图层列表中,如果你想增加某一图层的层级,把它放置到后面就可以了(这样渲染时,数组后面的元素就会处在DOM流的后面了。对应的层叠顺序也就居上了),这样不仅操作方便,也不用增加额外冗余代码,可谓一举两得

放大/缩小

核心实现:在画布组件的四个角(↖️、↗️、↙️、↘️)分别加一个小圆点:

1.左上:组件left、top均减小;width、height均变大
2.右上:组件left不变、top减小;width、height均变大
3.左下:组件left减小、top不变;width、height均变大
4.右下:组件left、top均不变;width、height均变大

撤销/重做

撤销、重做其实是我们平时一直在用的操作。对应快捷键一般就是⌘ Z / Ctrl+Z⌘⇧ Z / Ctrl+Shift+Z。这个功能是很常见的,他可以极大的提升用户体验,提高编辑效率,但是用代码应该如何实现呢?

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

10名IB学生获得满分,新加坡环球印度国际学校成为一匹黑马

近年来留学人数大幅度上升,其中新加坡因多元的文化环境、健全的教育制度,深受学生、家长青睐喜爱,新加坡国际学校也因此成为了香饽饽。 在每年的IB成绩放榜中,新加坡国际学校IB表现都非常亮眼。 除了我们熟悉的华中、德威、UWC等一…

宏记录器 Macro Recorder 2.0 注册版

记录鼠标和键盘操作以无限重播...... 不再执行相同的任务两次! Macro Recorder 像录音机一样捕捉鼠标事件和击键,让您在计算机上自动执行繁琐的程序。 按记录。执行操作。 Macro Recorder 记录您的鼠标移动、鼠标点击和键盘输入。就像电脑的录音机一样。…

RabbitMQ第四个实操小案例——DirectExchange

文章目录RabbitMQ第四个实操小案例——DirectExchangeRabbitMQ第四个实操小案例——DirectExchange DirectExchange:这种交换机的模式跟前面的Fouout(广播)不太一样,DirectExchange 会将接收到的消息根据规则路由到指定的Queue&a…

(免费分享)基于springboot,vue毕业设计管理系统

源码获取:关注文末gongzhonghao,输入011领取下载链接 开发工具IDEA ,数据库mysql5.7 ,前后端分离 系统分学生、教师、管理员三个角色 1.学生截图 2.教师截图: 3.管理员截图: package com.cx.controller;import cn.…

C++实现哈希表。

目录 一. unordered_map unordered_set 和 map set的区别 二. 哈希 1. 哈希,哈希表,哈希函数。 2. 哈希冲突。 3. 哈希函数补充 3. 解决哈希冲突的两大方法:闭散列,开散列 闭散列 闭散列实现代码: 闭散列的删除…

MDF和DHF、DMR、DHR三者差异?注册与备案文件?

医疗器械研发、工艺、注册、质量人员时常会接触到DHF, DMR, DHR。这几个单词缩写不光是拼写非常相似,其含义也有许多相同之处,所以十分容易混淆。本文就来聊一聊这三者的区别和联系。 ISO13485:2016 证明符合法规要求 MDF-Medical Device File&#xf…

计算机毕业设计——税务缴纳信息管理

一.项目介绍 本项目包含管理员、普通用户两种角色 普通用户 可以浏览 首页、新闻信息、法律法规、税务政策、通知公告、留言板、个人中心、后台管理、在线咨询 普通用户进入后台管理可以修改密码、个人信息以及税务申报、填写留言 管理员用户登录可以操作 个人中心、用…

服务器证书是网络信息安全的基础

我们都清楚,互联网安全存在两大基本隐患,一是因为身份认证机制的缺位,使网络成为各种“钓鱼”诈骗行为的温床,导致互联网空间缺乏信任。互联网每时每刻都在传输数以亿万的信息,这些信息如果未经加密,就有可…

自动化测试如何区分用例集合及编写规范

目录 前言 业务量和复杂度增长现状是什么? 如何区分自动化测试的用例集合? 区分用例集合的过程要注意什么? 自动化测试用例选择 自动化测试用例编写 用例编写规范 结语 前言 前面的文章介绍过如何设计自动化测试case,有同…

一、翻越前端的三座大山——继承/原型链

文章目录原型专题前言什么是原型?实例和原型的关系什么是原型链?Object 和 Function 的继承关系原型运用例子:手写instanceof手写call,apply手写new六大继承方式原型链继承构造函数继承原型链 构造函数优化(原型链 构…

mysql 中的锁

文章目录锁的分类锁粒度InnoDB 中的表锁InnoDB 中的行锁锁的分类 共享锁(Shared Locks) // 行共享锁 // 获取到当前记录的S锁(共享锁),兼容其他事务的S锁,不兼容X锁(排他锁) select…

服务注册中心

什么是注册中心? 注册中心主要有三种角色: ● 服务提供者(Provider):在启动时,向 Registry 注册自身服务,并向 Registry 定期发送心跳汇报存活状态。 ● 服务消费者(Consumer&#…

HCSC: Hierarchical Contrastive Selective Coding

原型对比学习:图像表征与聚类中心之间的交互,可以简单总结为在表征空间中最大化图像特征与其所属的聚类中心的相似度。分层语义结构 自然存在于图像数据集中,其中几个语义相关的图像簇可以进一步集成到一个更大的簇中,具有更粗粒度…

Tomcat多实例部署

文章目录一、Tomcat多实例的操作步骤1、关闭防火墙,将安装 Tomcat 所需软件包传到/opt目录下2、安装JDK3、安装 tomcat4、配置 tomcat 环境变量5、修改 tomcat2 中的 server.xml 文件,要求各 tomcat 实例配置不能有重复的端口号6、修改各 tomcat 实例中的…

openpnp软件的使用 - 配置自动电动飞达

文章目录openpnp软件的使用 - 配置自动电动飞达概述笔记新建执行器(电动飞达类型)新建电动飞达的料站配置飞达的x,y位置配置飞达移动到料表面时的高度测试这个Z高度, 是否能让吸嘴取得元件?设置元件料封装使用的吸嘴.试试开始贴片贴片后的元件位置目测备注ENDopenpnp软件的使用…

实操小微风控报告中的地址信息的清洗与照面和司法数据使用

在中小微企业的大数据风控体系中,工商数据与司法数据是最基本也是最常见的两类信息维度,在企业大数据体系的应用场景中扮演着重要角色。由于企业工商与司法数据的多部分内容属于社会公开化信息,因此在行业市场内也是非常容易获取的&#xff0…

详解:看似遥不可及的元宇宙

导语:元宇宙是人们娱乐、生活乃至工作的虚拟时空。Roblox 这款游戏,展示了元宇宙的诸多特征。核心是数字创造、数字资产、数字交易、数字货币和数字消费,尤其是在用户体验方面,达到了真假难辨、虚实混同的境界。 大约再过15 年,互联网就可能会发生一次重大的变革。正如从…

技术 | 终端安全 | 服务器并不像您想象的那么安全

在从1到10的评分中,现状方法对服务器安全的有效性如何? 从理论上讲,应该是10分。保护服务器免受外界影响的途径(分段、防火墙、漏洞修补、安全解决方案等)是众所周知的。 然而,现实生活的结果显示出与理论的巨大差距。从红十字会…

【前端】Ajax-form表单与模板引擎

目录 一、form表单的基本使用 1.1什么是表单 1.2表单的组成部分 1.3form标签属性 1.4表单的同步提交及缺点 1.4.1什么是表单的同步提交 1.4.2表单同步提交的缺点 1.4.3如何解决表单同步提交的缺点 二、通过Ajax提交表单数据 2.1监听表单提交事件 2.2阻止表单默认提交…

(超级详细1秒钟秒懂)华为网络初级工程师知识总结(一)

文章目录一,人机交互的工作模式二,OSI参考模型---OSI/RM三,常见的网络协议端口号四,网络层的地址查询,转发。五,ARP协议的转发原理六,TCP/IP协议的封装和解封装及跨层封装一,人机交互…