基于Vue实现微信小程序的图文编辑器


Posted in Javascript onJuly 25, 2018

由于微信小程序不能使用常规的图文编辑器(比如百度的UEditor )编辑新闻内容之类的,所以用vue写了个针对小程序用的图文编辑器。效果如下

基于Vue实现微信小程序的图文编辑器

多图上传图片用到了  ajaxfileupload.js (不知道哪位仁兄写的,拿来用了,很好用)

最终形成一串Json数据(转成字符串,传入后台存入数据库,小程序端用JSON.parse 转成JSON ,按照后台一样的方式渲染即可【小程序端代码还没写,后面再贴出来吧】)

json格式如

[{"mytype":1,"content":"测试数据\n\n11111\n\n","font":{"size":0,"weight":1,"del":1,"line":0,"center":1,"color":"#ED1C24","bgcolor":"#fff","showcolor":0}},{"mytype":3,"content":""},{"mytype":2,"content":"/upload/dyProductImgs/20180725/9841925131090216.jpg_E500_100.jpg","loading":1,"groupid":"627459ec-d372-e372-218e-b93b83cb2d02"},{"mytype":2,"content":"/upload/dyProductImgs/20180725/1574162212592205.jpg_E500_100.jpg","loading":1,"groupid":"627459ec-d372-e372-218e-b93b83cb2d02"},{"mytype":2,"content":"/upload/dyProductImgs/20180725/8745023656415428.jpg_E500_100.jpg","loading":1,"groupid":"627459ec-d372-e372-218e-b93b83cb2d02"},{"mytype":2,"content":"/upload/dyProductImgs/20180725/7027501123579481.jpg_E500_100.jpg","loading":1,"groupid":"627459ec-d372-e372-218e-b93b83cb2d02"}] 

 html代码

