关于富文本编辑器:简单富文本编辑器实现

在线demo预览,实现原理如下: 1)获取选区内容,如果没有选区则给整段文本设置款式。2)新增span标签包裹选区内容,为span标签减少style款式。3)优化DOM构造。比方demo中删除了空白节点、合并父子层级款式缩小层级嵌套。第一步:通过 www.getSelection() 获取选区,sel.isCollapsed判断选区的起始点和终止点是否位于一个地位 //返回一个 Selection 对象,示意用户抉择的文本范畴或光标的以后地位let sel = window.getSelection()let range = sel.getRangeAt(0) //返回一个蕴含以后选区内容的区域对象。if (sel.isCollapsed) { //没有框选设置整段文本款式 container.style[key] = val return}第二步:新增span标签包裹选区内容 const f = range.extractContents() //获取选取内容,并删除let span = document.createElement('span')span.style.setProperty(key, val)span.appendChild(f) //span标签包裹选取内容第三步:优化dom构造,替换选取节点内容 const keys = ['font-size', 'letter-spacing', 'font-weight', 'color']function getNodeStyle(n) { const style = n.style let ret = {} keys.filter(k => style[k] != null && style[k] != '').forEach(k => { ret[k] = style[k] }) return ret}function appendStyle(n, style) { for (let key in style) { n.style.setProperty(key, style[key]) }}function isSame(s1, s2) { return JSON.stringify(s1) == JSON.stringify(s2)}//找到文本,向上获取父节点的款式附加到本身,缩小span嵌套层级function flatNode(node) { let list = [] let pStyle = getNodeStyle(node) let last = null; function _each(n, s) { for (let i = 0; i < n.childNodes.length; i++) { let child = n.childNodes[i] if (child.nodeType == 3) { let newStyle = Object.assign({}, s, pStyle) if (last && isSame(newStyle, getNodeStyle(last))) { last.appendChild(child.cloneNode()) last.normalize() //相邻文本节点合并 } else { let span = document.createElement('span') span.appendChild(child.cloneNode()) appendStyle(span, newStyle) list.push(span) last = span; } } else { _each(child, Object.assign({}, s, getNodeStyle(child), pStyle)) } } } _each(node, {}) return list; }const nodeList = flatNode(span)let newF = document.createDocumentFragment()newF.replaceChildren(...nodeList)newF.normalize()range.insertNode(newF) //选区地位插入新节点

March 22, 2022 · 1 min · jiezi

关于富文本编辑器:富文本编辑器之游戏角色升级ing

