From 953487735060cfdb64725f8485d3c6c0b6ef5a54 Mon Sep 17 00:00:00 2001 From: unknown <18119604035@163.com> Date: Mon, 8 Jan 2024 13:15:09 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=A7=86=E9=A2=91=E6=9C=80?= =?UTF-8?q?=E5=B0=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/commen/upload.png | Bin 0 -> 919 bytes src/compenents/upload-video-button/index.tsx | 218 ++------------ .../index.module.less | 26 ++ .../upload-video-float-button/index.tsx | 278 ++++++++++++++++++ src/index.less | 5 + src/pages/init/index.tsx | 2 + src/store/user/loginUserSlice.ts | 18 +- 7 files changed, 347 insertions(+), 200 deletions(-) create mode 100644 src/assets/images/commen/upload.png create mode 100644 src/compenents/upload-video-float-button/index.module.less create mode 100644 src/compenents/upload-video-float-button/index.tsx diff --git a/src/assets/images/commen/upload.png b/src/assets/images/commen/upload.png new file mode 100644 index 0000000000000000000000000000000000000000..a9684bae4947176ac5425f9560116c45e8ee121c GIT binary patch literal 919 zcmV;I18Dq-P)Px&QAtEWR9HvVnN5rgQ4oN?YHK!o@RJZBBn~7T#09Y@A(1%PogP0Uh?5h-orDt! z&V)TV31P6)X7?bGtT;(gb-F@IWU7Md9P=7r)Ot&`}OR|nA0Tvs_J`HUG?f! z3kiM-9=^W;*n+YSScS3#m{U>r9pMA;3gIa*LJ&L_D7%^y7Xj_dE=5X5Q4Sz1bCE@P zhj0~5U6i5R`?#{P0CmTX1^m*}h#myG<0@~mu87JNX3HrV9Q-n!u4w@^o!*Pe1z_QH z${e4M;&D`h;Ko#PO#yZD<}T*WyMS`Y(XiWcVSsOA$K=6--;D$t0%|tfL$Q=Y+3sd7 z;c8)+?o?KCxgYf!CIMDFm;YyQRU!lVZ7fso$;p6e|8m>I+RV|yfZPoZ*FKH_RubZR z!a&W8n8)7Q#2E*)rQ~<{WeeF_Lgb94R6Pl#5)zNn<*lxkpb5af%q2u4?sYIw!eh4t zLBaC*em`YyC5BYFEJOJtMwbPwoDgpk7U2k7byF=u7SYrSbK-=6{r*|txP!;4OB=$? z5ac;21SiBE{C)mg=d%@}VFeLTyW!gZNyBZ0UQMu#1;|;Q5gm`S-)~Zt|_2d@*3qlB7NqKjBN`> z4dBm&^{>v(lzK}>L$k8;6YoclO1&pAlm}i+e)+0-ZE2d7Q(YLal6&dEDqZ(3mA?xk~XXr$QccB zv|$Y)TWa~II*Z*ixW47LfURGeZc8Ds<;uN7itM5BrO7G|=P*gHou3fhWKF3&1Qxlw z4%6Q^zIIlXX(wOY?dHRwQRj|6EMzASkH$mY$>X=$m%C#Jm=VZM9sx%{J9UU&U(X_+ tA { - const [showModal, setShowModal] = useState(false); - const localFileList = useRef([]); - const [fileList, setFileList] = useState([]); + const dispatch = useDispatch(); + const uploadStatus = useSelector( + (state: any) => state.loginUser.value.uploadStatus + ); - const getMinioUploadId = async () => { - let resp: any = await minioUploadId("mp4"); - return resp.data; - }; - - const uploadProps = { - multiple: true, - beforeUpload: async (file: File) => { - if (file.type === "video/mp4") { - // 视频封面解析 || 视频时长解析 - let videoInfo = await parseVideo(file); - // 添加到本地待上传 - let data = await getMinioUploadId(); - let run = new UploadChunk(file, data["upload_id"], data["filename"]); - let item: FileItem = { - id: generateUUID(), - file: file, - upload: { - handler: run, - progress: 0, - status: 0, - remark: "", - }, - video: { - duration: videoInfo.duration, - poster: videoInfo.poster, - }, - }; - item.upload.handler.on("success", () => { - minioMergeVideo( - data["filename"], - data["upload_id"], - props.categoryIds.join(","), - item.file.name, - "mp4", - item.file.size, - item.video?.duration || 0, - item.video?.poster || "" - ).then(() => { - item.upload.progress = 100; - item.upload.status = item.upload.handler.getUploadStatus(); - setFileList([...localFileList.current]); - }); - }); - item.upload.handler.on("progress", (p: number) => { - item.upload.status = item.upload.handler.getUploadStatus(); - item.upload.progress = p >= 100 ? 99 : p; - setFileList([...localFileList.current]); - }); - item.upload.handler.on("error", (msg: string) => { - item.upload.status = item.upload.handler.getUploadStatus(); - item.upload.remark = msg; - setFileList([...localFileList.current]); - }); - item.upload.handler.start(); - // 先插入到ref - localFileList.current.push(item); - // 再更新list - setFileList([...localFileList.current]); - } else { - message.error(`${file.name} 并不是 mp4 视频文件`); - } - return Upload.LIST_IGNORE; - }, - }; - - const closeWin = () => { - if (fileList.length > 0) { - fileList.forEach((item) => { - if (item.upload.status !== 5 && item.upload.status !== 7) { - item.upload.handler.cancel(); - } - }); + useEffect(() => { + if (!uploadStatus) { + props.onUpdate(); } - props.onUpdate(); - localFileList.current = []; - setFileList([]); - setShowModal(false); - }; + }, [uploadStatus]); return ( <> - - {showModal ? ( - { - closeWin(); - }} - maskClosable={false} - closable={false} - onOk={() => { - closeWin(); - }} - okText="完成" - > - - - -

