ushare/frontend/src/api/upload.ts
loveuer a2635698e0 init: 0.1.1
feat: login page && auth required
todo: 断点上传
2025-05-12 15:54:49 +08:00

83 lines
2.7 KiB
TypeScript

import { useState } from 'react';
interface UploadRes {
code: string
}
export const useFileUpload = () => {
const [progress, setProgress] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const uploadFile = async (file: File): Promise<string> => {
setLoading(true);
setError(null);
setProgress(0);
try {
console.log(`[D] api.Upload: upload file = ${file.name}, size = ${file.size}`, file);
const url = `/api/share/${file.name}`;
// 1. 初始化上传
const res1 = await fetch(url, {
method: "PUT",
headers: {"X-File-Size": file.size.toString()}
});
if (!res1.ok) {
console.log(`[D] upload: put file not ok, status = ${res1.status}, res = ${await res1.text()}`)
if (res1.status === 401) {
window.location.href = "/login?next=/share"
return ""
}
throw new Error("上传失败<1>");
}
const j1 = await res1.json() as UploadRes;
if (!j1.code) {
throw new Error("上传失败<2>");
}
// 2. 准备分片上传
const CHUNK_SIZE = 1024 * 1024;
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
const code = j1.code;
// 3. 上传分片
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
const start = chunkIndex * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const res = await fetch(`/api/share/${code}`, {
method: "POST",
headers: {
"Range": `bytes=${start}-${end - 1}`,
"Content-Type": "application/octet-stream"
},
body: chunk
});
if (!res.ok) {
const err = await res.text();
throw new Error(`上传失败<3>: ${err}`);
}
// 更新进度
// const currentProgress = Number(((chunkIndex + 1) / totalChunks * 100).toFixed(2)); // 小数
const currentProgress = Math.round(((chunkIndex + 1) / totalChunks) * 100); // 整数 0-100
setProgress(currentProgress);
}
return code;
} catch (err) {
throw err; // 将错误继续抛出以便组件处理
} finally {
setLoading(false);
}
};
return { uploadFile, progress, loading, error };
};