mcpField
I
implementing-ui-bundle-file-upload

implementing-ui-bundle-file-upload

github.com/forcedotcom开发1.1k3.5k 次安装1 天前更新
代码仓库

关于 implementing-ui-bundle-file-upload

面向 Salesforce React UI bundle 的文件上传技能,通过 API 提供上传能力。适合自行构建上传界面的开发者,支持进度跟踪、取消操作,以及立即或延后关联记录。

主要功能

  1. 01通过回调跟踪上传进度
  2. 02将上传文件关联到 Salesforce 记录
  3. 03上传后创建 ContentVersion
  4. 04使用 AbortController 取消上传
  5. 05获取当前 Salesforce 用户 ID

适用场景

  1. 01为现有记录附加文件
  2. 02在记录创建前上传文件
  3. 03构建自定义上传与进度界面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/forcedotcom/sf-skills --skill implementing-ui-bundle-file-upload

安装后在 Claude Code 中输入/implementing-ui-bundle-file-upload确认已加载。

详细介绍

文件上传 API(工作流)

当用户希望在 React UI bundle 中使用文件上传功能时,请遵循此工作流。此功能仅提供 API——你必须使用提供的 API 自行构建 UI 组件。

关键:这是一个仅提供 API 的 package

此 package 导出的是程序化 API,而不是 React 组件或钩子。你将:

  • 使用 upload() 函数处理文件上传并跟踪进度
  • 自行构建自定义 UI(文件输入框、拖放区域、进度条等)
  • 通过 onProgress 回调跟踪上传进度

请勿:

  • 期待有预构建组件,例如 <FileUpload />——这些组件不会导出
  • 尝试导入 React 钩子,例如 useFileUpload——这些钩子不会导出
  • 查找拖放区域组件——这些组件不会导出

源代码中包含用于演示的参考组件,但它们不能作为导入项使用。请将其作为示例,用于构建自己的 UI。

1. 安装 package

npm install @salesforce/ui-bundle-template-feature-react-file-upload

依赖项会自动安装:

  • @salesforce/ui-bundle(API 客户端)
  • @salesforce/sdk-data(数据 SDK)

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

此 package 提供后端,你来构建前端。以下是一个最简示例:

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 ID
  • recordId — 用于 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 决策树

用户要求文件上传功能:

  1. 询问记录上下文:

    • “你想将上传的文件关联到特定记录,还是先上传文件,之后再关联?”
  2. 根据回答:

    • 关联到现有记录 → 使用带 recordId 的模式 B
    • 先上传,之后再关联 → 使用模式 A(不带 recordId),然后使用模式 C 进行关联
    • 关联到当前用户 → 使用带 getCurrentUserId() 的模式 B
  3. 构建 UI:

    • 创建文件输入框或拖放区域(软件包未提供)
    • 为每个文件添加进度显示(状态 + 进度条)
    • 在 UI 中处理错误
  4. 测试实现:

    • 验证进度回调是否正确触发
    • 检查是否返回了 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 /> 组件 — 它不会导出
  • ❌ 尝试导入 useFileUpload hook — 它不会导出
  • ❌ 此功能可用时仍使用第三方文件上传库
  • ❌ 跳过进度跟踪 — 始终向用户反馈进度
  • ❌ 忽略错误 — 始终处理并显示错误消息

评论

支持 Markdown