文件上传

查看源代码

一个用于上传单个或多个文件的组件。

演示

拖动文件到这里
文件最大限制 2MB
import {
  Button,
  FilePicker,
  FilePickerList,
  FilePickerItem,
  FilePickerDropzone,
  FilePickerTrigger,
  FilePickerItemPreview,
  FilePickerItemSlot,
  FilePickerItemDelete,
  useToast,
  Toast,
  ToastContent,
  ToastTitle,
  ToastDescription,
  ToastCloseButton,
} from "@resolid/react-ui";
import { formatBytes } from "@resolid/utils";
import { SpriteIcon } from "~/components/sprite-icon";

export default function App() {
  const { show } = useToast();

  const handleError = (e) => {
    show(
      <Toast color="warning" className={"flex gap-2"}>
        <ToastContent className={"flex flex-col gap-1"}>
          <ToastTitle>文件选择错误</ToastTitle>
          <ToastDescription className={"text-sm"}>
            {Array.isArray(e) ? e.join("<br>") : e}
          </ToastDescription>
        </ToastContent>
        <ToastCloseButton className={"absolute inset-e-2 top-2"} />
      </Toast>,
      {
        placement: "top",
      },
    );
  };

  return (
    <FilePicker
      name="files"
      maxSize={2 * 1024 * 1024}
      onError={handleError}
      className="w-full flex-col pb-9 md:w-1/2"
    >
      <FilePickerDropzone className="flex-col gap-2">
        <div className={"rounded-full border border-bd-subtle p-2 text-fg-subtle"}>
          <SpriteIcon size={"1.75rem"} name="upload" />
        </div>
        <div className="text-sm">
          <div className={"font-medium"}>拖动文件到这里</div>
          <div className={"text-fg-subtlest"}>文件最大限制 2MB</div>
        </div>
        <FilePickerTrigger
          variant={"soft"}
          size={"sm"}
          color={"neutral"}
          render={(props, { disabled }) => <Button {...props} disabled={disabled} />}
        >
          浏览文件
        </FilePickerTrigger>
      </FilePickerDropzone>
      <FilePickerList>
        <FilePickerItem className="flex flex-row items-center gap-2 border border-bd-normal p-2">
          <FilePickerItemPreview className="size-9 shrink-0" />
          <div className="flex min-w-0 flex-1 flex-col">
            <FilePickerItemSlot>
              {(file) => (
                <>
                  <span className="truncate text-sm text-fg-muted">{file.file.name}</span>
                  <span className="truncate text-xs text-fg-subtlest">
                    {formatBytes(file.file.size)}
                  </span>
                </>
              )}
            </FilePickerItemSlot>
          </div>
          <FilePickerItemDelete className="size-7 shrink-0" />
        </FilePickerItem>
      </FilePickerList>
    </FilePicker>
  );
}

用法

