vue富文本编辑器

阅读目录

Vue-Quill-Editor

主流富文本编辑器对比

前言:vue 中很多项目都需要用到富文本编辑器,在使用了 ueditor 和 tinymce 后,发现并不理想。所以果断使用 vue-quill-editor 来实现。
  1. wangEditor(国产,基于 javascript 和 css 开发的 web 富文本编辑器,开源免费)优势:轻量简介,最重要的是开源且中文文档齐全。缺点:更新不及时。没有强大的开发团队支撑。
  2. UEditor(百度)优势:插件多,基本曼度各种需求,由百度 web 前端研发部开发。缺点:插件提交较大,网页加载速度相对就慢了些。使用复杂。属于前后端不分离插件。在使用时需要配置后端的一些东西,使用不便。
  1. Kindeditor () 优势:文档齐全,为中文,阅读方便。缺点:图片上传存在问题,上传历史过多,会全部加载,导致浏览器卡顿。
  2. 补充:Tinymce 也是一款不错的富文本编辑器,种植,各有优势和劣势,关键是选择一款最适合的就好。因为笔者在开发 vue,所以直接使用 vue-quill-editor 较为方便些。具体看情况使用。

vue-quill-editor 基本配置

  1. npm install vue-quill-editor -s
回到顶部

main.js 中引入

  1. import VueQuillEditor from 'vue-quill-editor'
  2. import 'quill/dist/quill.core.css'
  3. import 'quill/dist/quill.snow.css'
  4. import 'quill/dist/quill.bubble.css'
  5. Vue.use(VueQuillEditor);
回到顶部

