SpringBoot + React Ant Design 实现图片上传到Minio 中

发布于:2024-04-26 ⋅ 阅读:(28) ⋅ 点赞:(0)

1:效果图

上传回显:

上传预览:

预览-删除

2:前端代码

react 函数式组件

/**
 * @Author 
 * @Date Created in  2024/04/11 15:20
 * @DESCRIPTION:  主讲人信息
 * @Version V1.0
 */
import React, {useEffect, useId, useState} from "react";
import {
    Button,
    Col,
    DatePicker,
    Form,
    Image,
    Input,
    message,
    Modal,
    Pagination,
    Popconfirm, Popover,
    Row,
    Select,
    Space,
    Spin,
    Switch,
    Table,
    Tooltip,
    Typography,
    Upload
} from "antd";
import {
    deletePhoto,
    deleteSpeaker,
    getAllSpeaker,
    getSpeakerByPage,
    getSpeakerDepartment,
    getSpeakerDetail,
    getVisibleCollege,
    insertOrUpdateSpeaker,
    updateIsUse,
    uploadPhotoApi
} from "api/index.js";
import {useNavigate} from "react-router-dom";
import {LeftOutlined, PlusOutlined} from "@ant-design/icons";
import TextArea from "antd/es/input/TextArea.js";
import MyUpload from "@/components/Upload/index.js";

const {Title, Text} = Typography;
const {RangePicker} = DatePicker;
const getBase64 = (file) =>
    new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => resolve(reader.result);
        reader.onerror = (error) => reject(error);
    });

