Wangeditor的基本实现

附官网地址:主题 | wangEditor

代码实例:直接参考官网写得很清楚主题 | wangEditor

<template>
    <div class="border">
        <Toolbar
            class="border"
            :editor="editorRef"
            :defaultConfig="toolbarConfig"
            mode="default"
        />
        <Editor
            style="height: 300px;"
            v-model="valueHtml"
            :defaultConfig="editorConfig"
            mode="default"
            @onCreated="handleCreated"
            @customAlert="customAlert"
            @customPaste="customPaste"
        />
   </div>
</template>

<script setup>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 内容 HTML
const valueHtml = ref('hello world')

const editorConfig = {
  placeholder: "请输入...",
  MENU_CONF: {
    //文字颜色
    color: {
      colors: ["#000", "#333", "#fff"],
    },
    //背景颜色
    bgColor: {
      colors: ["#000", "#333", "#fff"],
    },
    //字号
    //行高
    //表情
    //.....前往官网查看更多
  },
};

// 修改 uploadImage 菜单配置
editorConfig.MENU_CONF['uploadImage'] = {
  server: '/api',
  // form-data fieldName ,默认值 'wangeditor-uploaded-image'
  fieldName: 'fieldName',
  // 单个文件的最大体积限制,默认为 2M
  maxFileSize: 1 * 1024 * 1024, // 1M
  // 最多可上传几个文件,默认为 100
  maxNumberOfFiles: 10,
  // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
  allowedFileTypes: ['image/*'],
  // ......
  //【注意】不需要修改的不用写,wangEditor 会去 merge 当前其他配置
}

const toolbarConfig = {
  // toolbar 配置
  excludeKeys: [
    // "headerSelect",// 排除菜单组,写菜单组 key 的值即可
    "blockquote",
    "bold",
    "underline",
    "italic",
    "group-more-style", 
    "lineHeight",
    "bgColor",
  ],
};
const handleCreated = (editor) => {
  editorRef.value = editor;
};
const customAlert = (info, type) => {
  alert(`【自定义提示】${type} - ${info}`)
}
const customPaste = (editor, event, callback) => {
  console.log('ClipboardEvent 粘贴事件对象', event)
  // const html = event.clipboardData.getData('text/html') // 获取粘贴的 html
  const text = event.clipboardData.getData('text/plain') // 获取粘贴的纯文本
  // const rtf = event.clipboardData.getData('text/rtf') // 获取 rtf 数据(如从 word wsp 复制粘贴)

  // 自定义插入内容
  editor.insertText(text)

  // 返回 false ,阻止默认粘贴行为
  event.preventDefault()
  callback(false) // 返回值(注意,vue 事件的返回值,不能用 return)

  // 返回 true ,继续默认的粘贴行为
  // callback(true)
}

</script>

<style lang="less" scoped>
.border{
  border: 1px solid #ccc;
}
</style>

Quilleditor的基本使用

 附官网地址:Configuration - Quill Rich Text Editor

代码实例:


<template>
  <div>
    <!-- 图片上传组件辅助-->
    <el-upload
      id="upload"
      class="editor-upload"
      :action="uploadUrl"
      :data="uploadData"
      name="file"
      :headers="headers"
      :show-file-list="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)"
      @ready="ready($event)"
    >
    </quill-editor>
  </div>
</template>

