我使用Antd+Mobx,我想修改Dragger,以便它从剪贴板接收文件。主面板有图像粘贴处理程序,可将新文件添加到存储中的文件列表中。 最后,拖动的文件和对话框中的文件成功加载,但剪贴板中的文件仅添加到fileList中,未触发加载(未调用onChange)。我如何触发它以加载到服务器?
主面板:
const MathNewTaskPanel: React.FC<NewTaskProps> = inject('newTaskProps')(observer((props: NewTaskProps) => {
...
const handlePaste = (event: ClipboardEvent<HTMLDivElement>) => {
if (event.clipboardData.files.length) {
const ff = event.clipboardData.files[0];
const file: UploadFile = {
name: ff.name,
size: ff.size,
type: ff.type,
uid: uuid(),
originFileObj: ff
};
//main panel has two draggers. Only one is active at once
photoUploaderStore.uploader.setFile(file);
solutionUploaderStore.uploader.setFile(file);
}
};
return (
<div onPaste={handlePaste}>
...
</div>
);
}));
修改拖动器:
const ImageUploader: React.FC<IProps> = inject('uploader')(observer((props: IProps) => {
const antUploaderProps = {
name: 'file',
multiple: true,
listType: 'picture' as UploadListType,
action: props.uploader.url,
progress: 'line' as ProgressProps,
fileList: props.uploader.files,
disabled: props.uploader.inactive,
onChange(info: UploadChangeParam<UploadFile<any>>) {
props.uploader.handleOnChange(info);
}
};
return (
<div>
<Dragger className={props.uploader.inactive ? "inactive-element" : ""} {...antUploaderProps}>
<p className="ant-upload-drag-icon"><InboxOutlined /></p>
</Dragger>
</div>
);
}));
拖拉机商店:
export default class ImageUploaderStore {
@observable
inactive: boolean;
@observable
taskId: number|null;
@observable
url: string;
@observable
files: UploadFile[] | [];
isMainPhoto: boolean;
subject: Subjects;
mathTaskService: MathTaskService;
constructor(subject: Subjects, isMainPhoto: boolean) {
this.inactive = true;
this.subject = subject;
this.taskId = null;
this.url = "";
this.mathTaskService = new MathTaskService();
this.isMainPhoto = isMainPhoto;
this.files = [];
}
@action
changeActivated(active: boolean) {
this.inactive = !active;
}
@action
setTaskId(taskId: number) {
this.taskId = taskId;
this.url = this.getUrl(taskId);
}
@action
setFile(newFile: UploadFile) {
if (!this.inactive) {
this.files = [...this.files, newFile];
}
}
@action
setFiles(newFiles: UploadFile[]) {
if (!this.inactive) {
this.files = newFiles;
}
}
@action
handleOnChange(info: UploadChangeParam<UploadFile<any>>) {
runInAction(() => {
const { status } = info.file;
if (status === 'done') {
message.success(`${info.file.name} is successful loaded`);
} else if (status === 'error') {
message.error(`${info.file.name} loading failed`);
}
this.files = info.fileList;
});
}
getUrl(taskId: number): string {
if (this.subject === Subjects.MATH) {
return this.isMainPhoto
? this.mathTaskService.getPhotoUploadUrl(taskId)
: this.mathTaskService.getSolutionPhotoUploadUrl(taskId);
}
return "";
}
}
我在使用 antd upload 时遇到过类似的情况。在搜索解决方案后,我找到了这个主题,但是他们提供的解决方案并不完美。我花了一些时间让它变得更好。
这是我对这个问题的解决方案: 在我的解决方案中,我在 antd Upload 的输入中调度 drop 事件
首先我们要访问antd的隐藏。他们使用 rc-upload,其中 uploader 是私有方法,并且他们没有任何方法来获取 uploader。我们可以使用 typescript 的“后门”来覆盖它的接口:
...
import { UploadRef } from 'antd/es/upload/Upload'
import RcUpload from 'rc-upload'
const { Dragger } = Upload
interface ExtendedUploadRef<T = any> extends Omit<UploadRef<T>, 'upload'> {
upload: Omit<RcUpload, 'uploader'> & {
uploader: any
}
}
const uploadRef = useRef<ExtendedUploadRef<any> | null>(null)
这个引用应该传递给 antd 组件,但 typescript 不会喜欢它,这就是为什么我们将它传递为
React.RefObject<UploadRef<any>>
:
<Dragger
className="chat_file_upload_dragger"
{...draggerProps}
ref={uploadRef as React.RefObject<UploadRef<any>>}
>
<div className="chat_file_upload_middle">
<p className="ant-upload-drag-icon">
<FaFileUpload />
</p>
<p className="ant-upload-text">
{t('Click or drag file to this area to upload')}
</p>
</div>
</Dragger>
现在我们可以进入最有趣的部分,首先我们必须处理过去的事件,为此我使用了在项目中某处声明的可重用自定义挂钩:
import { useEffect, useRef } from "react"
export default function useEventListener(
eventType: any,
callback: any,
element = window
) {
const callbackRef = useRef(callback)
useEffect(() => {
callbackRef.current = callback
}, [callback])
useEffect(() => {
if (element == null) return
const handler = (e: any) => callbackRef.current(e)
element.addEventListener(eventType, handler)
return () => element.removeEventListener(eventType, handler)
}, [eventType, element])
}
然后我们用 antd Upload 告诉我们的组件监听这个事件:
useEventListener('paste', handlePasteFiles)
handlePasteFiles 函数中发生的所有魔法如下:
function handlePasteFiles(e: ClipboardEvent) {
const items = e.clipboardData?.items
if (!items) return
const arrItems = Array.from(items)
if (arrItems.every((item) => item.kind !== 'file')) return
e.preventDefault() //to not paste file path if focused in some input text
const fileList = new DataTransfer()
arrItems.forEach((item) => {
const file = item.getAsFile()
if (!file) return
fileList.items.add(file)
})
if (fileList.items.length > 0) {
const dropEvent = new DragEvent('drop', {
dataTransfer: fileList,
bubbles: true,
cancelable: true,
})
uploadRef.current?.upload?.uploader.fileInput.dispatchEvent(dropEvent)
}
}
因此,当文件被拖放到其区域时,antd 会处理从剪贴板粘贴的文件