关于对tinymce富文本编辑器使用的心得

news2024/11/19 22:33:40

本文分为一下几个功能:

  1. tinymce初始化的一些配置
  2. 在工具栏和文本中间插入特定的标题和摘要
  3. 自定义工具栏按钮,实现特定功能
  4. 上传图片时去掉网络上传功能
  5. 粘贴过来的图片实现默认上传
  6. 给图片添加水印功能
  7. 实现一键排版

    一、tinymce初始化的一些配置

          1、首先需要引入下面两个插件

        cnpm install @tinymce/tinymce-vue --save                     版本:2.0.0

        cnpm install tinymce --save                                             版本:5.0.3
        注:在之前使用时我发现,在tinymce 6 以上版本有些内置插件不存在了,导致一些功能无法使用,所以这里标注了一下版本

        2、当下载好插件后,需要到node_modules中找到tinymce文件夹,将里面的skins文件夹复制出来放到vue项目的public文件夹中

   

        3、还需要下载一个语言包,将插件翻译成中文,下载地址如下

   官网地址:https://www.tiny.cloud/get-tiny/language-packages/     

    找到 zh-ch进行下载,如下:

        之后将下载好的语言包,放置在vue的public文件夹中,然后在代码中引入进来,我下载的语言包名字是zh-Hans的,和其他人的可能不太一样,但最终效果都是一样的,根据文件名称直接引入即可

        下面是代码及引入的一些插件和一些基础配置

<template>
  <div class="editor_wraper">
    <editor :init="tinymceInit" v-model="content"> </editor>
  </div>
</template>

<script>
import tinymce from "tinymce";
import Editor from "@tinymce/tinymce-vue";
import "tinymce/themes/silver";
import {
  toolbar,
  fontsizeFormats,
  fontFormats,
  lineheightFormats,
} from "@/utils/tinymceConfig";
export default {
  data() {
    return {
      content: "",
      tinymceInit: {
        language_url: "tinymce/langs/zh-Hans.js", //引入语言包文件
        language: "zh-Hans", //语言类型
        skin_url: "tinymce/skins/ui/oxide", //皮肤:浅色
        quickbars_image_toolbar: "", // 选中媒体文件时的弹框
        // skin_url: '/tinymce/skins/ui/oxide-dark',//皮肤:暗色
        toolbar: toolbar, //工具栏配置,设为false则隐藏
        menubar: false, //菜单栏配置,设为false则隐藏,不配置则默认显示全部菜单,也可自定义配置--查看 http://tinymce.ax-z.cn/configure/editor-appearance.php --搜索“自定义菜单”
        height: 500, // 富文本高度
        model: 'dom', // 是否可拉伸富文本框
        fontsize_formats: fontsizeFormats, //字体大小
        font_formats: fontFormats, //字体样式
        lineheight_formats: lineheightFormats, //行高配置,也可配置成"12px 14px 16px 20px"这种形式
        placeholder: "在这里输入文字",
        branding: false, //tiny技术支持信息是否显示
        resize: "both", //编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可,注意引号
        statusbar: false,  //最下方的元素路径和字数统计那一栏是否显示
        paste_data_images: true, //图片是否可粘贴
        elementpath: false, //元素路径是否显示
      },
    };
  },
  components: { Editor },
};
</script>

        这里是上方引入的tinymceConfig文件中的一些配置

/**
 * @description: 工具栏配置
 * @return {*}
 */
export const toolbar = `
  undo redo restoredraft |
  removeformat|
  formatselect |
  subscript superscript |
  bold italic underline strikethrough link anchor |
  numlist bullist blockquote |
  alignleft aligncenter alignright alignjustify|
  quicklink searchreplace image|
  forecolor backcolor |
  fontselect fontsizeselect|
  outdent indent lineheight|`

/**
 * @description: 字体大小设置
 * @return {*}
 */
export const fontsizeFormats = `12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px`
/**
 * @description: 字体设置
 * @return {*}
 */
export const fontFormats = `微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;`
/**
 * @description: 行高设置
 * @return {*}
 */
export const lineheightFormats = "0.5 0.8 1 1.2 1.5 1.75 2 2.5 3 4 5"

配置好以上内容,页面可能显示出如下效果