一、前言想必大家看到这个题目,心中不禁会浮现几个问题: 什么是富文本编辑器?富文本编辑器和游戏角色有什么关系?为什么是降级ing?什么是富文本编辑器——富文本编辑器集成了格局设置、媒体嵌入、社交互动等一系列编辑性能,所见即所得的给用户提供多元的展现成果。譬如论坛、社区、评论等等都用到了富文本编辑器。 和游戏角色的关系——富文本编辑器和游戏角色有很多共通之处,为了让富文本编辑器的介绍更加有代入感,本文将采纳游戏角色类比的形式进行解说。至于共通之处体现在哪里,前面将一一介绍。 为什么是降级ing——“降级ing”代表继续的进行时,本文的目标是聚焦富文本编辑器的共性问题,抛砖引玉,心愿能给大家提供一些解决思路。富文本编辑器始终在继续倒退中,而对于共性问题的摸索也从未停歇过。 本篇文章次要分为五个局部: 前言理解富文本编辑器富文本编辑器选型指南富文本编辑器如何扩大总结本文通过游戏角色类比的形式,心愿可能让富文本编辑器接触较少的开发者,都能够深刻的理解富文本编辑器。明天,咱们就一起来探讨下在富文本编辑器选型、扩大过程中遇到的共性问题。 二、理解富文本编辑器通常,咱们在抉择一款新的游戏之前,都会抉择先去官网、论坛理解游戏材料,从中筛选出无效信息,辅助咱们抉择适合的角色。 开发人员在接到富文本编辑器需要时,也不会轻易抉择其中一个,而是基于宏大的数据进行技术选型。这一节内容,就是为后续的选型所做的筹备工作。 2.1 角色格调 - 富文本编辑器状态游戏角色在开服上线前,都会默认装备不同的格调,则格调往往决定了咱们对于角色的初始印象。 富文本编辑器同样具备几种罕用的初始状态,经典模式、文档模式、内联模式,如下图所示: 那么从上图的比照中,能够看进去:富文本编辑器必不可少的组成部分是内容编辑区域。状态栏是用来记录编辑时的相干数据,能够暗藏。而工具栏则能够任意调整显示的地位、机会甚至切换至幕后操控(通过快捷键等形式触发)。 反之,咱们能够取得这样一条讯息:通过工具栏、内容区域、状态栏、菜单栏的不同组合能够赋予富文本编辑器不同的展现状态。 2.2 成长阶段 - 富文本编辑器倒退历程游戏中的角色都是可成长角色,在成长过程总会呈现一些瓶颈期,而跨过所谓的瓶颈期之后,角色的能力将呈现不言而喻的扭转。 在整个倒退过程中,富文本编辑器遇到过一些窘境。也正是因为这些窘境,能够将倒退历程分为L0、L1、L2和L3阶段。 L0->L1 L0,即初代的富文本编辑器,依赖于浏览器本身的execCommand,仅提供了无限的命令,实现最简略的性能。随着对款式越来越丰盛的要求,此时的富文本编辑器无奈满足需要,L1阶段的编辑器应运而生。L1的富文本编辑器采纳 自定义execCommand的计划,能够实现更加丰盛的富文本性能。 L1->L2 L0、L1的富文本编辑器,依然都是通过execCommand批改HTML。而不同浏览器中,对于同一表象的富文本,其HTML构造可能大不相同。 比如说 加粗 ,其HTML可能是加粗,可能是加粗,也可能是加粗等等。为了解决数据与视图无奈一一对应的问题,提出了自定义数据模型的概念。 自定义数据模型, 是富文本编辑器在富文本HTML-DOM树的根底上抽离进去的数据结构,雷同的数据结构能够保障渲染的HTML也是雷同的。自定义的命令间接控制数据模型,最终保障渲染的HTML文档的一致性。 对于雷同的HTML,不同的富文本编辑器最终出现的数据模型并不相同。以 Hello EditorName 为例,这里比照了Quill、ProseMirror、Draft、Slate的数据模型如下: L2阶段的富文本编辑器,通过抽离数据模型,解决了富文本中脏数据、简单性能难以实现的问题。通过数据驱动,能够更好的满足定制性能、跨端解析、在线合作等需要。 L2->L3 到L2阶段的编辑器,能够满足绝大部分的应用场景。那为什么前面又倒退出L3呢? 这是因为,L0-L2的富文本编辑器都是基于浏览器的contentEditable,在批改数据模型时,往往须要对用户操作进行拦挡。对用户行为进行拦挡是很难管制的,再加上不同浏览器的兼容问题,很容易呈现bug。 为了解决contentEditable编辑不可控的问题,以 Google Docs 为代表的编辑器通过“自研排版引擎”步入了L3阶段。 自研排版引擎,彻底摈弃了contentEditable,通过自行管制光标地位、选区绘制、排版、监听输出等行为,实现和浏览器类似的编辑成果。“自研”,无疑具备了更高的扩展性。但与此绝对应的,其开发难度高、老本高、隐性问题多,在整体体验和性能上与原生浏览器渲染仍存在肯定差距,该阶段的编辑器还有一段路要走。 ps: There are a thousand Hamlets in a thousand people's eyes。 下述对于成长阶段的划分仅基于作者自己的认识。 回顾富文本编辑器的倒退历程,不难发现:富文本编辑器的构造脱离不了模型、视图、控制器这三大模块。如下图所示: 正如游戏角色所冲破的瓶颈期,富文本编辑器在L1跃迁至L2产生的扭转是:自定义数据模型的抽离;L2跃迁至L3的扭转则是:排版引擎的全新定义。 三、富文本编辑器选型指南当咱们曾经通过各种渠道理解到游戏背景、人物材料之后,下一步就要登录游戏创立游戏角色。此时,老手经常遇到的困扰无疑就是:如何抉择最合适本人的游戏角色。 相似的,对首次接触富文本编辑器的小伙伴来说,常提到的问题是:我该抉择哪款富文本编辑器? 首先,能够依据你的业务需要,抉择对应阶段的富文本编辑器: 业务自身就是以富文本编辑器为外围,或者有协同编辑需要。—— 抉择L2、L3的编辑器扩大,或者自研编辑器。这里能够参考有道云笔记和语雀的计划,参考链接见文末。业务需要频繁迭代,交互设计要求较高的。—— 倡议抉择L2的编辑器。业务较为稳固,要求不高的。——L1、L2任选。如果业务场景比较复杂,难以评判之后的业务场景。——倡议抉择L2的编辑器。其次,在选定好阶段的根底上,依据我的项目架构(Vue、React、Augular等),以及富文本编辑器本身的特点,抉择适宜的编辑器就能够。能够从下述几个方面思考: 开源水平社区生态交互细节扩大反对度定制化老本以上,就是我梳理的选型套路。像是CKEditor、TinyMCE、Quill等都是交口称誉的,大家在选型的时候无妨能够思考下这些编辑器: 四、富文本编辑器如何扩大抉择适合的角色,仅仅只是游戏的开始。在游戏过程中,须要一直地调整游戏角色的技能树,将其后劲倒退到极限。 ...

