富文本

这里使用的是vue-quill-editor组件作为富文本功能。
在VUE中使用vue-quill-editor非常方便,引入组件就可以马上使用,但是通常情况下,我们都需要进行一些自定义的功能,所以需要自己进行二次封装。

初始化

	<template>
	  <quillEditor
	    class="ql-toolbar"
	    v-model="content"
	    ref="quillEditor"
	  >
	  </quillEditor>
	</template>
	<script>
	import { quillEditor } from 'vue-quill-editor';
	export default {
	  components:[quillEditor],
	  props:{
	  	value:{
			type:String.
			default:''
		}
	  },
		data() {
		  return {
		   content: '',
		  };
		},
		watch:{
		    value(v) {
      			this.content = v;
    		},
    	}
	}
	</script>

富文本初始化

封装了一个方法和配置,在页面初始化的时候调用即可。

const titleConfig = {
  'ql-bold': '加粗',
  'ql-color': '颜色',
  'ql-font': '字体',
  'ql-code': '插入代码',
  'ql-italic': '斜体',
  'ql-link': '添加链接',
  'ql-background': '背景颜色',
  'ql-size': '字体大小',
  'ql-strike': '删除线',
  'ql-script': '上标/下标',
  'ql-underline': '下划线',
  'ql-blockquote': '引用',
  'ql-header': '标题',
  'ql-indent': '缩进',
  'ql-list': '列表',
  'ql-align': '文本对齐',
  'ql-direction': '文本方向',
  'ql-code-block': '代码块',
  'ql-formula': '公式',
  'ql-image': '图片',
  'ql-clean': '清除字体样式',
};
function addQuillTitle() {
  const oToolBar = document.querySelector('.ql-toolbar');
  const aButton = oToolBar.querySelectorAll('button');
  const aSelect = oToolBar.querySelectorAll('select');
  aButton.forEach((item) => {
    if (item.className === 'ql-script') {
      item.value === 'sub' ? (item.title = '下标') : (item.title = '上标');
    } else if (item.className === 'ql-indent') {
      item.value === '+1' ? (item.title = '向右缩进') : (item.title = '向左缩进');
    } else {
      item.title = titleConfig[item.classList[0]];
    }
  });
  aSelect.forEach((item) => {
    item.parentNode.title = titleConfig[item.classList[0]];
  });
  // 关闭单词检测提示
  document.querySelector('.ql-editor').setAttribute('spellcheck', false);
  return oToolBar;
}
<template>
  <quillEditor
    class="ql-toolbar"
    v-model="content"
    :options="editorOption"
    ref="quillEditor"
  >
  </quillEditor>
</template>
// 使用computed
export default {
   computed:{
    editorOption() {
      const editorOption = {
        placeholder: this.placeholder,
      };
      editorOption.modules = !this.readOnly
        ? {
            toolbar: [
              'bold',
              'underline',
              'strike',
              'blockquote',
              'code-block',
              'italic',
              { color: [] },
              { background: [] },
              { list: 'ordered' },
              { list: 'bullet' },
              { script: 'sub' },
              { script: 'super' },
              { indent: '-1' },
              { indent: '+1' },
              'link',
              'image',
              'clean',
            ],
          }
        : {
            toolbar: [],
          };
      return editorOption;
    },
   }
}

增加只读样式

为了方便后续仅展示的情况,我们需要增加一个只读样式

  • 编辑模式
    在这里插入图片描述
  • 只读模式
    在这里插入图片描述代码预览:
<template>
  <quillEditor
    class="ql-toolbar"
    :class="{
      'hide-toolbar': readOnly,
    }"
    v-model="content"
    :options="editorOption"
    ref="quillEditor"
  >
  </quillEditor>
</template>
export default {
  props: {
    readOnly: {
      type: Boolean,
      default: false,
    },
    placeholder: {
      type: String,
      default: '',
    },
    value: {
      type: String,
      defualt: '',
    },
  },
    computed: {
    editorOption() {
    	//..........//
    },
    quill() {
    	// 获取富文本的vue节点
      return this.$refs.quillEditor.quill;
    },
  },
    watch: {
    value(v) {
     	/*          */
    },
    readOnly(v) {
    	// 设置为不可编辑
      this.quill.enable(!v);
    },
  },
}
// css 样式
<style lang="less">
.hide-toolbar {
  .ql-toolbar {
    height: 0;
    padding: 0 !important;
    border-top-color: transparent !important;
    border: none !important;
  }

  .ql-container.ql-snow {
    border: none;
  }
  .ql-editor {
    background-color: #ffffff;
  }
}
</style>

支持el-form-item的校验

我们从源码可以只读,el-inpt这些表单元素在改变值的时候都会向上发送一个$EMIT事件,为了兼容el-form-item的校验。我们也需要加入这样的传播事件。

/// 广播事件
methods:{
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      console.log(parent);
      if (parent) {
        parent.$emit(...[eventName].concat(params));
      }
    },
}

