mirror of
https://github.com/PlayEdu/backend
synced 2025-06-22 04:53:00 +08:00
commit
ffdd80369f
@ -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}`, {});
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
.menu-box {
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
height: calc(100% - 74px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
@ -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<any>([]);
|
||||
const [selectVideos, setSelectVideos] = useState<any>([]);
|
||||
const [selectKeys, setSelectKeys] = useState<number[]>([]);
|
||||
const [selectVideos, setSelectVideos] = useState<selAttachmentModel[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setInit(true);
|
||||
setRefresh(!refresh);
|
||||
}, [props.open]);
|
||||
|
||||
@ -28,23 +33,15 @@ export const SelectAttachment = (props: PropsInterface) => {
|
||||
key: "1",
|
||||
label: `课件`,
|
||||
children: (
|
||||
<div
|
||||
className="float-left"
|
||||
style={{ display: init ? "none" : "block" }}
|
||||
>
|
||||
<UploadCoursewareSub
|
||||
label="课件"
|
||||
defaultCheckedList={props.defaultKeys}
|
||||
open={refresh}
|
||||
onSelected={(arr: any[], videos: any[]) => {
|
||||
setSelectKeys(arr);
|
||||
setSelectVideos(videos);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
setInit(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<UploadCoursewareSub
|
||||
label="课件"
|
||||
defaultCheckedList={props.defaultKeys}
|
||||
open={refresh}
|
||||
onSelected={(arr: any[], videos: any[]) => {
|
||||
setSelectKeys(arr);
|
||||
setSelectVideos(videos);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
@ -77,11 +74,6 @@ export const SelectAttachment = (props: PropsInterface) => {
|
||||
<Row>
|
||||
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
|
||||
</Row>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
) : null}
|
||||
</>
|
||||
|
@ -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<any>([]);
|
||||
const [selectVideos, setSelectVideos] = useState<any>([]);
|
||||
const [selectKeys, setSelectKeys] = useState<number[]>([]);
|
||||
const [selectVideos, setSelectVideos] = useState<selVideosModel[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setInit(true);
|
||||
setRefresh(!refresh);
|
||||
}, [props.open]);
|
||||
|
||||
@ -28,10 +33,7 @@ export const SelectResource = (props: PropsInterface) => {
|
||||
key: "1",
|
||||
label: `视频`,
|
||||
children: (
|
||||
<div
|
||||
className="float-left"
|
||||
style={{ display: init ? "none" : "block" }}
|
||||
>
|
||||
<div className="float-left">
|
||||
<UploadVideoSub
|
||||
label="视频"
|
||||
defaultCheckedList={props.defaultKeys}
|
||||
@ -40,9 +42,6 @@ export const SelectResource = (props: PropsInterface) => {
|
||||
setSelectKeys(arr);
|
||||
setSelectVideos(videos);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
setInit(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
@ -77,11 +76,6 @@ export const SelectResource = (props: PropsInterface) => {
|
||||
<Row>
|
||||
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
|
||||
</Row>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
) : null}
|
||||
</>
|
||||
|
@ -18,7 +18,7 @@ interface PropInterface {
|
||||
export const TreeCategory = (props: PropInterface) => {
|
||||
const [treeData, setTreeData] = useState<any>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [selectKey, setSelectKey] = useState<any>([]);
|
||||
const [selectKey, setSelectKey] = useState<number[]>([]);
|
||||
|
||||
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={<i className="iconfont icon-icon-fold c-gray" />}
|
||||
/>
|
||||
)}
|
||||
|
@ -20,7 +20,7 @@ interface PropInterface {
|
||||
export const TreeDepartment = (props: PropInterface) => {
|
||||
const [treeData, setTreeData] = useState<any>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [selectKey, setSelectKey] = useState<any>([]);
|
||||
const [selectKey, setSelectKey] = useState<number[]>([]);
|
||||
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={<i className="iconfont icon-icon-fold c-gray" />}
|
||||
/>
|
||||
)}
|
||||
|
@ -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<any>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [videoList, setVideoList] = useState<VideoItem[]>([]);
|
||||
@ -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) => {
|
||||
<>
|
||||
<Row style={{ width: 752, minHeight: 520 }}>
|
||||
<Col span={7}>
|
||||
<TreeCategory
|
||||
selected={[]}
|
||||
type="no-cate"
|
||||
text={props.label}
|
||||
onUpdate={(keys: any) => setCategoryIds(keys)}
|
||||
/>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="float-left"
|
||||
style={{ display: init ? "none" : "block" }}
|
||||
>
|
||||
<TreeCategory
|
||||
selected={[]}
|
||||
type="no-cate"
|
||||
text={props.label}
|
||||
onUpdate={(keys: any) => setCategoryIds(keys)}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={17}>
|
||||
<Row style={{ marginBottom: 24, paddingLeft: 10 }}>
|
||||
@ -172,7 +187,15 @@ export const UploadCoursewareSub = (props: PropsInterface) => {
|
||||
></UploadCoursewareButton>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className={styles["video-list"]}>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={styles["video-list"]}
|
||||
style={{ display: init ? "none" : "block" }}
|
||||
>
|
||||
{videoList.length === 0 && (
|
||||
<Col span={24} style={{ marginTop: 150 }}>
|
||||
<Empty description="暂无课件" />
|
||||
|
@ -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<FileItem[]>([]);
|
||||
const intervalId = useRef<number>();
|
||||
const [fileList, setFileList] = useState<FileItem[]>([]);
|
||||
|
||||
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 ? (
|
||||
<Tag color="red">{record.upload.remark}</Tag>
|
||||
<>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
className="b-n-link c-red"
|
||||
onClick={() => {
|
||||
record.upload.handler.retry();
|
||||
}}
|
||||
>
|
||||
失败.重试
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{record.upload.status === 7 ? (
|
||||
|
@ -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<any>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [videoList, setVideoList] = useState<VideoItem[]>([]);
|
||||
@ -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) => {
|
||||
<>
|
||||
<Row style={{ width: 752, minHeight: 520 }}>
|
||||
<Col span={7}>
|
||||
<TreeCategory
|
||||
selected={[]}
|
||||
type="no-cate"
|
||||
text={props.label}
|
||||
onUpdate={(keys: any) => setCategoryIds(keys)}
|
||||
/>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="float-left"
|
||||
style={{ display: init ? "none" : "block" }}
|
||||
>
|
||||
<TreeCategory
|
||||
selected={[]}
|
||||
type="no-cate"
|
||||
text={props.label}
|
||||
onUpdate={(keys: any) => setCategoryIds(keys)}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={17}>
|
||||
<Row style={{ marginBottom: 24, paddingLeft: 10 }}>
|
||||
@ -172,7 +187,15 @@ export const UploadVideoSub = (props: PropsInterface) => {
|
||||
></UploadVideoButton>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className={styles["video-list"]}>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={styles["video-list"]}
|
||||
style={{ display: init ? "none" : "block" }}
|
||||
>
|
||||
{videoList.length === 0 && (
|
||||
<Col span={24} style={{ marginTop: 150 }}>
|
||||
<Empty description="暂无视频" />
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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<PropInterface> = ({
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [init, setInit] = useState(true);
|
||||
const [attachmentVisible, setAttachmentVisible] = useState<boolean>(false);
|
||||
const [attachmentData, setAttachmentData] = useState<any>([]);
|
||||
const [attachments, setAttachments] = useState<any>([]);
|
||||
const [attachmentData, setAttachmentData] = useState<AttachmentDataModel[]>(
|
||||
[]
|
||||
);
|
||||
const [attachments, setAttachments] = useState<number[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setInit(true);
|
||||
if (id === 0) {
|
||||
return;
|
||||
}
|
||||
@ -43,6 +47,7 @@ export const CourseAttachmentUpdate: React.FC<PropInterface> = ({
|
||||
setAttachmentData(arr);
|
||||
setAttachments(keys);
|
||||
}
|
||||
setInit(false);
|
||||
});
|
||||
};
|
||||
|
||||
@ -169,47 +174,57 @@ export const CourseAttachmentUpdate: React.FC<PropInterface> = ({
|
||||
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>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
<div
|
||||
className="float-left"
|
||||
style={{ display: init ? "none" : "block" }}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</Drawer>
|
||||
) : null}
|
||||
|
@ -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<any>([]);
|
||||
const [treeData, setTreeData] = useState<Option[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
useEffect(() => {
|
||||
const hours = props.data;
|
||||
|
@ -32,6 +32,12 @@ interface PropInterface {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
interface Option {
|
||||
value: string | number;
|
||||
title: string;
|
||||
children?: Option[];
|
||||
}
|
||||
|
||||
export const CourseCreate: React.FC<PropInterface> = ({
|
||||
cateIds,
|
||||
depIds,
|
||||
@ -45,22 +51,24 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
||||
const defaultThumb1 = courseDefaultThumbs[0];
|
||||
const defaultThumb2 = courseDefaultThumbs[1];
|
||||
const defaultThumb3 = courseDefaultThumbs[2];
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [departments, setDepartments] = useState<any>([]);
|
||||
const [categories, setCategories] = useState<any>([]);
|
||||
const [thumb, setThumb] = useState<string>("");
|
||||
const [type, setType] = useState<string>("open");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [departments, setDepartments] = useState<Option[]>([]);
|
||||
const [categories, setCategories] = useState<Option[]>([]);
|
||||
const [thumb, setThumb] = useState("");
|
||||
const [type, setType] = useState("open");
|
||||
const [chapterType, setChapterType] = useState(0);
|
||||
const [chapters, setChapters] = useState<any>([]);
|
||||
const [hours, setHours] = useState<any>([]);
|
||||
const [chapters, setChapters] = useState<CourseChaptersModel[]>([]);
|
||||
const [hours, setHours] = useState<number[]>([]);
|
||||
const [chapterHours, setChapterHours] = useState<any>([]);
|
||||
const [videoVisible, setVideoVisible] = useState<boolean>(false);
|
||||
const [treeData, setTreeData] = useState<any>([]);
|
||||
const [videoVisible, setVideoVisible] = useState(false);
|
||||
const [treeData, setTreeData] = useState<CourseHourModel[]>([]);
|
||||
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>([]);
|
||||
const [showDrop, setShowDrop] = useState(false);
|
||||
const [attachmentVisible, setAttachmentVisible] = useState(false);
|
||||
const [attachmentData, setAttachmentData] = useState<AttachmentDataModel[]>(
|
||||
[]
|
||||
);
|
||||
const [attachments, setAttachments] = useState<number[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@ -91,7 +99,7 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
message.error("请配置课时");
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
course
|
||||
.storeCourse(
|
||||
values.title,
|
||||
@ -234,8 +246,12 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
||||
attachmentData
|
||||
)
|
||||
.then((res: any) => {
|
||||
setLoading(false);
|
||||
message.success("保存成功!");
|
||||
onCancel();
|
||||
})
|
||||
.catch((e) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
@ -438,7 +454,6 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
footer={
|
||||
<Space className="j-r-flex">
|
||||
<Button onClick={() => onCancel()}>取 消</Button>
|
||||
<Button onClick={() => form.submit()} type="primary">
|
||||
<Button
|
||||
loading={loading}
|
||||
onClick={() => form.submit()}
|
||||
type="primary"
|
||||
>
|
||||
确 认
|
||||
</Button>
|
||||
</Space>
|
||||
@ -676,7 +695,7 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
||||
form.setFieldsValue({ thumb: url });
|
||||
}}
|
||||
></UploadImageButton>
|
||||
<span className="helper-text ml-16">
|
||||
<span className="helper-text ml-8">
|
||||
(推荐尺寸:400x300px)
|
||||
</span>
|
||||
</div>
|
||||
|
@ -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<PropInterface> = ({
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [init, setInit] = useState(true);
|
||||
const [chapterType, setChapterType] = useState(0);
|
||||
const [chapters, setChapters] = useState<any>([]);
|
||||
const [hours, setHours] = useState<any>([]);
|
||||
const [chapters, setChapters] = useState<CourseChaptersModel[]>([]);
|
||||
const [hours, setHours] = useState<number[]>([]);
|
||||
const [chapterHours, setChapterHours] = useState<any>([]);
|
||||
const [videoVisible, setVideoVisible] = useState<boolean>(false);
|
||||
const [treeData, setTreeData] = useState<any>([]);
|
||||
const [treeData, setTreeData] = useState<CourseHourModel[]>([]);
|
||||
const [addvideoCurrent, setAddvideoCurrent] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setInit(true);
|
||||
if (id === 0) {
|
||||
return;
|
||||
}
|
||||
@ -77,6 +79,7 @@ export const CourseHourUpdate: React.FC<PropInterface> = ({
|
||||
setHours([]);
|
||||
}
|
||||
}
|
||||
setInit(false);
|
||||
});
|
||||
};
|
||||
|
||||
@ -231,7 +234,7 @@ export const CourseHourUpdate: React.FC<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Form
|
||||
form={form}
|
||||
name="hour-update-basic"
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
initialValues={{ remember: true }}
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
autoComplete="off"
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="float-left"
|
||||
style={{ display: init ? "none" : "block" }}
|
||||
>
|
||||
{chapterType === 0 && (
|
||||
<div className="c-flex">
|
||||
<Form.Item>
|
||||
<div className="ml-42">
|
||||
<Button
|
||||
onClick={() => setVideoVisible(true)}
|
||||
type="primary"
|
||||
>
|
||||
添加课时
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<div className={styles["hous-box"]}>
|
||||
{treeData.length === 0 && (
|
||||
<span className={styles["no-hours"]}>
|
||||
请点击上方按钮添加课时
|
||||
</span>
|
||||
)}
|
||||
{treeData.length > 0 && (
|
||||
<TreeHours
|
||||
data={treeData}
|
||||
onRemoveItem={(id: number) => {
|
||||
delHour(id);
|
||||
}}
|
||||
onUpdate={(arr: any[]) => {
|
||||
transHour(arr);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{chapterType === 1 && (
|
||||
<div className="c-flex">
|
||||
{chapters.length > 0 &&
|
||||
chapters.map((item: any, index: number) => {
|
||||
return (
|
||||
<div
|
||||
key={item.hours.length + "章节" + index}
|
||||
className={styles["chapter-item"]}
|
||||
<Form
|
||||
form={form}
|
||||
name="hour-update-basic"
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
initialValues={{ remember: true }}
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
autoComplete="off"
|
||||
>
|
||||
{chapterType === 0 && (
|
||||
<div className="c-flex">
|
||||
<Form.Item>
|
||||
<div className="ml-42">
|
||||
<Button
|
||||
onClick={() => setVideoVisible(true)}
|
||||
type="primary"
|
||||
>
|
||||
<div className="d-flex">
|
||||
<div className={styles["label"]}>
|
||||
章节{index + 1}:
|
||||
</div>
|
||||
<Input
|
||||
value={item.name}
|
||||
className={styles["input"]}
|
||||
onChange={(e) => {
|
||||
setChapterName(index, e.target.value);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
saveChapterName(index, e.target.value);
|
||||
}}
|
||||
placeholder="请在此处输入章节名称"
|
||||
allowClear
|
||||
/>
|
||||
<Button
|
||||
disabled={!item.name}
|
||||
className="mr-16"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setVideoVisible(true);
|
||||
setAddvideoCurrent(index);
|
||||
}}
|
||||
>
|
||||
添加课时
|
||||
</Button>
|
||||
<Button onClick={() => delChapter(index)}>
|
||||
删除章节
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles["chapter-hous-box"]}>
|
||||
{item.hours.length === 0 && (
|
||||
<span className={styles["no-hours"]}>
|
||||
请点击上方按钮添加课时
|
||||
</span>
|
||||
)}
|
||||
{item.hours.length > 0 && (
|
||||
<TreeHours
|
||||
data={item.hours}
|
||||
onRemoveItem={(id: number) => {
|
||||
delChapterHour(index, id);
|
||||
}}
|
||||
onUpdate={(arr: any[]) => {
|
||||
transChapterHour(index, arr);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<Form.Item>
|
||||
<div className="ml-42">
|
||||
<Button onClick={() => addNewChapter()}>添加章节</Button>
|
||||
添加课时
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<div className={styles["hous-box"]}>
|
||||
{treeData.length === 0 && (
|
||||
<span className={styles["no-hours"]}>
|
||||
请点击上方按钮添加课时
|
||||
</span>
|
||||
)}
|
||||
{treeData.length > 0 && (
|
||||
<TreeHours
|
||||
data={treeData}
|
||||
onRemoveItem={(id: number) => {
|
||||
delHour(id);
|
||||
}}
|
||||
onUpdate={(arr: any[]) => {
|
||||
transHour(arr);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
)}
|
||||
{chapterType === 1 && (
|
||||
<div className="c-flex">
|
||||
{chapters.length > 0 &&
|
||||
chapters.map((item: any, index: number) => {
|
||||
return (
|
||||
<div
|
||||
key={item.hours.length + "章节" + index}
|
||||
className={styles["chapter-item"]}
|
||||
>
|
||||
<div className="d-flex">
|
||||
<div className={styles["label"]}>
|
||||
章节{index + 1}:
|
||||
</div>
|
||||
<Input
|
||||
value={item.name}
|
||||
className={styles["input"]}
|
||||
onChange={(e) => {
|
||||
setChapterName(index, e.target.value);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
saveChapterName(index, e.target.value);
|
||||
}}
|
||||
placeholder="请在此处输入章节名称"
|
||||
allowClear
|
||||
/>
|
||||
<Button
|
||||
disabled={!item.name}
|
||||
className="mr-16"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setVideoVisible(true);
|
||||
setAddvideoCurrent(index);
|
||||
}}
|
||||
>
|
||||
添加课时
|
||||
</Button>
|
||||
<Button onClick={() => delChapter(index)}>
|
||||
删除章节
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles["chapter-hous-box"]}>
|
||||
{item.hours.length === 0 && (
|
||||
<span className={styles["no-hours"]}>
|
||||
请点击上方按钮添加课时
|
||||
</span>
|
||||
)}
|
||||
{item.hours.length > 0 && (
|
||||
<TreeHours
|
||||
data={item.hours}
|
||||
onRemoveItem={(id: number) => {
|
||||
delChapterHour(index, id);
|
||||
}}
|
||||
onUpdate={(arr: any[]) => {
|
||||
transChapterHour(index, arr);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<Form.Item>
|
||||
<div className="ml-42">
|
||||
<Button onClick={() => addNewChapter()}>
|
||||
添加章节
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
) : null}
|
||||
|
@ -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<any>([]);
|
||||
const [treeData, setTreeData] = useState<Option[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
useEffect(() => {
|
||||
const hours = props.data;
|
||||
|
@ -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<PropInterface> = ({
|
||||
id,
|
||||
open,
|
||||
@ -35,11 +44,11 @@ export const CourseUpdate: React.FC<PropInterface> = ({
|
||||
const defaultThumb1 = courseDefaultThumbs[0];
|
||||
const defaultThumb2 = courseDefaultThumbs[1];
|
||||
const defaultThumb3 = courseDefaultThumbs[2];
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [departments, setDepartments] = useState<any>([]);
|
||||
const [categories, setCategories] = useState<any>([]);
|
||||
const [thumb, setThumb] = useState<string>("");
|
||||
const [type, setType] = useState<string>("open");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [departments, setDepartments] = useState<Option[]>([]);
|
||||
const [categories, setCategories] = useState<Option[]>([]);
|
||||
const [thumb, setThumb] = useState("");
|
||||
const [type, setType] = useState("open");
|
||||
|
||||
useEffect(() => {
|
||||
setInit(true);
|
||||
@ -87,10 +96,14 @@ export const CourseUpdate: React.FC<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
footer={
|
||||
<Space className="j-r-flex">
|
||||
<Button onClick={() => onCancel()}>取 消</Button>
|
||||
<Button onClick={() => form.submit()} type="primary">
|
||||
<Button
|
||||
loading={loading}
|
||||
onClick={() => form.submit()}
|
||||
type="primary"
|
||||
>
|
||||
确 认
|
||||
</Button>
|
||||
</Space>
|
||||
@ -363,7 +393,7 @@ export const CourseUpdate: React.FC<PropInterface> = ({
|
||||
form.setFieldsValue({ thumb: url });
|
||||
}}
|
||||
></UploadImageButton>
|
||||
<span className="helper-text ml-16">
|
||||
<span className="helper-text ml-8">
|
||||
(推荐尺寸:400x300px)
|
||||
</span>
|
||||
</div>
|
||||
@ -378,6 +408,22 @@ export const CourseUpdate: React.FC<PropInterface> = ({
|
||||
maxLength={200}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="上架时间">
|
||||
<Space align="baseline" style={{ height: 32 }}>
|
||||
<Form.Item name="published_at">
|
||||
<DatePicker
|
||||
disabledDate={disabledDate}
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style={{ width: 240 }}
|
||||
showTime
|
||||
placeholder="请选择上架时间"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="helper-text">
|
||||
(上架时间越晚,排序越靠前)
|
||||
</div>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
@ -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<any>([]);
|
||||
const [list, setList] = useState<DataType[]>([]);
|
||||
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 [title, setTitle] = useState<string>("");
|
||||
const [dep_ids, setDepIds] = useState<any>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [category_ids, setCategoryIds] = useState<number[]>([]);
|
||||
const [title, setTitle] = useState("");
|
||||
const [dep_ids, setDepIds] = useState<number[]>([]);
|
||||
const [selLabel, setLabel] = useState<string>(
|
||||
result.get("label") ? String(result.get("label")) : "全部分类"
|
||||
);
|
||||
const [selDepLabel, setDepLabel] = useState<string>(
|
||||
result.get("label") ? String(result.get("label")) : "全部部门"
|
||||
);
|
||||
const [course_category_ids, setCourseCategoryIds] = useState<any>({});
|
||||
const [course_dep_ids, setCourseDepIds] = useState<any>({});
|
||||
const [categories, setCategories] = useState<any>({});
|
||||
const [departments, setDepartments] = useState<any>({});
|
||||
const [course_category_ids, setCourseCategoryIds] =
|
||||
useState<CategoryIdsModel>({});
|
||||
const [course_dep_ids, setCourseDepIds] = useState<DepIdsModel>({});
|
||||
const [categories, setCategories] = useState<CategoriesModel>({});
|
||||
const [departments, setDepartments] = useState<DepartmentsModel>({});
|
||||
const [tabKey, setTabKey] = useState(result.get("did") ? "2" : "1");
|
||||
|
||||
const [createVisible, setCreateVisible] = useState<boolean>(false);
|
||||
const [updateVisible, setUpdateVisible] = useState<boolean>(false);
|
||||
const [updateHourVisible, setHourUpdateVisible] = useState<boolean>(false);
|
||||
const [updateAttachmentVisible, setUpdateAttachmentVisible] =
|
||||
useState<boolean>(false);
|
||||
const [cid, setCid] = useState<number>(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) => <span>{dateFormat(text)}</span>,
|
||||
},
|
||||
{
|
||||
|
@ -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<any>([]);
|
||||
const [course, setCourse] = useState<any>({});
|
||||
const [records, setRecords] = useState<any>({});
|
||||
const [hourCount, setHourCount] = useState<any>({});
|
||||
const [userDepIds, setUserDepIds] = useState<any>({});
|
||||
const [departments, setDepartments] = useState<any>({});
|
||||
const [list, setList] = useState<DataType[]>([]);
|
||||
const [course, setCourse] = useState<CourseModel | null>(null);
|
||||
const [records, setRecords] = useState<UserCourseRecordsModel>({});
|
||||
const [hourCount, setHourCount] = useState<HourCountModel>({});
|
||||
const [userDepIds, setUserDepIds] = useState<DepIdsModel>({});
|
||||
const [departments, setDepartments] = useState<DepartmentsModel>({});
|
||||
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 [name, setName] = useState<string>("");
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [idCard, setIdCard] = useState<string>("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [idCard, setIdCard] = useState("");
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]);
|
||||
const [title, setTitle] = useState<string>(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}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
@ -116,11 +147,11 @@ const CourseUserPage = () => {
|
||||
},
|
||||
{
|
||||
title: "学习完成时间",
|
||||
dataIndex: "finished_at",
|
||||
dataIndex: "id",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{records[record.id] ? (
|
||||
<span>{dateFormat(records[record.id].finished_at)}</span>
|
||||
<span>{dateFormat(String(records[record.id].finished_at))}</span>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
|
@ -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<any>([]);
|
||||
const [basicData, setBasicData] = useState<BasicDataModel | null>(null);
|
||||
|
||||
const getData = () => {
|
||||
dashboard.dashboardList().then((res: any) => {
|
||||
@ -164,31 +192,35 @@ const DashboardPage = () => {
|
||||
<div className={styles["label"]}>今日学习学员</div>
|
||||
<div className={styles["info"]}>
|
||||
<div className={styles["num"]}>
|
||||
{basicData.user_learn_today}
|
||||
</div>
|
||||
<div className={styles["compare"]}>
|
||||
<span className="mr-5">较昨日</span>
|
||||
{compareNum(
|
||||
basicData.user_learn_today,
|
||||
basicData.user_learn_yesterday
|
||||
)}
|
||||
{basicData?.user_learn_today}
|
||||
</div>
|
||||
{basicData && (
|
||||
<div className={styles["compare"]}>
|
||||
<span className="mr-5">较昨日</span>
|
||||
{compareNum(
|
||||
basicData.user_learn_today,
|
||||
basicData.user_learn_yesterday
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["label-item"]}>
|
||||
<div className={styles["label"]}>总学员数</div>
|
||||
<div className={styles["info"]}>
|
||||
<div className={styles["num"]}>{basicData.user_total}</div>
|
||||
<div className={styles["compare"]}>
|
||||
<span className="mr-5">较昨日</span>
|
||||
{compareNum(basicData.user_today, 0)}
|
||||
</div>
|
||||
<div className={styles["num"]}>{basicData?.user_total}</div>
|
||||
{basicData && (
|
||||
<div className={styles["compare"]}>
|
||||
<span className="mr-5">较昨日</span>
|
||||
{compareNum(basicData.user_today, 0)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["label-item"]}>
|
||||
<div className={styles["label"]}>线上课数</div>
|
||||
<div className={styles["info"]}>
|
||||
<div className={styles["num"]}>{basicData.course_total}</div>
|
||||
<div className={styles["num"]}>{basicData?.course_total}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -249,7 +281,7 @@ const DashboardPage = () => {
|
||||
<div className="playedu-main-top mt-24" style={{ minHeight: 376 }}>
|
||||
<div className={styles["large-title"]}>今日学习排行</div>
|
||||
<div className={styles["rank-list"]}>
|
||||
{basicData.user_learn_top10 && (
|
||||
{basicData?.user_learn_top10 && (
|
||||
<div className={styles["half-list"]}>
|
||||
<div className={styles["rank-item"]}>
|
||||
<div className={styles["left-item"]}>
|
||||
@ -258,15 +290,16 @@ const DashboardPage = () => {
|
||||
src={iconN1}
|
||||
alt=""
|
||||
/>
|
||||
{basicData.user_learn_top10[0] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[0].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[0] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[0].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[0] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -283,15 +316,16 @@ const DashboardPage = () => {
|
||||
src={iconN2}
|
||||
alt=""
|
||||
/>
|
||||
{basicData.user_learn_top10[1] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[1].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[1] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[1].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[1] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -308,15 +342,16 @@ const DashboardPage = () => {
|
||||
src={iconN3}
|
||||
alt=""
|
||||
/>
|
||||
{basicData.user_learn_top10[2] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[2].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[2] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[2].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[2] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -329,15 +364,16 @@ const DashboardPage = () => {
|
||||
<div className={styles["rank-item"]}>
|
||||
<div className={styles["left-item"]}>
|
||||
<div className={styles["item-num"]}>4</div>
|
||||
{basicData.user_learn_top10[3] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[3].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[3] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[3].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[3] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -350,15 +386,16 @@ const DashboardPage = () => {
|
||||
<div className={styles["rank-item"]}>
|
||||
<div className={styles["left-item"]}>
|
||||
<div className={styles["item-num"]}>5</div>
|
||||
{basicData.user_learn_top10[4] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[4].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[4] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[4].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[4] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -370,20 +407,21 @@ const DashboardPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10 && (
|
||||
{basicData?.user_learn_top10 && (
|
||||
<div className={styles["half-list"]}>
|
||||
<div className={styles["rank-item"]}>
|
||||
<div className={styles["left-item"]}>
|
||||
<div className={styles["item-num"]}>6</div>
|
||||
{basicData.user_learn_top10[5] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[5].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[5] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[5].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[5] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -396,15 +434,16 @@ const DashboardPage = () => {
|
||||
<div className={styles["rank-item"]}>
|
||||
<div className={styles["left-item"]}>
|
||||
<div className={styles["item-num"]}>7</div>
|
||||
{basicData.user_learn_top10[6] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[6].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[6] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[6].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[6] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -417,15 +456,16 @@ const DashboardPage = () => {
|
||||
<div className={styles["rank-item"]}>
|
||||
<div className={styles["left-item"]}>
|
||||
<div className={styles["item-num"]}>8</div>
|
||||
{basicData.user_learn_top10[7] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[7].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[7] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[7].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[7] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -438,15 +478,16 @@ const DashboardPage = () => {
|
||||
<div className={styles["rank-item"]}>
|
||||
<div className={styles["left-item"]}>
|
||||
<div className={styles["item-num"]}>9</div>
|
||||
{basicData.user_learn_top10[8] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[8].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[8] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[8].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[8] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -459,15 +500,16 @@ const DashboardPage = () => {
|
||||
<div className={styles["rank-item"]}>
|
||||
<div className={styles["left-item"]}>
|
||||
<div className={styles["item-num"]}>10</div>
|
||||
{basicData.user_learn_top10[9] && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[9].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
{basicData.user_learn_top10[9] &&
|
||||
basicData.user_learn_top10_users && (
|
||||
<div className={styles["item-name"]}>
|
||||
{
|
||||
basicData.user_learn_top10_users[
|
||||
basicData.user_learn_top10[9].user_id
|
||||
]?.name
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{basicData.user_learn_top10[9] && (
|
||||
<div className={styles["item-time"]}>
|
||||
@ -489,7 +531,7 @@ const DashboardPage = () => {
|
||||
<div className={styles["label"]}>部门数</div>
|
||||
<div className={styles["info"]}>
|
||||
<div className={styles["num"]}>
|
||||
{basicData.department_total}
|
||||
{basicData?.department_total}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -497,7 +539,7 @@ const DashboardPage = () => {
|
||||
<div className={styles["label"]}>分类数</div>
|
||||
<div className={styles["info"]}>
|
||||
<div className={styles["num"]}>
|
||||
{basicData.resource_category_total}
|
||||
{basicData?.resource_category_total}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -505,7 +547,7 @@ const DashboardPage = () => {
|
||||
<div className={styles["label"]}>管理员</div>
|
||||
<div className={styles["info"]}>
|
||||
<div className={styles["num"]}>
|
||||
{basicData.admin_user_total}
|
||||
{basicData?.admin_user_total}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@ export const DepartmentCreate: React.FC<PropInterface> = ({
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [departments, setDepartments] = useState<any>([]);
|
||||
const [parent_id, setParentId] = useState<number>(0);
|
||||
|
||||
@ -78,11 +78,19 @@ export const DepartmentCreate: React.FC<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => onCancel()}
|
||||
maskClosable={false}
|
||||
okButtonProps={{ loading: loading }}
|
||||
>
|
||||
<div className="float-left mt-24">
|
||||
<Form
|
||||
|
@ -22,7 +22,7 @@ export const DepartmentUpdate: React.FC<PropInterface> = ({
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [init, setInit] = useState(true);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [departments, setDepartments] = useState<any>([]);
|
||||
const [parent_id, setParentId] = useState<number>(0);
|
||||
const [sort, setSort] = useState<number>(0);
|
||||
@ -93,11 +93,19 @@ export const DepartmentUpdate: React.FC<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => onCancel()}
|
||||
maskClosable={false}
|
||||
okButtonProps={{ loading: loading }}
|
||||
>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
|
@ -23,15 +23,18 @@ const DepartmentPage = () => {
|
||||
const permissions = useSelector(
|
||||
(state: any) => state.loginUser.value.permissions
|
||||
);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [treeData, setTreeData] = useState<any>([]);
|
||||
const [selectKey, setSelectKey] = useState<any>([]);
|
||||
const [treeData, setTreeData] = useState<Option[]>([]);
|
||||
const [selectKey, setSelectKey] = useState<number[]>([]);
|
||||
|
||||
const [createVisible, setCreateVisible] = useState<boolean>(false);
|
||||
const [updateVisible, setUpdateVisible] = useState<boolean>(false);
|
||||
const [createVisible, setCreateVisible] = useState(false);
|
||||
const [updateVisible, setUpdateVisible] = useState(false);
|
||||
const [did, setDid] = useState<number>(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: (
|
||||
<>
|
||||
<div className="tree-title-elli">{departments[id][i].name}</div>
|
||||
<div className="d-flex">
|
||||
<Tooltip placement="top" title="可拖拽排序">
|
||||
<i
|
||||
className="iconfont icon-icon-drag mr-16"
|
||||
style={{ fontSize: 24 }}
|
||||
/>
|
||||
</Tooltip>
|
||||
{through("department-cud") && (
|
||||
<>
|
||||
if (ldapEnabled) {
|
||||
arr.push({
|
||||
title: (
|
||||
<>
|
||||
<div className="tree-title-elli">{departments[id][i].name}</div>
|
||||
</>
|
||||
),
|
||||
key: departments[id][i].id,
|
||||
});
|
||||
} else {
|
||||
arr.push({
|
||||
title: (
|
||||
<>
|
||||
<div className="tree-title-elli">{departments[id][i].name}</div>
|
||||
<div className="d-flex">
|
||||
<Tooltip placement="top" title="可拖拽排序">
|
||||
<i
|
||||
className="iconfont icon-icon-edit mr-16"
|
||||
className="iconfont icon-icon-drag mr-16"
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() => {
|
||||
setDid(departments[id][i].id);
|
||||
setUpdateVisible(true);
|
||||
}}
|
||||
/>
|
||||
<i
|
||||
className="iconfont icon-icon-delete"
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() =>
|
||||
removeItem(
|
||||
departments[id][i].id,
|
||||
departments[id][i].name
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
key: departments[id][i].id,
|
||||
});
|
||||
</Tooltip>
|
||||
{through("department-cud") && (
|
||||
<>
|
||||
<i
|
||||
className="iconfont icon-icon-edit mr-16"
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() => {
|
||||
setDid(departments[id][i].id);
|
||||
setUpdateVisible(true);
|
||||
}}
|
||||
/>
|
||||
<i
|
||||
className="iconfont icon-icon-delete"
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() =>
|
||||
removeItem(
|
||||
departments[id][i].id,
|
||||
departments[id][i].name
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
key: departments[id][i].id,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const new_arr: Option[] = checkArr(departments, departments[id][i].id);
|
||||
arr.push({
|
||||
title: (
|
||||
<>
|
||||
<div className="tree-title-elli">{departments[id][i].name}</div>
|
||||
<div className="d-flex">
|
||||
<Tooltip placement="top" title="可拖拽排序">
|
||||
<i
|
||||
className="iconfont icon-icon-drag mr-16"
|
||||
style={{ fontSize: 24 }}
|
||||
/>
|
||||
</Tooltip>
|
||||
{through("department-cud") && (
|
||||
<>
|
||||
if (ldapEnabled) {
|
||||
arr.push({
|
||||
title: (
|
||||
<>
|
||||
<div className="tree-title-elli">{departments[id][i].name}</div>
|
||||
</>
|
||||
),
|
||||
key: departments[id][i].id,
|
||||
children: new_arr,
|
||||
});
|
||||
} else {
|
||||
arr.push({
|
||||
title: (
|
||||
<>
|
||||
<div className="tree-title-elli">{departments[id][i].name}</div>
|
||||
<div className="d-flex">
|
||||
<Tooltip placement="top" title="可拖拽排序">
|
||||
<i
|
||||
className="iconfont icon-icon-edit mr-16"
|
||||
className="iconfont icon-icon-drag mr-16"
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() => {
|
||||
setDid(departments[id][i].id);
|
||||
setUpdateVisible(true);
|
||||
}}
|
||||
/>
|
||||
<i
|
||||
className="iconfont icon-icon-delete"
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() =>
|
||||
removeItem(
|
||||
departments[id][i].id,
|
||||
departments[id][i].name
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
key: departments[id][i].id,
|
||||
children: new_arr,
|
||||
});
|
||||
</Tooltip>
|
||||
{through("department-cud") && (
|
||||
<>
|
||||
<i
|
||||
className="iconfont icon-icon-edit mr-16"
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() => {
|
||||
setDid(departments[id][i].id);
|
||||
setUpdateVisible(true);
|
||||
}}
|
||||
/>
|
||||
<i
|
||||
className="iconfont icon-icon-delete"
|
||||
style={{ fontSize: 24 }}
|
||||
onClick={() =>
|
||||
removeItem(
|
||||
departments[id][i].id,
|
||||
departments[id][i].name
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
key: departments[id][i].id,
|
||||
children: new_arr,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
@ -365,22 +391,29 @@ const DepartmentPage = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="playedu-main-top mb-24">
|
||||
{contextHolder}
|
||||
<div className="d-flex">
|
||||
<PerButton
|
||||
type="primary"
|
||||
text="新建部门"
|
||||
class="mr-16"
|
||||
icon={<PlusOutlined />}
|
||||
p="department-cud"
|
||||
onClick={() => setCreateVisible(true)}
|
||||
disabled={null}
|
||||
/>
|
||||
{!ldapEnabled && (
|
||||
<div className="playedu-main-top mb-24">
|
||||
{contextHolder}
|
||||
<div className="d-flex">
|
||||
<PerButton
|
||||
type="primary"
|
||||
text="新建部门"
|
||||
class="mr-16"
|
||||
icon={<PlusOutlined />}
|
||||
p="department-cud"
|
||||
onClick={() => setCreateVisible(true)}
|
||||
disabled={null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="playedu-main-body">
|
||||
<div style={{ width: 366 }}>
|
||||
{loading && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: loading ? "none" : "block", width: 366 }}>
|
||||
{treeData.length > 0 && (
|
||||
<Tree
|
||||
onSelect={onSelect}
|
||||
@ -389,7 +422,6 @@ const DepartmentPage = () => {
|
||||
blockNode
|
||||
onDragEnter={onDragEnter}
|
||||
onDrop={onDrop}
|
||||
defaultExpandAll={true}
|
||||
switcherIcon={<i className="iconfont icon-icon-fold c-gray" />}
|
||||
/>
|
||||
)}
|
||||
|
@ -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"],
|
||||
|
@ -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"],
|
||||
|
@ -24,7 +24,7 @@ export const MemberCreate: React.FC<PropInterface> = ({
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [departments, setDepartments] = useState<any>([]);
|
||||
const memberDefaultAvatar = useSelector(
|
||||
(state: any) => state.systemConfig.value.memberDefaultAvatar
|
||||
@ -80,10 +80,14 @@ export const MemberCreate: React.FC<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
values.dep_ids
|
||||
)
|
||||
.then((res: any) => {
|
||||
setLoading(false);
|
||||
message.success("保存成功!");
|
||||
onCancel();
|
||||
})
|
||||
.catch((e) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
@ -115,6 +123,7 @@ export const MemberCreate: React.FC<PropInterface> = ({
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => onCancel()}
|
||||
maskClosable={false}
|
||||
okButtonProps={{ loading: loading }}
|
||||
>
|
||||
<div className="member-form float-left mt-24">
|
||||
<Form
|
||||
@ -161,6 +170,7 @@ export const MemberCreate: React.FC<PropInterface> = ({
|
||||
rules={[{ required: true, message: "请输入登录邮箱!" }]}
|
||||
>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
allowClear
|
||||
style={{ width: 274 }}
|
||||
placeholder="请输入学员登录邮箱"
|
||||
@ -172,6 +182,7 @@ export const MemberCreate: React.FC<PropInterface> = ({
|
||||
rules={[{ required: true, message: "请输入登录密码!" }]}
|
||||
>
|
||||
<Input.Password
|
||||
autoComplete="off"
|
||||
allowClear
|
||||
style={{ width: 274 }}
|
||||
placeholder="请输入登录密码"
|
||||
|
@ -25,7 +25,7 @@ export const MemberUpdate: React.FC<PropInterface> = ({
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [init, setInit] = useState(true);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [departments, setDepartments] = useState<any>([]);
|
||||
const memberDefaultAvatar = useSelector(
|
||||
(state: any) => state.systemConfig.value.memberDefaultAvatar
|
||||
@ -105,11 +105,14 @@ export const MemberUpdate: React.FC<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
values.dep_ids
|
||||
)
|
||||
.then((res: any) => {
|
||||
setLoading(false);
|
||||
message.success("保存成功!");
|
||||
onCancel();
|
||||
})
|
||||
.catch((e) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
@ -144,6 +151,7 @@ export const MemberUpdate: React.FC<PropInterface> = ({
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => onCancel()}
|
||||
maskClosable={false}
|
||||
okButtonProps={{ loading: loading }}
|
||||
>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
@ -202,6 +210,7 @@ export const MemberUpdate: React.FC<PropInterface> = ({
|
||||
rules={[{ required: true, message: "请输入登录邮箱!" }]}
|
||||
>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请输入学员登录邮箱"
|
||||
@ -209,6 +218,7 @@ export const MemberUpdate: React.FC<PropInterface> = ({
|
||||
</Form.Item>
|
||||
<Form.Item label="登录密码" name="password">
|
||||
<Input.Password
|
||||
autoComplete="off"
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请输入登录密码"
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
import { user } from "../../api/index";
|
||||
import { dateFormat } from "../../utils/index";
|
||||
import { Link, Navigate, useLocation } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { TreeDepartment, PerButton } from "../../compenents";
|
||||
import { MemberCreate } from "./compenents/create";
|
||||
import { MemberUpdate } from "./compenents/update";
|
||||
@ -28,35 +29,47 @@ const { confirm } = Modal;
|
||||
|
||||
interface DataType {
|
||||
id: React.Key;
|
||||
name: string;
|
||||
avatar: string;
|
||||
create_city?: string;
|
||||
create_ip?: string;
|
||||
created_at?: string;
|
||||
credit1?: number;
|
||||
email: string;
|
||||
created_at: string;
|
||||
credit1: number;
|
||||
id_card: string;
|
||||
is_lock: 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;
|
||||
}
|
||||
|
||||
const MemberPage = () => {
|
||||
const result = new URLSearchParams(useLocation().search);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [size, setSize] = useState(10);
|
||||
const [list, setList] = useState<any>([]);
|
||||
const [list, setList] = useState<DataType[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const [nickname, setNickname] = useState<string>("");
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [dep_ids, setDepIds] = useState<any>([]);
|
||||
const [nickname, setNickname] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [dep_ids, setDepIds] = useState<number[]>([]);
|
||||
const [selLabel, setLabel] = useState<string>(
|
||||
result.get("label") ? String(result.get("label")) : "全部部门"
|
||||
);
|
||||
const [createVisible, setCreateVisible] = useState<boolean>(false);
|
||||
const [updateVisible, setUpdateVisible] = useState<boolean>(false);
|
||||
const [mid, setMid] = useState<number>(0);
|
||||
const [user_dep_ids, setUserDepIds] = useState<any>({});
|
||||
const [departments, setDepartments] = useState<any>({});
|
||||
const [createVisible, setCreateVisible] = useState(false);
|
||||
const [updateVisible, setUpdateVisible] = useState(false);
|
||||
const [mid, setMid] = useState(0);
|
||||
const [user_dep_ids, setUserDepIds] = useState<DepIdsModel>({});
|
||||
const [departments, setDepartments] = useState<DepartmentsModel>({});
|
||||
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}
|
||||
/>
|
||||
</Link>
|
||||
<div className="form-column"></div>
|
||||
<Dropdown menu={{ items }}>
|
||||
<Button
|
||||
type="link"
|
||||
className="b-link c-red"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<Space size="small" align="center">
|
||||
更多
|
||||
<DownOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
{!ldapEnabled && (
|
||||
<>
|
||||
<div className="form-column"></div>
|
||||
<Dropdown menu={{ items }}>
|
||||
<Button
|
||||
type="link"
|
||||
className="b-link c-red"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<Space size="small" align="center">
|
||||
更多
|
||||
<DownOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
@ -288,16 +305,18 @@ const MemberPage = () => {
|
||||
</div>
|
||||
<div className="float-left j-b-flex mb-24">
|
||||
<div className="d-flex">
|
||||
<PerButton
|
||||
type="primary"
|
||||
text="添加学员"
|
||||
class="mr-16"
|
||||
icon={<PlusOutlined />}
|
||||
p="user-store"
|
||||
onClick={() => setCreateVisible(true)}
|
||||
disabled={null}
|
||||
/>
|
||||
{dep_ids.length === 0 && (
|
||||
{!ldapEnabled && (
|
||||
<PerButton
|
||||
type="primary"
|
||||
text="添加学员"
|
||||
class="mr-16"
|
||||
icon={<PlusOutlined />}
|
||||
p="user-store"
|
||||
onClick={() => setCreateVisible(true)}
|
||||
disabled={null}
|
||||
/>
|
||||
)}
|
||||
{!ldapEnabled && dep_ids.length === 0 && (
|
||||
<Link style={{ textDecoration: "none" }} to={`/member/import`}>
|
||||
<PerButton
|
||||
type="default"
|
||||
|
@ -11,33 +11,71 @@ import { MemberLearnProgressDialog } from "./compenents/progress";
|
||||
|
||||
interface DataType {
|
||||
id: React.Key;
|
||||
title: string;
|
||||
type: string;
|
||||
charge: number;
|
||||
class_hour: number;
|
||||
created_at: string;
|
||||
total_duration: number;
|
||||
finished_duration: number;
|
||||
is_finished: boolean;
|
||||
is_required: number;
|
||||
is_show: number;
|
||||
short_desc: string;
|
||||
thumb: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
type UserCourseRecordsModel = {
|
||||
[key: number]: UserRecordModel;
|
||||
};
|
||||
|
||||
type UserRecordModel = {
|
||||
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]: number;
|
||||
};
|
||||
|
||||
type OptionModel = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type DepartmentsListModel = {
|
||||
reated_at: string;
|
||||
id: number;
|
||||
name: string;
|
||||
parent_chain: string;
|
||||
parent_id: number;
|
||||
sort: number;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
const MemberLearnPage = () => {
|
||||
let chartRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const result = new URLSearchParams(useLocation().search);
|
||||
const [loading2, setLoading2] = useState<boolean>(false);
|
||||
const [list2, setList2] = useState<any>([]);
|
||||
const [loading2, setLoading2] = useState(false);
|
||||
const [list2, setList2] = useState<DepartmentsListModel[]>([]);
|
||||
const [courses, setCourses] = useState<any>({});
|
||||
const [deps, setDeps] = useState<any>([]);
|
||||
const [depValue, setDepValue] = useState<number>(0);
|
||||
const [currentCourses, setCurrentCourses] = useState<any>([]);
|
||||
const [openCourses, setOpenCourses] = useState<any>([]);
|
||||
const [records, setRecords] = useState<any>({});
|
||||
const [hourCount, setHourCount] = useState<any>({});
|
||||
const [deps, setDeps] = useState<OptionModel[]>([]);
|
||||
const [depValue, setDepValue] = useState(0);
|
||||
const [currentCourses, setCurrentCourses] = useState<DataType[]>([]);
|
||||
const [openCourses, setOpenCourses] = useState<CourseModel[]>([]);
|
||||
const [records, setRecords] = useState<UserCourseRecordsModel>({});
|
||||
const [hourCount, setHourCount] = useState<HourCountModel>({});
|
||||
const [total2, setTotal2] = useState(0);
|
||||
const [refresh2, setRefresh2] = useState(false);
|
||||
const [uid, setUid] = useState(Number(result.get("id")));
|
||||
const [userName, setUserName] = useState<string>(String(result.get("name")));
|
||||
const [visiable, setVisiable] = useState(false);
|
||||
const [courseId, setcourseId] = useState<number>(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] ? (
|
||||
<span>{dateFormat(records[record.id].finished_at)}</span>
|
||||
<span>{dateFormat(String(records[record.id].finished_at))}</span>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
@ -232,7 +270,6 @@ const MemberLearnPage = () => {
|
||||
},
|
||||
{
|
||||
title: "学习进度",
|
||||
dataIndex: "is_finished",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{records[record.id] ? (
|
||||
|
@ -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<any>([]);
|
||||
const [adminUsers, setAdminUsers] = useState<any>({});
|
||||
const [existingTypes, setExistingTypes] = useState<any>([]);
|
||||
const [list, setList] = useState<DataType[]>([]);
|
||||
const [adminUsers, setAdminUsers] = useState<AdminUsersModel>({});
|
||||
const [existingTypes, setExistingTypes] = useState<string[]>([]);
|
||||
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 [loading, setLoading] = useState(true);
|
||||
const [category_ids, setCategoryIds] = useState<number[]>([]);
|
||||
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 [title, setTitle] = useState("");
|
||||
const [multiConfig, setMultiConfig] = useState(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 [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) => <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>,
|
||||
},
|
||||
{
|
||||
|
@ -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<any>([]);
|
||||
const [selectKey, setSelectKey] = useState<any>([]);
|
||||
const [visibleArr, setVisibleArr] = useState<any>([]);
|
||||
const [hoverArr, setHoverArr] = useState<any>([]);
|
||||
const [category_ids, setCategoryIds] = useState<number[]>([]);
|
||||
const [selectKey, setSelectKey] = useState<number[]>([]);
|
||||
const [visibleArr, setVisibleArr] = useState<boolean[]>([]);
|
||||
const [hoverArr, setHoverArr] = useState<boolean[]>([]);
|
||||
const [selLabel, setLabel] = useState<string>(
|
||||
result.get("label") ? String(result.get("label")) : "全部图片"
|
||||
);
|
||||
const [loading, setLoading] = useState<boolean>(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);
|
||||
|
@ -19,7 +19,7 @@ export const ResourceCategoryCreate: React.FC<PropInterface> = ({
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [categories, setCategories] = useState<any>([]);
|
||||
const [parent_id, setParentId] = useState<number>(0);
|
||||
|
||||
@ -78,11 +78,19 @@ export const ResourceCategoryCreate: React.FC<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
width={416}
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => onCancel()}
|
||||
okButtonProps={{ loading: loading }}
|
||||
>
|
||||
<div className="float-left mt-24">
|
||||
<Form
|
||||
|
@ -22,7 +22,7 @@ export const ResourceCategoryUpdate: React.FC<PropInterface> = ({
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [init, setInit] = useState(true);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [categories, setCategories] = useState<any>([]);
|
||||
const [parent_id, setParentId] = useState<number>(0);
|
||||
const [sort, setSort] = useState<number>(0);
|
||||
@ -91,11 +91,19 @@ export const ResourceCategoryUpdate: React.FC<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => onCancel()}
|
||||
maskClosable={false}
|
||||
okButtonProps={{ loading: loading }}
|
||||
>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
|
@ -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<boolean>(true);
|
||||
const [init, setInit] = useState(true);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [treeData, setTreeData] = useState<any>([]);
|
||||
const [selectKey, setSelectKey] = useState<any>([]);
|
||||
const [treeData, setTreeData] = useState<Option[]>([]);
|
||||
const [selectKey, setSelectKey] = useState<number[]>([]);
|
||||
const [createVisible, setCreateVisible] = useState<boolean>(false);
|
||||
const [updateVisible, setUpdateVisible] = useState<boolean>(false);
|
||||
const [cid, setCid] = useState<number>(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 = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="playedu-main-body">
|
||||
<div style={{ width: 366 }}>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: init ? "none" : "block", width: 366 }}>
|
||||
{treeData.length > 0 && (
|
||||
<Tree
|
||||
onSelect={onSelect}
|
||||
@ -399,7 +407,6 @@ const ResourceCategoryPage = () => {
|
||||
blockNode
|
||||
onDragEnter={onDragEnter}
|
||||
onDrop={onDrop}
|
||||
defaultExpandAll={true}
|
||||
switcherIcon={<i className="iconfont icon-icon-fold c-gray" />}
|
||||
/>
|
||||
)}
|
||||
|
@ -9,6 +9,12 @@ interface PropInterface {
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
interface Option {
|
||||
value: string | number;
|
||||
title: string;
|
||||
children?: Option[];
|
||||
}
|
||||
|
||||
export const VideosUpdateDialog: React.FC<PropInterface> = ({
|
||||
id,
|
||||
open,
|
||||
@ -16,9 +22,9 @@ export const VideosUpdateDialog: React.FC<PropInterface> = ({
|
||||
onSuccess,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [init, setInit] = useState(true);
|
||||
const [categories, setCategories] = useState<any>([]);
|
||||
const [categories, setCategories] = useState<Option[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setInit(true);
|
||||
@ -33,7 +39,7 @@ export const VideosUpdateDialog: React.FC<PropInterface> = ({
|
||||
|
||||
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<PropInterface> = ({
|
||||
});
|
||||
};
|
||||
|
||||
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]) {
|
||||
|
@ -17,7 +17,7 @@ export const VideoPlayDialog: React.FC<PropInterface> = ({
|
||||
open,
|
||||
onCancel,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && url) {
|
||||
|
@ -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<any>([]);
|
||||
const [videosExtra, setVideoExtra] = useState<any>([]);
|
||||
const [adminUsers, setAdminUsers] = useState<any>({});
|
||||
const [videoList, setVideoList] = useState<DataType[]>([]);
|
||||
const [videosExtra, setVideoExtra] = useState<VideosExtraModel>({});
|
||||
const [adminUsers, setAdminUsers] = useState<AdminUsersModel>({});
|
||||
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 [loading, setLoading] = useState(true);
|
||||
const [category_ids, setCategoryIds] = useState<number[]>([]);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]);
|
||||
const [selLabel, setLabel] = useState<string>(
|
||||
result.get("label") ? String(result.get("label")) : "全部视频"
|
||||
);
|
||||
const [cateId, setCateId] = useState(Number(result.get("cid")));
|
||||
const [updateVisible, setUpdateVisible] = useState<boolean>(false);
|
||||
const [playVisible, setPlayeVisible] = useState<boolean>(false);
|
||||
const [multiConfig, setMultiConfig] = useState<boolean>(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<string>("");
|
||||
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) => (
|
||||
<DurationText duration={videosExtra[id].duration}></DurationText>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "创建人",
|
||||
dataIndex: "admin_id",
|
||||
render: (text: number) =>
|
||||
JSON.stringify(adminUsers) !== "{}" && <span>{adminUsers[text]}</span>,
|
||||
render: (admin_id: number) =>
|
||||
JSON.stringify(adminUsers) !== "{}" && (
|
||||
<span>{adminUsers[admin_id]}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "创建时间",
|
||||
dataIndex: "created_at",
|
||||
render: (text: string) => <span>{dateFormat(text)}</span>,
|
||||
render: (created_at: string) => <span>{dateFormat(created_at)}</span>,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
|
@ -10,6 +10,11 @@ interface PropInterface {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
type selRoleModel = {
|
||||
label: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export const SystemAdministratorCreate: React.FC<PropInterface> = ({
|
||||
roleId,
|
||||
refresh,
|
||||
@ -17,8 +22,8 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [roles, setRoles] = useState<any>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [roles, setRoles] = useState<selRoleModel[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@ -43,7 +48,7 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
};
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
adminUser
|
||||
.storeAdminUser(
|
||||
values.name,
|
||||
@ -64,8 +73,12 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({
|
||||
values.roleIds
|
||||
)
|
||||
.then((res: any) => {
|
||||
setLoading(false);
|
||||
message.success("保存成功!");
|
||||
onCancel();
|
||||
})
|
||||
.catch((e) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
@ -95,6 +108,7 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => onCancel()}
|
||||
maskClosable={false}
|
||||
okButtonProps={{ loading: loading }}
|
||||
>
|
||||
<div className="float-left mt-24">
|
||||
<Form
|
||||
|
@ -10,6 +10,11 @@ interface PropInterface {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
type selRoleModel = {
|
||||
label: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export const SystemAdministratorUpdate: React.FC<PropInterface> = ({
|
||||
id,
|
||||
refresh,
|
||||
@ -18,8 +23,8 @@ export const SystemAdministratorUpdate: React.FC<PropInterface> = ({
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [init, setInit] = useState(true);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [roles, setRoles] = useState<any>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [roles, setRoles] = useState<selRoleModel[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@ -40,7 +45,7 @@ export const SystemAdministratorUpdate: React.FC<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
|
||||
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<PropInterface> = ({
|
||||
};
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
adminUser
|
||||
.updateAdminUser(
|
||||
id,
|
||||
@ -75,8 +84,12 @@ export const SystemAdministratorUpdate: React.FC<PropInterface> = ({
|
||||
values.roleIds
|
||||
)
|
||||
.then((res: any) => {
|
||||
setLoading(false);
|
||||
message.success("保存成功!");
|
||||
onCancel();
|
||||
})
|
||||
.catch((e) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
@ -106,6 +119,7 @@ export const SystemAdministratorUpdate: React.FC<PropInterface> = ({
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => onCancel()}
|
||||
maskClosable={false}
|
||||
okButtonProps={{ loading: loading }}
|
||||
>
|
||||
{init && (
|
||||
<div className="float-left text-center mt-30">
|
||||
|
@ -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<boolean>(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [size, setSize] = useState(10);
|
||||
const [list, setList] = useState<any>([]);
|
||||
const [roles, setRoles] = useState<any>([]);
|
||||
const [userRoleIds, setUserRoleIds] = useState<any>({});
|
||||
const [list, setList] = useState<DataType[]>([]);
|
||||
const [roles, setRoles] = useState<RolesModel>({});
|
||||
const [userRoleIds, setUserRoleIds] = useState<RoleIdsModel>({});
|
||||
const [total, setTotal] = useState(0);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [createVisible, setCreateVisible] = useState<boolean>(false);
|
||||
const [updateVisible, setUpdateVisible] = useState<boolean>(false);
|
||||
const [createRoleVisible, setCreateRoleVisible] = useState<boolean>(false);
|
||||
const [updateRoleVisible, setUpdateRoleVisible] = useState<boolean>(false);
|
||||
const [cid, setCid] = useState<number>(0);
|
||||
const [role_ids, setRoleIds] = useState<any>([]);
|
||||
const [selLabel, setLabel] = useState<string>("全部管理员");
|
||||
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<number[]>([]);
|
||||
const [selLabel, setLabel] = useState("全部管理员");
|
||||
const [roleDelSuccess, setRoleDelSuccess] = useState(false);
|
||||
const [isSuper, setIsSuper] = useState(false);
|
||||
|
||||
const [name, setName] = useState<string>("");
|
||||
const [name, setName] = useState("");
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{
|
||||
|
@ -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<PropInterface> = ({
|
||||
param,
|
||||
id,
|
||||
open,
|
||||
onCancel,
|
||||
result,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(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<PropInterface> = ({
|
||||
centered
|
||||
forceRender
|
||||
open={true}
|
||||
width={416}
|
||||
width={600}
|
||||
onOk={() => onCancel()}
|
||||
onCancel={() => onCancel()}
|
||||
footer={null}
|
||||
maskClosable={false}
|
||||
>
|
||||
<div className="mt-24">
|
||||
<div
|
||||
className="mt-24"
|
||||
style={{ maxHeight: 600, overflowY: "auto", overflowX: "hidden" }}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
name="adminlog-detail"
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
labelCol={{ span: 3 }}
|
||||
wrapperCol={{ span: 21 }}
|
||||
initialValues={{ remember: true }}
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
|
@ -1,7 +1,6 @@
|
||||
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";
|
||||
@ -11,32 +10,35 @@ 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;
|
||||
error_msg?: string;
|
||||
ip: string;
|
||||
ip_area: string;
|
||||
method: string;
|
||||
module: string;
|
||||
opt: number;
|
||||
param: string;
|
||||
request_method: string;
|
||||
result: string;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const SystemLogPage = () => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [size, setSize] = useState(10);
|
||||
const [list, setList] = useState<any>([]);
|
||||
const [list, setList] = useState<DataType[]>([]);
|
||||
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 [created_at, setCreatedAt] = useState<string[]>([]);
|
||||
const [createdAts, setCreatedAts] = useState<any>([]);
|
||||
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 = () => {
|
||||
/>
|
||||
</div>
|
||||
<AdminLogDetailDialog
|
||||
param={param}
|
||||
result={result}
|
||||
id={admId}
|
||||
open={visiable}
|
||||
onCancel={() => setVisiable(false)}
|
||||
></AdminLogDetailDialog>
|
||||
|
@ -19,9 +19,9 @@ export const SystemAdminrolesCreate: React.FC<PropInterface> = ({
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [permissions, setPermissions] = useState<any>([]);
|
||||
const [actions, setActions] = useState<any>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [permissions, setPermissions] = useState<Option[]>([]);
|
||||
const [actions, setActions] = useState<Option[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@ -126,6 +126,9 @@ export const SystemAdminrolesCreate: React.FC<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
footer={
|
||||
<Space className="j-r-flex">
|
||||
<Button onClick={() => onCancel()}>取 消</Button>
|
||||
<Button onClick={() => form.submit()} type="primary">
|
||||
<Button
|
||||
loading={loading}
|
||||
onClick={() => form.submit()}
|
||||
type="primary"
|
||||
>
|
||||
确 认
|
||||
</Button>
|
||||
</Space>
|
||||
|
@ -31,9 +31,9 @@ export const SystemAdminrolesUpdate: React.FC<PropInterface> = ({
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [init, setInit] = useState(true);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [permissions, setPermissions] = useState<any>([]);
|
||||
const [actions, setActions] = useState<any>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [permissions, setPermissions] = useState<Option[]>([]);
|
||||
const [actions, setActions] = useState<Option[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@ -152,6 +152,9 @@ export const SystemAdminrolesUpdate: React.FC<PropInterface> = ({
|
||||
};
|
||||
|
||||
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<PropInterface> = ({
|
||||
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<PropInterface> = ({
|
||||
footer={
|
||||
<Space className="j-r-flex">
|
||||
<Button onClick={() => onCancel()}>取 消</Button>
|
||||
<Button onClick={() => form.submit()} type="primary">
|
||||
<Button
|
||||
loading={loading}
|
||||
onClick={() => form.submit()}
|
||||
type="primary"
|
||||
>
|
||||
确 认
|
||||
</Button>
|
||||
</Space>
|
||||
|
@ -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<boolean>(false);
|
||||
const [logo, setLogo] = useState<string>("");
|
||||
const [thumb, setThumb] = useState<string>("");
|
||||
const [avatar, setAvatar] = useState<string>("");
|
||||
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 = () => {
|
||||
}}
|
||||
></UploadImageButton>
|
||||
</div>
|
||||
<div className="helper-text ml-24">
|
||||
<div className="helper-text ml-8">
|
||||
(推荐尺寸:240x80px,支持JPG、PNG)
|
||||
</div>
|
||||
</div>
|
||||
@ -273,7 +331,7 @@ const SystemConfigPage = () => {
|
||||
}}
|
||||
></UploadImageButton>
|
||||
</div>
|
||||
<div className="helper-text ml-24">
|
||||
<div className="helper-text ml-8">
|
||||
(推荐尺寸:240x80px,支持JPG、PNG)
|
||||
</div>
|
||||
</div>
|
||||
@ -352,7 +410,7 @@ const SystemConfigPage = () => {
|
||||
<Form.Item name="player.disabled_drag" valuePropName="checked">
|
||||
<Switch onChange={onDragChange} />
|
||||
</Form.Item>
|
||||
<div className="helper-text ml-24">
|
||||
<div className="helper-text">
|
||||
(打开后禁止学员在首次学习中拖动进度条,以防刷课)
|
||||
</div>
|
||||
</Space>
|
||||
@ -365,7 +423,7 @@ const SystemConfigPage = () => {
|
||||
>
|
||||
<Switch onChange={onSwitchChange} />
|
||||
</Form.Item>
|
||||
<div className="helper-text ml-24">
|
||||
<div className="helper-text">
|
||||
(打开后播放器会随机出现跑马灯水印,以防录屏传播)
|
||||
</div>
|
||||
</Space>
|
||||
@ -446,7 +504,7 @@ const SystemConfigPage = () => {
|
||||
form.setFieldsValue({ "player.poster": url });
|
||||
}}
|
||||
></UploadImageButton>
|
||||
<div className="helper-text ml-24">
|
||||
<div className="helper-text ml-8">
|
||||
(推荐尺寸:1920x1080px,视频播放未开始时展示)
|
||||
</div>
|
||||
</div>
|
||||
@ -468,7 +526,7 @@ const SystemConfigPage = () => {
|
||||
form.setFieldsValue({ "player.poster": url });
|
||||
}}
|
||||
></UploadImageButton>
|
||||
<div className="helper-text ml-24">
|
||||
<div className="helper-text ml-8">
|
||||
(推荐尺寸:1920x1080px,视频播放未开始时展示)
|
||||
</div>
|
||||
</div>
|
||||
@ -523,7 +581,7 @@ const SystemConfigPage = () => {
|
||||
form.setFieldsValue({ "member.default_avatar": url });
|
||||
}}
|
||||
></UploadImageButton>
|
||||
<div className="helper-text ml-24">(新学员的默认头像)</div>
|
||||
<div className="helper-text ml-8">(新学员的默认头像)</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
@ -543,7 +601,7 @@ const SystemConfigPage = () => {
|
||||
form.setFieldsValue({ "member.default_avatar": url });
|
||||
}}
|
||||
></UploadImageButton>
|
||||
<div className="helper-text ml-24">(新学员的默认头像)</div>
|
||||
<div className="helper-text ml-8">(新学员的默认头像)</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
@ -639,6 +697,100 @@ const SystemConfigPage = () => {
|
||||
</Form>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "5",
|
||||
label: `LDAP配置`,
|
||||
children: (
|
||||
<Form
|
||||
form={form}
|
||||
name="LDAP-basic"
|
||||
labelCol={{ span: 3 }}
|
||||
wrapperCol={{ span: 21 }}
|
||||
style={{ width: 1000, paddingTop: 30 }}
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
style={{ marginBottom: 30 }}
|
||||
label="启用"
|
||||
name="ldap.enabled"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch onChange={onLDAPChange} />
|
||||
</Form.Item>
|
||||
<Form.Item style={{ marginBottom: 30 }} label="服务地址">
|
||||
<Space align="baseline" style={{ height: 32 }}>
|
||||
<Form.Item name="ldap.url">
|
||||
<Input
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请填写服务地址"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="helper-text">
|
||||
(LDAP的对外服务地址。例如:ldap.example.com)
|
||||
</div>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
<Form.Item style={{ marginBottom: 30 }} label="用户名">
|
||||
<Space align="baseline" style={{ height: 32 }}>
|
||||
<Form.Item name="ldap.admin_user">
|
||||
<Input
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请填写用户名"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="helper-text">
|
||||
(用户登录到LDAP。例子:cn=admin,dc=playedu,dc=xyz)
|
||||
</div>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ marginBottom: 30 }}
|
||||
name="ldap.admin_pass"
|
||||
label="密码"
|
||||
>
|
||||
<Input style={{ width: 274 }} allowClear placeholder="请填写密码" />
|
||||
</Form.Item>
|
||||
<Form.Item style={{ marginBottom: 30 }} label="基本DN">
|
||||
<Space align="baseline" style={{ height: 32 }}>
|
||||
<Form.Item name="ldap.base_dn">
|
||||
<Input
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请填写基本DN"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="helper-text">(从LDAP根节点搜索用户)</div>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
<Form.Item style={{ marginBottom: 30 }} label="附件用户DN">
|
||||
<Space align="baseline" style={{ height: 32 }}>
|
||||
<Form.Item name="ldap.user_dn_prefix">
|
||||
<Input
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请填写基本DN"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="helper-text">
|
||||
(搜索用户时,基于基础DN的搜索范围限制)
|
||||
</div>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ marginBottom: 30 }}
|
||||
wrapperCol={{ offset: 3, span: 21 }}
|
||||
>
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
保存
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const onChange = (key: string) => {
|
||||
|
122
src/playedu.d.ts
vendored
122
src/playedu.d.ts
vendored
@ -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 {};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
type SystemConfigStoreInterface = {
|
||||
"ldap-enabled"?: boolean;
|
||||
systemApiUrl?: string;
|
||||
systemPcUrl?: string;
|
||||
systemH5Url?: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user