一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

十八、编辑器组件图层面板功能开发

1、前言
* 引言
    - 欢迎大家回到B端项目的开发,这周让我们着眼于编辑器右侧设置部分的编码工作,完成组件属性设
      置,图层设置,以及页面设置的一系列功能。为了不让大家陷入业务的泥潭中,会挑选典型的几个
      业务和知识点进行讲述和开发。
* 将收获什么
    - 裁剪图片的实现
        ~ 借助阿里云OSS的图片处理
        ~ 重新获取裁剪的图片数据并且重新上传
    - 创建Vue3钩子函数的原则
    - 拖动排序的实现原理
    - 复杂正则表达式的分析过程
* 主要内容
    - 使用Cropper.js完成图片裁剪功能
    - 创建LayerList组件以及完成InlineEdit组件的编码
    - 自研一个简单的列表拖动方案,了解原理后,然后使用Vue Draggble Next进行替换
    - 完成EditGroup的编码,了解使用伪代码分析问题的过程
    - 完成BackgroundProcesser组件,学习正则表达式的分析过程
* 学习方案
    - 先了解原理,自己简单实现,然后可以使用成熟的第三方工具
    - 使用伪代码或者趁手的第三方工具,帮助完成复杂的数据转换的分析
2、选择图片裁剪工具
* 寻找合适开源库的经验之谈
    - 使用google或者github来搜索
    - 使用英文作为关键词
* 考量一个开源库是否符合标准
    - star数量
    - issue数量
    - releases活跃度
    - 查看它的DEMO
3、使用Cropper.js获取裁剪图片数据
* 实例上的一个方法-getCroppedCanvas
    - https://github.com/fengyuanchen/cropperjs/blob/master/README.md#getcroppedcanvasoptions
* HTMLCanvasElement
    - https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
########
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, 150, 75);
// https://www.runoob.com/try/try.php?
filename = tryhtml5_canvas_first
########
* 两个有用的方法
    - toDataURL-返回base64编码的URL
    - toBlob-File继承Blob,const formData=new FormData();
      formData.append(name,value,filename),当value为Blob,filename默认'blob'
      当value为File,filename为文件名称
4、图层属性需求分析
* 图层锁定和隐藏/显示以及点击选中
    - 在editor.ts的store中的components添加更多标识符
########
{
...
isLocked: boolean;
isHidden: boolean;
}
########
    - 点击按钮切换为不同的值,使用这个值在页面上做判断。
    - 点击选中,设置currentElement的值
* 图片名称编辑
    - 添加更多属性-layerName
    - 点击图层名称的时候,在input和普通标签之间切换。
    - 添加按钮响应-对于esc和enter键的响应
        ~ 可能抽象一个通用的hooks函数-useKeyPress
    - 点击到input外部区域的响应
        ~ 可能抽象一个通用的hooks函数-useClickOutside
* 拖动改变顺序
    - 最有难度的一个需求,涉及到一个较复杂的交互
    - 最终目的其实就是改变store中components数组的顺序
    - 在这块功能进行编码的时候,再开始详细的分析
5、列表排序的演示和需求分析
* 新的学习方法
    - 用手写简单的方法实现一个功能。
    - 然后使用比较成熟的第三方解决方案。
    - 技能学习原理又能学习第三方库的使用。
* 从两个DEMO开始
    - Vue Draggable Next:https://sortablejs.github.io/vue.draggable.next/#/simple
    - React Sortable HOC:https://clauderic.github.io/react-sortable-hoc/
* 列表排序的三个阶段
    - 拖动开始(dragstart)
        ~ 被拖动图层的状态变化
        ~ 会出现一个浮层
    - 拖动进行中(dragmove)
        ~ 浮层会随着鼠标移动
        ~ 条目发生**换位**:当浮层下沿超过被拖动条目二分之一的时候,触发换位
    - 松开鼠标阶段(drop)
        ~ 浮层消失
        ~ 被拖动图层状态复原
        ~ 数据被更新
