[关闭]
@Bios 2019-06-28T06:49:46.000000Z 字数 8326 阅读 1704

Vue + Element + vue-quill-editor 实现源码编辑、自定义图片上传和汉化

Vue ElementUi


集百家之长,看了很多博客再结合自身情况,写了这个小组件功能,仅供参考。

实现源码编辑

vue-quill-editor的配置文件:

  1. // toolbar工具栏的工具选项(默认展示全部)
  2. const toolOptions = [
  3. // 加粗 斜体 下划线 删除线
  4. ['bold', 'italic', 'underline', 'strike'],
  5. // 加粗 斜体 下划线 删除线
  6. ['blockquote', 'code-block'],
  7. // 1、2 级标题
  8. [{header: 1}, {header: 2}],
  9. // 有序、无序列表
  10. [{list: 'ordered'}, {list: 'bullet'}],
  11. // 上标/下标
  12. [{script: 'sub'}, {script: 'super'}],
  13. // 缩进
  14. [{indent: '-1'}, {indent: '+1'}],
  15. // 文本方向
  16. [{direction: 'rtl'}],
  17. // 字体大小
  18. [{size: ['small', false, 'large', 'huge']}],
  19. // 标题
  20. [{header: [1, 2, 3, 4, 5, 6, false]}],
  21. // 字体颜色、字体背景颜色
  22. [{color: []}, {background: []}],
  23. // 字体种类
  24. [{font: []}],
  25. // 对齐方式
  26. [{align: []}],
  27. [{clean: '源码编辑'}], // 这是自己加的
  28. // 链接、图片、视频
  29. ['link', 'image'],
  30. // 新添加的工具
  31. ['sourceEditor']
  32. ];
  33. const handlers = {
  34. shadeBox: null,
  35. // 添加工具方法
  36. sourceEditor: function () {
  37. // alert('我新添加的工具方法');
  38. const container = this.container;
  39. const firstChild = container.nextElementSibling.firstChild;
  40. // 在第一次点击源码编辑的时候,会在整个工具条上加一个div,层级比工具条高,再次点击工具条任意位置,就会退出源码编辑。可以在下面cssText里面加个背景颜色看看效果。
  41. if (!this.shadeBox) {
  42. let shadeBox = this.shadeBox = document.createElement('div');
  43. shadeBox.style.cssText = 'position:absolute; top:0; left:0; width:100%; height:100%; cursor:pointer';
  44. container.style.position = 'relative';
  45. container.appendChild(shadeBox);
  46. firstChild.innerText = firstChild.innerHTML;
  47. shadeBox.addEventListener('click', function () {
  48. this.style.display = 'none';
  49. firstChild.innerHTML = firstChild.innerText.trim();
  50. }, false);
  51. } else {
  52. this.shadeBox.style.display = 'block';
  53. firstChild.innerText = firstChild.innerHTML;
  54. }
  55. }
  56. };
  57. export default {
  58. placeholder: '',
  59. // 主题
  60. theme: 'snow',
  61. modules: {
  62. toolbar: {
  63. // 工具栏选项
  64. container: toolOptions,
  65. // 事件重写
  66. handlers: handlers
  67. }
  68. },
  69. // 在使用的页面中初始化按钮样式
  70. initButton: function () {
  71. // 样式随便改
  72. const sourceEditorButton = document.querySelector('.ql-sourceEditor');
  73. sourceEditorButton.style.cssText = 'font-size:18px';
  74. // 加了elementui的icon
  75. sourceEditorButton.classList.add('el-icon-edit-outline');
  76. // 鼠标放上去显示的提示文字
  77. sourceEditorButton.title = '源码编辑';
  78. }
  79. };

工具名,工具方法名,类名:
这里要注意的是:工具名和工具方法名是一样的,并且生成的button工具拥有ql-工具名的类名。
例如上面代码中,我的工具名是sourceEditor,我的方法名也是sourceEditor,而生成的button工具的类名就是ql-sourceEditor了。

自定义上传图片

vue-quill-editor自带的上传,是把图片变成了base64的格式,不符合一般的项目需求。我猜它是用的FileReader的API。