<script>
  //引入Qill插件
  import Quill from "quill";

  // 自定义字体
  let fontFamily = ['宋体', '黑体', '微软雅黑', '楷体', '仿宋', 'Arial', '苹方'];
  Quill.imports['attributors/style/font'].whitelist = fontFamily;
  Quill.register(Quill.imports['attributors/style/font']);

  // 自定义文字大小
  let fontSize = ['10px', '12px', '14px', '16px', '20px', '24px', '36px']
  Quill.imports['attributors/style/size'].whitelist = fontSize;
  Quill.register(Quill.imports['attributors/style/size']);

  // 新增行高
  let Parchment = Quill.import("parchment");
  let lingHeight = ['initial', '1', '1.5', '1.75', '2', '3', '4'];
  class lineHeightAttributor extends Parchment.Attributor.Style {}
  const lineHeightStyle = new lineHeightAttributor("lineHeight", "line-height", {
    scope: Parchment.Scope.INLINE,
    whitelist: lingHeight
  });
  Quill.register({ "formats/lineHeight": lineHeightStyle }, true);

  // 对齐方式样式都改成style方式,而不是class
  let Align = Quill.import('attributors/style/align');
  Align.whitelist = ['right', 'center', 'justify'];
  Quill.register(Align, true);

  const toolbarOptions = [
    [
      "bold",
      "italic",
      "underline",
      "strike",
      "blockquote",
      "code-block",
      { header: 1 },
      { header: 2 },
      { list: "ordered" },
      { list: "bullet" },
      { indent: "-1" },
      { indent: "+1" },
      { script: "sub" }, // 下标
      { script: "super" }, // 上标
      { align: [] },
      { color: [] },
      { background: [] },
      "link",
      "image",
    ],
    [{ size: fontSize }], // 文字大小
    [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
    [{ font: fontFamily }], // 字体
    [{ lineheight: lingHeight }]   // 行高
  ];
  export default {
    name: "Editor",
    data() {
      return {
        content: null,
        uploadUrl: 'http://',  // 上传图片接口地址
        headers: { Authorization: "Bearer " + "TOKEN" },
        editorOption: {
          modules: {
            toolbar: {
              container: toolbarOptions,
              handlers: {
                // 编辑器菜单点击图片上传事件
                image: function (value) {
                  console.log(value)
                  if (value) {
                    // 调用element上传组件
                    document.querySelector("#upload input").click();
                  }
                },
                lineheight: function (value) {
                  if (value) {
                    this.quill.format('lineHeight', value);
                  } else {
                    console.log(value);
                  }
                }
              },
            },
          },
        },
      };
    },
    computed: {
      // 图片上传额外的参数
      uploadData() {
        return { type: 1, id: '2' };
      },
    },
    methods: {
      // 失去焦点事件
      onEditorBlur() {
        this.$emit("on-blur", this.content);
      },
      onEditorFocus() {
        //获得焦点事件
      },
      // 内容改变事件
      onEditorChange() {      
        this.$emit("on-blur", this.content);
      },
      ready() {
        Quill.register({ 'formats/lineHeight': lineHeightStyle }, true);
      },
      // 上传图片前
      beforeUpload(file) {
        const isLt2M = file.size / 1024 / 1024 < 2;
        if (!isLt2M) {
          this.$message.error("上传图片不能大于 2M");
        }
      },
      // 上传图片成功
      uploadSuccess(res) {
        if (res.code == 200) {
          let img = `<img src="https:${res.data.url}" />`;
          this.content = (this.content || "") + img;
        }
      },
      // 上传图片失败
      uploadError(res) {
        console.log(res);
      },
      // 更新文本编辑器内容
      upDateContent(v) {
        this.content = v;
      },
    },
  };
</script>

<style lang="scss" scoped>
  ::v-deep .quill-editor{
    width: 1000px;
    .ql-container {
      height: 300px;
    }
    .editor-upload {
      display: none;
    }
    /*
      文字大小
    */
    .ql-snow .ql-picker.ql-size{
      width: 70px;  // 菜单栏占比宽度
    }
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='10px']::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='10px']::before {
      content: '10px';
    }
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='12px']::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='12px']::before {
      content: '12px';
    }
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='14px']::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='14px']::before {
      content: '14px';
    }
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='16px']::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='16px']::before {
      content: '16px';
    }
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='20px']::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='20px']::before {
      content: '20px';
    }
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='24px']::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='24px']::before {
      content: '24px';
    }
    .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='36px']::before,
    .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='36px']::before {
      content: '36px';
    }

    /*
      字体
    */
    .ql-snow .ql-picker.ql-font{
      width: 80px;  // 菜单栏占比宽度
    }
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='宋体']::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='宋体']::before {
      content: '宋体';
    }
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='黑体']::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='黑体']::before {
      content: '黑体';
    }
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='微软雅黑']::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='微软雅黑']::before {
      content: '微软雅黑';
    }
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='楷体']::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='楷体']::before {
      content: '楷体';
    }
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='仿宋']::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='仿宋']::before {
      content: '仿宋';
    }
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='Arial']::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='Arial']::before {
      content: 'Arial';
    }
    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='苹方']::before,
    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='苹方']::before {
      content: '苹方';
    }

    /*
      标题  
    */
    .ql-snow .ql-picker.ql-header{
      width: 80px;  // 菜单栏占比宽度
    }
    .ql-snow .ql-picker.ql-header .ql-picker-label::before,
    .ql-snow .ql-picker.ql-header .ql-picker-item::before {
      content: "文本" !important;
    }
    .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" !important;
    }
    .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" !important;
    }
    .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" !important;
    }
    .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" !important;
    }
    .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" !important;
    }
    .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" !important;
    }
    //配置编辑器行高
    .ql-snow .ql-picker.ql-lineheight {
      width: 70px;  // 菜单栏占比宽度
    }
    .ql-snow .ql-picker.ql-lineheight .ql-picker-label::before {
      content: '行高';
    }
    .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='initial']::before {
      content: '默认';
    }
    .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1']::before {
      content: '1';
    }
    .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.5']::before {
      content: '1.5';
    }
    .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.75']::before {
      content: '1.75';
    }
    .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='2']::before {
      content: '2';
    }
    .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='3']::before {
      content: '3';
    }
    .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='4']::before {
      content: '4';
    }
    .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='5']::before {
      content: '5';
    }
  }
