mirror of
https://github.com/PlayEdu/backend
synced 2025-07-21 08:00:14 +08:00
aerge branch 'dev'
This commit is contained in:
commit
518dea2b57
21
src/api/admin-log.ts
Normal file
21
src/api/admin-log.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import client from "./internal/httpClient";
|
||||||
|
|
||||||
|
export function adminLogList(
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
admin_name: string,
|
||||||
|
title: string,
|
||||||
|
opt: string,
|
||||||
|
start_time: string,
|
||||||
|
end_time: string
|
||||||
|
) {
|
||||||
|
return client.get("/backend/v1/admin/log/index", {
|
||||||
|
page: page,
|
||||||
|
size: size,
|
||||||
|
admin_name: admin_name,
|
||||||
|
title: title,
|
||||||
|
opt: opt,
|
||||||
|
start_time: start_time,
|
||||||
|
end_time: end_time,
|
||||||
|
});
|
||||||
|
}
|
20
src/api/course-attachment.ts
Normal file
20
src/api/course-attachment.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import client from "./internal/httpClient";
|
||||||
|
|
||||||
|
export function storeCourseAttachmentMulti(
|
||||||
|
courseId: number,
|
||||||
|
attachments: number[]
|
||||||
|
) {
|
||||||
|
return client.post(`/backend/v1/course/${courseId}/attachment/create-batch`, {
|
||||||
|
attachments: attachments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroyAttachment(courseId: number, id: number) {
|
||||||
|
return client.destroy(`/backend/v1/course/${courseId}/attachment/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transCourseAttachment(courseId: number, ids: number[]) {
|
||||||
|
return client.put(`/backend/v1/course/${courseId}/attachment/update/sort`, {
|
||||||
|
ids: ids,
|
||||||
|
});
|
||||||
|
}
|
@ -35,7 +35,8 @@ export function storeCourse(
|
|||||||
depIds: number[],
|
depIds: number[],
|
||||||
categoryIds: number[],
|
categoryIds: number[],
|
||||||
chapters: number[],
|
chapters: number[],
|
||||||
hours: number[]
|
hours: number[],
|
||||||
|
attachments: any[]
|
||||||
) {
|
) {
|
||||||
return client.post("/backend/v1/course/create", {
|
return client.post("/backend/v1/course/create", {
|
||||||
title: title,
|
title: title,
|
||||||
@ -47,6 +48,7 @@ export function storeCourse(
|
|||||||
category_ids: categoryIds,
|
category_ids: categoryIds,
|
||||||
chapters: chapters,
|
chapters: chapters,
|
||||||
hours: hours,
|
hours: hours,
|
||||||
|
attachments: attachments,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ export * as courseCategory from "./course-category";
|
|||||||
export * as courseChapter from "./course-chapter";
|
export * as courseChapter from "./course-chapter";
|
||||||
export * as course from "./course";
|
export * as course from "./course";
|
||||||
export * as courseHour from "./course-hour";
|
export * as courseHour from "./course-hour";
|
||||||
|
export * as courseAttachment from "./course-attachment";
|
||||||
export * as department from "./department";
|
export * as department from "./department";
|
||||||
export * as resourceCategory from "./resource-category";
|
export * as resourceCategory from "./resource-category";
|
||||||
export * as resource from "./resource";
|
export * as resource from "./resource";
|
||||||
@ -13,3 +14,4 @@ export * as upload from "./upload";
|
|||||||
export * as user from "./user";
|
export * as user from "./user";
|
||||||
export * as appConfig from "./app-config";
|
export * as appConfig from "./app-config";
|
||||||
export * as dashboard from "./dashboard";
|
export * as dashboard from "./dashboard";
|
||||||
|
export * as adminLog from "./admin-log";
|
||||||
|
@ -7,6 +7,10 @@ const GoLogin = () => {
|
|||||||
window.location.href = "/login";
|
window.location.href = "/login";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GoError = (code: number) => {
|
||||||
|
// window.location.href = "/error?code=" + code;
|
||||||
|
};
|
||||||
|
|
||||||
export class HttpClient {
|
export class HttpClient {
|
||||||
axios: Axios;
|
axios: Axios;
|
||||||
|
|
||||||
@ -39,7 +43,24 @@ export class HttpClient {
|
|||||||
|
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
|
} else if (code === 404) {
|
||||||
|
message.error(msg);
|
||||||
|
// 跳转到404页面
|
||||||
|
GoError(404);
|
||||||
|
} else if (code === 403) {
|
||||||
|
message.error(msg);
|
||||||
|
// 跳转到无权限页面
|
||||||
|
GoError(403);
|
||||||
|
} else if (code === 429) {
|
||||||
|
message.error(msg);
|
||||||
|
// 跳转到429页面
|
||||||
|
GoError(429);
|
||||||
|
} else if (code === 500) {
|
||||||
|
message.error(msg);
|
||||||
|
// 跳转到500异常页面
|
||||||
|
GoError(500);
|
||||||
} else {
|
} else {
|
||||||
|
GoError(code);
|
||||||
message.error(msg);
|
message.error(msg);
|
||||||
}
|
}
|
||||||
return Promise.reject(response);
|
return Promise.reject(response);
|
||||||
@ -52,13 +73,18 @@ export class HttpClient {
|
|||||||
GoLogin();
|
GoLogin();
|
||||||
} else if (status === 404) {
|
} else if (status === 404) {
|
||||||
// 跳转到404页面
|
// 跳转到404页面
|
||||||
GoLogin();
|
GoError(404);
|
||||||
} else if (status === 403) {
|
} else if (status === 403) {
|
||||||
// 跳转到无权限页面
|
// 跳转到无权限页面
|
||||||
GoLogin();
|
GoError(403);
|
||||||
|
} else if (status === 429) {
|
||||||
|
// 跳转到429页面
|
||||||
|
GoError(429);
|
||||||
} else if (status === 500) {
|
} else if (status === 500) {
|
||||||
// 跳转到500异常页面
|
// 跳转到500异常页面
|
||||||
GoLogin();
|
GoError(500);
|
||||||
|
} else {
|
||||||
|
GoError(status);
|
||||||
}
|
}
|
||||||
return Promise.reject(error.response);
|
return Promise.reject(error.response);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export function minioMergeVideo(
|
|||||||
duration: number,
|
duration: number,
|
||||||
poster: string
|
poster: string
|
||||||
) {
|
) {
|
||||||
return client.post("/backend/v1/upload/minio/merge-video", {
|
return client.post("/backend/v1/upload/minio/merge-file", {
|
||||||
filename,
|
filename,
|
||||||
upload_id: uploadId,
|
upload_id: uploadId,
|
||||||
original_filename: originalFilename,
|
original_filename: originalFilename,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 3943555 */
|
font-family: "iconfont"; /* Project id 3943555 */
|
||||||
src: url('iconfont.woff2?t=1679383201256') format('woff2'),
|
src: url('iconfont.woff2?t=1690600882833') format('woff2'),
|
||||||
url('iconfont.woff?t=1679383201256') format('woff'),
|
url('iconfont.woff?t=1690600882833') format('woff'),
|
||||||
url('iconfont.ttf?t=1679383201256') format('truetype');
|
url('iconfont.ttf?t=1690600882833') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -13,6 +13,54 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-playedu:before {
|
||||||
|
content: "\e756";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-icon-xuexi:before {
|
||||||
|
content: "\e753";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-icon-wode:before {
|
||||||
|
content: "\e754";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-icon-shouye:before {
|
||||||
|
content: "\e755";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-icon-xiala:before {
|
||||||
|
content: "\e752";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-close:before {
|
||||||
|
content: "\e751";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-fullscreen:before {
|
||||||
|
content: "\e74b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-speed:before {
|
||||||
|
content: "\e74c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-mute:before {
|
||||||
|
content: "\e74d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-play:before {
|
||||||
|
content: "\e74e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pause:before {
|
||||||
|
content: "\e74f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-unmute:before {
|
||||||
|
content: "\e750";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-icon-tips:before {
|
.icon-icon-tips:before {
|
||||||
content: "\e74a";
|
content: "\e74a";
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -10,3 +10,6 @@ export * from ".//tree-adminroles";
|
|||||||
export * from "./duration-text";
|
export * from "./duration-text";
|
||||||
export * from "./upload-video-sub";
|
export * from "./upload-video-sub";
|
||||||
export * from "./select-resource";
|
export * from "./select-resource";
|
||||||
|
export * from "./upload-courseware-button";
|
||||||
|
export * from "./upload-courseware-sub";
|
||||||
|
export * from "./select-attachment";
|
||||||
|
@ -46,6 +46,7 @@ const items = [
|
|||||||
[
|
[
|
||||||
getItem("视频", "/videos", null, null, null, null),
|
getItem("视频", "/videos", null, null, null, null),
|
||||||
getItem("图片", "/images", null, null, null, null),
|
getItem("图片", "/images", null, null, null, null),
|
||||||
|
getItem("课件", "/courseware", null, null, null, null),
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
@ -90,6 +91,7 @@ const items = [
|
|||||||
null,
|
null,
|
||||||
"admin-user-index"
|
"admin-user-index"
|
||||||
),
|
),
|
||||||
|
getItem("管理日志", "/system/adminlog", null, null, null, "admin-log"),
|
||||||
// getItem("角色配置", "/system/adminroles", null, null, null, null),
|
// getItem("角色配置", "/system/adminroles", null, null, null, null),
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
@ -103,6 +105,7 @@ export const LeftMenu: React.FC = () => {
|
|||||||
const children2Parent: any = {
|
const children2Parent: any = {
|
||||||
"^/video": ["resource"],
|
"^/video": ["resource"],
|
||||||
"^/image": ["resource"],
|
"^/image": ["resource"],
|
||||||
|
"^/courseware": ["resource"],
|
||||||
"^/member": ["user"],
|
"^/member": ["user"],
|
||||||
"^/department": ["user"],
|
"^/department": ["user"],
|
||||||
"^/course": ["courses"],
|
"^/course": ["courses"],
|
||||||
|
74
src/compenents/select-attachment/index.tsx
Normal file
74
src/compenents/select-attachment/index.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Row, Modal, Tabs } from "antd";
|
||||||
|
import styles from "./index.module.less";
|
||||||
|
import { UploadCoursewareSub } from "../../compenents";
|
||||||
|
import type { TabsProps } from "antd";
|
||||||
|
|
||||||
|
interface PropsInterface {
|
||||||
|
defaultKeys: any[];
|
||||||
|
open: boolean;
|
||||||
|
onSelected: (arr: any[], videos: any[]) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectAttachment = (props: PropsInterface) => {
|
||||||
|
const [refresh, setRefresh] = useState(true);
|
||||||
|
const [tabKey, setTabKey] = useState(1);
|
||||||
|
const [selectKeys, setSelectKeys] = useState<any>([]);
|
||||||
|
const [selectVideos, setSelectVideos] = useState<any>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}, [props.open]);
|
||||||
|
|
||||||
|
const items: TabsProps["items"] = [
|
||||||
|
{
|
||||||
|
key: "1",
|
||||||
|
label: `课件`,
|
||||||
|
children: (
|
||||||
|
<div className="float-left">
|
||||||
|
<UploadCoursewareSub
|
||||||
|
label="课件"
|
||||||
|
defaultCheckedList={props.defaultKeys}
|
||||||
|
open={refresh}
|
||||||
|
onSelected={(arr: any[], videos: any[]) => {
|
||||||
|
setSelectKeys(arr);
|
||||||
|
setSelectVideos(videos);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const onChange = (key: string) => {
|
||||||
|
setTabKey(Number(key));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
title="资源素材库"
|
||||||
|
centered
|
||||||
|
closable={false}
|
||||||
|
onCancel={() => {
|
||||||
|
setSelectKeys([]);
|
||||||
|
setSelectVideos([]);
|
||||||
|
props.onCancel();
|
||||||
|
}}
|
||||||
|
open={props.open}
|
||||||
|
width={800}
|
||||||
|
maskClosable={false}
|
||||||
|
onOk={() => {
|
||||||
|
props.onSelected(selectKeys, selectVideos);
|
||||||
|
setSelectKeys([]);
|
||||||
|
setSelectVideos([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
|
||||||
|
</Row>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,16 +1,9 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Button, Row, Modal, message, Tabs } from "antd";
|
import { Row, Modal, Tabs } from "antd";
|
||||||
import styles from "./index.module.less";
|
import styles from "./index.module.less";
|
||||||
import { UploadVideoSub } from "../../compenents";
|
import { UploadVideoSub } from "../../compenents";
|
||||||
import type { TabsProps } from "antd";
|
import type { TabsProps } from "antd";
|
||||||
|
|
||||||
interface VideoItem {
|
|
||||||
id: number;
|
|
||||||
category_id: number;
|
|
||||||
name: string;
|
|
||||||
duration: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PropsInterface {
|
interface PropsInterface {
|
||||||
defaultKeys: any[];
|
defaultKeys: any[];
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -24,6 +17,10 @@ export const SelectResource = (props: PropsInterface) => {
|
|||||||
const [selectKeys, setSelectKeys] = useState<any>([]);
|
const [selectKeys, setSelectKeys] = useState<any>([]);
|
||||||
const [selectVideos, setSelectVideos] = useState<any>([]);
|
const [selectVideos, setSelectVideos] = useState<any>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}, [props.open]);
|
||||||
|
|
||||||
const items: TabsProps["items"] = [
|
const items: TabsProps["items"] = [
|
||||||
{
|
{
|
||||||
key: "1",
|
key: "1",
|
||||||
|
262
src/compenents/upload-courseware-button/index.tsx
Normal file
262
src/compenents/upload-courseware-button/index.tsx
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
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 { minioMergeVideo, minioUploadId } from "../../api/upload";
|
||||||
|
import { UploadChunk } from "../../js/minio-upload-chunk";
|
||||||
|
|
||||||
|
interface PropsInterface {
|
||||||
|
categoryIds: number[];
|
||||||
|
onUpdate: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UploadCoursewareButton = (props: PropsInterface) => {
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const localFileList = useRef<FileItem[]>([]);
|
||||||
|
const intervalId = useRef<number>();
|
||||||
|
const [fileList, setFileList] = useState<FileItem[]>([]);
|
||||||
|
|
||||||
|
const getMinioUploadId = async (extension: string) => {
|
||||||
|
let resp: any = await minioUploadId(extension);
|
||||||
|
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) => {
|
||||||
|
let extension: any = file.name.split(".");
|
||||||
|
extension = extension[extension.length - 1];
|
||||||
|
if (
|
||||||
|
file.type ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
|
||||||
|
file.type === "application/msword" ||
|
||||||
|
file.type === "application/vnd.ms-word.document.macroEnabled.12" ||
|
||||||
|
file.type === "application/vnd.ms-word.template.macroEnabled.12" ||
|
||||||
|
file.type === "text/plain" ||
|
||||||
|
file.type === "application/pdf" ||
|
||||||
|
file.type === "application/x-zip-compressed" ||
|
||||||
|
file.type === "application/octet-stream" ||
|
||||||
|
file.type === "application/zip" ||
|
||||||
|
file.type === "application/x-rar" ||
|
||||||
|
file.type ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
|
||||||
|
file.type === "application/vnd.ms-excel" ||
|
||||||
|
file.type ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.template" ||
|
||||||
|
file.type === "application/vnd.ms-excel.sheet.macroEnabled.12" ||
|
||||||
|
file.type === "application/vnd.ms-excel.template.macroEnabled.12" ||
|
||||||
|
file.type === "application/vnd.ms-excel.addin.macroEnabled.12" ||
|
||||||
|
file.type === "application/vnd.ms-excel.sheet.binary.macroEnabled.12" ||
|
||||||
|
file.type === "application/vnd.ms-powerpoint" ||
|
||||||
|
file.type ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation" ||
|
||||||
|
file.type ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.template" ||
|
||||||
|
file.type ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.slideshow" ||
|
||||||
|
file.type === "application/vnd.ms-powerpoint.addin.macroEnabled.12" ||
|
||||||
|
file.type ===
|
||||||
|
"application/vnd.ms-powerpoint.presentation.macroEnabled.12" ||
|
||||||
|
file.type === "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"
|
||||||
|
) {
|
||||||
|
// 添加到本地待上传
|
||||||
|
let data = await getMinioUploadId(extension);
|
||||||
|
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: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
item.upload.handler.on("success", () => {
|
||||||
|
minioMergeVideo(
|
||||||
|
data["filename"],
|
||||||
|
data["upload_id"],
|
||||||
|
props.categoryIds.join(","),
|
||||||
|
item.file.name,
|
||||||
|
extension,
|
||||||
|
item.file.size,
|
||||||
|
0,
|
||||||
|
""
|
||||||
|
).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]);
|
||||||
|
});
|
||||||
|
// 先插入到ref
|
||||||
|
localFileList.current.push(item);
|
||||||
|
// 再更新list
|
||||||
|
setFileList([...localFileList.current]);
|
||||||
|
} else {
|
||||||
|
message.error(`${file.name} 并不是可上传文件`);
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
props.onUpdate();
|
||||||
|
localFileList.current = [];
|
||||||
|
setFileList([]);
|
||||||
|
setShowModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setShowModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
上传课件
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{showModal ? (
|
||||||
|
<Modal
|
||||||
|
width={800}
|
||||||
|
title="上传课件"
|
||||||
|
open={true}
|
||||||
|
onCancel={() => {
|
||||||
|
closeWin();
|
||||||
|
}}
|
||||||
|
maskClosable={false}
|
||||||
|
closable={false}
|
||||||
|
onOk={() => {
|
||||||
|
closeWin();
|
||||||
|
}}
|
||||||
|
okText="完成"
|
||||||
|
>
|
||||||
|
<Row gutter={[0, 10]}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Dragger {...uploadProps}>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<InboxOutlined />
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">请将文件拖拽到此处上传</p>
|
||||||
|
<p className="ant-upload-hint">
|
||||||
|
支持一次上传多个 /
|
||||||
|
支持word、excel、ppt、pdf、zip、rar、txt格式文件
|
||||||
|
</p>
|
||||||
|
</Dragger>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Table
|
||||||
|
pagination={false}
|
||||||
|
rowKey="id"
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "课件",
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
render: (_, record) => <span>{record.file.name}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "大小",
|
||||||
|
dataIndex: "size",
|
||||||
|
key: "size",
|
||||||
|
render: (_, record) => (
|
||||||
|
<span>
|
||||||
|
{(record.file.size / 1024 / 1024).toFixed(2)}M
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "进度",
|
||||||
|
dataIndex: "progress",
|
||||||
|
key: "progress",
|
||||||
|
render: (_, record: FileItem) => (
|
||||||
|
<>
|
||||||
|
{record.upload.status === 0 ? (
|
||||||
|
"等待上传"
|
||||||
|
) : (
|
||||||
|
<Progress
|
||||||
|
size="small"
|
||||||
|
steps={20}
|
||||||
|
percent={record.upload.progress}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
render: (_, record) => (
|
||||||
|
<>
|
||||||
|
{record.upload.status === 5 ? (
|
||||||
|
<Tag color="red">{record.upload.remark}</Tag>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{record.upload.status === 7 ? (
|
||||||
|
<Tag color="success">上传成功</Tag>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
dataSource={fileList}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Modal>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
199
src/compenents/upload-courseware-sub/index.tsx
Normal file
199
src/compenents/upload-courseware-sub/index.tsx
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Row, Col, Empty, Table } from "antd";
|
||||||
|
import type { ColumnsType } from "antd/es/table";
|
||||||
|
import { resource } from "../../api";
|
||||||
|
import styles from "./index.module.less";
|
||||||
|
import { TreeCategory, UploadCoursewareButton } from "../../compenents";
|
||||||
|
|
||||||
|
interface VideoItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
created_at: string;
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
path: string;
|
||||||
|
size: number;
|
||||||
|
extension: string;
|
||||||
|
admin_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataType {
|
||||||
|
id: React.Key;
|
||||||
|
name: string;
|
||||||
|
created_at: string;
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
path: string;
|
||||||
|
size: number;
|
||||||
|
extension: string;
|
||||||
|
admin_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PropsInterface {
|
||||||
|
defaultCheckedList: any[];
|
||||||
|
label: string;
|
||||||
|
open: boolean;
|
||||||
|
onSelected: (arr: any[], videos: []) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UploadCoursewareSub = (props: PropsInterface) => {
|
||||||
|
const [category_ids, setCategoryIds] = useState<any>([]);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [videoList, setVideoList] = useState<VideoItem[]>([]);
|
||||||
|
const [existingTypes, setExistingTypes] = useState<any>([]);
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [size, setSize] = useState(10);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]);
|
||||||
|
|
||||||
|
// 加载列表
|
||||||
|
useEffect(() => {
|
||||||
|
getvideoList();
|
||||||
|
}, [props.open, category_ids, refresh, page, size]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.defaultCheckedList.length > 0) {
|
||||||
|
setSelectedRowKeys(props.defaultCheckedList);
|
||||||
|
}
|
||||||
|
}, [props.defaultCheckedList]);
|
||||||
|
|
||||||
|
// 获取列表
|
||||||
|
const getvideoList = () => {
|
||||||
|
let categoryIds = category_ids.join(",");
|
||||||
|
resource
|
||||||
|
.resourceList(
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"WORD,EXCEL,PPT,PDF,TXT,RAR,ZIP",
|
||||||
|
categoryIds
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
|
setTotal(res.data.result.total);
|
||||||
|
setExistingTypes(res.data.existing_types);
|
||||||
|
setVideoList(res.data.result.data);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log("错误,", err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置列表
|
||||||
|
const resetVideoList = () => {
|
||||||
|
setPage(1);
|
||||||
|
setVideoList([]);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
current: page, //当前页码
|
||||||
|
pageSize: size,
|
||||||
|
total: total, // 总条数
|
||||||
|
onChange: (page: number, pageSize: number) =>
|
||||||
|
handlePageChange(page, pageSize), //改变页码的函数
|
||||||
|
showSizeChanger: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
setPage(page);
|
||||||
|
setSize(pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ColumnsType<DataType> = [
|
||||||
|
{
|
||||||
|
title: "课件",
|
||||||
|
render: (_, record: any) => (
|
||||||
|
<div className="d-flex">
|
||||||
|
<i
|
||||||
|
className="iconfont icon-icon-file"
|
||||||
|
style={{
|
||||||
|
fontSize: 14,
|
||||||
|
color: "rgba(0,0,0,0.3)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="video-title ml-8">{record.name}</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "类型",
|
||||||
|
render: (_, record: any) => <span>{record.type}</span>,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const rowSelection = {
|
||||||
|
selectedRowKeys: selectedRowKeys,
|
||||||
|
onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
|
||||||
|
let row: any = selectedRows;
|
||||||
|
let arrVideos: any = [];
|
||||||
|
if (row) {
|
||||||
|
for (var i = 0; i < row.length; i++) {
|
||||||
|
if (props.defaultCheckedList.indexOf(row[i].id) === -1) {
|
||||||
|
arrVideos.push({
|
||||||
|
name: row[i].name,
|
||||||
|
type: row[i].type,
|
||||||
|
rid: row[i].id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.onSelected(selectedRowKeys, arrVideos);
|
||||||
|
}
|
||||||
|
setSelectedRowKeys(selectedRowKeys);
|
||||||
|
},
|
||||||
|
getCheckboxProps: (record: any) => ({
|
||||||
|
disabled: props.defaultCheckedList.indexOf(record.id) !== -1, //禁用的条件
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row style={{ width: 752, minHeight: 520 }}>
|
||||||
|
<Col span={7}>
|
||||||
|
<TreeCategory
|
||||||
|
selected={[]}
|
||||||
|
type="no-cate"
|
||||||
|
text={props.label}
|
||||||
|
onUpdate={(keys: any) => setCategoryIds(keys)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={17}>
|
||||||
|
<Row style={{ marginBottom: 24, paddingLeft: 10 }}>
|
||||||
|
<Col span={24}>
|
||||||
|
<UploadCoursewareButton
|
||||||
|
categoryIds={category_ids}
|
||||||
|
onUpdate={() => {
|
||||||
|
resetVideoList();
|
||||||
|
}}
|
||||||
|
></UploadCoursewareButton>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<div className={styles["video-list"]}>
|
||||||
|
{videoList.length === 0 && (
|
||||||
|
<Col span={24} style={{ marginTop: 150 }}>
|
||||||
|
<Empty description="暂无课件" />
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
{videoList.length > 0 && (
|
||||||
|
<div className="list-select-column-box c-flex">
|
||||||
|
<Table
|
||||||
|
rowSelection={{
|
||||||
|
type: "checkbox",
|
||||||
|
...rowSelection,
|
||||||
|
}}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={videoList}
|
||||||
|
loading={loading}
|
||||||
|
pagination={paginationProps}
|
||||||
|
rowKey={(record) => record.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,19 +1,33 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Checkbox, Row, Col, Empty, message, Pagination } from "antd";
|
import { Row, Col, Empty, Table } from "antd";
|
||||||
|
import type { ColumnsType } from "antd/es/table";
|
||||||
import { resource } from "../../api";
|
import { resource } from "../../api";
|
||||||
import styles from "./index.module.less";
|
import styles from "./index.module.less";
|
||||||
import { UploadVideoButton } from "../upload-video-button";
|
import { UploadVideoButton } from "../upload-video-button";
|
||||||
import { DurationText, TreeCategory } from "../../compenents";
|
import { DurationText, TreeCategory } from "../../compenents";
|
||||||
import type { CheckboxChangeEvent } from "antd/es/checkbox";
|
|
||||||
import type { CheckboxValueType } from "antd/es/checkbox/Group";
|
|
||||||
|
|
||||||
const CheckboxGroup = Checkbox.Group;
|
|
||||||
|
|
||||||
interface VideoItem {
|
interface VideoItem {
|
||||||
id: number;
|
id: number;
|
||||||
category_id: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
duration: number;
|
created_at: string;
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
path: string;
|
||||||
|
size: number;
|
||||||
|
extension: string;
|
||||||
|
admin_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataType {
|
||||||
|
id: React.Key;
|
||||||
|
name: string;
|
||||||
|
created_at: string;
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
path: string;
|
||||||
|
size: number;
|
||||||
|
extension: string;
|
||||||
|
admin_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PropsInterface {
|
interface PropsInterface {
|
||||||
@ -32,14 +46,21 @@ export const UploadVideoSub = (props: PropsInterface) => {
|
|||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [size, setSize] = useState(10);
|
const [size, setSize] = useState(10);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
|
const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]);
|
||||||
|
|
||||||
const [plainOptions, setPlainOptions] = useState<any>([]);
|
// 加载列表
|
||||||
const [checkedList, setCheckedList] = useState<CheckboxValueType[]>([]);
|
useEffect(() => {
|
||||||
const [indeterminate, setIndeterminate] = useState(false);
|
getvideoList();
|
||||||
const [checkAll, setCheckAll] = useState(false);
|
}, [props.open, category_ids, refresh, page, size]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.defaultCheckedList.length > 0) {
|
||||||
|
setSelectedRowKeys(props.defaultCheckedList);
|
||||||
|
}
|
||||||
|
}, [props.defaultCheckedList]);
|
||||||
|
|
||||||
// 获取列表
|
// 获取列表
|
||||||
const getvideoList = (defaultKeys: any[]) => {
|
const getvideoList = () => {
|
||||||
let categoryIds = category_ids.join(",");
|
let categoryIds = category_ids.join(",");
|
||||||
resource
|
resource
|
||||||
.resourceList(page, size, "", "", "", "VIDEO", categoryIds)
|
.resourceList(page, size, "", "", "", "VIDEO", categoryIds)
|
||||||
@ -47,46 +68,12 @@ export const UploadVideoSub = (props: PropsInterface) => {
|
|||||||
setTotal(res.data.result.total);
|
setTotal(res.data.result.total);
|
||||||
setVideoExtra(res.data.videos_extra);
|
setVideoExtra(res.data.videos_extra);
|
||||||
setVideoList(res.data.result.data);
|
setVideoList(res.data.result.data);
|
||||||
let data = res.data.result.data;
|
|
||||||
const arr = [];
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
arr.push({
|
|
||||||
label: (
|
|
||||||
<div className="d-flex">
|
|
||||||
<i
|
|
||||||
className="iconfont icon-icon-video"
|
|
||||||
style={{
|
|
||||||
fontSize: 16,
|
|
||||||
color: "rgba(0,0,0,0.3)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="video-title ml-8">{data[i].name}</div>
|
|
||||||
<div className="video-time">
|
|
||||||
<DurationText
|
|
||||||
duration={res.data.videos_extra[data[i].id].duration}
|
|
||||||
></DurationText>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
value: data[i].id,
|
|
||||||
disabled: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (defaultKeys.length > 0 && arr.length > 0) {
|
|
||||||
for (let i = 0; i < defaultKeys.length; i++) {
|
|
||||||
for (let j = 0; j < arr.length; j++) {
|
|
||||||
if (arr[j].value === defaultKeys[i]) {
|
|
||||||
arr[j].disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setPlainOptions(arr);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log("错误,", err);
|
console.log("错误,", err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置列表
|
// 重置列表
|
||||||
const resetVideoList = () => {
|
const resetVideoList = () => {
|
||||||
setPage(1);
|
setPage(1);
|
||||||
@ -94,78 +81,71 @@ export const UploadVideoSub = (props: PropsInterface) => {
|
|||||||
setRefresh(!refresh);
|
setRefresh(!refresh);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载列表
|
const paginationProps = {
|
||||||
useEffect(() => {
|
current: page, //当前页码
|
||||||
const arr = [...props.defaultCheckedList];
|
pageSize: size,
|
||||||
setCheckedList(arr);
|
total: total, // 总条数
|
||||||
if (arr.length === 0) {
|
onChange: (page: number, pageSize: number) =>
|
||||||
setIndeterminate(false);
|
handlePageChange(page, pageSize), //改变页码的函数
|
||||||
setCheckAll(false);
|
showSizeChanger: true,
|
||||||
}
|
|
||||||
getvideoList(arr);
|
|
||||||
}, [props.open, props.defaultCheckedList, category_ids, refresh, page, size]);
|
|
||||||
|
|
||||||
const onChange = (list: CheckboxValueType[]) => {
|
|
||||||
setCheckedList(list);
|
|
||||||
setIndeterminate(!!list.length && list.length < plainOptions.length);
|
|
||||||
setCheckAll(list.length === plainOptions.length);
|
|
||||||
const defalut = [...props.defaultCheckedList];
|
|
||||||
let localKeys: any = [];
|
|
||||||
list.map((item: any) => {
|
|
||||||
if (defalut.indexOf(item) === -1) {
|
|
||||||
localKeys.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let arrVideos: any = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < localKeys.length; i++) {
|
|
||||||
videoList.map((item: any, index: number) => {
|
|
||||||
if (item.id === localKeys[i]) {
|
|
||||||
arrVideos.push({
|
|
||||||
name: item.name,
|
|
||||||
type: item.type,
|
|
||||||
rid: item.id,
|
|
||||||
duration: videosExtra[item.id].duration,
|
|
||||||
disabled: plainOptions[index].disabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
props.onSelected(localKeys, arrVideos);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCheckAllChange = (e: CheckboxChangeEvent) => {
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
const arr = plainOptions.map((item: any) => item.value);
|
setPage(page);
|
||||||
setCheckedList(e.target.checked ? arr : []);
|
setSize(pageSize);
|
||||||
setIndeterminate(false);
|
};
|
||||||
setCheckAll(e.target.checked);
|
|
||||||
const defalut = [...props.defaultCheckedList];
|
const columns: ColumnsType<DataType> = [
|
||||||
let localKeys: any = [];
|
{
|
||||||
arr.map((item: any) => {
|
title: "视频",
|
||||||
if (defalut.indexOf(item) === -1) {
|
render: (_, record: any) => (
|
||||||
localKeys.push(item);
|
<div className="d-flex">
|
||||||
}
|
<i
|
||||||
});
|
className="iconfont icon-icon-video"
|
||||||
let arrVideos: any = [];
|
style={{
|
||||||
for (let i = 0; i < localKeys.length; i++) {
|
fontSize: 14,
|
||||||
videoList.map((item: any, index: number) => {
|
color: "rgba(0,0,0,0.3)",
|
||||||
if (item.id === localKeys[i]) {
|
}}
|
||||||
arrVideos.push({
|
/>
|
||||||
name: item.name,
|
<div className="video-title ml-8">{record.name}</div>
|
||||||
type: item.type,
|
</div>
|
||||||
rid: item.id,
|
),
|
||||||
duration: videosExtra[item.id].duration,
|
},
|
||||||
disabled: plainOptions[index].disabled,
|
{
|
||||||
});
|
title: "时长",
|
||||||
|
render: (_, record: any) => (
|
||||||
|
<div>
|
||||||
|
<DurationText
|
||||||
|
duration={videosExtra[record.id].duration}
|
||||||
|
></DurationText>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const rowSelection = {
|
||||||
|
selectedRowKeys: selectedRowKeys,
|
||||||
|
onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
|
||||||
|
let row: any = selectedRows;
|
||||||
|
let arrVideos: any = [];
|
||||||
|
if (row) {
|
||||||
|
for (var i = 0; i < row.length; i++) {
|
||||||
|
if (props.defaultCheckedList.indexOf(row[i].id) === -1) {
|
||||||
|
arrVideos.push({
|
||||||
|
name: row[i].name,
|
||||||
|
type: row[i].type,
|
||||||
|
rid: row[i].id,
|
||||||
|
duration: videosExtra[row[i].id].duration,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
props.onSelected(selectedRowKeys, arrVideos);
|
||||||
}
|
}
|
||||||
if (e.target.checked) {
|
setSelectedRowKeys(selectedRowKeys);
|
||||||
props.onSelected(localKeys, arrVideos);
|
},
|
||||||
} else {
|
getCheckboxProps: (record: any) => ({
|
||||||
props.onSelected([], []);
|
disabled: props.defaultCheckedList.indexOf(record.id) !== -1, //禁用的条件
|
||||||
}
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -198,43 +178,20 @@ export const UploadVideoSub = (props: PropsInterface) => {
|
|||||||
)}
|
)}
|
||||||
{videoList.length > 0 && (
|
{videoList.length > 0 && (
|
||||||
<div className="list-select-column-box c-flex">
|
<div className="list-select-column-box c-flex">
|
||||||
<Checkbox
|
<Table
|
||||||
indeterminate={indeterminate}
|
rowSelection={{
|
||||||
onChange={onCheckAllChange}
|
type: "checkbox",
|
||||||
checked={checkAll}
|
...rowSelection,
|
||||||
>
|
}}
|
||||||
全选
|
columns={columns}
|
||||||
</Checkbox>
|
dataSource={videoList}
|
||||||
<CheckboxGroup
|
loading={loading}
|
||||||
className="c-flex"
|
pagination={paginationProps}
|
||||||
options={plainOptions}
|
rowKey={(record) => record.id}
|
||||||
value={checkedList}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Row
|
|
||||||
style={{
|
|
||||||
paddingLeft: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{videoList.length > 0 && total > 10 && (
|
|
||||||
<Col
|
|
||||||
span={24}
|
|
||||||
style={{ display: "flex", flexDirection: "row-reverse" }}
|
|
||||||
>
|
|
||||||
<Pagination
|
|
||||||
onChange={(currentPage, currentSize) => {
|
|
||||||
setPage(currentPage);
|
|
||||||
setSize(currentSize);
|
|
||||||
}}
|
|
||||||
defaultCurrent={page}
|
|
||||||
total={total}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
</Row>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
|
@ -525,7 +525,7 @@ textarea.ant-input {
|
|||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
color: #ff4d4f !important;
|
color: #ff4d4f !important;
|
||||||
border-color: #ff4d4f;
|
border-color: #ff4d4f !important;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -593,22 +593,18 @@ textarea.ant-input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.list-select-column-box {
|
.list-select-column-box {
|
||||||
.ant-checkbox-wrapper {
|
width: 100%;
|
||||||
margin-inline-start: 0px;
|
height: auto;
|
||||||
height: 38px;
|
float: left;
|
||||||
|
.ant-table-cell {
|
||||||
|
padding: 0px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-title {
|
.video-title {
|
||||||
width: 390px;
|
width: 360px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.video-time {
|
|
||||||
width: 80px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-list-box {
|
.image-list-box {
|
||||||
@ -660,3 +656,25 @@ textarea.ant-input {
|
|||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drop-item {
|
||||||
|
width: 140px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
&.active {
|
||||||
|
i {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { Provider } from "react-redux";
|
|||||||
import store from "./store";
|
import store from "./store";
|
||||||
import { ConfigProvider } from "antd";
|
import { ConfigProvider } from "antd";
|
||||||
import zhCN from "antd/locale/zh_CN";
|
import zhCN from "antd/locale/zh_CN";
|
||||||
|
import "dayjs/locale/zh-cn";
|
||||||
import AutoScorllTop from "./AutoTop";
|
import AutoScorllTop from "./AutoTop";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
43
src/pages/course/compenents/attachment-update.module.scss
Normal file
43
src/pages/course/compenents/attachment-update.module.scss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.hous-box {
|
||||||
|
width: 500.53px;
|
||||||
|
min-height: 56px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
/* Firefox */
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
/* Safari */
|
||||||
|
padding: 8px 8px 0px 8px;
|
||||||
|
margin-left: 42px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.no-hours {
|
||||||
|
height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.25);
|
||||||
|
line-height: 24px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.top-content {
|
||||||
|
width: 502px;
|
||||||
|
height: auto;
|
||||||
|
background: rgba(255, 77, 79, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin: 0 auto;
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #ff4d4f;
|
||||||
|
line-height: 24px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
218
src/pages/course/compenents/attachment-update.tsx
Normal file
218
src/pages/course/compenents/attachment-update.tsx
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Button, Drawer, Form, Modal, message } from "antd";
|
||||||
|
import styles from "./hour-update.module.less";
|
||||||
|
import { course, courseAttachment } from "../../../api/index";
|
||||||
|
import { SelectAttachment } from "../../../compenents";
|
||||||
|
import { ExclamationCircleFilled } from "@ant-design/icons";
|
||||||
|
import { TreeAttachments } from "./attachments";
|
||||||
|
|
||||||
|
const { confirm } = Modal;
|
||||||
|
|
||||||
|
interface PropInterface {
|
||||||
|
id: number;
|
||||||
|
open: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CourseAttachmentUpdate: React.FC<PropInterface> = ({
|
||||||
|
id,
|
||||||
|
open,
|
||||||
|
onCancel,
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [attachmentVisible, setAttachmentVisible] = useState<boolean>(false);
|
||||||
|
const [attachmentData, setAttachmentData] = useState<any>([]);
|
||||||
|
const [attachments, setAttachments] = useState<any>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getDetail();
|
||||||
|
}, [id, open]);
|
||||||
|
|
||||||
|
const getDetail = () => {
|
||||||
|
course.course(id).then((res: any) => {
|
||||||
|
let treeData = res.data.attachments;
|
||||||
|
if (treeData.length > 0) {
|
||||||
|
const arr: any = resetAttachments(treeData).arr;
|
||||||
|
const keys: any = resetAttachments(treeData).keys;
|
||||||
|
setAttachmentData(arr);
|
||||||
|
setAttachments(keys);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetAttachments = (data: any) => {
|
||||||
|
const arr: any = [];
|
||||||
|
const keys: any = [];
|
||||||
|
if (data) {
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
arr.push({
|
||||||
|
type: data[i].type,
|
||||||
|
name: data[i].title,
|
||||||
|
rid: data[i].rid,
|
||||||
|
id: data[i].id,
|
||||||
|
});
|
||||||
|
keys.push(data[i].rid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { arr, keys };
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinish = (values: any) => {};
|
||||||
|
|
||||||
|
const onFinishFailed = (errorInfo: any) => {
|
||||||
|
console.log("Failed:", errorInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectAttachmentData = (arr: any, videos: any) => {
|
||||||
|
const hours: any = [];
|
||||||
|
for (let i = 0; i < videos.length; i++) {
|
||||||
|
hours.push({
|
||||||
|
sort: attachmentData.length + i,
|
||||||
|
title: videos[i].name,
|
||||||
|
type: videos[i].type,
|
||||||
|
rid: videos[i].rid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hours.length === 0) {
|
||||||
|
message.error("请选择课件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
courseAttachment
|
||||||
|
.storeCourseAttachmentMulti(id, hours)
|
||||||
|
.then((res: any) => {
|
||||||
|
console.log("ok");
|
||||||
|
setAttachmentVisible(false);
|
||||||
|
getDetail();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
message.error(err.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const delAttachments = (hid: number) => {
|
||||||
|
const data = [...attachmentData];
|
||||||
|
confirm({
|
||||||
|
title: "操作确认",
|
||||||
|
icon: <ExclamationCircleFilled />,
|
||||||
|
content: "确认删除此课件?",
|
||||||
|
centered: true,
|
||||||
|
okText: "确认",
|
||||||
|
cancelText: "取消",
|
||||||
|
onOk() {
|
||||||
|
const index = data.findIndex((i: any) => i.rid === hid);
|
||||||
|
let delId = data[index].id;
|
||||||
|
if (index >= 0) {
|
||||||
|
data.splice(index, 1);
|
||||||
|
}
|
||||||
|
if (data.length > 0) {
|
||||||
|
setAttachmentData(data);
|
||||||
|
const keys = data.map((item: any) => item.rid);
|
||||||
|
setAttachments(keys);
|
||||||
|
} else {
|
||||||
|
setAttachmentData([]);
|
||||||
|
setAttachments([]);
|
||||||
|
}
|
||||||
|
if (delId) {
|
||||||
|
courseAttachment.destroyAttachment(id, delId).then((res: any) => {
|
||||||
|
console.log("ok");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log("Cancel");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const transAttachments = (arr: any) => {
|
||||||
|
setAttachments(arr);
|
||||||
|
const data = [...attachmentData];
|
||||||
|
const newArr: any = [];
|
||||||
|
const hourIds: any = [];
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
data.map((item: any) => {
|
||||||
|
if (item.rid === arr[i]) {
|
||||||
|
newArr.push(item);
|
||||||
|
hourIds.push(item.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setAttachmentData(newArr);
|
||||||
|
courseAttachment.transCourseAttachment(id, hourIds).then((res: any) => {
|
||||||
|
console.log("ok");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Drawer
|
||||||
|
title="课件管理"
|
||||||
|
onClose={onCancel}
|
||||||
|
maskClosable={false}
|
||||||
|
open={open}
|
||||||
|
width={634}
|
||||||
|
>
|
||||||
|
<div className={styles["top-content"]}>
|
||||||
|
<p>1.线上课课件调整及时生效,操作不可逆,请谨慎操作。</p>
|
||||||
|
</div>
|
||||||
|
<div className="float-left mt-24">
|
||||||
|
<SelectAttachment
|
||||||
|
defaultKeys={attachments}
|
||||||
|
open={attachmentVisible}
|
||||||
|
onCancel={() => {
|
||||||
|
setAttachmentVisible(false);
|
||||||
|
}}
|
||||||
|
onSelected={(arr: any, videos: any) => {
|
||||||
|
selectAttachmentData(arr, videos);
|
||||||
|
}}
|
||||||
|
></SelectAttachment>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
name="attachment-update-basic"
|
||||||
|
labelCol={{ span: 5 }}
|
||||||
|
wrapperCol={{ span: 19 }}
|
||||||
|
initialValues={{ remember: true }}
|
||||||
|
onFinish={onFinish}
|
||||||
|
onFinishFailed={onFinishFailed}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<div className="c-flex">
|
||||||
|
<Form.Item>
|
||||||
|
<div className="ml-42">
|
||||||
|
<Button
|
||||||
|
onClick={() => setAttachmentVisible(true)}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
添加课件
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
<div className={styles["hous-box"]}>
|
||||||
|
{attachmentData.length === 0 && (
|
||||||
|
<span className={styles["no-hours"]}>
|
||||||
|
请点击上方按钮添加课件
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{attachmentData.length > 0 && (
|
||||||
|
<TreeAttachments
|
||||||
|
data={attachmentData}
|
||||||
|
onRemoveItem={(id: number) => {
|
||||||
|
delAttachments(id);
|
||||||
|
}}
|
||||||
|
onUpdate={(arr: any[]) => {
|
||||||
|
transAttachments(arr);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
157
src/pages/course/compenents/attachments.tsx
Normal file
157
src/pages/course/compenents/attachments.tsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import { message, Tree, Tooltip } from "antd";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import type { DataNode, TreeProps } from "antd/es/tree";
|
||||||
|
|
||||||
|
interface Option {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PropInterface {
|
||||||
|
data: Option[];
|
||||||
|
onRemoveItem: (id: number) => void;
|
||||||
|
onUpdate: (arr: any[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TreeAttachments = (props: PropInterface) => {
|
||||||
|
const [treeData, setTreeData] = useState<any>([]);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
useEffect(() => {
|
||||||
|
const hours = props.data;
|
||||||
|
if (hours.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkTree(hours);
|
||||||
|
}, [props.data]);
|
||||||
|
|
||||||
|
const checkTree = (hours: any) => {
|
||||||
|
const arr = [];
|
||||||
|
for (let i = 0; i < hours.length; i++) {
|
||||||
|
arr.push({
|
||||||
|
title: (
|
||||||
|
<div className="d-flex">
|
||||||
|
<div className="d-flex">
|
||||||
|
<i
|
||||||
|
className="iconfont icon-icon-file"
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: "rgba(0,0,0,0.3)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="tree-video-title mr-24">{hours[i].name}</div>
|
||||||
|
</div>
|
||||||
|
<Tooltip placement="top" title="可拖拽排序">
|
||||||
|
<i
|
||||||
|
className="iconfont icon-icon-drag mr-16"
|
||||||
|
style={{ fontSize: 24 }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<i
|
||||||
|
className="iconfont icon-icon-delete"
|
||||||
|
style={{ fontSize: 24 }}
|
||||||
|
onClick={() => removeItem(hours[i].rid)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
key: hours[i].rid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTreeData(arr);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeItem = (id: number) => {
|
||||||
|
if (id === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
props.onRemoveItem(id);
|
||||||
|
};
|
||||||
|
const onDrop: TreeProps["onDrop"] = (info) => {
|
||||||
|
const dropKey = info.node.key;
|
||||||
|
const dragKey = info.dragNode.key;
|
||||||
|
const dropPos = info.node.pos.split("-");
|
||||||
|
const dropPosition =
|
||||||
|
info.dropPosition - Number(dropPos[dropPos.length - 1]);
|
||||||
|
const loop = (
|
||||||
|
data: DataNode[],
|
||||||
|
key: React.Key,
|
||||||
|
callback: (node: DataNode, i: number, data: DataNode[]) => void
|
||||||
|
) => {
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
if (data[i].key === key) {
|
||||||
|
return callback(data[i], i, data);
|
||||||
|
}
|
||||||
|
if (data[i].children) {
|
||||||
|
loop(data[i].children!, key, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const data = [...treeData];
|
||||||
|
let isTop = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
if (data[i].key === dragKey) {
|
||||||
|
isTop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find dragObject
|
||||||
|
let dragObj: DataNode;
|
||||||
|
loop(data, dragKey, (item, index, arr) => {
|
||||||
|
arr.splice(index, 1);
|
||||||
|
dragObj = item;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!info.dropToGap) {
|
||||||
|
// Drop on the content
|
||||||
|
loop(data, dropKey, (item) => {
|
||||||
|
item.children = item.children || [];
|
||||||
|
// where to insert 示例添加到头部,可以是随意位置
|
||||||
|
item.children.unshift(dragObj);
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
((info.node as any).props.children || []).length > 0 && // Has children
|
||||||
|
(info.node as any).props.expanded && // Is expanded
|
||||||
|
dropPosition === 1 // On the bottom gap
|
||||||
|
) {
|
||||||
|
loop(data, dropKey, (item) => {
|
||||||
|
item.children = item.children || [];
|
||||||
|
// where to insert 示例添加到头部,可以是随意位置
|
||||||
|
item.children.unshift(dragObj);
|
||||||
|
// in previous version, we use item.children.push(dragObj) to insert the
|
||||||
|
// item to the tail of the children
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let ar: DataNode[] = [];
|
||||||
|
let i: number;
|
||||||
|
loop(data, dropKey, (_item, index, arr) => {
|
||||||
|
ar = arr;
|
||||||
|
i = index;
|
||||||
|
});
|
||||||
|
if (dropPosition === -1) {
|
||||||
|
ar.splice(i!, 0, dragObj!);
|
||||||
|
} else {
|
||||||
|
ar.splice(i! + 1, 0, dragObj!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTreeData(data);
|
||||||
|
const keys = data.map((item: any) => item.key);
|
||||||
|
props.onUpdate(keys);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragEnter: TreeProps["onDragEnter"] = (info) => {
|
||||||
|
console.log(info);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tree
|
||||||
|
draggable
|
||||||
|
blockNode
|
||||||
|
onDragEnter={onDragEnter}
|
||||||
|
onDrop={onDrop}
|
||||||
|
selectable={false}
|
||||||
|
treeData={treeData}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -14,9 +14,14 @@ import {
|
|||||||
import styles from "./create.module.less";
|
import styles from "./create.module.less";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { course, department } from "../../../api/index";
|
import { course, department } from "../../../api/index";
|
||||||
import { UploadImageButton, SelectResource } from "../../../compenents";
|
import {
|
||||||
|
UploadImageButton,
|
||||||
|
SelectResource,
|
||||||
|
SelectAttachment,
|
||||||
|
} from "../../../compenents";
|
||||||
import { ExclamationCircleFilled } from "@ant-design/icons";
|
import { ExclamationCircleFilled } from "@ant-design/icons";
|
||||||
import { TreeHours } from "./hours";
|
import { TreeHours } from "./hours";
|
||||||
|
import { TreeAttachments } from "./attachments";
|
||||||
|
|
||||||
const { confirm } = Modal;
|
const { confirm } = Modal;
|
||||||
|
|
||||||
@ -27,12 +32,6 @@ interface PropInterface {
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option {
|
|
||||||
value: string | number;
|
|
||||||
title: string;
|
|
||||||
children?: Option[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CourseCreate: React.FC<PropInterface> = ({
|
export const CourseCreate: React.FC<PropInterface> = ({
|
||||||
cateIds,
|
cateIds,
|
||||||
depIds,
|
depIds,
|
||||||
@ -58,6 +57,10 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
const [videoVisible, setVideoVisible] = useState<boolean>(false);
|
const [videoVisible, setVideoVisible] = useState<boolean>(false);
|
||||||
const [treeData, setTreeData] = useState<any>([]);
|
const [treeData, setTreeData] = useState<any>([]);
|
||||||
const [addvideoCurrent, setAddvideoCurrent] = useState(0);
|
const [addvideoCurrent, setAddvideoCurrent] = useState(0);
|
||||||
|
const [showDrop, setShowDrop] = useState<boolean>(false);
|
||||||
|
const [attachmentVisible, setAttachmentVisible] = useState<boolean>(false);
|
||||||
|
const [attachmentData, setAttachmentData] = useState<any>([]);
|
||||||
|
const [attachments, setAttachments] = useState<any>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@ -80,6 +83,9 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
setChapterHours([]);
|
setChapterHours([]);
|
||||||
setHours([]);
|
setHours([]);
|
||||||
setTreeData([]);
|
setTreeData([]);
|
||||||
|
setAttachmentData([]);
|
||||||
|
setAttachments([]);
|
||||||
|
setShowDrop(false);
|
||||||
}, [form, open]);
|
}, [form, open]);
|
||||||
|
|
||||||
const getParams = () => {
|
const getParams = () => {
|
||||||
@ -224,7 +230,8 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
dep_ids,
|
dep_ids,
|
||||||
values.category_ids,
|
values.category_ids,
|
||||||
chapters,
|
chapters,
|
||||||
treeData
|
treeData,
|
||||||
|
attachmentData
|
||||||
)
|
)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
message.success("保存成功!");
|
message.success("保存成功!");
|
||||||
@ -268,6 +275,20 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
setVideoVisible(false);
|
setVideoVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectAttachmentData = (arr: any, videos: any) => {
|
||||||
|
if (arr.length === 0) {
|
||||||
|
message.error("请选择课件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let keys = [...attachments];
|
||||||
|
let data = [...attachmentData];
|
||||||
|
keys = keys.concat(arr);
|
||||||
|
data = data.concat(videos);
|
||||||
|
setAttachments(keys);
|
||||||
|
setAttachmentData(data);
|
||||||
|
setAttachmentVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
const getChapterType = (e: any) => {
|
const getChapterType = (e: any) => {
|
||||||
const arr = [...chapters];
|
const arr = [...chapters];
|
||||||
if (arr.length > 0) {
|
if (arr.length > 0) {
|
||||||
@ -326,6 +347,36 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
setTreeData(newArr);
|
setTreeData(newArr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const delAttachments = (id: number) => {
|
||||||
|
const data = [...attachmentData];
|
||||||
|
const index = data.findIndex((i: any) => i.rid === id);
|
||||||
|
if (index >= 0) {
|
||||||
|
data.splice(index, 1);
|
||||||
|
}
|
||||||
|
if (data.length > 0) {
|
||||||
|
setAttachmentData(data);
|
||||||
|
const keys = data.map((item: any) => item.rid);
|
||||||
|
setAttachments(keys);
|
||||||
|
} else {
|
||||||
|
setAttachmentData([]);
|
||||||
|
setAttachments([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const transAttachments = (arr: any) => {
|
||||||
|
setAttachments(arr);
|
||||||
|
const data = [...attachmentData];
|
||||||
|
const newArr: any = [];
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
data.map((item: any) => {
|
||||||
|
if (item.rid === arr[i]) {
|
||||||
|
newArr.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setAttachmentData(newArr);
|
||||||
|
};
|
||||||
|
|
||||||
const addNewChapter = () => {
|
const addNewChapter = () => {
|
||||||
const arr = [...chapters];
|
const arr = [...chapters];
|
||||||
const keys = [...chapterHours];
|
const keys = [...chapterHours];
|
||||||
@ -445,6 +496,16 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<SelectAttachment
|
||||||
|
defaultKeys={attachments}
|
||||||
|
open={attachmentVisible}
|
||||||
|
onCancel={() => {
|
||||||
|
setAttachmentVisible(false);
|
||||||
|
}}
|
||||||
|
onSelected={(arr: any, videos: any) => {
|
||||||
|
selectAttachmentData(arr, videos);
|
||||||
|
}}
|
||||||
|
></SelectAttachment>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
name="create-basic"
|
name="create-basic"
|
||||||
@ -621,14 +682,6 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="课程简介" name="short_desc">
|
|
||||||
<Input.TextArea
|
|
||||||
style={{ width: 424, minHeight: 80 }}
|
|
||||||
allowClear
|
|
||||||
placeholder="请输入课程简介(最多200字)"
|
|
||||||
maxLength={200}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="课时列表"
|
label="课时列表"
|
||||||
name="hasChapter"
|
name="hasChapter"
|
||||||
@ -642,7 +695,7 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{chapterType === 0 && (
|
{chapterType === 0 && (
|
||||||
<div className="c-flex">
|
<div className="c-flex mb-24">
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<div className="ml-120">
|
<div className="ml-120">
|
||||||
<Button
|
<Button
|
||||||
@ -674,7 +727,7 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{chapterType === 1 && (
|
{chapterType === 1 && (
|
||||||
<div className="c-flex">
|
<div className="c-flex mb-24">
|
||||||
{chapters.length > 0 &&
|
{chapters.length > 0 &&
|
||||||
chapters.map((item: any, index: number) => {
|
chapters.map((item: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
@ -737,6 +790,57 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Form.Item label="更多选项">
|
||||||
|
<div
|
||||||
|
className={showDrop ? "drop-item active" : "drop-item"}
|
||||||
|
onClick={() => setShowDrop(!showDrop)}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
style={{ fontSize: 14 }}
|
||||||
|
className="iconfont icon-icon-xiala c-red"
|
||||||
|
/>
|
||||||
|
<span>(课程简介、课件)</span>
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
<div
|
||||||
|
className="c-flex"
|
||||||
|
style={{ display: showDrop ? "block" : "none" }}
|
||||||
|
>
|
||||||
|
<Form.Item label="课程简介" name="short_desc">
|
||||||
|
<Input.TextArea
|
||||||
|
style={{ width: 424, minHeight: 80 }}
|
||||||
|
allowClear
|
||||||
|
placeholder="请输入课程简介(最多200字)"
|
||||||
|
maxLength={200}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="课程附件">
|
||||||
|
<Button
|
||||||
|
onClick={() => setAttachmentVisible(true)}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
添加课件
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
<div className={styles["hous-box"]}>
|
||||||
|
{attachmentData.length === 0 && (
|
||||||
|
<span className={styles["no-hours"]}>
|
||||||
|
请点击上方按钮添加课件
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{attachmentData.length > 0 && (
|
||||||
|
<TreeAttachments
|
||||||
|
data={attachmentData}
|
||||||
|
onRemoveItem={(id: number) => {
|
||||||
|
delAttachments(id);
|
||||||
|
}}
|
||||||
|
onUpdate={(arr: any[]) => {
|
||||||
|
transAttachments(arr);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Space, Button, Drawer, Form, Input, Modal, message } from "antd";
|
import { Button, Drawer, Form, Input, Modal, message } from "antd";
|
||||||
import styles from "./hour-update.module.less";
|
import styles from "./hour-update.module.less";
|
||||||
import { course, courseHour, courseChapter } from "../../../api/index";
|
import { course, courseHour, courseChapter } from "../../../api/index";
|
||||||
import { SelectResource } from "../../../compenents";
|
import { SelectResource } from "../../../compenents";
|
||||||
@ -14,12 +14,6 @@ interface PropInterface {
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option {
|
|
||||||
value: string | number;
|
|
||||||
label: string;
|
|
||||||
children?: Option[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CourseHourUpdate: React.FC<PropInterface> = ({
|
export const CourseHourUpdate: React.FC<PropInterface> = ({
|
||||||
id,
|
id,
|
||||||
open,
|
open,
|
||||||
@ -106,16 +100,14 @@ export const CourseHourUpdate: React.FC<PropInterface> = ({
|
|||||||
const selectData = (arr: any, videos: any) => {
|
const selectData = (arr: any, videos: any) => {
|
||||||
const hours: any = [];
|
const hours: any = [];
|
||||||
for (let i = 0; i < videos.length; i++) {
|
for (let i = 0; i < videos.length; i++) {
|
||||||
if (videos[i].disabled === false) {
|
hours.push({
|
||||||
hours.push({
|
chapter_id: 0,
|
||||||
chapter_id: 0,
|
sort: treeData.length + i,
|
||||||
sort: treeData.length + i,
|
title: videos[i].name,
|
||||||
title: videos[i].name,
|
type: videos[i].type,
|
||||||
type: videos[i].type,
|
duration: videos[i].duration,
|
||||||
duration: videos[i].duration,
|
rid: videos[i].rid,
|
||||||
rid: videos[i].rid,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (hours.length === 0) {
|
if (hours.length === 0) {
|
||||||
message.error("请选择视频");
|
message.error("请选择视频");
|
||||||
@ -141,16 +133,14 @@ export const CourseHourUpdate: React.FC<PropInterface> = ({
|
|||||||
}
|
}
|
||||||
const hours: any = [];
|
const hours: any = [];
|
||||||
for (let i = 0; i < videos.length; i++) {
|
for (let i = 0; i < videos.length; i++) {
|
||||||
if (videos[i].disabled === false) {
|
hours.push({
|
||||||
hours.push({
|
chapter_id: data[addvideoCurrent].id,
|
||||||
chapter_id: data[addvideoCurrent].id,
|
sort: data[addvideoCurrent].hours.length + i,
|
||||||
sort: data[addvideoCurrent].hours.length + i,
|
title: videos[i].name,
|
||||||
title: videos[i].name,
|
type: videos[i].type,
|
||||||
type: videos[i].type,
|
duration: videos[i].duration,
|
||||||
duration: videos[i].duration,
|
rid: videos[i].rid,
|
||||||
rid: videos[i].rid,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (hours.length === 0) {
|
if (hours.length === 0) {
|
||||||
message.error("请选择视频");
|
message.error("请选择视频");
|
||||||
|
@ -16,20 +16,12 @@ import { useSelector } from "react-redux";
|
|||||||
import { course, department } from "../../../api/index";
|
import { course, department } from "../../../api/index";
|
||||||
import { UploadImageButton } from "../../../compenents";
|
import { UploadImageButton } from "../../../compenents";
|
||||||
|
|
||||||
const { confirm } = Modal;
|
|
||||||
|
|
||||||
interface PropInterface {
|
interface PropInterface {
|
||||||
id: number;
|
id: number;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option {
|
|
||||||
value: string | number;
|
|
||||||
title: string;
|
|
||||||
children?: Option[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CourseUpdate: React.FC<PropInterface> = ({
|
export const CourseUpdate: React.FC<PropInterface> = ({
|
||||||
id,
|
id,
|
||||||
open,
|
open,
|
||||||
|
@ -27,6 +27,7 @@ import type { TabsProps } from "antd";
|
|||||||
import { CourseCreate } from "./compenents/create";
|
import { CourseCreate } from "./compenents/create";
|
||||||
import { CourseUpdate } from "./compenents/update";
|
import { CourseUpdate } from "./compenents/update";
|
||||||
import { CourseHourUpdate } from "./compenents/hour-update";
|
import { CourseHourUpdate } from "./compenents/hour-update";
|
||||||
|
import { CourseAttachmentUpdate } from "./compenents/attachment-update";
|
||||||
|
|
||||||
const { confirm } = Modal;
|
const { confirm } = Modal;
|
||||||
|
|
||||||
@ -66,6 +67,8 @@ const CoursePage = () => {
|
|||||||
const [createVisible, setCreateVisible] = useState<boolean>(false);
|
const [createVisible, setCreateVisible] = useState<boolean>(false);
|
||||||
const [updateVisible, setUpdateVisible] = useState<boolean>(false);
|
const [updateVisible, setUpdateVisible] = useState<boolean>(false);
|
||||||
const [updateHourVisible, setHourUpdateVisible] = useState<boolean>(false);
|
const [updateHourVisible, setHourUpdateVisible] = useState<boolean>(false);
|
||||||
|
const [updateAttachmentVisible, setUpdateAttachmentVisible] =
|
||||||
|
useState<boolean>(false);
|
||||||
const [cid, setCid] = useState<number>(0);
|
const [cid, setCid] = useState<number>(0);
|
||||||
const [cateId, setCateId] = useState(Number(result.get("cid")));
|
const [cateId, setCateId] = useState(Number(result.get("cid")));
|
||||||
const [did, setDid] = useState(Number(result.get("did")));
|
const [did, setDid] = useState(Number(result.get("did")));
|
||||||
@ -241,6 +244,23 @@ const CoursePage = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "3",
|
key: "3",
|
||||||
|
label: (
|
||||||
|
<Button
|
||||||
|
style={{ verticalAlign: "middle" }}
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
className="b-n-link c-red"
|
||||||
|
onClick={() => {
|
||||||
|
setCid(Number(record.id));
|
||||||
|
setUpdateAttachmentVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
课件
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "4",
|
||||||
label: (
|
label: (
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
@ -455,6 +475,14 @@ const CoursePage = () => {
|
|||||||
setRefresh(!refresh);
|
setRefresh(!refresh);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<CourseAttachmentUpdate
|
||||||
|
id={cid}
|
||||||
|
open={updateAttachmentVisible}
|
||||||
|
onCancel={() => {
|
||||||
|
setUpdateAttachmentVisible(false);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,15 +1,35 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
import { Button, Result } from "antd";
|
import { Button, Result } from "antd";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useParams, useNavigate, useLocation } from "react-router-dom";
|
||||||
import styles from "./index.module.less";
|
import styles from "./index.module.less";
|
||||||
|
|
||||||
const ErrorPage = () => {
|
const ErrorPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const result = new URLSearchParams(useLocation().search);
|
||||||
|
const [code, setCode] = useState(Number(result.get("code")));
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCode(Number(result.get("code")));
|
||||||
|
}, [result.get("code")]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (code === 403) {
|
||||||
|
setError("无权限操作");
|
||||||
|
} else if (code === 404) {
|
||||||
|
setError("URL或资源不存在");
|
||||||
|
} else if (code === 429) {
|
||||||
|
setError("请求次数过多,请稍后再试");
|
||||||
|
} else {
|
||||||
|
setError("系统错误");
|
||||||
|
}
|
||||||
|
}, [code]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Result
|
<Result
|
||||||
status="404"
|
status="404"
|
||||||
title="404"
|
title={code}
|
||||||
subTitle="您访问的页面不存在"
|
subTitle={error}
|
||||||
className={styles["main"]}
|
className={styles["main"]}
|
||||||
extra={
|
extra={
|
||||||
<Button
|
<Button
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import React, { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import styles from "./index.module.less";
|
import styles from "./index.module.less";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
import { Header, LeftMenu } from "../../compenents";
|
import { Header, LeftMenu } from "../../../compenents";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import LoadingPage from "../../loading";
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
useEffect(() => {}, []);
|
useEffect(() => {}, []);
|
||||||
@ -17,8 +19,10 @@ const HomePage = () => {
|
|||||||
<Header></Header>
|
<Header></Header>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["right-main"]}>
|
<div className={styles["right-main"]}>
|
||||||
{/* 二级路由出口 */}
|
<Suspense fallback={<LoadingPage height="100vh" />}>
|
||||||
<Outlet />
|
{/* 二级路由出口 */}
|
||||||
|
<Outlet />{" "}
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -0,0 +1,8 @@
|
|||||||
|
.layout-wrap {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
16
src/pages/layouts/without-header-without-footer/index.tsx
Normal file
16
src/pages/layouts/without-header-without-footer/index.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import styles from "./index.module.less";
|
||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
import LoadingPage from "../../loading";
|
||||||
|
|
||||||
|
const WithoutHeaderWithoutFooter = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles["layout-wrap"]}>
|
||||||
|
<Suspense fallback={<LoadingPage height="100vh" />}>
|
||||||
|
<Outlet />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithoutHeaderWithoutFooter;
|
@ -1,6 +1,6 @@
|
|||||||
.loading-parent-box {
|
.loading-box {
|
||||||
width: 100vd;
|
width: 100vw;
|
||||||
height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 100vh;
|
line-height: 100vh;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
import { Spin } from "antd";
|
import { Spin } from "antd";
|
||||||
import styles from "./index.module.less";
|
import styles from "./index.module.less";
|
||||||
|
|
||||||
const LoadingPage = () => {
|
interface PropsInterface {
|
||||||
|
height?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoadingPage = (props: PropsInterface) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles["loading-parent-box"]}>
|
<>
|
||||||
<Spin size="large" />
|
<div
|
||||||
</div>
|
className={styles["loading-box"]}
|
||||||
|
style={{ height: props.height || "100vh" }}
|
||||||
|
>
|
||||||
|
<Spin size="large" />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
150
src/pages/resource/courseware/compenents/update-dialog/index.tsx
Normal file
150
src/pages/resource/courseware/compenents/update-dialog/index.tsx
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Modal, Form, Input, message, TreeSelect } from "antd";
|
||||||
|
import { resource, resourceCategory } from "../../../../../api/index";
|
||||||
|
|
||||||
|
interface PropInterface {
|
||||||
|
id: number;
|
||||||
|
open: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CoursewareUpdateDialog: React.FC<PropInterface> = ({
|
||||||
|
id,
|
||||||
|
open,
|
||||||
|
onCancel,
|
||||||
|
onSuccess,
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [categories, setCategories] = useState<any>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (open) {
|
||||||
|
getCategory();
|
||||||
|
getDetail();
|
||||||
|
}
|
||||||
|
}, [id, open]);
|
||||||
|
|
||||||
|
const getCategory = () => {
|
||||||
|
resourceCategory.resourceCategoryList().then((res: any) => {
|
||||||
|
const categories = res.data.categories;
|
||||||
|
if (JSON.stringify(categories) !== "{}") {
|
||||||
|
const new_arr: any = checkArr(categories, 0, null);
|
||||||
|
setCategories(new_arr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDetail = () => {
|
||||||
|
resource.videoDetail(id).then((res: any) => {
|
||||||
|
let data = res.data.resources;
|
||||||
|
form.setFieldsValue({
|
||||||
|
name: data.name,
|
||||||
|
category_id: res.data.category_ids,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkArr = (departments: any[], id: number, counts: any) => {
|
||||||
|
const arr = [];
|
||||||
|
for (let i = 0; i < departments[id].length; i++) {
|
||||||
|
if (!departments[departments[id][i].id]) {
|
||||||
|
arr.push({
|
||||||
|
title: departments[id][i].name,
|
||||||
|
value: departments[id][i].id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const new_arr: any = checkArr(
|
||||||
|
departments,
|
||||||
|
departments[id][i].id,
|
||||||
|
counts
|
||||||
|
);
|
||||||
|
arr.push({
|
||||||
|
title: departments[id][i].name,
|
||||||
|
value: departments[id][i].id,
|
||||||
|
children: new_arr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinish = (values: any) => {
|
||||||
|
setLoading(true);
|
||||||
|
if (Array.isArray(values.category_id)) {
|
||||||
|
values.category_id = values.category_id[0];
|
||||||
|
}
|
||||||
|
resource
|
||||||
|
.videoUpdate(id, values)
|
||||||
|
.then((res: any) => {
|
||||||
|
setLoading(false);
|
||||||
|
message.success("保存成功!");
|
||||||
|
onSuccess();
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinishFailed = (errorInfo: any) => {
|
||||||
|
console.log("Failed:", errorInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
title="编辑课件"
|
||||||
|
centered
|
||||||
|
forceRender
|
||||||
|
open={open}
|
||||||
|
width={416}
|
||||||
|
onOk={() => form.submit()}
|
||||||
|
onCancel={() => onCancel()}
|
||||||
|
maskClosable={false}
|
||||||
|
>
|
||||||
|
<div className="float-left mt-24">
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
name="videos-update"
|
||||||
|
labelCol={{ span: 8 }}
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ remember: true }}
|
||||||
|
onFinish={onFinish}
|
||||||
|
onFinishFailed={onFinishFailed}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label="课件分类"
|
||||||
|
name="category_id"
|
||||||
|
rules={[{ required: true, message: "请选择课件分类!" }]}
|
||||||
|
>
|
||||||
|
<TreeSelect
|
||||||
|
showCheckedStrategy={TreeSelect.SHOW_ALL}
|
||||||
|
allowClear
|
||||||
|
style={{ width: 200 }}
|
||||||
|
treeData={categories}
|
||||||
|
placeholder="课件分类"
|
||||||
|
treeDefaultExpandAll
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="课件名称"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: "请输入课件名称!" }]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
allowClear
|
||||||
|
style={{ width: 200 }}
|
||||||
|
placeholder="请输入课件名称"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
0
src/pages/resource/courseware/index.module.less
Normal file
0
src/pages/resource/courseware/index.module.less
Normal file
392
src/pages/resource/courseware/index.tsx
Normal file
392
src/pages/resource/courseware/index.tsx
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Table,
|
||||||
|
message,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
Button,
|
||||||
|
} from "antd";
|
||||||
|
import type { MenuProps } from "antd";
|
||||||
|
import { resource } from "../../../api";
|
||||||
|
// import styles from "./index.module.less";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { DownOutlined, ExclamationCircleFilled } from "@ant-design/icons";
|
||||||
|
import type { ColumnsType } from "antd/es/table";
|
||||||
|
import { dateFormat } from "../../../utils/index";
|
||||||
|
import { TreeCategory, UploadCoursewareButton } from "../../../compenents";
|
||||||
|
import { CoursewareUpdateDialog } from "./compenents/update-dialog";
|
||||||
|
|
||||||
|
const { confirm } = Modal;
|
||||||
|
|
||||||
|
interface DataType {
|
||||||
|
id: React.Key;
|
||||||
|
name: string;
|
||||||
|
created_at: string;
|
||||||
|
type: string;
|
||||||
|
number: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResourceCoursewarePage = () => {
|
||||||
|
const result = new URLSearchParams(useLocation().search);
|
||||||
|
const [list, setList] = useState<any>([]);
|
||||||
|
const [adminUsers, setAdminUsers] = useState<any>({});
|
||||||
|
const [existingTypes, setExistingTypes] = useState<any>([]);
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [size, setSize] = useState(10);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [category_ids, setCategoryIds] = useState<any>([]);
|
||||||
|
const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]);
|
||||||
|
const [type, setType] = useState("WORD,EXCEL,PPT,PDF,TXT,RAR,ZIP");
|
||||||
|
const [title, setTitle] = useState<string>("");
|
||||||
|
const [multiConfig, setMultiConfig] = useState<boolean>(false);
|
||||||
|
const [selLabel, setLabel] = useState<string>(
|
||||||
|
result.get("label") ? String(result.get("label")) : "全部课件"
|
||||||
|
);
|
||||||
|
const [cateId, setCateId] = useState(Number(result.get("cid")));
|
||||||
|
const [updateId, setUpdateId] = useState(0);
|
||||||
|
const [updateVisible, setUpdateVisible] = useState<boolean>(false);
|
||||||
|
const types = [
|
||||||
|
{ label: "全部", value: "WORD,EXCEL,PPT,PDF,TXT,RAR,ZIP" },
|
||||||
|
{ label: "WORD", value: "WORD" },
|
||||||
|
{ label: "EXCEL", value: "EXCEL" },
|
||||||
|
{ label: "PPT", value: "PPT" },
|
||||||
|
{ label: "PDF", value: "PDF" },
|
||||||
|
{ label: "TXT", value: "TXT" },
|
||||||
|
{ label: "RAR", value: "RAR" },
|
||||||
|
{ label: "ZIP", value: "ZIP" },
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCateId(Number(result.get("cid")));
|
||||||
|
if (Number(result.get("cid"))) {
|
||||||
|
let arr = [];
|
||||||
|
arr.push(Number(result.get("cid")));
|
||||||
|
setCategoryIds(arr);
|
||||||
|
}
|
||||||
|
}, [result.get("cid")]);
|
||||||
|
|
||||||
|
// 加载课件列表
|
||||||
|
useEffect(() => {
|
||||||
|
getList();
|
||||||
|
}, [category_ids, refresh, page, size]);
|
||||||
|
|
||||||
|
const getList = () => {
|
||||||
|
setLoading(true);
|
||||||
|
let categoryIds = category_ids.join(",");
|
||||||
|
resource
|
||||||
|
.resourceList(page, size, "", "", title, type, categoryIds)
|
||||||
|
.then((res: any) => {
|
||||||
|
setTotal(res.data.result.total);
|
||||||
|
setList(res.data.result.data);
|
||||||
|
setExistingTypes(res.data.existing_types);
|
||||||
|
setAdminUsers(res.data.admin_users);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log("错误,", err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ColumnsType<DataType> = [
|
||||||
|
{
|
||||||
|
title: "课件名称",
|
||||||
|
render: (_, record: any) => (
|
||||||
|
<div className="d-flex">
|
||||||
|
<i
|
||||||
|
className="iconfont icon-icon-file"
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: "rgba(0,0,0,0.3)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="ml-8">
|
||||||
|
{record.name}.{record.extension}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "课件格式",
|
||||||
|
dataIndex: "type",
|
||||||
|
width: 204,
|
||||||
|
render: (type: string) => <span>{type}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "课件大小",
|
||||||
|
dataIndex: "size",
|
||||||
|
width: 204,
|
||||||
|
render: (size: number) => <span>{(size / 1024 / 1024).toFixed(2)}M</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "创建人",
|
||||||
|
dataIndex: "admin_id",
|
||||||
|
width: 204,
|
||||||
|
render: (text: number) =>
|
||||||
|
JSON.stringify(adminUsers) !== "{}" && <span>{adminUsers[text]}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "创建时间",
|
||||||
|
dataIndex: "created_at",
|
||||||
|
width: 204,
|
||||||
|
render: (text: string) => <span>{dateFormat(text)}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
fixed: "right",
|
||||||
|
width: 180,
|
||||||
|
render: (_, record: any) => {
|
||||||
|
return (
|
||||||
|
<Space size="small">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
className="b-n-link c-red"
|
||||||
|
onClick={() => {
|
||||||
|
downLoadFile(record.url);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
下载
|
||||||
|
</Button>
|
||||||
|
<div className="form-column"></div>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="b-link c-red"
|
||||||
|
onClick={() => {
|
||||||
|
setUpdateId(record.id);
|
||||||
|
setUpdateVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<div className="form-column"></div>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="b-link c-red"
|
||||||
|
onClick={() => removeResource(record.id)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
current: page, //当前页码
|
||||||
|
pageSize: size,
|
||||||
|
total: total, // 总条数
|
||||||
|
onChange: (page: number, pageSize: number) =>
|
||||||
|
handlePageChange(page, pageSize), //改变页码的函数
|
||||||
|
showSizeChanger: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
setPage(page);
|
||||||
|
setSize(pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rowSelection = {
|
||||||
|
onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
|
||||||
|
setSelectedRowKeys(selectedRowKeys);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置列表
|
||||||
|
const resetList = () => {
|
||||||
|
setPage(1);
|
||||||
|
setSize(10);
|
||||||
|
setList([]);
|
||||||
|
setTitle("");
|
||||||
|
setSelectedRowKeys([]);
|
||||||
|
setType("WORD,EXCEL,PPT,PDF,TXT,RAR,ZIP");
|
||||||
|
setRefresh(!refresh);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除课件
|
||||||
|
const removeResource = (id: number) => {
|
||||||
|
if (id === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
confirm({
|
||||||
|
title: "操作确认",
|
||||||
|
icon: <ExclamationCircleFilled />,
|
||||||
|
content: "删除前请检查选中课件文件无关联课程,确认删除?",
|
||||||
|
centered: true,
|
||||||
|
okText: "确认",
|
||||||
|
cancelText: "取消",
|
||||||
|
onOk() {
|
||||||
|
resource.destroyResource(id).then(() => {
|
||||||
|
message.success("删除成功");
|
||||||
|
resetList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log("Cancel");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量删除课件
|
||||||
|
const removeResourceMulti = () => {
|
||||||
|
if (selectedRowKeys.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
confirm({
|
||||||
|
title: "操作确认",
|
||||||
|
icon: <ExclamationCircleFilled />,
|
||||||
|
content: "删除前请检查选中课件文件无关联课程,确认删除?",
|
||||||
|
centered: true,
|
||||||
|
okText: "确认",
|
||||||
|
cancelText: "取消",
|
||||||
|
onOk() {
|
||||||
|
resource.destroyResourceMulti(selectedRowKeys).then(() => {
|
||||||
|
message.success("删除成功");
|
||||||
|
resetList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
console.log("Cancel");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const downLoadFile = (url: string) => {
|
||||||
|
console.log(url);
|
||||||
|
window.open(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="tree-main-body">
|
||||||
|
<div className="left-box">
|
||||||
|
<TreeCategory
|
||||||
|
selected={category_ids}
|
||||||
|
type="no-cate"
|
||||||
|
text={"课件"}
|
||||||
|
onUpdate={(keys: any, title: any) => {
|
||||||
|
setPage(1);
|
||||||
|
setCategoryIds(keys);
|
||||||
|
if (typeof title === "string") {
|
||||||
|
setLabel(title);
|
||||||
|
} else {
|
||||||
|
setLabel(title.props.children[0]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="right-box">
|
||||||
|
<div className="d-flex playedu-main-title float-left mb-24">
|
||||||
|
课件 | {selLabel}
|
||||||
|
</div>
|
||||||
|
<div className="float-left j-b-flex mb-24">
|
||||||
|
<div>
|
||||||
|
<UploadCoursewareButton
|
||||||
|
categoryIds={category_ids}
|
||||||
|
onUpdate={() => {
|
||||||
|
resetList();
|
||||||
|
}}
|
||||||
|
></UploadCoursewareButton>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
className="ml-16"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedRowKeys([]);
|
||||||
|
setMultiConfig(!multiConfig);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{multiConfig ? "取消操作" : "批量操作"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
className="ml-16"
|
||||||
|
onClick={() => removeResourceMulti()}
|
||||||
|
disabled={selectedRowKeys.length === 0}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex">
|
||||||
|
<div className="d-flex">
|
||||||
|
<div className="d-flex mr-24">
|
||||||
|
<Typography.Text>名称:</Typography.Text>
|
||||||
|
<Input
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => {
|
||||||
|
setTitle(e.target.value);
|
||||||
|
}}
|
||||||
|
allowClear
|
||||||
|
style={{ width: 160 }}
|
||||||
|
placeholder="请输入名称关键字"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex mr-24">
|
||||||
|
<Typography.Text>格式:</Typography.Text>
|
||||||
|
<Select
|
||||||
|
style={{ width: 160 }}
|
||||||
|
placeholder="请选择格式"
|
||||||
|
value={type}
|
||||||
|
onChange={(value: string) => setType(value)}
|
||||||
|
options={types}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button className="mr-16" onClick={resetList}>
|
||||||
|
重 置
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setPage(1);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查 询
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="float-left">
|
||||||
|
{multiConfig ? (
|
||||||
|
<Table
|
||||||
|
rowSelection={{
|
||||||
|
type: "checkbox",
|
||||||
|
...rowSelection,
|
||||||
|
}}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={list}
|
||||||
|
loading={loading}
|
||||||
|
pagination={paginationProps}
|
||||||
|
rowKey={(record) => record.id}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={list}
|
||||||
|
loading={loading}
|
||||||
|
pagination={paginationProps}
|
||||||
|
rowKey={(record) => record.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CoursewareUpdateDialog
|
||||||
|
id={Number(updateId)}
|
||||||
|
open={updateVisible}
|
||||||
|
onCancel={() => setUpdateVisible(false)}
|
||||||
|
onSuccess={() => {
|
||||||
|
setUpdateVisible(false);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
></CoursewareUpdateDialog>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResourceCoursewarePage;
|
@ -183,33 +183,35 @@ const ResourceImagesPage = () => {
|
|||||||
<Row gutter={16} style={{ marginBottom: 24 }}>
|
<Row gutter={16} style={{ marginBottom: 24 }}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<div className="j-b-flex">
|
<div className="j-b-flex">
|
||||||
<UploadImageSub
|
|
||||||
categoryIds={category_ids}
|
|
||||||
onUpdate={() => {
|
|
||||||
resetImageList();
|
|
||||||
}}
|
|
||||||
></UploadImageSub>
|
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
|
<UploadImageSub
|
||||||
|
categoryIds={category_ids}
|
||||||
|
onUpdate={() => {
|
||||||
|
resetImageList();
|
||||||
|
}}
|
||||||
|
></UploadImageSub>
|
||||||
{selectKey.length > 0 && (
|
{selectKey.length > 0 && (
|
||||||
<Button className="mr-16" onClick={() => cancelAll()}>
|
<Button className="ml-16" onClick={() => cancelAll()}>
|
||||||
取消选择
|
取消操作
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{selectKey.length === 0 && (
|
||||||
|
<Button className="ml-16" onClick={() => selectAll()}>
|
||||||
|
批量操作
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{imageList.length !== 0 && (
|
{imageList.length !== 0 && (
|
||||||
<>
|
<Button
|
||||||
<Button className="mr-16" onClick={() => selectAll()}>
|
className="ml-16"
|
||||||
全选
|
disabled={selectKey.length === 0}
|
||||||
</Button>
|
type="primary"
|
||||||
<Button
|
onClick={() => removeResource()}
|
||||||
disabled={selectKey.length === 0}
|
>
|
||||||
type="primary"
|
删除
|
||||||
onClick={() => removeResource()}
|
</Button>
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="d-flex"></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -158,6 +158,15 @@ const ResourceCategoryPage = () => {
|
|||||||
res.data.videos.length === 0
|
res.data.videos.length === 0
|
||||||
) {
|
) {
|
||||||
delUser(id);
|
delUser(id);
|
||||||
|
} else if (
|
||||||
|
res.data.children &&
|
||||||
|
res.data.children.length === 0 &&
|
||||||
|
res.data.courses &&
|
||||||
|
res.data.courses.length === 0 &&
|
||||||
|
!res.data.images &&
|
||||||
|
!res.data.videos
|
||||||
|
) {
|
||||||
|
delUser(id);
|
||||||
} else {
|
} else {
|
||||||
if (res.data.children && res.data.children.length > 0) {
|
if (res.data.children && res.data.children.length > 0) {
|
||||||
modal.warning({
|
modal.warning({
|
||||||
|
@ -7,7 +7,7 @@ import { useLocation } from "react-router-dom";
|
|||||||
import { DownOutlined, ExclamationCircleFilled } from "@ant-design/icons";
|
import { DownOutlined, ExclamationCircleFilled } from "@ant-design/icons";
|
||||||
import type { ColumnsType } from "antd/es/table";
|
import type { ColumnsType } from "antd/es/table";
|
||||||
import { dateFormat } from "../../../utils/index";
|
import { dateFormat } from "../../../utils/index";
|
||||||
import { TreeCategory, DurationText, PerButton } from "../../../compenents";
|
import { TreeCategory, DurationText } from "../../../compenents";
|
||||||
import { UploadVideoButton } from "../../../compenents/upload-video-button";
|
import { UploadVideoButton } from "../../../compenents/upload-video-button";
|
||||||
import { VideoPlayDialog } from "./compenents/video-play-dialog";
|
import { VideoPlayDialog } from "./compenents/video-play-dialog";
|
||||||
import { VideosUpdateDialog } from "./compenents/update-dialog";
|
import { VideosUpdateDialog } from "./compenents/update-dialog";
|
||||||
@ -83,7 +83,7 @@ const ResourceVideosPage = () => {
|
|||||||
JSON.stringify(adminUsers) !== "{}" && <span>{adminUsers[text]}</span>,
|
JSON.stringify(adminUsers) !== "{}" && <span>{adminUsers[text]}</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "视频时长",
|
title: "创建时间",
|
||||||
dataIndex: "created_at",
|
dataIndex: "created_at",
|
||||||
render: (text: string) => <span>{dateFormat(text)}</span>,
|
render: (text: string) => <span>{dateFormat(text)}</span>,
|
||||||
},
|
},
|
||||||
@ -286,11 +286,9 @@ const ResourceVideosPage = () => {
|
|||||||
resetVideoList();
|
resetVideoList();
|
||||||
}}
|
}}
|
||||||
></UploadVideoButton>
|
></UploadVideoButton>
|
||||||
</div>
|
|
||||||
<div className="d-flex">
|
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
className="mr-16"
|
className="ml-16"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
setMultiConfig(!multiConfig);
|
setMultiConfig(!multiConfig);
|
||||||
@ -299,6 +297,7 @@ const ResourceVideosPage = () => {
|
|||||||
{multiConfig ? "取消操作" : "批量操作"}
|
{multiConfig ? "取消操作" : "批量操作"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
className="ml-16"
|
||||||
type="default"
|
type="default"
|
||||||
onClick={() => removeResourceMulti()}
|
onClick={() => removeResourceMulti()}
|
||||||
disabled={selectedRowKeys.length === 0}
|
disabled={selectedRowKeys.length === 0}
|
||||||
@ -306,6 +305,7 @@ const ResourceVideosPage = () => {
|
|||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="d-flex"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="float-left">
|
<div className="float-left">
|
||||||
{multiConfig ? (
|
{multiConfig ? (
|
||||||
|
57
src/pages/system/adminlog/compenents/detail-dialog.tsx
Normal file
57
src/pages/system/adminlog/compenents/detail-dialog.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Modal, Form } from "antd";
|
||||||
|
|
||||||
|
interface PropInterface {
|
||||||
|
param: string;
|
||||||
|
result: string;
|
||||||
|
open: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdminLogDetailDialog: React.FC<PropInterface> = ({
|
||||||
|
param,
|
||||||
|
open,
|
||||||
|
onCancel,
|
||||||
|
result,
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const onFinish = (values: any) => {};
|
||||||
|
|
||||||
|
const onFinishFailed = (errorInfo: any) => {
|
||||||
|
console.log("Failed:", errorInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
title="日志详情"
|
||||||
|
centered
|
||||||
|
forceRender
|
||||||
|
open={open}
|
||||||
|
width={416}
|
||||||
|
onOk={() => onCancel()}
|
||||||
|
onCancel={() => onCancel()}
|
||||||
|
footer={null}
|
||||||
|
maskClosable={false}
|
||||||
|
>
|
||||||
|
<div className="mt-24">
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
name="adminlog-detail"
|
||||||
|
labelCol={{ span: 5 }}
|
||||||
|
wrapperCol={{ span: 19 }}
|
||||||
|
initialValues={{ remember: true }}
|
||||||
|
onFinish={onFinish}
|
||||||
|
onFinishFailed={onFinishFailed}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Form.Item label="Param">{param}</Form.Item>
|
||||||
|
<Form.Item label="Result">{result}</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
0
src/pages/system/adminlog/index.module.less
Normal file
0
src/pages/system/adminlog/index.module.less
Normal file
224
src/pages/system/adminlog/index.tsx
Normal file
224
src/pages/system/adminlog/index.tsx
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Table, Typography, Input, Button, DatePicker } from "antd";
|
||||||
|
import { adminLog } from "../../../api";
|
||||||
|
// import styles from "./index.module.less";
|
||||||
|
import type { ColumnsType } from "antd/es/table";
|
||||||
|
import { dateWholeFormat, transUtcTime } from "../../../utils/index";
|
||||||
|
import { AdminLogDetailDialog } from "./compenents/detail-dialog";
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
interface DataType {
|
||||||
|
id: React.Key;
|
||||||
|
admin_id: number;
|
||||||
|
ip: string;
|
||||||
|
opt: string;
|
||||||
|
admin_name: string;
|
||||||
|
module: string;
|
||||||
|
created_at: string;
|
||||||
|
title: string;
|
||||||
|
ip_area: string;
|
||||||
|
param: string;
|
||||||
|
result: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SystemLogPage = () => {
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [size, setSize] = useState(10);
|
||||||
|
const [list, setList] = useState<any>([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [adminId, setAdminId] = useState("");
|
||||||
|
const [adminName, setAdminName] = useState("");
|
||||||
|
const [created_at, setCreatedAt] = useState<any>([]);
|
||||||
|
const [createdAts, setCreatedAts] = useState<any>([]);
|
||||||
|
const [param, setParam] = useState("");
|
||||||
|
const [result, setResult] = useState("");
|
||||||
|
const [visiable, setVisiable] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, [refresh, page, size]);
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
setLoading(true);
|
||||||
|
adminLog
|
||||||
|
.adminLogList(
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
adminName,
|
||||||
|
title,
|
||||||
|
"",
|
||||||
|
created_at[0],
|
||||||
|
created_at[1]
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
|
setList(res.data.data);
|
||||||
|
setTotal(res.data.total);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetData = () => {
|
||||||
|
setTitle("");
|
||||||
|
setAdminId("");
|
||||||
|
setAdminName("");
|
||||||
|
setPage(1);
|
||||||
|
setSize(10);
|
||||||
|
setList([]);
|
||||||
|
setCreatedAts([]);
|
||||||
|
setCreatedAt([]);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
current: page, //当前页码
|
||||||
|
pageSize: size,
|
||||||
|
total: total, // 总条数
|
||||||
|
onChange: (page: number, pageSize: number) =>
|
||||||
|
handlePageChange(page, pageSize), //改变页码的函数
|
||||||
|
showSizeChanger: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
setPage(page);
|
||||||
|
setSize(pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const disabledDate = (current: any) => {
|
||||||
|
return current && current >= moment().add(0, "days"); // 选择时间要大于等于当前天。若今天不能被选择,去掉等号即可。
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ColumnsType<DataType> = [
|
||||||
|
{
|
||||||
|
title: "管理员名称",
|
||||||
|
width: 150,
|
||||||
|
render: (_, record: any) => <span>{record.admin_name}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
render: (_, record: any) => <span>{record.title}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "IP地区",
|
||||||
|
width: 250,
|
||||||
|
dataIndex: "ip_area",
|
||||||
|
render: (ip_area: string) => <span>{ip_area}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "时间",
|
||||||
|
width: 200,
|
||||||
|
dataIndex: "created_at",
|
||||||
|
render: (created_at: string) => (
|
||||||
|
<span>{dateWholeFormat(created_at)}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
fixed: "right",
|
||||||
|
width: 160,
|
||||||
|
render: (_, record) => (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="b-link c-red"
|
||||||
|
onClick={() => {
|
||||||
|
setParam(record.param);
|
||||||
|
setResult(record.result);
|
||||||
|
setVisiable(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="playedu-main-body">
|
||||||
|
<div className="float-left j-b-flex mb-24">
|
||||||
|
<div className="d-flex"></div>
|
||||||
|
<div className="d-flex">
|
||||||
|
<div className="d-flex mr-24">
|
||||||
|
<Typography.Text>管理员名称:</Typography.Text>
|
||||||
|
<Input
|
||||||
|
value={adminName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setAdminName(e.target.value);
|
||||||
|
}}
|
||||||
|
allowClear
|
||||||
|
style={{ width: 160 }}
|
||||||
|
placeholder="请输入管理员名称"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex mr-24">
|
||||||
|
<Typography.Text>操作:</Typography.Text>
|
||||||
|
<Input
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => {
|
||||||
|
setTitle(e.target.value);
|
||||||
|
}}
|
||||||
|
allowClear
|
||||||
|
style={{ width: 160 }}
|
||||||
|
placeholder="请输入操作"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex mr-24">
|
||||||
|
<Typography.Text>时间:</Typography.Text>
|
||||||
|
<RangePicker
|
||||||
|
disabledDate={disabledDate}
|
||||||
|
format={"YYYY-MM-DD"}
|
||||||
|
value={createdAts}
|
||||||
|
style={{ marginLeft: 10 }}
|
||||||
|
onChange={(date, dateString) => {
|
||||||
|
let date1 = dateString[0] + " 00:00:00";
|
||||||
|
let date2 = dateString[1] + " 23:59:59";
|
||||||
|
dateString[0] = transUtcTime(date1);
|
||||||
|
dateString[1] = transUtcTime(date2);
|
||||||
|
setCreatedAt(dateString);
|
||||||
|
setCreatedAts(date);
|
||||||
|
}}
|
||||||
|
placeholder={["时间-开始", "时间-结束"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex">
|
||||||
|
<Button className="mr-16" onClick={resetData}>
|
||||||
|
重 置
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setPage(1);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查 询
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="float-left">
|
||||||
|
<Table
|
||||||
|
loading={loading}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={list}
|
||||||
|
rowKey={(record) => record.id}
|
||||||
|
pagination={paginationProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<AdminLogDetailDialog
|
||||||
|
param={param}
|
||||||
|
result={result}
|
||||||
|
open={visiable}
|
||||||
|
onCancel={() => setVisiable(false)}
|
||||||
|
></AdminLogDetailDialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemLogPage;
|
@ -62,6 +62,11 @@ export const SystemAdminrolesCreate: React.FC<PropInterface> = ({
|
|||||||
value: "管理员-n",
|
value: "管理员-n",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "管理员日志",
|
||||||
|
value: "管理员日志-n",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "管理员角色",
|
title: "管理员角色",
|
||||||
value: "管理员角色-n",
|
value: "管理员角色-n",
|
||||||
|
@ -65,6 +65,11 @@ export const SystemAdminrolesUpdate: React.FC<PropInterface> = ({
|
|||||||
value: "管理员-n",
|
value: "管理员-n",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "管理员日志",
|
||||||
|
value: "管理员日志-n",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "管理员角色",
|
title: "管理员角色",
|
||||||
value: "管理员角色-n",
|
value: "管理员角色-n",
|
||||||
|
@ -7,7 +7,9 @@ import KeepAlive from "../compenents/keep-alive";
|
|||||||
// 页面加载
|
// 页面加载
|
||||||
import InitPage from "../pages/init";
|
import InitPage from "../pages/init";
|
||||||
import LoginPage from "../pages/login";
|
import LoginPage from "../pages/login";
|
||||||
import HomePage from "../pages/home";
|
import WithHeaderWithoutFooter from "../pages/layouts/with-header-without-footer";
|
||||||
|
import WithoutHeaderWithoutFooter from "../pages/layouts/without-header-without-footer";
|
||||||
|
|
||||||
//首页
|
//首页
|
||||||
const DashboardPage = lazy(() => import("../pages/dashboard"));
|
const DashboardPage = lazy(() => import("../pages/dashboard"));
|
||||||
//修改密码页面
|
//修改密码页面
|
||||||
@ -18,6 +20,9 @@ const ResourceCategoryPage = lazy(
|
|||||||
);
|
);
|
||||||
const ResourceImagesPage = lazy(() => import("../pages/resource/images"));
|
const ResourceImagesPage = lazy(() => import("../pages/resource/images"));
|
||||||
const ResourceVideosPage = lazy(() => import("../pages/resource/videos"));
|
const ResourceVideosPage = lazy(() => import("../pages/resource/videos"));
|
||||||
|
const ResourceCoursewarePage = lazy(
|
||||||
|
() => import("../pages/resource/courseware")
|
||||||
|
);
|
||||||
//课程相关
|
//课程相关
|
||||||
const CoursePage = lazy(() => import("../pages/course/index"));
|
const CoursePage = lazy(() => import("../pages/course/index"));
|
||||||
const CourseUserPage = lazy(() => import("../pages/course/user"));
|
const CourseUserPage = lazy(() => import("../pages/course/user"));
|
||||||
@ -34,6 +39,7 @@ const SystemAdministratorPage = lazy(
|
|||||||
() => import("../pages/system/administrator")
|
() => import("../pages/system/administrator")
|
||||||
);
|
);
|
||||||
const SystemAdminrolesPage = lazy(() => import("../pages/system/adminroles"));
|
const SystemAdminrolesPage = lazy(() => import("../pages/system/adminroles"));
|
||||||
|
const SystemLogPage = lazy(() => import("../pages/system/adminlog"));
|
||||||
//部门页面
|
//部门页面
|
||||||
const DepartmentPage = lazy(() => import("../pages/department"));
|
const DepartmentPage = lazy(() => import("../pages/department"));
|
||||||
//测试
|
//测试
|
||||||
@ -77,7 +83,7 @@ const routes: RouteObject[] = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: <PrivateRoute Component={<HomePage />} />,
|
element: <PrivateRoute Component={<WithHeaderWithoutFooter />} />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
@ -99,6 +105,10 @@ const routes: RouteObject[] = [
|
|||||||
path: "/videos",
|
path: "/videos",
|
||||||
element: <PrivateRoute Component={<ResourceVideosPage />} />,
|
element: <PrivateRoute Component={<ResourceVideosPage />} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/courseware",
|
||||||
|
element: <PrivateRoute Component={<ResourceCoursewarePage />} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/course",
|
path: "/course",
|
||||||
element: <PrivateRoute Component={<CoursePage />} />,
|
element: <PrivateRoute Component={<CoursePage />} />,
|
||||||
@ -143,6 +153,10 @@ const routes: RouteObject[] = [
|
|||||||
path: "/system/adminroles",
|
path: "/system/adminroles",
|
||||||
element: <PrivateRoute Component={<SystemAdminrolesPage />} />,
|
element: <PrivateRoute Component={<SystemAdminrolesPage />} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/system/adminlog",
|
||||||
|
element: <PrivateRoute Component={<SystemLogPage />} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/department",
|
path: "/department",
|
||||||
element: <PrivateRoute Component={<DepartmentPage />} />,
|
element: <PrivateRoute Component={<DepartmentPage />} />,
|
||||||
@ -150,16 +164,26 @@ const routes: RouteObject[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/login",
|
path: "/",
|
||||||
element: <LoginPage />,
|
element: <WithoutHeaderWithoutFooter />,
|
||||||
},
|
children: [
|
||||||
{
|
{
|
||||||
path: "/test",
|
path: "/login",
|
||||||
element: <TestPage />,
|
element: <LoginPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "*",
|
path: "/test",
|
||||||
element: <ErrorPage />,
|
element: <TestPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/error",
|
||||||
|
element: <ErrorPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "*",
|
||||||
|
element: <ErrorPage />,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -133,3 +133,20 @@ export function checkUrl(value: any) {
|
|||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dateWholeFormat(dateStr: string) {
|
||||||
|
if (!dateStr) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return moment(dateStr).format("YYYY-MM-DD HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transUtcTime(value: string) {
|
||||||
|
const specifiedTime = value;
|
||||||
|
// 创建一个新的Date对象,传入指定时间
|
||||||
|
const specifiedDate = new Date(specifiedTime);
|
||||||
|
//将指定时间转换为UTC+0时间
|
||||||
|
const utcTime = specifiedDate.toISOString();
|
||||||
|
|
||||||
|
return utcTime;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user