6、拖动排序功能开发
* 第一阶段Dragstart
    - 被拖动图层的状态变化,常规做法
        ~ 添加mouseDown事件,检查当前的target是哪个元素,然后给他添
          加特定的状态
        ~ 添加mouseMove事件,创建一个和被拖动元素一模一样的浮层,将
          它的定位设置为绝对定位,并且随着鼠标的坐标更新。
    - 使用HTML的Drag特性
        ~ 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations
        ~ 浏览器的默认拖拽行为:支持图像,链接和选择的文本。
        ~ 其他元素默认情况是不可拖拽的。
        ~ 如果想可以拖拽可以设置为draggable='true'
        ~ 使用dragstart事件监控拖动开始,并设置对应属性。
7、本周总结
* 过程回顾和要点
    - 图片裁剪
        ~ 选择Cropper.js作为基本库
        ~ 初始化Cropper区域,并且通过事件拿到对应坐标
        ~ 第一种方式:使用阿里云OSS完成图片裁剪功能
        ~ 第二种方式:使用Cropper.js获取图片数据
        ~ getCroppedCanvas()->toBlob()->重新上传Blob对象
    - 图层列表功能
        ~ 隐藏和锁定-画布元素的属性设计
        ~ InlineEdit组件
        ~ 测试驱动开发
        ~ 添加常用的钩子函数-useKeyPress//useClickOutside
        ~ 界面上测试并且带动优化
    - 图层列表拖动排序的原理
        ~ 拖拽排序四部曲
          设置draggable属性(浏览器帮你实现拖动效果)
          监控dragstart事件(添加拖动开始时状态)
          监控dragover事件(在特定时刻完成数据交互)
          监控drp事件(恢复到初始状态)
        ~ 使用第三方库Vue Draggable Next实现排序
    - EditGroup属性分组组件开发
        ~ 对于复杂的问题,先使用流程图和伪代码理清关系再开始动手
    - 页面设置面板开发
        ~ 正则的分析和好帮手:https://regexr.com/
        ~ 属性修改表单组件的开发原则

十九、让元素动起来-编辑器画布交互功能开发

1、前言
* 引言
    - 对于一个可视化编辑器,用户的易用性和交互在我看来非常重要,也是整个前端的难点。
      这些内容,我觉得就是面试问到的或者简历里面可以写的,**在工作的项目中,你做了什么有挑战
      性的工作**的完美答案,所以在以后的求职生涯中,如果要体现亮点,你不妨可以试试把这一周的
      内容和经历写在里面。
* 将收获什么
    - 拖拽移动图层的实现
    - 拖拽改变图层大小的实现
    - 复杂快捷键的实现
        ~ Hotkeys.js
        ~ 插件的概念
    - 撤销和重做的实现
        ~ 单元测试
        ~ 函数截流
        ~ 等等很多优化的点
    - 右键菜单的实现
        ~ 函数式创建组件的方式
* 学习方法
    - 对于元素的交互操作原理,可以使用画图的方式来帮助你
    - 对于Vuex的数据操作,不要忘记单元测试。
2、快捷键操作的需求
* **元素选择**前提都是在元素被选中的情况下
    - 拷贝图层-⌘C/Ctrl+C:新建当前选择的元素的一个数据结构
    - 粘贴图层-⌘V/Ctrl+V:将新建的元素添加到components数组中
    - 删除图层-Backspace/Delete:在components数组中删除选择的元素
    - 取消选中-ESC:currentElement设置为空
* **元素移动**
    - 上下左右移动-像素-↑↓→←:更新选中元素props的top/left值
    - 上下左右移动+像素-Shift+↑↓→←:更新选中元素props的top/left值
* **撤销/重做**
    - 撤销-⌘Z/Ctrl+Z
    - 重做-⌘⇧Z/Ctrl+Shift+Z
* 好用的按键响应库-HotKeys.js
    - 项目地址:https://github.com/jaywcjlove/hotkeys
    - 演示地址:https://wangchujiang.com/hotkeys/