July 5, 2021 · 2 min · jiezi

关于wangeditor:wangEditor的使用笔记

前言半路接手的后盾我的项目,外面的富文本编辑器应用的kindeditor,总感觉有些臃肿,而且之前的人在封装组件后,应用起来有些bug,于是从网上搜了一下,决定应用wangeditor进行从新封装,这里将应用笔记留下,供当前查看,并分享。 1.根本应用这个没什么好说的了,去官网看文档就好了。呈现的几个小问题: 留神设置一下editor的z-index(editor.config.zIndex),因为他的默认z-index很大,很有可能遮挡住你的其余控件(如弹窗等)。应用接口获取曾经保留的富文本内容,并将内容v-bind到editor组件的时候,因为申请数据须要工夫,很大可能造成富文本框组件渲染时props还没获取到富文本内容,无奈在mounted事件中间接将内容回填到富文本框,这里我间接在editor组件中通过watch解决的。2.图片上传如果须要本人实现上传逻辑,可参考并配置editor.config.customUploadImg。我的状况是,咱们有本人的图片上传组件,心愿应用本人的组件进行上传,这时须要配置editor.config.uploadImgFromMedia办法,替换本地上传性能,在其中退出本人的逻辑。记着上传完结后调用editor.cmd.do办法将图片插入富文本内容中。 3.视频上传同上,咱们也心愿应用本人的组件替换原始的本地上传,可是文档中没有提供相似editor.config.uploadImgFromMedia的办法。没方法,只能应用自定义扩大Button菜单的形式实现了。思路如下: 配置中删除video菜单。退出自定义扩大Button菜单。减少配置一个editor.config.uploadVideoFromMedia函数,来实现咱们本人的逻辑。在自定义扩大Button菜单的类中: 自定义菜单沿用之前video菜单的款式,保障款式统一。在自定义菜单中,设置菜单点击事件去调用咱们本人配置的editor.config.uploadVideoFromMedia。这样,实现了视频上传中相似editor.config.uploadImgFromMedia的办法。 自定义扩大Button菜单的类的代码如下: //wangeditor的自定义视频上传菜单import E from 'wangeditor';const BtnMenu = E.BtnMenu;class NewVideoMenu extends BtnMenu{ constructor(editor){ const $elem = E.$( `<div class="w-e-menu" data-title="视频"> <i class="w-e-icon-play"></i> </div>` ); super($elem, editor); } clickHandler(){ this.editor.config.uploadVideoFromMedia(); } tryChangeActive(){}}export default NewVideoMenu;通过管制自定义菜单在editor的配置数组editor.config.menus中的插入地位,来调整菜单的显示地位。 4.上传的视频在富文本框没有光标、无奈删除的问题视频上传完结时: let videoHTML = '&nbsp;<video src="' + this.video_url + '" controls style="max-width:100%"></video>&nbsp;';this.editor.cmd.do('insertHTML', videoHTML);视频标签的两侧各加一个空格的转义字符&nbsp;,能够使光标在富文本的视频四周呈现,并能够删除视频。测试环境中测试无效。 结语大部分的需要下,咱们不须要应用性能过于简单的富文本控件,应用一个简洁轻量的富文本控件就好。wangEditor就是其中之一。除此之外,其可扩展性好,文档写的也不错,还是很举荐大家应用的。

April 23, 2021 · 1 min · jiezi

关于富文本编辑器:想实现强大的富文本编辑器功能这个第三方控件要了解