import {
  FilePicker,
  FilePickerDropzone,
  FilePickerTrigger,
  FilePickerList,
  FilePickerItem,
  FilePickerItemPreview,
  FilePickerImagePreview,
  FilePickerItemSlot,
  FilePickerItemDelete,
  FileUpload,
  FileUploadButton,
} from "@resolid/react-ui";
  • FilePicker: 文件上传选择器。
  • FilePickerDropzone: 文件拖拽区域组件。
  • FilePickerTrigger: 文件选择触发器组件。
  • FilePickerList: 文件列表容器组件。
  • FilePickerItem: 单个文件项组件。
  • FilePickerItemPreview: 文件预览组件。
  • FilePickerImagePreview: 图片文件专用预览组件。(默认预览 image/* 图片, 如果要自定义这个图片预览可以重复增加这个规则)
  • FilePickerItemSlot: 文件项插槽组件。
  • FilePickerItemDelete: 文件删除按钮组件。
  • FileUpload: 文件上传控制组件。(可选)
  • FileUploadButton: 文件上传或重试按钮组件。(可选)
import {
  FileUpload,
  FileUploadButton,
  FilePicker,
  FilePickerList,
  FilePickerItem,
  FilePickerDropzone,
  FilePickerTrigger,
  FilePickerItemPreview,
  FilePickerImagePreview,
  FilePickerItemSlot,
  FilePickerItemDelete,
} from "@resolid/react-ui";

<FileUpload>
  <FilePicker>
    <FilePickerDropzone>
      <FilePickerTrigger />
    </FilePickerDropzone>
    <FilePickerList>
      <FilePickerItem>
        <FilePickerItemPreview rules={[{ type: "application/pdf", render: <PdfFileIcon /> }]} />
        <FilePickerItemSlot />
        <FilePickerItemDelete />
        <FileUploadButton />
      </FilePickerItem>
    </FilePickerList>
  </FilePicker>
</FileUpload>;

举例

文件上传

使用 FileUpload 包裹 FilePicker 组件可以实现文件上传。

拖动文件到这里
仅支持图片和PDF文件, 最大限制 5MB
import {
  Button,
  FilePicker,
  FilePickerList,
  FilePickerItem,
  FilePickerDropzone,
  FilePickerTrigger,
  FilePickerItemPreview,
  FilePickerItemSlot,
  FilePickerItemDelete,
  FileUpload,
  ProgressBar,
  FileUploadButton,
} from "@resolid/react-ui";
import { formatBytes } from "@resolid/utils";
import { SpriteIcon } from "~/components/sprite-icon";
import { uploadthingTransport } from "~/utils/uploadthing-transport";

export default function App() {
  return (
    <FileUpload transport={uploadthingTransport}>
      <FilePicker
        maxSize={5 * 1024 * 1024}
        accept="image/*,.pdf"
        name="files"
        multiple
        onError={(e) => console.log(e)}
        className="w-full flex-col md:w-1/2"
      >
        <FilePickerDropzone className="flex-col gap-2">
          <div className={"rounded-full border border-bd-subtle p-2 text-fg-subtle"}>
            <SpriteIcon size={"1.75rem"} name="upload" />
          </div>
          <div className="text-sm">
            <div className={"font-medium"}>拖动文件到这里</div>
            <div className={"text-fg-subtlest"}>仅支持图片和PDF文件, 最大限制 5MB</div>
          </div>
          <FilePickerTrigger
            variant={"soft"}
            size={"sm"}
            color={"neutral"}
            render={(props, { disabled }) => <Button {...props} disabled={disabled} />}
          >
            浏览文件
          </FilePickerTrigger>
        </FilePickerDropzone>
        <FilePickerList>
          <FilePickerItem className="flex flex-row items-center gap-2 border border-bd-normal p-2">
            <FilePickerItemPreview
              className="size-10 shrink-0"
              rules={[
                {
                  type: "application/pdf",
                  render: (
                    <SpriteIcon className={"size-2/3 text-fg-subtle"} size={null} name="file-pdf" />
                  ),
                },
              ]}
            />
            <div className="flex min-w-0 flex-1 flex-col">
              <FilePickerItemSlot>
                {(file) => (
                  <>
                    <span className="truncate text-sm text-fg-muted">{file.file.name}</span>
                    <span className="truncate text-xs text-fg-subtlest">
                      {formatBytes(file.file.size)}
                      {file.kind == "remote" && (
                        <SpriteIcon
                          size={"1rem"}
                          className={"ms-1 -mt-px inline-block text-fg-success"}
                          name="cloud-done"
                        />
                      )}
                    </span>
                    {file.status == "uploading" && (
                      <ProgressBar size="xs" percent={file.progress} />
                    )}
                    {file.status == "error" && (
                      <span className="text-xs text-fg-danger">{file.error}</span>
                    )}
                  </>
                )}
              </FilePickerItemSlot>
            </div>
            <FileUploadButton />
            <FilePickerItemDelete className="size-7 shrink-0" />
          </FilePickerItem>
        </FilePickerList>
      </FilePicker>
    </FileUpload>
  );
}

精简排列

import {
  Button,
  FilePicker,
  FilePickerList,
  FilePickerItem,
  FilePickerDropzone,
  FilePickerTrigger,
  FilePickerItemPreview,
  FilePickerItemSlot,
  FilePickerItemDelete,
  FileUpload,
  ProgressBar,
  FileUploadButton,
} from "@resolid/react-ui";
import { formatBytes } from "@resolid/utils";
import { SpriteIcon } from "~/components/sprite-icon";
import { uploadthingTransport } from "~/utils/uploadthing-transport";

export default function App() {
  return (
    <FileUpload transport={uploadthingTransport}>
      <FilePicker
        maxSize={5 * 1024 * 1024}
        accept="image/*"
        name="files"
        multiple
        onError={(e) => console.log(e)}
        className="w-full"
      >
        <FilePickerList>
          <FilePickerItem className="flex size-25 justify-center border border-bd-normal">
            <FilePickerItemPreview className="size-full" />
            <FilePickerItemSlot>
              {(file) => (
                <>
                  {file.kind == "remote" && (
                    <span className={"absolute inset-s-1 top-1 rounded-md bg-bg-neutral/60 p-px"}>
                      <SpriteIcon size={"1rem"} className={"text-fg-success"} name="cloud-done" />
                    </span>
                  )}
                  {file.status == "uploading" && (
                    <div className={"absolute inset-s-0 bottom-0 w-full"}>
                      <ProgressBar radius={"none"} size="xs" percent={file.progress} />
                    </div>
                  )}
                  {file.status == "error" && (
                    <span className="inset-s absolute bottom-0 w-full truncate bg-bg-neutral/60 p-px text-xs text-fg-danger">
                      {file.error}
                    </span>
                  )}
                </>
              )}
            </FilePickerItemSlot>
            <FileUploadButton className={"absolute bottom-6 opacity-80"} />
            <FilePickerItemDelete
              size={"1rem"}
              radius={"md"}
              variant={"soft"}
              noPadding={true}
              className="absolute inset-e-1 top-1 size-5 opacity-60"
            />
          </FilePickerItem>
        </FilePickerList>
        <FilePickerDropzone className="relative size-25">
          <FilePickerTrigger
            className={
              "absolute inset-0 flex justify-center enabled:hover:cursor-pointer enabled:hover:bg-bg-subtlest"
            }
          >
            <SpriteIcon size={"1.5rem"} name="upload" />
          </FilePickerTrigger>
        </FilePickerDropzone>
      </FilePicker>
    </FileUpload>
  );
}

头像上传

import {
  Button,
  FilePicker,
  FilePickerList,
  FilePickerItem,
  FilePickerDropzone,
  FilePickerTrigger,
  FilePickerItemPreview,
  FilePickerItemSlot,
  FilePickerItemDelete,
  FileUpload,
  ProgressBar,
  FileUploadButton,
} from "@resolid/react-ui";
import { formatBytes } from "@resolid/utils";
import { SpriteIcon } from "~/components/sprite-icon";
import { uploadthingTransport } from "~/utils/uploadthing-transport";

export default function App() {
  return (
    <FileUpload transport={uploadthingTransport}>
      <FilePicker
        maxSize={5 * 1024 * 1024}
        accept="image/*"
        name="avatar"
        onError={(e) => console.log(e)}
        className=""
      >
        <FilePickerList className={"absolute inset-0 z-10"}>
          <FilePickerItem className="flex size-25 justify-center border border-bd-normal">
            <FilePickerItemPreview className="size-full" />
            <FilePickerItemSlot>
              {(file) => (
                <>
                  {file.kind == "remote" && (
                    <span className={"absolute inset-s-1 top-1 rounded-md bg-bg-neutral/60 p-px"}>
                      <SpriteIcon size={"1rem"} className={"text-fg-success"} name="cloud-done" />
                    </span>
                  )}
                  {file.status == "uploading" && (
                    <div className={"absolute inset-s-0 bottom-0 w-full"}>
                      <ProgressBar radius={"none"} size="xs" percent={file.progress} />
                    </div>
                  )}
                  {file.status == "error" && (
                    <span className="absolute inset-s-0 bottom-0 w-full truncate bg-bg-neutral/60 p-px text-xs text-fg-danger">
                      {file.error}
                    </span>
                  )}
                </>
              )}
            </FilePickerItemSlot>
            <FileUploadButton className={"absolute bottom-6 opacity-80"} />
            <FilePickerItemDelete
              size={"1rem"}
              radius={"md"}
              variant={"soft"}
              noPadding={true}
              className="absolute inset-e-1 top-1 size-5 opacity-60"
            />
          </FilePickerItem>
        </FilePickerList>
        <FilePickerDropzone className="relative size-25">
          <FilePickerTrigger
            className={
              "absolute inset-0 flex justify-center text-fg-subtlest enabled:hover:cursor-pointer enabled:hover:bg-bg-subtlest"
            }
          >
            <SpriteIcon size={"3.5rem"} name="user" />
          </FilePickerTrigger>
        </FilePickerDropzone>
      </FilePicker>
    </FileUpload>
  );
}

属性

FilePicker

属性
accept
类型string默认值"*"必须false
属性
maxFiles

最大文件数量

类型number默认值Infinity必须false
属性
maxSize

最大文件大小(字节单位)

类型number默认值Infinity必须false
属性
minSize

最小文件大小(字节单位)

类型number默认值0必须false
属性
onError

选取文件出现错误回调

类型(error: string | string[]) => void默认值-必须false
属性
transformFile

用于转换已接受的文件

类型(file: File) => Promise<File>默认值-必须false
属性
name

字段的名称, 提交表单时使用

类型string默认值-必须false
属性
multiple

允许多文件

类型boolean默认值false必须false
属性
value

受控值

类型FileItem | FileItem[] | null默认值-必须false
属性
defaultValue

默认值

类型RemoteFile | RemoteFile[] | null默认值null | []必须false
属性
onChange

onChange 回调

类型(value: FileItem | FileItem[] | null) => void默认值-必须false
属性
disabled

是否禁用

类型boolean默认值false必须false
属性
required

是否必需

类型boolean默认值false必须false
属性
readOnly

是否只读

类型boolean默认值false必须false

FilePickerList

属性
orientation

方向

类型"horizontal" | "vertical"默认值"horizontal"必须false

FilePickerItemPreview

属性
rules

预览内容显示规则列表(按顺序匹配, 命中即停止)

## type 匹配的文件 mimeType 类型

- `image/*`:匹配所有图片类型

- `video/*`:匹配所有视频类型

- `application/pdf`:精确匹配 PDF

## render 渲染的预览内容

类型{ type: string; render: ReactNode; }[]默认值-必须false

FileUpload

属性
autoUpload

是否自动上传

类型boolean默认值true必须false
属性
maxParallel

最大并行数

类型number默认值3必须false
属性
transport

文件上传传输方法

类型UploadTransport默认值-必须true

建议更改此页面