</style>

注意:Quill使用过程中有很多次碰壁,其中注册自定义菜单以及css的处理一度饱受折磨,我这边目前的实现依然存在一些交互和样式的问题,需注意一哈。参考了大神的文档链接放这里啦quill富文本编辑器自定义字体、文字大小(编辑器内含element上传组件)_quil编辑器 自定义字体-CSDN博客

总结:

总体使用起来个人还是更喜欢Wangeditor,官网写得十分清晰易懂,生命周期使用起来很方便,执行流畅度也有处理。

官方回答的差异如下【已C过来】:

WangEditor 和 Quill 都是流行的富文本编辑器,它们各自有不同的特点和适用场景。下面是它们的比较:

1. 易用性

  • WangEditor: 是一款轻量级的富文本编辑器,界面简洁,安装和使用都非常简单。它的目标是提供一个易于集成的编辑器,适合需要快速搭建编辑功能的项目。WangEditor 支持简单的工具栏配置,易于定制化。
  • Quill: 相比之下,Quill 更加强大和复杂,功能丰富。它的文档和 API 非常完善,适合需要高度定制和扩展的项目。Quill 的学习曲线略高,但提供了更强的灵活性。

2. 功能

  • WangEditor:
    • 提供常用的富文本功能,如加粗、斜体、标题、插入图片、链接、视频等。
    • 支持 Markdown 语法导入和导出。
    • 可以通过插件扩展一些额外功能。
    • 支持上传图片、文件,支持自定义图片上传方式。
  • Quill:
    • 功能更强大,支持更细粒度的文本格式化。
    • 内建对表格、代码块、无序列表、有序列表等更多功能的支持。
    • 支持完整的模块化设计,可以扩展和自定义工具栏、内容格式、图片上传等。
    • 丰富的 API 支持,允许开发者进行更复杂的编辑需求。

3. 性能

  • WangEditor: 因为它比较轻量,所以性能表现较好,适用于要求不高但需要快速开发的场景。它适合对性能要求较高、用户量不大且功能需求较简单的应用。
  • Quill: 性能方面较为稳健,但由于它的功能更复杂,因此在一些高频操作或大型文档的情况下,可能会出现性能问题。然而,Quill 依然可以处理较复杂的文档和富文本格式。

4. 定制化与扩展性

  • WangEditor:
    • 对于大多数常规的编辑需求,WangEditor 提供了很多现成的功能,可以快速集成到项目中。
    • 但在一些高级自定义需求方面,可能需要自己开发一些插件。
  • Quill:
    • 提供了更强的定制化功能,几乎所有的内容和行为都可以通过 API 或自定义模块进行扩展。
    • 由于其开放性和模块化设计,开发者可以非常灵活地实现复杂的需求。

5. 社区与文档

  • WangEditor:
    • 文档清晰易懂,社区活跃。提供了较为完善的中文文档,并且比较容易上手。
    • 社区问题解答速度较快,但相对 Quill 来说规模较小。
  • Quill:
    • Quill 的社区相对更大,文档相对也更详细。社区支持广泛,开发者可以快速获取解决方案。
    • 由于它是由专门的团队维护,文档和更新频繁,支持更为专业。

6. 集成与支持

  • WangEditor:
    • 集成非常简单,特别适合前端快速开发应用。
    • 支持 Vue、React 等现代前端框架,且集成方式相对简便。
  • Quill:
    • Quill 也提供了对各大前端框架的支持,但集成过程可能比 WangEditor 更复杂一些,尤其是需要做高级定制时。

7. 浏览器兼容性

  • WangEditor: 支持现代浏览器和 IE11+。
  • Quill: 支持主流现代浏览器,并且提供了对移动端的支持。

总结:

  • WangEditor 更适合需要轻量级、快速集成的项目,功能简单且易用,适合一般的富文本编辑需求。
  • Quill 更适合功能复杂、需要高度定制化的项目,尤其适合开发者有一定开发需求,想要更多控制编辑器行为的场景。

选择哪个取决于你的项目需求。如果需要快速集成且功能不复杂,WangEditor 是一个不错的选择;如果你需要更强大的定制功能,或者你的项目中有复杂的富文本编辑需求,Quill 可能是更合适的选择。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