基于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字符串处理函数 - split()、join()、substring()和indexOf()
Jun 02 Javascript
js预加载图片方法汇总
Jun 15 Javascript
JavaScript实现可拖拽的拖动层Div实例
Aug 05 Javascript
Node.js中process模块常用的属性和方法
Dec 13 Javascript
JS中如何实现点击a标签返回页面顶部的问题
Jan 19 Javascript
使用Bootstrap美化按钮实例代码(demo)
Feb 03 Javascript
javascript基础练习之翻转字符串与回文
Feb 20 Javascript
WebSocket实现简单客服聊天系统
May 12 Javascript
js编写简单的计时器功能
Jul 15 Javascript
详解webpack 入门与解析
Apr 09 Javascript
JS关闭子窗口并且刷新上一个窗口的实现示例
Mar 10 Javascript
JS创建自定义对象的六种方法总结
Dec 15 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
多文件上传的例子
2006/10/09 PHP
关于php fread()使用技巧
2010/01/22 PHP
PHP CURL CURLOPT参数说明(curl_setopt)
2013/09/30 PHP
php上传中文文件名乱码问题处理方案
2015/02/03 PHP
php实现文件上传及头像预览功能
2017/01/15 PHP
如何在JavaScript中实现私有属性的写类方式(一)
2013/12/04 Javascript
js中实现多态采用和继承类似的方法
2014/08/22 Javascript
浅析node.js中close事件
2014/11/26 Javascript
基于jQuery插件实现环形图标菜单旋转切换特效
2015/05/15 Javascript
jQuery实用技巧必备(中)
2015/11/03 Javascript
使用jsonp实现跨域获取数据实例讲解
2016/12/25 Javascript
AngularJS使用ng-app自动加载bootstrap框架问题分析
2017/01/04 Javascript
js中数组插入、删除元素操作的方法
2017/02/15 Javascript
JavaScript基础进阶之数组方法总结(推荐)
2017/09/04 Javascript
详解Vue开发微信H5微信分享签名失败问题解决方案
2018/08/09 Javascript
纯javascript实现选择框的全选与反选功能
2019/04/08 Javascript
layui 表格操作列按钮动态显示的实现方法
2019/09/06 Javascript
解决三元运算符 报错“SyntaxError: can''t assign to conditional expression”
2020/02/12 Javascript
JavaScript 防盗链的原理以及破解方法
2020/12/29 Javascript
[04:52]DOTA2亚洲邀请赛附加赛 TOP10精彩集锦
2015/01/29 DOTA
[02:23]1个至宝=115个英雄特效 最“绿”至宝拉比克“魔导师密钥”登场
2018/12/29 DOTA
python中使用while循环的实例
2019/08/05 Python
Python一行代码解决矩阵旋转的问题
2019/11/30 Python
tensorflow之变量初始化(tf.Variable)使用详解
2020/02/06 Python
彻底解决pip下载pytorch慢的问题方法
2021/03/01 Python
美体小铺印度官网:The Body Shop印度
2019/10/17 全球购物
学前教育教师求职自荐信
2013/09/22 职场文书
建议书标准格式
2014/03/12 职场文书
元旦联欢会策划方案
2014/06/11 职场文书
医学求职自荐信
2014/06/21 职场文书
2014年信息中心工作总结
2014/12/17 职场文书
华清池导游词
2015/02/02 职场文书
证券区域经理岗位职责
2015/04/10 职场文书
初一数学教学反思
2016/02/17 职场文书
民事调解协议书
2016/03/21 职场文书
Python极值整数的边界探讨分析
2021/09/15 Python