有兴趣的可以试试这个,拖拽图片转base64预览:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title></title>
  6. <style media="screen">
  7. #div1 {width:400px; height:300px; background:#CCC; border:1px solid black; text-align:center; line-height:300px;}
  8. </style>
  9. <script>
  10. window.onload=function (){
  11. let oDiv=document.getElementById('div1');
  12. let oImg=document.getElementById('img1');
  13. oDiv.addEventListener('dragenter', function (){
  14. oDiv.innerHTML='请松手';
  15. }, false);
  16. oDiv.addEventListener('dragleave', function (){
  17. oDiv.innerHTML='拖到这里上传';
  18. }, false);
  19. oDiv.addEventListener('dragover', function (ev){
  20. ev.preventDefault();
  21. }, false);
  22. oDiv.addEventListener('drop', function (ev){
  23. ev.preventDefault();
  24. //
  25. let oFile=ev.dataTransfer.files[0];
  26. //读取
  27. let reader=new FileReader();
  28. reader.readAsDataURL(oFile);
  29. reader.onload=function (){
  30. //alert('成功');
  31. oImg.src=this.result;
  32. };
  33. reader.onerror=function (){
  34. alert('读取失败了');
  35. };
  36. console.log(reader);
  37. }, false);
  38. }
  39. </script>
  40. </head>
  41. <body>
  42. <div id="div1">拖到这里上传</div>
  43. <img src="" id="img1">
  44. </body>
  45. </html>
  1. // 自定义vue-quill-editor的主要文件
  2. <template>
  3. <div
  4. v-loading="imageLoading"
  5. element-loading-text="请稍等,图片上传中"
  6. >
  7. <quill-editor
  8. ref="myTextEditor"
  9. v-model="content"
  10. :options="quillOption"
  11. @change="onEditorChange($event)"
  12. @focus="onEditorFocus($event)"
  13. @ready="onEditorReady($event)"
  14. >
  15. </quill-editor>
  16. // 这个是elementui的上传,把它display:none
  17. <el-upload
  18. style="display:none;"
  19. :class="name"
  20. :action="upload"
  21. :show-file-list="false"
  22. :on-success="handleAvatarSuccess"
  23. :before-upload="beforeAvatarUpload"
  24. :on-progress="onProgress"
  25. >
  26. </el-upload>
  27. </div>
  28. </template>
  29. <script>
  30. import {
  31. quillEditor
  32. } from 'vue-quill-editor';
  33. import {
  34. upload
  35. } from '@/api/upload_api.js';
  36. import Quill from 'quill';
  37. import quillConfig from './quill_config.js';
  38. import 'quill/dist/quill.core.css';
  39. import 'quill/dist/quill.snow.css';
  40. import 'quill/dist/quill.bubble.css';
  41. export default {
  42. props: {
  43. // 富文本内容
  44. value: String,
  45. // 富文本的名字 同一个页面的多个富文本name不能重复的
  46. name: String,
  47. // 图片类型
  48. imgType: {
  49. type: Array,
  50. default: () => ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/bmp']
  51. },
  52. // 图片限制大小 单位 M
  53. limitSize: {
  54. type: Number,
  55. default: 1
  56. }
  57. },
  58. components: {
  59. quillEditor
  60. },
  61. mounted() {
  62. //
  63. quillConfig.initButton();
  64. var vm = this;
  65. // 当点击图书上传是,触发elementui的upload点击
  66. var imgHandler = async function (image) {
  67. vm.addImgRange = vm.$refs.myTextEditor.quill.getSelection();
  68. if (image) {
  69. document.querySelector(`.${vm.name} input`).click();
  70. }
  71. };
  72. vm.$refs.myTextEditor.quill.getModule('toolbar').addHandler('image', imgHandler);
  73. },
  74. model: {
  75. props: 'content',
  76. // 必须的change事件不然是不会响应的
  77. event: 'change'
  78. },
  79. computed: {
  80. content: {
  81. get: function () {
  82. return this.value;
  83. },
  84. set: function () {}
  85. }
  86. },
  87. data() {
  88. return {
  89. // 配置文件
  90. quillOption: quillConfig,
  91. imageLoading: false,
  92. upload: upload(),
  93. };
  94. },
  95. methods: {
  96. onEditorChange(e) {
  97. this.$emit('change', e.html);
  98. },
  99. onEditorFocus(e) {
  100. this.$emit('focus', e);
  101. },
  102. onEditorReady(e) {
  103. this.$emit('ready', e);
  104. },
  105. // 图片上传成功
  106. handleAvatarSuccess(res) {
  107. // console.log(res, file);
  108. if (res.code == 200) {
  109. // this.form.custom_logo_url = res.data.url;
  110. let url = res.data.url,
  111. vm = this;
  112. if (url !== null && url.length > 0) {
  113. var value = url;
  114. // ***主要的东西就是这里:上传成功回显
  115. vm.addImgRange = vm.$refs.myTextEditor.quill.getSelection();
  116. value = value.indexOf('http') != -1 ? value : 'http:' + value;
  117. vm.$refs.myTextEditor.quill.insertEmbed(vm.addImgRange !== null ? vm.addImgRange.index : 0, 'image', value, Quill.sources.USER);
  118. } else {
  119. vm.$message.warning('图片增加失败');
  120. }
  121. } else {
  122. this.$message.error(res.message);
  123. }
  124. this.imageLoading = false;
  125. },
  126. // 图片上传前
  127. beforeAvatarUpload(file) {
  128. let date = new Date().getTime();
  129. let imgType = this.imgType;
  130. const fileType = file.type;
  131. const isLt1M = file.size / 1024 / 1024 < this.limitSize;
  132. const isAllowType = imgType.indexOf(fileType) != -1;
  133. // console.log(fileType);
  134. // const isPng = fileType == 'image/jpeg';
  135. if (!isAllowType) {
  136. this.$message.error('请上传符合文件格式的图片!');
  137. return false;
  138. }
  139. if (!isLt1M) {
  140. this.$message.error('上传logo图片大小不能超过 1MB!');
  141. return false;
  142. }
  143. return isAllowType && isLt1M;
  144. },
  145. onProgress() {
  146. this.imageLoading = true;
  147. }
  148. }
  149. };
  150. </script>
  1. // 使用方式
  2. <quill-editor v-model="form.content" name="policy"/>
  3. // 做了双向绑定的,也可以自己监听change事件

