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


    1:效果图

    上传回显:

    上传预览:

    预览-删除

    2:前端代码

    react 函数式组件

    1. /**
    2. * @Author
    3. * @Date Created in 2024/04/11 15:20
    4. * @DESCRIPTION: 主讲人信息
    5. * @Version V1.0
    6. */
    7. import React, {useEffect, useId, useState} from "react";
    8. import {
    9. Button,
    10. Col,
    11. DatePicker,
    12. Form,
    13. Image,
    14. Input,
    15. message,
    16. Modal,
    17. Pagination,
    18. Popconfirm, Popover,
    19. Row,
    20. Select,
    21. Space,
    22. Spin,
    23. Switch,
    24. Table,
    25. Tooltip,
    26. Typography,
    27. Upload
    28. } from "antd";
    29. import {
    30. deletePhoto,
    31. deleteSpeaker,
    32. getAllSpeaker,
    33. getSpeakerByPage,
    34. getSpeakerDepartment,
    35. getSpeakerDetail,
    36. getVisibleCollege,
    37. insertOrUpdateSpeaker,
    38. updateIsUse,
    39. uploadPhotoApi
    40. } from "api/index.js";
    41. import {useNavigate} from "react-router-dom";
    42. import {LeftOutlined, PlusOutlined} from "@ant-design/icons";
    43. import TextArea from "antd/es/input/TextArea.js";
    44. import MyUpload from "@/components/Upload/index.js";
    45. const {Title, Text} = Typography;
    46. const {RangePicker} = DatePicker;
    47. const getBase64 = (file) =>
    48. new Promise((resolve, reject) => {
    49. const reader = new FileReader();
    50. reader.readAsDataURL(file);
    51. reader.onload = () => resolve(reader.result);
    52. reader.onerror = (error) => reject(error);
    53. });
    54. export default () => {
    55. const [screeningForm] = Form.useForm();
    56. const [myModalForm] = Form.useForm();
    57. const [modalForm] = Form.useForm();
    58. const navigate = useNavigate();
    59. const [base64Data, setBase64Data] = useState(""); // 用于保存原始的 base64 数据
    60. const [isShowModal, setIsShowModal] = useState(false);
    61. const [resetPageSize, setResetPageSize] = useState(10);
    62. const [currentPage, setCurrentPage] = useState(1);
    63. const [currentPageSize, setCurrentPageSize] = useState(10);
    64. const [isInsert, setIsInsert] = useState(true);
    65. const [tableSource, setTableSource] = useState([])
    66. const [tableSourceDetail, setTableSourceDetail] = useState([])
    67. const [departmentOptions, setDepartmentOptions] = useState([])
    68. const [isUpdateShow, setIsUpdateShow] = useState(false);
    69. const [name, setName] = useState();
    70. const [nameId, setNameId] = useState();
    71. const [photoData, setPhotoData] = useState();
    72. const [isLoading,setIsLoading] = useState(false);
    73. // 参与讲座详情 分页参数
    74. const [detailParam, setDetailParam] = useState({});
    75. const [detailPage, setDetailPage] = useState(1);
    76. const [detailPageSize, setDetailPageSize] = useState(5);
    77. const detailPageChange = (pageNo, pageSize) => {
    78. setDetailPage(pageNo)
    79. setDetailPageSize(pageSize)
    80. const param = {
    81. ...detailParam,
    82. pageNum: pageNo,
    83. pageSize: pageSize,
    84. }
    85. getSpeakerDetail(param).then(res => {
    86. if (res.code === 200) {
    87. setTableSourceDetail(res.data)
    88. }
    89. })
    90. };
    91. const detailPageSizeChange = (current, pageSize) => {
    92. setDetailPage(current)
    93. setDetailPageSize(pageSize)
    94. }
    95. /// 上传相关 2 个 state
    96. const [tableList, setTableList] = useState(
    97. {
    98. "foreignSchool": '0',
    99. "name": null,
    100. "isUse": 0,
    101. "pageNum": 1,
    102. "pageSize": 10,
    103. "speakerTotal": null,
    104. });
    105. const speakerDetail = [
    106. {
    107. title: '讲座名称',
    108. dataIndex: 'speakerName',
    109. key: 'speakerName',
    110. width: 200,
    111. align: "center"
    112. },
    113. {
    114. title: '举办时间',
    115. dataIndex: 'holdingTime',
    116. key: 'holdingTime',
    117. width: 300,
    118. align: "center"
    119. },
    120. ]
    121. const tableColumns = [
    122. {
    123. title: '主讲人姓名',
    124. dataIndex: 'name',
    125. key: 'name',
    126. width: 120,
    127. render: (text) => (
    128. <Tooltip title={text}>
    129. <div
    130. style={{
    131. color: 'black',
    132. overflow: 'hidden',
    133. whiteSpace: 'nowrap',
    134. textOverflow: 'ellipsis',
    135. maxWidth: '30ch', // 设置超出部分的背景颜色
    136. }}
    137. >
    138. {text}
    139. div>
    140. Tooltip>
    141. ),
    142. },
    143. {
    144. title: '职工号',
    145. dataIndex: 'userId',
    146. key: 'userId',
    147. width: 120,
    148. render: (text) => (
    149. <Tooltip title={text}>
    150. <div
    151. style={{
    152. color: 'black',
    153. overflow: 'hidden',
    154. whiteSpace: 'nowrap',
    155. textOverflow: 'ellipsis',
    156. maxWidth: '30ch', // 设置超出部分的背景颜色
    157. }}
    158. >
    159. {text}
    160. div>
    161. Tooltip>
    162. ),
    163. },
    164. {
    165. title: '所属学院/单位',
    166. dataIndex: 'department',
    167. key: 'department',
    168. width: 150,
    169. render: (text) => (
    170. <Tooltip title={text}>
    171. <div
    172. style={{
    173. color: 'black',
    174. overflow: 'hidden',
    175. whiteSpace: 'nowrap',
    176. textOverflow: 'ellipsis',
    177. maxWidth: '10ch', // 设置超出部分的背景颜色
    178. }}
    179. >
    180. {text}
    181. div>
    182. Tooltip>
    183. ),
    184. },
    185. {
    186. title: '职务',
    187. dataIndex: 'job',
    188. key: 'job',
    189. width: 100,
    190. render: (text) => (
    191. <Tooltip title={text}>
    192. <div
    193. style={{
    194. color: 'black',
    195. overflow: 'hidden',
    196. whiteSpace: 'nowrap',
    197. textOverflow: 'ellipsis',
    198. maxWidth: '10ch', // 设置超出部分的背景颜色
    199. }}
    200. >
    201. {text}
    202. div>
    203. Tooltip>
    204. ),
    205. },
    206. {
    207. title: '专业领域',
    208. dataIndex: 'professionalField',
    209. key: 'professionalField',
    210. width: 150,
    211. render: (text) => (
    212. <Tooltip title={text}>
    213. <div
    214. style={{
    215. color: 'black',
    216. overflow: 'hidden',
    217. whiteSpace: 'nowrap',
    218. textOverflow: 'ellipsis',
    219. maxWidth: '10ch', // 设置超出部分的背景颜色
    220. }}
    221. >
    222. {text}
    223. div>
    224. Tooltip>
    225. ),
    226. },
    227. {
    228. title: '个人简介',
    229. dataIndex: 'personalProfile',
    230. key: 'personalProfile',
    231. width: 150,
    232. render: (text) => (
    233. <Popover
    234. color= '#252525'
    235. content={
    236. <div style={{ maxWidth: '300px',
    237. maxHeight: '300px',
    238. color: 'white',
    239. overflowY: 'auto',}}>
    240. {text}
    241. div>
    242. }
    243. trigger="hover" >
    244. <div
    245. style={{
    246. color: 'black',
    247. overflow: 'hidden',
    248. whiteSpace: 'nowrap',
    249. textOverflow: 'ellipsis',
    250. maxWidth: '10ch', // 设置超出部分的背景颜色
    251. }}
    252. >
    253. {text}
    254. div>
    255. Popover>
    256. ),
    257. },
    258. {
    259. title: '参与讲座',
    260. dataIndex: 'speakerTotal',
    261. key: 'speakerTotal',
    262. width: 120,
    263. align: "center",
    264. render: (text, record, index) => {
    265. return (
    266. <Space size="middle">
    267. <a onClick={event => {
    268. mySpeakerDetail(record);
    269. }}>{text || 0}场a>
    270. Space>
    271. )
    272. }
    273. },
    274. {
    275. title: '可见学院/单位',
    276. dataIndex: 'visibleCollege',
    277. key: 'visibleCollege',
    278. width: 150,
    279. render: (text) => (
    280. <Tooltip title={text}>
    281. <div
    282. style={{
    283. color: 'black',
    284. overflow: 'hidden',
    285. whiteSpace: 'nowrap',
    286. textOverflow: 'ellipsis',
    287. maxWidth: '10ch', // 设置超出部分的背景颜色
    288. }}
    289. >
    290. {text}
    291. div>
    292. Tooltip>
    293. ),
    294. },
    295. {
    296. title: '照片',
    297. dataIndex: 'photoNumber',
    298. key: 'photoNumber',
    299. width: 150,
    300. align: "center",
    301. render: (photoNumber) => (
    302. photoNumber ? (
    303. <Image
    304. src={photoNumber}
    305. alt="--"
    306. style={{maxWidth: '100px', maxHeight: '100px', cursor: 'pointer'}}
    307. />
    308. ) : (
    309. <span>span>
    310. )
    311. ),
    312. },
    313. {
    314. title: '开启有效',
    315. dataIndex: 'isUse',
    316. key: 'isUse',
    317. width: 150,
    318. align: "center",
    319. render: (text, record, index) => {
    320. return (
    321. <span>
    322. <Switch checked={text === "1"} onClick={(e) => {
    323. switchChange(record);
    324. }}/>
    325. span>
    326. )
    327. }
    328. },
    329. {
    330. title: "操作",
    331. key: "action",
    332. width: 150,
    333. render: (text, record, index) => {
    334. return (
    335. <Space size="middle">
    336. <a onClick={event => {
    337. updateShowModal(record);
    338. }}>修改a>
    339. {record.isUse === "0" && (
    340. <Popconfirm
    341. description="确定要删除吗?"
    342. onConfirm={confirm.bind(this, record)}
    343. onCancel={cancel}
    344. okText="删除"
    345. cancelText="取消"
    346. >
    347. <a>删除a>
    348. Popconfirm>
    349. )}
    350. Space>
    351. )
    352. }
    353. }
    354. ];
    355. const [modalVisible, setModalVisible] = useState(false);
    356. const [modalImage, setModalImage] = useState('');
    357. const showModal = (image) => {
    358. setModalVisible(true);
    359. setModalImage(image);
    360. };
    361. const cancel = (e) => {
    362. // console.log(e);
    363. // message.info('取消删除');
    364. };
    365. const handleModalClose = () => {
    366. setModalVisible(false);
    367. };
    368. //控制 保存按钮 是否被禁用
    369. const [isDisableSave, setIsDisableSave] = useState(false)
    370. const beforeUpload = (file) => {
    371. const isJpgOrPng = file.type === 'image/jpeg'
    372. || file.type === 'image/png'
    373. || file.type === 'image/jpg'
    374. || file.type === 'image/webp';
    375. if (!isJpgOrPng) {
    376. message.error('上传失败!');
    377. setIsDisableSave(true)
    378. return false;
    379. }
    380. const isLt2M = file.size / 1024 / 1024 < 5;
    381. if (!isLt2M) {
    382. message.error('照片大小不能超过5M!');
    383. setIsDisableSave(true)
    384. return false;
    385. }
    386. };
    387. //获取分页信息
    388. useEffect(() => {
    389. getSpeakerByPage(tableList).then((res) => {
    390. if (res.code === 200) {
    391. setTableSource(res.data)
    392. } else {
    393. message.error(res.data)
    394. }
    395. });
    396. }, [tableList]);
    397. //更新状态是否失效
    398. const switchChange = (record) => {
    399. record.isUse === "1" ? record.isUse = "0" : record.isUse = "1";
    400. // 在这里可以进行其他操作,比如更新数据或发送请求
    401. updateIsUse(record).then((res) => {
    402. if (res.code === 200) {
    403. message.success("修改成功")
    404. setTableList({
    405. "foreignSchool": '0',
    406. "name": null,
    407. "isUse": 0,
    408. "pageNum": 1,
    409. "pageSize": 10,
    410. "speakerTotal": null,
    411. })
    412. } else {
    413. message.error("修改失败")
    414. }
    415. })
    416. }
    417. // 删除弹窗
    418. const confirm = (record) => {
    419. const param = {
    420. ...record,
    421. foreignSchool: "0"
    422. }
    423. deleteSpeaker(param).then(res => {
    424. if (res.code === 200) {
    425. message.success('删除成功');
    426. setTableList({
    427. "foreignSchool": '0',
    428. "name": null,
    429. "isUse": 0,
    430. "pageNum": 1,
    431. "pageSize": 10,
    432. "speakerTotal": null,
    433. })
    434. } else {
    435. message.error("删除失败:" + res.message);
    436. }
    437. })
    438. };
    439. const onScreeningFormFinish = (values) => {
    440. setTableList({
    441. "foreignSchool": '0',
    442. "name": values.name,
    443. "isUse": 0,
    444. "pageNum": currentPage,
    445. "pageSize": currentPageSize,
    446. "speakerTotal": values.speakerTotal,
    447. })
    448. };
    449. // 分页
    450. const onPageChange = (pageNo, pageSize) => {
    451. setCurrentPage(pageNo)
    452. setCurrentPageSize(pageSize)
    453. setTableList({
    454. "foreignSchool": '0',
    455. "pageNum": pageNo,
    456. "pageSize": pageSize
    457. });
    458. };
    459. const pageSizeChange = (current, pageSize) => {
    460. setCurrentPage(current)
    461. setResetPageSize(pageSize)
    462. setCurrentPageSize(pageSize)
    463. }
    464. const onChange = (e) => {
    465. console.log(e);
    466. };
    467. const onScreeningFormFinishFailed = (errorInfo) => {
    468. message.error("信息有误,请仔细检查后重新提交!").then(r => {
    469. });
    470. };
    471. //详情:
    472. const mySpeakerDetail = (record) => {
    473. setTableSourceDetail([])
    474. setIsShowModal(true)
    475. const param = {
    476. ...record,
    477. pageNum: 1,
    478. pageSize: 5,
    479. }
    480. setDetailParam(record)
    481. getSpeakerDetail(param).then(res => {
    482. if (res.code === 200) {
    483. setTableSourceDetail(res.data)
    484. }
    485. })
    486. }
    487. //修改
    488. const updateShowModal = (record) => {
    489. setName(record.name)
    490. setNameId(record.userId)
    491. modalForm.resetFields();
    492. setIsUpdateShow(true)
    493. // 将照片置空
    494. setBase64Data("")
    495. setPreviewImage('');
    496. setFileList([])
    497. //赋值操作
    498. modalForm.setFieldValue("id", record.id)
    499. modalForm.setFieldValue("userId", record.userName);
    500. modalForm.setFieldValue("departmentId", record.department);
    501. modalForm.setFieldValue("job", record.job);
    502. modalForm.setFieldValue("professionalField", record.professionalField);
    503. modalForm.setFieldValue("personalProfile", record.personalProfile);
    504. const strings = record.visibleCollege.split(',');
    505. modalForm.setFieldValue("visibleCollege", strings)
    506. if (record.photoNumber !== null && record.photoNumber !== undefined) {
    507. const uid = Math.random().toString(36).substring(7); // 生成唯一 ID
    508. const blob = dataURItoBlob(record.photoNumber);
    509. const fileObj = new File([blob], 'image.png', {type: 'image/png'});
    510. setFileList([{uid: uid, name: 'image.png', status: 'done', url: record.photoNumber}]); // 将文件对象添加到 fileList 中
    511. setBase64Data(record.photoNumber)
    512. } else {
    513. setBase64Data("")
    514. setPreviewImage('')
    515. }
    516. setIsInsert(false)
    517. }
    518. const dataURItoBlob = dataURI => {
    519. const byteString = atob(dataURI.split(',')[1]);
    520. const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
    521. const ab = new ArrayBuffer(byteString.length);
    522. const ia = new Uint8Array(ab);
    523. for (let i = 0; i < byteString.length; i++) {
    524. ia[i] = byteString.charCodeAt(i);
    525. }
    526. return new Blob([ab], {type: mimeString});
    527. };
    528. const handleOk = () => {
    529. myModalForm.submit();
    530. };
    531. const goInsert = () => {
    532. modalForm.resetFields();
    533. setBase64Data("")
    534. setKey({})
    535. setFileList([])
    536. setIsInsert(false)
    537. };
    538. //返回
    539. const goSpeaker = () => {
    540. setIsUpdateShow(false)
    541. setPreviewImage('')
    542. setBase64Data("");
    543. setIsDisableSave(false)
    544. // navigate('/lectureManagement/mainSpeakerManagement/insertAfterSchool', {});
    545. if (key !== null && Object.keys(key).length !== 0) {
    546. deletePhoto(key).then((res) => {
    547. if (res.code === 200) {
    548. setKey({})
    549. }
    550. });
    551. }
    552. setIsInsert(true)
    553. };
    554. //重置
    555. const resetScreeningForm = () => {
    556. // 重置 pageSizeOptions
    557. setIsDisableSave(false)
    558. setIsUpdateShow(false)
    559. setBase64Data("")
    560. setIsInsert(true)
    561. setTableList({
    562. "foreignSchool": '0',
    563. "name": null,
    564. "isUse": 0,
    565. "pageNum": 1,
    566. "pageSize": 10,
    567. "speakerTotal": null,
    568. })
    569. setResetPageSize(10)
    570. screeningForm.resetFields()
    571. };
    572. // 弹窗取消按钮事件
    573. const handleCancel = () => {
    574. setDetailParam({})
    575. setIsShowModal(false);
    576. };
    577. const onFormValuesChanged = (changedValues, allValues) => {
    578. }
    579. const [loadings, setLoadings] = useState([]);
    580. const onModalFormFinish = (values) => {
    581. // 创建定时器并保存标识符
    582. setLoadings((prevLoadings) => {
    583. const newLoadings = [...prevLoadings];
    584. newLoadings[1] = true;
    585. return newLoadings;
    586. });
    587. const param = {
    588. ...values,
    589. id: values.id,
    590. name: values.userId || name,
    591. visibleCollege: values.visibleCollege.join(','),
    592. photoNumber: key.objectKey,
    593. userId: values.userId.value || nameId,
    594. }
    595. insertOrUpdateSpeaker(param).then(res => {
    596. if (res.data === true) {
    597. if (param.id === null || param.id === undefined) {
    598. setIsInsert(true)
    599. message.success("新增成功")
    600. } else {
    601. setIsInsert(true)
    602. message.success("修改成功")
    603. }
    604. setTableList({
    605. "foreignSchool": '0',
    606. "name": null,
    607. "isUse": 0,
    608. "pageNum": 1,
    609. "pageSize": 10,
    610. "speakerTotal": null,
    611. });
    612. setIsUpdateShow(false)
    613. setFileList([])
    614. myModalForm.resetFields()
    615. } else {
    616. message.error("主讲人信息重复!")
    617. }
    618. })
    619. setTimeout(() => {
    620. setLoadings((prevLoadings) => {
    621. const newLoadings = [...prevLoadings];
    622. newLoadings[1] = false;
    623. return newLoadings;
    624. });
    625. }, 1000);
    626. }
    627. const onModalFormFinishFailed = (errorInfo) => {
    628. message.error("信息有误,请仔细检查后重新提交!");
    629. };
    630. /// 输入框
    631. const [searchValue, setSearchValue] = useState();
    632. const [searching, setSearching] = useState(false);
    633. const [turnUser, setTurnUser] = useState([]);
    634. const [turnUserList, setTurnUserList] = useState([]);
    635. const [selectOption, setSelectOption] = useState([]);
    636. const [visibleCollegeList, setVisibleCollegeList] = useState([]);
    637. const changeTurnUser = (value, option) => {
    638. //解决方法,先把整个表单置空,然后再给第一个 下拉框赋值
    639. // modalForm.setFieldValue("departmentId","");
    640. myModalForm.resetFields();
    641. myModalForm.setFieldValue("userId", option);
    642. if (!value) {
    643. setTurnUser(null); // 清除选择的值
    644. setSelectOption([]); // 设置selectOption为空数组
    645. } else {
    646. setTurnUser(value);
    647. }
    648. }
    649. const handleDwmcChange = (value) => {
    650. // modalForm.setFieldValue("departmentId","");
    651. //解决方法,先把整个表单置空,然后再给第一个 下拉框赋值
    652. modalForm.resetFields();
    653. modalForm.setFieldValue("userId", value);
    654. // setSelectOption([])
    655. console.log("wo1",value)
    656. searchUser(value)
    657. }
    658. // 根据搜索的人 模糊匹配显示的人
    659. const searchUser = (value) => {
    660. if (value !== null && Object.keys(value).length !== 0) {
    661. const matchedValues = turnUserList.filter(item => item.label.toLowerCase()
    662. .includes(value.toLowerCase()));
    663. // 存储匹配到的value和label值
    664. setSelectOption(matchedValues);
    665. }
    666. }
    667. //获取主讲人对应的部门
    668. useEffect(() => {
    669. if (selectOption.length !== 0) {
    670. //解决方法,先把整个表单置空,然后再给第一个 下拉框赋值
    671. const user = {
    672. userId: selectOption[0].value,
    673. }
    674. getSpeakerDepartment(user).then(res => {
    675. if (res.code === 200) {
    676. const speakerDepartment = res.data.map((item) => ({
    677. value: item.departmentId,
    678. label: item.department
    679. }));
    680. setDepartmentOptions(speakerDepartment)
    681. }
    682. })
    683. }
    684. }, [selectOption]);
    685. //获取可见的单位;
    686. useEffect(() => {
    687. getVisibleCollege().then(res => {
    688. if (res.code === 200) {
    689. const visibleCollege = res.data.map((item) => ({
    690. value: item.departmentId,
    691. label: item.department
    692. }));
    693. setVisibleCollegeList(visibleCollege)
    694. }
    695. })
    696. }, []);
    697. //获取主讲人信息
    698. useEffect(() => {
    699. getAllSpeaker().then(res => {
    700. if (res.code === 200) {
    701. const speakerData = res.data.map((item) => ({
    702. value: item.userId,
    703. label: item.speakerName
    704. }));
    705. setTurnUserList(speakerData)
    706. }
    707. })
    708. }, []);
    709. //过滤搜索
    710. const selectFilterOption = (input, option) =>
    711. (option?.label ?? '').includes(input.toLowerCase());
    712. const selectFilterOptionTwo = (input, option) =>
    713. (option?.label ?? '').includes(input.toLowerCase());
    714. //
    715. const [previewOpen, setPreviewOpen] = useState(false);
    716. const [previewImage, setPreviewImage] = useState('');
    717. const [fileList, setFileList] = useState([]);
    718. const [key, setKey] = useState({});
    719. //预览图片
    720. const handlePreview = async file => {
    721. if (!file.url && !file.preview && file.originFileObj) {
    722. const preview = await getBase64(file.originFileObj);
    723. file.preview = preview;
    724. setPreviewImage(preview); // 设置预览放大的图片
    725. } else {
    726. setPreviewImage(file.url || file.preview); // 设置预览放大的图片
    727. }
    728. setPreviewOpen(true);
    729. };
    730. const handleChange = info => {
    731. if (info.file.status === 'done') {
    732. // 上传成功
    733. message.success('上传成功');
    734. setKey(info.file.response.data)
    735. // 在这里可以处理上传成功后的逻辑,比如更新 fileList 状态
    736. } else if (info.file.status === 'error') {
    737. // 上传失败
    738. // setFileList([])
    739. // message.error('上传失败', info.file.error);
    740. // 在这里可以处理上传失败后的逻辑,比如提示用户重新上传
    741. }
    742. // 更新 fileList 状态
    743. info.fileList.forEach(async file => {
    744. // 如果上传成功且有原始文件对象,则获取其 base64 编码值
    745. const base64 = await getBase64(file.originFileObj);
    746. setPhotoData(base64)
    747. });
    748. setFileList(info.fileList);
    749. };
    750. const uploadButton = (
    751. <button
    752. style={{
    753. border: 0,
    754. background: 'none',
    755. }}
    756. type="button"
    757. >
    758. <PlusOutlined/>
    759. <div
    760. style={{
    761. marginTop: 8,
    762. }}
    763. >
    764. div>
    765. button>
    766. );
    767. const onImagePreviewRemove = (file) => {
    768. setBase64Data("")
    769. setIsDisableSave(false)
    770. if (key !== null && Object.keys(key).length !== 0) {
    771. deletePhoto(key).then((res) => {
    772. if (res.code === 200) {
    773. setKey({});
    774. }
    775. });
    776. }
    777. }
    778. return (
    779. <div>
    780. {isInsert ? (
    781. <div className="container" style={{padding: '5px'}}>
    782. <Modal title={<h2 style={{textAlign: "center"}}>参与讲座详情h2>}
    783. open={isShowModal}
    784. footer={null}
    785. width={600}
    786. onCancel={handleCancel}
    787. >
    788. <div>
    789. <Table columns={speakerDetail} size={"middle"}
    790. style={{textAlign: 'center'}} // 整个表格数据居中
    791. dataSource={tableSourceDetail.list}
    792. pagination={false}/>
    793. <Row
    794. style={{marginTop: 10}}>
    795. <Col span={4}>
    796. <Text style={{float: "left"}}>
    797. 共 {tableSourceDetail.total} 项数据
    798. Text>
    799. Col>
    800. <Col span={20}>
    801. <Pagination
    802. style={{float: "right"}}
    803. total={tableSourceDetail.total}
    804. current={detailPage}
    805. defaultPageSize={5}
    806. onChange={detailPageChange}
    807. onShowSizeChange={detailPageSizeChange}
    808. />
    809. Col>
    810. Row>
    811. div>
    812. Modal>
    813. <Form form={screeningForm} onFinish={onScreeningFormFinish} style={{marginTop: 10}}
    814. onFinishFailed={onScreeningFormFinishFailed} autoComplete="off">
    815. <Row gutter={[10, 10]}>
    816. <Col span={7}>
    817. <Form.Item label="姓名" colon={false} name="name">
    818. <Input allowClear onChange={onChange} placeholder="请输入姓名"/>
    819. Form.Item>
    820. Col>
    821. <Col style={{marginLeft: '16.5%'}} span={9}>
    822. <Form.Item label="讲座名称" colon={false} name="speakerTotal">
    823. <Input allowClear onChange={onChange} placeholder="请输入"/>
    824. Form.Item>
    825. Col>
    826. <Col span={2}>
    827. <Form.Item colon={false}><Button style={{float: "right"}} type="primary"
    828. htmlType="submit">搜索Button>Form.Item>
    829. Col>
    830. <Col span={2}><Form.Item><Button style={{float: "right"}}
    831. onClick={resetScreeningForm}
    832. >重置Button>Form.Item>Col>
    833. Row>
    834. Form>
    835. <Row>
    836. <Col span={24}>
    837. <Button style={{float: "left"}} onClick={goInsert}>新增Button>
    838. Col>
    839. Row>
    840. <div style={{marginTop: 10}}>
    841. div>
    842. <div style={{marginTop: 10}}>
    843. <Table columns={tableColumns} size={"middle"}
    844. dataSource={tableSource.list}
    845. style={{textAlign: 'center'}} // 整个表格数据居中
    846. pagination={false}/>
    847. <Row
    848. style={{marginTop: 10}}>
    849. <Col span={4}>
    850. <Text
    851. style={{float: "left"}}>
    852. 共 {tableSource.total || 0} 项数据
    853. Text>
    854. Col>
    855. <Col span={20}>
    856. <Pagination
    857. style={{float: "right"}}
    858. total={tableSource.total}
    859. current={currentPage}
    860. defaultPageSize={10}
    861. defaultCurrent={1}
    862. onChange={onPageChange}
    863. pageSize={resetPageSize}
    864. onShowSizeChange={pageSizeChange}
    865. showSizeChanger={true}
    866. />
    867. Col>
    868. Row>
    869. div>
    870. div>
    871. ) : (
    872. <div className="container" style={{padding: '5px'}}>
    873. <div style={{
    874. minHeight: '1%',
    875. marginBottom: '20px',
    876. backgroundColor: '#fff',
    877. borderRadius: '8px',
    878. padding: '-2px',
    879. overflowY: "auto"
    880. }}>
    881. <Button type="text" style={{marginLeft: '-15px'}} onClick={goSpeaker}><LeftOutlined/> 返回 Button>
    882. div>
    883. <Form style={{marginTop: "20px"}}
    884. onValuesChange={onFormValuesChanged}
    885. form={myModalForm}
    886. autoComplete="on">
    887. Form>
    888. <Form style={{marginTop: "20px"}}
    889. form={modalForm}
    890. onFinish={onModalFormFinish}
    891. onFinishFailed={onModalFormFinishFailed}
    892. >
    893. <Row>
    894. <Col span={10}>
    895. <Form.Item label="主讲人姓名        "
    896. colon={false}
    897. name="userId"
    898. style={{marginBottom: '30px'}}
    899. rules={[
    900. {
    901. required: true,
    902. message: '此项为必填项请填写后提交',
    903. },
    904. ]}>
    905. <Select
    906. showSearch
    907. disabled={isUpdateShow}
    908. filterOption={selectFilterOption} onSearch={searchUser}
    909. notFoundContent={searching ? <Spin size="small"/> : null}
    910. value={turnUser}
    911. placeholder="请输入主讲人姓名"
    912. onSelect={changeTurnUser}
    913. options={selectOption}
    914. onChange={handleDwmcChange}
    915. />
    916. Form.Item>
    917. Col>
    918. Row>
    919. <Row>
    920. <Col span={10}>
    921. <Form.Item label="所属学院/单位   " colon={false} name="departmentId"
    922. style={{marginBottom: '30px'}}
    923. rules={[
    924. {
    925. required: true,
    926. message: '此项为必填项请填写后提交',
    927. },
    928. ]}
    929. >
    930. <Select
    931. placeholder="请输入所属学院/单位"
    932. options={departmentOptions}
    933. />
    934. Form.Item>
    935. Col>
    936. Row>
    937. <Row>
    938. <Col span={10}>
    939. <Form.Item
    940. label="职务                   "
    941. colon={false}
    942. name="job"
    943. rules={[
    944. {
    945. required: true,
    946. message: '此项为必填项请填写后提交',
    947. },
    948. ]}
    949. style={{marginBottom: '30px'}}
    950. >
    951. <Input
    952. showCount
    953. maxLength={100}
    954. allowClear={true}
    955. placeholder="请输入职务"
    956. />
    957. Form.Item>
    958. Col>
    959. Row>
    960. <Row>
    961. <Col span={10}>
    962. <Form.Item
    963. hidden={true}
    964. label="id"
    965. colon={false} name="id"
    966. style={{marginBottom: '30px'}}
    967. >
    968. <Input
    969. allowClear={true}
    970. placeholder="请输入"
    971. />
    972. Form.Item>
    973. Col>
    974. Row>
    975. <Row>
    976. <Col span={10}>
    977. <Form.Item
    978. label="专业领域            "
    979. colon={false} name="professionalField"
    980. rules={[
    981. {
    982. required: true,
    983. message: '此项为必填项请填写后提交',
    984. },
    985. ]}
    986. style={{marginBottom: '30px'}}
    987. >
    988. <Input
    989. showCount
    990. maxLength={100}
    991. allowClear={true}
    992. placeholder="请输入专业领域"
    993. />
    994. Form.Item>
    995. Col>
    996. Row>
    997. <Row>
    998. <Col span={15}>
    999. <Form.Item
    1000. label="个人简介            "
    1001. colon={false} name="personalProfile"
    1002. rules={[
    1003. {
    1004. required: true,
    1005. message: '此项为必填项请填写后提交',
    1006. },
    1007. ]}
    1008. style={{marginBottom: '30px'}}
    1009. >
    1010. <TextArea
    1011. showCount
    1012. maxLength={1000}
    1013. onChange={onChange}
    1014. placeholder="请输入个人简介"
    1015. style={{
    1016. height: 120,
    1017. resize: 'none',
    1018. }}
    1019. />
    1020. Form.Item>
    1021. Col>
    1022. Row>
    1023. <Row>
    1024. <Col span={10}>
    1025. <Form.Item
    1026. style={{marginBottom: '30px'}}
    1027. label="可见学院/单位    "
    1028. colon={false}
    1029. name="visibleCollege"
    1030. rules={[
    1031. {
    1032. required: true,
    1033. message: '此项为必填项请填写后提交',
    1034. },
    1035. ]}
    1036. >
    1037. <Select
    1038. disabled={isUpdateShow}
    1039. allowClear={true}
    1040. mode="multiple"
    1041. placeholder="请输入可见学院/单位(可多选)"
    1042. filterOption={selectFilterOptionTwo}
    1043. options={visibleCollegeList}
    1044. />
    1045. Form.Item>
    1046. Col>
    1047. Row>
    1048. <Row>
    1049. <Col span={5}>
    1050. <Form.Item
    1051. label="照片上传                "
    1052. style={{marginBottom: '50px'}}
    1053. colon={false}
    1054. >
    1055. <Upload
    1056. name="file"
    1057. style={{width: '100px'}}
    1058. action={uploadPhotoApi}
    1059. listType="picture-card"
    1060. onPreview={handlePreview}
    1061. fileList={fileList} // fileList 传递给 Upload 组件
    1062. onChange={handleChange}
    1063. onRemove={onImagePreviewRemove}
    1064. beforeUpload={beforeUpload}
    1065. >
    1066. {fileList.length >= 1 ? null : uploadButton}
    1067. Upload>
    1068. {previewImage && (
    1069. <Image style={{width: "100px"}}
    1070. // className="custom-preview"
    1071. src={previewImage}
    1072. wrapperStyle={{
    1073. display: 'none'
    1074. }}
    1075. className="ant-upload-list-item"
    1076. preview={{
    1077. visible: previewOpen,
    1078. onVisibleChange: (visible) => setPreviewOpen(visible),
    1079. afterOpenChange: (visible) => !visible && setPreviewImage(''),
    1080. }}
    1081. />
    1082. )}
    1083. <Row style={{marginTop: '10px'}}>
    1084. <Col span={5}>
    1085. <Form.Item colon={false}>
    1086. <Button style={{ float: "left" }}
    1087. type="primary"
    1088. disabled={isDisableSave}
    1089. loading={loadings[1]}
    1090. htmlType="submit">保存Button>
    1091. Form.Item>
    1092. Col>
    1093. <Col span={10} style={{ display: 'flex', alignItems: 'center' }}>
    1094. <Form.Item style={{ marginLeft: '60px' }}>
    1095. <Button onClick={resetScreeningForm}>取消Button>
    1096. Form.Item>
    1097. Col>
    1098. Row>
    1099. Form.Item>
    1100. Col>
    1101. Row>
    1102. Form>
    1103. div>
    1104. )}
    1105. div>
    1106. );
    1107. };

    2:后端

    需要正常配置好Minio

    yml依赖:

    Minio工具类

    1. package com.ly.cloud.util.minio;
    2. import com.ly.cloud.config.MinioConfig;
    3. import io.minio.*;
    4. import io.minio.http.Method;
    5. import org.springframework.beans.factory.annotation.Value;
    6. import org.springframework.stereotype.Component;
    7. import javax.annotation.Resource;
    8. import java.io.File;
    9. import java.io.FileInputStream;
    10. import java.io.InputStream;
    11. @Component
    12. public class MinioUtil {
    13. @Resource
    14. private MinioConfig minioConfig;
    15. /**
    16. * minio参数
    17. */
    18. @Value("${minio.url}")
    19. private String ENDPOINT;
    20. @Value("${minio.access-key}")
    21. private String ACCESS_KEY;
    22. @Value("${minio.secret-key}")
    23. private String SECRET_KEY ;
    24. /**
    25. * 桶占位符
    26. */
    27. private static final String BUCKET_PARAM = "${bucket}";
    28. /**
    29. * bucket权限-只读
    30. */
    31. 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 + "/*\"]}]}";
    32. /**
    33. * bucket权限-只读
    34. */
    35. 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 + "/*\"]}]}";
    36. /**
    37. * bucket权限-读写
    38. */
    39. 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 + "/*\"]}]}";
    40. /**
    41. * 文件url前半段
    42. *
    43. * @param bucket 桶
    44. * @return 前半段
    45. */
    46. public String getObjectPrefixUrl(String bucket) {
    47. return String.format("%s/%s/", ENDPOINT, bucket);
    48. }
    49. /**
    50. * 创建桶
    51. *
    52. * @param bucket 桶
    53. */
    54. public void makeBucket(String bucket) throws Exception {
    55. MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
    56. // 判断桶是否存在
    57. boolean isExist = client.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
    58. if (!isExist) {
    59. // 新建桶
    60. client.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
    61. }
    62. }
    63. /**
    64. * 更新桶权限策略
    65. *
    66. * @param bucket 桶
    67. * @param policy 权限
    68. */
    69. public void setBucketPolicy(String bucket, String policy) throws Exception {
    70. MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
    71. setBucketPolicy(bucket, policy, client);
    72. }
    73. /**
    74. * 更新桶权限策略
    75. *
    76. * @param bucket 桶
    77. * @param policy 权限
    78. */
    79. public void setBucketPolicy(String bucket, String policy, MinioClient client) throws Exception {
    80. switch (policy) {
    81. case "read-only":
    82. client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).config(READ_ONLY.replace(BUCKET_PARAM, bucket)).build());
    83. break;
    84. case "write-only":
    85. client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).config(WRITE_ONLY.replace(BUCKET_PARAM, bucket)).build());
    86. break;
    87. case "read-write":
    88. client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).config(READ_WRITE.replace(BUCKET_PARAM, bucket)).build());
    89. break;
    90. case "none":
    91. default:
    92. break;
    93. }
    94. }
    95. /**
    96. * 上传本地文件
    97. *
    98. * @param bucket 桶
    99. * @param objectKey 文件key
    100. * @param filePath 文件路径
    101. * @return 文件url
    102. */
    103. public String uploadFile(String bucket, String objectKey, String filePath) throws Exception {
    104. MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
    105. client.uploadObject(UploadObjectArgs.builder().bucket(bucket).object(objectKey).filename(filePath).contentType("image/png").build());
    106. return getObjectPrefixUrl(bucket) + objectKey;
    107. }
    108. /**
    109. * 流式上传文件
    110. *
    111. * @param bucket 桶
    112. * @param objectKey 文件key
    113. * @param inputStream 文件输入流
    114. * @return 文件url
    115. */
    116. public String uploadInputStream(String bucket, String objectKey, InputStream inputStream) throws Exception {
    117. MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
    118. client.putObject(PutObjectArgs.builder().bucket(bucket).object(objectKey).stream(inputStream, inputStream.available(), -1).contentType("image/png").build());
    119. return getObjectPrefixUrl(bucket) + objectKey;
    120. }
    121. /**
    122. * 下载文件
    123. * @param bucket 桶
    124. * @param objectKey 文件key
    125. * @return 文件流
    126. */
    127. public InputStream download(String bucket, String objectKey) throws Exception {
    128. MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
    129. return client.getObject(GetObjectArgs.builder().bucket(bucket).object(objectKey).build());
    130. }
    131. /**
    132. * 文件复制
    133. *
    134. * @param sourceBucket 源桶
    135. * @param sourceObjectKey 源文件key
    136. * @param bucket 桶
    137. * @param objectKey 文件key
    138. * @return 新文件url
    139. */
    140. public String copyFile(String sourceBucket, String sourceObjectKey, String bucket, String objectKey) throws Exception {
    141. MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
    142. CopySource source = CopySource.builder().bucket(sourceBucket).object(sourceObjectKey).build();
    143. client.copyObject(CopyObjectArgs.builder().bucket(bucket).object(objectKey).source(source).build());
    144. return getObjectPrefixUrl(bucket) + objectKey;
    145. }
    146. /**
    147. * 删除文件
    148. *
    149. * @param bucket 桶
    150. * @param objectKey 文件key
    151. */
    152. public void deleteFile(String bucket, String objectKey) throws Exception {
    153. MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
    154. client.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectKey).build());
    155. }
    156. /**
    157. * 获取文件签名url
    158. * @param bucket 桶
    159. * @param objectKey 文件key
    160. * @param expires 签名有效时间 单位秒
    161. * @return 文件签名地址
    162. */
    163. public String getSignedUrl(String bucket, String objectKey, int expires) throws Exception {
    164. MinioClient client = MinioClient.builder().endpoint(ENDPOINT).credentials(ACCESS_KEY, SECRET_KEY).build();
    165. return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucket).object(objectKey).expiry(expires).build());
    166. }
    167. }

    上传:

    1. @Override
    2. public Map uploadPhoto(MultipartFile file) throws Exception {
    3. FileInputStream stream = FileUtil.convertMultipartFileToInputStream(file);
    4. String stringTime = DateTimeUtils.stringTime();
    5. String fileName = generateRandomSixDigitNumber() + stringTime;
    6. minioUtil.uploadInputStream("mpbucket", "sjs/" + "/"+ fileName + ".png", stream);
    7. return map;
    8. }

    下载:

    返回base64 编码

    1. public static final String BASE_64 = "data:image/png;base64,";
    2. //获取文件的base 64 编码
    3. InputStream download = minioUtil.download("mpbucket", "sjs/" + SLASH_SUFFIX + fileName + ".png");
    4. byte[] bytes = IOUtils.toByteArray(download);
    5. String encoded = Base64.getEncoder().encodeToString(bytes);
    6. Map map = new HashMap<>();
    7. map.put("objectKey", fileName + ".png");
    8. map.put("base64", BASE_64 + encoded);

  • 相关阅读:
    阿里云IoT流转到postgresql数据库方案
    C语言 pivot_root的Invalid argument错误解决方案
    2023年内网穿透常用的几个工具
    MathType2024最新word公式编辑器
    https证书的常用版本有哪些
    为什么你开发的网页不应该大于 14KB?
    自建音乐服务器Navidrome之一
    GitLab CI/CD 自动化部署-springBoot-demo示例
    MR混合现实情景实训教学系统在临床医学课堂教学中的应用
    【JavaEE精炼宝库】网络原理基础——UDP详解
  • 原文地址:https://blog.csdn.net/XikYu/article/details/137830220