3、回滚和重做-可以优化的点
* 功能开发演化过程
    - 需求分析
        ~ 全量保存
        ~ 只保存修改的数据
        ~ 需要什么类型的数据(新增/删除/修改)
    - 基本功能第一步-操作以后保存特定类型的数据
    - 基本功能第二步-设置数据操作的指针,undo往前移动,并且对应不同数据做相反操作
    - 基本功能第三部-添加测试,验证基本功能的运行
    - 界面测试-提出改进意见
    - 进阶功能-一次操作可以回退多个值(移动/修改大小)
        ~ 改进updateComponent这个mutation,让它支持多个值的更新
        ~ 改进undo,传入数组,可以完成多个值的回滚
    - 进阶功能-支持添加记录的函数防抖
        ~ 主题简单的debounce函数
        ~ 缓存一开始修改的旧的值
* 可能完成的优化
    - 代码结构的优化
        ~ state结构的优化
        ~ 一些帮助函数位置的优化
    - 添加单元测试
    - 快捷键的添加
    - 添加页面设置的回滚功能
    - 看看有没有什么bug
* 思考一下
    - 对于一个简单的编辑器,实现undo/redo有什么思路?
    - 对于一个复杂的编辑器,比如vscode,实现这个功能有什么思路?
4、右键菜单
* 需求分析
    - 在编辑器区域在元素上**点击右键**弹出一个自定义的菜单
    - 生成的菜单中有一系列的操作
    - 点击对应的选项完成操作,并且自定义菜单消失
* 实现方法
    - 在中间编辑器区域拦截默认的右键点击事件
        ~ https://developer.mozilla.org/zh-CN/docs/Web/API/Element/contextmenu_event
    - 判断是否点击在组件元素上(判断鼠标事件的e.target是否在EditWrapper中)
    - 显示(display:block)一个自定义菜单,其中包括操作项,显示在鼠标位置(使用e.clientX
      和e.clientY)
    - 点击完成操作-重用快捷键已经支持的mutation,并且关闭自定义菜单(display:none)
5、本周总结
* 过程回顾和要点
    - 元素拖拽移动
        ~ 原理:修改top,left值
        ~ 注意计算gap值即可,鼠标在元素内部的偏移量
    - 元素拖拽改变大小
        ~ 原理:修改width,height(有可能修改top,left)
        ~ 注意不同的位置,计算方式的不同,(鼠标位置和元素位置的关系)
    - 快捷键的实现
        ~ 原理:在按下特定按键的时候触发对应的mutations
        ~ hotkeys.js作为第三方库
        ~ plugins插件的概念
    - 撤销和重做
        ~ 历史记录的需求以及方案设计
        ~ 单元测试测试基本操作
        ~ 在界面上操作并发现可以优化的点
        ~ updateComponents一次可以更新多个属性和值
        ~ 函数截流的实现
    - 右键菜单
        ~ contextmenu事件
        ~ 使用render函数将组件挂载到对应的节点上
        ~ 使其插件化
* 剩下的一些工作
    - 一些快捷键的补充
    - 一些单元测试的补充
    - 右键菜单的一些条目的补充
    - 代码的一些优化工作(精简state,以及提取通用函数到别的文件)

二十、前后端结合-编辑器整合后端接口

1、前言
* 引言
    - 这周我们要正式和后端打交道,让我们的作品数据可以持久化的保存在数据库中。并且针对SPA项
      目的一些前后端分离开发的通用难点和痛点进行攻坚。
* 完成的功能
    - 前后端分离开发,以及分析接口的功能和需求
    - 前端mock server的实现
        ~ 快速搭建
        ~ 自定义接口和返回数据
        ~ 使用JWT完成路由保护
    - 使用AForm完成表单验证的以及源代码解析
    - 应用通用状态的添加
        ~ 全局Loading状态
        ~ 细粒度的Loading状态
        ~ 全局错误状态
    - 实现登陆过程以及登陆状态的持久化
    - 路由权限验证
    - 编辑器作品操作
        ~ 获取作品
        ~ 保存作品
            点击保存
            自动保存
            未保存跳转前提示