点击获取工具>>DevExpress WinForms领有180+组件和UI库,能为Windows Forms平台创立具备影响力的业务解决方案。DevExpress WinForms能完满构建晦涩、好看且易于应用的应用程序。v20.2日前全新公布,此版本增强了PDF Viewer、富文本编辑器等控件性能! PDF Viewer便签 PDF Viewer当初反对PDF便笺,您能够通过代码或通过控件的UI增加、编辑、删除便笺,以及为便笺增加正文。 Ribbon和BarsBackstageView - 反对DirectX WinForms BackstageViewControl当初反对DirectX硬件加速,这样能够确保动画更加晦涩和高效存储,特地是在高分辨率显示器上。 富文本编辑器跨表中断表行 表格行当初能够逾越多个页面,此版本增加一个新的行选项 - 'Allow row to break across pages',要在代码中启用此性能,请应用TableRow.BreakAcrossPages属性。 OLE对象 WinForms富文本编辑器反对OLE对象,新API容许您拜访和编辑代码中的OLE对象,带有OLE对象的文档能够打印并导出为PDF。 表中的'Keep with Next' 和 'Widow/Orphan Control' Word Processing Document API 和WinForms / WPF RTF编辑器在显示、打印和导出具备表段落的文档时,将利用以下选项: Keep with NextWidow/Orphan Control脚注和尾注UI 富文本编辑器附带新的UI元素,旨在插入、导航和格式化文档脚注和尾注。 “表格填写”爱护 您当初能够在代码中或通过RTF编辑器的UI来治理“表格填写”爱护。 新格局 Word Processing Document API 和WinForms/WPF富文本编辑控件当初反对以下文件格式: DOCM(Microsoft Office Open XML启用了文档的格局)DOT(Microsoft Word 97-2003模板格局)DOTM(Microsoft Office Open XML启用的模板格局)DOTX(Microsoft Office Open XML模板格局)FlatOpc XML(存储在立体XML文件而不是ZIP包中的Microsoft Word XML文档)。文档渲染加强 ...

January 28, 2021 · 1 min · jiezi

一键markdown转html

使用marked 这个插件npm install marked --save然后再需要的页面引入, import marked from 'marked' 然后直接使用maeked()方法即可转换为html。 sendNews() { alert(marked(this.content)); },转换前 转换后

July 16, 2019 · 1 min · jiezi

SpringVue整合UEditor富文本实现图片附件上传

