微信小程序uploader上传文件并提交表单数据完整案例(接口框架WebAPI)

2023-10-27

写在前面的话

最近又自己在折腾微信小程序了。最新的一个功能中需要实现图片上传。幸运的是,微信小程序扩展能力中有现成的文件上传组件uploader可以使用,而不幸的是,这个组件坑实在太多了,而我又不是单纯的文件上传,还需要同步上传表单数据,因此各种坑,要么就是数据传不过去,要么就是后台取不到数据,折腾了我一天,各种尝试,终于搞定了。前后端完整用法记录一下,希望大家都能快速上手~

uploader介绍

uploader是微信小程序WeUI组件库中的一个图片上传的组件。大家可以在小程序开发文档中——扩展能力中找到相关用法。
在这里插入图片描述

这是一个集合了图片选择、上传、预览、删除的完整组件,属性定义也比较全面,可以自定义上传个数,有上传loading提醒和失败提醒,点击预览功能等,基本可以涵盖图片文件上传的所有功能要求。

用法也很简单,在json文件中加入引用后,在wxml文件中直接引入该组件就行,不需要跟自定义的那种文件上传一样,定义一堆标签和样式,方便多了。

官方文档有简单的使用案例:
1.在json中引入uploader组件
在这里插入图片描述2.在wxml中调用该组件,设置属性方法等

 <mp-uploader bindfail="uploadError" bindsuccess="uploadSuccess" select="{{selectFile}}" upload="{{uplaodFile}}" files="{{files}}" max-count="5" title="图片上传" tips="图片上传提示"></mp-uploader>

3.定义js中的上传方法

Page({
    data: {
        files: [{
            url: 'http://mmbiz.qpic.cn/mmbiz_png/VUIF3v9blLsicfV8ysC76e9fZzWgy8YJ2bQO58p43Lib8ncGXmuyibLY7O3hia8sWv25KCibQb7MbJW3Q7xibNzfRN7A/0',
        }, {
            loading: true
        }, {
            error: true
        }]
    },
    onLoad() {
        this.setData({
            selectFile: this.selectFile.bind(this),
            uplaodFile: this.uplaodFile.bind(this)
        })
    },

    selectFile(files) {
        console.log('files', files)
        // 返回false可以阻止某次文件上传
    },
    uplaodFile(files) {
        console.log('upload files', files)
        // 文件上传的函数,返回一个promise
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('some error')
            }, 1000)
        })
    },
    uploadError(e) {
        console.log('upload error', e.detail)
    },
    uploadSuccess(e) {
        console.log('upload success', e.detail)
    }
});

这部分代码里面其实只需要补充uplaodFile上传方法调用后台上传图片的接口,上传功能就算完整了,这算是一个可用的完整Demo。但是实际使用起来,还是需要完善一下滴。废话不多说,直接上代码~

用法与代码

小程序前端

1.如前文所说,在json中引入组件,在页面调用

 <mp-uploader bindfail="uploadError" bindsuccess="uploadSuccess" select="{{selectFile}}" upload="{{uplaodFile}}" files="{{files}}" max-count="1" title=""></mp-uploader>

因为我只需要上传一张图片,因此设置max-count等于1。
在这里插入图片描述

2.在uplaodFile中,需要调用resolve({urls})方法设置上传成功状态,否则图片会如下图所示一直显示在加载中,体验很不友好,因此先“假装”已经上传成功,等之后提交表单时再真正上传到后台。
在这里插入图片描述
修改uplaodFile方法,调用resolve({urls})方法设置上传成功状态,保存临时文件目录tempFilePaths(后面会用到)

  uplaodFile(files) {
    console.log('upload files', files);
    var that = this;
    // 文件上传的函数,返回一个promise
    return new Promise((resolve, reject) => {
      const tempFilePaths = files.tempFilePaths;
      that.setData(
        {
          filesUrl: tempFilePaths
        }
      )
      var object = {};
      object['urls'] = tempFilePaths;
      resolve(object);
    })
  },

此时图片会正常显示:
在这里插入图片描述3.在表单提交方法中调用文件上传接口。
在微信小程序中,有一个wx.uploadFile的API方法,用来将本地资源上传到服务器,同时这个方法还能上传HTTP 请求中其他额外的 form data,刚好满足我的需求。
在通用的app.js文件中定义了uploadFile 方法,参数url为后台接口路径,filePath是本地图片路径,param则是需要上传的表单数据。