我们添加change和bulr事件中添加广播事件

  watch: {
    value(v) {
      this.content = v;
      if (this.validateEvent) {
        this.dispatch('ElFormItem', 'el.form.change', [v]);
      }
    },
  },
  methods:{
	    handleBlur() {
	      if (this.validateEvent) {
	        this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
	      }
	    },
	}

错误样式提示

只需要增加一个.el-form-item.is-error父类的选择器即可。
在这里插入图片描述

.el-form-item.is-error .ql-container,
.el-form-item.is-error .ql-toolbar {
  border-color: #f56c6c;
}

完整代码

<template>
  <quillEditor
    class="ql-toolbar"
    :class="{
      'hide-toolbar': readOnly,
    }"
    v-model="content"
    :options="editorOption"
    ref="quillEditor"
    @blur="handleBlur"
  >
  </quillEditor>
</template>
<script>
/* eslint-disable indent */
import { quillEditor } from 'vue-quill-editor';
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';

const titleConfig = {
  'ql-bold': '加粗',
  'ql-color': '颜色',
  'ql-font': '字体',
  'ql-code': '插入代码',
  'ql-italic': '斜体',
  'ql-link': '添加链接',
  'ql-background': '背景颜色',
  'ql-size': '字体大小',
  'ql-strike': '删除线',
  'ql-script': '上标/下标',
  'ql-underline': '下划线',
  'ql-blockquote': '引用',
  'ql-header': '标题',
  'ql-indent': '缩进',
  'ql-list': '列表',
  'ql-align': '文本对齐',
  'ql-direction': '文本方向',
  'ql-code-block': '代码块',
  'ql-formula': '公式',
  'ql-image': '图片',
  'ql-clean': '清除字体样式',
};
function addQuillTitle() {
  const oToolBar = document.querySelector('.ql-toolbar');
  const aButton = oToolBar.querySelectorAll('button');
  const aSelect = oToolBar.querySelectorAll('select');
  aButton.forEach((item) => {
    if (item.className === 'ql-script') {
      item.value === 'sub' ? (item.title = '下标') : (item.title = '上标');
    } else if (item.className === 'ql-indent') {
      item.value === '+1' ? (item.title = '向右缩进') : (item.title = '向左缩进');
    } else {
      item.title = titleConfig[item.classList[0]];
    }
  });
  aSelect.forEach((item) => {
    item.parentNode.title = titleConfig[item.classList[0]];
  });
  // 关闭单词检测提示
  document.querySelector('.ql-editor').setAttribute('spellcheck', false);
  return oToolBar;
}

export default {
  props: {
    readOnly: {
      type: Boolean,
      default: false,
    },
    placeholder: {
      type: String,
      default: '',
    },
    value: {
      type: String,
      defualt: '',
    },
    validateEvent: {
      type: Boolean,
      default: true,
    },
  },
  components: {
    quillEditor,
  },
  data() {
    return {
      content: '',
    };
  },
  computed: {
    editorOption() {
      const editorOption = {
        placeholder: this.placeholder,
      };
      editorOption.modules = !this.readOnly
        ? {
            toolbar: [
              'bold',
              'underline',
              'strike',
              'blockquote',
              'code-block',
              'italic',
              { color: [] },
              { background: [] },
              { list: 'ordered' },
              { list: 'bullet' },
              { script: 'sub' },
              { script: 'super' },
              { indent: '-1' },
              { indent: '+1' },
              'link',
              'image',
              'clean',
            ],
          }
        : {
            toolbar: [],
          };
      return editorOption;
    },
    quill() {
      return this.$refs.quillEditor.quill;
    },
  },
  watch: {
    value(v) {
      this.content = v;
      if (this.validateEvent) {
        this.dispatch('ElFormItem', 'el.form.change', [v]);
      }
    },
    readOnly(v) {
      this.quill.enable(!v);
    },
  },
  mounted() {
    addQuillTitle();
    this.initQuillEvent();
    this.content = this.value;
  },
  methods: {
    handleBlur() {
      if (this.validateEvent) {
        this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
      }
    },
    // 广播事件
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      console.log(parent);
      if (parent) {
        parent.$emit(...[eventName].concat(params));
      }
    },
    initQuillEvent() {
      this.quill.enable(!this.readOnly);
      this.quill.on('text-change', (delta, oldDelta, source) => {
        if (source === 'api') {
          // console.log('An API call triggered this change.', delta);
        } else if (source === 'user') {
          // console.log('A user action triggered this change.', delta);
        }
        this.$emit('input', this.content);
      });
    },
  },
};
</script>
<style lang="less">
.ql-container {
  height: calc(~'100% - 40px');
  // height: 100%;
}
.hide-toolbar {
  .ql-toolbar {
    height: 0;
    padding: 0 !important;
    border-top-color: transparent !important;
    border: none !important;
  }

  .ql-container.ql-snow {
    border: none;
  }
  .ql-editor {
    background-color: #ffffff;
  }
}
</style>

Logo

前往低代码交流专区

更多推荐