下载UEditorhttps://ueditor.baidu.com/web...下载完整源码和JSP版本 Spring后端集成1. 解压完整源码,拷贝jsp目录下的java源码,到spring mvc后端 jsp目录下java源码 集成spring mvc后端2. 配置config.json解压JSP版本拷贝jsp目录下config.json 放到java项目的resource目录下,在这里是ueditorConfig.json 配置config.json文件名称,这里是ueditorConfig.json 3. 项目常量配置文件新建upload.properties,也放在resouce目录下,文件内容如下:#host地址host=http://localhost:8081/ssm_project#文件上传服务器地址(ip+端口)uploadHost=http://localhost:8090/#普通图片上传保存目录imagePath = fileUpload/image/#系统用户头像上传保存目录headImgPath = fileUpload/image/headImg/#系统用户默认头像sysUserDefImg = sysUser-default.jpg#文本文件上传保存目录documentPath = fileUpload/document/#音频文件上传保存目录soundPath = fileUpload/sound/#视频文件上传保存目录videoPath = fileUpload/video/#ueditor编辑器上传文件保存目录(包括图片、视频、音频、文本等文件)ueditor = fileUpload/ueditor/将upload.properties添加到Spring启动配置文件application.xml中,以便后面Controller访问<!-- 引入数据库配置文件 --><bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath:config.properties</value> <value>classpath:redis.properties</value> <value>classpath:upload.properties</value> </list> </property></bean>4. 编写工具类UploadUtil.javapackage cn.lega.common.util;import com.sun.jersey.api.client.Client;import com.sun.jersey.api.client.WebResource;import org.apache.commons.io.FilenameUtils;import org.springframework.util.FileCopyUtils;import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.File;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.UUID;public class UploadUtil { /** * 上传文件 * * @param request * @param response * @param serverPath 服务器地址:(http://172.16.5.102:8090/) * @param path 文件路径(不包含服务器地址:upload/) * @return */ public static String upload(Client client, MultipartFile file, HttpServletRequest request, HttpServletResponse response, String serverPath, String path) { // 文件名称生成策略(日期时间+uuid ) UUID uuid = UUID.randomUUID(); Date d = new Date(); SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); String formatDate = format.format(d); // 获取文件的扩展名 String extension = FilenameUtils.getExtension(file.getOriginalFilename()); // 文件名 String fileName = formatDate + "-" + uuid + "." + extension; //相对路径 String relaPath = path + fileName;// String a = serverPath + path.substring(0, path.lastIndexOf("/"));// File file2 = new File(a);// if (!file2.exists()) {// boolean mkdirs = file2.mkdirs();// System.out.println(mkdirs);// } // 另一台tomcat的URL(真实路径) String realPath = serverPath + relaPath; // 设置请求路径// WebResource resource = client.resource(realPath); // 发送开始post get put(基于put提交)// try {// resource.put(String.class, file.getBytes());// return fileName + ";" + relaPath + ";" + realPath;// } catch (IOException e) {// e.printStackTrace();// return "";// } // 用户目录/root/fileUpload/ueditor String userDir = System.getProperty("user.home"); String ueditorUploadPath = userDir + File.separator + path; File file2 = new File(ueditorUploadPath); if (!file2.exists()) { file2.mkdirs(); } String newFilePath = ueditorUploadPath + fileName; // 保存在本地 File file3 = new File(newFilePath); try { FileCopyUtils.copy(file.getBytes(), file3); return fileName + ";" + relaPath + ";" + realPath; } catch (IOException e) { e.printStackTrace(); return ""; } } public static String delete(String filePath) { try { Client client = new Client(); WebResource resource = client.resource(filePath); resource.delete(); return "y"; } catch (Exception e) { e.printStackTrace(); return "n"; } }}5. 编写Controller类UeditorController.java,为前端提供上传接口package cn.lega.common.controller;import cn.lega.common.baidu.ueditor.ActionEnter;import cn.lega.common.util.ResponseUtils;import cn.lega.common.util.StrUtils;import cn.lega.common.util.UploadUtil;import cn.lega.common.web.BaseController;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.sun.jersey.api.client.Client;import org.apache.commons.io.FilenameUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.ClassPathResource;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import org.springframework.web.multipart.MultipartHttpServletRequest;import org.springframework.web.multipart.MultipartResolver;import org.springframework.web.multipart.commons.CommonsMultipartResolver;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.*;import java.util.Map;/** * 用于处理关于ueditor插件相关的请求 * * @author silianpan */@RestController@CrossOrigin@RequestMapping("/common/ueditor")public class UeditorController extends BaseController { // 后台图片保存地址 @Value("#{configProperties['ueditor']}") private String ueditor; @Value("#{configProperties['uploadHost']}") private String uploadHost; //项目host路径 /** * ueditor文件上传(上传到外部服务器) * * @param request * @param response * @param action */ @ResponseBody @RequestMapping(value = "/ueditorUpload.do", method = {RequestMethod.GET, RequestMethod.POST}) public void editorUpload(HttpServletRequest request, HttpServletResponse response, String action) { response.setContentType("application/json"); String rootPath = request.getSession().getServletContext().getRealPath("/"); try { if ("config".equals(action)) { // 如果是初始化 String exec = new ActionEnter(request, rootPath).exec(); PrintWriter writer = response.getWriter(); writer.write(exec); writer.flush(); writer.close(); } else if ("uploadimage".equals(action) || "uploadvideo".equals(action) || "uploadfile".equals(action)) { // 如果是上传图片、视频、和其他文件 try { MultipartResolver resolver = new CommonsMultipartResolver(request.getSession().getServletContext()); MultipartHttpServletRequest Murequest = resolver.resolveMultipart(request); Map<String, MultipartFile> files = Murequest.getFileMap();// 得到文件map对象 // 实例化一个jersey Client client = new Client(); for (MultipartFile pic : files.values()) { JSONObject jo = new JSONObject(); long size = pic.getSize(); // 文件大小 String originalFilename = pic.getOriginalFilename(); // 原来的文件名 if (StrUtils.isEmpty(uploadHost) || uploadHost.equals("default")) { uploadHost = System.getProperty("user.home") + File.separator; } String uploadInfo = UploadUtil.upload(client, pic, request, response, uploadHost, ueditor); if (!"".equals(uploadInfo)) { // 如果上传成功 String[] infoList = uploadInfo.split(";"); jo.put("state", "SUCCESS"); jo.put("original", originalFilename);//原来的文件名 jo.put("size", size); // 文件大小 jo.put("title", infoList[1]); // 随意,代表的是鼠标经过图片时显示的文字 jo.put("type", FilenameUtils.getExtension(pic.getOriginalFilename())); // 文件后缀名 jo.put("url", infoList[2]);// 这里的url字段表示的是上传后的图片在图片服务器的完整地址(http://ip:端口/***/***/***.jpg) } else { // 如果上传失败 } ResponseUtils.renderJson(response, jo.toString()); } } catch (Exception e) { e.printStackTrace(); } } } catch (Exception e) { } }// @RequestMapping(value = "/exec")// public void config(HttpServletRequest request, HttpServletResponse response) {// // response.setContentType("application/json");// String rootPath = request.getSession().getServletContext().getRealPath("/");// response.setHeader("Content-Type" , "text/html");// try {// String exec = new ActionEnter(request, rootPath).exec();// PrintWriter writer = response.getWriter();// writer.write(exec);// writer.flush();// writer.close();// } catch (IOException e) {// e.printStackTrace();// }// } @RequestMapping(value = "/exec") @ResponseBody public String exec(HttpServletRequest request) throws UnsupportedEncodingException { request.setCharacterEncoding("utf-8"); String rootPath = request.getSession().getServletContext().getRealPath("/"); return new ActionEnter(request, rootPath).exec(); } @RequestMapping("/ueconfig") public void getUEConfig(HttpServletRequest request, HttpServletResponse response) { org.springframework.core.io.Resource res = new ClassPathResource("ueditorConfig.json"); InputStream is = null; response.setHeader("Content-Type", "text/html"); try { is = new FileInputStream(res.getFile()); StringBuffer sb = new StringBuffer(); byte[] b = new byte[1024]; int length = 0; while (-1 != (length = is.read(b))) { sb.append(new String(b, 0, length, "utf-8")); } String result = sb.toString().replaceAll("/\\*(.|[\\r\\n])*?\\*/", ""); JSONObject json = JSON.parseObject(result); PrintWriter out = response.getWriter(); out.print(json.toString()); } catch (IOException e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } }}Vue前端集成1. 解压jsp版本,拷贝到Vue前端项目static目录中 ...