export default () => {
    const [screeningForm] = Form.useForm();
    const [myModalForm] = Form.useForm();
    const [modalForm] = Form.useForm();
    const navigate = useNavigate();
    const [base64Data, setBase64Data] = useState(""); // 用于保存原始的 base64 数据

    const [isShowModal, setIsShowModal] = useState(false);
    const [resetPageSize, setResetPageSize] = useState(10);
    const [currentPage, setCurrentPage] = useState(1);
    const [currentPageSize, setCurrentPageSize] = useState(10);
    const [isInsert, setIsInsert] = useState(true);
    const [tableSource, setTableSource] = useState([])
    const [tableSourceDetail, setTableSourceDetail] = useState([])
    const [departmentOptions, setDepartmentOptions] = useState([])
    const [isUpdateShow, setIsUpdateShow] = useState(false);
    const [name, setName] = useState();
    const [nameId, setNameId] = useState();
    const [photoData, setPhotoData] = useState();
    const [isLoading,setIsLoading] = useState(false);
    // 参与讲座详情 分页参数
    const [detailParam, setDetailParam] = useState({});
    const [detailPage, setDetailPage] = useState(1);
    const [detailPageSize, setDetailPageSize] = useState(5);
    const detailPageChange = (pageNo, pageSize) => {
        setDetailPage(pageNo)
        setDetailPageSize(pageSize)
        const param = {
            ...detailParam,
            pageNum: pageNo,
            pageSize: pageSize,
        }
        getSpeakerDetail(param).then(res => {
            if (res.code === 200) {
                setTableSourceDetail(res.data)
            }
        })
    };

    const detailPageSizeChange = (current, pageSize) => {
        setDetailPage(current)
        setDetailPageSize(pageSize)
    }

    /// 上传相关 2 个 state

    const [tableList, setTableList] = useState(
        {
            "foreignSchool": '0',
            "name": null,
            "isUse": 0,
            "pageNum": 1,
            "pageSize": 10,
            "speakerTotal": null,
        });
    const speakerDetail = [
        {
            title: '讲座名称',
            dataIndex: 'speakerName',
            key: 'speakerName',
            width: 200,
            align: "center"
        },
        {
            title: '举办时间',
            dataIndex: 'holdingTime',
            key: 'holdingTime',
            width: 300,
            align: "center"
        },
    ]
    const tableColumns = [
        {
            title: '主讲人姓名',
            dataIndex: 'name',
            key: 'name',
            width: 120,
            render: (text) => (
                <Tooltip title={text}>
                    <div
                        style={{
                            color: 'black',
                            overflow: 'hidden',
                            whiteSpace: 'nowrap',
                            textOverflow: 'ellipsis',
                            maxWidth: '30ch', // 设置超出部分的背景颜色
                        }}
                    >
                        {text}
                    </div>
                </Tooltip>
            ),
        },
        {
            title: '职工号',
            dataIndex: 'userId',
            key: 'userId',
            width: 120,
            render: (text) => (
                <Tooltip title={text}>
                    <div
                        style={{
                            color: 'black',
                            overflow: 'hidden',
                            whiteSpace: 'nowrap',
                            textOverflow: 'ellipsis',
                            maxWidth: '30ch', // 设置超出部分的背景颜色
                        }}
                    >
                        {text}
                    </div>
                </Tooltip>
            ),
        },
        {
            title: '所属学院/单位',
            dataIndex: 'department',
            key: 'department',
            width: 150,
            render: (text) => (
                <Tooltip title={text}>
                    <div
                        style={{
                            color: 'black',
                            overflow: 'hidden',
                            whiteSpace: 'nowrap',
                            textOverflow: 'ellipsis',
                            maxWidth: '10ch', // 设置超出部分的背景颜色
                        }}
                    >
                        {text}
                    </div>
                </Tooltip>
            ),
        },
        {
            title: '职务',
            dataIndex: 'job',
            key: 'job',
            width: 100,
            render: (text) => (
                <Tooltip title={text}>
                    <div
                        style={{
                            color: 'black',
                            overflow: 'hidden',
                            whiteSpace: 'nowrap',
                            textOverflow: 'ellipsis',
                            maxWidth: '10ch', // 设置超出部分的背景颜色
                        }}
                    >
                        {text}
                    </div>
                </Tooltip>
            ),
        },
        {
            title: '专业领域',
            dataIndex: 'professionalField',
            key: 'professionalField',
            width: 150,
            render: (text) => (
                <Tooltip title={text}>
                    <div
                        style={{
                            color: 'black',
                            overflow: 'hidden',
                            whiteSpace: 'nowrap',
                            textOverflow: 'ellipsis',
                            maxWidth: '10ch', // 设置超出部分的背景颜色
                        }}
                    >
                        {text}
                    </div>
                </Tooltip>
            ),
        },
        {
            title: '个人简介',
            dataIndex: 'personalProfile',
            key: 'personalProfile',
            width: 150,
            render: (text) => (
                <Popover
                    color= '#252525'
                    content={
                        <div style={{ maxWidth: '300px',
                            maxHeight: '300px',
                            color: 'white',
                            overflowY: 'auto',}}>
                            {text}
                        </div>
                    }
                    trigger="hover" >
                    <div
                        style={{
                            color: 'black',
                            overflow: 'hidden',
                            whiteSpace: 'nowrap',
                            textOverflow: 'ellipsis',
                            maxWidth: '10ch', // 设置超出部分的背景颜色
                        }}
                    >
                        {text}
                    </div>
                </Popover>
            ),
        },
        {
            title: '参与讲座',
            dataIndex: 'speakerTotal',
            key: 'speakerTotal',
            width: 120,
            align: "center",
            render: (text, record, index) => {
                return (
                    <Space size="middle">
                        <a onClick={event => {
                            mySpeakerDetail(record);
                        }}>{text || 0}场</a>
                    </Space>
                )
            }
        },
        {
            title: '可见学院/单位',
            dataIndex: 'visibleCollege',
            key: 'visibleCollege',
            width: 150,
            render: (text) => (
                <Tooltip title={text}>
                    <div
                        style={{
                            color: 'black',
                            overflow: 'hidden',
                            whiteSpace: 'nowrap',
                            textOverflow: 'ellipsis',
                            maxWidth: '10ch', // 设置超出部分的背景颜色
                        }}
                    >
                        {text}
                    </div>
                </Tooltip>
            ),
        },
        {
            title: '照片',
            dataIndex: 'photoNumber',
            key: 'photoNumber',
            width: 150,
            align: "center",
            render: (photoNumber) => (
                photoNumber ? (
                    <Image
                        src={photoNumber}
                        alt="--"
                        style={{maxWidth: '100px', maxHeight: '100px', cursor: 'pointer'}}
                    />
                ) : (
                    <span></span>
                )
            ),
        },
        {
            title: '开启有效',
            dataIndex: 'isUse',
            key: 'isUse',
            width: 150,
            align: "center",
            render: (text, record, index) => {
                return (
                    <span>
                        <Switch checked={text === "1"} onClick={(e) => {
                            switchChange(record);
                        }}/>
                    </span>
                )
            }
        },
        {
            title: "操作",
            key: "action",
            width: 150,
            render: (text, record, index) => {
                return (
                    <Space size="middle">
                        <a onClick={event => {
                            updateShowModal(record);
                        }}>修改</a>
                        {record.isUse === "0" && (
                            <Popconfirm
                                description="确定要删除吗?"
                                onConfirm={confirm.bind(this, record)}
                                onCancel={cancel}
                                okText="删除"
                                cancelText="取消"
                            >
                                <a>删除</a>
                            </Popconfirm>
                        )}
                    </Space>
                )
            }
        }
    ];

    const [modalVisible, setModalVisible] = useState(false);
    const [modalImage, setModalImage] = useState('');

    const showModal = (image) => {
        setModalVisible(true);
        setModalImage(image);
    };
    const cancel = (e) => {
        // console.log(e);
        // message.info('取消删除');
    };
    const handleModalClose = () => {
        setModalVisible(false);
    };
    //控制 保存按钮 是否被禁用
    const [isDisableSave, setIsDisableSave] = useState(false)
    const beforeUpload = (file) => {
        const isJpgOrPng = file.type === 'image/jpeg'
            || file.type === 'image/png'
            || file.type === 'image/jpg'
            || file.type === 'image/webp';
        if (!isJpgOrPng) {
            message.error('上传失败!');
            setIsDisableSave(true)
            return false;
        }
        const isLt2M = file.size / 1024 / 1024 < 5;
        if (!isLt2M) {
            message.error('照片大小不能超过5M!');
            setIsDisableSave(true)
            return false;
        }
    };
    //获取分页信息
    useEffect(() => {
        getSpeakerByPage(tableList).then((res) => {
            if (res.code === 200) {
                setTableSource(res.data)
            } else {
                message.error(res.data)
            }
        });

    }, [tableList]);
    //更新状态是否失效
    const switchChange = (record) => {
        record.isUse === "1" ? record.isUse = "0" : record.isUse = "1";
        // 在这里可以进行其他操作,比如更新数据或发送请求
        updateIsUse(record).then((res) => {
            if (res.code === 200) {
                message.success("修改成功")
                setTableList({
                    "foreignSchool": '0',
                    "name": null,
                    "isUse": 0,
                    "pageNum": 1,
                    "pageSize": 10,
                    "speakerTotal": null,
                })
            } else {
                message.error("修改失败")
            }
        })
    }
    // 删除弹窗
    const confirm = (record) => {
        const param = {
            ...record,
            foreignSchool: "0"
        }
        deleteSpeaker(param).then(res => {
            if (res.code === 200) {
                message.success('删除成功');
                setTableList({
                    "foreignSchool": '0',
                    "name": null,
                    "isUse": 0,
                    "pageNum": 1,
                    "pageSize": 10,
                    "speakerTotal": null,
                })
            } else {
                message.error("删除失败:" + res.message);
            }
        })
    };
    const onScreeningFormFinish = (values) => {
        setTableList({
            "foreignSchool": '0',
            "name": values.name,
            "isUse": 0,
            "pageNum": currentPage,
            "pageSize": currentPageSize,
            "speakerTotal": values.speakerTotal,
        })
    };
    // 分页
    const onPageChange = (pageNo, pageSize) => {
        setCurrentPage(pageNo)
        setCurrentPageSize(pageSize)
        setTableList({
            "foreignSchool": '0',
            "pageNum": pageNo,
            "pageSize": pageSize
        });
    };

    const pageSizeChange = (current, pageSize) => {
        setCurrentPage(current)
        setResetPageSize(pageSize)
        setCurrentPageSize(pageSize)
    }
    const onChange = (e) => {
        console.log(e);
    };
    const onScreeningFormFinishFailed = (errorInfo) => {
        message.error("信息有误,请仔细检查后重新提交!").then(r => {
        });
    };
    //详情:
    const mySpeakerDetail = (record) => {
        setTableSourceDetail([])
        setIsShowModal(true)
        const param = {
            ...record,
            pageNum: 1,
            pageSize: 5,
        }
        setDetailParam(record)
        getSpeakerDetail(param).then(res => {
            if (res.code === 200) {
                setTableSourceDetail(res.data)
            }
        })

    }
    //修改
    const updateShowModal = (record) => {
        setName(record.name)
        setNameId(record.userId)
        modalForm.resetFields();
        setIsUpdateShow(true)
        // 将照片置空
        setBase64Data("")
        setPreviewImage('');
        setFileList([])
        //赋值操作
        modalForm.setFieldValue("id", record.id)
        modalForm.setFieldValue("userId", record.userName);
        modalForm.setFieldValue("departmentId", record.department);
        modalForm.setFieldValue("job", record.job);
        modalForm.setFieldValue("professionalField", record.professionalField);
        modalForm.setFieldValue("personalProfile", record.personalProfile);
        const strings = record.visibleCollege.split(',');
        modalForm.setFieldValue("visibleCollege", strings)

        if (record.photoNumber !== null && record.photoNumber !== undefined) {
            const uid = Math.random().toString(36).substring(7); // 生成唯一 ID
            const blob = dataURItoBlob(record.photoNumber);
            const fileObj = new File([blob], 'image.png', {type: 'image/png'});
            setFileList([{uid: uid, name: 'image.png', status: 'done', url: record.photoNumber}]); // 将文件对象添加到 fileList 中
            setBase64Data(record.photoNumber)
        } else {
            setBase64Data("")
            setPreviewImage('')
        }
        setIsInsert(false)
    }
    const dataURItoBlob = dataURI => {
        const byteString = atob(dataURI.split(',')[1]);
        const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
        const ab = new ArrayBuffer(byteString.length);
        const ia = new Uint8Array(ab);
        for (let i = 0; i < byteString.length; i++) {
            ia[i] = byteString.charCodeAt(i);
        }
        return new Blob([ab], {type: mimeString});
    };

    const handleOk = () => {
        myModalForm.submit();
    };
    const goInsert = () => {
        modalForm.resetFields();
        setBase64Data("")
        setKey({})
        setFileList([])
        setIsInsert(false)
    };
    //返回
    const goSpeaker = () => {
        setIsUpdateShow(false)
        setPreviewImage('')
        setBase64Data("");
        setIsDisableSave(false)
        // navigate('/lectureManagement/mainSpeakerManagement/insertAfterSchool', {});
        if (key !== null && Object.keys(key).length !== 0) {
            deletePhoto(key).then((res) => {
                if (res.code === 200) {
                    setKey({})
                }
            });
        }
        setIsInsert(true)
    };
    //重置
    const resetScreeningForm = () => {
        // 重置 pageSizeOptions
        setIsDisableSave(false)
        setIsUpdateShow(false)
        setBase64Data("")
        setIsInsert(true)
        setTableList({
            "foreignSchool": '0',
            "name": null,
            "isUse": 0,
            "pageNum": 1,
            "pageSize": 10,
            "speakerTotal": null,
        })
        setResetPageSize(10)
        screeningForm.resetFields()
    };
    // 弹窗取消按钮事件
    const handleCancel = () => {
        setDetailParam({})
        setIsShowModal(false);
    };
    const onFormValuesChanged = (changedValues, allValues) => {
    }
    const [loadings, setLoadings] = useState([]);
    const onModalFormFinish = (values) => {
        // 创建定时器并保存标识符
        setLoadings((prevLoadings) => {
            const newLoadings = [...prevLoadings];
            newLoadings[1] = true;
            return newLoadings;
        });

        const param = {
            ...values,
            id: values.id,
            name: values.userId || name,
            visibleCollege: values.visibleCollege.join(','),
            photoNumber: key.objectKey,
            userId: values.userId.value || nameId,
        }
        insertOrUpdateSpeaker(param).then(res => {
            if (res.data === true) {
                if (param.id === null || param.id === undefined) {
                    setIsInsert(true)
                    message.success("新增成功")
                } else {
                    setIsInsert(true)
                    message.success("修改成功")
                }
                setTableList({
                    "foreignSchool": '0',
                    "name": null,
                    "isUse": 0,
                    "pageNum": 1,
                    "pageSize": 10,
                    "speakerTotal": null,
                });
                setIsUpdateShow(false)
                setFileList([])
                myModalForm.resetFields()
            } else {
                message.error("主讲人信息重复!")
            }
        })
        setTimeout(() => {
            setLoadings((prevLoadings) => {
                const newLoadings = [...prevLoadings];
                newLoadings[1] = false;
                return newLoadings;
            });
        }, 1000);
    }
    const onModalFormFinishFailed = (errorInfo) => {
        message.error("信息有误,请仔细检查后重新提交!");
    };
    /// 输入框
    const [searchValue, setSearchValue] = useState();
    const [searching, setSearching] = useState(false);
    const [turnUser, setTurnUser] = useState([]);
    const [turnUserList, setTurnUserList] = useState([]);
    const [selectOption, setSelectOption] = useState([]);
    const [visibleCollegeList, setVisibleCollegeList] = useState([]);

    const changeTurnUser = (value, option) => {
        //解决方法,先把整个表单置空,然后再给第一个 下拉框赋值
        // modalForm.setFieldValue("departmentId","");
        myModalForm.resetFields();
        myModalForm.setFieldValue("userId", option);
        if (!value) {
            setTurnUser(null); // 清除选择的值
            setSelectOption([]); // 设置selectOption为空数组
        } else {
            setTurnUser(value);
        }
    }
    const handleDwmcChange = (value) => {
        // modalForm.setFieldValue("departmentId","");
        //解决方法,先把整个表单置空,然后再给第一个 下拉框赋值
        modalForm.resetFields();
        modalForm.setFieldValue("userId", value);
        // setSelectOption([])
        console.log("wo1",value)
        searchUser(value)
    }
    // 根据搜索的人 模糊匹配显示的人
    const searchUser = (value) => {
        if (value !== null && Object.keys(value).length !== 0) {
            const matchedValues = turnUserList.filter(item => item.label.toLowerCase()
                .includes(value.toLowerCase()));
            // 存储匹配到的value和label值
            setSelectOption(matchedValues);
        }
    }
    //获取主讲人对应的部门
    useEffect(() => {
        if (selectOption.length !== 0) {
            //解决方法,先把整个表单置空,然后再给第一个 下拉框赋值
            const user = {
                userId: selectOption[0].value,
            }
            getSpeakerDepartment(user).then(res => {
                if (res.code === 200) {
                    const speakerDepartment = res.data.map((item) => ({
                        value: item.departmentId,
                        label: item.department
                    }));
                    setDepartmentOptions(speakerDepartment)
                }
            })
        }
    }, [selectOption]);
    //获取可见的单位;
    useEffect(() => {
        getVisibleCollege().then(res => {
            if (res.code === 200) {
                const visibleCollege = res.data.map((item) => ({
                    value: item.departmentId,
                    label: item.department
                }));
                setVisibleCollegeList(visibleCollege)
            }
        })
    }, []);
    //获取主讲人信息
    useEffect(() => {
        getAllSpeaker().then(res => {
            if (res.code === 200) {
                const speakerData = res.data.map((item) => ({
                    value: item.userId,
                    label: item.speakerName
                }));
                setTurnUserList(speakerData)
            }
        })
    }, []);

    //过滤搜索
    const selectFilterOption = (input, option) =>
        (option?.label ?? '').includes(input.toLowerCase());
    const selectFilterOptionTwo = (input, option) =>
        (option?.label ?? '').includes(input.toLowerCase());
    //
    const [previewOpen, setPreviewOpen] = useState(false);
    const [previewImage, setPreviewImage] = useState('');
    const [fileList, setFileList] = useState([]);
    const [key, setKey] = useState({});
    //预览图片
    const handlePreview = async file => {
        if (!file.url && !file.preview && file.originFileObj) {
            const preview = await getBase64(file.originFileObj);
            file.preview = preview;
            setPreviewImage(preview); // 设置预览放大的图片
        } else {


            setPreviewImage(file.url || file.preview); // 设置预览放大的图片
        }
        setPreviewOpen(true);
    };


    const handleChange = info => {
        if (info.file.status === 'done') {
            // 上传成功
            message.success('上传成功');
            setKey(info.file.response.data)
            // 在这里可以处理上传成功后的逻辑,比如更新 fileList 状态
        } else if (info.file.status === 'error') {
            // 上传失败
            // setFileList([])
            // message.error('上传失败', info.file.error);
            // 在这里可以处理上传失败后的逻辑,比如提示用户重新上传
        }
        // 更新 fileList 状态
        info.fileList.forEach(async file => {
            // 如果上传成功且有原始文件对象,则获取其 base64 编码值
            const base64 = await getBase64(file.originFileObj);
            setPhotoData(base64)
        });
        setFileList(info.fileList);
    };

    const uploadButton = (
        <button
            style={{
                border: 0,
                background: 'none',
            }}
            type="button"
        >
            <PlusOutlined/>
            <div
                style={{
                    marginTop: 8,
                }}
            >
            </div>
        </button>
    );

    const onImagePreviewRemove = (file) => {
        setBase64Data("")
        setIsDisableSave(false)
        if (key !== null && Object.keys(key).length !== 0) {
            deletePhoto(key).then((res) => {
                if (res.code === 200) {
                    setKey({});
                }
            });
        }
    }
    return (
        <div>
            {isInsert ? (

                <div className="container" style={{padding: '5px'}}>

                    <Modal title={<h2 style={{textAlign: "center"}}>参与讲座详情</h2>}
                           open={isShowModal}
                           footer={null}
                           width={600}
                           onCancel={handleCancel}
                    >
                        <div>
                            <Table columns={speakerDetail} size={"middle"}
                                   style={{textAlign: 'center'}} // 整个表格数据居中
                                   dataSource={tableSourceDetail.list}
                                   pagination={false}/>
                            <Row
                                style={{marginTop: 10}}>
                                <Col span={4}>
                                    <Text style={{float: "left"}}>
                                        共 {tableSourceDetail.total} 项数据
                                    </Text>
                                </Col>
                                <Col span={20}>
                                    <Pagination
                                        style={{float: "right"}}
                                        total={tableSourceDetail.total}
                                        current={detailPage}
                                        defaultPageSize={5}
                                        onChange={detailPageChange}
                                        onShowSizeChange={detailPageSizeChange}
                                    />
                                </Col>
                            </Row>
                        </div>
                    </Modal>

                    <Form form={screeningForm} onFinish={onScreeningFormFinish} style={{marginTop: 10}}
                          onFinishFailed={onScreeningFormFinishFailed} autoComplete="off">
                        <Row gutter={[10, 10]}>
                            <Col span={7}>
                                <Form.Item label="姓名" colon={false} name="name">
                                    <Input allowClear onChange={onChange} placeholder="请输入姓名"/>
                                </Form.Item>
                            </Col>
                            <Col style={{marginLeft: '16.5%'}} span={9}>
                                <Form.Item label="讲座名称" colon={false} name="speakerTotal">
                                    <Input allowClear onChange={onChange} placeholder="请输入"/>
                                </Form.Item>
                            </Col>
                            <Col span={2}>
                                <Form.Item colon={false}><Button style={{float: "right"}} type="primary"
                                                                 htmlType="submit">搜索</Button></Form.Item>
                            </Col>
                            <Col span={2}><Form.Item><Button style={{float: "right"}}
                                                             onClick={resetScreeningForm}
                            >重置</Button></Form.Item></Col>
                        </Row>
                    </Form>
                    <Row>
                        <Col span={24}>
                            <Button style={{float: "left"}} onClick={goInsert}>新增</Button>
                        </Col>
                    </Row>
                    <div style={{marginTop: 10}}>
                    </div>

                    <div style={{marginTop: 10}}>
                        <Table columns={tableColumns} size={"middle"}
                               dataSource={tableSource.list}
                               style={{textAlign: 'center'}} // 整个表格数据居中
                               pagination={false}/>
                        <Row
                            style={{marginTop: 10}}>
                            <Col span={4}>
                                <Text
                                    style={{float: "left"}}>
                                    共 {tableSource.total || 0} 项数据
                                </Text>
                            </Col>
                            <Col span={20}>
                                <Pagination
                                    style={{float: "right"}}
                                    total={tableSource.total}
                                    current={currentPage}
                                    defaultPageSize={10}
                                    defaultCurrent={1}
                                    onChange={onPageChange}
                                    pageSize={resetPageSize}
                                    onShowSizeChange={pageSizeChange}
                                    showSizeChanger={true}
                                />
                            </Col>
                        </Row>
                    </div>
                </div>

            ) : (
                <div className="container" style={{padding: '5px'}}>
                    <div style={{
                        minHeight: '1%',
                        marginBottom: '20px',
                        backgroundColor: '#fff',
                        borderRadius: '8px',
                        padding: '-2px',
                        overflowY: "auto"
                    }}>
                        <Button type="text" style={{marginLeft: '-15px'}} onClick={goSpeaker}><LeftOutlined/> 返回 </Button>
                    </div>

                    <Form style={{marginTop: "20px"}}
                          onValuesChange={onFormValuesChanged}
                          form={myModalForm}
                          autoComplete="on">

                    </Form>
                    <Form style={{marginTop: "20px"}}
                          form={modalForm}
                          onFinish={onModalFormFinish}
                          onFinishFailed={onModalFormFinishFailed}
                    >
                        <Row>
                            <Col span={10}>
                                <Form.Item label="主讲人姓名&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
                                           colon={false}
                                           name="userId"
                                           style={{marginBottom: '30px'}}
                                           rules={[
                                               {
                                                   required: true,
                                                   message: '此项为必填项,请填写后提交',
                                               },
                                           ]}>
                                    <Select
                                        showSearch
                                        disabled={isUpdateShow}
                                        filterOption={selectFilterOption} onSearch={searchUser}
                                        notFoundContent={searching ? <Spin size="small"/> : null}
                                        value={turnUser}
                                        placeholder="请输入主讲人姓名"
                                        onSelect={changeTurnUser}
                                        options={selectOption}
                                        onChange={handleDwmcChange}
                                    />
                                </Form.Item>
                            </Col>
                        </Row>
                        <Row>
                            <Col span={10}>
                                <Form.Item label="所属学院/单位&nbsp;&nbsp;&nbsp;" colon={false} name="departmentId"
                                           style={{marginBottom: '30px'}}
                                           rules={[
                                               {
                                                   required: true,
                                                   message: '此项为必填项,请填写后提交',
                                               },
                                           ]}
                                >
                                    <Select
                                        placeholder="请输入所属学院/单位"
                                        options={departmentOptions}
                                    />
                                </Form.Item>
                            </Col>
                        </Row>
                        <Row>
                            <Col span={10}>
                                <Form.Item
                                    label="职务&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
                                    colon={false}
                                    name="job"
                                    rules={[
                                        {
                                            required: true,
                                            message: '此项为必填项,请填写后提交',
                                        },
                                    ]}
                                    style={{marginBottom: '30px'}}
                                >
                                    <Input
                                        showCount
                                        maxLength={100}
                                        allowClear={true}
                                        placeholder="请输入职务"
                                    />
                                </Form.Item>
                            </Col>
                        </Row>
                        <Row>
                            <Col span={10}>
                                <Form.Item
                                    hidden={true}
                                    label="id"
                                    colon={false} name="id"
                                    style={{marginBottom: '30px'}}
                                >
                                    <Input
                                        allowClear={true}
                                        placeholder="请输入"
                                    />
                                </Form.Item>
                            </Col>
                        </Row>
                        <Row>
                            <Col span={10}>
                                <Form.Item
                                    label="专业领域&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
                                    colon={false} name="professionalField"
                                    rules={[
                                        {
                                            required: true,
                                            message: '此项为必填项,请填写后提交',
                                        },
                                    ]}
                                    style={{marginBottom: '30px'}}
                                >
                                    <Input
                                        showCount
                                        maxLength={100}
                                        allowClear={true}
                                        placeholder="请输入专业领域"
                                    />
                                </Form.Item>
                            </Col>
                        </Row>
                        <Row>
                            <Col span={15}>
                                <Form.Item
                                    label="个人简介&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
                                    colon={false} name="personalProfile"
                                    rules={[
                                        {
                                            required: true,
                                            message: '此项为必填项,请填写后提交',
                                        },
                                    ]}
                                    style={{marginBottom: '30px'}}
                                >
                                    <TextArea
                                        showCount
                                        maxLength={1000}
                                        onChange={onChange}
                                        placeholder="请输入个人简介"
                                        style={{
                                            height: 120,
                                            resize: 'none',
                                        }}
                                    />
                                </Form.Item>
                            </Col>
                        </Row>
                        <Row>
                            <Col span={10}>
                                <Form.Item
                                    style={{marginBottom: '30px'}}
                                    label="可见学院/单位&nbsp;&nbsp;&nbsp;&nbsp;"
                                    colon={false}
                                    name="visibleCollege"
                                    rules={[
                                        {
                                            required: true,
                                            message: '此项为必填项,请填写后提交',
                                        },
                                    ]}
                                >
                                    <Select
                                        disabled={isUpdateShow}
                                        allowClear={true}
                                        mode="multiple"
                                        placeholder="请输入可见学院/单位(可多选)"
                                        filterOption={selectFilterOptionTwo}
                                        options={visibleCollegeList}
                                    />
                                </Form.Item>
                            </Col>
                        </Row>

                        <Row>
                            <Col span={5}>
                                <Form.Item
                                    label="照片上传&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
                                    style={{marginBottom: '50px'}}
                                    colon={false}
                                >
                                    <Upload
                                        name="file"
                                        style={{width: '100px'}}
                                        action={uploadPhotoApi}
                                        listType="picture-card"
                                        onPreview={handlePreview}
                                        fileList={fileList} // 将 fileList 传递给 Upload 组件
                                        onChange={handleChange}
                                        onRemove={onImagePreviewRemove}
                                        beforeUpload={beforeUpload}
                                    >
                                        {fileList.length >= 1 ? null : uploadButton}
                                    </Upload>
                                    {previewImage && (
                                        <Image style={{width: "100px"}}
                                            // className="custom-preview"
                                               src={previewImage}
                                               wrapperStyle={{
                                                   display: 'none'
                                               }}
                                               className="ant-upload-list-item"
                                               preview={{
                                                   visible: previewOpen,
                                                   onVisibleChange: (visible) => setPreviewOpen(visible),
                                                   afterOpenChange: (visible) => !visible && setPreviewImage(''),
                                               }}
                                        />
                                    )}
                                    <Row style={{marginTop: '10px'}}>
                                        <Col span={5}>
                                            <Form.Item colon={false}>
                                                <Button style={{ float: "left" }}
                                                        type="primary"
                                                        disabled={isDisableSave}
                                                        loading={loadings[1]}
                                                        htmlType="submit">保存</Button>
                                            </Form.Item>
                                        </Col>
                                        <Col span={10} style={{ display: 'flex', alignItems: 'center' }}>
                                            <Form.Item style={{ marginLeft: '60px' }}>
                                                <Button onClick={resetScreeningForm}>取消</Button>
                                            </Form.Item>
                                        </Col>
                                    </Row>


                                </Form.Item>

                            </Col>
                        </Row>
                    </Form>
                </div>
            )}
        </div>
    );
};