* 知识点
    - Vuex action
    - JSON server
    - Ant Design Vue
        ~ AForm
        ~ AFormItem
        ~ useForm
    - axios拦截器
    - JWT
    - vue router的钩子函数
        ~ beforeEach
        ~ beforeRouteLeave
    - **高阶函数!!**
* 提供的测试接口
    - **http://182.92.168.192:8081/**
2、前后端分离开发
* 简单描述下过程
    - 定制规范
        ~ RESTful协议
        ~ 将其文档化-https://www.yuque.com/docs/share/4c353b03-5973-440b-9041-b7edbef5e6f6?#
        ~ 使其版本化-每次升级以后,可以清楚的查看变动,修改-https://shopify.dev/concepts/about-apis/versioning/release-notes
        ~ 使用工具进行实时查看和运行
            Postman:https://www.postman.com/
            Swagger:https://swagger.io/
    - 开发阶段-前端
        ~ 使用mock server
        ~ 并行开发,不依赖后端服务器,提升效率
    - 连调阶段
        ~ 前端直接切换为后端的接口,进行检验
* 简单说一下这种方式的优点
    - 打造精益团队
    - 提升开发效率
    - 增强代码的可维护性
3、需求以及接口分析
* 两类接口
    - 需要特殊权限才能访问的(比如编辑器中的操作)
    - 不需要权限可以直接访问的(比如获取热门海报)
* 获取权限的接口(用户)
    - 我们使用手机号+验证码的方式来登陆
    - 获取验证码接口-POST /users/genVeriCode
    - 登陆接口-获取token-POST /users/loginByPhoneNumber
    - 使用token访问需要权限的接口-比如获取当前用户信息- GET /api/users/getUserInfo
        ~ 使用authorization Header-添加Bearer+token信息
* 编辑器操作作品接口,需要权限
    - 新建作品-POST /works
    - 查询作品-GET /works
    - 保存作品-PATCH /works/${id}
    - 发布作品-POST /works/publish/${id}
4、Mock server的选择和搭建
* 引言
    - 是否需要选用express,koa,egg.js等等成熟的框架?
* 好用的Mock Server需要有的特点
    - **快速搭建**
    - 支持标准Restful操作和路由规则
        ~ /templates-拿全部数据
        ~ /templates/${id}-拿一条数据
    - 一些进阶扩展-自定义路由,中间件等等
* 隆重退出JSON Server
    - https://github.com/typicode/json-server#access-control-example
* 安装
########
npm install --save-dev json-server
########
* 启动
########
npx json-server --watch db.json
########
5、Token实现权限管理的简单原理
* **token是无状态的**,服务器上不需要记录任何信息。每个发送到服务器的请求都会带上一个
  token,服务器利用这个token检查确认请求的真实性。
* **token的组成**通过特定的加密算法,将用户登录后的一些信息(比如用户Id)和过期时间等信
  息存储在一个加密过的字符串中。
* 在JSON Web Token官网感受一下。https://jwt.io/

二十一、前后端结合-完成剩余的需求

1、前言
* 引言
    - 我们的整个应用的大体结构都已经完成,剩下的页面有很多重复的工作,这种需求我并不会和大家
      一起编码完成。这周的内容是从中找到一些我认为比较独特,大家在工作中比较少见的需求进行完
      成,扩宽大家的知识面,并且学习几个工具的原理。
* 完成的功能
    - 完成作品发布的流程
        ~ 对编辑区域进行截图
        ~ 截图完成重新上传文件
        ~ 上传完成保存作品并且创建渠道
    - 发布后渠道窗口的功能
        ~ 管理渠道-创建和删除渠道
        ~ 使用钩子函数整合发布流程-usePublishWork
        ~ 将渠道链接生成二维码
    - 拷贝文本到剪贴板的功能
    - 一个钩子函数的进化过程
        ~ 首页加载更多
        ~ 作品页实现翻页
    - 下载文件的原理以及编码过程