July 10, 2019 · 4 min · jiezi

Vue-Element-vuequilleditor-实现源码编辑自定义图片上传和汉化

集百家之长,看了很多博客再结合自身情况,写了这个小组件功能,仅供参考。实现源码编辑vue-quill-editor的配置文件: // toolbar工具栏的工具选项(默认展示全部)const toolOptions = [ // 加粗 斜体 下划线 删除线 ['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线 ['blockquote', 'code-block'], // 1、2 级标题 [{header: 1}, {header: 2}], // 有序、无序列表 [{list: 'ordered'}, {list: 'bullet'}], // 上标/下标 [{script: 'sub'}, {script: 'super'}], // 缩进 [{indent: '-1'}, {indent: '+1'}], // 文本方向 [{direction: 'rtl'}], // 字体大小 [{size: ['small', false, 'large', 'huge']}], // 标题 [{header: [1, 2, 3, 4, 5, 6, false]}], // 字体颜色、字体背景颜色 [{color: []}, {background: []}], // 字体种类 [{font: []}], // 对齐方式 [{align: []}], [{clean: '源码编辑'}], // 这是自己加的 // 链接、图片、视频 ['link', 'image'], // 新添加的工具 ['sourceEditor']];const handlers = { shadeBox: null, // 添加工具方法 sourceEditor: function () { // alert('我新添加的工具方法'); const container = this.container; const firstChild = container.nextElementSibling.firstChild;// 在第一次点击源码编辑的时候,会在整个工具条上加一个div,层级比工具条高,再次点击工具条任意位置,就会退出源码编辑。可以在下面cssText里面加个背景颜色看看效果。 if (!this.shadeBox) { let shadeBox = this.shadeBox = document.createElement('div'); shadeBox.style.cssText = 'position:absolute; top:0; left:0; width:100%; height:100%; cursor:pointer'; container.style.position = 'relative'; container.appendChild(shadeBox); firstChild.innerText = firstChild.innerHTML; shadeBox.addEventListener('click', function () { this.style.display = 'none'; firstChild.innerHTML = firstChild.innerText.trim(); }, false); } else { this.shadeBox.style.display = 'block'; firstChild.innerText = firstChild.innerHTML; } }};export default { placeholder: '', // 主题 theme: 'snow', modules: { toolbar: { // 工具栏选项 container: toolOptions, // 事件重写 handlers: handlers } }, // 在使用的页面中初始化按钮样式 initButton: function () { // 样式随便改 const sourceEditorButton = document.querySelector('.ql-sourceEditor'); sourceEditorButton.style.cssText = 'font-size:18px'; // 加了elementui的icon sourceEditorButton.classList.add('el-icon-edit-outline'); // 鼠标放上去显示的提示文字 sourceEditorButton.title = '源码编辑'; }};工具名,工具方法名,类名:这里要注意的是:工具名和工具方法名是一样的,并且生成的button工具拥有ql-工具名的类名。例如上面代码中,我的工具名是sourceEditor,我的方法名也是sourceEditor,而生成的button工具的类名就是ql-sourceEditor了。 ...

June 24, 2019 · 4 min · jiezi

