refactor travel

This commit is contained in:
Timi
2025-12-13 18:44:37 +08:00
parent 880e702288
commit 69659a1746
37 changed files with 4154 additions and 400 deletions

View 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);
});
});
}
}