二、在工具栏和文本中间插入特定的标题和摘要

        插入标题和摘要的方式是需要使用内联模式才可以实现,如下我是在上面代码的基础上进行的修改

       注意:如果开启内联模式后配置了toolbar_persist为true,你的tinymce还是需要点击才能显示工具栏,而不是固定显示,那很有可能是你之前通过npm uninstall的方式删除tinymce包时没有删干净,node_modules中有很多个tinymce包,这时,你需要删除你整个的node_modules,然后重新install一下就好了,亲测的坑

<template>
  <div class="editor_wraper">
    <div id="mytoolbar"></div>
    <el-input
      class="titleStyle"
      v-model="defaultTitle"
      type="textarea"
      resize="none"
      :autosize="{ minRows: 1, maxRows: 3 }"
      placeholder="请在这里输入标题"
    ></el-input>
    <el-input
      class="abstractStyle"
      v-model="defaultAbstract"
      type="textarea"
      resize="none"
      :autosize="{ minRows: 1 }"
      placeholder="摘要(可选)"
    ></el-input>
    <editor :init="tinymceInit" v-model="content"> </editor>
  </div>
</template>

<script>
import tinymce from "tinymce";
import Editor from "@tinymce/tinymce-vue";
import "tinymce/themes/silver";
import {
  toolbar,
  fontsizeFormats,
  fontFormats,
  lineheightFormats,
} from "@/utils/tinymceConfig";
import "tinymce/icons/default"; // 方式工具栏的icon乱码
export default {
  data() {
    return {
      defaultTitle: "",
      defaultAbstract: "",
      content: "",
      tinymceInit: {
        language_url: "tinymce/langs/zh-Hans.js", //引入语言包文件
        language: "zh-Hans", //语言类型
        skin_url: "tinymce/skins/ui/oxide", //皮肤:浅色
        quickbars_image_toolbar: "", // 选中媒体文件时的弹框
        // skin_url: '/tinymce/skins/ui/oxide-dark',//皮肤:暗色
        toolbar: toolbar, //工具栏配置,设为false则隐藏
        menubar: false, //菜单栏配置,设为false则隐藏,不配置则默认显示全部菜单,也可自定义配置--查看 http://tinymce.ax-z.cn/configure/editor-appearance.php --搜索“自定义菜单”
        height: 500, // 富文本高度
        model: 'dom', // 是否可拉伸富文本框
        fontsize_formats: fontsizeFormats, //字体大小
        font_formats: fontFormats, //字体样式
        lineheight_formats: lineheightFormats, //行高配置,也可配置成"12px 14px 16px 20px"这种形式
        placeholder: "在这里输入文字",
        branding: false, //tiny技术支持信息是否显示
        resize: "both", //编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可,注意引号
        statusbar: false,  //最下方的元素路径和字数统计那一栏是否显示
        paste_data_images: true, //图片是否可粘贴
        elementpath: false, //元素路径是否显示

        inline: true, //使用内联模式
        toolbar_persist: true, // 在内联模式中,工具栏是否自动显示和隐藏
        fixed_toolbar_container: "#mytoolbar", // 将工具栏显示在一个固定的html元素上
      },
    };
  },
  components: { Editor },
};
</script>

<style lang="scss">
.editor_wraper {
  width: 60%;
  height: 80vh;
  margin: 0 auto;
}
.mce-content-body{
  height: 100%;
  outline: none;
}
.titleStyle .el-textarea__inner {
  width: 100%;
  height: 56px;
  font-size: 24px;
  font-weight: 500;
  margin-top: 10px;
  padding-left: 0;
  border: none;
}
.abstractStyle .el-textarea__inner {
  font-size: 22px;
  font-weight: 500;
  color: #bbb;
  margin-top: 5px;
  padding-left: 0;
  border: none;
}
</style>

 配合好后就可以显示出以下效果

 

三、自定义工具栏按钮,实现特定功能

        创建自定义按钮一共有三步:

        1、在我的 tinymceConfig 文件中创建一个方法

/**
 * @description: 自定义按钮
 * @param {*} editor
 * @return {*}
 */
export const customBtn = (editor) => {
  // 参数一:自定义名称,需要放置在工具栏中
  editor.ui.registry.addButton("customBtn", {
    icon: "brightness", // 显示的图标
    tooltip: '自定义按钮', // 提示文字
    onAction: function () {
      console.log("点击了自定义按钮")
    }
  })
}

        2、在tinymce配置中引用它

        3、将你在addButton中声明的自定义名称放置在工具栏中

      

        最后就能显示出来创建的按钮了

        注:如果想要修改图标可以参照官网给出的一些图标
        地址:https://www.tiny.cloud/docs/tinymce/6/editor-icon-identifiers/         

 四、上传图片时去掉网络上传功能

        所谓去掉就是我自己重新自定义了一个图片上传,没有使用tinymce中的图片上传,创建自定义按钮可根据上面的方式进行创建,这里只写了如何进行图片上传

