-
- {t("Aborted")}
+
+
+
+
+ {file.status === "cancelled" ? t("Aborted") : t("Failed")}
+
+
+
-
-
+ {file.status === "error" && file.error && (
+
+ {file.error}
+
+ )}
)
)}
diff --git a/hooks/use-file-upload.ts b/hooks/use-file-upload.ts
index c0302fd..f938484 100644
--- a/hooks/use-file-upload.ts
+++ b/hooks/use-file-upload.ts
@@ -146,7 +146,25 @@ export function useFileUpload({ bucketInfo, userId, api }: Props) {
});
if (!response.ok) {
- throw new Error("获取预签名 URL 失败");
+ // 尝试获取后端返回的具体错误信息
+ let errorMessage = "获取预签名 URL 失败";
+ try {
+ const errorText = await response.text();
+ if (errorText) {
+ // 如果返回的是JSON格式的错误信息,尝试解析
+ try {
+ const errorData = JSON.parse(errorText);
+ errorMessage = errorData.message || errorData.error || errorText;
+ } catch {
+ // 如果不是JSON,直接使用文本内容
+ errorMessage = errorText;
+ }
+ }
+ } catch {
+ // 如果无法读取响应内容,使用默认错误信息
+ errorMessage = `上传失败 (${response.status})`;
+ }
+ throw new Error(errorMessage);
}
const data = await response.json();
@@ -331,14 +349,15 @@ export function useFileUpload({ bucketInfo, userId, api }: Props) {
await Promise.allSettled(uploadPromises);
} catch (error) {
console.error("上传失败:", error);
- // 将所有 pending 状态的文件设置为错误状态
+ // 将所有 pending 状态的文件设置为错误状态,并显示具体错误信息
+ const errorMessage = error instanceof Error ? error.message : "上传失败";
setFiles((prev) =>
prev.map((file) =>
file.status === "pending"
? {
...file,
status: "error",
- error: "上传失败",
+ error: errorMessage,
}
: file,
),
diff --git a/lib/dto/files.ts b/lib/dto/files.ts
index df7b65c..4b2a44c 100644
--- a/lib/dto/files.ts
+++ b/lib/dto/files.ts
@@ -276,7 +276,7 @@ export async function getUserFileStats(userId: string) {
success: true,
data: {
totalFiles,
- totalSize: totalSize._sum.size || 0,
+ totalSize: storageValueToBytes(totalSize._sum.size || 0),
filesByProvider,
},
};
@@ -359,3 +359,39 @@ export async function cleanupExpiredFiles(days: number = 30) {
return { success: false, error: "Failed to clean up expired files" };
}
}
+
+// 获取特定存储桶的使用量统计
+export async function getBucketStorageUsage(
+ bucket: string,
+ providerName: string,
+): Promise<
+ | { success: true; data: { totalSize: number; totalFiles: number } }
+ | { success: false; error: string }
+> {
+ try {
+ const result = await prisma.userFile.aggregate({
+ where: {
+ bucket,
+ providerName,
+ status: 1,
+ },
+ _sum: {
+ size: true,
+ },
+ _count: {
+ id: true,
+ },
+ });
+
+ return {
+ success: true,
+ data: {
+ totalSize: storageValueToBytes(result._sum.size || 0),
+ totalFiles: result._count.id || 0,
+ },
+ };
+ } catch (error) {
+ console.error("Failed to get bucket storage usage:", error);
+ return { success: false, error: "Failed to get bucket storage usage" };
+ }
+}
diff --git a/lib/r2.ts b/lib/r2.ts
index 3aa3b98..dbf54ce 100644
--- a/lib/r2.ts
+++ b/lib/r2.ts
@@ -34,6 +34,7 @@ export interface BucketItem {
prefix?: string;
file_types?: string;
file_size?: string;
+ max_storage?: string; // 存储桶最大存储容量(字节)
region?: string;
public: boolean;
}
diff --git a/locales/en.json b/locales/en.json
index 73ec486..4bbf049 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -214,6 +214,11 @@
"storageFull": "Storage space is almost full",
"storageHigh": "Storage space usage is high",
"storageGood": "Storage space is sufficient",
+ "planQuota": "Plan Quota",
+ "bucketQuota": "Bucket Quota",
+ "bucketCapacity": "Bucket Capacity",
+ "bucketStorageFull": "Bucket storage is almost full",
+ "bucketStorageHigh": "Bucket storage usage is high",
"items": "items",
"Total": "Total",
"Configuration Error": "Configuration Error"
@@ -360,6 +365,7 @@
"Uploading": "Uploading",
"Completed": "Completed",
"Aborted": "Aborted",
+ "Failed": "Failed",
"Drop files to upload them to": "Drop files to upload them to",
"Drag and drop file(s) here": "Drag and drop file(s) here",
"or": "or",
@@ -623,6 +629,8 @@
"Region": "Region",
"Prefix": "Prefix",
"Optional": "Optional",
+ "Max Storage": "Max Storage",
+ "maxStorageTooltip": "Set maximum storage capacity for this bucket (in bytes). If not set, uses plan quota global limit.",
"Allowed File Types": "Allowed File Types",
"Public": "Public",
"Publicize this storage bucket, all registered users can upload files to this storage bucket; If not public, only administrators can upload files to this storage bucket": "Publicize this storage bucket, all registered users can upload files to this storage bucket; If not public, only administrators can upload files to this storage bucket",
diff --git a/locales/zh.json b/locales/zh.json
index 237aa1e..5c87543 100644
--- a/locales/zh.json
+++ b/locales/zh.json
@@ -214,6 +214,11 @@
"storageFull": "存储空间即将用完",
"storageHigh": "存储空间使用较多",
"storageGood": "存储空间充足",
+ "planQuota": "计划配额",
+ "bucketQuota": "存储桶配额",
+ "bucketCapacity": "存储桶容量",
+ "bucketStorageFull": "存储桶空间即将用完",
+ "bucketStorageHigh": "存储桶空间使用较多",
"items": "条",
"Total": "共",
"Configuration Error": "配置错误"
@@ -360,6 +365,7 @@
"Uploading": "上传中",
"Completed": "已完成",
"Aborted": "已中止",
+ "Failed": "失败",
"Drop files to upload them to": "将文件上传到",
"Drag and drop file(s) here": "将文件拖到此处上传",
"or": "或",
@@ -623,6 +629,8 @@
"Region": "存储桶区域",
"Prefix": "前缀",
"Optional": "可选",
+ "Max Storage": "最大存储容量",
+ "maxStorageTooltip": "设置此存储桶的最大存储容量(字节)。如果不设置,默认使用 Plan 配额的全局限制。",
"Allowed File Types": "允许的文件类型",
"Public": "公开",
"Publicize this storage bucket, all registered users can upload files to this storage bucket; If not public, only administrators can upload files to this storage bucket": "公开此存储桶,所有注册用户都可以上传文件到此存储桶; 若不公开,只有管理员可以上传文件到此存储桶",