基于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 相关文章推荐
JavaScript基本对象
Jan 11 Javascript
在IE6下发生Internet Explorer cannot open the Internet site错误
Jun 21 Javascript
ie下动态加态js文件的方法
Sep 13 Javascript
深入学习JavaScript中的原型prototype
Aug 13 Javascript
JavaScript程序开发之JS代码放置的位置
Jan 15 Javascript
Bootstrap中点击按钮后变灰并显示加载中实例代码
Sep 23 Javascript
原生js图片轮播效果实现代码
Oct 19 Javascript
浅谈js原生拖放
Nov 21 Javascript
使用jsonp实现跨域获取数据实例讲解
Dec 25 Javascript
javascript实现循环广告条效果
Dec 12 Javascript
vue swipe自定义组件实现轮播效果
Jul 03 Javascript
jQuery利用cookie 实现本地收藏功能(不重复无需多次命名)
Nov 07 jQuery
详解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
PHP fopen 读取带中文URL地址的一点见解
2012/09/25 PHP
Codeigniter实现多文件上传并创建多个缩略图
2014/06/12 PHP
php搜索文件程序分享
2015/10/30 PHP
PHP实现导出带样式的Excel
2016/08/28 PHP
PHP 等比例缩放图片详解及实例代码
2016/09/18 PHP
JQuery与JSon实现的无刷新分页代码
2011/09/13 Javascript
javascript suggest效果 自动完成实现代码分享
2012/02/17 Javascript
Jquery操作下拉框(DropDownList)实现取值赋值
2013/08/13 Javascript
解析img图片没找到onerror事件 Stack overflow at line: 0
2013/12/23 Javascript
JQuery创建DOM节点的方法
2015/06/11 Javascript
jquery实现浮动的侧栏实例
2015/06/25 Javascript
微信小程序 点击控件后选中其它反选实例详解
2017/02/21 Javascript
vuejs绑定class和style样式
2017/04/11 Javascript
如何快速解决JS或Jquery ajax异步跨域的问题
2018/01/08 jQuery
Node.js + express实现上传大文件的方法分析【图片、文本文件】
2019/03/14 Javascript
浅析vue-router实现原理及两种模式
2020/02/11 Javascript
微信小程序实现倒计时功能
2020/11/19 Javascript
Python中使用pprint函数进行格式化输出的教程
2015/04/07 Python
python扫描proxy并获取可用代理ip的实例
2017/08/07 Python
Python编程使用tkinter模块实现计算器软件完整代码示例
2017/11/29 Python
Python基于xlrd模块操作Excel的方法示例
2018/06/21 Python
python plotly画柱状图代码实例
2019/12/13 Python
使用python matplotlib 画图导入到word中如何保证分辨率
2020/04/16 Python
Python格式化输出--%s,%d,%f的代码解析
2020/04/29 Python
python文件及目录操作代码汇总
2020/07/08 Python
python 使用csv模块读写csv格式文件的示例
2020/12/02 Python
HTML5 Blob对象的具体使用
2020/05/22 HTML / CSS
Burberry英国官网:英国标志性奢侈品牌
2017/03/29 全球购物
群胜软件Java笔试题
2012/09/29 面试题
财务会计人员求职的自我评价
2014/01/13 职场文书
庆祝教师节活动方案
2014/01/31 职场文书
食品采购员岗位职责
2014/04/14 职场文书
安全生产计划书
2014/05/04 职场文书
班主任工作经验交流材料
2014/05/13 职场文书
初中历史教学反思
2016/02/19 职场文书
优化Mysql查询的示例
2022/04/26 MySQL