<div class="editor-box vue-container">
 <div class="vuefor" v-for="i in editorData.length+1" v-on:click="hidecolorbox(i-1)">
  <div class="tool-box">
   <div class="tool-box-sub">
    <div class="tool-list">
     <div v-if="reload">
      <input type="file" v-on:change.stop="uploadfile(i-1)" v-bind:id="buildfileid(i-1)" v-bind:name="buildfileid(i-1)" multiple="multiple">
     </div>
     <label class="tool-item" v-on:click.stop="itemadd(i-1,1)">
      <div class="icon"><img src="~/res/img/icon-font.png" alt="" /></div>
      <div class="text">文字</div>
     </label>
     <!--v-on:click.stop="itemadd(i-1,2)"-->
     <label class="tool-item" v-bind:for="buildfileid(i-1)">
      <div class="icon"><img src="~/res/img/icon-img.png" alt="" /></div>
      <div class="text">图片</div>
     </label>
     <label class="tool-item" v-on:click.stop="itemadd(i-1,3)">
      <div class="icon"><img src="~/res/img/icon-line.png" alt="" /></div>
      <div class="text">分割</div>
     </label>
     <label class="tool-item enabled" v-on:click.stop="itemadd(i-1,4)">
      <div class="icon"><img src="~/res/img/icon-video.png" alt="" /></div>
      <div class="text">视频</div>
     </label>
     <label class="tool-item enabled" v-on:click.stop="itemadd(i-1,5)">
      <div class="icon"><img src="~/res/img/icon-link.png" alt="" /></div>
      <div class="text">链接</div>
     </label>
    </div>
   </div>
  </div>
  <div class="editor-item" v-if="i <= editorData.length">
   <div class="head">
    <div class="h-btn fleft" v-on:click.stop="itemup(i-1)">
     <img src="~/res/img/icon-up.png" />
    </div>
    <div class="h-btn fleft" v-on:click.stop="itembottom(i-1)">
     <img src="~/res/img/icon-bottom.png" />
    </div>
    <div class="h-btn fright" v-on:click.stop="itemdel(i-1)">
     <img src="~/res/img/icon-del.png" />
    </div>
   </div>
   <div class="content" v-if="editorData[i-1].mytype==1">
    <!--文字类型的输入框-->
    <div class="text-box">
     <div class="head">
      <div title="加粗" v-on:click.stop="fontweight(i-1)" v-bind:class="{ 'head-btn': true,'sel':editorData[i-1].font.weight==1 }"><img src="~/res/img/icon-font-weight.png" alt="" /></div>
      <div title="放大字体" v-on:click.stop="fontda(i-1)" v-bind:class="{ 'head-btn': true}"><img src="~/res/img/icon-font-da.png" alt="" /></div>
      <div title="缩小字体" v-on:click.stop="fontxiao(i-1)" v-bind:class="{ 'head-btn': true}"><img src="~/res/img/icon-font-xiao.png" alt="" /></div>
      <div title="删除线" v-on:click.stop="fontdel(i-1)" v-bind:class="{ 'head-btn': true,'sel':editorData[i-1].font.del==1 }"><img src="~/res/img/icon-font-del.png" alt="" /></div>
      <div title="下划线" v-on:click.stop="fontline(i-1)" v-bind:class="{ 'head-btn': true,'sel':editorData[i-1].font.line==1 }"><img src="~/res/img/icon-font-line.png" alt="" /></div>
      <div title="居中" v-on:click.stop="fontcenter(i-1)" v-bind:class="{ 'head-btn': true,'sel':editorData[i-1].font.center==1 }"><img src="~/res/img/icon-font-center.png" alt="" /></div>
      <div title="字体颜色" v-on:click.stop="fontshowcolor(i-1)" v-bind:class="{ 'head-btn': true }" v-bind:style="initfontcolor(editorData[i-1].font)">
       A
       <div v-on:click.stop="stopclick" class="color-box" v-bind:class="{'hide':editorData[i-1].font.showcolor!=1}">
        <div class="color-title">
         字体颜色
        </div>
        <div class="color-list">
         <div class="color-item" v-for="color in colors">
          <span v-on:click.stop="fontsetcolor(i-1,color)" v-bind:style="initbgcolor(color)"></span>
         </div>
        </div>
        <div class="color-title">
         字体颜色代码
        </div>
        <div class="color-input">
         <input type="text" v-model="editorData[i-1].font.color" />
        </div>
        <!--<div class="color-title">
         字体背景颜色
        </div>
        <div class="color-list">
         <div class="color-item" v-for="color in colors">
          <span v-on:click.stop="fontsetcolor(i-1,color)" v-bind:style="initbgcolor(color)"></span>
         </div>
        </div>
        <div class="color-title">
         字体背景颜色代码
        </div>
        <div class="color-input">
         <input type="text" v-model="editorData[i-1].font.bgcolor" />
        </div>-->
       </div>
      </div>
     </div>
     <div class="line"></div>
     <div class="input">
      <textarea name="" rows="" cols="" v-bind:style="initstyle(editorData[i-1].font)" v-model="editorData[i-1].content"></textarea>
     </div>
     <div class="line"></div>
    </div>
   </div>
   <div class="content" v-if="editorData[i-1].mytype==2" style="">
    <!--图片-->
    <div class="img-box">
     <img v-if="editorData[i-1].loading==1" v-bind:src="editorData[i-1].content" alt="" />
     <img class="loading" v-if="editorData[i-1].loading==0" src="~/res/img/img_loading.gif" alt="" />
    </div>
   </div>
   <div class="content" v-if="editorData[i-1].mytype==3">
    <!--分割线-->
    <div class="line-box">
    </div>
   </div>
   <div class="clear" style=""></div>
  </div>
 </div>
</div>

js 代码

需要引用 jquery、vue、ajaxfileupload