关于写作那些事之我该选择哪种格式

markdown和富文本不知道你是否留意过平时写作时的编辑器,有的是 markdown 编辑器,有的是各种富文本编辑器,到底选择哪一个相信你有自己的判断.如果只是在某一家平台上写作,哪一种编辑器都无所谓,只要你喜欢就好.可是如果你需要同时发布到各个平台呢?此时,真的需要停下来思考一下,我该使用哪一种编辑器了?各家的编辑器的界面设计风格迥然不同,不仅按钮排序顺序不一样,而且最终输出效果也不尽相同.这就给我们带来了一个问题,明明已经排好版的文章,复制到另外一家平台样式不一样了,或者格式被清除了?!心中一万只羊驼呼啸而过,尽管如此,还是在心里告诉自己要冷静,要冷静!既然我们追求的一处编写,到处复制,那么我就有必要郑重向你推荐 markdown 编辑器.简单地说,markdown 编辑器是一种标记语言,写的是源码,输出的是 html.所以很多情况下, markdown 更适合技术人员写文章,不用关心排版布局,回归写作本质,而富文本格式适合文学工作者,强调布局美观,重视审美体验.两者看似相互独立,实际上最终展示效果几乎太大差别, markdown 格式和富文本格式最终都输出 html格式,毕竟绝大多数阅读媒介还是各种浏览器.markdown 语法支持嵌套 html 语法,从而可以实现较为复杂的排版布局.markdown 格式如果使用的是 markdown 格式编写文章,首先需要记忆常用的基本语法,半个小时足够入门写博客了,比txt 高级,比 html 简洁,取代 word 地位!正是因为 markdown 语法规范,所以可以说是跨平台的写作语言,基本上各大主流的博客平台均支持 markdown 格式,保证了"一处编写,到处复制"的优良特性.值得注意的是,不同平台对 markdown 格式的渲染结果稍有差异,甚至语法支持度不同,这要求我们尽量写通用语法或者因地制宜有针对性编写文章.## markdown 二号标题- markdown 无序列表1- markdown 无序列表2- markdown 无序列表3markdown 加粗文字效果markdown 超链接文字markdown 快速入门富文本格式平常熟悉的 word 编辑器可以理解为一种富文本格式,布局,标题,超链接,图片等均以控件的形式展示,需要填写标题了点一下按钮,需要加粗效果再点一下按钮,效果直观,不需要二次渲染,但不同的平台自然是不同的布局.一家平台的布局还不一定能够完美复制到另一家平台,虽然适合大多数人,但可移植性差!如果需要同时发布到多家平台,简直不敢相信,复制不了样式,需要重新排版等问题绝对是一种折磨.小结markdown : 拥有编程经验,不关心排版布局,专注写作多家平台发表首选 markdown 编辑器,“一处编写,到处复制”,可移植性好,最值渲染效果也不错!富文本格式: 可视化书写文章,无需编程经验的话,首选富文本编辑器,调整鼠标就能搞定页面布局还是很轻松的,同步更新到多家平台时,页面布局格式差强人意,后期维护难度大!

April 15, 2019 · 1 min · jiezi

使用vue开发移动端管理后台