- -

-

请将视频拖拽到此处上传

-

- 支持一次上传多个 / 支持 mp4 格式视频 -

-
- - - {record.file.name}, - }, - { - title: "大小", - dataIndex: "size", - key: "size", - render: (_, record) => ( - - {(record.file.size / 1024 / 1024).toFixed(2)}M - - ), - }, - { - title: "进度", - dataIndex: "progress", - key: "progress", - render: (_, record: FileItem) => ( - <> - {record.upload.status === 0 ? ( - "等待上传" - ) : ( - - )} - - ), - }, - { - title: "操作", - key: "action", - render: (_, record) => ( - <> - {record.upload.status === 5 ? ( - <> - - - ) : null} - - {record.upload.status === 7 ? ( - 上传成功 - ) : null} - - ), - }, - ]} - dataSource={fileList} - /> - - - - ) : null} ); }; diff --git a/src/compenents/upload-video-float-button/index.module.less b/src/compenents/upload-video-float-button/index.module.less new file mode 100644 index 0000000..444588e --- /dev/null +++ b/src/compenents/upload-video-float-button/index.module.less @@ -0,0 +1,26 @@ +.float-button { + width: auto; + height: 32px; + background: #ffffff; + box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-sizing: border-box; + padding: 6px 8px; + display: flex; + align-items: center; + position: fixed; + right: 24px; + bottom: 20px; + z-index: 50; + cursor: pointer; + img { + width: 20px; + height: 20px; + } + span { + margin-left: 6px; + font-size: 12px; + font-weight: 400; + color: #ff4d4f; + } +} diff --git a/src/compenents/upload-video-float-button/index.tsx b/src/compenents/upload-video-float-button/index.tsx new file mode 100644 index 0000000..b5a8aaa --- /dev/null +++ b/src/compenents/upload-video-float-button/index.tsx @@ -0,0 +1,278 @@ +import { InboxOutlined } from "@ant-design/icons"; +import { + Button, + Col, + message, + Modal, + Progress, + Row, + Table, + Tag, + Upload, +} from "antd"; +import Dragger from "antd/es/upload/Dragger"; +import { useEffect, useRef, useState } from "react"; +import { generateUUID, parseVideo } from "../../utils"; +import styles from "./index.module.less"; +import { minioMergeVideo, minioUploadId } from "../../api/upload"; +import { UploadChunk } from "../../js/minio-upload-chunk"; +import { useDispatch } from "react-redux"; +import { useSelector } from "react-redux"; +import { uploadAction } from "../../store/user/loginUserSlice"; +import upIcon from "../../assets/images/commen/upload.png"; + +export const UploadVideoFloatButton = () => { + const dispatch = useDispatch(); + const [showModal, setShowModal] = useState(false); + const localFileList = useRef([]); + const intervalId = useRef(); + const intervalId2 = useRef(); + const [successNum, setSuccessNum] = useState(0); + const [fileList, setFileList] = useState([]); + const uploadStatus = useSelector( + (state: any) => state.loginUser.value.uploadStatus + ); + const categoryIds = useSelector( + (state: any) => state.loginUser.value.uploadCateIds + ); + + const getMinioUploadId = async () => { + let resp: any = await minioUploadId("mp4"); + return resp.data; + }; + + useEffect(() => { + if (uploadStatus) { + setShowModal(true); + intervalId.current = setInterval(() => { + let num = localFileList.current.filter( + (it) => it.upload.status === 7 + ).length; + setSuccessNum(num); + }, 5000); + let timeDiv = document.createElement("div"); + document.body.appendChild(timeDiv); + intervalId2.current = setInterval(() => { + timeDiv && timeDiv.click(); + }, 10000); + } else { + window.clearInterval(intervalId.current); + window.clearInterval(intervalId2.current); + console.log("定时器已销毁"); + } + }, [uploadStatus]); + + const uploadProps = { + multiple: true, + beforeUpload: async (file: File) => { + if (file.type === "video/mp4") { + // 视频封面解析 || 视频时长解析 + let videoInfo = await parseVideo(file); + // 添加到本地待上传 + let data = await getMinioUploadId(); + let run = new UploadChunk(file, data["upload_id"], data["filename"]); + let item: FileItem = { + id: generateUUID(), + file: file, + upload: { + handler: run, + progress: 0, + status: 0, + remark: "", + }, + video: { + duration: videoInfo.duration, + poster: videoInfo.poster, + }, + }; + item.upload.handler.on("success", () => { + minioMergeVideo( + data["filename"], + data["upload_id"], + categoryIds.join(","), + item.file.name, + "mp4", + item.file.size, + item.video?.duration || 0, + item.video?.poster || "" + ).then(() => { + item.upload.progress = 100; + item.upload.status = item.upload.handler.getUploadStatus(); + setFileList([...localFileList.current]); + }); + }); + item.upload.handler.on("progress", (p: number) => { + item.upload.status = item.upload.handler.getUploadStatus(); + item.upload.progress = p >= 100 ? 99 : p; + setFileList([...localFileList.current]); + }); + item.upload.handler.on("error", (msg: string) => { + item.upload.status = item.upload.handler.getUploadStatus(); + item.upload.remark = msg; + setFileList([...localFileList.current]); + }); + item.upload.handler.start(); + // 先插入到ref + localFileList.current.push(item); + // 再更新list + setFileList([...localFileList.current]); + } else { + message.error(`${file.name} 并不是 mp4 视频文件`); + } + return Upload.LIST_IGNORE; + }, + }; + + const closeWin = () => { + if (fileList.length > 0) { + fileList.forEach((item) => { + if (item.upload.status !== 5 && item.upload.status !== 7) { + item.upload.handler.cancel(); + } + }); + } + localFileList.current = []; + setFileList([]); + setSuccessNum(0); + setShowModal(false); + dispatch( + uploadAction({ + uploadStatus: false, + uploadCateIds: [], + }) + ); + }; + + return ( + <> + {uploadStatus ? ( + <> +
setShowModal(true)} + > + + + 视频上传成功 ({successNum}/{fileList.length}) + +
+ { + closeWin(); + }} + > + +
+ +