/**
 * @description: 自定义图片上传
 * @param {*} editor
 * @return {*}
 */
export const imageUpload = (editor) => {
  editor.ui.registry.addButton("imageUpload", {
    icon: "image",
    tooltip: '上传图片',
    onAction: function () {
      var input = document.createElement("input");
      input.setAttribute("type", "file");
      input.setAttribute("accept", "image/*");
      input.onchange = function () {
        var file = this.files[0];
        if (file.size / 1024 / 1024 > 20) {
          failure("上传失败,图片大小请控制在 20M 以内");
        } else {
          let formData = new FormData();
          formData.append("picture", file);
          formData.append("action", "add");
          // 上传后的逻辑
          uploadImage({ formData }).then((res) => {
            var reader = new FileReader();
            reader.onload = function () {
              var id = "blobid" + new Date().getTime();
              var blobCache = tinymce.activeEditor.editorUpload.blobCache;
              var base64 = reader.result.split(",")[1];
              var blobInfo = blobCache.create(id, file, base64);
              blobCache.add(blobInfo);
              // 将图片插入到文本中
              editor.insertContent(`<img src="${blobInfo.blobUri()}"/>`);
            };
            reader.readAsDataURL(file);
          }).catch(() => {
            failure("上传出错,服务器开小差了");
          });
        }
      };
      // 触发上传
      input.click();
    },
  });
}

五、粘贴过来的图片实现默认上传

        使用粘贴复制功能需要引入一下paste插件 直接在页面中

         import "tinymce/plugins/paste"; 引入即可,然后再配置中添加 plugins进行配置

 然后在配置中通过paste_postprocess监听复制的内容,在paste_postprocess中写粘贴后的逻辑

<script>
import tinymce from "tinymce";
import Editor from "@tinymce/tinymce-vue";
import "tinymce/themes/silver";
import {
  toolbar,
  fontsizeFormats,
  fontFormats,
  lineheightFormats,
  customBtn,
  imageUpload,
} from "@/utils/tinymceConfig";
import "tinymce/plugins/paste"; // 复制粘贴的插件
import "tinymce/icons/default"; // 方式工具栏的icon乱码
export default {
  data() {
    return {
      defaultTitle: "",
      defaultAbstract: "",
      content: "",
      tinymceInit: {
        language_url: "tinymce/langs/zh-Hans.js", //引入语言包文件
        language: "zh-Hans", //语言类型
        skin_url: "tinymce/skins/ui/oxide", //皮肤:浅色
        quickbars_image_toolbar: "", // 选中媒体文件时的弹框
        plugins: "paste", //插件配置
        // skin_url: '/tinymce/skins/ui/oxide-dark',//皮肤:暗色
        toolbar: toolbar, //工具栏配置,设为false则隐藏
        menubar: false, //菜单栏配置,设为false则隐藏,不配置则默认显示全部菜单,也可自定义配置--查看 http://tinymce.ax-z.cn/configure/editor-appearance.php --搜索“自定义菜单”
        height: 500, // 富文本高度
        model: "dom", // 是否可拉伸富文本框
        fontsize_formats: fontsizeFormats, //字体大小
        font_formats: fontFormats, //字体样式
        lineheight_formats: lineheightFormats, //行高配置,也可配置成"12px 14px 16px 20px"这种形式
        placeholder: "在这里输入文字",
        branding: false, //tiny技术支持信息是否显示
        resize: "both", //编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可,注意引号
        statusbar: false, //最下方的元素路径和字数统计那一栏是否显示
        paste_data_images: true, //图片是否可粘贴
        elementpath: false, //元素路径是否显示

        inline: true, // 使用内联模式
        toolbar_persist: true, // 在内联模式中,工具栏是否自动显示和隐藏
        fixed_toolbar_container: "#mytoolbar", // 将工具栏显示在一个固定的html元素上
        // 粘贴内容时的回调
        paste_postprocess: (plugin, args) => {
          this.$nextTick(() => {
            // 获取当前编辑器标签
            const doc = tinymce.activeEditor.getBody();
            // 循序所有标签
            doc.childNodes.forEach(async (item) => {
              const tag = item.firstChild; // 获取当前标签下的第一个标签
              // 判断当前标签或当前标签下面的第一个标签是否是一个img
              if (item.localName === "img" || tag.localName === "img") {
                const src =
                  item.localName === "img"
                    ? item.getAttribute("src")
                    : tag.getAttribute("src");
                // 如果是则拿到当前标签的图片地址,传递给后端,进行保存
                const { data } = await uploadImgByUrl({ url: src });
                // 拼接后端保存后的图片地址
                const url = process.env.VUE_APP_BASE_API + data;
                // 然后将新地址重新赋值给img标签
                item.localName === "img"
                  ? item.setAttribute("src", url)
                  : tag.setAttribute("src", url);
              }
            });
          });
        },
        // 自定义工具栏配置
        setup: function (editor) {
          // 自定义图片上传功能
          customBtn(editor);
          // 图片上传
          imageUpload(editor);
        },
      },
    };
  },
  components: { Editor },
};
</script>