var pageData = {
  editorData: [],
  colors: [
   "#000",
   "#7F7F7F",
   "#880015",
   "#ED1C24",
   "#FF7F27",
   "#FFF200",
   "#22B14C",
   "#3F48CC",
   "#E36C09",
   "#31859B",
   "#5F497A",
   "#76923C",
   "#953734",
   "#366092",
   "#938953",
   "#fff"
  ],
  reload:true
 };
  //初始化vue
 var vmMenu = new Vue({
  el: '.vue-container',
  data: pageData,
  methods: {
   //生成一个fileid
   buildfileid: function (index) {
    return "file" + index;
   },
   initstyle: function (font) {
    var stylestr = "";
    var fontsize = 18;
    fontsize += font.size * 3;
    stylestr += "font-size: " + fontsize + "px;"
    if (font.weight == 1) stylestr += "font-weight: bold;"
    if (font.del == 1) stylestr += "text-decoration:line-through;"
    if (font.line == 1) stylestr += "text-decoration:underline;"
    if (font.center == 1) stylestr += "text-align: center;"
    if (font.color) stylestr += ("color:" + font.color + ";");
    if (font.bgcolor) stylestr += ("display: inline;background-color:" + font.bgcolor + ";");
    return stylestr;
   },
   //字体的颜色
   initfontcolor: function (font) {
    var result = "";
    result += "color:";
    result += font.color;
    result += ";";
    result += "background-color:";
    result += font.bgcolor;
    result += ";";
    return result;
   },
   //字体背景的颜色
   initbgcolor: function (color) {
    return "background-color:" + color;
   },
   //加粗或者取消嘉措
   fontweight: function (index) {
    pageData.editorData[index].font.weight = (pageData.editorData[index].font.weight == 1 ? 0 : 1);
   },
   //字体加大
   fontda: function (index) {
    pageData.editorData[index].font.size++;
   },
   //字体减小
   fontxiao: function (index) {
    pageData.editorData[index].font.size--;
   },
   //删除线
   fontdel: function (index) {
    pageData.editorData[index].font.del = (pageData.editorData[index].font.del == 1 ? 0 : 1);
   },
   //下划线
   fontline: function (index) {
    pageData.editorData[index].font.line = (pageData.editorData[index].font.line == 1 ? 0 : 1);
   },
   //居中显示
   fontcenter: function (index) {
    pageData.editorData[index].font.center = (pageData.editorData[index].font.center == 1 ? 0 : 1);
   },
   fontshowcolor: function (index) {
    pageData.editorData[index].font.showcolor = (pageData.editorData[index].font.showcolor == 1 ? 0 : 1);
   },
   //选择字体颜色
   fontsetcolor: function (index, color) {
    pageData.editorData[index].font.color = color;
    this.hidecolorbox(index);
   },
   //隐藏颜色选择框
   hidecolorbox: function (index) {
    if (pageData.editorData && pageData.editorData.length > index && pageData.editorData[index].mytype == 1)
     pageData.editorData[index].font.showcolor = 0;
   },
   //上传图片
   uploadfile: function (index) {
    //用于强制重新渲染 input.file 用于清空之前的文件 ^_^
    pageData.reload = false;
    //添加一个组ID,方便后面上传完成后识别应该更新哪条数据
    var groupid = guid();
    var that = this;
    var fileid = "file" + index;
    var files = $("#" + fileid)[0].files;
    for (var i = 0; i < files.length; i++) {
     that.itemadd(index + i, 2, groupid);
    }
    jQuery.ajaxFileUpload({
     url: '@Url.Content("~/img/uploadproductdpicArray?path=dyProductImgs")', //用于文件上传的服务器端请求地址
     secureuri: false, //是否需要安全协议,一般设置为false
     fileElementId: fileid, //文件上传域的ID
     dataType: 'json', //返回值类型 一般设置为json
     success: function (data) //服务器成功响应处理函数
     {
      //var result = JSON.parse(data);
      pageData.reload = true;
      var result = data;
      console.log(result);
      if (result.resultState == "1") {
       var j = 0;
       for (var i = 0; i < pageData.editorData.length; i++) {
        if (pageData.editorData[i].groupid && pageData.editorData[i].groupid == groupid) {
         pageData.editorData[i].content = "@Url.Content("~")" + result.Data[j].substring(1);
         pageData.editorData[i].loading = 1;
         j++;
        }
       }
       console.log(result);
      }
      else alert("上传失败!");
     },
     error: function (data)//服务器响应失败处理函数
     {
      alert("上传失败!");
     }
    });
   },
   //上升模块
   itemup: function (index) {
    if (index > 0) {
     var itemData = pageData.editorData[index];
     pageData.editorData.splice(index, 1);
     pageData.editorData.splice(index - 1, 0, itemData);
    }
   },
   //下降模块
   itembottom: function (index) {
    if (index + 1 < pageData.editorData.length) {
     var itemData = pageData.editorData[index];
     pageData.editorData.splice(index, 1);
     pageData.editorData.splice(index + 1, 0, itemData);
    }
   },
   //删除模块
   itemdel: function (index) {
    pageData.editorData.splice(index, 1);
   },
   //添加一个新的模块
   itemadd: function (index, type, groupid) {
    var itemData = null;
    switch (type) {
     case 1:
      itemData = {
       mytype: 1,
       content: "",
       font: {
        size: 0, //字体大小 每+1 字体+2px -1同减
        weight: 0, //是否加粗
        del: 0, //是否删除线
        line: 0, //是否下划线
        center: 0, //是否居中
        color: "#000", //字体颜色
        bgcolor: "#fff", //字体颜色
        showcolor: 0 //是否显示颜色选择框
       }
      };
      break;
     case 2:
      itemData = {
       mytype: 2,
       content: "res/img/1.jpg",
       loading: 0 //是否已经成功上传
      };
      break;
     case 3:
      itemData = {
       mytype: 3,
       content: ""
      };
      break;
     default:
      alert('暂不支持');
      break;
    }
    if (itemData) {
     if (groupid) itemData.groupid = groupid;
     pageData.editorData.splice(index, 0, itemData);
    }
   },
   //一个用于阻止冒泡的事件
   stopclick: function () { },
  },
  //实例被调用后
  created: function () {
  },
  //el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
  updated: function () {
   this.$nextTick(function () {
    ////console.log(pageData);
    //var files = this.$refs.feedbakcImg;
    //for (var i = 0; i < files.length; i++) {
    // files[i].clearFiles();
    //}
   })
  }
 });