独立完成一个移动端项目(不是很明白为何会有这样的商品管理后台),还是有些经验不足,包括对产品的全局思考,对插件的选择等,都有考虑不周的缺点,导致自己中途想换图形界面插件,浪费了点时间,这里记录下,总结下经验,理一下思路。1.对于项目的一些心得与体会首先的一点,就是,对于图形界面框架的选型,这个很重要,对于一项目来说,开始动手前就要对项目的设计图有个完整的了解,以便于自己选择插件或者框架;然后就是,对于交互性操作,比如:上传图片,预览图片啥的,应该选择是否是用图形界面框架来实现还是另选专门的插件来实现在完成项目中,我又新学到了上传图片插件vue-core-image-upload,移动端富文本编辑器vue-quill-editor还有个地址的三级联动mt-picker,(是基于mint-ui图形界面框架的)2.rem与px的转换从同事传授中获到的经验,对于rem与px的转换,就是在index.html模板文件中加入下面的脚本,然后就是1rem=100px(这个可能不准确,有更好的方法,各位大佬请在评论中留下,感激不尽)<script type=“text/javascript”> document.getElementsByTagName(“html”)[0].style.fontSize = 100 / 750 * window.screen.width + “px”;</script>3.对于上传图片插件vue-core-image-upload中遇到的坑对于跨域问题,有好多方法可以解决,这里讲的挺多的前端跨域解决方法还有就是后台设置响应头access-control-allow-origin可以指定特定的域名,我这里的后台设置的就是access-control-allow-origin:,就是因为这样,用这个上传图片的插件就会报错Access to XMLHttpRequest at ‘https://….’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘’ when the request’s credentials mode is ‘include’. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.这个问题我蒙圈了好久,和后台也讲了,就是处于蒙圈状态,已经允许跨域了,怎么还报错呢?很烦然后,终于找了个方法解决(有用过其他的上传插件,感觉不好用,代码或者思路好乱)其实这个插件中的文档也有提示,只是刚用,还不是很会就是在使用这个插件的代码中加上这个字段就可以了<vue-core-image-upload class=“btn btn-primary” :crop=“false” input-of-file=“file” @imageuploaded=“loadMainImg” :max-file-size=“5242880” :url=“serverUrl” :credentials=“false” //允许携带cookie></vue-core-image-upload>对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“”。这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“”,请求将会失败。也就是说Access-Control-Allow-Credentials设置为true的情况下Access-Control-Allow-Origin不能设置为*4.基于mint-ui的三级地址选择效果图template文件<div class=“modal” @click=“handleCloseAddress”> <div class=“cateContainer” @click.stop> <div class=“operateBtn”> <div class=“cancelBtn” @click=“handleCloseAddress”>取消</div> <div class=“confirmBtn” @click=“handleCloseAddress”>确定</div> </div> <mt-picker class=“addressPicker” :slots=“myAddressSlots” @change=“onAddressChange”></mt-picker> </div></div>js文件// 定义一个包含中国省市区信息的json文件import addressJson from ‘@/assets/common/address’export default { data() { return { myAddressSlots: [ { flex: 1, values: Object.keys(addressJson), className: ‘slot1’, textAlign: ‘center’ }, { divider: true, content: ‘-’, className: ‘slot2’ }, { flex: 1, values: [‘市辖区’], className: ‘slot3’, textAlign: ‘center’ }, { divider: true, content: ‘-’, className: ‘slot4’ }, { flex: 1, values: [‘东城区’], className: ‘slot5’, textAlign: ‘center’ } ], province:‘省’, city:‘市’, county:‘区/县’, } }, methods: { onAddressChange(picker, values) { if(addressJson[values[0]]) { picker.setSlotValues(1, Object.keys(addressJson[values[0]])); picker.setSlotValues(2, addressJson[values[0]][values[1]]); this.province = values[0]; this.city = values[1]; this.county = values[2]; } }, }}5.关于对是否登录的处理开始也有做过登录的管理后台,不过,在进行登录时,总会一闪过登录的界面,这种感觉很不好,在这里记录下相比之前更好点的方法在main.js文件中添加对router的钩子函数router.beforeEach((to, from, next) => { let token = localStorage.getItem(’token’); if (!token && to.path !== ‘/login’) { next(’/login’); } else { next(); }});通过判断缓存里是否有token来进行路由的跳转相对于之前的那种方法,这里对路由的跳转进行的拦截,在路由进行跳转前,进行判断6.上拉加载mescroll.js插件这里对于分页加载第二页使用的上拉加载的插件还是用了原来的插件,还是感觉挺好用的这里有讲述上拉加载,下拉刷新,滚动无限加载7.移动端富文本插件Vue-Quill-Editor效果图这里有相关案例代码vue-quill-editor<template> <quill-editor v-model=“richTextContent” ref=“myQuillEditor” :options=“editorOption” @change=“onEditorChange($event)"> </quill-editor></template><script> import { quillEditor } from “vue-quill-editor”; import ‘quill/dist/quill.core.css’; import ‘quill/dist/quill.snow.css’; import ‘quill/dist/quill.bubble.css’; export default{ data() { return {} }, methods: { onEditorChange(e) {} } }</script>响应事件onEditorChange(e){ console.log(e) this.richTextContent = e.html;},8.移动端图片预览插件vue-picture-preview<img :src=“url” v-preview=“url” preview-nav-enable=“false” />需要在app.vue中加入如下代码<lg-preview></lg-preview>效果图代码挺少的9.总结在以后的项目中,首先的一件事就是要对产品要有完成的了解,然后进行技术、框架的选型对于插件,自己多尝试才能知道是否符合你的要求正在努力学习中,若对你的学习有帮助,留下你的印记呗(点个赞咯^_^)往期好文推荐:判断ios和Android及PC端webpack打包(有面试题)纯css实现瀑布流(multi-column多列及flex布局)实现单行及多行文字超出后加省略号 ...

March 7, 2019 · 2 min · jiezi