小程序上传进度条,还要自己监听 onProgressUpdate 吗?

你小程序里多半有一段长得像这样的上传代码:

javascript 复制代码
// pages/feedback/index.vue
export default {
  data() {
    return { uploading: false, percent: 0, uploadTask: null };
  },
  methods: {
    handleUpload() {
      uni.chooseImage({
        count: 1,
        success: res => {
          this.uploading = true;
          const task = uni.uploadFile({
            url: 'https://api.example.com/uploadImg',
            filePath: res.tempFilePaths[0],
            name: 'file',
            formData: { from: 'feedback' },
            success: () => { this.uploading = false; },
            fail: () => {
              this.uploading = false;
              uni.showToast({ title: '上传失败', icon: 'none' });
            }
          });
          task.onProgressUpdate(e => { this.percent = e.progress; });
          this.uploadTask = task; // 存起来,取消时才用得到
        }
      });
    },
    handleCancel() {
      this.uploadTask && this.uploadTask.abort();
      this.uploading = false;
      this.percent = 0;
    }
  }
};

它能跑,也上线了。但这段代码有三处不对劲:

  1. 状态是手工账。 uploading、percent、uploadTask 三个字段,得在 success / fail / 取消三条路径里挨个复位。哪天加个"失败自动清进度",漏改一条路径,进度条就永远停在 73%。
  2. 取消靠自觉。 uploadTask 引用得自己存,取消得自己调 abort(),复位得自己写。页面一销毁忘了取消,请求还在后台跑。
  3. 没法复用。 头像、意见反馈、工单附件,每个页面把五十行复制一遍改个 url。三个月后想统一加登录态过期处理,得找出所有副本挨个改。

用户那头的体感更直接:点了上传没进度,以为卡死,再点一次,服务器收到两张一样的图。

理想的上传该长什么样

把需求摊开,其实就四条:

  • 进度、加载态是响应式状态,不是手工变量,任何路径退出都自动复位;
  • 取消是内置动作,不用自己攒 task 引用;
  • 重传就是再调一次,不用把 chooseImage 到 uploadFile 整条链重走;
  • 写法和普通请求一致,上传不该是一套独立于 GET/POST 之外的方言。

第 4 条最容易被忽略,但它决定三个月后的维护成本:分页一套写法、上传又一套写法,团队每个人都在维护好几套私房封装。

顺着这份清单找,会发现这正是请求策略库在做的事。alova 的定位是一套请求 API 跑在 Web、小程序/App(uni-app/Taro)、服务端,上传只是其中一个策略场景。

换成 alova 写一遍

先装包(uni-app 适配器,注意只支持 vue3 版 uni-app):

bash 复制代码
npm install alova @alova/adapter-uniapp @alova/shared --save

建实例,请求/存储适配器一次配齐:

javascript 复制代码
// api/index.js
import { createAlova } from 'alova';
import AdapterUniapp from '@alova/adapter-uniapp';

export const alovaInst = createAlova({
  baseURL: 'https://api.example.com',
  ...AdapterUniapp(),
  responded(response) {
    const { statusCode, data } = response;
    if (statusCode >= 400) throw new Error('请求错误');
    return data || null;
  }
});

上传本身,在 method 的 config 里标 requestType: 'upload',适配器内部就改调 uni.uploadFile;data 里的 name、filePath 传给 uploadFile,其余字段自动进 formData:

html 复制代码
<template>
  <button @click="handleImageChoose">上传图片</button>
  <button v-if="loading" @click="abort">取消</button>
  <progress v-if="loading" :percent="percent" show-info />
</template>

<script setup>
import { computed } from 'vue';
import { useRequest } from 'alova/client';
import { alovaInst } from '@/api';

const uploadFile = (name, filePath, formData) =>
  alovaInst.Post(
    '/uploadImg',
    { name, filePath, ...formData }, // 额外字段自动进 formData
    { requestType: 'upload', fileType: 'image' }
  );

const { loading, uploading, send, abort } = useRequest(uploadFile, {
  immediate: false
});

// uploading = { loaded, total },响应式,直接算百分比
const percent = computed(() =>
  uploading.value.total > 0
    ? Math.floor((uploading.value.loaded / uploading.value.total) * 100)
    : 0
);

const handleImageChoose = () => {
  uni.chooseImage({
    count: 1,
    success: res => send('file', res.tempFilePaths[0], { from: 'feedback' })
  });
};
</script>

对照开头那三条清单:

  • 进度:uploading.loaded / uploading.total,响应式,没有任何一行 onProgressUpdate;
  • 加载态:loading 自动起落,失败、取消都会复位,不存在"进度条卡在 73%";
  • 取消:解构出来的 abort() 直接绑按钮,task 引用的存取是库内部的事;
  • 重传:失败后再点按钮,send() 再走一次就是重传,参数还能换。

data、loading 这些和普通请求完全同名同形,这就是清单第 4 条:上传不再是方言。团队里会写 useRequest(getList) 的人,看这段代码零学习成本。

顺带一提,如果你项目已经在用 axios,它不冲突:axios 可以作为 alova 的请求适配器继续发请求(@alova/adapter-axios),axios 负责发请求,alova 负责怎么请求。不过小程序环境用不到 axios,这里按下不表。

多文件、批量怎么办

单文件够用就到此为止。要做"九宫格传图 + 每张独立进度 + 失败单张重传",alova@3.3+ 还有个 useUploader:base64 / File / Blob / ArrayBuffer 直接丢进去,每个文件独立的 status 和 progress,upload(1, 3) 指定重传,mode: 'batch' 批量提交。要说明的是:它默认的弹窗选文件只在浏览器里有效,小程序/uni-app 环境需要通过 useUploader.selectFile 自定义选择逻辑。单文件场景用上面的 useRequest 写法就好,不必上它。


alova 是一套请求 API,跑在 Web、小程序/App(uni-app/Taro)和服务端(BFF)。上传只是入口,分页竞态、token 并发刷新、请求缓存这些"每个项目都要再写一遍"的东西,才是请求策略层真正省心的地方。

相关推荐
福兮说13 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理13 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆66614 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
凤城老人14 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)14 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan15 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_16 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒16 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技16 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu17 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub