diff --git a/src/api/internal/httpClient.ts b/src/api/internal/httpClient.ts index dd615ad..0b29793 100644 --- a/src/api/internal/httpClient.ts +++ b/src/api/internal/httpClient.ts @@ -52,10 +52,13 @@ export class HttpClient { GoLogin(); } else if (status === 404) { // 跳转到404页面 + GoLogin(); } else if (status === 403) { // 跳转到无权限页面 + GoLogin(); } else if (status === 500) { // 跳转到500异常页面 + GoLogin(); } return Promise.reject(error.response); } diff --git a/src/compenents/upload-video-button/index.tsx b/src/compenents/upload-video-button/index.tsx index 9174393..ecc6bfc 100644 --- a/src/compenents/upload-video-button/index.tsx +++ b/src/compenents/upload-video-button/index.tsx @@ -11,7 +11,7 @@ import { Upload, } from "antd"; import Dragger from "antd/es/upload/Dragger"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { generateUUID, parseVideo } from "../../utils"; import { minioMergeVideo, minioUploadId } from "../../api/upload"; import { UploadChunk } from "../../js/minio-upload-chunk"; @@ -21,41 +21,44 @@ interface PropsInterface { onUpdate: () => void; } -interface FileItem { - id: string; - filename: string; - uploadId: string; - name: string; - duration: number; - size: number; - progress: number; - file: File; - resourceType: string; - loading: boolean; - run: UploadChunk; - isSuc: boolean; - isErr: boolean; - errMsg: string; - remoteName: string; - poster: string; -} - export const UploadVideoButton = (props: PropsInterface) => { const [showModal, setShowModal] = useState(false); const localFileList = useRef([]); + const intervalId = useRef(); const [fileList, setFileList] = useState([]); - const upRef = useRef(0); const getMinioUploadId = async () => { let resp: any = await minioUploadId("mp4"); return resp.data; }; + useEffect(() => { + if (showModal) { + intervalId.current = setInterval(() => { + if (localFileList.current.length === 0) { + return; + } + for (let i = 0; i < localFileList.current.length; i++) { + if (localFileList.current[i].upload.status === 0) { + localFileList.current[i].upload.handler.start(); + break; + } + if (localFileList.current[i].upload.status === 3) { + break; + } + } + }, 1000); + console.log("定时器已创建", intervalId.current); + } else { + window.clearInterval(intervalId.current); + console.log("定时器已销毁"); + } + }, [showModal]); + const uploadProps = { multiple: true, beforeUpload: async (file: File) => { if (file.type === "video/mp4") { - upRef.current++; // 视频封面解析 || 视频时长解析 let videoInfo = await parseVideo(file); // 添加到本地待上传 @@ -63,58 +66,47 @@ export const UploadVideoButton = (props: PropsInterface) => { let run = new UploadChunk(file, data["upload_id"], data["filename"]); let item: FileItem = { id: generateUUID(), - duration: videoInfo.duration, - filename: data["filename"], - uploadId: data["upload_id"], - name: file.name, - size: file.size, - progress: 0, file: file, - resourceType: data["resource_type"], - loading: true, - run: run, - isSuc: false, - isErr: false, - errMsg: "", - remoteName: data["filename"], - poster: videoInfo.poster, + upload: { + handler: run, + progress: 0, + status: 0, + remark: "", + }, + video: { + duration: videoInfo.duration, + poster: videoInfo.poster, + }, }; - item.run.on("success", () => { + item.upload.handler.on("success", () => { minioMergeVideo( - item.filename, - item.uploadId, + data["filename"], + data["upload_id"], props.categoryIds.join(","), - item.name, + item.file.name, "mp4", - item.size, - item.duration, - item.poster + item.file.size, + item.video?.duration || 0, + item.video?.poster || "" ).then(() => { - item.isSuc = true; + item.upload.progress = 100; + item.upload.status = item.upload.handler.getUploadStatus(); setFileList([...localFileList.current]); - message.success(`${item.file.name} 上传成功`); - upRef.current--; }); }); - item.run.on("retry", () => { - item.isErr = false; - item.errMsg = ""; + 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.run.on("progress", (progress: number) => { - item.progress = progress; + item.upload.handler.on("error", (msg: string) => { + item.upload.status = item.upload.handler.getUploadStatus(); + item.upload.remark = msg; setFileList([...localFileList.current]); }); - item.run.on("error", (msg: string) => { - item.isErr = true; - item.errMsg = msg; - setFileList([...localFileList.current]); - upRef.current--; - }); - setTimeout(() => { - item.run.start(); - }, 500); + // 先插入到ref localFileList.current.push(item); + // 再更新list setFileList([...localFileList.current]); } else { message.error(`${file.name} 并不是 mp4 视频文件`); @@ -124,14 +116,17 @@ export const UploadVideoButton = (props: PropsInterface) => { }; const closeWin = () => { - if (upRef.current > 0) { - message.error(`等待上传成功后才能关闭`); - return; + if (fileList.length > 0) { + fileList.forEach((item) => { + if (item.upload.status !== 5 && item.upload.status !== 7) { + item.upload.handler.cancel(); + } + }); } - setShowModal(false); - setFileList([]); - localFileList.current = []; props.onUpdate(); + localFileList.current = []; + setFileList([]); + setShowModal(false); }; return ( @@ -145,7 +140,7 @@ export const UploadVideoButton = (props: PropsInterface) => { 上传视频 - {showModal && ( + {showModal ? ( { onOk={() => { closeWin(); }} + okText="完成" > @@ -180,13 +176,16 @@ export const UploadVideoButton = (props: PropsInterface) => { title: "视频", dataIndex: "name", key: "name", + render: (_, record) => {record.file.name}, }, { title: "大小", dataIndex: "size", key: "size", render: (_, record) => ( - {(record.size / 1024 / 1024).toFixed(2)} M + + {(record.file.size / 1024 / 1024).toFixed(2)}M + ), }, { @@ -195,12 +194,13 @@ export const UploadVideoButton = (props: PropsInterface) => { key: "progress", render: (_, record: FileItem) => ( <> - {record.progress === 0 && "等待上传"} - {record.progress > 0 && ( + {record.upload.status === 0 ? ( + "等待上传" + ) : ( )} @@ -211,32 +211,13 @@ export const UploadVideoButton = (props: PropsInterface) => { key: "action", render: (_, record) => ( <> - {record.progress > 0 && - record.isSuc === false && - record.isErr === false && ( - - )} + {record.upload.status === 5 ? ( + {record.upload.remark} + ) : null} - {record.isErr && ( - <> - {record.errMsg} - - - )} + {record.upload.status === 7 ? ( + 上传成功 + ) : null} ), }, @@ -246,7 +227,7 @@ export const UploadVideoButton = (props: PropsInterface) => { - )} + ) : null} ); }; diff --git a/src/js/minio-upload-chunk.ts b/src/js/minio-upload-chunk.ts index 2698fec..170f2da 100644 --- a/src/js/minio-upload-chunk.ts +++ b/src/js/minio-upload-chunk.ts @@ -11,11 +11,14 @@ export class UploadChunk { chunkIndex: number; uploadId: string; filename: string; + // 上传状态[0:等待上传,3:上传中,5:上传失败,7:上传成功] + uploadStatus: number; + uploadRemark: string; - onError: ((err: string) => void | undefined) | undefined; - onSuccess: (() => void | undefined) | undefined; - onRetry: (() => void | undefined) | undefined; - onProgress: ((progress: number) => void) | undefined; + onError?: (err: string) => void | undefined; + onSuccess?: () => void | undefined; + onRetry?: () => void | undefined; + onProgress?: (progress: number) => void; constructor(file: File, uploadId: string, filename: string) { this.client = axios.create({ @@ -31,6 +34,9 @@ export class UploadChunk { this.uploadId = uploadId; this.filename = filename; + + this.uploadStatus = 0; + this.uploadRemark = ""; } on(event: string, handle: any) { @@ -49,23 +55,25 @@ export class UploadChunk { if (this.isStop) { return; } + + // 检测是否上传完成 if (this.chunkIndex > this.chunkNumber) { - //上传完成 - this.onSuccess && this.onSuccess(); + this.uploadCompleted(); return; } - this.onProgress && - this.onProgress( - parseInt((this.chunkIndex / this.chunkNumber) * 100 + "") - ); + + // 进度更新 + this.uploadProgressUpdated(); let start = (this.chunkIndex - 1) * this.chunkSize; const chunkData = this.file.slice(start, start + this.chunkSize); const boolname = this.file.name + "-" + this.chunkIndex; const tmpFile = new File([chunkData], boolname); + // 首先获取上传minio的签名 minioPreSignUrl(this.uploadId, this.filename, this.chunkIndex) .then((res: any) => { + // 拿到签名之后将分块内容上传到minio return this.client.put(res.data.url, tmpFile, { headers: { "Content-Type": "multipart/form-data", @@ -76,12 +84,15 @@ export class UploadChunk { this.chunkIndex += 1; this.start(); }) - .catch((e) => { - console.error("文件分片上传失败", e); - this.onError && this.onError("失败.2"); + .catch((e: any) => { + this.uploadedFail(e); }); } + isOver() { + return this.uploadStatus === 5 || this.uploadStatus === 7; + } + cancel() { this.isStop = true; this.onError && this.onError("已取消"); @@ -92,4 +103,40 @@ export class UploadChunk { this.start(); this.onRetry && this.onRetry(); } + + uploadProgressUpdated() { + if (this.uploadStatus === 0) { + this.uploadStatus = 3; + } + this.onProgress && + this.onProgress( + parseInt((this.chunkIndex / this.chunkNumber) * 100 + "") + ); + } + + uploadCompleted() { + this.uploadStatus = 7; + this.onSuccess && this.onSuccess(); + } + + uploadedFail(e: any) { + console.log("上传失败,错误信息:", e); + this.uploadStatus = 5; + this.onError && this.onError("失败.2"); + } + + getUploadStatus(): number { + return this.uploadStatus; + } + + getUploadProgress(): number { + if (this.chunkNumber === 0) { + return 0; + } + return (this.chunkIndex / this.chunkNumber) * 100; + } + + getUploadRemark(): string { + return this.uploadRemark; + } } diff --git a/src/pages/system/administrator/compenents/create.tsx b/src/pages/system/administrator/compenents/create.tsx index 641cfa6..544c7b0 100644 --- a/src/pages/system/administrator/compenents/create.tsx +++ b/src/pages/system/administrator/compenents/create.tsx @@ -148,6 +148,7 @@ export const SystemAdministratorCreate: React.FC = ({ rules={[{ required: true, message: "请输入登录密码!" }]} > = ({ import("../pages/dashboard")); +//修改密码页面 +const ChangePasswordPage = lazy(() => import("../pages/change-password")); +//资源管理相关 +const ResourceCategoryPage = lazy( + () => import("../pages/resource/resource-category") +); +const ResourceImagesPage = lazy(() => import("../pages/resource/images")); +const ResourceVideosPage = lazy(() => import("../pages/resource/videos")); +//课程相关 +const CoursePage = lazy(() => import("../pages/course/index")); +const CourseUserPage = lazy(() => import("../pages/course/user")); +//学员相关 +const MemberPage = lazy(() => import("../pages/member")); +const MemberImportPage = lazy(() => import("../pages/member/import")); +const MemberLearnPage = lazy(() => import("../pages/member/learn")); +const MemberDepartmentProgressPage = lazy( + () => import("../pages/member/departmentUser") +); +//系统相关 +const SystemConfigPage = lazy(() => import("../pages/system/config")); +const SystemAdministratorPage = lazy( + () => import("../pages/system/administrator") +); +const SystemAdminrolesPage = lazy(() => import("../pages/system/adminroles")); +//部门页面 +const DepartmentPage = lazy(() => import("../pages/department")); +//测试 +const TestPage = lazy(() => import("../pages/test")); +//错误页面 +const ErrorPage = lazy(() => import("../pages/error")); + import PrivateRoute from "../compenents/private-route"; // const LoginPage = lazy(() => import("../pages/login")); @@ -51,9 +67,6 @@ if (getToken()) { }); }); } else { - if (window.location.pathname !== "/login") { - window.location.href = "/login"; - } RootPage = ; }