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="主讲人姓名 "
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="所属学院/单位 " 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="职务 "
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="专业领域 "
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="个人简介 "
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="可见学院/单位 "
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="照片上传 "
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);