六、给图片添加水印功能

        添加水印的功能,我也是单独创建了一个自定义按钮来实现,这里的添加水印是将文章中所有的图片都会添加上水印,如果有个别需求,大家可自行修改,创建自定义按钮看上面的方式即可

        如果图片过多,加水印时可能时间会稍长一些,建议是添加一个加载状态

/**
 * @description: 添加图片水印
 * @param {*} editor
 * @return {*}
 */
export const imageWatermark = (editor) => {
  editor.ui.registry.addButton("imageWatermark", {
    icon: "edit-image",
    tooltip: '添加图片水印',
    onAction: function () {
      const doc = tinymce.activeEditor.getBody()
      doc.childNodes.forEach(item => {
        const tag = item.firstChild
        // 单独修改img标签
        if (item.localName === "img" || tag.localName === "img") {
          editImages(tag)
        }
      })
    }
  })
}

/**
 * @description: 给图片添加水印
 * @param {*} child
 * @return {*}
 */
const editImages = (child) => {
  const src = child.getAttribute("src")
  var image = new Image();
  image.crossOrigin = 'anonymous';
  image.src = src;
  image.onload = async () => {
    // 创建canvas,并将创建的img绘制成canvas
    const canvas = document.createElement('canvas')
    canvas.width = child.width
    canvas.height = child.height
    const ctx = canvas.getContext('2d')
    ctx.drawImage(image, 0, 0)
    ctx.textAlign = "left"
    ctx.textBaseline = "top"
    ctx.font = "18px Microsoft Yahei"
    ctx.fillStyle = "rgba(255, 255, 255, 0.5)"
    ctx.rotate((Math.PI / 180) * 15)
    for (let i = 0; i < child.height / 120; i++) {
      for (let j = 0; j < child.width / 50; j++) {
        ctx.fillText("这是水印", i * 200, j * 100, child.width)
      }
    }
    const base64Url = canvas.toDataURL()
    setTimeout(() => {
      // 将添加好水印的图片重新插入到富文本中
      child.setAttribute("src", base64Url)
    }, 5000)
  }
}

七、实现一键排版

一键排版的方式,同样新建了一个自定义按钮,实现方式基本一样,就是在获取dom节点后判断出对应节点标签,然后给对应的标签添加不同的样式

/**
 * @description: 一键排版
 * @param {*} editor
 * @return {*}
 */
const objLabel = {
  "h1": "title",
  "h2": "title",
  "h3": "title",
  "p": "paragraph"
}
const objStyle = {
  title: {
    "font-family": "微软雅黑",
    "font-size": "22px"
  },
  paragraph: {
    "font-size": "16px",
    "color": "red"
  }
}
export const oneClickLayout = (editor) => {
  // 参数一:自定义名称,需要放置在工具栏中
  editor.ui.registry.addButton("oneClickLayout", {
    icon: "orientation", // 显示的图标
    tooltip: '一键布局', // 提示文字
    onAction: function () {
      const doc = tinymce.activeEditor.getBody()
      doc.childNodes.forEach(item => {
        // 获取需要修改的标签
        const text = objLabel[item.localName]
        if (text) {
          // 找到对应样式循环进行配置
          for (let v in objStyle[text]) {
            item.style[v] = objStyle[text][v]
          }
        }
      })
    }
  })
}

到此所有的功能就已经完成了,下面附上整个功能的代码

