富文本编辑器--Quill 移动端使用总结(vue环境)

1,151 阅读1分钟

1. 依赖安装

npm i vue-quill-editor --save

2. 基础使用 --vue

  1. template 部分
<template>
  <!-- bidirectional data binding(双向数据绑定) -->
  <!-- <quill-editor
    v-model="content"
    ref="myQuillEditor"
    :options="editorOption"
    @blur="onEditorBlur($event)"
    @focus="onEditorFocus($event)"
    @ready="onEditorReady($event)"
  >
  </quill-editor> -->

  <!-- Or manually control the data synchronization(或手动控制数据流) -->
  <quill-editor
    ref="myQuillEditor"
    :content="eValue"
    :options="editorOption"
    @change="onEditorChange($event)"
  ></quill-editor>
</template>
  1. script 部分
<script>
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';
import { quillEditor } from 'vue-quill-editor';
import * as Quill from 'quill';
import { XDialog, TransferDom } from 'vux';
let quillAlign = Quill.import('attributors/style/align');
Quill.register(quillAlign, true);

export default {
  name: 'dtQuill',
  directives: {
    TransferDom,
  },
  components: {
    quillEditor,
    XDialog,
    TransferDom,
  },
  model: {
    prop: 'eValue',
    event: 'change',
  },
  props: {
    eValue: {
      type: String,
      default: '',
    },
    placeholder: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      editorOption: {
        modules: {
          toolbar: [
            ['bold', 'italic', 'underline'],
            [{ align: [] }],
            [{ align: '' }],
            [{ align: 'center' }],
            [{ align: 'right' }],
            [{ list: 'bullet' }, { list: 'ordered' }],
            [{ align: 'left' }],
          ],
        },
      },
      textLength: 0,
    };
  },
  mounted() {
    console.log('this is current quill instance object', this.editor);
  },
  mounted() {
    this.editor = null;
    this.$set(this.editorOption, 'placeholder', this.placeholder || '请输入模板内容');
    setTimeout(() => {
      this.editor = this.$refs.myQuillEditor.quill;
    }, 100);
  },
  // manually control the data synchronization
  // 如果需要手动控制数据同步,父组件需要显式地处理changed事件
  methods: {
    onEditorChange({ quill, html, text }) {
      if (!this.editor) {
        this.editor = quill;
      }
      this.$emit('change', html);
    },
  },
};
</script>
  1. 样式重置
.ql-container.ql-snow {
    border: none;
}
#custom-button {
    img {
        width: 100%;
        height: 100%;
    }
}

3. 自定义工具栏

  1. template 部分

<div id="editor-toolbar">
  <!-- Add buttons as you would before -->
  <button class="ql-bold"></button>
  <button class="ql-italic"></button>
  <button class="ql-underline"></button>
  <button class="ql-align" value=""></button>
  <button class="ql-align" value="center"></button>
  <button class="ql-align" value="right"></button>
  <button class="ql-list" value="bullet"></button>
  <button class="ql-list" value="ordered"></button>

  <!-- But you can also add your own -->
  <button id="custom-button" @click.stop="showConfirmClose = true">
    <img src="~@/assets/imgs/icon-image.png" />
  </button>
</div>
  1. js
editorOption: {
    modules: {
        toolbar: '#editor-toolbar',
    }
}
  1. css
#editor-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border: none;
    background-color: #f5f5f5;
    button.ql-active {
      color: #ff3333;
      .ql-stroke {
        stroke: #ff3333;
      }
    }
}

4. 字数统计

  1. template
<div class="word-limit">
  <span :class="{ 'c-ff3333': textLength >= 2000 }">{{ textLength }}</span
  ><span>/2000</span>
</div>
  1. script
this.textLength = this.setTextLength(this.eValue, this.editor);
setTextLength(html, editor) {
  if (!editor) {
    return 0;
  }
  let imgL = html.split('<img').length - 1;
  return editor.getText().replace(/\n/g,'').length + imgL * 200;
},
  1. css
.word-limit {
    position: fixed;
    bottom: 80px;
    left: 18px;
    width: 71px;
    height: 25px;
    background: rgba(0, 0, 0, 0.6);
    border-radius: 25px;
    text-align: center;
    line-height: 25px;
    font-size: 12px;
    color: #fff;
    font-weight: 400;
    letter-spacing: 1px;
}

5. 强制文本使用 style 而非 class

