- 新增文章详情页面,支持根据文章 ID 加载和展示文章内容 - 添加文章卡片组件,展示推荐文章的标题、封面和阅读量 - 更新文章服务,支持获取文章列表和根据 ID 获取文章详情 - 集成腾讯云 COS SDK,支持文件上传功能 - 优化打卡功能,支持按日期加载和展示打卡记录 - 更新相关依赖,确保项目兼容性和功能完整性 - 调整样式以适应新功能的展示和交互
86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
import { buildCosKey, buildPublicUrl } from '@/constants/Cos';
|
||
import { uploadWithRetry } from '@/services/cos';
|
||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||
import { Platform } from 'react-native';
|
||
|
||
export type UseCosUploadOptions = {
|
||
prefix?: string;
|
||
userId?: string;
|
||
contentType?: string;
|
||
};
|
||
|
||
export function useCosUpload(defaultOptions?: UseCosUploadOptions) {
|
||
const abortRef = useRef<AbortController | null>(null);
|
||
const [progress, setProgress] = useState(0);
|
||
const [uploading, setUploading] = useState(false);
|
||
|
||
const cancel = useCallback(() => {
|
||
abortRef.current?.abort();
|
||
}, []);
|
||
|
||
const upload = useCallback(
|
||
async (file: { uri?: string; name?: string; type?: string; buffer?: any; blob?: Blob } | Blob | string | any, options?: UseCosUploadOptions) => {
|
||
const finalOptions = { ...(defaultOptions || {}), ...(options || {}) };
|
||
const extGuess = (() => {
|
||
const name = (file && (file.name || (file as any).filename)) || '';
|
||
const match = name.match(/\.([a-zA-Z0-9]+)$/);
|
||
return match ? match[1] : undefined;
|
||
})();
|
||
const key = buildCosKey({ prefix: finalOptions.prefix, userId: finalOptions.userId, ext: extGuess });
|
||
const controller = new AbortController();
|
||
abortRef.current = controller;
|
||
setProgress(0);
|
||
setUploading(true);
|
||
try {
|
||
let res: any;
|
||
if (Platform.OS === 'web') {
|
||
// Web:使用 Blob 走 cos-js-sdk-v5 分支
|
||
let body: any = null;
|
||
if (typeof file === 'string') {
|
||
// 允许直接传 Base64/DataURL 字符串
|
||
body = file;
|
||
} else if ((file as any)?.blob) {
|
||
body = (file as any).blob;
|
||
} else if ((file as any)?.uri) {
|
||
const resp = await fetch((file as any).uri);
|
||
body = await resp.blob();
|
||
} else if (typeof Blob !== 'undefined' && file instanceof Blob) {
|
||
body = file;
|
||
} else {
|
||
throw new Error('无效的文件:请提供 uri 或 Blob');
|
||
}
|
||
res = await uploadWithRetry({
|
||
key,
|
||
body,
|
||
contentType: finalOptions.contentType || (file as any)?.type,
|
||
signal: controller.signal,
|
||
onProgress: ({ percent }: { percent: number }) => setProgress(percent),
|
||
} as any);
|
||
} else {
|
||
// 原生:直接传本地路径
|
||
const srcUri = (file as any)?.uri || (typeof file === 'string' ? file : undefined);
|
||
if (!srcUri || typeof srcUri !== 'string') {
|
||
throw new Error('请提供包含 uri 的对象,或传入本地文件路径字符串');
|
||
}
|
||
res = await uploadWithRetry({
|
||
key,
|
||
srcUri,
|
||
contentType: finalOptions.contentType || (file as any)?.type,
|
||
signal: controller.signal,
|
||
onProgress: ({ percent }: { percent: number }) => setProgress(percent),
|
||
} as any);
|
||
}
|
||
const url = (res as any).publicUrl || buildPublicUrl(res.key);
|
||
return { key: res.key, url };
|
||
} finally {
|
||
setUploading(false);
|
||
}
|
||
},
|
||
[defaultOptions]
|
||
);
|
||
|
||
return useMemo(() => ({ upload, cancel, progress, uploading }), [upload, cancel, progress, uploading]);
|
||
}
|
||
|
||
|