2:后端

需要正常配置好Minio

yml依赖:

Minio工具类

package com.ly.cloud.util.minio;

import com.ly.cloud.config.MinioConfig;
import io.minio.*;
import io.minio.http.Method;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

@Component
public class MinioUtil {

    @Resource
    private MinioConfig minioConfig;
    /**
     * minio参数
     */
    @Value("${minio.url}")
    private  String ENDPOINT;
    @Value("${minio.access-key}")
    private  String ACCESS_KEY;
    @Value("${minio.secret-key}")
    private  String SECRET_KEY ;

    /**
     * 桶占位符
     */
    private static final String BUCKET_PARAM = "${bucket}";
    /**
     * bucket权限-只读
     */
    private static final String READ_ONLY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
    /**
     * bucket权限-只读
     */
    private static final String WRITE_ONLY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
    /**
     * bucket权限-读写
     */
    private static final String READ_WRITE = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";

    /**
     * 文件url前半段
     *
     * @param bucket 桶
     * @return 前半段
     */
    public  String getObjectPrefixUrl(String bucket) {
        return String.format("%s/%s/", ENDPOINT, bucket);
    }

    /**
     * 创建桶
     *
     * @param bucket 桶
     */
    public  void makeBucket(String bucket) throws Exception {
        MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
        // 判断桶是否存在
        boolean isExist = client.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
        if (!isExist) {
            // 新建桶
            client.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
        }
    }