使用

  需要注意的是 toolbar 的配置
 
  1. 只需要填写功能名的
     加粗 - bold;
     斜体 - italic
     下划线 - underline
     删除线 - strike
     引用 - blockquote
     代码块 - code-block
     公式 - formula
     图片 - image
     视频 - video
     清除字体样式 - clean
     这一类的引用 直接 ['name','name'] 这种格式就好了
 
  2. 需要有默认值的
 
 
  1. 标题 - header
  2. [{ 'header': 1 }, { 'header': 2 }] 值字体大小
  3. 列表 - list
  4. [{ 'list': 'ordered'}, { 'list': 'bullet' }], orderedbullet
  5. 上标/ 下标 - script
  6. [{ 'script': 'sub'}, { 'script': 'super' }], subsuper
  7. 缩进 - indent
  8. [{ 'indent': '-1'}, { 'indent': '+1' }], 值 -1,+1
  9. 文本方向 - direction
  10. [{'direction':'rtl'}]
  
  1. <template>
  2. <quill-editor class="editor"
  3. ref="myTextEditor"
  4. v-model="content"
  5. :options="editorOption"
  6. @blur="onEditorBlur($event)"
  7. @focus="onEditorFocus($event)"
  8. @ready="onEditorReady($event)"
  9. @change="onEditorChange($event)">
  10. </quill-editor>
  11. </template>
  12. <script>
  13. export default {
  14. data () {
  15. return {
  16. content: null,
  17. editorOption: {
  18. modules: {
  19. toolbar: [
  20. ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
  21. ["blockquote", "code-block"], // 引用 代码块
  22. [{ header: 1 }, { header: 2 }], // 1、2 级标题
  23. [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
  24. [{ script: "sub" }, { script: "super" }], // 上标 / 下标
  25. [{ indent: "-1" }, { indent: "+1" }], // 缩进
  26. // [{'direction': 'rtl'}], // 文本方向
  27. [{ size: ["small", false, "large", "huge"] }], // 字体大小
  28. [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
  29. [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
  30. [{ font: [] }], // 字体种类
  31. [{ align: [] }], // 对齐方式
  32. ["clean"], // 清除文本格式
  33. ["link", "image", "video"] // 链接、图片、视频
  34. ], //工具菜单栏配置
  35. },
  36. placeholder: '请在这里添加产品描述', //提示
  37. readyOnly: false, //是否只读
  38. theme: 'snow', //主题 snow/bubble
  39. syntax: true, //语法检测
  40. }
  41. }
  42. },
  43. methods: {
  44. // 失去焦点
  45. onEditorBlur(editor) {},
  46. // 获得焦点
  47. onEditorFocus(editor) {},
  48. // 开始
  49. onEditorReady(editor) {},
  50. // 值发生变化
  51. onEditorChange(editor) {
  52. this.content = editor.html;
  53. console.log(editor);
  54. },
  55. },
  56. computed: {
  57. editor() {
  58. return this.$refs.myTextEditor.quillEditor;
  59. }
  60. },
  61. mounted() {
  62. // console.log('this is my editor',this.editor);
  63. }
  64. }
  65. </script>
汉化
汉化只需要更改 toolbar 工具栏中的样式即可实现
  
  1. <style>
  2. .editor {
  3. line-height: normal !important;
  4. height: 800px;
  5. }
  6. .ql-snow .ql-tooltip[data-mode=link]::before {
  7. content: "请输入链接地址:";
  8. }
  9. .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  10. border-right: 0px;
  11. content: '保存';
  12. padding-right: 0px;
  13. }
  14. .ql-snow .ql-tooltip[data-mode=video]::before {
  15. content: "请输入视频地址:";
  16. }
  17. .ql-snow .ql-picker.ql-size .ql-picker-label::before,
  18. .ql-snow .ql-picker.ql-size .ql-picker-item::before {
  19. content: '14px';
  20. }
  21. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
  22. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
  23. content: '10px';
  24. }
  25. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
  26. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
  27. content: '18px';
  28. }
  29. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
  30. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
  31. content: '32px';
  32. }
  33. .ql-snow .ql-picker.ql-header .ql-picker-label::before,
  34. .ql-snow .ql-picker.ql-header .ql-picker-item::before {
  35. content: '文本';
  36. }
  37. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
  38. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
  39. content: '标题 1';
  40. }
  41. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
  42. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
  43. content: '标题 2';
  44. }
  45. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
  46. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
  47. content: '标题 3';
  48. }
  49. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
  50. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
  51. content: '标题 4';
  52. }
  53. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
  54. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
  55. content: '标题 5';
  56. }
  57. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
  58. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  59. content: '标题 6';
  60. }
  61. .ql-snow .ql-picker.ql-font .ql-picker-label::before,
  62. .ql-snow .ql-picker.ql-font .ql-picker-item::before {
  63. content: '标准字体';
  64. }
  65. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
  66. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
  67. content: '衬线字体';
  68. }
  69. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
  70. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
  71. content: '等宽字体';
  72. }
  73. </style>
实现以上配置后就可以看到效果如图:
以上就是 vue-quill-editor 的基本配置了。

图片上传的配置

因为图片这块,大多情况下,我们都不需要 base64 格式的,所以我们需要将图片通过 OSS 换取网络路径然后发送给后端和回显。
回到顶部

更换 quill-editor 的点击事件为自定义事件

这里借助 element-ui 的图片功能,因为其功能齐全,图片上传前,上传后,都有交互效果的处理,所以可以选择性使用
 
  1. editorOption: {
  2. modules: {
  3. toolbar: {
  4. handlers: {
  5. image: function(value) {
  6. if (value) {
  7. // 触发 input 框选择图片文件
  8. document.querySelector(".avatar-uploader input").click();//自定义元素的点击事件
  9. } else {
  10. this.quill.format("image", false);
  11. }
  12. },
  13. // link: function(value) {
  14. // if (value) {
  15. // var href = prompt('请输入 url');
  16. // this.quill.format("link", href);
  17. // } else {
  18. // this.quill.format("link", false);
  19. // }
  20. // },
  21. }
  22. }
  23. }
  24. },
