diff --git a/src/api/admin-log.ts b/src/api/admin-log.ts index 806c607..fcc7bcb 100644 --- a/src/api/admin-log.ts +++ b/src/api/admin-log.ts @@ -19,3 +19,7 @@ export function adminLogList( end_time: end_time, }); } + +export function adminLogDetail(id: number) { + return client.get(`/backend/v1/admin/log/detail/${id}`, {}); +} diff --git a/src/api/course.ts b/src/api/course.ts index 87dedc4..68080ba 100644 --- a/src/api/course.ts +++ b/src/api/course.ts @@ -34,8 +34,8 @@ export function storeCourse( isRequired: number, depIds: number[], categoryIds: number[], - chapters: number[], - hours: number[], + chapters: any[], + hours: any[], attachments: any[] ) { return client.post("/backend/v1/course/create", { @@ -66,7 +66,8 @@ export function updateCourse( depIds: number[], categoryIds: number[], chapters: number[], - hours: number[] + hours: number[], + publishedAt: string ) { return client.put(`/backend/v1/course/${id}`, { title: title, @@ -78,6 +79,7 @@ export function updateCourse( category_ids: categoryIds, chapters: chapters, hours: hours, + published_at: publishedAt, }); } diff --git a/src/compenents/left-menu/index.module.less b/src/compenents/left-menu/index.module.less index 771d7ab..6f1b239 100644 --- a/src/compenents/left-menu/index.module.less +++ b/src/compenents/left-menu/index.module.less @@ -13,7 +13,7 @@ .menu-box { width: 200px; - height: 100%; + height: calc(100% - 74px); overflow-y: auto; overflow-x: hidden; } diff --git a/src/compenents/select-attachment/index.tsx b/src/compenents/select-attachment/index.tsx index 5a4f08a..78297b7 100644 --- a/src/compenents/select-attachment/index.tsx +++ b/src/compenents/select-attachment/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Row, Modal, Tabs, Spin } from "antd"; +import { Row, Modal, Tabs } from "antd"; import styles from "./index.module.less"; import { UploadCoursewareSub } from "../../compenents"; import type { TabsProps } from "antd"; @@ -11,15 +11,20 @@ interface PropsInterface { onCancel: () => void; } +type selAttachmentModel = { + name: string; + rid: number; + type: string; +}; + export const SelectAttachment = (props: PropsInterface) => { const [refresh, setRefresh] = useState(true); - const [init, setInit] = useState(true); + const [tabKey, setTabKey] = useState(1); - const [selectKeys, setSelectKeys] = useState([]); - const [selectVideos, setSelectVideos] = useState([]); + const [selectKeys, setSelectKeys] = useState([]); + const [selectVideos, setSelectVideos] = useState([]); useEffect(() => { - setInit(true); setRefresh(!refresh); }, [props.open]); @@ -28,23 +33,15 @@ export const SelectAttachment = (props: PropsInterface) => { key: "1", label: `课件`, children: ( -
- { - setSelectKeys(arr); - setSelectVideos(videos); - }} - onSuccess={() => { - setInit(false); - }} - /> -
+ { + setSelectKeys(arr); + setSelectVideos(videos); + }} + /> ), }, ]; @@ -77,11 +74,6 @@ export const SelectAttachment = (props: PropsInterface) => { - {init && ( -
- -
- )} ) : null} diff --git a/src/compenents/select-resource/index.tsx b/src/compenents/select-resource/index.tsx index 93f4ca8..8410f84 100644 --- a/src/compenents/select-resource/index.tsx +++ b/src/compenents/select-resource/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Row, Modal, Tabs, Spin } from "antd"; +import { Row, Modal, Tabs } from "antd"; import styles from "./index.module.less"; import { UploadVideoSub } from "../../compenents"; import type { TabsProps } from "antd"; @@ -11,15 +11,20 @@ interface PropsInterface { onCancel: () => void; } +type selVideosModel = { + name: string; + rid: number; + type: string; + duration: number; +}; + export const SelectResource = (props: PropsInterface) => { const [refresh, setRefresh] = useState(true); - const [init, setInit] = useState(true); const [tabKey, setTabKey] = useState(1); - const [selectKeys, setSelectKeys] = useState([]); - const [selectVideos, setSelectVideos] = useState([]); + const [selectKeys, setSelectKeys] = useState([]); + const [selectVideos, setSelectVideos] = useState([]); useEffect(() => { - setInit(true); setRefresh(!refresh); }, [props.open]); @@ -28,10 +33,7 @@ export const SelectResource = (props: PropsInterface) => { key: "1", label: `视频`, children: ( -
+
{ setSelectKeys(arr); setSelectVideos(videos); }} - onSuccess={() => { - setInit(false); - }} />
), @@ -77,11 +76,6 @@ export const SelectResource = (props: PropsInterface) => { - {init && ( -
- -
- )} ) : null} diff --git a/src/compenents/tree-category/index.tsx b/src/compenents/tree-category/index.tsx index 9f0a259..bc01a50 100644 --- a/src/compenents/tree-category/index.tsx +++ b/src/compenents/tree-category/index.tsx @@ -18,7 +18,7 @@ interface PropInterface { export const TreeCategory = (props: PropInterface) => { const [treeData, setTreeData] = useState([]); const [loading, setLoading] = useState(true); - const [selectKey, setSelectKey] = useState([]); + const [selectKey, setSelectKey] = useState([]); useEffect(() => { if (props.selected && props.selected.length > 0) { @@ -28,7 +28,7 @@ export const TreeCategory = (props: PropInterface) => { useEffect(() => { resourceCategory.resourceCategoryList().then((res: any) => { - const categories = res.data.categories; + const categories: CategoriesBoxModel = res.data.categories; if (JSON.stringify(categories) !== "{}") { const new_arr: Option[] = checkArr(categories, 0); if (props.type === "no-cate") { @@ -43,7 +43,7 @@ export const TreeCategory = (props: PropInterface) => { }); }, []); - const checkArr = (categories: any[], id: number) => { + const checkArr = (categories: CategoriesBoxModel, id: number) => { const arr = []; for (let i = 0; i < categories[id].length; i++) { if (!categories[categories[id][i].id]) { @@ -102,7 +102,7 @@ export const TreeCategory = (props: PropInterface) => { selectedKeys={selectKey} onExpand={onExpand} treeData={treeData} - defaultExpandAll={true} + // defaultExpandAll={true} switcherIcon={} /> )} diff --git a/src/compenents/tree-department/index.tsx b/src/compenents/tree-department/index.tsx index 249d7a2..3bba14d 100644 --- a/src/compenents/tree-department/index.tsx +++ b/src/compenents/tree-department/index.tsx @@ -20,7 +20,7 @@ interface PropInterface { export const TreeDepartment = (props: PropInterface) => { const [treeData, setTreeData] = useState([]); const [loading, setLoading] = useState(true); - const [selectKey, setSelectKey] = useState([]); + const [selectKey, setSelectKey] = useState([]); const [userTotal, setUserTotal] = useState(0); useEffect(() => { @@ -32,8 +32,8 @@ export const TreeDepartment = (props: PropInterface) => { useEffect(() => { setLoading(true); department.departmentList().then((res: any) => { - const departments = res.data.departments; - const departCount = res.data.dep_user_count; + const departments: DepartmentsBoxModel = res.data.departments; + const departCount: DepIdsModel = res.data.dep_user_count; setUserTotal(res.data.user_total); if (JSON.stringify(departments) !== "{}") { if (props.showNum) { @@ -57,7 +57,11 @@ export const TreeDepartment = (props: PropInterface) => { }); }, [props.refresh]); - const checkNewArr = (departments: any[], id: number, counts: any) => { + const checkNewArr = ( + departments: DepartmentsBoxModel, + id: number, + counts: any + ) => { const arr = []; for (let i = 0; i < departments[id].length; i++) { if (!departments[departments[id][i].id]) { @@ -89,7 +93,7 @@ export const TreeDepartment = (props: PropInterface) => { return arr; }; - const checkArr = (departments: any[], id: number) => { + const checkArr = (departments: DepartmentsBoxModel, id: number) => { const arr = []; for (let i = 0; i < departments[id].length; i++) { if (!departments[departments[id][i].id]) { @@ -161,7 +165,7 @@ export const TreeDepartment = (props: PropInterface) => { onSelect={onSelect} onExpand={onExpand} treeData={treeData} - defaultExpandAll={true} + // defaultExpandAll={true} switcherIcon={} /> )} diff --git a/src/compenents/upload-courseware-sub/index.tsx b/src/compenents/upload-courseware-sub/index.tsx index 013264b..9866ca2 100644 --- a/src/compenents/upload-courseware-sub/index.tsx +++ b/src/compenents/upload-courseware-sub/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Row, Col, Empty, Table } from "antd"; +import { Row, Col, Empty, Table, Spin } from "antd"; import type { ColumnsType } from "antd/es/table"; import { resource } from "../../api"; import styles from "./index.module.less"; @@ -34,10 +34,10 @@ interface PropsInterface { label: string; open: boolean; onSelected: (arr: any[], videos: []) => void; - onSuccess: () => void; } export const UploadCoursewareSub = (props: PropsInterface) => { + const [init, setInit] = useState(true); const [category_ids, setCategoryIds] = useState([]); const [loading, setLoading] = useState(false); const [videoList, setVideoList] = useState([]); @@ -50,6 +50,7 @@ export const UploadCoursewareSub = (props: PropsInterface) => { // 加载列表 useEffect(() => { + setInit(true); getvideoList(); }, [props.open, category_ids, refresh, page, size]); @@ -61,6 +62,7 @@ export const UploadCoursewareSub = (props: PropsInterface) => { // 获取列表 const getvideoList = () => { + setLoading(true); let categoryIds = category_ids.join(","); resource .resourceList( @@ -76,9 +78,12 @@ export const UploadCoursewareSub = (props: PropsInterface) => { setTotal(res.data.result.total); setExistingTypes(res.data.existing_types); setVideoList(res.data.result.data); - props.onSuccess(); + setLoading(false); + setInit(false); }) .catch((err) => { + setLoading(false); + setInit(false); console.log("错误,", err); }); }; @@ -154,12 +159,22 @@ export const UploadCoursewareSub = (props: PropsInterface) => { <> - setCategoryIds(keys)} - /> + {init && ( +
+ +
+ )} +
+ setCategoryIds(keys)} + /> +
@@ -172,7 +187,15 @@ export const UploadCoursewareSub = (props: PropsInterface) => { > -
+ {init && ( +
+ +
+ )} +
{videoList.length === 0 && ( diff --git a/src/compenents/upload-video-button/index.tsx b/src/compenents/upload-video-button/index.tsx index ecc6bfc..394c595 100644 --- a/src/compenents/upload-video-button/index.tsx +++ b/src/compenents/upload-video-button/index.tsx @@ -11,7 +11,7 @@ import { Upload, } from "antd"; import Dragger from "antd/es/upload/Dragger"; -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import { generateUUID, parseVideo } from "../../utils"; import { minioMergeVideo, minioUploadId } from "../../api/upload"; import { UploadChunk } from "../../js/minio-upload-chunk"; @@ -24,7 +24,6 @@ interface PropsInterface { export const UploadVideoButton = (props: PropsInterface) => { const [showModal, setShowModal] = useState(false); const localFileList = useRef([]); - const intervalId = useRef(); const [fileList, setFileList] = useState([]); const getMinioUploadId = async () => { @@ -32,29 +31,6 @@ export const UploadVideoButton = (props: PropsInterface) => { return resp.data; }; - useEffect(() => { - if (showModal) { - intervalId.current = setInterval(() => { - if (localFileList.current.length === 0) { - return; - } - for (let i = 0; i < localFileList.current.length; i++) { - if (localFileList.current[i].upload.status === 0) { - localFileList.current[i].upload.handler.start(); - break; - } - if (localFileList.current[i].upload.status === 3) { - break; - } - } - }, 1000); - console.log("定时器已创建", intervalId.current); - } else { - window.clearInterval(intervalId.current); - console.log("定时器已销毁"); - } - }, [showModal]); - const uploadProps = { multiple: true, beforeUpload: async (file: File) => { @@ -104,6 +80,7 @@ export const UploadVideoButton = (props: PropsInterface) => { item.upload.remark = msg; setFileList([...localFileList.current]); }); + item.upload.handler.start(); // 先插入到ref localFileList.current.push(item); // 再更新list @@ -212,7 +189,18 @@ export const UploadVideoButton = (props: PropsInterface) => { render: (_, record) => ( <> {record.upload.status === 5 ? ( - {record.upload.remark} + <> + + ) : null} {record.upload.status === 7 ? ( diff --git a/src/compenents/upload-video-sub/index.tsx b/src/compenents/upload-video-sub/index.tsx index cd81a19..a503a75 100644 --- a/src/compenents/upload-video-sub/index.tsx +++ b/src/compenents/upload-video-sub/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Row, Col, Empty, Table } from "antd"; +import { Row, Col, Empty, Table, Spin } from "antd"; import type { ColumnsType } from "antd/es/table"; import { resource } from "../../api"; import styles from "./index.module.less"; @@ -35,10 +35,10 @@ interface PropsInterface { label: string; open: boolean; onSelected: (arr: any[], videos: []) => void; - onSuccess: () => void; } export const UploadVideoSub = (props: PropsInterface) => { + const [init, setInit] = useState(true); const [category_ids, setCategoryIds] = useState([]); const [loading, setLoading] = useState(false); const [videoList, setVideoList] = useState([]); @@ -51,6 +51,7 @@ export const UploadVideoSub = (props: PropsInterface) => { // 加载列表 useEffect(() => { + setInit(true); getvideoList(); }, [props.open, category_ids, refresh, page, size]); @@ -62,6 +63,7 @@ export const UploadVideoSub = (props: PropsInterface) => { // 获取列表 const getvideoList = () => { + setLoading(true); let categoryIds = category_ids.join(","); resource .resourceList(page, size, "", "", "", "VIDEO", categoryIds) @@ -69,9 +71,12 @@ export const UploadVideoSub = (props: PropsInterface) => { setTotal(res.data.result.total); setVideoExtra(res.data.videos_extra); setVideoList(res.data.result.data); - props.onSuccess(); + setLoading(false); + setInit(false); }) .catch((err) => { + setLoading(false); + setInit(false); console.log("错误,", err); }); }; @@ -154,12 +159,22 @@ export const UploadVideoSub = (props: PropsInterface) => { <> - setCategoryIds(keys)} - /> + {init && ( +
+ +
+ )} +
+ setCategoryIds(keys)} + /> +
@@ -172,7 +187,15 @@ export const UploadVideoSub = (props: PropsInterface) => { > -
+ {init && ( +
+ +
+ )} +
{videoList.length === 0 && ( diff --git a/src/js/minio-upload-chunk.ts b/src/js/minio-upload-chunk.ts index 170f2da..4eb1e86 100644 --- a/src/js/minio-upload-chunk.ts +++ b/src/js/minio-upload-chunk.ts @@ -29,7 +29,7 @@ export class UploadChunk { this.progress = 0; this.isStop = false; this.chunkIndex = 1; - this.chunkSize = 6 * 1024 * 1024; + this.chunkSize = 5 * 1024 * 1024; //分块大小-5mb this.chunkNumber = Math.ceil(file.size / this.chunkSize); this.uploadId = uploadId; @@ -100,6 +100,7 @@ export class UploadChunk { retry() { this.isStop = false; + this.uploadStatus = 0; this.start(); this.onRetry && this.onRetry(); } diff --git a/src/pages/course/compenents/attachment-update.tsx b/src/pages/course/compenents/attachment-update.tsx index 3fd7867..4d77bed 100644 --- a/src/pages/course/compenents/attachment-update.tsx +++ b/src/pages/course/compenents/attachment-update.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { Button, Drawer, Form, Modal, message } from "antd"; +import { Button, Drawer, Form, Modal, message, Spin } from "antd"; import styles from "./hour-update.module.less"; import { course, courseAttachment } from "../../../api/index"; import { SelectAttachment } from "../../../compenents"; @@ -20,11 +20,15 @@ export const CourseAttachmentUpdate: React.FC = ({ onCancel, }) => { const [form] = Form.useForm(); + const [init, setInit] = useState(true); const [attachmentVisible, setAttachmentVisible] = useState(false); - const [attachmentData, setAttachmentData] = useState([]); - const [attachments, setAttachments] = useState([]); + const [attachmentData, setAttachmentData] = useState( + [] + ); + const [attachments, setAttachments] = useState([]); useEffect(() => { + setInit(true); if (id === 0) { return; } @@ -43,6 +47,7 @@ export const CourseAttachmentUpdate: React.FC = ({ setAttachmentData(arr); setAttachments(keys); } + setInit(false); }); }; @@ -169,47 +174,57 @@ export const CourseAttachmentUpdate: React.FC = ({ selectAttachmentData(arr, videos); }} > -
-
- -
- -
-
-
- {attachmentData.length === 0 && ( - - 请点击上方按钮添加课件 - - )} - {attachmentData.length > 0 && ( - { - delAttachments(id); - }} - onUpdate={(arr: any[]) => { - transAttachments(arr); - }} - /> - )} -
+ {init && ( +
+
- + )} +
+
+
+ +
+ +
+
+
+ {attachmentData.length === 0 && ( + + 请点击上方按钮添加课件 + + )} + {attachmentData.length > 0 && ( + { + delAttachments(id); + }} + onUpdate={(arr: any[]) => { + transAttachments(arr); + }} + /> + )} +
+
+
+
) : null} diff --git a/src/pages/course/compenents/attachments.tsx b/src/pages/course/compenents/attachments.tsx index 5a0aa58..216d48c 100644 --- a/src/pages/course/compenents/attachments.tsx +++ b/src/pages/course/compenents/attachments.tsx @@ -1,20 +1,20 @@ -import { message, Tree, Tooltip } from "antd"; +import { 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[]; + data: AttachmentDataModel[]; onRemoveItem: (id: number) => void; onUpdate: (arr: any[]) => void; } +interface Option { + key: string | number; + title: any; +} + export const TreeAttachments = (props: PropInterface) => { - const [treeData, setTreeData] = useState([]); + const [treeData, setTreeData] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const hours = props.data; diff --git a/src/pages/course/compenents/create.tsx b/src/pages/course/compenents/create.tsx index ac9e37c..c0d1dce 100644 --- a/src/pages/course/compenents/create.tsx +++ b/src/pages/course/compenents/create.tsx @@ -32,6 +32,12 @@ interface PropInterface { onCancel: () => void; } +interface Option { + value: string | number; + title: string; + children?: Option[]; +} + export const CourseCreate: React.FC = ({ cateIds, depIds, @@ -45,22 +51,24 @@ export const CourseCreate: React.FC = ({ const defaultThumb1 = courseDefaultThumbs[0]; const defaultThumb2 = courseDefaultThumbs[1]; const defaultThumb3 = courseDefaultThumbs[2]; - const [loading, setLoading] = useState(true); - const [departments, setDepartments] = useState([]); - const [categories, setCategories] = useState([]); - const [thumb, setThumb] = useState(""); - const [type, setType] = useState("open"); + const [loading, setLoading] = useState(false); + const [departments, setDepartments] = useState([]); + const [categories, setCategories] = useState([]); + const [thumb, setThumb] = useState(""); + const [type, setType] = useState("open"); const [chapterType, setChapterType] = useState(0); - const [chapters, setChapters] = useState([]); - const [hours, setHours] = useState([]); + const [chapters, setChapters] = useState([]); + const [hours, setHours] = useState([]); const [chapterHours, setChapterHours] = useState([]); - const [videoVisible, setVideoVisible] = useState(false); - const [treeData, setTreeData] = useState([]); + const [videoVisible, setVideoVisible] = useState(false); + const [treeData, setTreeData] = useState([]); const [addvideoCurrent, setAddvideoCurrent] = useState(0); - const [showDrop, setShowDrop] = useState(false); - const [attachmentVisible, setAttachmentVisible] = useState(false); - const [attachmentData, setAttachmentData] = useState([]); - const [attachments, setAttachments] = useState([]); + const [showDrop, setShowDrop] = useState(false); + const [attachmentVisible, setAttachmentVisible] = useState(false); + const [attachmentData, setAttachmentData] = useState( + [] + ); + const [attachments, setAttachments] = useState([]); useEffect(() => { if (open) { @@ -91,7 +99,7 @@ export const CourseCreate: React.FC = ({ const getParams = () => { department.departmentList().then((res: any) => { const departments = res.data.departments; - const departCount = res.data.dep_user_count; + const departCount: DepIdsModel = res.data.dep_user_count; if (JSON.stringify(departments) !== "{}") { const new_arr: any = checkArr(departments, 0, departCount); setDepartments(new_arr); @@ -212,6 +220,9 @@ export const CourseCreate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } let dep_ids: any[] = []; if (type === "elective") { dep_ids = values.dep_ids; @@ -220,6 +231,7 @@ export const CourseCreate: React.FC = ({ message.error("请配置课时"); return; } + setLoading(true); course .storeCourse( values.title, @@ -234,8 +246,12 @@ export const CourseCreate: React.FC = ({ attachmentData ) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -438,7 +454,6 @@ export const CourseCreate: React.FC = ({ const keys = [...chapterHours]; keys[index] = arr; setChapterHours(keys); - const data = [...chapters]; const newArr: any = []; for (let i = 0; i < arr.length; i++) { @@ -473,7 +488,11 @@ export const CourseCreate: React.FC = ({ footer={ - @@ -676,7 +695,7 @@ export const CourseCreate: React.FC = ({ form.setFieldsValue({ thumb: url }); }} > - + (推荐尺寸:400x300px)
diff --git a/src/pages/course/compenents/hour-update.tsx b/src/pages/course/compenents/hour-update.tsx index 90d1958..3a86d03 100644 --- a/src/pages/course/compenents/hour-update.tsx +++ b/src/pages/course/compenents/hour-update.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { Button, Drawer, Form, Input, Modal, message } from "antd"; +import { Button, Drawer, Form, Input, Modal, message, Spin } from "antd"; import styles from "./hour-update.module.less"; import { course, courseHour, courseChapter } from "../../../api/index"; import { SelectResource } from "../../../compenents"; @@ -20,15 +20,17 @@ export const CourseHourUpdate: React.FC = ({ onCancel, }) => { const [form] = Form.useForm(); + const [init, setInit] = useState(true); const [chapterType, setChapterType] = useState(0); - const [chapters, setChapters] = useState([]); - const [hours, setHours] = useState([]); + const [chapters, setChapters] = useState([]); + const [hours, setHours] = useState([]); const [chapterHours, setChapterHours] = useState([]); const [videoVisible, setVideoVisible] = useState(false); - const [treeData, setTreeData] = useState([]); + const [treeData, setTreeData] = useState([]); const [addvideoCurrent, setAddvideoCurrent] = useState(0); useEffect(() => { + setInit(true); if (id === 0) { return; } @@ -77,6 +79,7 @@ export const CourseHourUpdate: React.FC = ({ setHours([]); } } + setInit(false); }); }; @@ -231,7 +234,7 @@ export const CourseHourUpdate: React.FC = ({ const arr = [...chapters]; if (arr[index].id) { courseChapter - .updateCourseChapter(id, arr[index].id, value, arr.length) + .updateCourseChapter(id, Number(arr[index].id), value, arr.length) .then((res: any) => { console.log("ok"); getDetail(); @@ -259,7 +262,7 @@ export const CourseHourUpdate: React.FC = ({ onOk() { if (arr[index].id) { courseChapter - .destroyCourseChapter(id, arr[index].id) + .destroyCourseChapter(id, Number(arr[index].id)) .then((res: any) => { console.log("ok"); getDetail(); @@ -373,117 +376,129 @@ export const CourseHourUpdate: React.FC = ({ } }} /> -
+ +
+ )} +
- {chapterType === 0 && ( -
- -
- -
-
-
- {treeData.length === 0 && ( - - 请点击上方按钮添加课时 - - )} - {treeData.length > 0 && ( - { - delHour(id); - }} - onUpdate={(arr: any[]) => { - transHour(arr); - }} - /> - )} -
-
- )} - {chapterType === 1 && ( -
- {chapters.length > 0 && - chapters.map((item: any, index: number) => { - return ( -
+ {chapterType === 0 && ( +
+ +
+ - -
-
- {item.hours.length === 0 && ( - - 请点击上方按钮添加课时 - - )} - {item.hours.length > 0 && ( - { - delChapterHour(index, id); - }} - onUpdate={(arr: any[]) => { - transChapterHour(index, arr); - }} - /> - )} -
-
- ); - })} - -
- + 添加课时 + +
+
+
+ {treeData.length === 0 && ( + + 请点击上方按钮添加课时 + + )} + {treeData.length > 0 && ( + { + delHour(id); + }} + onUpdate={(arr: any[]) => { + transHour(arr); + }} + /> + )}
- -
- )} - +
+ )} + {chapterType === 1 && ( +
+ {chapters.length > 0 && + chapters.map((item: any, index: number) => { + return ( +
+
+
+ 章节{index + 1}: +
+ { + setChapterName(index, e.target.value); + }} + onBlur={(e) => { + saveChapterName(index, e.target.value); + }} + placeholder="请在此处输入章节名称" + allowClear + /> + + +
+
+ {item.hours.length === 0 && ( + + 请点击上方按钮添加课时 + + )} + {item.hours.length > 0 && ( + { + delChapterHour(index, id); + }} + onUpdate={(arr: any[]) => { + transChapterHour(index, arr); + }} + /> + )} +
+
+ ); + })} + +
+ +
+
+
+ )} + +
) : null} diff --git a/src/pages/course/compenents/hours.tsx b/src/pages/course/compenents/hours.tsx index c8e7c34..8765df8 100644 --- a/src/pages/course/compenents/hours.tsx +++ b/src/pages/course/compenents/hours.tsx @@ -3,18 +3,18 @@ import { useState, useEffect } from "react"; import type { DataNode, TreeProps } from "antd/es/tree"; interface Option { - id: number; - name: string; + key: string | number; + title: any; } interface PropInterface { - data: Option[]; + data: CourseHourModel[]; onRemoveItem: (id: number) => void; onUpdate: (arr: any[]) => void; } export const TreeHours = (props: PropInterface) => { - const [treeData, setTreeData] = useState([]); + const [treeData, setTreeData] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const hours = props.data; diff --git a/src/pages/course/compenents/update.tsx b/src/pages/course/compenents/update.tsx index 60356ab..af05403 100644 --- a/src/pages/course/compenents/update.tsx +++ b/src/pages/course/compenents/update.tsx @@ -6,6 +6,7 @@ import { Drawer, Form, TreeSelect, + DatePicker, Input, message, Image, @@ -15,6 +16,8 @@ import styles from "./update.module.less"; import { useSelector } from "react-redux"; import { course, department } from "../../../api/index"; import { UploadImageButton } from "../../../compenents"; +import dayjs from "dayjs"; +import moment from "moment"; interface PropInterface { id: number; @@ -22,6 +25,12 @@ interface PropInterface { onCancel: () => void; } +interface Option { + value: string | number; + title: string; + children?: Option[]; +} + export const CourseUpdate: React.FC = ({ id, open, @@ -35,11 +44,11 @@ export const CourseUpdate: React.FC = ({ const defaultThumb1 = courseDefaultThumbs[0]; const defaultThumb2 = courseDefaultThumbs[1]; const defaultThumb3 = courseDefaultThumbs[2]; - const [loading, setLoading] = useState(true); - const [departments, setDepartments] = useState([]); - const [categories, setCategories] = useState([]); - const [thumb, setThumb] = useState(""); - const [type, setType] = useState("open"); + const [loading, setLoading] = useState(false); + const [departments, setDepartments] = useState([]); + const [categories, setCategories] = useState([]); + const [thumb, setThumb] = useState(""); + const [type, setType] = useState("open"); useEffect(() => { setInit(true); @@ -87,10 +96,14 @@ export const CourseUpdate: React.FC = ({ type: type, short_desc: res.data.course.short_desc, hasChapter: chapterType, + published_at: res.data.course.published_at + ? dayjs(res.data.course.published_at) + : "", }); setType(type); setThumb(res.data.course.thumb); setInit(false); + console.log(dayjs(res.data.course.published_at, "YYYY-MM-DD HH:mm:ss")); }); }; @@ -136,10 +149,14 @@ export const CourseUpdate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } let dep_ids: any[] = []; if (type === "elective") { dep_ids = values.dep_ids; } + setLoading(true); course .updateCourse( id, @@ -151,11 +168,16 @@ export const CourseUpdate: React.FC = ({ dep_ids, values.category_ids, [], - [] + [], + values.published_at ) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -167,6 +189,10 @@ export const CourseUpdate: React.FC = ({ setType(e.target.value); }; + const disabledDate = (current: any) => { + return current && current >= moment().add(0, "days"); // 选择时间要大于等于当前天。若今天不能被选择,去掉等号即可。 + }; + return ( <> {open ? ( @@ -178,7 +204,11 @@ export const CourseUpdate: React.FC = ({ footer={ - @@ -363,7 +393,7 @@ export const CourseUpdate: React.FC = ({ form.setFieldsValue({ thumb: url }); }} > - + (推荐尺寸:400x300px)
@@ -378,6 +408,22 @@ export const CourseUpdate: React.FC = ({ maxLength={200} /> + + + + + +
+ (上架时间越晚,排序越靠前) +
+
+
diff --git a/src/pages/course/index.tsx b/src/pages/course/index.tsx index 5374f6b..42e103c 100644 --- a/src/pages/course/index.tsx +++ b/src/pages/course/index.tsx @@ -12,7 +12,6 @@ import { Dropdown, } from "antd"; import { course } from "../../api"; -// import styles from "./index.module.less"; import { PlusOutlined, DownOutlined, @@ -33,43 +32,46 @@ const { confirm } = Modal; interface DataType { id: React.Key; - title: string; - created_at: string; - thumb: string; charge: number; + class_hour: number; + created_at: string; + is_required: number; is_show: number; + short_desc: string; + thumb: string; + title: string; } const CoursePage = () => { const result = new URLSearchParams(useLocation().search); const navigate = useNavigate(); - const [list, setList] = useState([]); + const [list, setList] = useState([]); const [refresh, setRefresh] = useState(false); const [page, setPage] = useState(1); const [size, setSize] = useState(10); const [total, setTotal] = useState(0); - const [loading, setLoading] = useState(true); - const [category_ids, setCategoryIds] = useState([]); - const [title, setTitle] = useState(""); - const [dep_ids, setDepIds] = useState([]); + const [loading, setLoading] = useState(true); + const [category_ids, setCategoryIds] = useState([]); + const [title, setTitle] = useState(""); + const [dep_ids, setDepIds] = useState([]); const [selLabel, setLabel] = useState( result.get("label") ? String(result.get("label")) : "全部分类" ); const [selDepLabel, setDepLabel] = useState( result.get("label") ? String(result.get("label")) : "全部部门" ); - const [course_category_ids, setCourseCategoryIds] = useState({}); - const [course_dep_ids, setCourseDepIds] = useState({}); - const [categories, setCategories] = useState({}); - const [departments, setDepartments] = useState({}); + const [course_category_ids, setCourseCategoryIds] = + useState({}); + const [course_dep_ids, setCourseDepIds] = useState({}); + const [categories, setCategories] = useState({}); + const [departments, setDepartments] = useState({}); const [tabKey, setTabKey] = useState(result.get("did") ? "2" : "1"); - const [createVisible, setCreateVisible] = useState(false); - const [updateVisible, setUpdateVisible] = useState(false); - const [updateHourVisible, setHourUpdateVisible] = useState(false); - const [updateAttachmentVisible, setUpdateAttachmentVisible] = - useState(false); - const [cid, setCid] = useState(0); + const [createVisible, setCreateVisible] = useState(false); + const [updateVisible, setUpdateVisible] = useState(false); + const [updateHourVisible, setHourUpdateVisible] = useState(false); + const [updateAttachmentVisible, setUpdateAttachmentVisible] = useState(false); + const [cid, setCid] = useState(0); const [cateId, setCateId] = useState(Number(result.get("cid"))); const [did, setDid] = useState(Number(result.get("did"))); @@ -198,8 +200,8 @@ const CoursePage = () => { ), }, { - title: "创建时间", - dataIndex: "created_at", + title: "上架时间", + dataIndex: "published_at", render: (text: string) => {dateFormat(text)}, }, { diff --git a/src/pages/course/user.tsx b/src/pages/course/user.tsx index b175032..6583b6f 100644 --- a/src/pages/course/user.tsx +++ b/src/pages/course/user.tsx @@ -22,30 +22,61 @@ const { confirm } = Modal; interface DataType { id: React.Key; - title: string; - created_at: string; - thumb: string; - charge: number; - is_show: number; + avatar: string; + create_city?: string; + create_ip?: string; + created_at?: string; + credit1?: number; + email: string; + id_card?: string; + is_active?: number; + is_lock?: number; + is_set_password?: number; + is_verify?: number; + login_at?: string; + name: string; + updated_at?: string; + verify_at?: string; } +type UserCourseRecordsModel = { + [key: number]: CourseRecordModel; +}; + +type CourseRecordModel = { + course_id: number; + created_at: string; + finished_at?: string; + finished_count: number; + hour_count: number; + id: number; + is_finished: number; + progress: number; + updated_at: string; + user_id: number; +}; + +type HourCountModel = { + [key: number]: string; +}; + const CourseUserPage = () => { const params = useParams(); const result = new URLSearchParams(useLocation().search); - const [list, setList] = useState([]); - const [course, setCourse] = useState({}); - const [records, setRecords] = useState({}); - const [hourCount, setHourCount] = useState({}); - const [userDepIds, setUserDepIds] = useState({}); - const [departments, setDepartments] = useState({}); + const [list, setList] = useState([]); + const [course, setCourse] = useState(null); + const [records, setRecords] = useState({}); + const [hourCount, setHourCount] = useState({}); + const [userDepIds, setUserDepIds] = useState({}); + const [departments, setDepartments] = useState({}); const [refresh, setRefresh] = useState(false); const [page, setPage] = useState(1); const [size, setSize] = useState(10); const [total, setTotal] = useState(0); - const [loading, setLoading] = useState(true); - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [idCard, setIdCard] = useState(""); + const [loading, setLoading] = useState(true); + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [idCard, setIdCard] = useState(""); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [title, setTitle] = useState(String(result.get("title"))); @@ -95,7 +126,7 @@ const CourseUserPage = () => { {(records[record.id] && records[record.id].finished_count) || 0} /{" "} {(records[record.id] && records[record.id].hour_count) || - course.class_hour} + course?.class_hour} ), }, @@ -116,11 +147,11 @@ const CourseUserPage = () => { }, { title: "学习完成时间", - dataIndex: "finished_at", + dataIndex: "id", render: (_, record: any) => ( <> {records[record.id] ? ( - {dateFormat(records[record.id].finished_at)} + {dateFormat(String(records[record.id].finished_at))} ) : ( - )} diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index d3dfed7..f09580a 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -12,10 +12,38 @@ import { dashboard } from "../../api/index"; import { timeFormat } from "../../utils/index"; import * as echarts from "echarts"; +type BasicDataModel = { + admin_user_total: number; + course_total: number; + department_total: number; + resource_category_total: number; + resource_image_total: number; + resource_video_total: number; + user_learn_today: number; + user_learn_top10?: Top10Model[]; + user_learn_top10_users?: Top10UserModel; + user_learn_yesterday: number; + user_today: number; + user_total: number; + user_yesterday: number; + version: string; +}; + +type Top10Model = { + created_date: string; + duration: number; + id: number; + user_id: number; +}; + +type Top10UserModel = { + [key: number]: UserModel; +}; + const DashboardPage = () => { let chartRef = useRef(null); const navigate = useNavigate(); - const [basicData, setBasicData] = useState([]); + const [basicData, setBasicData] = useState(null); const getData = () => { dashboard.dashboardList().then((res: any) => { @@ -164,31 +192,35 @@ const DashboardPage = () => {
今日学习学员
- {basicData.user_learn_today} -
-
- 较昨日 - {compareNum( - basicData.user_learn_today, - basicData.user_learn_yesterday - )} + {basicData?.user_learn_today}
+ {basicData && ( +
+ 较昨日 + {compareNum( + basicData.user_learn_today, + basicData.user_learn_yesterday + )} +
+ )}
总学员数
-
{basicData.user_total}
-
- 较昨日 - {compareNum(basicData.user_today, 0)} -
+
{basicData?.user_total}
+ {basicData && ( +
+ 较昨日 + {compareNum(basicData.user_today, 0)} +
+ )}
线上课数
-
{basicData.course_total}
+
{basicData?.course_total}
@@ -249,7 +281,7 @@ const DashboardPage = () => {
今日学习排行
- {basicData.user_learn_top10 && ( + {basicData?.user_learn_top10 && (
@@ -258,15 +290,16 @@ const DashboardPage = () => { src={iconN1} alt="" /> - {basicData.user_learn_top10[0] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[0].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[0] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[0].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[0] && (
@@ -283,15 +316,16 @@ const DashboardPage = () => { src={iconN2} alt="" /> - {basicData.user_learn_top10[1] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[1].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[1] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[1].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[1] && (
@@ -308,15 +342,16 @@ const DashboardPage = () => { src={iconN3} alt="" /> - {basicData.user_learn_top10[2] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[2].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[2] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[2].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[2] && (
@@ -329,15 +364,16 @@ const DashboardPage = () => {
4
- {basicData.user_learn_top10[3] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[3].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[3] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[3].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[3] && (
@@ -350,15 +386,16 @@ const DashboardPage = () => {
5
- {basicData.user_learn_top10[4] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[4].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[4] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[4].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[4] && (
@@ -370,20 +407,21 @@ const DashboardPage = () => {
)} - {basicData.user_learn_top10 && ( + {basicData?.user_learn_top10 && (
6
- {basicData.user_learn_top10[5] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[5].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[5] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[5].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[5] && (
@@ -396,15 +434,16 @@ const DashboardPage = () => {
7
- {basicData.user_learn_top10[6] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[6].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[6] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[6].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[6] && (
@@ -417,15 +456,16 @@ const DashboardPage = () => {
8
- {basicData.user_learn_top10[7] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[7].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[7] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[7].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[7] && (
@@ -438,15 +478,16 @@ const DashboardPage = () => {
9
- {basicData.user_learn_top10[8] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[8].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[8] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[8].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[8] && (
@@ -459,15 +500,16 @@ const DashboardPage = () => {
10
- {basicData.user_learn_top10[9] && ( -
- { - basicData.user_learn_top10_users[ - basicData.user_learn_top10[9].user_id - ]?.name - } -
- )} + {basicData.user_learn_top10[9] && + basicData.user_learn_top10_users && ( +
+ { + basicData.user_learn_top10_users[ + basicData.user_learn_top10[9].user_id + ]?.name + } +
+ )}
{basicData.user_learn_top10[9] && (
@@ -489,7 +531,7 @@ const DashboardPage = () => {
部门数
- {basicData.department_total} + {basicData?.department_total}
@@ -497,7 +539,7 @@ const DashboardPage = () => {
分类数
- {basicData.resource_category_total} + {basicData?.resource_category_total}
@@ -505,7 +547,7 @@ const DashboardPage = () => {
管理员
- {basicData.admin_user_total} + {basicData?.admin_user_total}
diff --git a/src/pages/department/compenents/create.tsx b/src/pages/department/compenents/create.tsx index ca8ca80..9e0bce9 100644 --- a/src/pages/department/compenents/create.tsx +++ b/src/pages/department/compenents/create.tsx @@ -19,7 +19,7 @@ export const DepartmentCreate: React.FC = ({ onCancel, }) => { const [form] = Form.useForm(); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [departments, setDepartments] = useState([]); const [parent_id, setParentId] = useState(0); @@ -78,11 +78,19 @@ export const DepartmentCreate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } + setLoading(true); department .storeDepartment(values.name, parent_id || 0, 0) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -115,6 +123,7 @@ export const DepartmentCreate: React.FC = ({ onOk={() => form.submit()} onCancel={() => onCancel()} maskClosable={false} + okButtonProps={{ loading: loading }} >
= ({ }) => { const [form] = Form.useForm(); const [init, setInit] = useState(true); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [departments, setDepartments] = useState([]); const [parent_id, setParentId] = useState(0); const [sort, setSort] = useState(0); @@ -93,11 +93,19 @@ export const DepartmentUpdate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } + setLoading(true); department .updateDepartment(id, values.name, parent_id || 0, sort) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -142,6 +150,7 @@ export const DepartmentUpdate: React.FC = ({ onOk={() => form.submit()} onCancel={() => onCancel()} maskClosable={false} + okButtonProps={{ loading: loading }} > {init && (
diff --git a/src/pages/department/index.tsx b/src/pages/department/index.tsx index be02a6a..7a4ad65 100644 --- a/src/pages/department/index.tsx +++ b/src/pages/department/index.tsx @@ -23,15 +23,18 @@ const DepartmentPage = () => { const permissions = useSelector( (state: any) => state.loginUser.value.permissions ); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(true); const [refresh, setRefresh] = useState(false); - const [treeData, setTreeData] = useState([]); - const [selectKey, setSelectKey] = useState([]); + const [treeData, setTreeData] = useState([]); + const [selectKey, setSelectKey] = useState([]); - const [createVisible, setCreateVisible] = useState(false); - const [updateVisible, setUpdateVisible] = useState(false); + const [createVisible, setCreateVisible] = useState(false); + const [updateVisible, setUpdateVisible] = useState(false); const [did, setDid] = useState(0); const [modal, contextHolder] = Modal.useModal(); + const ldapEnabled = useSelector( + (state: any) => state.systemConfig.value["ldap-enabled"] + ); const onSelect = (selectedKeys: any, info: any) => { setSelectKey(selectedKeys); @@ -45,13 +48,13 @@ const DepartmentPage = () => { }; useEffect(() => { + setLoading(true); getData(); }, [refresh, permissions]); const getData = () => { - setLoading(true); department.departmentList().then((res: any) => { - const departments = res.data.departments; + const departments: DepartmentsBoxModel = res.data.departments; if (JSON.stringify(departments) !== "{}") { const new_arr: Option[] = checkArr(departments, 0); setTreeData(new_arr); @@ -61,89 +64,112 @@ const DepartmentPage = () => { }); }; - const checkArr = (departments: any[], id: number) => { + const checkArr = (departments: DepartmentsBoxModel, id: number) => { const arr = []; for (let i = 0; i < departments[id].length; i++) { if (!departments[departments[id][i].id]) { - arr.push({ - title: ( - <> -
{departments[id][i].name}
-
- - - - {through("department-cud") && ( - <> + if (ldapEnabled) { + arr.push({ + title: ( + <> +
{departments[id][i].name}
+ + ), + key: departments[id][i].id, + }); + } else { + arr.push({ + title: ( + <> +
{departments[id][i].name}
+
+ { - setDid(departments[id][i].id); - setUpdateVisible(true); - }} /> - - removeItem( - departments[id][i].id, - departments[id][i].name - ) - } - /> - - )} -
- - ), - key: departments[id][i].id, - }); + + {through("department-cud") && ( + <> + { + setDid(departments[id][i].id); + setUpdateVisible(true); + }} + /> + + removeItem( + departments[id][i].id, + departments[id][i].name + ) + } + /> + + )} +
+ + ), + key: departments[id][i].id, + }); + } } else { const new_arr: Option[] = checkArr(departments, departments[id][i].id); - arr.push({ - title: ( - <> -
{departments[id][i].name}
-
- - - - {through("department-cud") && ( - <> + if (ldapEnabled) { + arr.push({ + title: ( + <> +
{departments[id][i].name}
+ + ), + key: departments[id][i].id, + children: new_arr, + }); + } else { + arr.push({ + title: ( + <> +
{departments[id][i].name}
+
+ { - setDid(departments[id][i].id); - setUpdateVisible(true); - }} /> - - removeItem( - departments[id][i].id, - departments[id][i].name - ) - } - /> - - )} -
- - ), - key: departments[id][i].id, - children: new_arr, - }); + + {through("department-cud") && ( + <> + { + setDid(departments[id][i].id); + setUpdateVisible(true); + }} + /> + + removeItem( + departments[id][i].id, + departments[id][i].name + ) + } + /> + + )} +
+ + ), + key: departments[id][i].id, + children: new_arr, + }); + } } } return arr; @@ -365,22 +391,29 @@ const DepartmentPage = () => { return ( <> -
- {contextHolder} -
- } - p="department-cud" - onClick={() => setCreateVisible(true)} - disabled={null} - /> + {!ldapEnabled && ( +
+ {contextHolder} +
+ } + p="department-cud" + onClick={() => setCreateVisible(true)} + disabled={null} + /> +
-
+ )}
-
+ {loading && ( +
+ +
+ )} +
{treeData.length > 0 && ( { blockNode onDragEnter={onDragEnter} onDrop={onDrop} - defaultExpandAll={true} switcherIcon={} /> )} diff --git a/src/pages/init/index.tsx b/src/pages/init/index.tsx index cf99b4f..b71db95 100644 --- a/src/pages/init/index.tsx +++ b/src/pages/init/index.tsx @@ -19,6 +19,7 @@ const InitPage = (props: Props) => { if (props.configData) { let config: SystemConfigStoreInterface = { + "ldap-enabled": props.configData["ldap-enabled"], systemName: props.configData["system.name"], systemLogo: props.configData["system.logo"], systemApiUrl: props.configData["system.api_url"], diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index e5d5fc8..a7918bd 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; import styles from "./index.module.less"; import { Input, Button, message } from "antd"; import { login as loginApi, system } from "../../api/index"; @@ -59,6 +59,7 @@ const LoginPage = () => { const getSystemConfig = async () => { let res: any = await system.getSystemConfig(); let data: SystemConfigStoreInterface = { + "ldap-enabled": res.data["ldap-enabled"], systemName: res.data["system.name"], systemLogo: res.data["system.logo"], systemApiUrl: res.data["system.api_url"], diff --git a/src/pages/member/compenents/create.tsx b/src/pages/member/compenents/create.tsx index f5deb66..e5c588f 100644 --- a/src/pages/member/compenents/create.tsx +++ b/src/pages/member/compenents/create.tsx @@ -24,7 +24,7 @@ export const MemberCreate: React.FC = ({ onCancel, }) => { const [form] = Form.useForm(); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [departments, setDepartments] = useState([]); const memberDefaultAvatar = useSelector( (state: any) => state.systemConfig.value.memberDefaultAvatar @@ -80,10 +80,14 @@ export const MemberCreate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } if (values.idCard !== "" && !ValidataCredentials(values.idCard)) { message.error("请输入正确的身份证号!"); return; } + setLoading(true); user .storeUser( values.email, @@ -94,8 +98,12 @@ export const MemberCreate: React.FC = ({ values.dep_ids ) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -115,6 +123,7 @@ export const MemberCreate: React.FC = ({ onOk={() => form.submit()} onCancel={() => onCancel()} maskClosable={false} + okButtonProps={{ loading: loading }} >
= ({ rules={[{ required: true, message: "请输入登录邮箱!" }]} > = ({ rules={[{ required: true, message: "请输入登录密码!" }]} > = ({ }) => { const [form] = Form.useForm(); const [init, setInit] = useState(true); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [departments, setDepartments] = useState([]); const memberDefaultAvatar = useSelector( (state: any) => state.systemConfig.value.memberDefaultAvatar @@ -105,11 +105,14 @@ export const MemberUpdate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } if (values.idCard !== "" && !ValidataCredentials(values.idCard)) { message.error("请输入正确的身份证号!"); return; } - + setLoading(true); user .updateUser( id, @@ -121,8 +124,12 @@ export const MemberUpdate: React.FC = ({ values.dep_ids ) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -144,6 +151,7 @@ export const MemberUpdate: React.FC = ({ onOk={() => form.submit()} onCancel={() => onCancel()} maskClosable={false} + okButtonProps={{ loading: loading }} > {init && (
@@ -202,6 +210,7 @@ export const MemberUpdate: React.FC = ({ rules={[{ required: true, message: "请输入登录邮箱!" }]} > = ({ { const result = new URLSearchParams(useLocation().search); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [size, setSize] = useState(10); - const [list, setList] = useState([]); + const [list, setList] = useState([]); const [total, setTotal] = useState(0); const [refresh, setRefresh] = useState(false); - const [nickname, setNickname] = useState(""); - const [email, setEmail] = useState(""); - const [dep_ids, setDepIds] = useState([]); + const [nickname, setNickname] = useState(""); + const [email, setEmail] = useState(""); + const [dep_ids, setDepIds] = useState([]); const [selLabel, setLabel] = useState( result.get("label") ? String(result.get("label")) : "全部部门" ); - const [createVisible, setCreateVisible] = useState(false); - const [updateVisible, setUpdateVisible] = useState(false); - const [mid, setMid] = useState(0); - const [user_dep_ids, setUserDepIds] = useState({}); - const [departments, setDepartments] = useState({}); + const [createVisible, setCreateVisible] = useState(false); + const [updateVisible, setUpdateVisible] = useState(false); + const [mid, setMid] = useState(0); + const [user_dep_ids, setUserDepIds] = useState({}); + const [departments, setDepartments] = useState({}); const [did, setDid] = useState(Number(result.get("did"))); + const ldapEnabled = useSelector( + (state: any) => state.systemConfig.value["ldap-enabled"] + ); useEffect(() => { setDid(Number(result.get("did"))); @@ -171,19 +184,23 @@ const MemberPage = () => { disabled={null} /> -
- - - + {!ldapEnabled && ( + <> +
+ + + + + )} ); }, @@ -288,16 +305,18 @@ const MemberPage = () => {
- } - p="user-store" - onClick={() => setCreateVisible(true)} - disabled={null} - /> - {dep_ids.length === 0 && ( + {!ldapEnabled && ( + } + p="user-store" + onClick={() => setCreateVisible(true)} + disabled={null} + /> + )} + {!ldapEnabled && dep_ids.length === 0 && ( { let chartRef = useRef(null); const navigate = useNavigate(); const result = new URLSearchParams(useLocation().search); - const [loading2, setLoading2] = useState(false); - const [list2, setList2] = useState([]); + const [loading2, setLoading2] = useState(false); + const [list2, setList2] = useState([]); const [courses, setCourses] = useState({}); - const [deps, setDeps] = useState([]); - const [depValue, setDepValue] = useState(0); - const [currentCourses, setCurrentCourses] = useState([]); - const [openCourses, setOpenCourses] = useState([]); - const [records, setRecords] = useState({}); - const [hourCount, setHourCount] = useState({}); + const [deps, setDeps] = useState([]); + const [depValue, setDepValue] = useState(0); + const [currentCourses, setCurrentCourses] = useState([]); + const [openCourses, setOpenCourses] = useState([]); + const [records, setRecords] = useState({}); + const [hourCount, setHourCount] = useState({}); const [total2, setTotal2] = useState(0); const [refresh2, setRefresh2] = useState(false); const [uid, setUid] = useState(Number(result.get("id"))); const [userName, setUserName] = useState(String(result.get("name"))); const [visiable, setVisiable] = useState(false); - const [courseId, setcourseId] = useState(0); + const [courseId, setcourseId] = useState(0); useEffect(() => { setUid(Number(result.get("id"))); @@ -157,7 +195,7 @@ const MemberLearnPage = () => { setHourCount(res.data.user_course_hour_count); setRecords(res.data.user_course_records); if (res.data.departments.length > 0) { - let box: any = []; + let box: OptionModel[] = []; res.data.departments.map((item: any) => { box.push({ label: item.name, @@ -223,7 +261,7 @@ const MemberLearnPage = () => { render: (_, record: any) => ( <> {records[record.id] ? ( - {dateFormat(records[record.id].finished_at)} + {dateFormat(String(records[record.id].finished_at))} ) : ( - )} @@ -232,7 +270,6 @@ const MemberLearnPage = () => { }, { title: "学习进度", - dataIndex: "is_finished", render: (_, record: any) => ( <> {records[record.id] ? ( diff --git a/src/pages/resource/courseware/index.tsx b/src/pages/resource/courseware/index.tsx index fe49a49..50752b0 100644 --- a/src/pages/resource/courseware/index.tsx +++ b/src/pages/resource/courseware/index.tsx @@ -9,11 +9,9 @@ import { 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 { ExclamationCircleFilled } from "@ant-design/icons"; import type { ColumnsType } from "antd/es/table"; import { dateFormat } from "../../../utils/index"; import { TreeCategory, UploadCoursewareButton } from "../../../compenents"; @@ -22,34 +20,45 @@ import { CoursewareUpdateDialog } from "./compenents/update-dialog"; const { confirm } = Modal; interface DataType { + admin_id: number; + created_at: string; + disk: string; + extension: string; + file_id: string; id: React.Key; name: string; - created_at: string; + parent_id: number; + path: string; + size: number; type: string; - number: number; + url: string; } +type AdminUsersModel = { + [key: number]: string; +}; + const ResourceCoursewarePage = () => { const result = new URLSearchParams(useLocation().search); - const [list, setList] = useState([]); - const [adminUsers, setAdminUsers] = useState({}); - const [existingTypes, setExistingTypes] = useState([]); + const [list, setList] = useState([]); + const [adminUsers, setAdminUsers] = useState({}); + const [existingTypes, setExistingTypes] = useState([]); const [refresh, setRefresh] = useState(false); const [page, setPage] = useState(1); const [size, setSize] = useState(10); const [total, setTotal] = useState(0); - const [loading, setLoading] = useState(true); - const [category_ids, setCategoryIds] = useState([]); + const [loading, setLoading] = useState(true); + const [category_ids, setCategoryIds] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [type, setType] = useState("WORD,EXCEL,PPT,PDF,TXT,RAR,ZIP"); - const [title, setTitle] = useState(""); - const [multiConfig, setMultiConfig] = useState(false); + const [title, setTitle] = useState(""); + const [multiConfig, setMultiConfig] = useState(false); const [selLabel, setLabel] = useState( result.get("label") ? String(result.get("label")) : "全部课件" ); const [cateId, setCateId] = useState(Number(result.get("cid"))); const [updateId, setUpdateId] = useState(0); - const [updateVisible, setUpdateVisible] = useState(false); + const [updateVisible, setUpdateVisible] = useState(false); const types = [ { label: "全部", value: "WORD,EXCEL,PPT,PDF,TXT,RAR,ZIP" }, { label: "WORD", value: "WORD" }, @@ -113,26 +122,22 @@ const ResourceCoursewarePage = () => { { title: "课件格式", dataIndex: "type", - width: 204, render: (type: string) => {type}, }, { title: "课件大小", dataIndex: "size", - width: 204, render: (size: number) => {(size / 1024 / 1024).toFixed(2)}M, }, { title: "创建人", dataIndex: "admin_id", - width: 204, render: (text: number) => JSON.stringify(adminUsers) !== "{}" && {adminUsers[text]}, }, { title: "创建时间", dataIndex: "created_at", - width: 204, render: (text: string) => {dateFormat(text)}, }, { diff --git a/src/pages/resource/images/index.tsx b/src/pages/resource/images/index.tsx index bc4f09f..dd1a7ed 100644 --- a/src/pages/resource/images/index.tsx +++ b/src/pages/resource/images/index.tsx @@ -14,7 +14,7 @@ import { resource } from "../../../api"; import { useLocation } from "react-router-dom"; import styles from "./index.module.less"; import { UploadImageSub } from "../../../compenents/upload-image-button/upload-image-sub"; -import { TreeCategory, PerButton } from "../../../compenents"; +import { TreeCategory } from "../../../compenents"; import { ExclamationCircleFilled, CheckOutlined } from "@ant-design/icons"; const { confirm } = Modal; @@ -39,14 +39,14 @@ const ResourceImagesPage = () => { const [page, setPage] = useState(1); const [size, setSize] = useState(32); const [total, setTotal] = useState(0); - const [category_ids, setCategoryIds] = useState([]); - const [selectKey, setSelectKey] = useState([]); - const [visibleArr, setVisibleArr] = useState([]); - const [hoverArr, setHoverArr] = useState([]); + const [category_ids, setCategoryIds] = useState([]); + const [selectKey, setSelectKey] = useState([]); + const [visibleArr, setVisibleArr] = useState([]); + const [hoverArr, setHoverArr] = useState([]); const [selLabel, setLabel] = useState( result.get("label") ? String(result.get("label")) : "全部图片" ); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); const [cateId, setCateId] = useState(Number(result.get("cid"))); useEffect(() => { @@ -96,7 +96,7 @@ const ResourceImagesPage = () => { .then((res: any) => { setTotal(res.data.result.total); setImageList(res.data.result.data); - let data = res.data.result.data; + let data: ImageItem[] = res.data.result.data; let arr = []; for (let i = 0; i < data.length; i++) { arr.push(false); diff --git a/src/pages/resource/resource-category/compenents/create.tsx b/src/pages/resource/resource-category/compenents/create.tsx index f22632f..e6563db 100644 --- a/src/pages/resource/resource-category/compenents/create.tsx +++ b/src/pages/resource/resource-category/compenents/create.tsx @@ -19,7 +19,7 @@ export const ResourceCategoryCreate: React.FC = ({ onCancel, }) => { const [form] = Form.useForm(); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [categories, setCategories] = useState([]); const [parent_id, setParentId] = useState(0); @@ -78,11 +78,19 @@ export const ResourceCategoryCreate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } + setLoading(true); resourceCategory .storeResourceCategory(values.name, parent_id || 0, 0) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -115,6 +123,7 @@ export const ResourceCategoryCreate: React.FC = ({ width={416} onOk={() => form.submit()} onCancel={() => onCancel()} + okButtonProps={{ loading: loading }} >
= ({ }) => { const [form] = Form.useForm(); const [init, setInit] = useState(true); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [categories, setCategories] = useState([]); const [parent_id, setParentId] = useState(0); const [sort, setSort] = useState(0); @@ -91,11 +91,19 @@ export const ResourceCategoryUpdate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } + setLoading(true); resourceCategory .updateResourceCategory(id, values.name, parent_id || 0, sort) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -140,6 +148,7 @@ export const ResourceCategoryUpdate: React.FC = ({ onOk={() => form.submit()} onCancel={() => onCancel()} maskClosable={false} + okButtonProps={{ loading: loading }} > {init && (
diff --git a/src/pages/resource/resource-category/index.tsx b/src/pages/resource/resource-category/index.tsx index 1566605..23991af 100644 --- a/src/pages/resource/resource-category/index.tsx +++ b/src/pages/resource/resource-category/index.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { Button, Tree, Modal, message, Tooltip } from "antd"; +import { Button, Tree, Modal, message, Tooltip, Spin } from "antd"; // import styles from "./index.module.less"; import { PlusOutlined, ExclamationCircleFilled } from "@ant-design/icons"; import { resourceCategory } from "../../../api/index"; @@ -24,15 +24,17 @@ const ResourceCategoryPage = () => { (state: any) => state.loginUser.value.permissions ); const [loading, setLoading] = useState(true); + const [init, setInit] = useState(true); const [refresh, setRefresh] = useState(false); - const [treeData, setTreeData] = useState([]); - const [selectKey, setSelectKey] = useState([]); + const [treeData, setTreeData] = useState([]); + const [selectKey, setSelectKey] = useState([]); const [createVisible, setCreateVisible] = useState(false); const [updateVisible, setUpdateVisible] = useState(false); const [cid, setCid] = useState(0); const [modal, contextHolder] = Modal.useModal(); useEffect(() => { + setInit(true); getData(); }, [refresh, permissions]); @@ -50,16 +52,17 @@ const ResourceCategoryPage = () => { const getData = () => { setLoading(true); resourceCategory.resourceCategoryList().then((res: any) => { - const categories = res.data.categories; + const categories: CategoriesBoxModel = res.data.categories; if (JSON.stringify(categories) !== "{}") { const new_arr: Option[] = checkArr(categories, 0); setTreeData(new_arr); } setLoading(false); + setInit(false); }); }; - const checkArr = (categories: any[], id: number) => { + const checkArr = (categories: CategoriesBoxModel, id: number) => { const arr = []; for (let i = 0; i < categories[id].length; i++) { if (!categories[categories[id][i].id]) { @@ -390,7 +393,12 @@ const ResourceCategoryPage = () => {
-
+ {init && ( +
+ +
+ )} +
{treeData.length > 0 && ( { blockNode onDragEnter={onDragEnter} onDrop={onDrop} - defaultExpandAll={true} switcherIcon={} /> )} diff --git a/src/pages/resource/videos/compenents/update-dialog/index.tsx b/src/pages/resource/videos/compenents/update-dialog/index.tsx index 7b424ea..efc17e0 100644 --- a/src/pages/resource/videos/compenents/update-dialog/index.tsx +++ b/src/pages/resource/videos/compenents/update-dialog/index.tsx @@ -9,6 +9,12 @@ interface PropInterface { onSuccess: () => void; } +interface Option { + value: string | number; + title: string; + children?: Option[]; +} + export const VideosUpdateDialog: React.FC = ({ id, open, @@ -16,9 +22,9 @@ export const VideosUpdateDialog: React.FC = ({ onSuccess, }) => { const [form] = Form.useForm(); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(true); const [init, setInit] = useState(true); - const [categories, setCategories] = useState([]); + const [categories, setCategories] = useState([]); useEffect(() => { setInit(true); @@ -33,7 +39,7 @@ export const VideosUpdateDialog: React.FC = ({ const getCategory = () => { resourceCategory.resourceCategoryList().then((res: any) => { - const categories = res.data.categories; + const categories: CategoriesBoxModel = res.data.categories; if (JSON.stringify(categories) !== "{}") { const new_arr: any = checkArr(categories, 0, null); setCategories(new_arr); @@ -52,7 +58,11 @@ export const VideosUpdateDialog: React.FC = ({ }); }; - const checkArr = (departments: any[], id: number, counts: any) => { + const checkArr = ( + departments: CategoriesBoxModel, + id: number, + counts: any + ) => { const arr = []; for (let i = 0; i < departments[id].length; i++) { if (!departments[departments[id][i].id]) { diff --git a/src/pages/resource/videos/compenents/video-play-dialog/index.tsx b/src/pages/resource/videos/compenents/video-play-dialog/index.tsx index df7d064..42fbdf4 100644 --- a/src/pages/resource/videos/compenents/video-play-dialog/index.tsx +++ b/src/pages/resource/videos/compenents/video-play-dialog/index.tsx @@ -17,7 +17,7 @@ export const VideoPlayDialog: React.FC = ({ open, onCancel, }) => { - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(true); useEffect(() => { if (open && url) { diff --git a/src/pages/resource/videos/index.tsx b/src/pages/resource/videos/index.tsx index 4376509..b53db98 100644 --- a/src/pages/resource/videos/index.tsx +++ b/src/pages/resource/videos/index.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from "react"; import { Modal, Table, message, Space, Dropdown, 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"; @@ -16,32 +15,54 @@ const { confirm } = Modal; interface DataType { id: React.Key; - name: string; + admin_id: number; created_at: string; disk: string; + extension: string; + file_id: string; + name: string; + parent_id: number; + path: string; + size: number; + type: string; + url: string; } +type VideosExtraModel = { + [key: number]: VideoModel; +}; + +type VideoModel = { + duration: number; + poster: string; + rid: number; +}; + +type AdminUsersModel = { + [key: number]: string; +}; + const ResourceVideosPage = () => { const result = new URLSearchParams(useLocation().search); - const [videoList, setVideoList] = useState([]); - const [videosExtra, setVideoExtra] = useState([]); - const [adminUsers, setAdminUsers] = useState({}); + const [videoList, setVideoList] = useState([]); + const [videosExtra, setVideoExtra] = useState({}); + const [adminUsers, setAdminUsers] = useState({}); const [refresh, setRefresh] = useState(false); const [page, setPage] = useState(1); const [size, setSize] = useState(10); const [total, setTotal] = useState(0); - const [loading, setLoading] = useState(true); - const [category_ids, setCategoryIds] = useState([]); + const [loading, setLoading] = useState(true); + const [category_ids, setCategoryIds] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selLabel, setLabel] = useState( result.get("label") ? String(result.get("label")) : "全部视频" ); const [cateId, setCateId] = useState(Number(result.get("cid"))); - const [updateVisible, setUpdateVisible] = useState(false); - const [playVisible, setPlayeVisible] = useState(false); - const [multiConfig, setMultiConfig] = useState(false); + const [updateVisible, setUpdateVisible] = useState(false); + const [playVisible, setPlayeVisible] = useState(false); + const [multiConfig, setMultiConfig] = useState(false); const [updateId, setUpdateId] = useState(0); - const [playUrl, setPlayUrl] = useState(""); + const [playUrl, setPlayUrl] = useState(""); useEffect(() => { setCateId(Number(result.get("cid"))); @@ -72,20 +93,22 @@ const ResourceVideosPage = () => { { title: "视频时长", dataIndex: "id", - render: (id: string) => ( + render: (id: number) => ( ), }, { title: "创建人", dataIndex: "admin_id", - render: (text: number) => - JSON.stringify(adminUsers) !== "{}" && {adminUsers[text]}, + render: (admin_id: number) => + JSON.stringify(adminUsers) !== "{}" && ( + {adminUsers[admin_id]} + ), }, { title: "创建时间", dataIndex: "created_at", - render: (text: string) => {dateFormat(text)}, + render: (created_at: string) => {dateFormat(created_at)}, }, { title: "操作", diff --git a/src/pages/system/administrator/compenents/create.tsx b/src/pages/system/administrator/compenents/create.tsx index 392c4a8..ffae3ed 100644 --- a/src/pages/system/administrator/compenents/create.tsx +++ b/src/pages/system/administrator/compenents/create.tsx @@ -10,6 +10,11 @@ interface PropInterface { onCancel: () => void; } +type selRoleModel = { + label: string; + value: number; +}; + export const SystemAdministratorCreate: React.FC = ({ roleId, refresh, @@ -17,8 +22,8 @@ export const SystemAdministratorCreate: React.FC = ({ onCancel, }) => { const [form] = Form.useForm(); - const [loading, setLoading] = useState(true); - const [roles, setRoles] = useState([]); + const [loading, setLoading] = useState(false); + const [roles, setRoles] = useState([]); useEffect(() => { if (open) { @@ -43,7 +48,7 @@ export const SystemAdministratorCreate: React.FC = ({ const getParams = () => { adminUser.createAdminUser().then((res: any) => { const arr = []; - let roles = res.data.roles; + let roles: RoleModel[] = res.data.roles; for (let i = 0; i < roles.length; i++) { arr.push({ label: roles[i].name, @@ -55,6 +60,10 @@ export const SystemAdministratorCreate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } + setLoading(true); adminUser .storeAdminUser( values.name, @@ -64,8 +73,12 @@ export const SystemAdministratorCreate: React.FC = ({ values.roleIds ) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -95,6 +108,7 @@ export const SystemAdministratorCreate: React.FC = ({ onOk={() => form.submit()} onCancel={() => onCancel()} maskClosable={false} + okButtonProps={{ loading: loading }} >
void; } +type selRoleModel = { + label: string; + value: number; +}; + export const SystemAdministratorUpdate: React.FC = ({ id, refresh, @@ -18,8 +23,8 @@ export const SystemAdministratorUpdate: React.FC = ({ }) => { const [form] = Form.useForm(); const [init, setInit] = useState(true); - const [loading, setLoading] = useState(true); - const [roles, setRoles] = useState([]); + const [loading, setLoading] = useState(false); + const [roles, setRoles] = useState([]); useEffect(() => { if (open) { @@ -40,7 +45,7 @@ export const SystemAdministratorUpdate: React.FC = ({ const getParams = () => { adminUser.createAdminUser().then((res: any) => { const arr = []; - let roles = res.data.roles; + let roles: RoleModel[] = res.data.roles; for (let i = 0; i < roles.length; i++) { arr.push({ label: roles[i].name, @@ -53,7 +58,7 @@ export const SystemAdministratorUpdate: React.FC = ({ const getDetail = () => { adminUser.AdminUser(id).then((res: any) => { - let user = res.data.user; + let user: AdminUserDetailModel = res.data.user; form.setFieldsValue({ email: user.email, name: user.name, @@ -65,6 +70,10 @@ export const SystemAdministratorUpdate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } + setLoading(true); adminUser .updateAdminUser( id, @@ -75,8 +84,12 @@ export const SystemAdministratorUpdate: React.FC = ({ values.roleIds ) .then((res: any) => { + setLoading(false); message.success("保存成功!"); onCancel(); + }) + .catch((e) => { + setLoading(false); }); }; @@ -106,6 +119,7 @@ export const SystemAdministratorUpdate: React.FC = ({ onOk={() => form.submit()} onCancel={() => onCancel()} maskClosable={false} + okButtonProps={{ loading: loading }} > {init && (
diff --git a/src/pages/system/administrator/index.tsx b/src/pages/system/administrator/index.tsx index 267780f..30af95d 100644 --- a/src/pages/system/administrator/index.tsx +++ b/src/pages/system/administrator/index.tsx @@ -15,34 +15,36 @@ const { confirm } = Modal; interface DataType { id: React.Key; - name: string; + created_at: string; email: string; + is_ban_login: number; login_at: string; login_ip: string; - is_ban_login: number; + login_times: number; + name: string; + updated_at: string; } const SystemAdministratorPage = () => { const navigate = useNavigate(); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [size, setSize] = useState(10); - const [list, setList] = useState([]); - const [roles, setRoles] = useState([]); - const [userRoleIds, setUserRoleIds] = useState({}); + const [list, setList] = useState([]); + const [roles, setRoles] = useState({}); + const [userRoleIds, setUserRoleIds] = useState({}); const [total, setTotal] = useState(0); const [refresh, setRefresh] = useState(false); - const [createVisible, setCreateVisible] = useState(false); - const [updateVisible, setUpdateVisible] = useState(false); - const [createRoleVisible, setCreateRoleVisible] = useState(false); - const [updateRoleVisible, setUpdateRoleVisible] = useState(false); - const [cid, setCid] = useState(0); - const [role_ids, setRoleIds] = useState([]); - const [selLabel, setLabel] = useState("全部管理员"); + const [createVisible, setCreateVisible] = useState(false); + const [updateVisible, setUpdateVisible] = useState(false); + const [createRoleVisible, setCreateRoleVisible] = useState(false); + const [updateRoleVisible, setUpdateRoleVisible] = useState(false); + const [cid, setCid] = useState(0); + const [role_ids, setRoleIds] = useState([]); + const [selLabel, setLabel] = useState("全部管理员"); const [roleDelSuccess, setRoleDelSuccess] = useState(false); const [isSuper, setIsSuper] = useState(false); - - const [name, setName] = useState(""); + const [name, setName] = useState(""); const columns: ColumnsType = [ { diff --git a/src/pages/system/adminlog/compenents/detail-dialog.tsx b/src/pages/system/adminlog/compenents/detail-dialog.tsx index 2070095..1ba7099 100644 --- a/src/pages/system/adminlog/compenents/detail-dialog.tsx +++ b/src/pages/system/adminlog/compenents/detail-dialog.tsx @@ -1,21 +1,35 @@ import React, { useState, useEffect } from "react"; import { Modal, Form } from "antd"; +import { adminLog } from "../../../../api"; interface PropInterface { - param: string; - result: string; + id: number; open: boolean; onCancel: () => void; } export const AdminLogDetailDialog: React.FC = ({ - param, + id, open, onCancel, - result, }) => { const [form] = Form.useForm(); - const [loading, setLoading] = useState(true); + const [param, setParam] = useState(""); + const [result, setResult] = useState(""); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (open && id > 0) { + getDetail(); + } + }, [open, id]); + + const getDetail = () => { + adminLog.adminLogDetail(id).then((res: any) => { + setParam(res.data.param); + setResult(res.data.result); + }); + }; const onFinish = (values: any) => {}; @@ -31,18 +45,21 @@ export const AdminLogDetailDialog: React.FC = ({ centered forceRender open={true} - width={416} + width={600} onOk={() => onCancel()} onCancel={() => onCancel()} footer={null} maskClosable={false} > -
+
{ - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [size, setSize] = useState(10); - const [list, setList] = useState([]); + const [list, setList] = useState([]); 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([]); + const [created_at, setCreatedAt] = useState([]); const [createdAts, setCreatedAts] = useState([]); - const [param, setParam] = useState(""); - const [result, setResult] = useState(""); const [visiable, setVisiable] = useState(false); + const [admId, setAdmId] = useState(0); useEffect(() => { getData(); @@ -128,8 +130,7 @@ const SystemLogPage = () => { type="link" className="b-link c-red" onClick={() => { - setParam(record.param); - setResult(record.result); + setAdmId(Number(record.id)); setVisiable(true); }} > @@ -212,8 +213,7 @@ const SystemLogPage = () => { />
setVisiable(false)} > diff --git a/src/pages/system/adminroles/compenents/create.tsx b/src/pages/system/adminroles/compenents/create.tsx index 383a5a3..1193a50 100644 --- a/src/pages/system/adminroles/compenents/create.tsx +++ b/src/pages/system/adminroles/compenents/create.tsx @@ -19,9 +19,9 @@ export const SystemAdminrolesCreate: React.FC = ({ onCancel, }) => { const [form] = Form.useForm(); - const [loading, setLoading] = useState(true); - const [permissions, setPermissions] = useState([]); - const [actions, setActions] = useState([]); + const [loading, setLoading] = useState(false); + const [permissions, setPermissions] = useState([]); + const [actions, setActions] = useState([]); useEffect(() => { if (open) { @@ -126,6 +126,9 @@ export const SystemAdminrolesCreate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } let pids = []; let aids = []; if (values.permission_ids.length === 0 && values.action_ids.length === 0) { @@ -138,11 +141,18 @@ export const SystemAdminrolesCreate: React.FC = ({ if (values.action_ids) { aids = values.action_ids; } + setLoading(true); const params = aids.concat(pids); - adminRole.storeAdminRole(values.name, params).then((res: any) => { - message.success("保存成功!"); - onCancel(); - }); + adminRole + .storeAdminRole(values.name, params) + .then((res: any) => { + setLoading(false); + message.success("保存成功!"); + onCancel(); + }) + .catch((e) => { + setLoading(false); + }); }; const onFinishFailed = (errorInfo: any) => { @@ -160,7 +170,11 @@ export const SystemAdminrolesCreate: React.FC = ({ footer={ - diff --git a/src/pages/system/adminroles/compenents/update.tsx b/src/pages/system/adminroles/compenents/update.tsx index 36b0504..a2c4327 100644 --- a/src/pages/system/adminroles/compenents/update.tsx +++ b/src/pages/system/adminroles/compenents/update.tsx @@ -31,9 +31,9 @@ export const SystemAdminrolesUpdate: React.FC = ({ }) => { const [form] = Form.useForm(); const [init, setInit] = useState(true); - const [loading, setLoading] = useState(true); - const [permissions, setPermissions] = useState([]); - const [actions, setActions] = useState([]); + const [loading, setLoading] = useState(false); + const [permissions, setPermissions] = useState([]); + const [actions, setActions] = useState([]); useEffect(() => { if (open) { @@ -152,6 +152,9 @@ export const SystemAdminrolesUpdate: React.FC = ({ }; const onFinish = (values: any) => { + if (loading) { + return; + } let pids = []; let aids = []; if (values.permission_ids.length === 0 && values.action_ids.length === 0) { @@ -164,11 +167,18 @@ export const SystemAdminrolesUpdate: React.FC = ({ if (values.action_ids) { aids = values.action_ids; } + setLoading(true); const params = aids.concat(pids); - adminRole.updateAdminRole(id, values.name, params).then((res: any) => { - message.success("保存成功!"); - onCancel(); - }); + adminRole + .updateAdminRole(id, values.name, params) + .then((res: any) => { + setLoading(false); + message.success("保存成功!"); + onCancel(); + }) + .catch((e) => { + setLoading(false); + }); }; const onFinishFailed = (errorInfo: any) => { @@ -186,7 +196,11 @@ export const SystemAdminrolesUpdate: React.FC = ({ footer={ - diff --git a/src/pages/system/config/index.tsx b/src/pages/system/config/index.tsx index 4e8dfbe..6aa754a 100644 --- a/src/pages/system/config/index.tsx +++ b/src/pages/system/config/index.tsx @@ -12,18 +12,23 @@ import { Slider, Space, } from "antd"; -// import styles from "./index.module.less"; -import { appConfig } from "../../../api/index"; +import { appConfig, system } from "../../../api/index"; import { UploadImageButton } from "../../../compenents"; +import { useDispatch } from "react-redux"; import type { TabsProps } from "antd"; import type { CheckboxChangeEvent } from "antd/es/checkbox"; +import { + SystemConfigStoreInterface, + saveConfigAction, +} from "../../../store/system/systemConfigSlice"; const SystemConfigPage = () => { + const dispatch = useDispatch(); const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [logo, setLogo] = useState(""); - const [thumb, setThumb] = useState(""); - const [avatar, setAvatar] = useState(""); + const [loading, setLoading] = useState(false); + const [logo, setLogo] = useState(""); + const [thumb, setThumb] = useState(""); + const [avatar, setAvatar] = useState(""); const [tabKey, setTabKey] = useState(1); const [nameChecked, setNameChecked] = useState(false); const [emailChecked, setEmailChecked] = useState(false); @@ -141,6 +146,34 @@ const SystemConfigPage = () => { form.setFieldsValue({ "minio.domain": configData[i].key_value, }); + } else if (configData[i].key_name === "ldap.enabled") { + let value = 0; + if (configData[i].key_value === "1") { + value = 1; + } + form.setFieldsValue({ + "ldap.enabled": value, + }); + } else if (configData[i].key_name === "ldap.url") { + form.setFieldsValue({ + "ldap.url": configData[i].key_value, + }); + } else if (configData[i].key_name === "ldap.admin_user") { + form.setFieldsValue({ + "ldap.admin_user": configData[i].key_value, + }); + } else if (configData[i].key_name === "ldap.admin_pass") { + form.setFieldsValue({ + "ldap.admin_pass": configData[i].key_value, + }); + } else if (configData[i].key_name === "ldap.base_dn") { + form.setFieldsValue({ + "ldap.base_dn": configData[i].key_value, + }); + } else if (configData[i].key_name === "ldap.user_dn_prefix") { + form.setFieldsValue({ + "ldap.user_dn_prefix": configData[i].key_value, + }); } } }); @@ -211,6 +244,23 @@ const SystemConfigPage = () => { message.success("保存成功!"); setLoading(false); getDetail(); + getSystemConfig(); + }); + }; + + const getSystemConfig = async () => { + system.getSystemConfig().then((res: any) => { + let data: SystemConfigStoreInterface = { + "ldap-enabled": res.data["ldap-enabled"], + systemName: res.data["system.name"], + systemLogo: res.data["system.logo"], + systemApiUrl: res.data["system.api_url"], + systemPcUrl: res.data["system.pc_url"], + systemH5Url: res.data["system.h5_url"], + memberDefaultAvatar: res.data["member.default_avatar"], + courseDefaultThumbs: res.data["default.course_thumbs"], + }; + dispatch(saveConfigAction(data)); }); }; @@ -218,6 +268,14 @@ const SystemConfigPage = () => { console.log("Failed:", errorInfo); }; + const onLDAPChange = (checked: boolean) => { + if (checked) { + form.setFieldsValue({ "ldap.enabled": 1 }); + } else { + form.setFieldsValue({ "ldap.enabled": 0 }); + } + }; + const items: TabsProps["items"] = [ { key: "1", @@ -251,7 +309,7 @@ const SystemConfigPage = () => { }} >
-
+
(推荐尺寸:240x80px,支持JPG、PNG)
@@ -273,7 +331,7 @@ const SystemConfigPage = () => { }} >
-
+
(推荐尺寸:240x80px,支持JPG、PNG)
@@ -352,7 +410,7 @@ const SystemConfigPage = () => { -
+
(打开后禁止学员在首次学习中拖动进度条,以防刷课)
@@ -365,7 +423,7 @@ const SystemConfigPage = () => { > -
+
(打开后播放器会随机出现跑马灯水印,以防录屏传播)
@@ -446,7 +504,7 @@ const SystemConfigPage = () => { form.setFieldsValue({ "player.poster": url }); }} > -
+
(推荐尺寸:1920x1080px,视频播放未开始时展示)
@@ -468,7 +526,7 @@ const SystemConfigPage = () => { form.setFieldsValue({ "player.poster": url }); }} > -
+
(推荐尺寸:1920x1080px,视频播放未开始时展示)
@@ -523,7 +581,7 @@ const SystemConfigPage = () => { form.setFieldsValue({ "member.default_avatar": url }); }} > -
(新学员的默认头像)
+
(新学员的默认头像)
@@ -543,7 +601,7 @@ const SystemConfigPage = () => { form.setFieldsValue({ "member.default_avatar": url }); }} > -
(新学员的默认头像)
+
(新学员的默认头像)
@@ -639,6 +697,100 @@ const SystemConfigPage = () => { ), }, + { + key: "5", + label: `LDAP配置`, + children: ( +
+ + + + + + + + +
+ (LDAP的对外服务地址。例如:ldap.example.com) +
+
+
+ + + + + +
+ (用户登录到LDAP。例子:cn=admin,dc=playedu,dc=xyz) +
+
+
+ + + + + + + + +
(从LDAP根节点搜索用户)
+
+
+ + + + + +
+ (搜索用户时,基于基础DN的搜索范围限制) +
+
+
+ + + +
+ ), + }, ]; const onChange = (key: string) => { diff --git a/src/playedu.d.ts b/src/playedu.d.ts index feb1b6a..48babd2 100644 --- a/src/playedu.d.ts +++ b/src/playedu.d.ts @@ -15,6 +15,128 @@ declare global { poster: string; //视频帧 }; } + + interface UserModel { + avatar: string; + create_city?: string; + create_ip?: string; + created_at?: string; + credit1?: number; + email: string; + id: number; + id_card?: string; + is_active?: number; + is_lock?: number; + is_set_password?: number; + is_verify?: number; + login_at?: string; + name: string; + updated_at?: string; + verify_at?: string; + } + + interface AdminUserDetailModel { + created_at: string; + email: string; + id: number; + is_ban_login: number; + login_at: string; + login_ip: string; + login_times: number; + name: string; + updated_at: string; + } + + interface CourseModel { + charge: number; + class_hour: number; + created_at: string; + id: number; + is_required: number; + is_show: number; + short_desc: string; + thumb: string; + title: string; + } + + interface CategoriesBoxModel { + [key: number]: CategoriesItemModel[]; + } + + interface CategoriesItemModel { + id: number; + name: string; + parent_chain: string; + parent_id: number; + sort: number; + } + + interface CategoriesModel { + [key: number]: string; + } + + interface DepartmentsModel { + [key: number]: string; + } + + interface DepIdsModel { + [key: number]: number[]; + } + + interface CategoryIdsModel { + [key: number]: number[]; + } + + interface DepartmentsBoxModel { + [key: number]: DepartmentsItemModel[]; + } + + interface DepartmentsItemModel { + created_at: string; + id: number; + name: string; + parent_chain: string; + parent_id: number; + sort: number; + updated_at: string; + } + + interface RolesModel { + [key: number]: RoleModel[]; + } + + interface RoleModel { + created_at: string; + id: number; + name: string; + slug: string; + updated_at: string; + } + + interface RoleIdsModel { + [key: number]: number[]; + } + + interface CourseChaptersModel { + id?: number; + hours: CourseHourModel[]; + name: string; + } + + interface CourseHourModel { + id?: number; + duration: number; + name: string; + rid: number; + type: string; + } + + interface AttachmentDataModel { + id?: number; + name: string; + rid: number; + type: string; + } } export {}; diff --git a/src/store/system/systemConfigSlice.ts b/src/store/system/systemConfigSlice.ts index 300a1b9..f4a37c7 100644 --- a/src/store/system/systemConfigSlice.ts +++ b/src/store/system/systemConfigSlice.ts @@ -1,6 +1,7 @@ import { createSlice } from "@reduxjs/toolkit"; type SystemConfigStoreInterface = { + "ldap-enabled"?: boolean; systemApiUrl?: string; systemPcUrl?: string; systemH5Url?: string;