    /**
     * 更新桶权限策略
     *
     * @param bucket 桶
     * @param policy 权限
     */
    public  void setBucketPolicy(String bucket, String policy) throws Exception {
        MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
        setBucketPolicy(bucket, policy, client);
    }

    /**
     * 更新桶权限策略
     *
     * @param bucket 桶
     * @param policy 权限
     */
    public  void setBucketPolicy(String bucket, String policy, MinioClient client) throws Exception {
        switch (policy) {
            case "read-only":
                client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).config(READ_ONLY.replace(BUCKET_PARAM, bucket)).build());
                break;
            case "write-only":
                client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).config(WRITE_ONLY.replace(BUCKET_PARAM, bucket)).build());
                break;
            case "read-write":
                client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).config(READ_WRITE.replace(BUCKET_PARAM, bucket)).build());
                break;
            case "none":
            default:
                break;
        }
    }

    /**
     * 上传本地文件
     *
     * @param bucket    桶
     * @param objectKey 文件key
     * @param filePath  文件路径
     * @return 文件url
     */
    public  String uploadFile(String bucket, String objectKey, String filePath) throws Exception {
        MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
        client.uploadObject(UploadObjectArgs.builder().bucket(bucket).object(objectKey).filename(filePath).contentType("image/png").build());
        return getObjectPrefixUrl(bucket) + objectKey;
    }

    /**
     * 流式上传文件
     *
     * @param bucket      桶
     * @param objectKey   文件key
     * @param inputStream 文件输入流
     * @return 文件url
     */
    public  String uploadInputStream(String bucket, String objectKey, InputStream inputStream) throws Exception {
        MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
        client.putObject(PutObjectArgs.builder().bucket(bucket).object(objectKey).stream(inputStream, inputStream.available(), -1).contentType("image/png").build());
        return getObjectPrefixUrl(bucket) + objectKey;
    }

    /**
     * 下载文件
     * @param bucket    桶
     * @param objectKey 文件key
     * @return 文件流
     */
    public  InputStream download(String bucket, String objectKey) throws Exception {
        MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
        return client.getObject(GetObjectArgs.builder().bucket(bucket).object(objectKey).build());
    }

    /**
     * 文件复制
     *
     * @param sourceBucket    源桶
     * @param sourceObjectKey 源文件key
     * @param bucket          桶
     * @param objectKey       文件key
     * @return 新文件url
     */
    public  String copyFile(String sourceBucket, String sourceObjectKey, String bucket, String objectKey) throws Exception {
        MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
        CopySource source = CopySource.builder().bucket(sourceBucket).object(sourceObjectKey).build();
        client.copyObject(CopyObjectArgs.builder().bucket(bucket).object(objectKey).source(source).build());
        return getObjectPrefixUrl(bucket) + objectKey;
    }

    /**
     * 删除文件
     *
     * @param bucket    桶
     * @param objectKey 文件key
     */
    public  void deleteFile(String bucket, String objectKey) throws Exception {
        MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
        client.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectKey).build());
    }

    /**
     * 获取文件签名url
     * @param bucket    桶
     * @param objectKey 文件key
     * @param expires   签名有效时间  单位秒
     * @return 文件签名地址
     */
    public  String getSignedUrl(String bucket, String objectKey, int expires) throws Exception {
        MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
        return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucket).object(objectKey).expiry(expires).build());
    }


}

上传:

    @Override
    public Map<String, Object> uploadPhoto(MultipartFile file) throws Exception {
        FileInputStream stream = FileUtil.convertMultipartFileToInputStream(file);
        String stringTime = DateTimeUtils.stringTime();
        String fileName = generateRandomSixDigitNumber() + stringTime;
        minioUtil.uploadInputStream("mpbucket", "sjs/" + "/"+ fileName + ".png", stream);
        return map;
    }

下载:

返回base64 编码

     public static final String BASE_64 = "data:image/png;base64,";

 //获取文件的base 64 编码
        InputStream download = minioUtil.download("mpbucket", "sjs/" + SLASH_SUFFIX + fileName + ".png");
        byte[] bytes = IOUtils.toByteArray(download);
        String encoded = Base64.getEncoder().encodeToString(bytes);
        Map<String, Object> map = new HashMap<>();
        map.put("objectKey", fileName + ".png");
        map.put("base64", BASE_64 + encoded);