import * as Quill from 'quill';
// 修改居中方式
let quillAlign = Quill.import('attributors/style/align');
quillAlign.whitelist = ['right', 'center', 'justify']
Quill.register(quillAlign, true);

6. 自定义按钮

  1. template

    <div id="editor-toolbar">
      <!-- Add buttons as you would before -->
      ...
      <!-- But you can also add your own -->
      <button id="custom-button" @click.stop="doSometing">
        <img src="~@/assets/imgs/icon-image.png" />
      </button>
    </div>
  1. js
method: {
    ...
    doSometing() {
        // do something you can
    },
    ...
},

7. 如何将图片插入到光标位置

  1. 页面刚进来时未触发内容change事件,需手动赋值下
created() {
    this.editor = null;
    this.$set(this.editorOption, 'placeholder', this.placeholder || '请输入模板内容');
    this.initEditorDom = true;
    setTimeout(() => {
      this.editor = this.$refs.myQuillEditor.quill;
      this.textLength = this.setTextLength(this.eValue, this.editor);
    }, 100);
},
  1. 插入图片方法
insertImg(item) {
  this.showConfirmClose = false;
  let selection = this.editor ? this.editor.selection : null;
  let len = selection ? selection.savedRange.index : 0;
  this.editor.insertEmbed(len, 'image', item.url);
},

8. 插入自定义html记录

  1. 我们需要插入一个自定义HTML块,同时又要Quill能够识别,聪明的你一定想到了,我们需要自定义一个Blot。通过定义好Blot的方式,让Quill在初始化的时候能够识别我们的HTML块展示,同时也让我们在插入HTML块的时候不会被Quill进行脏HTML过滤。
export default function (Quill) {
  // 引入源码中的BlockEmbed
  const BlockEmbed = Quill.import('blots/block/embed');
  // 定义新的blot类型
  class AppPanelEmbed extends BlockEmbed {
    static create(value) {
      const node = super.create(value);
      node.setAttribute('contenteditable', 'false');
      node.setAttribute('width', '100%');
      //   设置自定义html
      node.innerHTML = this.transformValue(value)
      return node;
    }

    static transformValue(value) {
      let handleArr = value.split('\n')
      handleArr = handleArr.map(e => e.replace(/^[\s]+/, '')
        .replace(/[\s]+$/, ''))
      return handleArr.join('')
    }

    // 返回节点自身的value值 用于撤销操作
    static value(node) {
      return node.innerHTML
    }
  }
  // blotName
  AppPanelEmbed.blotName = 'AppPanelEmbed';
  // class名将用于匹配blot名称
  AppPanelEmbed.className = 'embed-innerApp';
  // 标签类型自定义
  AppPanelEmbed.tagName = 'div';
  Quill.register(AppPanelEmbed, true);
}
  1. 调用
quill.insertEmbed(quill.getSelection().index || 0, 'AppPanelEmbed', `
  <div class="app_card_header">     
      自定义面板标题
  </div>
  <div class="app_card_content">     
      自定义面板内容
  </div>
  <div class="app_card_footer">     
      footer
  </div>
`);
  1. 链接参考

9. 解决ios无法获取焦点问题

  1. 原因
* {
    -webkit-user-select: none;
}
  1. 解决方式:覆盖样式即可
.dt-quill-editor {
  ...
  -webkit-user-select: text;
  * {
    -webkit-user-select: text;
  }
  ...
}

10. 禁止粘贴图片

  1. 参考文档
data() {
    return {
        ...
        initEditorDom: false, // 处理初始化时会过滤图片问题
      editorOption: {
        modules: {
          clipboard: {
            // 粘贴版,处理粘贴时候带图片
            matchers: [[Node.ELEMENT_NODE, this.handleCustomMatcher]],
          },
          ...
        }
      }
    }
},
created() {
    ...
    setTimeout(() => { this.initEditorDom = true; }, 1000)
    ...
},
beforeDestoryed() {
    this.initEditorDom = false;
}
method: {
    ...
    handleCustomMatcher(node, Delta) {
      if (!this.initEditorDom) {
          return Delta;
      }
      let ops = [];
      Delta.ops.forEach((op) => {
        if (op.insert && typeof op.insert === 'string') {
          // 如果粘贴了图片,这里会是一个对象,所以可以这样处理
          ops.push({
            insert: op.insert,
          });
        }
      });
      Delta.ops = ops;
      return Delta;
    },
    ...
},