欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-11 23:07:16
由于微信小程序不能使用常规的图文编辑器(比如百度的ueditor )编辑新闻内容之类的,所以用vue写了个针对小程序用的图文编辑器。效果如下 多图上传图片用到了&nb...

由于微信小程序不能使用常规的图文编辑器(比如百度的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实现微信小程序的图文编辑器,希望对大家有所帮助