<template>
  <div class="editor_wraper">
    <div id="mytoolbar"></div>
    <el-input
      class="titleStyle"
      v-model="defaultTitle"
      type="textarea"
      resize="none"
      :autosize="{ minRows: 1, maxRows: 3 }"
      placeholder="请在这里输入标题"
    ></el-input>
    <el-input
      class="abstractStyle"
      v-model="defaultAbstract"
      type="textarea"
      resize="none"
      :autosize="{ minRows: 1 }"
      placeholder="摘要(可选)"
    ></el-input>
    <editor :init="tinymceInit" v-model="content"> </editor>
  </div>
</template>

<script>
import tinymce from "tinymce";
import Editor from "@tinymce/tinymce-vue";
import "tinymce/themes/silver";
import {
  toolbar,
  fontsizeFormats,
  fontFormats,
  lineheightFormats,
  customBtn,
  imageUpload,
  oneClickLayout
} from "@/utils/tinymceConfig";
import "tinymce/plugins/paste";
import "tinymce/icons/default"; // 方式工具栏的icon乱码
export default {
  data() {
    return {
      defaultTitle: "",
      defaultAbstract: "",
      content: "",
      tinymceInit: {
        language_url: "tinymce/langs/zh-Hans.js", //引入语言包文件
        language: "zh-Hans", //语言类型
        skin_url: "tinymce/skins/ui/oxide", //皮肤:浅色
        quickbars_image_toolbar: "", // 选中媒体文件时的弹框
        plugins: "paste", //插件配置
        // skin_url: '/tinymce/skins/ui/oxide-dark',//皮肤:暗色
        toolbar: toolbar, //工具栏配置,设为false则隐藏
        menubar: false, //菜单栏配置,设为false则隐藏,不配置则默认显示全部菜单,也可自定义配置--查看 http://tinymce.ax-z.cn/configure/editor-appearance.php --搜索“自定义菜单”
        height: 500, // 富文本高度
        model: "dom", // 是否可拉伸富文本框
        fontsize_formats: fontsizeFormats, //字体大小
        font_formats: fontFormats, //字体样式
        lineheight_formats: lineheightFormats, //行高配置,也可配置成"12px 14px 16px 20px"这种形式
        placeholder: "在这里输入文字",
        branding: false, //tiny技术支持信息是否显示
        resize: "both", //编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可,注意引号
        statusbar: false, //最下方的元素路径和字数统计那一栏是否显示
        paste_data_images: true, //图片是否可粘贴
        elementpath: false, //元素路径是否显示

        inline: true, // 使用内联模式
        toolbar_persist: true, // 在内联模式中,工具栏是否自动显示和隐藏
        fixed_toolbar_container: "#mytoolbar", // 将工具栏显示在一个固定的html元素上
        // 粘贴内容时的回调
        paste_postprocess: (plugin, args) => {
          this.$nextTick(() => {
            // 获取当前编辑器标签
            const doc = tinymce.activeEditor.getBody();
            // 循序所有标签
            doc.childNodes.forEach(async (item) => {
              const tag = item.firstChild; // 获取当前标签下的第一个标签
              // 判断当前标签或当前标签下面的第一个标签是否是一个img
              if (item.localName === "img" || tag.localName === "img") {
                const src =
                  item.localName === "img"
                    ? item.getAttribute("src")
                    : tag.getAttribute("src");
                // 如果是则拿到当前标签的图片地址,传递给后端,进行保存
                const { data } = await uploadImgByUrl({ url: src });
                // 拼接后端保存后的图片地址
                const url = process.env.VUE_APP_BASE_API + data;
                // 然后将新地址重新赋值给img标签
                item.localName === "img"
                  ? item.setAttribute("src", url)
                  : tag.setAttribute("src", url);
              }
            });
          });
        },
        // 自定义工具栏配置
        setup: function (editor) {
          // 自定义图片上传功能
          customBtn(editor);
          // 图片上传
          imageUpload(editor);
          // 一键布局
          oneClickLayout(editor)
        },
      },
    };
  },
  components: { Editor },
};
</script>

<style lang="scss">
.editor_wraper {
  width: 60%;
  height: 80vh;
  margin: 0 auto;
}
.mce-content-body {
  height: 100%;
  outline: none;
}
.titleStyle .el-textarea__inner {
  width: 100%;
  height: 56px;
  font-size: 24px;
  font-weight: 500;
  margin-top: 10px;
  padding-left: 0;
  border: none;
}
.abstractStyle .el-textarea__inner {
  font-size: 22px;
  font-weight: 500;
  color: #bbb;
  margin-top: 5px;
  padding-left: 0;
  border: none;
}
</style>