+ +

+

请将视频拖拽到此处上传

+

+ 支持一次上传多个 / 支持 mp4 格式视频 +

+
+ + +
{record.file.name}, + }, + { + title: "大小", + dataIndex: "size", + key: "size", + render: (_, record) => ( + + {(record.file.size / 1024 / 1024).toFixed(2)}M + + ), + }, + { + title: "进度", + dataIndex: "progress", + key: "progress", + render: (_, record: FileItem) => ( + <> + {record.upload.status === 0 ? ( + "等待上传" + ) : ( + + )} + + ), + }, + { + title: "操作", + key: "action", + render: (_, record) => ( + <> + {record.upload.status === 5 ? ( + <> + + + ) : null} + + {record.upload.status === 7 ? ( + 上传成功 + ) : null} + + ), + }, + ]} + dataSource={fileList} + /> + + +
+ + +
+ + + + + ) : null} + + ); +}; diff --git a/src/index.less b/src/index.less index 8f2d6c6..a6b8e15 100644 --- a/src/index.less +++ b/src/index.less @@ -187,6 +187,11 @@ code { align-items: center; } +.r-r-flex { + display: flex; + flex-direction: row-reverse; +} + .flex-1 { flex: 1; } diff --git a/src/pages/init/index.tsx b/src/pages/init/index.tsx index 8bdbf1d..91bd384 100644 --- a/src/pages/init/index.tsx +++ b/src/pages/init/index.tsx @@ -5,6 +5,7 @@ import { SystemConfigStoreInterface, saveConfigAction, } from "../../store/system/systemConfigSlice"; +import { UploadVideoFloatButton } from "../../compenents/upload-video-float-button"; interface Props { loginData?: any; @@ -36,6 +37,7 @@ const InitPage = (props: Props) => { return ( <> + ); }; diff --git a/src/store/user/loginUserSlice.ts b/src/store/user/loginUserSlice.ts index d7e55ba..ad7744f 100644 --- a/src/store/user/loginUserSlice.ts +++ b/src/store/user/loginUserSlice.ts @@ -1,4 +1,5 @@ import { createSlice } from "@reduxjs/toolkit"; +import { message } from "antd"; type UserInterface = { id: number; @@ -10,12 +11,16 @@ type UserStoreInterface = { user: UserInterface | null; isLogin: boolean; permissions: string[]; + uploadStatus: boolean; + uploadCateIds: number[]; }; let defaultValue: UserStoreInterface = { user: null, isLogin: false, permissions: [], + uploadStatus: false, + uploadCateIds: [], }; const loginUserSlice = createSlice({ @@ -33,10 +38,21 @@ const loginUserSlice = createSlice({ stage.value.user = null; stage.value.isLogin = false; }, + uploadAction(stage, e) { + if ( + stage.value.uploadStatus === true && + e.payload.uploadStatus === true + ) { + message.error("请点击右下角悬浮窗"); + } + stage.value.uploadStatus = e.payload.uploadStatus; + stage.value.uploadCateIds = e.payload.uploadCateIds; + }, }, }); export default loginUserSlice.reducer; -export const { loginAction, logoutAction } = loginUserSlice.actions; +export const { loginAction, logoutAction, uploadAction } = + loginUserSlice.actions; export type { UserStoreInterface };