refactor travel
This commit is contained in:
440
miniprogram/utils/Network.ts
Normal file
440
miniprogram/utils/Network.ts
Normal file
@ -0,0 +1,440 @@
|
||||
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<T = any>
|
||||
|
||||
extends Omit<WechatMiniprogram.RequestOption, "url" | "success" | "fail" | "complete"> {
|
||||
|
||||
/** 接口路径(相对于 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<string, string> {
|
||||
return {
|
||||
Key: wx.getStorageSync("key") || ""
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用请求方法
|
||||
*
|
||||
* @template T - 响应数据类型
|
||||
* @param options - 请求选项
|
||||
* @returns Promise<T> - 返回业务数据
|
||||
*/
|
||||
static request<T = any>(options: RequestOptions): Promise<T> {
|
||||
const {
|
||||
url,
|
||||
method = "GET",
|
||||
data,
|
||||
header = {},
|
||||
showLoading = false,
|
||||
loadingText = "加载中...",
|
||||
autoHandleError = true,
|
||||
...restOptions
|
||||
} = options;
|
||||
// 显示加载提示
|
||||
if (showLoading) {
|
||||
wx.showLoading({ title: loadingText, mask: true });
|
||||
}
|
||||
return new Promise<T>((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<T>;
|
||||
// 业务成功
|
||||
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<T = any>(url: string, data?: any, options?: Partial<RequestOptions>): Promise<T> {
|
||||
return this.request<T>({
|
||||
url,
|
||||
method: "GET",
|
||||
data,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*
|
||||
* @template T - 响应数据类型
|
||||
* @param url - 接口路径
|
||||
* @param data - 请求数据
|
||||
* @param options - 其他选项
|
||||
*/
|
||||
static post<T = any>(url: string, data?: any, options?: Partial<RequestOptions>): Promise<T> {
|
||||
return this.request<T>({
|
||||
url,
|
||||
method: "POST",
|
||||
data,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE 请求
|
||||
*
|
||||
* @template T - 响应数据类型
|
||||
* @param url - 接口路径
|
||||
* @param data - 请求数据
|
||||
* @param options - 其他选项
|
||||
*/
|
||||
static delete<T = any>(url: string, data?: any, options?: Partial<RequestOptions>): Promise<T> {
|
||||
return this.request<T>({
|
||||
url,
|
||||
method: "DELETE",
|
||||
data,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询请求
|
||||
*
|
||||
* @template T - 列表项类型
|
||||
* @param url - 接口路径
|
||||
* @param pageParams - 分页参数
|
||||
* @param options - 其他选项
|
||||
*/
|
||||
static page<T = any>(
|
||||
url: string,
|
||||
pageParams: QueryPage,
|
||||
options?: Partial<RequestOptions>
|
||||
): Promise<QueryPageResult<T>> {
|
||||
return this.post<QueryPageResult<T>>(url, pageParams, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传单个文件
|
||||
*
|
||||
* @param options - 上传选项
|
||||
* @returns Promise<string> - 返回临时文件 ID
|
||||
*/
|
||||
static uploadFile(options: UploadOptions): Promise<string> {
|
||||
const { filePath, name = "file", onProgress } = options;
|
||||
|
||||
return new Promise<string>((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<TempFileResponse[]>;
|
||||
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<string[]> - 返回临时文件 ID 列表
|
||||
*/
|
||||
static uploadFiles(options: UploadFilesOptions): Promise<string[]> {
|
||||
const { mediaList, onProgress, showLoading = true } = options;
|
||||
|
||||
return new Promise<string[]>((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<number>((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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user