这里是tinymceConfig文件的代码


/**
 * @description: 工具栏配置
 * @return {*}
 */
export const toolbar = `
  customBtn imageUpload oneClickLayout|
  undo redo restoredraft |
  removeformat|
  formatselect |
  subscript superscript |
  bold italic underline strikethrough link anchor |
  numlist bullist blockquote |
  alignleft aligncenter alignright alignjustify|
  quicklink searchreplace image|
  forecolor backcolor |
  fontselect fontsizeselect|
  outdent indent lineheight|`

/**
 * @description: 字体大小设置
 * @return {*}
 */
export const fontsizeFormats = `12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px`
/**
 * @description: 字体设置
 * @return {*}
 */
export const fontFormats = `微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;`
/**
 * @description: 行高设置
 * @return {*}
 */
export const lineheightFormats = "0.5 0.8 1 1.2 1.5 1.75 2 2.5 3 4 5"

/**
 * @description: 自定义按钮
 * @param {*} editor
 * @return {*}
 */
export const customBtn = (editor) => {
  // 参数一:自定义名称,需要放置在工具栏中
  editor.ui.registry.addButton("customBtn", {
    icon: "brightness", // 显示的图标
    tooltip: '自定义按钮', // 提示文字
    onAction: function () {
      console.log("点击了自定义按钮")
    }
  })
}

/**
 * @description: 自定义图片上传
 * @param {*} editor
 * @return {*}
 */
export const imageUpload = (editor) => {
  editor.ui.registry.addButton("imageUpload", {
    icon: "image",
    tooltip: '上传图片',
    onAction: function () {
      var input = document.createElement("input");
      input.setAttribute("type", "file");
      input.setAttribute("accept", "image/*");
      input.onchange = function () {
        var file = this.files[0];
        if (file.size / 1024 / 1024 > 20) {
          failure("上传失败,图片大小请控制在 20M 以内");
        } else {
          let formData = new FormData();
          formData.append("picture", file);
          formData.append("action", "add");
          // 上传后的逻辑
          uploadImage({ formData }).then((res) => {
            var reader = new FileReader();
            reader.onload = function () {
              var id = "blobid" + new Date().getTime();
              var blobCache = tinymce.activeEditor.editorUpload.blobCache;
              var base64 = reader.result.split(",")[1];
              var blobInfo = blobCache.create(id, file, base64);
              blobCache.add(blobInfo);
              // 将图片插入到文本中
              editor.insertContent(`<img src="${blobInfo.blobUri()}"/>`);
            };
            reader.readAsDataURL(file);
          }).catch(() => {
            failure("上传出错,服务器开小差了");
          });
        }
      };
      // 触发上传
      input.click();
    },
  });
}
/**
 * @description: 一键排版
 * @param {*} editor
 * @return {*}
 */
const objLabel = {
  "h1": "title",
  "h2": "title",
  "h3": "title",
  "p": "paragraph"
}
const objStyle = {
  title: {
    "font-family": "微软雅黑",
    "font-size": "22px"
  },
  paragraph: {
    "font-size": "16px",
    "color": "red"
  }
}
export const oneClickLayout = (editor) => {
  // 参数一:自定义名称,需要放置在工具栏中
  editor.ui.registry.addButton("oneClickLayout", {
    icon: "orientation", // 显示的图标
    tooltip: '一键布局', // 提示文字
    onAction: function () {
      const doc = tinymce.activeEditor.getBody()
      doc.childNodes.forEach(item => {
        // 获取需要修改的标签
        const text = objLabel[item.localName]
        if (text) {
          // 找到对应样式循环进行配置
          for (let v in objStyle[text]) {
            item.style[v] = objStyle[text][v]
          }
        }
      })
    }
  })
}

/**
 * @description: 添加图片水印
 * @param {*} editor
 * @return {*}
 */
export const imageWatermark = (editor) => {
  editor.ui.registry.addButton("imageWatermark", {
    icon: "edit-image",
    tooltip: '添加图片水印',
    onAction: function () {
      const doc = tinymce.activeEditor.getBody()
      doc.childNodes.forEach(item => {
        const tag = item.firstChild
        // 单独修改img标签
        if (item.localName === "img" || tag.localName === "img") {
          editImages(tag)
        }
      })
    }
  })
}
/**
 * @description: 给图片添加水印
 * @param {*} child
 * @return {*}
 */
