From 0461573e7faf0e1f68998acc05d95fe50a2803f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BA=E7=8B=A8?= <18119604035@163.com> Date: Thu, 6 Jul 2023 16:57:56 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=BA=BA=E5=91=98?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E7=A6=81=E6=AD=A2=E6=B5=8F=E8=A7=88=E5=99=A8?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=A1=AB=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/system/administrator/compenents/create.tsx | 1 + src/pages/system/administrator/compenents/update.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/system/administrator/compenents/create.tsx b/src/pages/system/administrator/compenents/create.tsx index 641cfa6..f66ff72 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: "请输入登录密码!" }]} > = ({ Date: Mon, 17 Jul 2023 17:06:30 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=BC=93=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/internal/httpClient.ts | 3 ++ src/routes/index.tsx | 57 +++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 22 deletions(-) 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/routes/index.tsx b/src/routes/index.tsx index fd4c4c0..f44e6f8 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -2,29 +2,45 @@ import { lazy } from "react"; import { RouteObject } from "react-router-dom"; import { login, system } from "../api"; -import InitPage from "../pages/init"; import { getToken } from "../utils"; import KeepAlive from "../compenents/keep-alive"; - +// 页面加载 +import InitPage from "../pages/init"; import LoginPage from "../pages/login"; import HomePage from "../pages/home"; -import DashboardPage from "../pages/dashboard"; -import ChangePasswordPage from "../pages/change-password"; -import ResourceCategoryPage from "../pages/resource/resource-category"; -import ResourceImagesPage from "../pages/resource/images"; -import ResourceVideosPage from "../pages/resource/videos"; -import CoursePage from "../pages/course/index"; -import CourseUserPage from "../pages/course/user"; -import MemberPage from "../pages/member"; -import MemberImportPage from "../pages/member/import"; -import MemberLearnPage from "../pages/member/learn"; -import MemberDepartmentProgressPage from "../pages/member/departmentUser"; -import SystemConfigPage from "../pages/system/config"; -import SystemAdministratorPage from "../pages/system/administrator"; -import SystemAdminrolesPage from "../pages/system/adminroles"; -import DepartmentPage from "../pages/department"; -import TestPage from "../pages/test"; -import ErrorPage from "../pages/error"; +//首页 +const DashboardPage = lazy(() => 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 = ; } From defc71697aa31f6c45705fe8b872122ba558605c Mon Sep 17 00:00:00 2001 From: unknown <18119604035@163.com> Date: Thu, 20 Jul 2023 09:17:48 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E7=AA=97=E5=8F=A3=E5=8F=96=E6=B6=88=E6=89=80?= =?UTF-8?q?=E6=9C=89=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compenents/upload-video-button/index.tsx | 29 +++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/compenents/upload-video-button/index.tsx b/src/compenents/upload-video-button/index.tsx index 9174393..02934a2 100644 --- a/src/compenents/upload-video-button/index.tsx +++ b/src/compenents/upload-video-button/index.tsx @@ -124,14 +124,29 @@ export const UploadVideoButton = (props: PropsInterface) => { }; const closeWin = () => { - if (upRef.current > 0) { - message.error(`等待上传成功后才能关闭`); - return; + // if (upRef.current > 0) { + // message.error(`等待上传成功后才能关闭`); + // return; + // } + + if (fileList.length > 0) { + let i = 0; + fileList.map((item: any) => { + item.run.cancel(); + i++; + }); + if (i === fileList.length) { + setShowModal(false); + setFileList([]); + localFileList.current = []; + props.onUpdate(); + } + } else { + setShowModal(false); + setFileList([]); + localFileList.current = []; + props.onUpdate(); } - setShowModal(false); - setFileList([]); - localFileList.current = []; - props.onUpdate(); }; return ( From de090c63d020e32327cf6f7a3bc736c720f546c7 Mon Sep 17 00:00:00 2001 From: none Date: Thu, 20 Jul 2023 15:35:00 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compenents/upload-video-button/index.tsx | 133 ++++++------------- src/js/minio-upload-chunk.ts | 73 ++++++++-- src/playedu.d.ts | 20 +++ 3 files changed, 122 insertions(+), 104 deletions(-) create mode 100644 src/playedu.d.ts diff --git a/src/compenents/upload-video-button/index.tsx b/src/compenents/upload-video-button/index.tsx index 02934a2..5371e0a 100644 --- a/src/compenents/upload-video-button/index.tsx +++ b/src/compenents/upload-video-button/index.tsx @@ -21,30 +21,10 @@ 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 [fileList, setFileList] = useState([]); - const upRef = useRef(0); const getMinioUploadId = async () => { let resp: any = await minioUploadId("mp4"); @@ -55,7 +35,6 @@ export const UploadVideoButton = (props: PropsInterface) => { multiple: true, beforeUpload: async (file: File) => { if (file.type === "video/mp4") { - upRef.current++; // 视频封面解析 || 视频时长解析 let videoInfo = await parseVideo(file); // 添加到本地待上传 @@ -63,58 +42,50 @@ 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.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; + console.log("状态,进度", item.upload.status, item.upload.progress); 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(); + item.upload.handler.start(); }, 500); + // 先插入到ref localFileList.current.push(item); + // 再更新list setFileList([...localFileList.current]); } else { message.error(`${file.name} 并不是 mp4 视频文件`); @@ -124,11 +95,6 @@ export const UploadVideoButton = (props: PropsInterface) => { }; const closeWin = () => { - // if (upRef.current > 0) { - // message.error(`等待上传成功后才能关闭`); - // return; - // } - if (fileList.length > 0) { let i = 0; fileList.map((item: any) => { @@ -195,13 +161,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 + ), }, { @@ -210,12 +179,13 @@ export const UploadVideoButton = (props: PropsInterface) => { key: "progress", render: (_, record: FileItem) => ( <> - {record.progress === 0 && "等待上传"} - {record.progress > 0 && ( + {record.upload.status === 0 ? ( + "等待上传" + ) : ( )} @@ -226,32 +196,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} ), }, 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/playedu.d.ts b/src/playedu.d.ts new file mode 100644 index 0000000..feb1b6a --- /dev/null +++ b/src/playedu.d.ts @@ -0,0 +1,20 @@ +declare global { + interface FileItem { + id: string; //上传文件的唯一id + file: File; //上传的文件资源 + // 上传实际执行者 + upload: { + handler: UploadChunk; + progress: number; + status: number; + remark: string; + }; + // 视频文件信息 + video?: { + duration: number; //时长 + poster: string; //视频帧 + }; + } +} + +export {}; From 04facee48a60d455a36392d3781434387e78542f Mon Sep 17 00:00:00 2001 From: none Date: Thu, 20 Jul 2023 15:49:47 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E4=B8=80=E6=AC=A1=E5=8F=AA=E8=83=BD?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=B8=80=E4=B8=AA=E8=A7=86=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compenents/upload-video-button/index.tsx | 60 ++++++++++++-------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/src/compenents/upload-video-button/index.tsx b/src/compenents/upload-video-button/index.tsx index 5371e0a..1c0c9f7 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"; @@ -24,6 +24,7 @@ interface PropsInterface { export const UploadVideoButton = (props: PropsInterface) => { const [showModal, setShowModal] = useState(false); const localFileList = useRef([]); + const intervalId = useRef(); const [fileList, setFileList] = useState([]); const getMinioUploadId = async () => { @@ -31,6 +32,29 @@ export const UploadVideoButton = (props: PropsInterface) => { 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) => { @@ -65,14 +89,14 @@ export const UploadVideoButton = (props: PropsInterface) => { 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; - console.log("状态,进度", item.upload.status, item.upload.progress); + item.upload.progress = p >= 100 ? 99 : p; setFileList([...localFileList.current]); }); item.upload.handler.on("error", (msg: string) => { @@ -80,9 +104,6 @@ export const UploadVideoButton = (props: PropsInterface) => { item.upload.remark = msg; setFileList([...localFileList.current]); }); - setTimeout(() => { - item.upload.handler.start(); - }, 500); // 先插入到ref localFileList.current.push(item); // 再更新list @@ -96,23 +117,16 @@ export const UploadVideoButton = (props: PropsInterface) => { const closeWin = () => { if (fileList.length > 0) { - let i = 0; - fileList.map((item: any) => { - item.run.cancel(); - i++; + fileList.forEach((item) => { + if (item.upload.status !== 5 && item.upload.status !== 7) { + item.upload.handler.cancel(); + } }); - if (i === fileList.length) { - setShowModal(false); - setFileList([]); - localFileList.current = []; - props.onUpdate(); - } - } else { - setShowModal(false); - setFileList([]); - localFileList.current = []; - props.onUpdate(); } + props.onUpdate(); + localFileList.current = []; + setFileList([]); + setShowModal(false); }; return ( @@ -126,7 +140,7 @@ export const UploadVideoButton = (props: PropsInterface) => { 上传视频 - {showModal && ( + {showModal ? ( { - )} + ) : null} ); }; From 377ecff30b1f3092bc602073f38453a7fff6a7a5 Mon Sep 17 00:00:00 2001 From: none Date: Thu, 20 Jul 2023 16:27:24 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E6=96=87=E6=A1=88=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compenents/upload-video-button/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compenents/upload-video-button/index.tsx b/src/compenents/upload-video-button/index.tsx index 1c0c9f7..ecc6bfc 100644 --- a/src/compenents/upload-video-button/index.tsx +++ b/src/compenents/upload-video-button/index.tsx @@ -153,6 +153,7 @@ export const UploadVideoButton = (props: PropsInterface) => { onOk={() => { closeWin(); }} + okText="完成" > From 08b555afeadbcbcb1dbe09f51c29bacf7763041b Mon Sep 17 00:00:00 2001 From: unknown <18119604035@163.com> Date: Thu, 20 Jul 2023 16:59:30 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=BA=BA=E5=91=98?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E8=87=AA=E5=8A=A8=E5=A1=AB=E5=85=A5=E5=8F=96?= =?UTF-8?q?=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/system/administrator/compenents/create.tsx | 2 +- src/pages/system/administrator/compenents/update.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/system/administrator/compenents/create.tsx b/src/pages/system/administrator/compenents/create.tsx index f66ff72..544c7b0 100644 --- a/src/pages/system/administrator/compenents/create.tsx +++ b/src/pages/system/administrator/compenents/create.tsx @@ -148,7 +148,7 @@ export const SystemAdministratorCreate: React.FC = ({ rules={[{ required: true, message: "请输入登录密码!" }]} > = ({