///上传单个文件
const uploadFile = (url, filePath,param) => {
  return new Promise((resolve, reject) => {
    wx.uploadFile({
      url: url, //仅为示例,非真实的接口地址
      filePath:filePath,
      name: 'file',
      formData: param,
      success (res){ //上传成功
        console.log(res)
        //成功调用接口
        resolve(JSON.parse(res.data));
      },
      fail(err){
        console.log(err)
        wx.showToast({ title: '请求失败,请刷新后重试', icon: 'none' });
        reject(err)
      }
    })
  })
}

在页面调用uploadFile方法

  submitForm: function () {
    this.selectComponent('#form').validate((valid, errors) => {
      if (!valid) {   //数据校验
        const firstError = Object.keys(errors)
        if (firstError.length) {
          this.setData({
            error: errors[firstError[0]].message
          })
        }
      } else {  //校验通过,保存
        var that = this;
        var url='/api/TestAPI/Add'; //后台接口地址
        var filePath=that.data.filesUrl[0];
        var formData={   //表单数据
          '_Name': that.data.formData._Name,
          '_Description': that.data.formData._Description,
          '_Type': that.data.formData._Type,
          '_IsVisible': that.data.formData._IsAllVisible.toString(), //Boolean类型
          '_Tips': JSON.stringify(that.data.formData._tips) //Array类型
        };
        api.uploadFile(url,filePath,formData).then((res) => {  //上图图片并保存表单
            if (res.Code == "Success") {
              wx.showToast({
                title: '添加成功'
              });
              wx.navigateBack({  //返回上一页
                delta: 1,
              })
            }
        })
        .catch((err) => {
          wx.showToast({
            title: '保存失败'
          })
            })
      }
    })
  },

大家看上面的代码,可以发现我将formData数据重新赋值了一次,并且做了一次类型转化。为什么要这么麻烦呢?直接一个formData扔过去不行吗?——答案是不行,采坑记录1,后面再细说。反正这么写之后图片和数据都可以传过去了,后台再接收就可以了。

后台接口 WebAPI

后台接口我采用的是WebAPI框架,可以自动生成基于RESTful标准的接口帮助文档,很方便使用。

WebApi的接口参数有两种形式,一种是基于url的[FromUri] ,另一种则是基于表单数据的[FromBody] 。在我之前不需要上传文件,直接通过post请求获取前端表单数据时,只要使用[FromBody]参数就可以直接将前端的Json对象转化成实体类,很简单易用。
在这里插入图片描述然而使用wx.uploadFile上传数据之后,不再能接收到[FromBody] 参数。因此接口方法也需要做些调整。代码如下:

        [HttpPost]
        [Route("Add")]
        public ApiResultModel Add()
        {
            ApiResultModel result = new ApiResultModel() { Code = APIReturnCode.Error.ToString(), Message = "添加失败" };
            var hole = new TreeHole();
            HttpContextBase context = (HttpContextBase)Request.Properties["MS_HttpContext"];//获取传统context
            HttpRequestBase request = context.Request;//定义传统request对象
            hole.Name = request.Form["_Name"];
            hole.Description = request.Form["_Description"];
            hole.Type = Guid.Parse(request.Form["_Type"]);
            hole.IsAllVisible = Boolean.Parse(request.Form["_IsAllVisible"]); //获得boolean类型数据
            var obk = request.Form["_tips"];   
            var tips = JsonConvert.DeserializeObject<JArray>(obk);//获得Array类型数据
            XTransaction tran = SessionManager.Instance.BeginTransaction();
            try
            {
                string url;
                bool isUploaded = uploadImage(out url);  //保存图片
                if (!isUploaded)
                {
                    result.Message = "上传图片失败";
                    return result;
                }
                hole.Id = Guid.NewGuid();
                hole.CreateTime = DateTime.Now;
                hole.IsDelete = false;
                hole.HeadImage = url;

                //保存代码(略)
                
                result.Message = "添加成功!";
                result.Code = APIReturnCode.Success.ToString();
                result.Token = hole.Id.ToString();
            }
            catch (Exception ex)
            {
                if (tran != null) tran.RollbackTransaction();
                result.Message = ex.Message;
            }
            finally
            {
                tran.Dispose();
            }
            return result;
        }