⚠️注意点:
1. name不能一样
2. 上传逻辑按自己的来,跟我应该不一样
3. 其实最重要的是回显到富文本中的那段代码,无论你怎么上传,甚至可以不用elementui的上传组件,最后拿到上传成功的url,再放进去就搞定了。

汉化

把这段代码放到你的页面中就行了。

  1. .ql-snow .ql-tooltip[data-mode=link]::before {
  2. content: "请输入链接地址:" !important;
  3. }
  4. .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  5. border-right: 0px;
  6. content: '保存' !important;
  7. padding-right: 0px;
  8. }
  9. .ql-snow .ql-tooltip[data-mode=video]::before {
  10. content: "请输入视频地址:" !important;
  11. }
  12. .ql-snow .ql-picker.ql-size .ql-picker-label::before,
  13. .ql-snow .ql-picker.ql-size .ql-picker-item::before {
  14. content: '14px' !important;
  15. }
  16. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
  17. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
  18. content: '10px' !important;
  19. }
  20. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
  21. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
  22. content: '18px' !important;
  23. }
  24. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
  25. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
  26. content: '32px' !important;
  27. }
  28. .ql-snow .ql-picker.ql-header .ql-picker-label::before,
  29. .ql-snow .ql-picker.ql-header .ql-picker-item::before {
  30. content: '文本' !important;
  31. }
  32. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
  33. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
  34. content: '标题1' !important;
  35. }
  36. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
  37. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
  38. content: '标题2' !important;
  39. }
  40. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
  41. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
  42. content: '标题3' !important;
  43. }
  44. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
  45. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
  46. content: '标题4' !important;
  47. }
  48. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
  49. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
  50. content: '标题5' !important;
  51. }
  52. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
  53. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  54. content: '标题6' !important;
  55. }
  56. .ql-snow .ql-picker.ql-font .ql-picker-label::before,
  57. .ql-snow .ql-picker.ql-font .ql-picker-item::before {
  58. content: '标准字体' !important;
  59. }
  60. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
  61. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
  62. content: '衬线字体' !important;
  63. }
  64. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
  65. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
  66. content: '等宽字体' !important;
  67. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注