添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

H5 编辑器 Tinymce之解决图片上传/粘贴

TinyMCE 5是一款功能强大且灵活的富文本编辑器,可以嵌入Web应用程序中.

1.在HTML代码中 <head> 标签中引入下边的代码块

<script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>

*如果使用webpack包管理工具

 npm install tinymce
 

您也可以通过 tinyMCE 自定义自己所需要的pugins,以满足自己需求的同时,可以减少包的重量

2.在<body>中加入如下HTML代码

<h1>TinyMCE Quick Start Guide</h1> <form method="post"> <textarea id="mytextarea">Hello, World!</textarea> </form> </body>

3.新增<script>标签,调用tinymce的init方法

  <script>
      tinymce.init({
        selector: '#mytextarea'
      });
  </script>
 

当你引入tinyMCE插件时,全局自动注入tinymce对象,你可以在任何位置去调用它的方法,您可以使用console.log(tinymce)去查看它的所有方法

此时您的页面已初步构建完成,打开浏览器,可以看到如下的功能页面
在这里插入图片描述
下面我们就开始完善适合需求的各项功能组件

 window.tinymce.init({
    selector: `#mytextarea`, //选择器的标识
    language: "zh", //需要配置的语言
    //language_url: "./languages/zh_CN.js", //语言配置也可以自己引入自己本地的语言包
    width:'300px',//设置编辑器高度
    height: '300px',//设置编辑器的高度
    font_formats: //显示的自定义字体 以 xx = xx 形式显示
            "宋体=SimSun;微软雅黑=Microsoft Yahei;华文黑体=STHeiti;华文楷体=STKaiti;华文仿宋=华文仿宋;思源黑体=Source Han Sans CN;思源宋体=Source Han Serif SC;华文细黑=STXihei;黑体=SimHei;方正粗圆简体=方正粗圆简体;Andale Mono=andale mono,times;",
    fontsize_formats: "8pt 10pt 12pt 14pt 18pt 24pt 36pt",//字体的大小
    contextmenu://
            "link image imagetools inserttable | cell row column deletetable | headings",
    content_css: [ //自定义的content样式表
      "//fonts.googleapis.com/css?family=Lato:300,300i,400,400i",
      "//www.tinymce.com/css/codepen.min.css"
    //skin: "oxide-dark", //皮肤
    //statusbar: false, //底部状态栏
    body_class: "panel-body",//给iframe body标签添加class名
    object_resizing: false,
    toolbar: [ //工具栏
	    'undo redo |styleselect| bold italic forecolor backcolor | fontselect |fontsizeselect | alignleft aligncenter alignright alignjustify |' +
	    'ht| bullist numlist outdent indent | removeformat|' +
	    'link image media code codesample hr charmap preview anchor pagebreak insertdatetime me blocks' +
	    'dia emoticons fullscreen save'
    menubar: "file edit insert view format table",//顶部操作状态栏
    plugins: ['advlist anchor autolink autosave code codesample directionality emoticons fullscreen hr image imagetools media insertdatetime link lists nonbreaking noneditable pagebreak powerpaste preview print save searchreplace spellchecker tabfocus table textpattern visualblocks visualchars quickbars charmap'],//所需要的组件数组
    quickbars_selection_toolbar: //选择文本时的快捷栏
           "bold italic underline forecolor backcolor | fontselect |fontsizeselect | formatselect | quicklink blockquote",
    quickbars_image_toolbar: //选中图片时的快捷栏
            "alignleft aligncenter alignright quicklink |  imageoptions",
    //external_plugins: { //引入本地的组件包 原包许多组件需要收费,我们自行引入,但需要有资源
    //   powerpaste: "/public/tinymce/plugins/powerpaste/plugin.min.js"
   // },
   formats: { //自定义的
     alignleft: { //居左自动嵌套div元素,并添加display:flex样式
       block: "div",
       styles: { display: "flex", justifyContent: "flex-start" },
       classes: "left"
     aligncenter: {//居中
       block: "div",
       styles: {
         display: "flex",
         justifyContent: "center"
       classes: "center" //添加class名
     alignright: {//居右
       block: "div",
       styles: { display: "flex", justifyContent: "flex-end" },
       classes: "right"
   quickbars_insert_toolbar: "quickimage quicktable media", //点击空白区域的快捷工具栏 
   autosave_restore_when_empty: false, //浏览器崩溃自动保存本地
   end_container_on_empty_block: true,//是否在末尾添加空div
   powerpaste_word_import: "merge", //复制粘贴的文字样式处理 参数可以是propmt, merge, clean,效果自行切换对比
   powerpaste_html_import: "merge", //复制粘贴的html样式处理 propmt, merge, clean
   powerpaste_allow_local_images: true, //复制粘贴图书是否允许本地图片
   paste_data_images: true,
   paste_preprocess: (pluginApi, data) => {
     //console.log(data);
     // Apply custom filtering by mutating data.content
     // For example:
     const content = data.content;
     const newContent = this.yourCustomFilter(content);
     data.content = newContent;
   code_dialog_height: 450,// code 模态框的高度
   code_dialog_width: 1000,//code 模态框的宽度
   charmap_append: [],
   // advlist_bullet_styles: "circle",//列表样式
   // advlist_number_styles: "default",//数字列表样式
   imagetools_cors_hosts: ["www.tinymce.com", "codepen.io"],
   image_advtab: false,//是否显示图片高级选项
   // image_uploadtab: false,//是否显示上传图片按钮
   default_link_target: "_blank",//点击链接是否跳转新页面
   link_title: false,//链接标题
   media_live_embeds: true,
   //想要哪一个图标提供本地文件选择功能,参数可为media(媒体)、image(图片)、file(文件)
   file_picker_types: "media",//上传文件类型
   media_alt_source: false,
   //media_poster: false,//媒体的poster
   //media_filter_html: false,
   // file_picker_callback: function(callback, value, meta) {
          //   //当点击meidia图标上传时,判断meta.filetype == 'media'有必要,因为file_picker_callback是media(媒体)、image(图片)、file(文件)的共同入口
          //   console.log(meta);
          //   if (meta.filetype == "media") {
          //     //创建一个隐藏的type=file的文件选择input
          //     let input = document.createElement("input");
          //     input.setAttribute("type", "file");
          //     input.onchange = function() {
          //       let file = this.files[0]; //只选取第一个文件。如果要选取全部,后面注意做修改
          //       console.log(file);
          //       const url = "";
          //       let xhr, formData;
          //       xhr = new XMLHttpRequest();
          //       xhr.open("POST", self.apiUrl);
          //       xhr.withCredentials = self.credentials;
          //       xhr.upload.onprogress = function(e) {
          //         // 进度(e.loaded / e.total * 100)
          //         let percent = (e.loaded / e.total) * 100;
          //         if (percent < 100) {
          //           tinymce.activeEditor.setProgressState(true); //是否显示loading状态:1(显示)0(隐藏)
          //         } else {
          //           tinymce.activeEditor.setProgressState(false);
          //         }
          //       };
          //       xhr.onerror = function() {
          //         //根据自己的需要添加代码
          //         tinymce.activeEditor.setProgressState(false);
          //         return;
          //       };
          //       xhr.onload = function() {
          //         let json;
          //         if (xhr.status < 200 || xhr.status >= 300) {
          //           console.log("HTTP 错误: " + xhr.status);
          //           return;
          //         }
          //         json = JSON.parse(xhr.responseText);
          //         //假设接口返回JSON数据为{status: 0, msg: "上传成功", data: {location: "/localImgs/1546434503854.mp4"}}
          //         if (json.status == 0) {
          //           //接口返回的文件保存地址
          //           let mediaLocation = json.data.location;
          //           //cb()回调函数,将mediaLocation显示在弹框输入框中
          //           callback(mediaLocation, { title: file.name });
          //         } else {
          //           console.log(json.msg);
          //           return;
          //         }
          //       };
          //       formData = new FormData();
          //       //假设接口接收参数为file,值为选中的文件
          //       formData.append("file", file);
          //       //正式使用将下面被注释的内容恢复
          //       xhr.send(formData);
          //     };
          //     //触发点击
          //     input.click();
          //   }
          // },
   media_url_resolver: function(data, resolve) {//上传视频的自定义html代码块,必须调用resole返回代码块
     try {
       let videoUri = encodeURI(data.url);
       let embedHtml = `<p><video controls="controls" width="100%" height="auto">                                                                                                                                                                                                                                             <source src="${videoUri}" type="video/mp4" /></video></p>                                                                                                                                                                                                                                            <p style="text-align: left;">&nbsp;</p>`;
       resolve({ html: embedHtml });
     } catch (e) {
       resolve({ html: "" });
   save_enablewhendirty: true,
   nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
   init_instance_callback: editor => {//初始化执行代码
      if (_this.value) {
        editor.setContent(_this.value);
      _this.hasInit = true;
      //检测编辑器动作
      editor.on("NodeChange Change KeyUp SetContent", () => {
        if (this.value !== "") {
          this.hasChange = true;
        this.$emit("input", editor.getContent());
      });
   save_onsavecallback: () => { //点击保存按钮处理函数
      let content = window.tinymce.get(this.id).getContent();
            // 匹配并替换 任意html元素中 url 路径
            if (this.newImgUrl.length) {
              content = content.replace(
                /<img [^>]*src=['"]([^'"]+)[^>]*>/gi,
                (mactch, capture) => {
                  let current = "";
                  console.log(this.newImgUrl);
                  for (let i = 0; i < this.newImgUrl.length; i++) {
                    console.log(
                      capture.replace(/(&amp;)/gi, "&") ==
                        this.newImgUrl[i].originUrl
                    if (
                      capture.replace(/(&amp;)/gi, "&") ==
                      this.newImgUrl[i].originUrl
                      current = this.newImgUrl[i].url;
                      break;
                  // this.newImgUrl.forEach(item => {
                  //   console.log(
                  //     item.originUrl == capture.replace(/(&amp;)/gi, "&")
                  //   );
                  //   if (capture.replace(/(&amp;)/gi, "&") == item.originUrl) {
                  //     current = item.url;
                  //   }
                  // });
                  current = current ? current : capture;
                  return mactch.replace(
                    /src=[\'\"]?([^\'\"]*)[\'\"]?/i,
                    "src=" + current
            } // 匹配并替换 img中src图片路径
          setup(editor) {
            editor.on("FullscreenStateChanged", e => {
              _this.fullscreen = e.state;
            });
          images_upload_handler(blobInfo, success, failure, progress) { //图片上传处理逻辑
              //此处添加自己的上传图片逻辑代码      
          urlconverter_callback(url, node, on_save, name) {//自动检测不是自己服务器库的图片
            //设置白名单
            const assignUrl = [
              "blob:http://localhost",
              "data:image/gif;base64"
            let curl = url;
            let isInnerUrl = false; //默认不是内部链接
            try {
              assignUrl.forEach(item => {
                if (url.indexOf(item) > -1) {
                  isInnerUrl = true;
                  throw new Error("EndIterate");
              });
            } catch (e) {
              if (e.message != "EndIterate") throw e;
            if (node == "img" && !isInnerUrl) {
              let json;
              getBase64(url).then(base64 => {
                const url =	``;
                request({
                  url,
                  file: data2blob(base64, mimes["png"]),
                  filename: "file" + "." + "png"
                }).then(res => {
                  if (res.data.code == 0) {
                    _this.newImgUrl.push({
                      originUrl: curl,
                      url: res.data.data.url
                    });
                    //return res.data.data.url;
                    //deferred.resolve(res.data.url);
                  } else {
                    failure("Invalid JSON: " + res.data.data.msg);
                });
              });
            return url;
        });
 

因为复制粘贴组件powerpaste是收费项目,但又是一个完整编辑器不可或缺的功能,所以这里给出它的下载地址 https://download.csdn.net/download/wddwwwq1/12579334,请自行下载。

在这里我们重点讲解关于powerpaste的功能构建

urlconverter_callback(url, node, on_save, name) {
   //设置白名单
    const assignUrl = [
      "blob:http://localhost",
      "data:image/gif;base64",
      "http://192.168.2.221",
      "http://192.168.2.222",
      "http://192.168.2.223",
      "http://192.168.2.224",
    let curl = url;
    let isInnerUrl = false; //默认不是内部链接
    try {
      assignUrl.forEach(item => {
        if (url.indexOf(item) > -1) {
          isInnerUrl = true;
          throw new Error("EndIterate");
      });
    } catch (e) {
      if (e.message != "EndIterate") throw e;
    if (node == "img" && !isInnerUrl) {
      let json;
      getBase64(url).then(base64 => {
        const url =
          process.env.NODE_ENV !== "production"
            ? ""
            : "";
        request({
          url,
          file: data2blob(base64, mimes["png"]),
          filename: "file" + "." + "png"
        }).then(res => {
          if (res.data.code == 0) {
            _this.newImgUrl.push({
              originUrl: curl,
              url: res.data.data.url
            });
            //return res.data.data.url;
            //deferred.resolve(res.data.url);
          } else {
            failure("Invalid JSON: " + res.data.data.msg);
        });
      });
    return url;

上述代码是复制粘贴功能的核心,因为我们复制的图片不一定是自己服务器的图片,所以我们需要把其他服务器的http图片,保存在我们的服务器上。

  • 当编辑器文本中包含有图片首先我们给出一个数组assignUrl,列出您不想让编辑器检测的域名,然后使用回调函数的url去检查是否跟数组白名单的域名相匹配,如果包含,则表示是自己服务器的图片,不需要再上传服务器。

  • urlconverter_callback回调函数有四个参数(url, node, on_save, name),其中url表示检测的链接地址,node则表示此链接所在的标签名字。例如<a href="http://www.baidu.com"></a><img src="https://timgsa.baidu.com/timg.jpg" />

  • 判断node == img && !isInnerUrl,如果为true,则表明我们需要手动上传图片到我们自己的服务器,首先把http格式的图片转换为base64

//图片转换为base64
export default function(img) {
    function getBase64Image(img, width, height) { //width、height调用时传入具体像素值,控制大小 ,不传则默认图像大小
        var canvas = document.createElement("canvas");
        canvas.width = width ? width : img.width;
        canvas.height = height ? height : img.height;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
        var dataURL = canvas.toDataURL();
        return dataURL;
    var image = new Image();
    image.crossOrigin = '';
    image.src = img;
    //var deferred = $.Deferred();
    var deferred = new Deferred();
    if (img) {
        image.onload = function() {
            deferred.resolve(getBase64Image(image)); //将base64传给done上传处理
        //问题要让onload完成后再return sessionStorage['imgTest']
    } else {}
    return deferred.promise;
function Deferred() {
    var self = this;
    self.promise = new Promise(function(resolve, reject) {
        self._resolve = resolve;
        self._reject = reject;
    });
Deferred.prototype.resolve = function(data) {
    this._resolve(data);
Deferred.prototype.reject = function(data) {
    //this._reject.call(this.promise,data);
    this._reject(data);
 

这里使用canvas画图工具转换图片,最后使用canvas.toDataURL()在转换为base64的图片格式

说明:如不了解canvas,请自行学习

  • 因为上传图片是异步,并且上传需要时间,等待服务器返回图片地址时,我们已经把文本显示在编辑器之中,所以在我们return url的时候,还没有得到返回结果,由于tinymce不支持异步处理函数,我尝试用async await处理也没有实际作用,所以我们可以在点击保存文本的时候去手动替换需要替换的图片地址,在上传图片返回结果时我们把图片地址保存在一个数组之中_this.newImgUrl.push({ originUrl: curl, url: res.data.data.url });,以便在保存的时候操作回填。
 save_onsavecallback: () => {
    let content = window.tinymce.get(this.id).getContent();
            // 匹配并替换 任意html元素中 url 路径
       if (this.newImgUrl.length) {
              content = content.replace(
                /<img [^>]*src=['"]([^'"]+)[^>]*>/gi,
                (mactch, capture) => {
                  let current = "";
                  for (let i = 0; i < this.newImgUrl.length; i++) {
                    if (
                      capture.replace(/(&amp;)/gi, "&") ==
                      this.newImgUrl[i].originUrl
                      current = this.newImgUrl[i].url;
                      break;
                  current = current ? current : capture;
                  return mactch.replace(
                    /src=[\'\"]?([^\'\"]*)[\'\"]?/i,
                    "src=" + current
            } // 匹配并替换 img中src图片路径
           //...此处上传文本省略
 

因为我使用的VUE构建,代码块里的this请自行注意理解

*参考链接地址
【tinyMce官方文档】:https://www.tiny.cloud/docs/

1.4.2之后官方并没有做功能的改动,1.4.2在word复制这块没有bug,其他版本会出现手动无法转存的情况 本文使用的后台是Java。前端为Jsp(前端都一样,后台如果语言不通得自己做 Base64编码解码) 因为公司业务需要支持IE8 ,网上其实有很多富文本框,效果都很好。 例如www.wangEditor.com 但试了一圈都不支持IE8 。 所以回到Ueditor,由于官方没有维护,新的neuditor 也不知道什么时候能支持word自动转存,只能自己想办法。 如果没有必要,不建议使用.
tinymce是很优秀的一款富文本编辑器,可以去官网下载。https://www.tiny.cloud 这里分享的是它官网的一个收费插件powerpaste的旧版本源码,但也不影响功能使用。 http://blog.ncmem.com/wordpress/2019/08/07/umeditor%E7%B2%98%E8%B4%B4word%E5%9B%BE%E7%89%87/ 以vue为例说明: 将tinymce下载后放到static目录下,不用npm安装。 powerpaste放到\static\
很多时候我们用一些管理系统的时候,发布新闻、公告等文字类信息时,希望能很快的将word里面的内容直接粘贴富文本编辑器里面,然后发布出来。减少排版复杂的工作量。 下面是借用百度doc 来快速实现这个word 粘贴富文本编辑器里面 工具/原料 百度doc 任意富文本编辑器,以UEDdito为例 方法/步骤 登录,http://word.baidu.com 点击右上角 导入文档,如图所示 导入后,系统会自动将word的内容加载进去。此时 点击右上角编辑,Ctrl+A复制所有内容
这种方法是servlet,编写好在web.xml里配置servlet-class和servlet-mapping即可使用 后台(服务端)java服务代码:(上传至ROOT/lqxcPics文件夹下) <%@pagelanguage="java"import="java.util.*"pageEncoding="utf-8"%><%@ pagecontentType="text/html;charset=utf-8"%><%@ pageim...
要在tinymce/tinymce-vue富文本编辑器中实现图片上传功能,你可以按照以下步骤操作: 1. 首先,确保你已经安装了tinymcetinymce-vue的依赖包。你可以去[TinyMCE官方网站](https://www.tiny.cloud/get-tiny/self-hosted/)下载最新的TinyMCE压缩包,并解压到你的项目目录中。然后,安装tinymce-vue依赖包,可以使用npm或yarn进行安装。 2. 接下来,将TinyMCE的skins文件夹复制到你的项目的public文件夹下。这个文件夹包含了富文本编辑器的样式文件。 3. 然后,创建一个Vue组件来封装el-upload控件,并将其整合到tinymce-vue中。你可以将这个组件放在你的项目的src/components文件夹下。具体的组件代码可以参考上述提供的链接。 通过以上步骤,你就可以在tinymce/tinymce-vue富文本编辑器中实现图片上传功能了。请注意,这只是一种实现方式,具体的实现方式可能因项目需求的不同而有所差异。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [vue3 中 tinymce+tinymce-vue 富文本编辑器使用](https://blog.csdn.net/oooosadas/article/details/131176384)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [vue-tinymce 富文本编辑器自定义图片上传](https://download.csdn.net/download/hadues/13183093)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]