feat: 更新文章功能和相关依赖

- 新增文章详情页面,支持根据文章 ID 加载和展示文章内容
- 添加文章卡片组件,展示推荐文章的标题、封面和阅读量
- 更新文章服务,支持获取文章列表和根据 ID 获取文章详情
- 集成腾讯云 COS SDK,支持文件上传功能
- 优化打卡功能,支持按日期加载和展示打卡记录
- 更新相关依赖,确保项目兼容性和功能完整性
- 调整样式以适应新功能的展示和交互
This commit is contained in:
richarjiang
2025-08-14 16:03:19 +08:00
parent 532cf251e2
commit 5d09cc05dc
24 changed files with 1953 additions and 513 deletions

View File

@@ -1,6 +1,7 @@
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;
@@ -31,37 +32,44 @@ export function useCosUpload(defaultOptions?: UseCosUploadOptions) {
setProgress(0);
setUploading(true);
try {
let body: any = null;
// 1) 直接可用类型Blob 或 string
if (typeof file === 'string') {
body = file;
} else if (typeof Blob !== 'undefined' && file instanceof Blob) {
body = file;
} else if ((file as any)?.blob && (typeof Blob === 'undefined' || (file as any).blob instanceof Blob || (file as any).blob?._data)) {
// 2) 已提供 blob 字段
body = (file as any).blob;
} else if ((file as any)?.buffer) {
// 3) ArrayBuffer/TypedArray -> Blob
const buffer = (file as any).buffer;
body = new Blob([buffer], { type: (file as any)?.type || finalOptions.contentType || 'application/octet-stream' });
} else if ((file as any)?.uri) {
// 4) Expo ImagePicker/文件:必须先转 Blob
const resp = await fetch((file as any).uri);
body = await resp.blob();
} else {
// 兜底:尝试直接作为字符串,否则抛错
if (file && (typeof file === 'object')) {
throw new Error('无效的上传体:请提供 Blob/String或包含 uri 的对象');
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');
}
body = file;
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 res = await uploadWithRetry({
key,
body,
contentType: finalOptions.contentType || (file as any)?.type,
signal: controller.signal,
onProgress: ({ percent }) => setProgress(percent),
});
const url = (res as any).publicUrl || buildPublicUrl(res.key);
return { key: res.key, url };
} finally {