• 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基本配置

    npm install vue-quill-editor -s

    main.js中引入

      import VueQuillEditor from 'vue-quill-editor'
      import 'quill/dist/quill.core.css'
      import 'quill/dist/quill.snow.css'
      import 'quill/dist/quill.bubble.css'
      Vue.use(VueQuillEditor);

    使用

     需要注意的是toolbar的配置

      1. 只需要填写功能名的    

     加粗 - bold;
    
         斜体 - italic
    
         下划线 - underline
    
         删除线 - strike
    
         引用- blockquote
    
         代码块 - code-block
    
         公式 - formula
    
         图片 - image
    
         视频 - video
    
         清除字体样式- clean
    
         这一类的引用 直接['name','name']这种格式就好了

      2. 需要有默认值的

     标题 - header 
    
      [{ 'header': 1 }, { 'header': 2 }] 值字体大小
     
      列表 - list
      [{ 'list': 'ordered'}, { 'list': 'bullet' }], 值ordered,bullet
     
      上标/下标 - script
      [{ 'script': 'sub'}, { 'script': 'super' }],  值sub,super
     
      缩进 - indent
      [{ 'indent': '-1'}, { 'indent': '+1' }], 值-1,+1等
     
      文本方向 - direction
      [{'direction':'rtl'}]

    结构 

    <template>
          <quill-editor class="editor"
                        ref="myTextEditor"
                        v-model="content"
                        :options="editorOption"
                        @blur="onEditorBlur($event)"
                        @focus="onEditorFocus($event)"
                        @ready="onEditorReady($event)"
                        @change="onEditorChange($event)">
          </quill-editor>
      </template>
      <script>
          export default {
            data () {
              return {
                content: null,
                editorOption: {
                  modules: {
                    toolbar: [
                      ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
                      ["blockquote", "code-block"], // 引用  代码块
                      [{ header: 1 }, { header: 2 }], // 1、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", "video"] // 链接、图片、视频
                    ], //工具菜单栏配置
                  },
                  placeholder: '请在这里添加产品描述', //提示
                  readyOnly: false, //是否只读
                  theme: 'snow', //主题 snow/bubble
                  syntax: true, //语法检测
                }
              }
            },
            methods: {
              // 失去焦点
              onEditorBlur(editor) {},
              // 获得焦点
              onEditorFocus(editor) {},
              // 开始
              onEditorReady(editor) {},
              // 值发生变化
              onEditorChange(editor) {
                this.content = editor.html;
                console.log(editor);
              },
            },
            computed: {
              editor() {
                return this.$refs.myTextEditor.quillEditor;
              }
            },
            mounted() {
              // console.log('this is my editor',this.editor);
            } 
          }
      </script>

    汉化

    汉化只需要更改toolbar工具栏中的样式即可实现

    <style>
      .editor {
        line-height: normal !important;
        height: 800px;
      }
      .ql-snow .ql-tooltip[data-mode=link]::before {
        content: "请输入链接地址:";
      }
      .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
          border-right: 0px;
          content: '保存';
          padding-right: 0px;
      }
     
      .ql-snow .ql-tooltip[data-mode=video]::before {
          content: "请输入视频地址:";
      }
     
      .ql-snow .ql-picker.ql-size .ql-picker-label::before,
      .ql-snow .ql-picker.ql-size .ql-picker-item::before {
        content: '14px';
      }
      .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
      .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
        content: '10px';
      }
      .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
      .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
        content: '18px';
      }
      .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
      .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
        content: '32px';
      }
     
      .ql-snow .ql-picker.ql-header .ql-picker-label::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item::before {
        content: '文本';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
        content: '标题1';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
        content: '标题2';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
        content: '标题3';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
        content: '标题4';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
        content: '标题5';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
        content: '标题6';
      }
     
      .ql-snow .ql-picker.ql-font .ql-picker-label::before,
      .ql-snow .ql-picker.ql-font .ql-picker-item::before {
        content: '标准字体';
      }
      .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
      .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
        content: '衬线字体';
      }
      .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
      .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
        content: '等宽字体';
      }
      </style>

    实现以上配置后就可以看到效果如图:

    以上就是vue-quill-editor的基本配置了。

    图片上传的配置

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

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

    这里借助element-ui的图片功能,因为其功能齐全,图片上传前,上传后,都有交互效果的处理,所以可以选择性使用

     editorOption: {
          modules: {
              toolbar: {
                  handlers: {
                      image: function(value) {
                          if (value) {
                              // 触发input框选择图片文件
                              document.querySelector(".avatar-uploader input").click();//自定义元素的点击事件
                          } else {
                              this.quill.format("image", false);
                          }
                      },
                          // link: function(value) {
                          //   if (value) {
                          //     var href = prompt('请输入url');
                          //     this.quill.format("link", href);
                          //   } else {
                          //     this.quill.format("link", false);
                          //   }
                          // },
                  }
              }
          }
      },

    而后在自定义的元素上写入点击事件,然后将该元素隐藏掉。点击quill-editor的图片上传时,实际点击了自定义的图片上传,而后在返回网络路径后将图片插入富文本编辑器即可。

    插入返回的网络图片路径(这里借助的是element-ui)

    uploadSuccess(res, file) {
            // res为图片服务器返回的数据
            // 获取富文本组件实例
            let quill = this.$refs.myQuillEditor.quill;
            // 如果上传成功
            if (res.code == 200) {
              // 获取光标所在位置
              let length = quill.getSelection().index;
              // 插入图片  res.url为服务器返回的图片地址
              quill.insertEmbed(length, "image", res.url);
              // 调整光标到最后
              quill.setSelection(length + 1);
            } else {
              this.$message.error("图片插入失败");
            }
            // loading动画消失
            this.quillUpdateImg = false;
          },

    以上就是主要思路及代码,如果还是不懂就看下面vue组件的源码(也可直接使用,前提是下载了element-ui)

    组件封装源码及引用

    <template>
          <div>
              <!-- 图片上传组件辅助-->
            <el-upload
              class="avatar-uploader"
              :action="serverUrl"
              name="file"
              :headers="header"
              :show-file-list="false"
              list-type="picture"
              :multiple="false"
              :on-success="uploadSuccess"
              :on-error="uploadError"
              :before-upload="beforeUpload">
            </el-upload>
     
            <quill-editor
            class="editor"
            v-model="content"
            ref="myQuillEditor"
            :options="editorOption"
            @blur="onEditorBlur($event)" @focus="onEditorFocus($event)"
            @change="onEditorChange($event)">
            </quill-editor>
          </div>
      </template>
      <script>
      // 工具栏配置
      const toolbarOptions = [
        ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
        ["blockquote", "code-block"], // 引用  代码块
        [{ header: 1 }, { header: 2 }], // 1、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", "video"] // 链接、图片、视频
      ];
     
      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 {
        props: {
          /*编辑器的内容*/
          value: {
            type: String
          },
          /*图片大小*/
          maxSize: {
            type: Number,
            default: 4000 //kb
          }
        },
     
        components: {
          quillEditor
        },
     
        data() {
          return {
            content: this.value,
            quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
            editorOption: {
              theme: "snow", // or 'bubble'
              placeholder: "您想说点什么?",
              modules: {
                toolbar: {
                  container: toolbarOptions,
                  // container: "#toolbar",
                  handlers: {
                    image: function(value) {
                      if (value) {
                        // 触发input框选择图片文件
                        document.querySelector(".avatar-uploader input").click();
                      } else {
                        this.quill.format("image", false);
                      }
                    },
                    // link: function(value) {
                    //   if (value) {
                    //     var href = prompt('请输入url');
                    //     this.quill.format("link", href);
                    //   } else {
                    //     this.quill.format("link", false);
                    //   }
                    // },
                  }
                }
              }
            },
            serverUrl: "https://testihospitalapi.ebaiyihui.com/oss/api/file/store/v1/saveFile", // 这里写你要上传的图片服务器地址
            header: {
              // token: sessionStorage.token
            } // 有的图片服务器要求请求头需要有token
          };
        },
     
        methods: {
          onEditorBlur() {
            //失去焦点事件
          },
          onEditorFocus() {
            //获得焦点事件
          },
          onEditorChange() {
            //内容改变事件
            this.$emit("input", this.content);
          },
     
          // 富文本图片上传前
          beforeUpload() {
            // 显示loading动画
            this.quillUpdateImg = true;
          },
     
          uploadSuccess(res, file) {
            // res为图片服务器返回的数据
            // 获取富文本组件实例
            let quill = this.$refs.myQuillEditor.quill;
            // 如果上传成功
            if (res.code == 200) {
              // 获取光标所在位置
              let length = quill.getSelection().index;
              // 插入图片  res.url为服务器返回的图片地址
              quill.insertEmbed(length, "image", res.result.url);
              // 调整光标到最后
              quill.setSelection(length + 1);
            } else {
              this.$message.error("图片插入失败");
            }
            // loading动画消失
            this.quillUpdateImg = false;
          },
          // 富文本图片上传失败
          uploadError() {
            // loading动画消失
            this.quillUpdateImg = false;
            this.$message.error("图片插入失败");
          }
        }
      };
      </script>
     
      <style>
      .editor {
        line-height: normal !important;
        height: 800px;
      }
      .ql-snow .ql-tooltip[data-mode=link]::before {
        content: "请输入链接地址:";
      }
      .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
          border-right: 0px;
          content: '保存';
          padding-right: 0px;
      }
     
      .ql-snow .ql-tooltip[data-mode=video]::before {
          content: "请输入视频地址:";
      }
     
      .ql-snow .ql-picker.ql-size .ql-picker-label::before,
      .ql-snow .ql-picker.ql-size .ql-picker-item::before {
        content: '14px';
      }
      .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
      .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
        content: '10px';
      }
      .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
      .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
        content: '18px';
      }
      .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
      .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
        content: '32px';
      }
     
      .ql-snow .ql-picker.ql-header .ql-picker-label::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item::before {
        content: '文本';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
        content: '标题1';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
        content: '标题2';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
        content: '标题3';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
        content: '标题4';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
        content: '标题5';
      }
      .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
      .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
        content: '标题6';
      }
     
      .ql-snow .ql-picker.ql-font .ql-picker-label::before,
      .ql-snow .ql-picker.ql-font .ql-picker-item::before {
        content: '标准字体';
      }
      .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
      .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
        content: '衬线字体';
      }
      .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
      .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
        content: '等宽字体';
      }
      </style>
    引入:
      <template>
        <Editor v-model="article.content"/>
      </template>
      <script>
      import Editor from './quillEditor'
      export default {
        components: {
          Editor
        },
        data() {
          return {
            article: {
              content: '',
            }
          }
        }
      }
      </script>
      <style>
     
      </style>

    VUE组件封装

    <template>
      <quill-editor
        class="editor"
        ref="myTextEditor"
        v-model="content"
        :options="editorOption"
        @change="onEditorChange($event)"
      ></quill-editor>
    </template>
    
    <script>
    const toolbar = [
      ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
      ["blockquote", "code-block"], // 引用  代码块
      [{ header: 1 }, { header: 2 }], // 1、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", "video"] // 链接、图片、视频
    ]; //工具菜单栏配置
    export default {
      data() {
        return {
          content: null, //内容
          editorOption: {
            modules: {
              toolbar
            },
            placeholder: "", //提示
            readyOnly: false, //是否只读
            theme: "snow", //主题 snow/bubble
            syntax: true //语法检测
          }
        };
      },
      methods: {
        // 值发生变化
        onEditorChange(editor) {
          this.content = editor.html;
          this.$emit("content", editor.html);
        }
      }
    };
    </script>
    
    <style>
    .editor {
      line-height: normal !important;
      height: 800px;
      margin-bottom: 30px;
    }
    .ql-snow .ql-tooltip[data-mode="link"]::before {
      content: "请输入链接地址:";
    }
    .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
      border-right: 0px;
      content: "保存";
      padding-right: 0px;
    }
    
    .ql-snow .ql-tooltip[data-mode="video"]::before {
      content: "请输入视频地址:";
    }
    
    .ql-snow .ql-picker.ql-size .ql-picker-label::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item::before {
      content: "14px";
    }
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
      content: "10px";
    }
    
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
      content: "18px";
    }
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
      content: "32px";
    }
    
    .ql-snow .ql-picker.ql-header .ql-picker-label::before,
    .ql-snow .ql-picker.ql-header .ql-picker-item::before {
      content: "文本";
    }
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
      content: "标题1";
    }
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
      content: "标题2";
    }
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
      content: "标题3";
    }
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
      content: "标题4";
    }
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
      content: "标题5";
    }
    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
      content: "标题6";
    }
    
    .ql-snow .ql-picker.ql-font .ql-picker-label::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item::before {
      content: "标准字体";
    }
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
      content: "衬线字体";
    }
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
      content: "等宽字体";
    }
    </style>

    还有一种方式是不借助element-ui来实现图片上传,这一过程无非就是图片上传OSS换取网络路径,这一块,咱们其实可以自定义图片上传组件。这里就不做阐述了,笔者的另一篇图片上传的组件文章(https://blog.csdn.net/my_new_way/article/details/105812646),大家在这里就可以使用起来。

  • 相关阅读:
    BootstrapBlazor 组件库介绍
    BootstrapBlazor 组件库使用体验---Table篇
    【转载】Bootstrap Blazor 组件介绍 Table (一)自动生成列功能介绍
    【转载】Bootstrap Blazor 组件介绍 Table (二)自定义模板列功能介绍
    【转载】Bootstrap Blazor 组件介绍 Table (三)列数据格式功能介绍
    使用acme.sh从Let's Encrypt申请SSL证书
    Docker一些基本操作
    Nginx配置https以及配置说明
    vi操作
    CentOS 7下安装Docker
  • 原文地址:https://www.cnblogs.com/wjlbk/p/12884661.html
Copyright © 2020-2023  润新知