本贴会涉及以下几个技术点:

  1. Vue+Element-UI实现富文本编辑框,以及文本编辑框中事件拦截、插入图片
  2. Element-UI限制上传图片后,隐藏上传按钮:官网上是没有这个方法的,可以通过上传到指定张数后隐藏上传按钮来实现
  3. form表单验证

首先安装一下插件vue-quill-editor,npm嫌慢可以用cnpm

官网地址:https://www.npmjs.com/package/vue-quill-editor

npm install vue-quill-editor --save

引用:

import { quillEditor } from "vue-quill-editor";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
//这个是插件优化的样式,在下面贴出来
import "@/config/quill/quill.scss";

下面是HTML部分:

<template>
  <div class="page-content">
    <el-form ref="quillForm" :rules="quillRules" :model="quillForm" label-width="65px">
      <el-form-item label="内容:" prop="content">
        <!-- 图片上传组件辅助 -->
        <el-upload
          class="avatar-uploader"
          :action="editorOption.serverUrl"
          name="file"
          :show-file-list="false"
          :on-success="uploadSuccess"
          :on-error="uploadError"
          :before-upload="beforeUpload"
          style="display: none;">
        </el-upload>
        <quill-editor
          class="editor"
          ref="myQuillEditor"
          v-model.lazy.trim="quillForm.content"
          :options="editorOption"
          @blur="onEditorBlur($event)"
          @focus="onEditorFocus($event)"
          @ready="onEditorReady($event)"
          @change="onEditorChange($event)"/>
      </el-form-item>
      <el-form-item label="封面:" prop="coverImg">
        <el-upload
          name="coverImg"
          action="#"
          :auto-upload="false"
          :file-list="fileList"
          :class="{disabled: uploadDisabled}"
          accept="image/*"
          list-type="picture-card"
          :limit="1"
          :on-preview="handlePictureCardPreview"
          :on-remove="handleRemove"
          :on-change="getFile">
          <i slot="default" class="el-icon-plus"></i>
        </el-upload>
        <el-dialog :visible.sync="dialogVisible" append-to-body>
          <img width="100%" :src="dialogImageUrl" alt="">
        </el-dialog>
      </el-form-item>
    </el-form>
    <el-button @click="submit('quillForm')" type="primary">提交</el-button>
  </div>
</template>

当前页面css:

<style lang="scss" scoped>
  .page-content {
    width: 100%;
    height: 100%;

    /deep/ .editor {
      margin-top: 15px;

      .ql-editor {
        min-height: 300px;
      }
    }

    .header {
      padding-bottom: 15px;
      border-bottom: 1px solid #eee;
    }

    /deep/ .el-form {
      .el-form-item {
        max-width: 700px;
      }
    }

    /* 此处是隐藏上传按钮的样式 */
    /deep/ .disabled .el-upload--picture-card {
      display: none;
    }
  }
</style>

接下来是重点了,JavaScript它来了它来了,它迈着步伐走来了

<script>
  //定义编辑框功能区,具体的可到官网上查看
  const toolbarOptions = [
    ['bold', 'italic', 'underline', 'strike'],        // toggled buttons
    [{'header': 1}, {'header': 2}],               // custom button values
    [{'list': 'ordered'}, {'list': 'bullet'}],
    [{'indent': '-1'}, {'indent': '+1'}],          // outdent/indent
    [{'direction': 'rtl'}],                         // text direction
    [{'size': ['small', false, 'large', 'huge']}],  // custom dropdown
    [{'header': [1, 2, 3, 4, 5, 6, false]}],
    [{'color': []}, {'background': []}],          // dropdown with defaults from theme
    [{'font': []}],
    [{'align': []}],
    ['link', 'image'],
    ['clean']
  ]
  
  //提交数据的方法,自己定义吧
  import { shelfVehicle } from "@/api/disassemble"
  
  //从这儿往下5行是引入编辑框相关事宜,上面也有
  import { quillEditor } from "vue-quill-editor";
  import "quill/dist/quill.core.css";
  import "quill/dist/quill.snow.css";
  import "quill/dist/quill.bubble.css";
  import "@/config/quill/quill.scss";

  export default {
    //注册编辑框组件
    components: {
      quillEditor
    },
    data () {
      return {
        //加载动画
        loading: "",
        //form表单
        quillForm: {
          content: ""
        },
        //form表单校验
        quillRules: {
          content: [
            {required: true, message: '请输入内容', trigger: 'blur'},
          ],
        },
        //富文本配置
        editorOption: {
          placeholder: "请输入相关内容",
          modules: {
            toolbar: {
              container: toolbarOptions,
              handlers: {
                'image': function (value) {
                  if (value) {
                    document.querySelector('.avatar-uploader input').click()
                  } else {
                    this.quill.format('image', false);
                  }
                }
              }
            }
          },
          // 此项配置是选择图片之后的上传路径
          serverUrl: "xxx"
        },
        //上传图片
        fileList: [],
        dialogImageUrl: "",
        dialogVisible: false,
        postFile: "",
      }
    },
    computed: {
      //计算属性:计算fileList的长度,我的项目要求是上传一张图片,具体的可以自己改
      uploadDisabled() {
        return this.fileList.length > 0
      },
    },
    methods: {
      //监听富文本编辑框动向,敌不动我不动敌若动我乱动,还有个上传图片
      onEditorChange({editor, html, text}) {
        // 此处三项可以自己在控制台打印
        this.quillForm.content = html
      },
      //图片上传前,显示loading动画,element官网上的,可以自行查找
      beforeUpload() {
        this.loading = this.$loading({
          lock: true,
          text: '图片加载中,请稍后!',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
        });
      },
      // 上传成功
      uploadSuccess(res, file) {
        // 获取富文本组件实例
        let quill = this.$refs.myQuillEditor.quill
        if (res.code == 200 ) {
          // 获取光标所在位置
          let length = quill.getSelection().index;
          // 获取光标所在位置// 获取光标所在位置
          quill.insertEmbed(length, 'image', res.message)
          // 调整光标到最后
          quill.setSelection(length + 1)
        } else {
          this.$message.error(res.message)
        }
        this.loading.close();
      },
      // 上传失败来到这里
      uploadError() {
        this.loading.close();
        this.$message.error(res.message)
      },

      //监听编辑器事件,此处三个事件可以自己根据业务定义
      onEditorBlur(quill) {},
      onEditorFocus(quill) {},
      onEditorReady(quill) {},

      /* 下面是上传图片 */
      //拿到图片对象
      getFile(file, fileList) {
        this.postFile = file.raw
        this.fileList = fileList
      },
      //查看图片预览
      handlePictureCardPreview(file) {
        this.dialogImageUrl = file.url;
        this.dialogVisible = true;
      },
      //删除图片
      handleRemove(file, fileList) {
        this.dialogImageUrl = ""
        this.fileList = []
        this.dialogVisible = false
      },

      //最后的提交数据,此处有表单验证
      submit(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            //将数据组成FormData传递给后台
            let formData = new FormData();
            formData.append("VehiclePartsStore", JSON.stringify(this.quillForm));
            this.fileList.forEach( function (file) {
              formData.append('file', file.raw, file.raw.name);
            } );
            //与后台交互,得到返回结果
            shelfVehicle(formData).then(res => {
              console.log(res);
            })
          } else {
            return false;
          }
        });
      }
    },
  }
</script>

后端上传图片的方法,提交数据的方法不贴了(懒惰一下)自己根据业务实现吧

@PostMapping("haha")
public String haha (@RequestParam("file") MultipartFile file) {
    //此处不出意外的话你已经拿到了文件对象,具体业务逻辑就可以自己实现了
    return "xxx";
}

实现效果图,编辑前

Element ui设置字体加粗标签 element ui文本框_css

编辑后:

Element ui设置字体加粗标签 element ui文本框_vue.js_02

 刚刚学会,有不对出烦请指出~

差点儿忘了,还有个优化css

.ql-editor {
  min-height: 300px;
}

.ql-toolbar.ql-snow {
  border: 1px solid #DCDFE6;
  border-radius: 3px 3px 0 0;
}

.ql-container.ql-snow {
  border: 1px solid #DCDFE6;
  border-radius: 0 0 3px 3px;
}

.editor {
  line-height: normal !important;
  
  .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: '等宽字体';
  }
}