而后在自定义的元素上写入点击事件,然后将该元素隐藏掉。点击 quill-editor 的图片上传时,实际点击了自定义的图片上传,而后在返回网络路径后将图片插入富文本编辑器即可。
插入返回的网络图片路径 (这里借助的是 element-ui)
 
  1. uploadSuccess(res, file) {
  2. // res 为图片服务器返回的数据
  3. // 获取富文本组件实例
  4. let quill = this.$refs.myQuillEditor.quill;
  5. // 如果上传成功
  6. if (res.code == 200) {
  7. // 获取光标所在位置
  8. let length = quill.getSelection().index;
  9. // 插入图片 res.url 为服务器返回的图片地址
  10. quill.insertEmbed(length, "image", res.url);
  11. // 调整光标到最后
  12. quill.setSelection(length + 1);
  13. } else {
  14. this.$message.error("图片插入失败");
  15. }
  16. // loading 动画消失
  17. this.quillUpdateImg = false;
  18. },
以上就是主要思路及代码,如果还是不懂就看下面 vue 组件的源码(也可直接使用,前提是下载了 element-ui)

组件封装源码及引用

 
  1. <template>
  2. <div>
  3. <!-- 图片上传组件辅助-->
  4. <el-upload
  5. class="avatar-uploader"
  6. :action="serverUrl"
  7. name="file"
  8. :headers="header"
  9. :show-file-list="false"
  10. list-type="picture"
  11. :multiple="false"
  12. :on-success="uploadSuccess"
  13. :on-error="uploadError"
  14. :before-upload="beforeUpload">
  15. </el-upload>
  16. <quill-editor
  17. class="editor"
  18. v-model="content"
  19. ref="myQuillEditor"
  20. :options="editorOption"
  21. @blur="onEditorBlur($event)" @focus="onEditorFocus($event)"
  22. @change="onEditorChange($event)">
  23. </quill-editor>
  24. </div>
  25. </template>
  26. <script>
  27. // 工具栏配置
  28. const toolbarOptions = [
  29. ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
  30. ["blockquote", "code-block"], // 引用 代码块
  31. [{ header: 1 }, { header: 2 }], // 1、2 级标题
  32. [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
  33. [{ script: "sub" }, { script: "super" }], // 上标 / 下标
  34. [{ indent: "-1" }, { indent: "+1" }], // 缩进
  35. // [{'direction': 'rtl'}], // 文本方向
  36. [{ size: ["small", false, "large", "huge"] }], // 字体大小
  37. [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
  38. [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
  39. [{ font: [] }], // 字体种类
  40. [{ align: [] }], // 对齐方式
  41. ["clean"], // 清除文本格式
  42. ["link", "image", "video"] // 链接、图片、视频
  43. ];
  44. import { quillEditor } from "vue-quill-editor";
  45. import "quill/dist/quill.core.css";
  46. import "quill/dist/quill.snow.css";
  47. import "quill/dist/quill.bubble.css";
  48. export default {
  49. props: {
  50. /*编辑器的内容*/
  51. value: {
  52. type: String
  53. },
  54. /*图片大小*/
  55. maxSize: {
  56. type: Number,
  57. default: 4000 //kb
  58. }
  59. },
  60. components: {
  61. quillEditor
  62. },
  63. data() {
  64. return {
  65. content: this.value,
  66. quillUpdateImg: false, // 根据图片上传状态来确定是否显示 loading 动画,刚开始是 false, 不显示
  67. editorOption: {
  68. theme: "snow", // or 'bubble'
  69. placeholder: "您想说点什么?",
  70. modules: {
  71. toolbar: {
  72. container: toolbarOptions,
  73. // container: "#toolbar",
  74. handlers: {
  75. image: function(value) {
  76. if (value) {
  77. // 触发 input 框选择图片文件
  78. document.querySelector(".avatar-uploader input").click();
  79. } else {
  80. this.quill.format("image", false);
  81. }
  82. },
  83. // link: function(value) {
  84. // if (value) {
  85. // var href = prompt('请输入 url');
  86. // this.quill.format("link", href);
  87. // } else {
  88. // this.quill.format("link", false);
  89. // }
  90. // },
  91. }
  92. }
  93. }
  94. },
  95. serverUrl: "https://testihospitalapi.ebaiyihui.com/oss/api/file/store/v1/saveFile", // 这里写你要上传的图片服务器地址
  96. header: {
  97. // token: sessionStorage.token
  98. } // 有的图片服务器要求请求头需要有 token
  99. };
  100. },
  101. methods: {
  102. onEditorBlur() {
  103. //失去焦点事件
  104. },
  105. onEditorFocus() {
  106. //获得焦点事件
  107. },
  108. onEditorChange() {
  109. //内容改变事件
  110. this.$emit("input", this.content);
  111. },
  112. // 富文本图片上传前
  113. beforeUpload() {
  114. // 显示 loading 动画
  115. this.quillUpdateImg = true;
  116. },
  117. uploadSuccess(res, file) {
  118. // res 为图片服务器返回的数据
  119. // 获取富文本组件实例
  120. let quill = this.$refs.myQuillEditor.quill;
  121. // 如果上传成功
  122. if (res.code == 200) {
  123. // 获取光标所在位置
  124. let length = quill.getSelection().index;
  125. // 插入图片 res.url 为服务器返回的图片地址
  126. quill.insertEmbed(length, "image", res.result.url);
  127. // 调整光标到最后
  128. quill.setSelection(length + 1);
  129. } else {
  130. this.$message.error("图片插入失败");
  131. }
  132. // loading 动画消失
  133. this.quillUpdateImg = false;
  134. },
  135. // 富文本图片上传失败
  136. uploadError() {
  137. // loading 动画消失
  138. this.quillUpdateImg = false;
  139. this.$message.error("图片插入失败");
  140. }
  141. }
  142. };
  143. </script>
  144. <style>
  145. .editor {
  146. line-height: normal !important;
  147. height: 800px;
  148. }
  149. .ql-snow .ql-tooltip[data-mode=link]::before {
  150. content: "请输入链接地址:";
  151. }
  152. .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  153. border-right: 0px;
  154. content: '保存';
  155. padding-right: 0px;
  156. }
  157. .ql-snow .ql-tooltip[data-mode=video]::before {
  158. content: "请输入视频地址:";
  159. }
  160. .ql-snow .ql-picker.ql-size .ql-picker-label::before,
  161. .ql-snow .ql-picker.ql-size .ql-picker-item::before {
  162. content: '14px';
  163. }
  164. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
  165. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
  166. content: '10px';
  167. }
  168. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
  169. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
  170. content: '18px';
  171. }
  172. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
  173. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
  174. content: '32px';
  175. }
  176. .ql-snow .ql-picker.ql-header .ql-picker-label::before,
  177. .ql-snow .ql-picker.ql-header .ql-picker-item::before {
  178. content: '文本';
  179. }
  180. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
  181. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
  182. content: '标题 1';
  183. }
  184. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
  185. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
  186. content: '标题 2';
  187. }
  188. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
  189. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
  190. content: '标题 3';
  191. }
  192. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
  193. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
  194. content: '标题 4';
  195. }
  196. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
  197. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
  198. content: '标题 5';
  199. }
  200. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
  201. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  202. content: '标题 6';
  203. }
  204. .ql-snow .ql-picker.ql-font .ql-picker-label::before,
  205. .ql-snow .ql-picker.ql-font .ql-picker-item::before {
  206. content: '标准字体';
  207. }
  208. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
  209. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
  210. content: '衬线字体';
  211. }
  212. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
  213. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
  214. content: '等宽字体';
  215. }
  216. </style>
  217. 引入:
  218. <template>
  219. <Editor v-model="article.content"/>
  220. </template>
  221. <script>
  222. import Editor from './quillEditor'
  223. export default {
  224. components: {
  225. Editor
  226. },
  227. data() {
  228. return {
  229. article: {
  230. content: '',
  231. }
  232. }
  233. }
  234. }
  235. </script>
  236. <style>
  237. </style>
还有一种方式是不借助 element-ui 来实现图片上传,这一过程无非就是图片上传 OSS 换取网络路径,这一块,咱们其实可以自定义图片上传组件。这里就不做阐述了,笔者的另一篇图片上传的组件文章 (https://www.cnblogs.com/bgwhite/p/10123065.html),大家在这里就可以使用起来。
🤙