根据网上的各类教程总结了一下 再根据我自己项目的需求 进行添加和修改 实现的图如下在这里插入图片描述

首先安装需要的插件

cnpm install vue-quill-editor -S
cnpm install quill -S
cnpm install quill-image-resize-module -S
cnpm install quill-image-drop-module -S

在project.config.js下写入如下代码

const webpack = require("webpack")
const plugins = [
  new webpack.ProvidePlugin({
    'window.Quill': 'quill/dist/quill.js',
    'Quill': 'quill/dist/quill.js'
  })
];

然后就是创建一个quillEditor.vue来配置富文本框

创建一个index.vue 来使用这个富文本框

两个部分的源码都已经在下面了

下面是源码部分

所有重要的地方都已加注释

quillEditor.vue

<template>
  <div class="editorBox">
    <!-- 这里没有用v-model 是因为vue-quill-editor会把v-model中的html代码中的行内样式过滤--> 		
    <!--而quill-image-resize-module 这个组件是给图片加行内样式的  -->
     <!-- 所以不绑定 在使用组件的页面直接让el.editor.innerHTML = 数据库传来的content  -->
     <!-- 这样行内样式就不会被过滤了 在使用富文本的组件中有代码  -->
    <quill-editor
      class="editor"
      ref="myQuillEditor"
      :options="editorOption"
      @blur="onEditorBlur($event)"
      @focus="onEditorFocus($event)"
      @change="onEditorChange($event)"
    >
    </quill-editor>
    <!-- 这里是文件上传的窗口 
    action 为上传的地址
    headers 为请求头 带token
     -->
    <el-upload
      :action="action"
      :headers="headers"
      :show-file-list="false"
      :accept="accept"
      :on-success="uploadSuccess"
      :on-error="uploadError"
      :before-upload="beforeUpload"
      class="avatar-uploader-editor"
    />
    <!-- 
      这里是上传附件的窗口
      action 为上传附件的接口
      headers为请求头
      limit为 最大文件个数
      fileList 为附件的list 通过父组件传值 来给filelist确定值
     -->
     <el-upload
      class="upload-demo"
      :action="attactAction"
      :headers="headers"
      :on-preview="handlePreview"
      :on-remove="handleRemove"
      :before-remove="beforeRemove"
      multiple
      :limit="1"
      :on-exceed="handleExceed"
      :ref="upload"
      :file-list="fileList">
        <el-button
          size="small"
          type="primary"
          @click="uploadBtn"
          class="my-upload"
          v-show="false"
          style="height:0px">点击上传</el-button>
        <div slot="tip" class="el-upload__tip" style="margin-top:0px">已选择附件:</div>
      </el-upload>
  </div>
</template>