后台代码 .net (有些方法没有放出来,后面我有时间整理一个单独的demo出来放到云盘)

/// <summary>
  /// 批量上传商品详情图片
  /// </summary>
  /// <returns></returns>
  [HttpPost]
  public ContentResult uploadproductdpicArray(string path)
  {
   rData<List<string>> result = new rData<List<string>>();
   result = UpLoadPicArray(path);
   if (result.resultState == 1)
   for (int i = 0; i < result.Data.Count; i++)
   {
    if (ST.Tool.ImageHelp.GetImageSuffix(result.Data[i]) != ".gif")
    {
      string imgPath = Server.MapPath($"~{result.Data[i]}");
      string imgPathNoSuffix = imgPath.Substring(0, imgPath.LastIndexOf("."));
      string imgSuffix = ST.Tool.ImageHelp.GetImageSuffix(imgPath);
      Image oldimg = Image.FromFile(imgPath); //读取图片
      //压缩宽度为500的图片,等比 清晰度 100
      ST.Tool.ImageHelp.PicThumbnail(oldimg, imgPath + "_E500_100" + imgSuffix, 500, 0, 100);
      oldimg.Dispose();
      result.Data[i] = result.Data[i] + "_E500_100" + imgSuffix;
    }
   }
   var jsonResult = JsonConvert.SerializeObject(result);
   return new ContentResult() { Content = jsonResult };
  }
 /// <summary>
  /// 上传图片
  /// </summary>
  /// <param name="_path">保存图片的文件夹名称</param>
  /// <returns>保存结果</returns>
  private rData<string> UpLoadPic(string _path="public")
  {
   rData<string> result = new rData<string>();
   HttpFileCollectionBase _file = Request.Files;
   if (_file.Count > 0)
   {
    long size = _file[0].ContentLength;
    string type = _file[0].ContentType;
    string name = _file[0].FileName;
    //文件格式
    string _tp = Path.GetExtension(name);
    if (_tp.ToLower() == ".jpg" || _tp.ToLower() == ".jpeg" || _tp.ToLower() == ".gif" || _tp.ToLower() == ".png" || _tp.ToLower() == ".swf")
    {
     Stream stream = _file[0].InputStream;
     Image image = Image.FromStream(stream);
     string dateDir = DateTime.Now.ToString("yyyyMMdd");
     string saveName = ST.Tool.ExpandString.GetNonceNumberT(16) + _tp;
     string filePath = $"{BaseConfig.headpath}{_path}/{dateDir}/";
     string path = Server.MapPath(filePath);
     if (!Directory.Exists(path)) Directory.CreateDirectory(path);
     //_file[0].SaveAs(Server.MapPath($"{filePath}{saveName}"));
     //初始化图片对象
     //Image image = new Bitmap(Server.MapPath($"{filePath}{saveName}"));
     foreach (var p in image.PropertyItems)
     {
      if (p.Id == 0x112)
      {
       var rft = p.Value[0] == 6 ? RotateFlipType.Rotate90FlipNone
         : p.Value[0] == 3 ? RotateFlipType.Rotate180FlipNone
         : p.Value[0] == 8 ? RotateFlipType.Rotate270FlipNone
         : p.Value[0] == 1 ? RotateFlipType.RotateNoneFlipNone
         : RotateFlipType.RotateNoneFlipNone;
       p.Value[0] = 0; //旋转属性值设置为不旋转
       image.SetPropertyItem(p); //回拷进图片流
       image.RotateFlip(rft);
      }
     }
     //重新保存为正常的图片
     image.Save(Server.MapPath($"{filePath}{saveName}"));
     result.Data = $"{filePath}{saveName}";
    }
    else result.errorMsg = "只能上传图片。";
   }
   else result.errorMsg = "未选择文件";
   return result;
  }
  /// <summary>
  /// 上传多张图片
  /// </summary>
  /// <param name="_path"></param>
  /// <returns></returns>
  private rData<List<string>> UpLoadPicArray(string _path = "public")
  {
   rData<List<string>> result = new rData<List<string>>();
   result.Data = new List<string>();
   HttpFileCollectionBase _file = Request.Files;
   if (_file.Count > 0)
    for (int i = 0; i < _file.Count; i++)
    {
     //Thread.Sleep(500);
     long size = _file[i].ContentLength;
     string type = _file[i].ContentType;
     string name = _file[i].FileName;
     //文件格式
     string _tp = Path.GetExtension(name);
     if (_tp.ToLower() == ".jpg" || _tp.ToLower() == ".jpeg" || _tp.ToLower() == ".gif" || _tp.ToLower() == ".png" || _tp.ToLower() == ".swf")
     {
      Stream stream = _file[i].InputStream;
      Image image = Image.FromStream(stream);
      string dateDir = DateTime.Now.ToString("yyyyMMdd");
      string saveName = ST.Tool.ExpandString.GetNonceNumberT(16) + _tp;
      string filePath = $"{BaseConfig.headpath}{_path}/{dateDir}/";
      string path = Server.MapPath(filePath);
      if (!Directory.Exists(path)) Directory.CreateDirectory(path);
      //_file[0].SaveAs(Server.MapPath($"{filePath}{saveName}"));
      //初始化图片对象
      //Image image = new Bitmap(Server.MapPath($"{filePath}{saveName}"));
      foreach (var p in image.PropertyItems)
      {
       if (p.Id == 0x112)
       {
        var rft = p.Value[0] == 6 ? RotateFlipType.Rotate90FlipNone
          : p.Value[0] == 3 ? RotateFlipType.Rotate180FlipNone
          : p.Value[0] == 8 ? RotateFlipType.Rotate270FlipNone
          : p.Value[0] == 1 ? RotateFlipType.RotateNoneFlipNone
          : RotateFlipType.RotateNoneFlipNone;
        p.Value[0] = 0; //旋转属性值设置为不旋转
        image.SetPropertyItem(p); //回拷进图片流
        image.RotateFlip(rft);
       }
      }
      //重新保存为正常的图片
      image.Save(Server.MapPath($"{filePath}{saveName}"));
      result.Data.Add($"{filePath}{saveName}");
      //result.Data = $"{filePath}{saveName}";
     }
     else result.errorMsg = "只能上传图片。";
    }
   else result.errorMsg = "未选择文件";
   return result;
  }

