2

我正在尝试使用 KendoReact Upload 组件制作可重用的多文件上传组件。我所拥有的是功能性的,但我对 React 还很陌生,所以希望有一种更简洁的方法来实现这一点。

默认情况下,该组件接受上传/删除请求的端点 URL,但 saveUrl/removeUrl 道具也可以采用允许操作上传文件的函数。我正在使用 FileReader 将文件转换为 base64,并在上传文件后采用可用于外部状态更新的函数。

我有以下实现,但感觉很重,因为我需要

  • Kendo 控件使用的文件状态中的 UploadFileInfo 集合
  • 包含要传递的 base64 内容的文件的状态中的 IFileUploadViewModel 集合
  • 用于触发外部状态更新的 useEffect 调用,以确保传递更新的状态
  • saveRequestPromise 和 removeRequestPromise 中的函数状态更新,以保持控制和外部文件集合同步
import React, { useEffect, useState } from "react";
import { Upload, UploadFileInfo, UploadOnAddEvent, UploadOnProgressEvent, UploadOnRemoveEvent, UploadOnStatusChangeEvent } from "@progress/kendo-react-upload";

export interface IFileUploadViewModel {
    uid: string;
    fileName: string;
    fileSize: number;
    content?: string | undefined;
}

export function KendoMultipleSmallFileUpload(props: {
    allowedExtensions?: string[] | undefined;
    minFileSizeMb?: number | undefined;
    maxFileSizeMb?: number | undefined;
    onFileChange: (files: Array<IFileUploadViewModel>) => void;
}) {
    // The Upload component accepts files from users only, an initial 'system' state e.g. pre-selected file determined by a viewmodel is not a valid use case.
    const [files, setFiles] = useState(new Array<UploadFileInfo>());
    const [fileUploads, setFileUploads] = useState(new Array<IFileUploadViewModel>());

    useEffect(() => {
        console.log(fileUploads);
        props.onFileChange(fileUploads);
    }, [fileUploads]);

    const convertMbToBytes = (mb: number) => mb * 1024 * 1024;
    // The Upload component does not allow empty files, convert file size restrictions to bytes and set a minimum value if not provided.
    const minFileSizeBytes = props.minFileSizeMb ? convertMbToBytes(props.minFileSizeMb) : 1;
    const maxFileSizeBytes = props.maxFileSizeMb ? convertMbToBytes(props.maxFileSizeMb) : undefined;

    // Fires when user clicks on the Remove button while the file upload is in progress. Can be used when the saveUrl option is set to a function that cancels custom requests.
    // For small files, conversion to a byte array is so quick it is impossible to Cancel through the UI and in any case is automatically followed by Remove so no implementation is required.
    // For large files we may need to consider aborting the upload process through the FileReader API.
    function onCancel(event: any) {}

    function onAdd(event: UploadOnAddEvent) {
        setFiles(event.newState);
    }

    function onRemove(event: UploadOnRemoveEvent) {
        setFiles(event.newState);
    }

    function onProgress(event: UploadOnProgressEvent) {
        setFiles(event.newState);
    }

    function onStatusChange(event: UploadOnStatusChangeEvent) {
        setFiles(event.newState);
    }

    function onSaveRequest(
        files: UploadFileInfo[],
        options: { formData: FormData; requestOptions: any },
        onProgress: (uid: string, event: ProgressEvent<EventTarget>) => void
    ): Promise<{ uid: string }> {
        const currentFile = files[0] as UploadFileInfo;
        const uid = currentFile.uid;
        console.log("onSaveRequest for " + currentFile.name);

        const saveRequestPromise = new Promise<{ uid: string }>(async (resolve, reject) => {
            // as currently configured it is impossible to request a save from the UI if there are validation errors, but that can be changed so keeping this check in place
            if (currentFile.validationErrors && currentFile.validationErrors.length > 0) {
                reject({ uid: uid });
            } else {
                const reader = new FileReader();
                // onload is executed when the load even is fired i.e. when content read with readAsArrayBuffer, readAsBinaryString, readAsDataURL or readAsText is available
                reader.onload = () => {
                    if (reader.result && typeof reader.result === "string") {
                        // stripping the data-url declaration as per https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
                        const base64Result = reader.result.split(",")[1];
                        // update viewModel and resolve
                        const fileUpload: IFileUploadViewModel = { uid: uid, fileName: currentFile.name, fileSize: currentFile.size!, content: base64Result };
                        setFileUploads((state) => [...state, fileUpload]);

                        resolve({ uid: uid });
                    } else {
                        reject({ uid: uid });
                    }
                };
                // onprogress is fired periodically as the FileReader reads data and the ProgressEvent can be passed directly to the Upload control, handy!
                reader.onprogress = (data) => {
                    onProgress(uid, data);
                };
                // if the read is not completed due to error or user intervention, reject
                reader.onabort = () => {
                    reject({ uid: uid });
                };
                reader.onerror = () => {
                    reject({ uid: uid });
                };

                reader.readAsDataURL(currentFile.getRawFile!());
            }
        });

        return saveRequestPromise;
    }

    function onRemoveRequest(files: UploadFileInfo[], options: { formData: FormData; requestOptions: any }): Promise<{ uid: string }> {
        const currentFile = files[0] as UploadFileInfo;
        const uid = currentFile.uid;

        const removeRequestPromise = new Promise<{ uid: string }>((resolve) => {
            const updatedFileUploads = fileUploads.filter((f) => f.uid !== uid);
            setFileUploads(updatedFileUploads);
            props.onFileChange(updatedFileUploads);
            resolve({ uid: uid });
        });

        return removeRequestPromise;
    }

    return (
        <>
            <Upload
                className="mb-2"
                autoUpload={true}
                batch={false}
                files={files}
                multiple={true}
                onAdd={onAdd}
                onCancel={onCancel}
                onProgress={onProgress}
                onRemove={onRemove}
                onStatusChange={onStatusChange}
                withCredentials={false}
                removeUrl={onRemoveRequest}
                saveUrl={onSaveRequest}
                restrictions={{ allowedExtensions: props.allowedExtensions, minFileSize: minFileSizeBytes, maxFileSize: maxFileSizeBytes }}
            />
            <div className="d-flex flex-row flex-wrap small">
                {props.minFileSizeMb && (
                    <div className="d-flex flex-row mr-2 mb-2">
                        <span className="label font-weight-bold mr-1">Minimum File Size: </span>
                        <span>{props.minFileSizeMb} MB.</span>
                    </div>
                )}
                {props.maxFileSizeMb && (
                    <div className="d-flex flex-row mr-2 mb-2">
                        <span className="label font-weight-bold mr-1">Maximum File Size: </span>
                        <span>{props.maxFileSizeMb} MB.</span>
                    </div>
                )}
                {props.allowedExtensions && (
                    <div className="d-flex flex-row mr-2 mb-2">
                        <span className="label font-weight-bold mr-1">Supported File Types: </span>
                        <span>{props.allowedExtensions.join(", ")}.</span>
                    </div>
                )}
            </div>
        </>
    );
}

任何建议,将不胜感激。

4

0 回答 0