const editImages = (child) => {
  const src = child.getAttribute("src")
  var image = new Image();
  image.crossOrigin = 'anonymous';
  image.src = src;
  image.onload = async () => {
    // 创建canvas,并将创建的img绘制成canvas
    const canvas = document.createElement('canvas')
    canvas.width = child.width
    canvas.height = child.height
    const ctx = canvas.getContext('2d')
    ctx.drawImage(image, 0, 0)
    ctx.textAlign = "left"
    ctx.textBaseline = "top"
    ctx.font = "18px Microsoft Yahei"
    ctx.fillStyle = "rgba(255, 255, 255, 0.5)"
    ctx.rotate((Math.PI / 180) * 15)
    for (let i = 0; i < child.height / 120; i++) {
      for (let j = 0; j < child.width / 50; j++) {
        ctx.fillText("这是水印", i * 200, j * 100, child.width)
      }
    }
    const base64Url = canvas.toDataURL()
    setTimeout(() => {
      child.setAttribute("src", base64Url)
    }, 5000)
  }
}

这是在使用tinymce期间的一些心得,也是花了很长时间才实现的,如果还有任何问题,欢迎大家一起讨论

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/537139.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机械大专生能学会云计算吗,完全零基础的

机械大专生能学会云计算吗&#xff0c;完全零基础的 正常来说&#xff0c;大专及以上学历都能学会云计算&#xff0c;但是会和满足就业需求是两回事哈。如果你想通过学习就业&#xff0c;就需要根据当下相关岗位的普遍技术需求以及其他方面的要求&#xff0c;来针对性的学习和提…

契约锁连续入选金融信创生态实验室「金融信创优秀解决方案」

近日&#xff0c;中国人民银行直属机构--金融信创生态实验室正式公布《金融信创优秀解决方案&#xff08;第二期&#xff09;》评选结果&#xff0c;契约锁「印章管控方案」成功入选&#xff0c;并被收录在“一般业务类-产业机构”名录&#xff0c;为金融机构的印章管控及电子签…

一个简单的运筹优化生产问题求解过程

运筹相关的一个简单的生产问题 题目来自于清华大学出版的《运筹学》第四版。 一、问题描述 二、图解法 三、单纯形法 第一次迭代&#xff1a; 第二次迭代&#xff1a; 第三次迭代&#xff1a; 下面描述一下第三次迭代的详细过程&#xff1a; 从表达式&#xff08;2-18&#…

一文理解Kafka

概述 Kafka是一个基于Zookeeper的分布式消息中间件&#xff0c;支持消息分区&#xff0c;提供发布和订阅功能。使用Scala编写&#xff0c;主要特点是可水平扩展&#xff0c;高吞吐率以及高并发。 常见的使用场景&#xff1a; 企业级别活动数据和运营数据的消息传递&am…

2023年自动化测试如何学?从头开始自动化测试指南,一路晋升...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

❤ 用JS 从零开始开发一个 Chrome 提示插件(简单易学 10分钟搞定)

❤ 为自己量身手写一个chrome暖心插件&#xff08;资源文章最后&#xff09; ❤ 最近看到了一个很温馨的提示代码,于是想着为自己的浏览器做一款chrome插件 1、chrome 插件理解&#xff1a; 一个html js css image的一个web应用 不同于普通的web应用&#xff0c; chrome插…

怎么把视频转换成gif动图,5个超强工具分享

在我们平时的聊天中&#xff0c;会经常遇到需要将视频转换成gif动图的情况。这样一来&#xff0c;我们可以轻松将视频中的经典片段转换成gif动图&#xff0c;方便分享和娱乐。同时&#xff0c;这种方式不仅能够传播视频内容&#xff0c;还能带来很多趣味。 然而&#xff0c;许…

1,Hadoop的基本概念和架构

Hadoop的基本概念和架构 学习路线 hadoop的基本概念和架构hadoop的安装和配置hadoop的HDFS文件系统hadoop的MapReduce计算框架hadoop的YARN资源管理器hadoop的高级特效&#xff0c;如HBase&#xff0c;Hive&#xff0c;Pig等hadoop的优化和调优hadoop的应用场景&#xff0c;如…

Qt中的互斥锁(QMutex和QMutexLocker)

QMutex和QMutexLocker 类 QMutex 的主要函数有&#xff1a; lock ()&#xff1b; 加锁&#xff0c;如果该互斥锁被占用&#xff0c;该函数阻塞&#xff0c;直到互斥锁被释放。unlock ()&#xff1b; 解锁bool tryLock (int timeout 0)&#xff1b; 表示尝试去加锁&#xff0…

