import config from "../config/index"; import { Response, QueryPage, QueryPageResult, TempFileResponse } from "../types/Model"; /** 微信媒体项(用于上传) */ export interface WechatMediaItem { /** 媒体路径或 URL */ path?: string; url?: string; /** 文件大小 */ size?: number; /** 媒体类型 */ type?: string; } /** 请求选项 */ export interface RequestOptions extends Omit { /** 接口路径(相对于 baseURL) */ url: string; /** 请求数据 */ data?: T; /** 是否显示加载提示 */ showLoading?: boolean; /** 加载提示文字 */ loadingText?: string; /** 是否自动处理错误提示 */ autoHandleError?: boolean; } /** 上传进度信息 */ export interface UploadProgress { /** 总大小(字节) */ total: number; /** 已上传大小(字节) */ uploaded: number; /** 当前每秒上传大小(字节/秒) */ speed: number; /** 上传进度百分比 (0-100) */ percent: number; } /** 上传选项 */ export interface UploadOptions { /** 文件路径 */ filePath: string; /** 文件字段名 */ name?: string; /** 上传进度回调 */ onProgress?: (progress: UploadProgress) => void; } /** 批量上传选项 */ export interface UploadFilesOptions { /** 媒体文件列表 */ mediaList: WechatMediaItem[]; /** 上传进度回调 */ onProgress?: (progress: UploadProgress) => void; /** 是否显示加载提示 */ showLoading?: boolean; } /** * 网络请求工具类 * * 设计原则: * 1. 简单直接 - 不过度封装,保持 API 清晰 * 2. 类型安全 - 充分利用 TypeScript 泛型 * 3. 统一处理 - 自动添加 header、统一错误处理 * 4. Promise 化 - 提供现代化的异步 API */ export class Network { /** 基础 URL */ private static baseURL = config.url; /** 获取通用请求头 */ private static getHeaders(): Record { return { Key: wx.getStorageSync("key") || "" }; } /** * 通用请求方法 * * @template T - 响应数据类型 * @param options - 请求选项 * @returns Promise - 返回业务数据 */ static request(options: RequestOptions): Promise { const { url, method = "GET", data, header = {}, showLoading = false, loadingText = "加载中...", autoHandleError = true, ...restOptions } = options; // 显示加载提示 if (showLoading) { wx.showLoading({ title: loadingText, mask: true }); } return new Promise((resolve, reject) => { wx.request({ url: `${this.baseURL}${url}`, method: method as any, data, header: { ...this.getHeaders(), ...header }, ...restOptions, success: (res: WechatMiniprogram.RequestSuccessCallbackResult) => { if (showLoading) { wx.hideLoading(); } const response = res.data as Response; // 业务成功 if (response.code === 20000) { resolve(response.data as T); } else { // 业务失败 const error = new Error(response.msg || "请求失败"); if (autoHandleError) { wx.showToast({ title: response.msg || "请求失败", icon: "error" }); } reject(error); } }, fail: (err) => { if (showLoading) { wx.hideLoading(); } if (autoHandleError) { wx.showToast({ title: "网络请求失败", icon: "error" }); } reject(err); } }); }); } /** * GET 请求 * * @template T - 响应数据类型 * @param url - 接口路径 * @param data - 请求参数 * @param options - 其他选项 */ static get(url: string, data?: any, options?: Partial): Promise { return this.request({ url, method: "GET", data, ...options }); } /** * POST 请求 * * @template T - 响应数据类型 * @param url - 接口路径 * @param data - 请求数据 * @param options - 其他选项 */ static post(url: string, data?: any, options?: Partial): Promise { return this.request({ url, method: "POST", data, ...options }); } /** * DELETE 请求 * * @template T - 响应数据类型 * @param url - 接口路径 * @param data - 请求数据 * @param options - 其他选项 */ static delete(url: string, data?: any, options?: Partial): Promise { return this.request({ url, method: "DELETE", data, ...options }); } /** * 分页查询请求 * * @template T - 列表项类型 * @param url - 接口路径 * @param pageParams - 分页参数 * @param options - 其他选项 */ static page( url: string, pageParams: QueryPage, options?: Partial ): Promise> { return this.post>(url, pageParams, options); } /** * 上传单个文件 * * @param options - 上传选项 * @returns Promise - 返回临时文件 ID */ static uploadFile(options: UploadOptions): Promise { const { filePath, name = "file", onProgress } = options; return new Promise((resolve, reject) => { // 先获取文件大小 wx.getFileSystemManager().getFileInfo({ filePath, success: (fileInfo) => { const total = fileInfo.size; let uploaded = 0; let lastuploaded = 0; let speed = 0; // 每秒计算一次上传速度 const speedUpdateInterval = setInterval(() => { const chunkSize = uploaded - lastuploaded; speed = chunkSize; lastuploaded = uploaded; if (onProgress) { onProgress({ total, uploaded, speed, percent: Math.round((uploaded / total) * 10000) / 100 }); } }, 1000); const uploadTask = wx.uploadFile({ url: `${this.baseURL}/temp/file/upload`, filePath, name, header: this.getHeaders(), success: (res) => { // 清除定时器 clearInterval(speedUpdateInterval); try { const response = JSON.parse(res.data) as Response; if (response.code === 20000) { resolve(response.data[0].id); } else { reject(new Error(response.msg || "文件上传失败")); } } catch (error) { reject(new Error("解析上传响应失败")); } }, fail: (err) => { // 清除定时器 clearInterval(speedUpdateInterval); reject(err); } }); // 监听上传进度 uploadTask.onProgressUpdate((res) => { uploaded = Math.floor((res.totalBytesExpectedToSend * res.progress) / 100); if (onProgress) { onProgress({ total, uploaded, speed, percent: res.progress }); } }); }, fail: (err) => { reject(new Error(`获取文件信息失败: ${err.errMsg}`)); } }); }); } /** * 批量上传文件 * * @param options - 批量上传选项 * @returns Promise - 返回临时文件 ID 列表 */ static uploadFiles(options: UploadFilesOptions): Promise { const { mediaList, onProgress, showLoading = true } = options; return new Promise((resolve, reject) => { // 空列表直接返回 if (mediaList.length === 0) { resolve([]); return; } if (showLoading) { wx.showLoading({ title: "正在上传..", mask: true }); } // 先获取所有文件的大小 const sizePromises = mediaList.map(item => { const filePath = item.path || item.url || ""; return new Promise((sizeResolve, sizeReject) => { wx.getFileSystemManager().getFileInfo({ filePath, success: (res) => sizeResolve(res.size), fail: (err) => sizeReject(err) }); }); }); Promise.all(sizePromises).then(fileSizes => { const total = fileSizes.reduce((acc, size) => acc + size, 0); const tempFileIds: string[] = []; let uploaded = 0; let lastuploaded = 0; let speed = 0; // 每秒计算一次上传速度 const speedUpdateInterval = setInterval(() => { const chunkSize = uploaded - lastuploaded; speed = chunkSize; lastuploaded = uploaded; if (onProgress) { onProgress({ total, uploaded, speed, percent: Math.round((uploaded / total) * 10000) / 100 }); } }, 1000); // 串行上传(避免并发过多) const uploadNext = (index: number) => { if (index >= mediaList.length) { // 清除定时器 clearInterval(speedUpdateInterval); if (showLoading) { wx.hideLoading(); } resolve(tempFileIds); return; } const media = mediaList[index]; const filePath = media.path || media.url || ""; let prevProgress = 0; this.uploadFile({ filePath, onProgress: (progressResult) => { // 计算当前文件的增量上传大小 const fileUploaded = (progressResult.total * progressResult.percent) / 100; const delta = fileUploaded - prevProgress; uploaded += delta; prevProgress = fileUploaded; if (onProgress) { onProgress({ total, uploaded, speed, percent: Math.round((uploaded / total) * 10000) / 100 }); } } }).then((tempFileId) => { tempFileIds.push(tempFileId); // 继续上传下一个 uploadNext(index + 1); }).catch((err) => { // 清除定时器 clearInterval(speedUpdateInterval); if (showLoading) { wx.hideLoading(); } wx.showToast({ title: "文件上传失败", icon: "error" }); reject(err); }); }; // 开始上传第一个文件 uploadNext(0); }).catch((err) => { if (showLoading) { wx.hideLoading(); } wx.showToast({ title: "获取文件信息失败", icon: "error" }); reject(err); }); }); } }