<script>
import {deleteInfoFile ,getInfo} from "@/api/banner";
import { getToken } from "@/utils/auth";
// 在这里导入需要用到的插件 
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 Quill from 'quill'
import resizeImage from 'quill-image-resize-module' // 调整大小组件。
import { ImageDrop } from 'quill-image-drop-module'; // 拖动加载图片组件。
Quill.register('modules/imageDrop', ImageDrop);
Quill.register('modules/resizeImage ', resizeImage )
export default {
  components: {
    quillEditor,
  },
  props: {
  	//这个是富文本的内容
    contentData: {
      type: String,
      default: "",
    },
    infoId: {
      type: String,
      default: ""
    },
    //这个是附件列表
    file: {
      type: Array
    }
  },
  data() {
    return {
    	
      fileList: [],
      upload:"",
      id:"",
      content: "",
      accept: "image/jpeg,image/jpg,image/png,image/vnd.microsoft.icon",
      //headers 和 action 根据自己项目的来确定
      headers: {
        authorization: "authorization-text",
        "x-auth-token": getToken(),
      },
      action: process.env.VUE_APP_BASE_API + "/xxxxxxxxxxxx",
      attactAction: ``,
      quillUpdateImg: false,
      editorOption: {
        modules: {
          toolbar: {
            container: [
              //这里是富文本的功能栏
              ["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"], //清除字体样式
              // ==下面这两个是自定义的功能栏==
              ["image"], //上传图片、上传视频
              ['sourceEditor'] //附件
            ],
            handlers: {
              //这里对应自定义的功能栏
              image: function (value) {
                console.log(value, "value");
                if (value) {
                  // 触发input框选择图片文件
                  document
                    .querySelector(".avatar-uploader-editor input")
                    .click();
                } else {
                  this.quill.format("image", false);
                }
              },
              sourceEditor: function () { // 添加工具方法
                  document.getElementsByClassName('my-upload')[0].click()
              }
            },
          },
          //下面是控制图片大小组件需要的内容
          history: {
              delay: 1000,
              maxStack: 50,
              userOnly: false
            },
            imageDrop: true,
            imageResize: {
              displayStyles: {
                backgroundColor: 'black',
                border: 'none',
                color: 'white'
              },
              modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
            }
        },
      },
    };
  },
  // computed:{
  //   infoId: function() {
  //   return  this.id;
  // }
  // },
  mounted() {
    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-video": "视频",
      "ql-clean": "清除字体样式",
      "ql-upload": "文件",
    };
    // 富文本提示信息
    this.$nextTick(() => {
      const oToolBar = document.getElementsByClassName("editor")[0];
      console.log("toobar",oToolBar);
      const aButton = oToolBar.querySelectorAll("button");
      const aSelect = oToolBar.querySelectorAll("select");
      aButton.forEach(function (item) {
        if (item.className === "ql-script") {
          item.value === "sub" ? (item.title = "下标") : (item.title = "上标");
          //这里将功能栏中的按钮循环出来 根据不同的classname 来给定不同的提示信息
        } else if(item.className === "ql-sourceEditor") {
          item.title = "附件"
        } else if (item.className === "ql-indent") {
          item.value === "+1"
            ? (item.title = "向右缩进")
            : (item.title = "向左缩进");
        } else {
          item.title = titleConfig[item.classList[0]];
        }
      });
      aSelect.forEach(function (item) {
        item.parentNode.title = titleConfig[item.classList[0]];
      });
    });
    //这里给自定义的附件功能给定一个icon
    this.myEl = this.$el.querySelector('.ql-sourceEditor');
    this.$el.querySelector(
      '.ql-sourceEditor'
    ).innerHTML = `<svg t="1592278063482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6358" width="18" height="200"><path d="M704 341.333333h64a64 64 0 0 1 64 64v362.666667a64 64 0 0 1-64 64H256a64 64 0 0 1-64-64V405.333333a64 64 0 0 1 64-64h64v64h-64v362.666667h512V405.333333h-64v-64zM517.653333 124.629333l150.826667 150.826667-45.226667 45.269333-74.026666-74.005333v304.768h-64V247.616l-73.173334 73.130667-45.248-45.248 150.826667-150.848z" p-id="6359"></path></svg>`
  },
  watch: {
  //监听父组件传值  令content 为父组件传来的值
    contentData: {
      handler(newVal) {
        this.content = newVal;
        this.$nextTick(() => {
          //这里会设置富文本框的高度和宽度
          this.$refs.myQuillEditor.quill.container.style.height = "400px";
          this.$refs.myQuillEditor.quill.container.style.width = "945px";
        })
      },
      deep: true,
      immediate: true,
    },
    infoId: {
       handler(newVal) {
         console.log("这里是id",newVal)
         this.id = newVal;
       }
    },
    file: {
    //这里监听附件列表
      handler(newVal) {
        this.fileList = newVal;
      }
    }
  },
  created() {
  },
  beforeUpdate() {
  },
  updated() {
  },
  methods: {
     handleExceed (files, fileList) {
      this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件`)
    },
    beforeRemove (file, fileList) {
      return this.$confirm(`确定移除 ${ file.name }`)
    },
    handleRemove (file, fileList) {
        var deleteId = 0
        if (file.id) {
          for (var i = 0; i < this.fileList.length; i++) {
            if (this.fileList[i].name === file.name) {
              this.fileList.splice(i, 1)
                deleteId = file.id
                break
            }
          }
        } else if (file.uid) {
          for (var j = 0; j < this.enclosureIDs.length; j++) {
            if (this.enclosureIDs[j].name === file.name) {
                deleteId = this.enclosureIDs[j]['en_id']
                this.enclosureIDs.splice(j, 1)
                break
            }
          }
        }
        // for (var i = this.fileList.length - 1; i > 0; i--) {
        //   console.log(this.fileList[i])
        //   }
        // this.$emit('contentData', this.quill.root.innerHTML, this.enclosureIDs)
        // // 请求删除附件接口
          deleteInfoFile(this.id, {
          })
          .then(res => {
            // this.$emit('contentData', this.quill.root.innerHTML, this.enclosureIDs)
            this.$message({
            type: 'success',
            message: '删除成功!'
          })
          })
      },
    handlePreview (file) {
    },
    uploadBtn () {
      //在这里给定上传附件的action
      this.attactAction = process.env.VUE_APP_BASE_API + `/xxxxx`,
      console.log(this.attactAction)
      console.log("这里是按钮id",this.id)
      console.log("这里是按钮current",this.content)
    },
    uploadSuccess(response, file, fileList) {
      let quill = this.$refs.myQuillEditor.quill;
      // 如果上传成功
      if (response.status == 200) {
        // 获取光标所在位置
        let length = quill.getSelection().index;
        // 插入图片  response.body.path为服务器返回的图片地址
        quill.insertEmbed(
          length,
          "image",
          //这里服务器地址 存进去的照片的scr为下面的地址
          "http://localhost:8084" + response.body.path
        );
        // 调整光标到最后
        quill.setSelection(length + 1);
      } else {
        this.$message.error("图片上传失败,请重新上传!");
      }
      // loading动画消失
      this.quillUpdateImg = false;
      // this.$message.success('图片上传成功');
    },
    uploadError() {},
    beforeUpload(file) {
      const isLt2M = file.size / 1024 / 1024 < 2;
      if (!isLt2M) {
        this.$message.error("上传图片大小不能超过 2MB!");
      }
      return isLt2M;
    },
    onEditorBlur(e) {},
    onEditorFocus(e) {},
    onEditorChange(e) {
    //这里是富文本框内容发生变化时 就会给父组件传值 传的为html代码 在父组件接受 
      e.quill.container.style.height = "200px";
      this.$emit("editorChange",e.html);
    },
  },
};
</script>
<style scoped>
::v-deep .editor.ql-blank {
  min-height: 200px;
}
.editor {
  padding: 0px;
  width: 945px;
  height: 400px;
  margin: auto;
}
.avatar-uploader {
  height: 0px;
}
</style>
<style>
.el-upload-list__item {
      transition: none !important;
    }

</style>
<style lang='scss' >
.editor {
  line-height: normal !important;
  height: 500px;
}
.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>


在这个组件中使用富文本

index.vue

<template>
    <el-card type="admin-full" shadow="never">
    <el-form ref="form" :model="formData" label-width="80px">
      <el-form-item label="学院概况" prop="content">
        <div style="text-align: center;"><span>请在调整图片对齐格式后再调整图片大小</span></div>	
         <!-- 这里面是我需要用到的内容 把富文本的内容传给contentData就可以了 -->
         <!-- file为附件的列表 -->
        <quillEditor :file="fileList" :infoId="formData.id" :contentData="formData.content" @editorChange="contentChange" />
      </el-form-item>
    </el-form>
    <span style="float: right;margin-bottom: 10px;">
      <el-button :loading="dialogBtnLoading" type="primary" @click="handleOk">
        保存
      </el-button>
    </span>
    </el-card>
</template>
<script>
import quillEditor from "@/pages/learn/views/admin/news/components/quillEditor.vue";
import UploaderFile from "@/components/Pages/UploaderFile/index";
import { validateLength } from "@/utils/validate";
import { getInfo,updateInfo,getInfoFile } from "@/api/banner";
import { getToken } from '@/utils/auth';
export default {
  components: {
    quillEditor,
    UploaderFile
  },
  props: {
    value: {
      type: Boolean,
      default: false
    },
    activeName: {
      type: String,
      default: "journalismInformation"
    },
    data: {
      type: Object,
      default: () => {}
    },
    type: {
      type: String,
      default: "create"
    }
  },
  data() {
    return {
      uploadFileUrl: process.env.VUE_APP_BASE_API + "/files/public/work",
      fileList: [],
      httpHeaders: {
        authorization: 'authorization-text',
        'x-auth-token': getToken()
      },
      dialogBtnLoading: false,
      formData: {},
    };
  },
  computed: {
    
  },
  watch: {
    
  },
  created() {
    this.initData();
  },
  methods: {
    initData(){
    //这里是我自己的接口 从数据库查出content
        getInfo({
          params: {
            name: "xxxxx"
          }
        })
        .then(res =>{
            this.formData = res;
            //===============这个地方是很重要的地方==========
            //因为vue-quill-editor 会过滤行内样式 导致图片的居左居中等行内样式不会显示在富文本框
            // 但是数据库中的content还是有行内样式的 只是被quill过滤 所以我们直接让他加载的时候
            // 加载我们数据库中的内容  如果在富文本的地方双向绑定 就会被过滤
            document.querySelector(".ql-editor").innerHTML = this.formData.content;
            getInfoFile(res.id)
            .then(res =>{
            this.fileList.push(res);
          })
        })
        
    },
    contentChange(val) {
    //	将content赋值为富文本组件传来的值
        this.formData.content = val;
    },
    onPreview() {},
    handleExceed(files, fileList) {
      this.$message.warning("只可以上传十个附件");
    },
    onSuccess(response, fileList) {
      console.log(response, "response");
      let file = {
        name: response.body.name,
        id: response.body.id
      };
      this.fileList.push(file);
      console.log(this.fileList, "fileList");
    },
    onRemove() {

    },
    onClose() {
      this.$emit("input", false);
    },
    handleOk() {
      this.formData.attachId=null;
      this.$refs["form"].validate(valid => {
        if (valid) {
          this.dialogBtnLoading = true;
            updateInfo({
              data: { ...this.formData }
            }).then(res => {
              this.$emit("ok");
              this.$message.success("保存成功");
              this.onClose();
              this.dialogBtnLoading = false;
            });
            this.$emit("ok");
        }
      });
    },
    handleSuccess(url) {
      console.log("this.formData", this.formData);
      this.formData.cover = url;
    }
  }
};
</script>
<style lang="scss">
@import "~@/styles/mixin.scss";
.set-container {
  min-width: 157px;
  .img-card {
    img {
      max-width: 100%;
      vertical-align: top;
    }
  }
  .img-add-card {
    background: rgba(255, 255, 255, 1);
    border-radius: 4px;
    color: #666666;
    line-height: 1.15;
    @include fontSize(14px);
    width: 157px;
    height: 180px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    box-shadow: 0 0 4px 0 #e4e4e4;
    border: 1px dashed #dcdfe6;
    i {
      font-size: 32px;

      &:hover {
        color: #4597fd;
      }
    }
  }
}
</style>

Logo

前往低代码交流专区

更多推荐