如何用R语言分析COVID-19相关数据

一、概述 COVID-19是当前全球面临的一项重大挑战。 本文将介绍如何使用R语言分析COVID-19相关数据&#xff0c;探索其感染率、死亡率和人口特征的相关性&#xff0c;以及使用统计建模方法预测COVID-19的死亡率。 二、数据导入与筛选 COVID-19 Data Repository by the Center…

CSS的使用

CSS 概述 CSS 是一门语言&#xff0c;用于控制网页表现。我们之前介绍过W3C标准。W3C标准规定了网页是由以下组成&#xff1a; 结构&#xff1a;HTML表现&#xff1a;CSS行为&#xff1a;JavaScript CSS也有一个专业的名字&#xff1a;Cascading Style Sheet&#xff08;层…

一起了解大数据可视化开发

在办公自动化快速发展的今天&#xff0c;大数据可视化开发的应用价值普遍增高。借助它的灵活、便捷、易操作等特性&#xff0c;可以助力企业实现办公自动化提质增效&#xff0c;数字化进程快速发展&#xff0c;因而得到了大家的信赖与支持。那么&#xff0c;什么是大数据可视化…

Spring Boot 使用SSL-HTTPS

Spring Boot 使用SSL-HTTPS HTTPS协议可以理解为HTTPSSL/TLS&#xff0c;可以理解为HTTP下加入了SSL层&#xff0c;通过SSL证书来验证服务器的身份&#xff0c;并为浏览器和服务器之间的通信进行加密。 SSL(Secure Socket Layer安全套接字层)&#xff1a;SSL协议位于TCP/IP协…

【Jenkins】Jenkins拉取Github代码(windows)

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化 &#x1f449;专__注&#x1f448;&#xff1a;专注主流机器人、人工智能等相关领域的开发、…

虹科新品 | 高可靠性、可适用于高磁/压的线性传感器!

PART 1 什么是线性传感器&#xff1f; 基本上&#xff0c;线性传感器是一种用于测量位移和距离的设备&#xff0c;具有高可靠性。测量网格通过光学传感器移动测量数据&#xff0c;数据被光学记录并通过控制器转换为电气数据&#xff0c;而控制器又可以转换为路径。 因此&…

怎么删除文件?分享3个文件删除的正确方法!

案例&#xff1a;怎么删除文件 【我每次想要删除文件时都感觉好麻烦啊&#xff01;想问问大家在删除文件时都是怎么进行操作的呢&#xff1f;】 在日常使用电脑的过程中&#xff0c;删除文件是一个很常见的操作&#xff0c;但是并不是每个人都知道删除文件的正确方式。正确的删…

企业做网站需要什么条件?

随着互联网的不断发展&#xff0c;企业做网站已成为市场营销的必要手段。但是&#xff0c;要想让一个网站达到预期效果&#xff0c;需要具备一定的条件和技巧。本文将从以下几个方面介绍企业做网站的条件和优化方法。 第一步&#xff1a;明确目标 企业做网站的第一步就是要明确…

【JAVAEE】线程安全的集合类及死锁

目录 1.多线程环境使用集合类 2.多线程环境使用队列 3.多线程环境使用哈希表 3.1HashTable 3.2ConcurrentHashMap 4.死锁 4.1死锁是什么 4.2死锁的代码示例 4.3产生死锁的原因 4.4如何避免死锁 这里有一个代码示例&#xff1a; 定义一个普通的集合类&#xff0c;通过…

动态规划之背包模型

文章目录 采药&#xff08;01背包&#xff09;装箱问题&#xff08;01背包&#xff09;宠物小精灵之收服(二维费用01背包&#x1f44d;&#x1f618;)数字组合(01背包)买书&#xff08;完全背包&#xff09;货币系统&#xff08;完全背包&#xff09; 采药&#xff08;01背包&a…

ROS:yaml文件解析:base_local_planner、global_costmap、local_costmap、base_local_planner

一.costmap_common_params.yaml # 设置了代价地图中障碍物信息的阀值 # obstacle_range&#xff1a;确定了最大范围传感器读数&#xff0c;这将导致障碍物被放入代价地图中。 # 此处设置为2.5m&#xff0c;意为着机器人只会更新其地图包含距离移动基座2.5m以内的障碍物信息 obs…