引入 NativeTabs 替代默认 Tabs 以支持原生标签栏样式,并添加 GlassButton 组件实现毛玻璃效果按钮。 移除对 useBottomTabBarHeight 的依赖,统一使用固定底部间距 60。 重构头像上传逻辑,使用新的 uploadImage API 替代 COS 直传方案。 更新 expo-router 至 ~6.0.1 版本以支持不稳定特性。
85 lines
2.4 KiB
TypeScript
85 lines
2.4 KiB
TypeScript
import { uploadImage } from '@/services/users';
|
||
|
||
|
||
type UploadOptions = {
|
||
key: string;
|
||
// React Native COS SDK 推荐使用本地文件路径(file:// 或 content://)
|
||
srcUri?: string;
|
||
// 为兼容旧实现(Web/Blob)。在 RN SDK 下会忽略 body
|
||
body?: any;
|
||
contentType?: string;
|
||
onProgress?: (progress: { percent: number }) => void;
|
||
signal?: AbortSignal;
|
||
};
|
||
|
||
|
||
export async function uploadToCos(options: UploadOptions): Promise<{ key: string; etag?: string; headers?: Record<string, string>; publicUrl?: string }> {
|
||
const { key, srcUri, contentType, signal } = options;
|
||
|
||
if (!srcUri || typeof srcUri !== 'string') {
|
||
throw new Error('请提供本地文件路径 srcUri(形如 file:/// 或 content://)');
|
||
}
|
||
|
||
if (signal?.aborted) {
|
||
throw new DOMException('Aborted', 'AbortError');
|
||
}
|
||
|
||
try {
|
||
// 创建 FormData 用于文件上传
|
||
const formData = new FormData();
|
||
|
||
// 从 srcUri 创建文件对象
|
||
let fileBlob;
|
||
if (srcUri.startsWith('file://') || srcUri.startsWith('content://')) {
|
||
// React Native 环境,使用 fetch 读取本地文件
|
||
const response = await fetch(srcUri);
|
||
fileBlob = await response.blob();
|
||
} else {
|
||
throw new Error('不支持的文件路径格式,请使用 file:// 或 content:// 格式');
|
||
}
|
||
|
||
formData.append('file', {
|
||
uri: srcUri,
|
||
type: "image/jpeg",
|
||
name: "upload.jpg",
|
||
} as any);
|
||
|
||
|
||
console.log('formData', formData)
|
||
// 使用新的上传接口
|
||
const result = await uploadImage(formData);
|
||
|
||
console.log('result', result);
|
||
|
||
return {
|
||
key: result.fileKey,
|
||
publicUrl: result.fileUrl,
|
||
};
|
||
} catch (error: any) {
|
||
if (signal?.aborted) {
|
||
throw new DOMException('Aborted', 'AbortError');
|
||
}
|
||
console.log('uploadToCos error:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
export async function uploadWithRetry(options: UploadOptions & { maxRetries?: number; backoffMs?: number }): Promise<{ key: string; etag?: string }> {
|
||
const { maxRetries = 2, backoffMs = 800, ...rest } = options;
|
||
let attempt = 0;
|
||
// 简单指数退避
|
||
while (true) {
|
||
try {
|
||
return await uploadToCos(rest);
|
||
} catch (e: any) {
|
||
if (rest.signal?.aborted) throw e;
|
||
if (attempt >= maxRetries) throw e;
|
||
const wait = backoffMs * Math.pow(2, attempt);
|
||
await new Promise(r => setTimeout(r, wait));
|
||
attempt++;
|
||
}
|
||
}
|
||
}
|
||
|
||
|