其中图片上传方法如下:

   /// <summary>
        /// 上传图片
        /// </summary>
        /// <param name="imageUrl"></param>
        /// <returns></returns>
    public bool uploadImage(out string picturePath)
        {
            picturePath = "";
            try
            {
                const string fileTypes = "gif,jpg,jpeg,png,bmp";//允许上传的图片文件格式
                var content = Request.Content;//获取或设置 HTTP 消息的内容(当需要获取HTTP信息是会使用到)
                const string tempUploadFiles = "/UploadFile/"; //保存路径
                var newFilePath = DateTime.Now.ToString("yyyy-MM-dd") + "/";
                var memoryStreamProvider = new MultipartMemoryStreamProvider();//获取文件流信息
                Task.Run(async () => await Request.Content.ReadAsMultipartAsync(memoryStreamProvider)).Wait(); //读取数据
                foreach (var item in memoryStreamProvider.Contents)
                {
                    if (item.Headers.ContentDisposition.FileName == null) continue; //判断数据类型
                    var filename = item.Headers.ContentDisposition.FileName.Replace("\"", "");//这里获取含有双引号'" ',需去掉
                    var file = new FileInfo(filename);
                    //upload(判断是否是运行上传的图片格式)
                    if (Array.IndexOf(fileTypes.Split(','), file.Extension.Substring(1).ToLower()) == -1)
                    {
                        return false;
                    }
                    //获取后缀
                    var extension = Path.GetExtension(filename);
                    var newFileName = Guid.NewGuid().ToString() + extension;//重命名
                    if (!Directory.Exists(HostingEnvironment.MapPath("/") + tempUploadFiles + newFilePath))
                    {
                        Directory.CreateDirectory(HostingEnvironment.MapPath("/") + tempUploadFiles + newFilePath);
                    }
                    var filePath = Path.Combine(HostingEnvironment.MapPath("/") + tempUploadFiles + newFilePath, newFileName);
                    picturePath = Path.Combine(tempUploadFiles + newFilePath, newFileName);//图片相对路径
                    var result = item.ReadAsStreamAsync().Result;
                    using (var br = new BinaryReader(result))
                    {
                        var data = br.ReadBytes((int)result.Length);
                        File.WriteAllBytes(filePath, data);//保存图片
                    }
                }
                //保存成功
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

采坑记录

采坑记录一:使用wx.uploadFile中的formData传递表单数据时,Boolean类型、数组等无法传递到后台。
wx.uploadFile是客户端发起一个 HTTPS POST 请求,其中 content-type 为 multipart/form-data。按照之前的开发经验,直接将表单对象序列化,整个扔过去就行了。如下图所示:


然而小程序中没有serialize方法,直接传formData,在后台接收时会出现如下问题:
在这里插入图片描述

因此我只能采用最简单最原始的方法,将表单对象中无法传递的类型转成字符串的形式,后台接收到之后再转化一下。其中Boolean类型直接toString就好了,数组类型则需要使用JSON.stringify()转换:

'_IsVisible': that.data.formData._IsAllVisible.toString(),
'_Tips': JSON.stringify(that.data.formData._tips)

采坑记录二:WebAPI接口 MultipartFormDataStreamProvider.FormData内容为空
因为wx.uploadFile的 content-type 为 multipart/form-data类型为空,所以接口中需要通过MultipartMemoryStreamProvider来获取数据。我在网上寻找参考案例时,发现很多都是如下写法:

   string root = HttpContext.Current.Server.MapPath("~/upload/TreeHole");
            if (!System.IO.File.Exists(root))
            {
                Directory.CreateDirectory(root);
            }
                var provider = new MultipartFormDataStreamProvider(root);
   foreach (var key in provider.FormData.AllKeys)
   {
      foreach (var val in provider.FormData.GetValues(key))
      {
        str += string.Format("{0}: {1}", key, val);
      }
   }

通过遍历MultipartFormDataStreamProvider中的FormData键值来获取数据。然后我在实际操作过程中,发现这样根本就去不到数据,FormData为Null! 我不知道是传值还是哪里出了问题,最后还是通过Request来获取值。

   HttpContextBase context = (HttpContextBase)Request.Properties["MS_HttpContext"];//获取传统context
   HttpRequestBase request = context.Request;//定义传统request对象
   hole.Name = request.Form["_Name"];
   hole.Description = request.Form["_Description"];
   hole.Type = Guid.Parse(request.Form["_Type"]);
   hole.IsAllVisible = Boolean.Parse(request.Form["_IsAllVisible"]); //获得boolean类型数据
   var obk = request.Form["_tips"];   
   var tips = JsonConvert.DeserializeObject<JArray>(obk);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

微信小程序uploader上传文件并提交表单数据完整案例(接口框架WebAPI) 的相关文章

  • Windows 上使用 g++ 的 Makefile,链接库

    我已经厌倦了 MSVC 6 以及每个人总是告诉我它是一个蹩脚的编译器等等 所以现在我决定尝试使用 vim 加 g 和 makefile 这是我的问题 我有以下 makefile This is supposed to be a commen
  • C 中的变量定义是什么意思[重复]

    这个问题在这里已经有答案了 你们能告诉我 这在 C 中意味着什么吗 define Privileged Data Privileged Data static int dVariable 编译器对变量进行寻址有特殊意义吗 这只是一个宏Pri
  • MVC 重定向到没有控制器的视图

    希望应该是一个简单的 我创建了一个通用错误视图 当整个站点的操作方法内发生异常时 我想显示该视图 我创建了一个部分页面 所有导航都位于其中 因此我不需要在此视图上使用控制器 那么如何从控制器内的操作方法重定向到它 像这样的东西 HttpPo
  • 更快的算法来计算有多少数字可以被范围内的特定整数整除

    int a b c d 0 cin gt gt a gt gt b gt gt c for int i a i lt b i if i c 0 d cout lt
  • WebClient读取错误页面的内容

    我有一个加载页面内容的应用程序 我使用 WebClient 类 即使服务器返回 404 500 等错误 我也需要检索内容 我需要这样的东西 WebClient wc new WebClient string pageContent try
  • 如何使用汇编获取BIOS时间?

    我正在从头开始实现一个小型操作系统 用于教育目的 现在 我想使用汇编来获取 BIOS 时间 我对此进行了很多搜索 但找不到任何代码示例来执行此操作 如果有人可以提供任何参考或代码示例或与此相关的任何内容 我将非常感激 See 时钟中断 1a
  • 如何将字节块读入结构体

    我有一个需要处理的资源文件 它包含一组文件 首先 资源文件列出了其中包含的所有文件 以及一些其他数据 例如在此结构中 struct FileEntry byte Value1 char Filename 12 byte Value2 byt
  • F10键没被抓住

    I have a Windows Form and there overriden ProcessCmdKey However this works with all of the F Keys except for F10 I am tr
  • .net Framework (.net 4.0) 中定义 Base 3 数字的类

    我正在寻找一些可以用来定义 3 基数 三进制数 的类 有什么我可以在 net 框架中使用的东西或者我需要写一些东西吗 谢谢你的帮助 您可以使用解析Convert ToInt32 s base http msdn microsoft com
  • 指示泛型返回动态类型的对象

    这个问题是我原来问题的后续问题here https stackoverflow com questions 2541184 using a type object to create a generic 假设我有以下泛型类 简化 class
  • C# 中处理 SQL 死锁的模式?

    我正在用 C 编写一个访问 SQL Server 2005 数据库的应用程序 该应用程序是数据库密集型的 即使我尝试优化所有访问 设置适当的索引等 我预计迟早会遇到死锁 我知道为什么会发生数据库死锁 但我怀疑我能否在某个时候发布不发生死锁的
  • 如何将字符串转换为 Indian Money 格式?

    我正在尝试将字符串转换为印度货币格式 例如如果输入为 1234567 则输出应为 12 34 567 我编写了以下代码 但它没有给出预期的输出 CultureInfo hindi new CultureInfo hi IN string t
  • 是什么原因导致 Linq 错误:此方法无法转换为存储表达式?

    我有一堆具有相同 select 语句的 Linq to Entity 方法 所以我想我会很聪明 并将其分离到它自己的方法中以减少冗余 但是当我尝试运行代码时 我得到了以下内容错误 该方法不能转化为 商店表达式 这是我创建的方法 public
  • Dynamics Crm:获取状态代码/状态代码映射的元数据

    在 Dynamics CRM 2011 中 在事件实体上 状态原因 选项集 也称为状态代码 与 状态 选项集 也称为状态代码 相关 例如看这个截图 当我使用 API 检索状态原因选项集时 如下所示 RetrieveAttributeRequ
  • 你能解释一下这个C++删除问题吗?

    我有以下代码 std string F WideString ws GetMyWideString std string ret StringUtils ConvertWideStringToUTF8 ws ret return ret W
  • 如何将 CSV 文件读入 .NET 数据表

    如何将 CSV 文件加载到System Data DataTable 根据CSV文件创建数据表 常规 ADO net 功能是否允许这样做 我一直在使用OleDb提供者 但是 如果您正在读取具有数值的行 但希望将它们视为文本 则会出现问题 但
  • 如何强制执行特定的 UserControl 设计

    我正在编写一个基本用户控件 它将由一堆其他用户控件继承 我需要对所有这些后代控件强制执行某种设计 例如 顶部必须有几个按钮以及一个或两个标签 后代用户控件区域的其余部分可以自由放置任何内容 最初 我认为我可以将一个面板放到 Base Use
  • 正在获取“未终止 [] 设置”。 C# 中的错误

    我正在 C 中使用以下正则表达式 Regex find new Regex url
  • 程序退出后,TcpListener Socket 仍处于活动状态

    当我的程序退出时 我试图停止 TCP 侦听器 我不关心套接字或任何活动客户端套接字上当前活动的任何数据 套接字清理代码本质上是 try myServer Server Shutdown SocketShutdown Both catch E
  • 在何处将 CFLAG(例如 -std=gnu99)添加到 (Eclipse CDT) 自动工具项目中

    我有一个简单的 Autotools C 项目 不是 C 其框架是由 Eclipse CDT Juno 为我创建的 CFLAG 通过检查 似乎是 g O2 我希望所有生成的 make 文件也具有 std gnu99附加到 CFLAG 因为我使

随机推荐

  • supervisor托管配置nginx

    前言 阅读本文档前 请先了解如何安装配置supervisor和nginx 以下是相关学习文档 超全面 CentOS7 安装及配置supervisor CentOS 安装及配置nginx 配置 1 创建supervisor托管配置文件 详细如
  • oracle导库报959,IMP-00003: 遇到 ORACLE 错误 959

    导入前先要建好表空间和用户 建议你导出的时候按用户导出 不要用sys全部导出来还有在导入的时候需要指定导入到哪个用中去 给个操作手顺吧 我今天刚弄完的 1 导出 exp user user dbname owner user file pa
  • opencv之初学

    浅浅地记录一下自己学习opencv的过程吧 我有想毕业之后从事图像处理方面的工作 所以就从现在学起 争取明年秋招时能拿到offer吧 1 下载opencv opencv有很多的版本 我大概在网上搜了一下它的下载过程 需要在Visual St
  • UE4中文本文件配置文件Json文件XML文件的读写

    虚幻引擎中提供了与平台无关的文件读写与访问接口 通过调用 可以完成一些文件的读写 比如文本文件 配置文件 json文件 xml文件等 完成文件读写 首先需要获取文件路径等相关信息 对调用这些操作 我们需要包含头文件PlatformFilem
  • stm32水质检测系统(TDS检测,水温检测,PH检测,wifi上传,上位机显示)

    一 硬件材料清单 1 STM32核心板 2 OLED显示屏 3 PH传感器 4 TDS传感器 5 DS18B02水温传感器 6 ESP8266 二 实现的功能 1 数据的实时检测 2 本地OLED数据实时刷新 3 远程终端上位机数据显示刷新
  • Flask系列 路由系统

    Flask路由系统细分 from flask import Flask app Flask name app route def index return ok if name main app run 从这个简单的代码入口 来剖析一下路由
  • SpringIOC和AOP概念原理

    springIOC概念和原理 控制反转 把对象创建和对象之间的调用过程 交给Spring进行管理 使用IOC目的 为了耦合度降低 IOC思想是基于IOC容器完成 IOC容器底层就是对象工厂 Spring提供了IOC容器2中实现方式 俩个接口
  • 利用强化学习进行股票操作实战(一)

    利用强化学习进行股票操作实战 今天开始利用强化学习实现股票操作 我在网上找了一个简单的强化学习进行股票操作的例子 并在此基础上进行了小改动 首先讲下建模的思路 当模型发出买入指令时 我们一次性全部买入 当模型发出卖出指令时则一次性全部卖出
  • Swift 中 10 个震惊小伙伴的单行代码

    作者 uraimo 原文链接 原文日期 2016 01 06译者 bestswifter 校对 numbbbbb 定稿 小锅 几年前 函数式编程的复兴正值巅峰 一篇介绍 Scala 中 10 个单行函数式代码的博文在网上走红 很快地 一系列
  • qnx 设备驱动开发_2021年起奥迪车将换装Linux系统 此前为QNX

    车东西5月21日消息 外媒Forbes报道 奥迪官方宣布到2021年 会对旗下多款车型的信息娱乐系统进行升级换代 此前 奥迪旗下车型的信息娱乐系统基于QNX研发而来 未来将更换为Linux系统 升级后的奥迪信息娱乐系统 最大的亮点在于增加的
  • xcode,ios单元测试网络请求 AFNetworking 无法引入

    单元测试引入AFNetworking 同需要在 Podfile 引入 platform ios 7 0 target MyDemoTests do pod AFNetworking gt 2 5 0 end 否则无法引入
  • C++11中enum class的使用

    枚举类型 enumeration 使我们可以将一组整型常量组织在一起 和类一样 每个枚举类型定义了一种新的类型 枚举属于字面值常量类型 C 包含两种枚举 限定作用域的和不限定作用域的 这里主要介绍限定作用域的 不限定作用域的使用可以参考 h
  • [亲测有效]QT生成项目时候,右下角显示红色构建进度条,但是不报错,且无法生成UI界面 的解决方法。

    最近用QT5 9时候 发现生成项目莫名的慢 即使是生成过的项目也要十几秒钟才能弹出UI界面 于是我就想换一个版本用一下 于是我选择了QT 5 6 1版本 但是我发现完成项目后 点击左下角的运行按钮 右下角显示红色进度条的构建过程 更为诡异的
  • OpenLayers绘制图形

    OpenLayers的显示构成由外向内为 ol Map 地图对象 ol layer Vector 图层对象layer Map含有多个layer 最终的显示效果是由多个layer叠加而成 ol source Vector和ol style S
  • FreeRTOS系列

    1 多任务系统 1 1 前后台系统 单片机裸机开发时 一般都是在main函数里面用while 1 做一个大循环来完成所有的处理 循环中调用相应的函数完成所需的处理 有时也需要在中断中完成一些处理 相对于多任务系统而言 这就是单人单任务系统也
  • 银河麒麟V10 + 飞腾D2000(ARM64) 安装Qt

    近期有个需求是在一个特定的硬件和系统组合下开发和发布软件 具体配置是 操作系统 银河麒麟V10桌面版 CPU 飞腾D2000 ARM64 折腾了很长时间 综合了多个网络资料 最终把Qt5装好了 记录如下 Qt版本选择5 9 9 一开始选择了
  • 推送服务本地通知频次及分类管控通知

    尊敬的华为开发者 为了给用户提供更好的消息通知体验 营造清朗网络空间 从2023年9月15日开始 华为推送服务将基于 华为消息分类标准 对本地通知进行灰度管控 主要包括对应用发送的本地通知进行分类管理 以及对资讯营销消息统一进行频次管控 注
  • kibana启动失败:server is not ready yet

    kibana启动失败 server is not ready yet 这篇文章主要是解决黑马项目 学成在线 的p106中的kibana无法正常启动 首先我们在虚拟机上查看kibana启动日志 docker logs f kibana 发现报
  • Java基础回顾 : Runtime类和System类

    1 Runtime 类的使用 Runtime 类的定义特点 Runtime类的介绍 Runtime 指的是运行时 当每一个JVM 进程启动的时候 都会存在有一个Runtime 类的实例化对象 它是随着JVM 的存在而存在的 通过查看APi可
  • 微信小程序uploader上传文件并提交表单数据完整案例(接口框架WebAPI)

    文章目录 写在前面的话 uploader介绍 用法与代码 小程序前端 后台接口 WebAPI 采坑记录 写在前面的话 最近又自己在折腾微信小程序了 最新的一个功能中需要实现图片上传 幸运的是 微信小程序扩展能力中有现成的文件上传组件uplo