feat(desktop): 收口桌面工作台视觉与活动反馈

This commit is contained in:
Cheng Zhou
2026-07-02 15:52:16 +08:00
parent 3a866accd9
commit 84d5daebab
19 changed files with 2027 additions and 222 deletions
@@ -154,6 +154,7 @@ import { isApiPermissionAllowed } from "../../utils/apiPermissionValue";
import { TEXT } from "../../locales";
import { clientRuntime } from "../../runtime";
import { openFileWithFeedback, pickFilesWithFeedback, saveFileWithFeedback } from "../../utils/fileTaskFeedback";
import { finishDesktopActivity, startDesktopActivity, updateDesktopActivity } from "../../session/desktopActivityCenter";
type AttachmentEntityGroup = {
entityType: string;
@@ -253,6 +254,7 @@ const tableProgress = computed(() => {
return group ? progressMap[group.key] || 0 : 0;
});
const isImmediateUploading = computed(() => tableProgress.value > 0 && tableProgress.value < 100);
const desktopActivityEnabled = () => nativeFiles;
const validateFile = (file: File) => {
if (file.size > maxSize.value * 1024 * 1024) {
@@ -293,14 +295,21 @@ const pendingSnapshot = () =>
uploadGroups.value.flatMap((group) => (pendingMap[group.key] || []).map((item) => `${group.key}:${pendingFileKey(item.file)}`));
const hasPendingUploads = computed(() => uploadGroups.value.some((group) => (pendingMap[group.key] || []).length > 0));
const uploadFile = async (group: UploadGroup, file: File, targetEntityId: string) => {
const uploadFile = async (
group: UploadGroup,
file: File,
targetEntityId: string,
onProgress?: (progress: number) => void,
) => {
if (!props.studyId || !targetEntityId) return;
if (!validateFile(file)) throw new Error("invalid file");
progressMap[group.key] = 0;
await uploadAttachment(props.studyId, group.entityType, targetEntityId, file, {
onUploadProgress: (evt: ProgressEvent) => {
if (evt.total) {
progressMap[group.key] = Math.round((evt.loaded / evt.total) * 100);
const progress = Math.round((evt.loaded / evt.total) * 100);
progressMap[group.key] = progress;
onProgress?.(progress);
}
},
});
@@ -313,6 +322,16 @@ const uploadPending = async (entityId?: string) => {
}
if (!targetEntityId) return;
let hasFailure = false;
const totalCount = uploadGroups.value.reduce((sum, group) => sum + (pendingMap[group.key] || []).length, 0);
const activityId = desktopActivityEnabled() && totalCount > 0
? startDesktopActivity({
kind: "upload",
title: "上传附件",
detail: `正在上传 ${totalCount} 个附件`,
progress: 0,
})
: "";
let completedCount = 0;
for (const group of uploadGroups.value) {
const items = [...(pendingMap[group.key] || [])];
const remainItems: PendingUploadItem[] = [];
@@ -320,19 +339,36 @@ const uploadPending = async (entityId?: string) => {
try {
item.status = "uploading";
item.error = "";
await uploadFile(group, item.file, targetEntityId);
await uploadFile(group, item.file, targetEntityId, (progress) => {
if (!activityId || !totalCount) return;
updateDesktopActivity(activityId, {
detail: `正在上传 ${completedCount + 1}/${totalCount}`,
progress: Math.min(99, Math.round(((completedCount + progress / 100) / totalCount) * 100)),
});
});
} catch (e: any) {
hasFailure = true;
item.status = "failed";
item.error = e?.response?.data?.message || TEXT.common.messages.uploadFailed;
remainItems.push(item);
} finally {
completedCount += 1;
if (activityId && totalCount) {
updateDesktopActivity(activityId, {
detail: `已处理 ${completedCount}/${totalCount}`,
progress: Math.min(99, Math.round((completedCount / totalCount) * 100)),
});
}
progressMap[group.key] = 0;
}
}
pendingMap[group.key] = remainItems;
}
if (hasFailure) throw new Error(TEXT.common.messages.uploadFailed);
if (hasFailure) {
finishDesktopActivity(activityId, "failed", { detail: "部分附件上传失败" });
throw new Error(TEXT.common.messages.uploadFailed);
}
finishDesktopActivity(activityId, "completed", { detail: "附件上传完成" });
};
const uploadImmediate = async (options: any) => {
@@ -340,11 +376,18 @@ const uploadImmediate = async (options: any) => {
const group = tableUploadGroup.value;
if (!file || !group || !props.entityId) return;
if (!validateFile(file)) return;
const activityId = desktopActivityEnabled()
? startDesktopActivity({ kind: "upload", title: "上传附件", detail: "正在上传附件", progress: 0 })
: "";
try {
await uploadFile(group, file, props.entityId);
ElMessage.success(TEXT.common.messages.uploadSuccess);
await uploadFile(group, file, props.entityId, (progress) => {
updateDesktopActivity(activityId, { detail: "正在上传附件", progress });
});
finishDesktopActivity(activityId, "completed", { detail: "附件上传完成" });
if (!desktopActivityEnabled()) ElMessage.success(TEXT.common.messages.uploadSuccess);
load();
} catch (e: any) {
finishDesktopActivity(activityId, "failed", { detail: "附件上传失败" });
ElMessage.error(e?.response?.data?.message || e?.message || TEXT.common.messages.uploadFailed);
} finally {
progressMap[group.key] = 0;
@@ -399,25 +442,48 @@ const fetchAttachmentBlob = async (row: any): Promise<Blob> => {
};
const download = async (row: any) => {
const activityId = desktopActivityEnabled()
? startDesktopActivity({ kind: "download", title: "下载附件", detail: "正在从服务器获取文件", progress: 20 })
: "";
try {
const blob = await fetchAttachmentBlob(row);
updateDesktopActivity(activityId, { detail: "等待选择保存位置", progress: 65 });
await saveFileWithFeedback({
suggestedName: row?.filename || "download",
mimeType: row?.content_type,
data: await fetchAttachmentBlob(row),
data: blob,
}, {
activityId,
title: "下载附件",
pendingDetail: "等待选择保存位置",
completedDetail: "附件已保存",
cancelledDetail: "已取消保存附件",
});
} catch {
finishDesktopActivity(activityId, "failed", { detail: "附件下载失败" });
ElMessage.error(TEXT.common.messages.downloadFailed);
}
};
const openExternally = async (row: any) => {
const activityId = desktopActivityEnabled()
? startDesktopActivity({ kind: "open", title: "打开附件", detail: "正在从服务器获取文件", progress: 20 })
: "";
try {
const blob = await fetchAttachmentBlob(row);
updateDesktopActivity(activityId, { detail: "正在准备文件", progress: 65 });
await openFileWithFeedback({
suggestedName: row?.filename || "attachment",
mimeType: row?.content_type,
data: await fetchAttachmentBlob(row),
data: blob,
}, {
activityId,
title: "打开附件",
pendingDetail: "正在准备文件",
completedDetail: "已交给系统打开",
});
} catch {
finishDesktopActivity(activityId, "failed", { detail: "附件打开失败" });
ElMessage.error(TEXT.common.messages.previewNotSupported);
}
};