* 功能点
    - html2canvas
        ~ 基本使用
        ~ 踩坑之旅
    - qrcode-生成二维码
    - Clipboard.js-复制文本到剪切板
        ~ 基本使用
        ~ 原理
    - useLoadMore-钩子函数的进化之旅
        ~ 支持点击加载更多
        ~ 支持无限滚动
        ~ 支持分页
    - 前端下载文件的原理
        ~ 同域文件的下载
        ~ 跨域文件的下载
        ~ FileSaver.js的使用和原理
2、关于widnow.devicePixelRation
* 设备像素/物理像素
    - 设备像素也被称为**物理**像素,他是显示设备中一个最微小的物理部件。在同一设备中,物理像素
      的总数是固定的。
* 独立像素/css像素
    - css像素是一个抽象的单位,主要使用在浏览器上,用来精确的度量(确定)Web页面上的内容。
    - css像素被称为与设备无关的像素(device-independent像素),简称为“DIPs”。
    - 在一个标准的显示密度下,一个css像素对应着一个设备像素。
* window.devicePixelRation
    - https://developer.mozilla.org/zh-CN/docs/Web/API/Window/devicePixelRatio
    - 返回当前显示设备的物理像素分辨率与css像素分辨率之比。简单来说,它告诉浏览器应使用多少
      屏幕实际像素来绘制单个css像素。
    - 所以在标准屏幕下,devicePixelRatio应该为**1**
* 特例
    - 视网膜(Retina)显示屏,它会使用更多的屏幕像素绘制相同的对象,从而获得更清晰的图像。
      devicePixelRatio为**2**。
    - 所以虽然我们中间的元素css尺寸是375px,但是因为Apple是视网膜屏幕,所以使用了两倍于
      css尺寸的设备像素来渲染它,这就是最后图片尺寸为750px的原因。
* html2canvas的处理
    - https://html2canvas.hertzen.com/configuration/中的scale属性
3、HTML2Canvas截图的原理
* 目的:一个canvas元素,上面有绘制有一系列的HTML节点
* 局限:Canvas中没法添加具体的HTML节点,它只是一张画布
    - 通过canvas.getContext("2d")可以拿到canvas提供的2D渲染上下文,然后在里面绘制形状,文
      本,图像和其他对象。
    - 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D
        ~ 矩形-fillRect()
        ~ 文本-fillText()
        ~ 图像-drawImage()
        ~ 等等...
* SVG来拯救我们
    - 可缩放矢量图形(Scalable Vector Graphics,SVG),是一种用于描述二维的矢量图形,基于XML
      的标记语言。
* SVG中有一个神奇的元素称之为foreignObject
    - 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/foreignObject
    - foreignObject元素允许包含来自不同的XML命名空间的元素。在浏览器的上下文中,很可能是
      XHTML/HTML
* 解题思路
    - 创建一个canvas元素
    - 创建svg文件,使用Blob构造函数
    - 将svg中的值填充foreignObject,然后填充想要复制节点的HTML
    - 创建image标签,将image.src = URL.createObjectURL(svg)
    - 在image完成读取以后,调用canvas的drawImage方法,将图片绘制到画布上。
4、复制到剪贴板的原理
* 看起来很简单的问题,但是由于不同浏览器之间存在不同的API实现和各种hack,所以它的实
  现很混乱
    - 方法一最现代的**Clipboard API**
        ~ 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Clipboard_API
        ~ 还在working draft阶段,浏览器兼容性有待加强。
    - 方案二document.execCommand()方法
        ~ 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand
        ~ 它不仅仅是解决复制的场景,而且是给可编辑区域的提供一系列功能
* document.execCommand('copy')解决思路分析
    - 手动创建可编辑元素,比如textArea,然后将要拷贝的值设置为它的value
    - 将它插入到页面中,调用textArea上的方法,对值进行选中
    - 然后再调用document.execCommand('copy')
    - 特别注意textArea要不可见,使用特殊的样式让它不出现在可见区域
    - 最后要将textArea节点删除
5、分析B端剩余的需求
* 引言
    - 剩余的需求难度并不大,我们解决了通用问题以后,剩下的需求只是罗列功能,发送异步请求而已
