关于 experience-ui-bundle-file-upload-generate
面向 Salesforce React UI bundle 的文件上传工作流技能,提供上传 API 和进度回调;界面由开发者自行构建。
主要功能
- 01通过回调跟踪上传进度
- 02将文件关联到 Salesforce 记录
- 03延后关联记录
- 04使用 AbortController 取消上传
- 05获取当前用户 ID
适用场景
- 01为现有记录附加文件
- 02创建记录前先上传文件
- 03构建自定义上传与进度界面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill experience-ui-bundle-file-upload-generate安装后在 Claude Code 中输入/experience-ui-bundle-file-upload-generate确认已加载。
详细介绍
文件上传 API(工作流)
当用户希望在 React UI 包中实现文件上传功能时,请遵循此工作流。此功能仅提供 API——你必须使用提供的 API 自行构建 UI 组件。
重要:这是一个仅提供 API 的包
此包导出的是程序化 API,而非 React 组件或钩子。你将:
- 使用
upload()函数处理文件上传并跟踪进度 - 构建自己的自定义 UI(文件输入框、拖放区、进度条等)
- 通过
onProgress回调跟踪上传进度
请勿:
- 期待有预构建组件,例如
<FileUpload />——此包不会导出这些组件 - 尝试导入 React 钩子,例如
useFileUpload——此包不会导出这些钩子 - 查找拖放区组件——此包不会导出这些组件
源代码中包含用于演示的参考组件,但它们无法通过导入使用。请将其用作示例,自行构建 UI。
1. 安装此包
npm install @salesforce/ui-bundle-template-feature-react-file-upload
依赖项会自动安装:
@salesforce/ui-bundle(API 客户端)@salesforce/platform-sdk(数据 SDK;旧名称@salesforce/sdk-data已停用——请参阅experience-ui-bundle-salesforce-data-access技能)
2. 了解三种上传模式
模式 A:基本上传(不关联记录)
将文件上传到 Salesforce,并获取每个文件对应的 contentBodyId。不会创建 ContentVersion 记录。
适用场景:
- 用户希望先上传文件,之后再创建记录或将文件关联到记录
- 构建多步骤表单,但记录尚不存在
- 延迟关联记录的场景
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";
const results = await upload({
files: [file1, file2],
onProgress: (progress) => {
console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
},
});
// results[0].contentBodyId: "069..." (always available)
// results[0].contentVersionId: undefined (no record linked)
模式 B:上传并立即关联记录
上传文件,并通过创建 ContentVersion 记录立即将其关联到现有 Salesforce 记录。
适用场景:
- 记录已存在(Account、Opportunity、Case 等)
- 用户希望文件立即附加到记录
- 直接上传并附加文件的场景
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";
const results = await upload({
files: [file1, file2],
recordId: "001xx000000yyyy", // Existing record ID
onProgress: (progress) => {
console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
},
});
// results[0].contentBodyId: "069..." (always available)
// results[0].contentVersionId: "068..." (linked to record)
模式 C:延迟关联记录(记录创建流程)
先在没有关联记录的情况下上传文件,然后在记录创建后再将文件关联到记录。
适用场景:
- 构建“创建带附件的记录”表单
- 记录要到表单提交后才会创建
- 需要先上传文件,之后才能知道最终的记录 ID
import {
upload,
createContentVersion,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
// Step 1: Upload files (no recordId)
const uploadResults = await upload({
files: [file1, file2],
onProgress: (progress) => console.log(progress),
});
// Step 2: Create the record
const newRecordId = await createRecord(formData);
// Step 3: Link uploaded files to the new record
for (const file of uploadResults) {
const contentVersionId = await createContentVersion(
new File([""], file.fileName),
file.contentBodyId,
newRecordId,
);
}
3. 构建自定义 UI
此包提供后端功能,前端由你构建。以下是一个最简示例:
import {
upload,
type FileUploadProgress,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
import { useState } from "react";
function CustomFileUpload({ recordId }: { recordId?: string }) {
const [progress, setProgress] = useState<Map<string, FileUploadProgress>>(new Map());
const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
await upload({
files,
recordId,
onProgress: (fileProgress) => {
setProgress((prev) => new Map(prev).set(fileProgress.fileName, fileProgress));
},
});
};
return (
<div>
<input type="file" multiple onChange={handleFileSelect} />
{Array.from(progress.entries()).map(([fileName, fileProgress]) => (
<div key={fileName}>
{fileName}: {fileProgress.status} - {fileProgress.progress}%
{fileProgress.error && <span>Error: {fileProgress.error}</span>}
</div>
))}
</div>
);
}
4. 跟踪上传进度
每个文件在上传过程中经过不同阶段时,onProgress 回调都会多次触发:
| 状态 | 时机 | 进度值 |
|---|---|---|
"pending" | 文件已加入上传队列 | 0 |
"uploading" | 正在上传(XHR) | 0-100(百分比) |
"processing" | 正在创建 ContentVersion(如果提供了 recordId) | 0 |
"success" | 上传完成 | 100 |
"error" | 上传失败 | 0 |
务必提供可视化反馈:
- 显示文件名
- 显示当前状态
- 在
"uploading"状态下显示进度条 - 在
"error"状态下显示错误消息
5. 取消上传(可选)
使用 AbortController 允许用户取消上传:
const abortController = new AbortController();
const handleUpload = async (files: File[]) => {
try {
await upload({
files,
signal: abortController.signal,
onProgress: (progress) => console.log(progress),
});
} catch (error) {
console.error("Upload cancelled or failed:", error);
}
};
const cancelUpload = () => {
abortController.abort();
};
6. 关联到当前用户(特殊情况)
如果用户希望将文件上传到自己的个人资料或个人库:
import {
upload,
getCurrentUserId,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
const userId = await getCurrentUserId();
await upload({ files, recordId: userId });
API 参考
upload(options)
主要上传 API,负责处理完整流程并跟踪进度。
interface UploadOptions {
files: File[];
recordId?: string | null; // If provided, creates ContentVersion
onProgress?: (progress: FileUploadProgress) => void;
signal?: AbortSignal; // Optional cancellation
}
interface FileUploadProgress {
fileName: string;
status: "pending" | "uploading" | "processing" | "success" | "error";
progress: number; // 0-100 for uploading, 0 for other states
error?: string;
}
interface FileUploadResult {
fileName: string;
size: number;
contentBodyId: string; // Always available
contentVersionId?: string; // Only if recordId was provided
}
返回值: Promise<FileUploadResult[]>
createContentVersion(file, contentBodyId, recordId)
根据之前上传的文件,手动创建 ContentVersion 记录。
async function createContentVersion(
file: File,
contentBodyId: string,
recordId: string,
): Promise<string | undefined>;
参数:
file— File 对象(用于获取名称等元数据)contentBodyId— 上一次上传返回的 ContentBody IDrecordId— 用于 FirstPublishLocationId 的记录 ID
返回: 成功时返回 ContentVersion ID
getCurrentUserId()
获取当前用户的 Salesforce ID。
async function getCurrentUserId(): Promise<string>;
返回: 当前用户 ID
常见 UI 模式
带按钮的文件输入框
<input type="file" multiple accept=".pdf,.doc,.docx,.jpg,.png" onChange={handleFileSelect} />
拖放区域
使用原生事件构建自己的拖放区域:
function DropZone({ onDrop }: { onDrop: (files: File[]) => void }) {
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);
onDrop(files);
};
return (
<div
onDrop={handleDrop}
onDragOver={(e) => e.preventDefault()}
style={{ border: "2px dashed #ccc", padding: "2rem" }}
>
Drop files here
</div>
);
}
进度条
{
progress.status === "uploading" && (
<div style={{ width: "100%", background: "#eee" }}>
<div
style={{
width: `${progress.progress}%`,
background: "#0176d3",
height: "8px",
}}
/>
</div>
);
}
Agent 决策树
用户要求实现文件上传功能:
-
询问记录上下文:
- “您想将上传的文件关联到特定记录,还是先上传文件再关联?”
-
根据回答:
- 关联到现有记录 → 使用模式 B,并提供
recordId - 先上传,之后再关联 → 使用模式 A(不提供 recordId),然后使用模式 C 进行关联
- 关联到当前用户 → 使用模式 B,并调用
getCurrentUserId()
- 关联到现有记录 → 使用模式 B,并提供
-
构建 UI:
- 创建文件输入框或拖放区域(此包未提供)
- 为每个文件显示进度(状态 + 进度条)
- 在 UI 中处理错误
-
测试实现:
- 确认进度回调能正确触发
- 检查是否返回了
contentBodyId - 如果提供了
recordId,确认是否返回了contentVersionId
参考实现
此包在 src/features/fileupload/ 中提供了一个参考实现,其中包括:
FileUpload.tsx— 带有拖放区域和对话框的完整组件FileUploadDialog.tsx— 进度跟踪对话框FileUploadDropZone.tsx— 拖放区域useFileUpload.ts— 用于状态管理的 React hook
这些内容不会导出,但可以作为示例查看。阅读源文件,了解构建自有 UI 的模式。
故障排除
上传因 CORS 错误而失败:
- 确保 UI bundle 已正确部署到 Salesforce,或正在
localhost上运行 - 检查组织是否已在 CORS 设置中允许该来源
没有进度更新:
- 确认已提供
onProgress回调 - 检查回调函数是否正确更新 React 状态
未创建 ContentVersion:
- 确认已向
upload()函数提供recordId - 检查记录 ID 是否有效且存在于组织中
- 确保用户拥有创建 ContentVersion 记录的权限
文件已上传,但未显示在记录中:
- 确认
recordId正确 - 检查是否已创建 ContentVersion(在结果中查找
contentVersionId) - 确认用户有权查看该记录中的文件
请勿执行以下操作
- 从头编写 XHR/fetch 上传逻辑——请使用
upload()API - 尝试导入
<FileUpload />组件——它不会导出 - 尝试导入
useFileUploadhook——它不会导出 - 此功能可用时,不要使用第三方文件上传库
- 不要跳过进度跟踪——务必向用户提供反馈
- 不要忽略错误——务必处理并显示错误消息