项目中为了简便直接使用了react-dropzone插件,但是遇到了点击上传后本地窗口怎么也关不掉这个bug,且自己也没找到原因(有了解原因的小伙伴欢迎),所以自己尝试完成一个可以点击/拖拽上传文件的组件:


项目技术栈:remix/tailwind
1、我把拖拽部分拆出来写了,方便复用或者额外操作,大家也可以与上传组件写一起:
import React from "react";
import type { DragEvent, FC } from "react";
interface DraggerProps {
onFile: (file: File) => void;//对文件的操作
children?: React.ReactNode;//自定义样式
}
export const Dragger: FC<DraggerProps> = (props) => {
const { onFile, children } = props;
const handleDrop = (e: DragEvent<HTMLElement>) => {
e.preventDefault();
onFile(e.dataTransfer.files[0]);
};
return (
<div
className="flex h-full w-full cursor-pointer items-center justify-center"
onDrop={handleDrop}
>
{children}
</div>
);
};
2、然后就是自定义的文件上传组件,可根据自身需求对传入的参数/上传的文件进行修改:
import {
FileArchive,
FileJson,
FileText,
UploadCloud,
XCircle,
} from "lucide-react";//一个icon插件,根据需求自定义
import type { ChangeEvent, FC, ReactNode } from "react";
import { useRef } from "react";
import { cn } from "~/lib/utils";//use
import Dragger from "../Dragger";
import { Button } from "../ui/button";
//文件大小限制
export const readableBytes = (bytes: number, decimals?: number) => {
if (bytes == 0) return "0 Bytes";
const k = 1024,
dm = decimals || 2,
sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
};
export interface UploadProps {
name?: string;//文件名
accept?: string;//接受的文件类型
maxSize?: number;//文件最大体积
tip?: string;//提示信息
file?: File;
preview?: ReactNode;//上传的文件预览
onFileChange?: (file?: File) => void;
}
export const FileUpload: FC<UploadProps> = (props) => {
const { accept, maxSize, tip, file, preview, onFileChange } = props;
const fileInput = useRef<HTMLInputElement>(null);
const handleClick = () => {
if (fileInput.current) {
fileInput.current.click();
}
};
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
if (!files?.length) {
return;
}
onFileChange?.(files[0]);
if (fileInput.current) {
fileInput.current.value = "";
}
};
return (
<div
className={cn(
"flex h-64 w-full items-center justify-center rounded-2xl bg-accent-background text-muted transition-colors hover:bg-item dark:bg-dialog-foreground dark:hover:bg-item"
)}
onClick={handleClick}
>
{file ? (
<div className="flex h-full w-full flex-col items-center p-4">
<div className="dark:border-dialog-background flex w-full items-center rounded-xl border border-muted-foreground bg-background px-6 py-3 dark:bg-[#302F34]">
<div className="mr-3 overflow-hidden rounded-lg">
{preview ? (
preview
) : file.type.startsWith("image") ? (
<img
src={URL.createObjectURL(file)}
alt="upload"
className="h-14 w-14"
/>
) : file.type.endsWith("json") ? (
<FileJson className="h-14 w-14" />
) : file.type.endsWith("zip") ? (
<FileArchive className="h-14 w-14" />
) : file.type.endsWith("txt") ? (
<FileText className="h-14 w-14" />
) : (
<span className="flex h-14 w-14 items-center justify-center rounded-lg bg-muted text-sm text-background">
.{file.type.split("/")[file.type.split("/").length - 1]}
</span>
)}
</div>
<div className="flex flex-1 flex-col space-y-1">
<span className="font-bold">{file.name}</span>
<span
className={cn("text-sm text-muted-background", {
"text-destructive": maxSize && file.size > maxSize,
})}
>
{readableBytes(file.size)}
</span>
</div>
<Button
size="sm"
variant="ghost"
onClick={() => onFileChange?.(undefined)}
className="hover:bg-transparent"
>
<XCircle className="delay-50 h-6 w-6 text-foreground hover:text-muted dark:text-secondary-foreground dark:hover:text-muted" />
</Button>
</div>
</div>
) : (
<Dragger onFile={(file: File) => onFileChange?.(file)}>
<div className="inter flex flex-col items-center justify-center space-y-2 p-6 text-center text-sm text-search-icon">
<div className="dark:bg-dialog-background flex h-10 w-10 items-center justify-center rounded-xl border border-muted-foreground bg-background text-search-placeholder dark:border-transparent dark:text-muted-background">
<UploadCloud className="h-5 w-5" />
</div>
<p>
<span className="font-bold">Click to upload</span> or drag and
drop
</p>
<p>
{`${
tip || ".svg, .png, .jpg, .gif, .txt, .pdf, .json, .mp4..."
} ${maxSize && `(up to ${readableBytes(maxSize)})`}`}
</p>
</div>
</Dragger>
)}
<input
className="hidden"
ref={fileInput}
onChange={handleFileChange}
type="file"
accept={accept}
disabled={!!file}
/>
</div>
);
};
FileUpload.defaultProps = {
name: "file",
};
export default FileUpload;
🥳🥳🥳至此一个简单不需要任何插件的自定义文件上传组件就完成啦~~其中代码逻辑还有许多不足,欢迎大家给我提出宝贵建议噢~~💕💕