总结

以上所述是小编给大家介绍的基于Vue实现微信小程序的图文编辑器,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
13 个JavaScript 性能提升技巧分享
Jul 26 Javascript
jquery获取子节点和父节点的示例代码
Sep 10 Javascript
详解JavaScript中undefined与null的区别
Mar 29 Javascript
js如何改变文章的字体大小
Jan 08 Javascript
Web前端框架bootstrap实战【第一次接触使用】
Dec 28 Javascript
Vue.js中的computed工作原理
Mar 22 Javascript
jQuery实现form表单序列化转换为json对象功能示例
May 23 jQuery
微信小程序实现打卡日历功能
Sep 21 Javascript
详解JS判断页面是在手机端还是在PC端打开的方法
Apr 26 Javascript
Vue项目中使用flow做类型检测的方法
Mar 18 Javascript
JavaScript中Object、map、weakmap的区别分析
Dec 15 Javascript
JS中箭头函数与this的写法和理解
Jan 14 Javascript
详解ECMAScript typeof用法
Jul 25 #Javascript
微信小程序动态生成二维码的实现代码
Jul 25 #Javascript
JavaScript设计模式之装饰者模式定义与应用示例
Jul 25 #Javascript
JavaScript实现的文本框placeholder提示文字功能示例
Jul 25 #Javascript
Bootstrap Fileinput 4.4.7文件上传实例详解
Jul 25 #Javascript
JavaScript简单实现关键字文本搜索高亮显示功能示例
Jul 25 #Javascript
微信小程序实现指定显示行数多余文字去掉用省略号代替
Jul 25 #Javascript
You might like
GBK的页面输出JSON格式的php函数
2010/02/16 PHP
PHP实现邮件群发的源码
2013/06/18 PHP
PHP内核探索:变量概述
2014/01/30 PHP
Mac系统完美安装PHP7详细教程
2017/06/06 PHP
JavaScipt基本教程之前言
2008/01/16 Javascript
ExtJS GridPanel 根据条件改变字体颜色
2010/03/08 Javascript
cnblogs中在闪存中屏蔽某人的实现代码
2010/11/14 Javascript
jQuery.prototype.init选择器构造函数源码思路分析
2013/02/05 Javascript
javascript在IE下trim函数无法使用的解决方法
2014/09/12 Javascript
基于jQuery实现多层次的手风琴效果附源码
2015/09/21 Javascript
JavaScript中函数表达式和函数声明及函数声明与函数表达式的不同
2015/11/15 Javascript
使用JS正则表达式 替换括号,尖括号等
2016/11/29 Javascript
angularjs之$timeout指令详解
2017/06/13 Javascript
Vue.js实现价格计算器功能
2020/03/30 Javascript
基于javascript中的typeof和类型判断(详解)
2017/10/27 Javascript
vue+element实现批量删除功能的示例
2018/02/28 Javascript
使用 js 简单的实现 bind、call 、aplly代码实例
2019/09/07 Javascript
jquery实现上传图片功能
2020/06/29 jQuery
Javascript中Math.max和Math.max.apply的区别和用法详解
2020/08/24 Javascript
python基础教程之数字处理(math)模块详解
2014/03/25 Python
跟老齐学Python之编写类之三子类
2014/10/11 Python
分析Python中设计模式之Decorator装饰器模式的要点
2016/03/02 Python
详解Python3操作Mongodb简明易懂教程
2017/05/25 Python
详解Python map函数及Python map()函数的用法
2017/11/16 Python
浅谈pytorch卷积核大小的设置对全连接神经元的影响
2020/01/10 Python
Python TestSuite生成测试报告过程解析
2020/07/23 Python
HTML5离线缓存Manifest是什么
2016/03/09 HTML / CSS
thinkphp5 redis缓存新增方法实例讲解
2021/03/24 PHP
毕业生多媒体设计求职信
2013/10/12 职场文书
秘书专业自荐信范文
2013/12/26 职场文书
2015年七夕情人节感言
2015/08/03 职场文书
《富饶的西沙群岛》教学反思
2016/02/16 职场文书
Python中快速掌握Data Frame的常用操作
2021/03/31 Python
手残删除python之后的补救方法
2021/06/26 Python
分享CSS盒子模型隐藏的几种方式
2022/02/28 HTML / CSS
DIY胆机必读:各国电子管评价
2022/04/06 无线电