126 lines
3.2 KiB
TypeScript
126 lines
3.2 KiB
TypeScript
import { useState } from "react";
|
|
import * as tus from "tus-js-client";
|
|
import { env } from "../env";
|
|
import { getCompressedImageUrl } from "@nice/utils";
|
|
// useTusUpload.ts
|
|
interface UploadProgress {
|
|
fileId: string;
|
|
progress: number;
|
|
}
|
|
|
|
interface UploadResult {
|
|
compressedUrl: string;
|
|
url: string;
|
|
fileId: string;
|
|
}
|
|
|
|
export function useTusUpload() {
|
|
const [uploadProgress, setUploadProgress] = useState<
|
|
Record<string, number>
|
|
>({});
|
|
const [isUploading, setIsUploading] = useState(false);
|
|
const [uploadError, setUploadError] = useState<string | null>(null);
|
|
|
|
const getFileId = (url: string) => {
|
|
const parts = url.split("/");
|
|
const uploadIndex = parts.findIndex((part) => part === "upload");
|
|
if (uploadIndex === -1 || uploadIndex + 4 >= parts.length) {
|
|
throw new Error("Invalid upload URL format");
|
|
}
|
|
return parts.slice(uploadIndex + 1, uploadIndex + 5).join("/");
|
|
};
|
|
const getResourceUrl = (url: string) => {
|
|
const parts = url.split("/");
|
|
const uploadIndex = parts.findIndex((part) => part === "upload");
|
|
if (uploadIndex === -1 || uploadIndex + 4 >= parts.length) {
|
|
throw new Error("Invalid upload URL format");
|
|
}
|
|
const resUrl = `http://${env.SERVER_IP}:${env.UOLOAD_PORT}/uploads/${parts.slice(uploadIndex + 1, uploadIndex + 6).join("/")}`;
|
|
|
|
return resUrl;
|
|
};
|
|
const handleFileUpload = async (
|
|
file: File,
|
|
onSuccess: (result: UploadResult) => void,
|
|
onError: (error: Error) => void,
|
|
fileKey: string // 添加文件唯一标识
|
|
) => {
|
|
// if (!file || !file.name || !file.type) {
|
|
// const error = new Error("不可上传该类型文件");
|
|
// setUploadError(error.message);
|
|
// onError(error);
|
|
// return;
|
|
// }
|
|
|
|
setIsUploading(true);
|
|
setUploadProgress((prev) => ({ ...prev, [fileKey]: 0 }));
|
|
setUploadError(null);
|
|
|
|
try {
|
|
const upload = new tus.Upload(file, {
|
|
endpoint: `http://${env.SERVER_IP}:${env.SERVER_PORT}/upload`,
|
|
retryDelays: [0, 1000, 3000, 5000],
|
|
metadata: {
|
|
filename: file.name,
|
|
filetype: file.type,
|
|
size: file.size as any,
|
|
},
|
|
onProgress: (bytesUploaded, bytesTotal) => {
|
|
const progress = Number(
|
|
((bytesUploaded / bytesTotal) * 100).toFixed(2)
|
|
);
|
|
setUploadProgress((prev) => ({
|
|
...prev,
|
|
[fileKey]: progress,
|
|
}));
|
|
},
|
|
onSuccess: async (payload) => {
|
|
try {
|
|
if (upload.url) {
|
|
const fileId = getFileId(upload.url);
|
|
const url = getResourceUrl(upload.url);
|
|
setIsUploading(false);
|
|
setUploadProgress((prev) => ({
|
|
...prev,
|
|
[fileKey]: 100,
|
|
}));
|
|
onSuccess({
|
|
compressedUrl: getCompressedImageUrl(url),
|
|
url,
|
|
fileId,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
const err =
|
|
error instanceof Error
|
|
? error
|
|
: new Error("Unknown error");
|
|
setIsUploading(false);
|
|
setUploadError(err.message);
|
|
onError(err);
|
|
}
|
|
},
|
|
onError: (error) => {
|
|
setIsUploading(false);
|
|
setUploadError(error.message);
|
|
onError(error);
|
|
},
|
|
});
|
|
upload.start();
|
|
} catch (error) {
|
|
const err =
|
|
error instanceof Error ? error : new Error("Upload failed");
|
|
setIsUploading(false);
|
|
setUploadError(err.message);
|
|
onError(err);
|
|
}
|
|
};
|
|
|
|
return {
|
|
uploadProgress,
|
|
isUploading,
|
|
uploadError,
|
|
handleFileUpload,
|
|
};
|
|
}
|