* 首页
    - 显示templates列表(已完成)
    - 点击加载更多(可否抽象成一个Hooks函数?)
        ~ 对应接口:/api/templates?pageIndex=${page}&pageSize=${size}&title=${title}
    - 点击搜索
        ~ 对应接口:/api/templates?pageIndex=${page}&pageSize=${size}&title=${title}
    - 显示最新的我的作品 -> 可以复用作品详情页
* 作品详情页
    - 点击创建
        ~ 对应接口:POST /api/works/copy/${id}
        ~ 下载图片
* 我的作品
    - 搜索
    - 对应接口(我的模板):/api/works?pageIndex=${page}&pageSize=${size}&title=${title}&isTemplate=1
    - 对应接口(我的作品):/api/works?pageIndex=${page}&pageSize=${size}&title=${title}&isTemplate=0
    - 分页 -> ant-design-vue的pagination组件
    - 上面有一系列操作
        ~ 删除:DELETE /api/works/${id}
        ~ 复制:POST /api/works/copy/${id}
        ~ 转赠:POST /api/works/transfer/${id}/${cellphone}
        ~ 下载图片
6、下载文件的原理
* A链接
    - 可以创建通向其他网页、文件、同一页面内的位置、电子邮件地址或任何其他URL的超链接。
* A链接的一个特殊属性:download
    - 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a
    - 此属性指示浏览器下载URL而不是导航到它,因此将提示用户将其保存为本地文件
* A链接的另外一个特殊属性:rel
    - 该属性指定了目标对象到链接对象的关系。
    - noopener一个重要的属性,对于web安全来说非常关键。
    - 当你使用target='_blank'打开一个新的标签页时,新页面的window对象上有一个属性opener,
      它指向的是前一个页面的window对象,因此,后一个页面就获得了前一个页面的控制权
* 我们模拟这个过程来完成下载。
    - 创建A链接
    - 设置href以及download属性
    - 触发A链接的点击事件
* download属性仅适用于同源URL
* 使用FileSaver.js完成下载
    - 文档地址:https://github.com/eligrey/FileSaver.js/
* 借助HTTP特殊的响应头出发浏览器自动下载
    - 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
    - Content-Disposition-最佳的下载方式,需要服务器端的支持,并且不需要任何的Javascript,
      需要在HTTP头部添加
########
Content-Type: 'application/octet-stream; charset=utf-8'
Content-Disposition: attachment; filename="filename.jpg";
########
7、本周总结
* 过程回顾和要点
    - html2canvas-DOM节点截图工具
        ~ 基本使用
        ~ 使用过程的一些坑(不支持box-shadow,图片要支持跨域访问)
        ~ html2canvas基本原理
            使用svg的foreignObject标签插入对应的DOM元素,并且创建文件
            创建image,使用对应的svg文件,并且使用canvas.getContext('2d').drawImage()方法
    - qrcode-生成二维码
        ~ 基本操作
        ~ 特别注意:watch在reactive数组上的问题,怎样获得新的值和旧的值
    - clipboard.js-实现内容的拷贝到剪贴板
        ~ 基本使用
        ~ clipboard.js的原理
            使用现代的ClipboardAPI-navigator.clipboard.writeText
            使用兼容性更好的document.execCommand('copy')
                创建textarea节点,设置它的位置不可见
                设置它的value,调用select选中,再调用docuemnt.execCommand('copy')
    - useLoadMore改造
        ~ 实现基本功能支持加载更多和无限滚动
        ~ 添加上一页,下一页功能
        ~ 实现分页选取的功能
    - FileSaver.js-浏览器端下载文件的方式
        ~ 浏览器下载文件的原理
            同源:使用A链接的download属性
            跨域:发送异步请求,返回Blob,将Blob存储在URL.createObjectURL()中,然后点击下载
        ~ 服务器端下载文件的原理
            使用content-disposition响应头,并且将值设置为attachment;filename="filename.jpg"
        ~ FileSaver.js解析
            简单实用
            源码解析-添加更多的polyfill代码-比如使用msSaveOrOpenBlob或FileReader的方法
posted on 2023-10-19 19:29  一路繁花似锦绣前程  阅读(15)  评论(0编辑  收藏  举报