mirror of
https://github.com/PlayEdu/backend
synced 2025-09-10 23:53:43 +08:00
commit
5d834ab2d6
@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"ahooks": "^3.7.6",
|
||||
"antd": "^5.3.2",
|
||||
"axios": "^1.3.4",
|
||||
"echarts": "^5.4.2",
|
||||
|
Binary file not shown.
@ -109,6 +109,10 @@ export function learnCourses(
|
||||
});
|
||||
}
|
||||
|
||||
export function learnAllCourses(id: number) {
|
||||
return client.get(`/backend/v1/user/${id}/all-courses`, {});
|
||||
}
|
||||
|
||||
export function departmentProgress(
|
||||
id: number,
|
||||
page: number,
|
||||
@ -121,3 +125,25 @@ export function departmentProgress(
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
export function learnCoursesProgress(
|
||||
id: number,
|
||||
courseId: number,
|
||||
params: any
|
||||
) {
|
||||
return client.get(`/backend/v1/user/${id}/learn-course/${courseId} `, params);
|
||||
}
|
||||
|
||||
export function destroyAllUserLearned(id: number, courseId: number) {
|
||||
return client.destroy(`/backend/v1/user/${id}/learn-course/${courseId}`);
|
||||
}
|
||||
|
||||
export function destroyUserLearned(
|
||||
id: number,
|
||||
courseId: number,
|
||||
hourId: number
|
||||
) {
|
||||
return client.destroy(
|
||||
`/backend/v1/user/${id}/learn-course/${courseId}/hour/${hourId}`
|
||||
);
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ export const CreateResourceCategory = (props: PropInterface) => {
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
|
@ -8,7 +8,7 @@ export const Footer: React.FC = () => {
|
||||
style={{
|
||||
width: "100%",
|
||||
backgroundColor: "#F6F6F6",
|
||||
height: 232,
|
||||
height: 166,
|
||||
paddingTop: 80,
|
||||
textAlign: "center",
|
||||
}}
|
||||
|
29
src/compenents/keep-alive/index.tsx
Normal file
29
src/compenents/keep-alive/index.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { useUpdate } from "ahooks";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useLocation, useOutlet } from "react-router-dom";
|
||||
|
||||
function KeepAlive() {
|
||||
const componentList = useRef(new Map());
|
||||
const outLet = useOutlet();
|
||||
const { pathname } = useLocation();
|
||||
const forceUpdate = useUpdate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!componentList.current.has(pathname)) {
|
||||
componentList.current.set(pathname, outLet);
|
||||
}
|
||||
forceUpdate();
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{Array.from(componentList.current).map(([key, component]) => (
|
||||
<div key={key} style={{ display: pathname === key ? "block" : "none" }}>
|
||||
{component}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default KeepAlive;
|
@ -50,7 +50,7 @@ const items = [
|
||||
"user",
|
||||
<i className="iconfont icon-icon-user" />,
|
||||
[
|
||||
getItem("学员", "/member", null, null, null),
|
||||
getItem("学员", "/member/index", null, null, null),
|
||||
getItem("部门", "/department", null, null, null),
|
||||
],
|
||||
null
|
||||
|
@ -5,6 +5,7 @@ import { resourceCategory } from "../../api/index";
|
||||
interface Option {
|
||||
key: string | number;
|
||||
title: any;
|
||||
|
||||
children?: Option[];
|
||||
}
|
||||
|
||||
|
@ -18,3 +18,20 @@
|
||||
line-height: 30px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.checked {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #ff4d4f;
|
||||
border-radius: 3px;
|
||||
border: 2px solid #ff4d4f;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
import { resource, resourceCategory } from "../../api";
|
||||
import styles from "./index.module.less";
|
||||
import { CreateResourceCategory } from "../create-rs-category";
|
||||
import { CloseOutlined } from "@ant-design/icons";
|
||||
import { CloseOutlined, CheckOutlined } from "@ant-design/icons";
|
||||
import { UploadImageSub } from "./upload-image-sub";
|
||||
import { TreeCategory } from "../../compenents";
|
||||
|
||||
@ -49,6 +49,7 @@ export const UploadImageButton = (props: PropsInterface) => {
|
||||
const [page, setPage] = useState(1);
|
||||
const [size, setSize] = useState(15);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [selected, setSelected] = useState<string>("");
|
||||
|
||||
// 获取图片列表
|
||||
const getImageList = () => {
|
||||
@ -97,13 +98,24 @@ export const UploadImageButton = (props: PropsInterface) => {
|
||||
open={true}
|
||||
width={820}
|
||||
maskClosable={false}
|
||||
onOk={() => {
|
||||
if (!selected) {
|
||||
message.error("请选择图片后确定");
|
||||
return;
|
||||
}
|
||||
props.onSelected(selected);
|
||||
setShowModal(false);
|
||||
}}
|
||||
>
|
||||
<Row style={{ width: 752, minHeight: 520, marginTop: 24 }}>
|
||||
<Col span={7}>
|
||||
<TreeCategory
|
||||
type="no-cate"
|
||||
text={"图片"}
|
||||
onUpdate={(keys: any) => setCategoryIds(keys)}
|
||||
onUpdate={(keys: any) => {
|
||||
setSelected("");
|
||||
setCategoryIds(keys);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={17}>
|
||||
@ -129,10 +141,21 @@ export const UploadImageButton = (props: PropsInterface) => {
|
||||
className="image-item"
|
||||
style={{ backgroundImage: `url(${item.url})` }}
|
||||
onClick={() => {
|
||||
props.onSelected(item.url);
|
||||
setShowModal(false);
|
||||
setSelected(item.url);
|
||||
}}
|
||||
></div>
|
||||
>
|
||||
{selected.indexOf(item.url) !== -1 && (
|
||||
<i
|
||||
className={styles.checked}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelected("");
|
||||
}}
|
||||
>
|
||||
<CheckOutlined />
|
||||
</i>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{imageList.length > 0 && (
|
||||
|
@ -614,6 +614,8 @@ textarea.ant-input {
|
||||
background-size: contain;
|
||||
background-position: center center;
|
||||
background-color: #f6f6f6;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,14 +45,14 @@ const ChangePasswordPage = () => {
|
||||
name="old_password"
|
||||
rules={[{ required: true, message: "请输入原密码!" }]}
|
||||
>
|
||||
<Input.Password placeholder="请输入原密码" />
|
||||
<Input.Password placeholder="请输入原密码" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="新密码"
|
||||
name="new_password"
|
||||
rules={[{ required: true, message: "请输入新密码!" }]}
|
||||
>
|
||||
<Input.Password placeholder="请输入新密码" />
|
||||
<Input.Password placeholder="请输入新密码" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
|
@ -470,6 +470,7 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
||||
<Input
|
||||
style={{ width: 424 }}
|
||||
placeholder="请在此处输入课程名称"
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
@ -683,6 +684,7 @@ export const CourseCreate: React.FC<PropInterface> = ({
|
||||
onChange={(e) => {
|
||||
setChapterName(index, e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
placeholder="请在此处输入章节名称"
|
||||
/>
|
||||
<Button
|
||||
|
@ -451,6 +451,7 @@ export const CourseHourUpdate: React.FC<PropInterface> = ({
|
||||
saveChapterName(index, e.target.value);
|
||||
}}
|
||||
placeholder="请在此处输入章节名称"
|
||||
allowClear
|
||||
/>
|
||||
<Button
|
||||
className="mr-16"
|
||||
|
@ -224,6 +224,7 @@ export const CourseUpdate: React.FC<PropInterface> = ({
|
||||
rules={[{ required: true, message: "请在此处输入课程名称!" }]}
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
style={{ width: 424 }}
|
||||
placeholder="请在此处输入课程名称"
|
||||
/>
|
||||
|
@ -51,6 +51,7 @@ const CoursePage = () => {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [dep_ids, setDepIds] = useState<any>([]);
|
||||
const [selLabel, setLabel] = useState<string>("全部分类");
|
||||
const [selDepLabel, setDepLabel] = useState<string>("全部部门");
|
||||
const [course_category_ids, setCourseCategoryIds] = useState<any>({});
|
||||
const [course_dep_ids, setCourseDepIds] = useState<any>({});
|
||||
const [categories, setCategories] = useState<any>({});
|
||||
@ -95,7 +96,7 @@ const CoursePage = () => {
|
||||
text={"部门"}
|
||||
onUpdate={(keys: any, title: any) => {
|
||||
setDepIds(keys);
|
||||
setLabel(title);
|
||||
setDepLabel(title);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@ -282,11 +283,16 @@ const CoursePage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 获取视频列表
|
||||
// 获取列表
|
||||
const getList = () => {
|
||||
setLoading(true);
|
||||
let categoryIds = category_ids.join(",");
|
||||
let depIds = dep_ids.join(",");
|
||||
let categoryIds = "";
|
||||
let depIds = "";
|
||||
if (tabKey === 1) {
|
||||
categoryIds = category_ids.join(",");
|
||||
} else {
|
||||
depIds = dep_ids.join(",");
|
||||
}
|
||||
course
|
||||
.courseList(page, size, "", "", title, depIds, categoryIds)
|
||||
.then((res: any) => {
|
||||
@ -314,7 +320,7 @@ const CoursePage = () => {
|
||||
// 加载列表
|
||||
useEffect(() => {
|
||||
getList();
|
||||
}, [category_ids, dep_ids, refresh, page, size]);
|
||||
}, [category_ids, dep_ids, refresh, page, size, tabKey]);
|
||||
|
||||
const paginationProps = {
|
||||
current: page, //当前页码
|
||||
@ -348,7 +354,7 @@ const CoursePage = () => {
|
||||
</div>
|
||||
<div className="right-box">
|
||||
<div className="playedu-main-title float-left mb-24">
|
||||
线上课 | {selLabel}
|
||||
线上课 | {tabKey === 1 ? selLabel : selDepLabel}
|
||||
</div>
|
||||
<div className="float-left j-b-flex mb-24">
|
||||
<div className="d-flex">
|
||||
@ -370,6 +376,7 @@ const CoursePage = () => {
|
||||
onChange={(e) => {
|
||||
setTitle(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
style={{ width: 160 }}
|
||||
placeholder="请输入名称关键字"
|
||||
/>
|
||||
@ -399,8 +406,8 @@ const CoursePage = () => {
|
||||
rowKey={(record) => record.id}
|
||||
/>
|
||||
<CourseCreate
|
||||
cateIds={category_ids}
|
||||
depIds={dep_ids}
|
||||
cateIds={tabKey === 1 ? category_ids : []}
|
||||
depIds={tabKey === 2 ? dep_ids : []}
|
||||
open={createVisible}
|
||||
onCancel={() => {
|
||||
setCreateVisible(false);
|
||||
|
@ -149,13 +149,13 @@ const CourseUserPage = () => {
|
||||
// 删除学员
|
||||
const delItem = () => {
|
||||
if (selectedRowKeys.length === 0) {
|
||||
message.error("请选择学员后再清除");
|
||||
message.error("请选择学员后再重置");
|
||||
return;
|
||||
}
|
||||
confirm({
|
||||
title: "操作确认",
|
||||
icon: <ExclamationCircleFilled />,
|
||||
content: "确认清除选中学员学习记录?",
|
||||
content: "确认重置选中学员学习记录?",
|
||||
centered: true,
|
||||
okText: "确认",
|
||||
cancelText: "取消",
|
||||
@ -190,7 +190,7 @@ const CourseUserPage = () => {
|
||||
<div className="d-flex">
|
||||
<PerButton
|
||||
type="primary"
|
||||
text="清除学习记录"
|
||||
text="重置学习记录"
|
||||
class="mr-16"
|
||||
icon={null}
|
||||
p="course"
|
||||
@ -206,6 +206,7 @@ const CourseUserPage = () => {
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
style={{ width: 160 }}
|
||||
placeholder="请输入姓名关键字"
|
||||
/>
|
||||
@ -217,6 +218,7 @@ const CourseUserPage = () => {
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
style={{ width: 160 }}
|
||||
placeholder="请输入学员邮箱"
|
||||
/>
|
||||
|
@ -199,7 +199,7 @@ const DashboardPage = () => {
|
||||
<div
|
||||
className={styles["link-mode"]}
|
||||
onClick={() => {
|
||||
navigate("/member");
|
||||
navigate("/member/index");
|
||||
}}
|
||||
>
|
||||
<i
|
||||
@ -516,7 +516,7 @@ const DashboardPage = () => {
|
||||
<div className={styles["usage-guide"]}>
|
||||
<img className={styles["banner"]} src={banner} alt="" />
|
||||
<Link
|
||||
to="https://www.playedu.xyz/docs/docs/intro/"
|
||||
to="https://www.playedu.xyz/docs/docs/guide/"
|
||||
target="blank"
|
||||
className={styles["link"]}
|
||||
>
|
||||
|
@ -147,7 +147,11 @@ export const DepartmentCreate: React.FC<PropInterface> = ({
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入部门名称!" }]}
|
||||
>
|
||||
<Input style={{ width: 200 }} placeholder="请输入部门名称" />
|
||||
<Input
|
||||
style={{ width: 200 }}
|
||||
allowClear
|
||||
placeholder="请输入部门名称"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
|
@ -194,7 +194,7 @@ const DepartmentPage = () => {
|
||||
type="link"
|
||||
style={{ paddingLeft: 4, paddingRight: 4 }}
|
||||
danger
|
||||
onClick={() => navigate("/member")}
|
||||
onClick={() => navigate("/member/index")}
|
||||
>
|
||||
({res.data.users.length}个学员),
|
||||
</Button>
|
||||
|
@ -73,7 +73,6 @@ const LoginPage = () => {
|
||||
|
||||
navigate("/", { replace: true });
|
||||
} catch (e) {
|
||||
message.error("登录出现错误");
|
||||
console.error("错误信息", e);
|
||||
setLoading(false);
|
||||
fetchImageCaptcha(); //刷新图形验证码
|
||||
@ -129,6 +128,7 @@ const LoginPage = () => {
|
||||
style={{ width: 400, height: 54 }}
|
||||
placeholder="请输入管理员邮箱账号"
|
||||
onKeyUp={(e) => keyUp(e)}
|
||||
allowClear
|
||||
/>
|
||||
</div>
|
||||
<div className="login-box d-flex mt-50">
|
||||
@ -137,6 +137,7 @@ const LoginPage = () => {
|
||||
onChange={(e) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
style={{ width: 400, height: 54 }}
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
@ -149,6 +150,7 @@ const LoginPage = () => {
|
||||
onChange={(e) => {
|
||||
setCaptchaVal(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
onKeyUp={(e) => keyUp(e)}
|
||||
/>
|
||||
<div className={styles["captcha-box"]}>
|
||||
|
@ -8,6 +8,7 @@ import { ValidataCredentials } from "../../../utils/index";
|
||||
|
||||
interface PropInterface {
|
||||
open: boolean;
|
||||
depIds: any;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@ -17,7 +18,11 @@ interface Option {
|
||||
children?: Option[];
|
||||
}
|
||||
|
||||
export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
|
||||
export const MemberCreate: React.FC<PropInterface> = ({
|
||||
open,
|
||||
depIds,
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [departments, setDepartments] = useState<any>([]);
|
||||
@ -39,10 +44,10 @@ export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
|
||||
password: "",
|
||||
avatar: memberDefaultAvatar,
|
||||
idCard: "",
|
||||
dep_ids: [],
|
||||
dep_ids: depIds,
|
||||
});
|
||||
setAvatar(memberDefaultAvatar);
|
||||
}, [form, open]);
|
||||
}, [form, open, depIds]);
|
||||
|
||||
const getParams = () => {
|
||||
department.departmentList().then((res: any) => {
|
||||
@ -154,7 +159,11 @@ export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
|
||||
name="email"
|
||||
rules={[{ required: true, message: "请输入登录邮箱!" }]}
|
||||
>
|
||||
<Input style={{ width: 274 }} placeholder="请输入学员登录邮箱" />
|
||||
<Input
|
||||
allowClear
|
||||
style={{ width: 274 }}
|
||||
placeholder="请输入学员登录邮箱"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="登录密码"
|
||||
@ -162,6 +171,7 @@ export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
|
||||
rules={[{ required: true, message: "请输入登录密码!" }]}
|
||||
>
|
||||
<Input.Password
|
||||
allowClear
|
||||
style={{ width: 274 }}
|
||||
placeholder="请输入登录密码"
|
||||
/>
|
||||
@ -182,7 +192,11 @@ export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="身份证号" name="idCard">
|
||||
<Input style={{ width: 274 }} placeholder="请填写学员身份证号" />
|
||||
<Input
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请填写学员身份证号"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
|
0
src/pages/member/compenents/progress.module.scss
Normal file
0
src/pages/member/compenents/progress.module.scss
Normal file
252
src/pages/member/compenents/progress.tsx
Normal file
252
src/pages/member/compenents/progress.tsx
Normal file
@ -0,0 +1,252 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import styles from "./progrss.module.less";
|
||||
import { Table, Modal, message } from "antd";
|
||||
import { PerButton, DurationText } from "../../../compenents";
|
||||
import { user as member } from "../../../api/index";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { dateFormat } from "../../../utils/index";
|
||||
import { ExclamationCircleFilled } from "@ant-design/icons";
|
||||
const { confirm } = Modal;
|
||||
|
||||
interface DataType {
|
||||
id: React.Key;
|
||||
title: string;
|
||||
type: string;
|
||||
created_at: string;
|
||||
duration: number;
|
||||
finished_duration: number;
|
||||
is_finished: boolean;
|
||||
finished_at: boolean;
|
||||
}
|
||||
|
||||
interface PropInterface {
|
||||
open: boolean;
|
||||
uid: number;
|
||||
id: number;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const MemberLearnProgressDialog: React.FC<PropInterface> = ({
|
||||
open,
|
||||
uid,
|
||||
id,
|
||||
onCancel,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [list, setList] = useState<any>([]);
|
||||
const [records, setRecords] = useState<any>({});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
getData();
|
||||
}
|
||||
}, [uid, id, open, refresh]);
|
||||
|
||||
const getData = () => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
member.learnCoursesProgress(uid, id, {}).then((res: any) => {
|
||||
setList(res.data.hours);
|
||||
setRecords(res.data.learn_records);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const column: ColumnsType<DataType> = [
|
||||
{
|
||||
title: "课时标题",
|
||||
dataIndex: "title",
|
||||
|
||||
render: (title: string) => (
|
||||
<>
|
||||
<span>{title}</span>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "总时长",
|
||||
width: 120,
|
||||
dataIndex: "duration",
|
||||
render: (duration: number) => (
|
||||
<>
|
||||
<DurationText duration={duration}></DurationText>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "已学习时长",
|
||||
width: 120,
|
||||
dataIndex: "finished_duration",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{records && records[record.id] ? (
|
||||
<span>
|
||||
<DurationText
|
||||
duration={records[record.id].finished_duration || 0}
|
||||
></DurationText>
|
||||
</span>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "是否学完",
|
||||
width: 100,
|
||||
dataIndex: "is_finished",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{records &&
|
||||
records[record.id] &&
|
||||
records[record.id].is_finished === 1 ? (
|
||||
<span className="c-green">已学完</span>
|
||||
) : (
|
||||
<span className="c-red">未学完</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "开始时间",
|
||||
width: 150,
|
||||
dataIndex: "created_at",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{records && records[record.id] ? (
|
||||
<span>{dateFormat(records[record.id].created_at)}</span>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "学完时间",
|
||||
width: 150,
|
||||
dataIndex: "finished_at",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{records && records[record.id] ? (
|
||||
<span>{dateFormat(records[record.id].finished_at)}</span>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
fixed: "right",
|
||||
width: 70,
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{records && records[record.id] ? (
|
||||
<PerButton
|
||||
type="link"
|
||||
text="重置"
|
||||
class="b-link c-red"
|
||||
icon={null}
|
||||
p="user-learn-destroy"
|
||||
onClick={() => {
|
||||
clearSingleProgress(records[record.id].hour_id);
|
||||
}}
|
||||
disabled={null}
|
||||
/>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const clearProgress = () => {
|
||||
confirm({
|
||||
title: "操作确认",
|
||||
icon: <ExclamationCircleFilled />,
|
||||
content: "确认重置此课程下所有课时的学习记录?",
|
||||
centered: true,
|
||||
okText: "确认",
|
||||
cancelText: "取消",
|
||||
onOk() {
|
||||
member.destroyAllUserLearned(uid, id).then((res: any) => {
|
||||
message.success("操作成功");
|
||||
setRefresh(!refresh);
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log("Cancel");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const clearSingleProgress = (hour_id: number) => {
|
||||
if (hour_id === 0) {
|
||||
return;
|
||||
}
|
||||
confirm({
|
||||
title: "操作确认",
|
||||
icon: <ExclamationCircleFilled />,
|
||||
content: "确认重置此课时的学习记录?",
|
||||
centered: true,
|
||||
okText: "确认",
|
||||
cancelText: "取消",
|
||||
onOk() {
|
||||
member.destroyUserLearned(uid, id, hour_id).then((res: any) => {
|
||||
message.success("操作成功");
|
||||
setRefresh(!refresh);
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
console.log("Cancel");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title="课时学习进度"
|
||||
centered
|
||||
forceRender
|
||||
open={open}
|
||||
width={1000}
|
||||
onOk={() => onCancel()}
|
||||
onCancel={() => onCancel()}
|
||||
maskClosable={false}
|
||||
footer={null}
|
||||
>
|
||||
<div className="d-flex mt-24">
|
||||
<PerButton
|
||||
type="primary"
|
||||
text="重置学习记录"
|
||||
class="c-white"
|
||||
icon={null}
|
||||
p="user-learn-destroy"
|
||||
onClick={() => {
|
||||
clearProgress();
|
||||
}}
|
||||
disabled={null}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex mt-24"
|
||||
style={{ maxHeight: 800, overflowY: "auto" }}
|
||||
>
|
||||
<Table
|
||||
columns={column}
|
||||
dataSource={list}
|
||||
loading={loading}
|
||||
rowKey={(record) => record.id}
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
@ -181,18 +181,27 @@ export const MemberUpdate: React.FC<PropInterface> = ({
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入学员姓名!" }]}
|
||||
>
|
||||
<Input style={{ width: 274 }} placeholder="请填写学员姓名" />
|
||||
<Input
|
||||
allowClear
|
||||
style={{ width: 274 }}
|
||||
placeholder="请填写学员姓名"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="登录邮箱"
|
||||
name="email"
|
||||
rules={[{ required: true, message: "请输入登录邮箱!" }]}
|
||||
>
|
||||
<Input style={{ width: 274 }} placeholder="请输入学员登录邮箱" />
|
||||
<Input
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请输入学员登录邮箱"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="登录密码" name="password">
|
||||
<Input.Password
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请输入登录密码"
|
||||
/>
|
||||
</Form.Item>
|
||||
@ -212,7 +221,11 @@ export const MemberUpdate: React.FC<PropInterface> = ({
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="身份证号" name="idCard">
|
||||
<Input style={{ width: 274 }} placeholder="请填写学员身份证号" />
|
||||
<Input
|
||||
allowClear
|
||||
style={{ width: 274 }}
|
||||
placeholder="请填写学员身份证号"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
|
@ -9,12 +9,14 @@ import {
|
||||
Space,
|
||||
message,
|
||||
Table,
|
||||
Select,
|
||||
} from "antd";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { BackBartment, DurationText } from "../../compenents";
|
||||
import { dateFormat } from "../../utils/index";
|
||||
import { user as member } from "../../api/index";
|
||||
const { Column, ColumnGroup } = Table;
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
interface DataType {
|
||||
id: React.Key;
|
||||
@ -40,8 +42,20 @@ const MemberDepartmentProgressPage = () => {
|
||||
const [name, setName] = useState<string>("");
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [id_card, setIdCard] = useState<string>("");
|
||||
const [showMode, setShowMode] = useState<string>("all");
|
||||
const [did, setDid] = useState(Number(result.get("id")));
|
||||
const [title, setTitle] = useState(String(result.get("title")));
|
||||
const [exportLoading, setExportLoading] = useState(false);
|
||||
const modes = [
|
||||
{ label: "全部", value: "all" },
|
||||
{ label: "不显示公开课", value: "only_dep" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
setDid(Number(result.get("id")));
|
||||
setTitle(String(result.get("title")));
|
||||
resetData();
|
||||
}, [result.get("id"), result.get("title")]);
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
@ -59,6 +73,7 @@ const MemberDepartmentProgressPage = () => {
|
||||
name: name,
|
||||
email: email,
|
||||
id_card: id_card,
|
||||
show_mode: showMode,
|
||||
})
|
||||
.then((res: any) => {
|
||||
setList(res.data.data);
|
||||
@ -81,6 +96,7 @@ const MemberDepartmentProgressPage = () => {
|
||||
setName("");
|
||||
setEmail("");
|
||||
setIdCard("");
|
||||
setShowMode("all");
|
||||
setPage(1);
|
||||
setSize(10);
|
||||
setList([]);
|
||||
@ -125,14 +141,80 @@ const MemberDepartmentProgressPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const exportExcel = () => {
|
||||
if (exportLoading) {
|
||||
return;
|
||||
}
|
||||
setExportLoading(true);
|
||||
let filter = {
|
||||
sort_field: "",
|
||||
sort_algo: "",
|
||||
name: name,
|
||||
email: email,
|
||||
id_card: id_card,
|
||||
show_mode: showMode,
|
||||
};
|
||||
member.departmentProgress(did, page, total, filter).then((res: any) => {
|
||||
if (res.data.total === 0) {
|
||||
message.error("数据为空");
|
||||
setExportLoading(false);
|
||||
return;
|
||||
}
|
||||
let filename = title + "学习进度.xlsx";
|
||||
let sheetName = "sheet1";
|
||||
let data = [];
|
||||
let arr = ["学员"];
|
||||
courses.map((item: any) => {
|
||||
arr.push(item.title);
|
||||
});
|
||||
arr.push("总计课时");
|
||||
data.push(arr);
|
||||
|
||||
res.data.data.forEach((item: any) => {
|
||||
let arr = [item.name];
|
||||
courses.map((it: any) => {
|
||||
if (records && records[item.id] && records[item.id][it.id]) {
|
||||
if (records && records[item.id][it.id].is_finished === 1) {
|
||||
arr.push("已学完");
|
||||
} else {
|
||||
arr.push(
|
||||
records &&
|
||||
records[item.id][it.id].finished_count + " / " + it.class_hour
|
||||
);
|
||||
}
|
||||
} else {
|
||||
arr.push(0 + " / " + it.class_hour);
|
||||
}
|
||||
});
|
||||
arr.push(getFinishedHours(records[item.id]) + " / " + totalHour);
|
||||
data.push(arr);
|
||||
});
|
||||
|
||||
const jsonWorkSheet = XLSX.utils.json_to_sheet(data);
|
||||
const workBook: XLSX.WorkBook = {
|
||||
SheetNames: [sheetName],
|
||||
Sheets: {
|
||||
[sheetName]: jsonWorkSheet,
|
||||
},
|
||||
};
|
||||
XLSX.writeFile(workBook, filename);
|
||||
setExportLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="playedu-main-body">
|
||||
<div className="float-left mb-24">
|
||||
<BackBartment title={title + "学习进度"} />
|
||||
</div>
|
||||
<div className="float-left j-b-flex mb-24">
|
||||
<div className="d-flex helper-text ">
|
||||
(以下表格内数字对应的是表头课程的“已学完课时数/总课时数”)
|
||||
<div className="d-flex">
|
||||
<Button type="default" onClick={() => exportExcel()}>
|
||||
批量导出表格
|
||||
</Button>
|
||||
<div className="helper-text ml-24">
|
||||
(以下表格内数字对应的是表头课程的“已学完课时数/总课时数”)
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
<div className="d-flex mr-24 ">
|
||||
@ -142,30 +224,32 @@ const MemberDepartmentProgressPage = () => {
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
style={{ width: 160 }}
|
||||
placeholder="请输入姓名关键字"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex mr-24">
|
||||
{/* <div className="d-flex mr-24">
|
||||
<Typography.Text>邮箱:</Typography.Text>
|
||||
<Input
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
style={{ width: 160 }}
|
||||
placeholder="请输入邮箱"
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="d-flex mr-24">
|
||||
<Typography.Text>身份证号:</Typography.Text>
|
||||
<Input
|
||||
value={id_card}
|
||||
onChange={(e) => {
|
||||
setIdCard(e.target.value);
|
||||
}}
|
||||
<div className="d-flex mr-24">
|
||||
<Typography.Text>模式:</Typography.Text>
|
||||
<Select
|
||||
style={{ width: 160 }}
|
||||
placeholder="请输入身份证号"
|
||||
allowClear
|
||||
placeholder="请选择"
|
||||
value={showMode}
|
||||
onChange={(value: string) => setShowMode(value)}
|
||||
options={modes}
|
||||
/>
|
||||
</div> */}
|
||||
<div className="d-flex">
|
||||
@ -198,7 +282,7 @@ const MemberDepartmentProgressPage = () => {
|
||||
title="学员"
|
||||
dataIndex="name"
|
||||
key="name"
|
||||
width={100}
|
||||
width={150}
|
||||
render={(_, record: any) => (
|
||||
<>
|
||||
<Image
|
||||
@ -218,12 +302,12 @@ const MemberDepartmentProgressPage = () => {
|
||||
ellipsis={true}
|
||||
dataIndex="id"
|
||||
key={item.id}
|
||||
width={100}
|
||||
width={168}
|
||||
render={(_, record: any) => (
|
||||
<>
|
||||
{records[record.id] && records[record.id][item.id] ? (
|
||||
records[record.id][item.id].is_finished === 1 ? (
|
||||
<span>已完成</span>
|
||||
<span>已学完</span>
|
||||
) : (
|
||||
<>
|
||||
<span>
|
||||
@ -243,10 +327,10 @@ const MemberDepartmentProgressPage = () => {
|
||||
))}
|
||||
<Column
|
||||
fixed="right"
|
||||
title="所有课程总课时"
|
||||
title="总计课时"
|
||||
dataIndex="id"
|
||||
key="id"
|
||||
width={100}
|
||||
width={150}
|
||||
render={(_, record: any) => (
|
||||
<>
|
||||
<span>{getFinishedHours(records[record.id])}</span> /{" "}
|
||||
|
@ -58,6 +58,7 @@ const MemberImportPage = () => {
|
||||
user
|
||||
.storeBatch(2, data)
|
||||
.then(() => {
|
||||
setErrorData([]);
|
||||
message.success("导入成功!");
|
||||
navigate(-1);
|
||||
})
|
||||
@ -92,9 +93,9 @@ const MemberImportPage = () => {
|
||||
{errorData &&
|
||||
errorData.map((item: any, index: number) => {
|
||||
return (
|
||||
<span key={index} className="c-red mb-10">
|
||||
<div key={index} className="c-red mb-10">
|
||||
{item}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
@ -142,7 +142,7 @@ const MemberPage = () => {
|
||||
<Space size="small">
|
||||
<Link
|
||||
style={{ textDecoration: "none" }}
|
||||
to={`/member/learn?id=${record.id}`}
|
||||
to={`/member/learn?id=${record.id}&name=${record.name}`}
|
||||
>
|
||||
<PerButton
|
||||
type="link"
|
||||
@ -320,6 +320,7 @@ const MemberPage = () => {
|
||||
}}
|
||||
style={{ width: 160 }}
|
||||
placeholder="请输入姓名关键字"
|
||||
allowClear
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex mr-24">
|
||||
@ -331,6 +332,7 @@ const MemberPage = () => {
|
||||
}}
|
||||
style={{ width: 160 }}
|
||||
placeholder="请输入邮箱账号"
|
||||
allowClear
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
@ -359,6 +361,7 @@ const MemberPage = () => {
|
||||
/>
|
||||
<MemberCreate
|
||||
open={createVisible}
|
||||
depIds={dep_ids}
|
||||
onCancel={() => {
|
||||
setCreateVisible(false);
|
||||
setRefresh(!refresh);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import styles from "./learn.module.less";
|
||||
import { Row, Image, Table } from "antd";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Row, Image, Table, Button, Select } from "antd";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { BackBartment, DurationText } from "../../compenents";
|
||||
import { dateFormat } from "../../utils/index";
|
||||
import { user as member } from "../../api/index";
|
||||
import * as echarts from "echarts";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { duration } from "moment";
|
||||
import { MemberLearnProgressDialog } from "./compenents/progress";
|
||||
|
||||
interface DataType {
|
||||
id: React.Key;
|
||||
@ -21,22 +21,29 @@ interface DataType {
|
||||
|
||||
const MemberLearnPage = () => {
|
||||
let chartRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const result = new URLSearchParams(useLocation().search);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [size, setSize] = useState(10);
|
||||
const [list, setList] = useState<any>([]);
|
||||
const [hours, setHours] = useState<any>({});
|
||||
const [total, setTotal] = useState(0);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [loading2, setLoading2] = useState<boolean>(false);
|
||||
const [page2, setPage2] = useState(1);
|
||||
const [size2, setSize2] = useState(10);
|
||||
const [list2, setList2] = useState<any>([]);
|
||||
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 [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);
|
||||
|
||||
useEffect(() => {
|
||||
setUid(Number(result.get("id")));
|
||||
setUserName(String(result.get("name")));
|
||||
setLoading2(false);
|
||||
setRefresh2(!refresh2);
|
||||
}, [result.get("id"), result.get("name")]);
|
||||
|
||||
useEffect(() => {
|
||||
getZxtData();
|
||||
@ -46,12 +53,22 @@ const MemberLearnPage = () => {
|
||||
}, [uid]);
|
||||
|
||||
useEffect(() => {
|
||||
getLearnHours();
|
||||
}, [refresh, page, size]);
|
||||
getLearnCourses();
|
||||
}, [refresh2, uid]);
|
||||
|
||||
useEffect(() => {
|
||||
getLearnCourses();
|
||||
}, [refresh2, page2, size2]);
|
||||
if (depValue === 0) {
|
||||
return;
|
||||
}
|
||||
let arr = [...courses[depValue]];
|
||||
let arr2 = [...openCourses];
|
||||
if (arr2.length > 0) {
|
||||
var data = arr.concat(arr2);
|
||||
setCurrentCourses(data);
|
||||
} else {
|
||||
setCurrentCourses(arr);
|
||||
}
|
||||
}, [depValue]);
|
||||
|
||||
const getZxtData = () => {
|
||||
member.learnStats(uid).then((res: any) => {
|
||||
@ -127,125 +144,34 @@ const MemberLearnPage = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const getLearnHours = () => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
member
|
||||
.learnHours(uid, page, size, {
|
||||
sort_field: "",
|
||||
sort_algo: "",
|
||||
is_finished: "",
|
||||
})
|
||||
.then((res: any) => {
|
||||
setList(res.data.data);
|
||||
setHours(res.data.hours);
|
||||
setTotal(res.data.total);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getLearnCourses = () => {
|
||||
if (loading2) {
|
||||
return;
|
||||
}
|
||||
setLoading2(true);
|
||||
member
|
||||
.learnCourses(uid, page2, size2, {
|
||||
sort_field: "",
|
||||
sort_algo: "",
|
||||
is_finished: "",
|
||||
})
|
||||
.then((res: any) => {
|
||||
setList2(res.data.data);
|
||||
setCourses(res.data.courses);
|
||||
setTotal2(res.data.total);
|
||||
setLoading2(false);
|
||||
});
|
||||
member.learnAllCourses(uid).then((res: any) => {
|
||||
setList2(res.data.departments);
|
||||
setCourses(res.data.dep_courses);
|
||||
setOpenCourses(res.data.open_courses);
|
||||
setRecords(res.data.user_course_records);
|
||||
if (res.data.departments.length > 0) {
|
||||
let box: any = [];
|
||||
res.data.departments.map((item: any) => {
|
||||
box.push({
|
||||
label: item.name,
|
||||
value: String(item.id),
|
||||
});
|
||||
});
|
||||
setDepValue(Number(box[0].value));
|
||||
setDeps(box);
|
||||
} else {
|
||||
setDepValue(0);
|
||||
setDeps([]);
|
||||
}
|
||||
setLoading2(false);
|
||||
});
|
||||
};
|
||||
|
||||
const paginationProps = {
|
||||
current: page, //当前页码
|
||||
pageSize: size,
|
||||
total: total, // 总条数
|
||||
onChange: (page: number, pageSize: number) =>
|
||||
handlePageChange(page, pageSize), //改变页码的函数
|
||||
showSizeChanger: true,
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPage(page);
|
||||
setSize(pageSize);
|
||||
};
|
||||
|
||||
const paginationProps2 = {
|
||||
current: page2, //当前页码
|
||||
pageSize: size2,
|
||||
total: total2, // 总条数
|
||||
onChange: (page: number, pageSize: number) =>
|
||||
handlePageChange2(page, pageSize), //改变页码的函数
|
||||
showSizeChanger: true,
|
||||
};
|
||||
|
||||
const handlePageChange2 = (page: number, pageSize: number) => {
|
||||
setPage2(page);
|
||||
setSize2(pageSize);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{
|
||||
title: "课时标题",
|
||||
dataIndex: "title",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
<span>{hours[record.hour_id].title}</span>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "课时类型",
|
||||
dataIndex: "type",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
<span>{hours[record.hour_id].type}</span>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "总时长",
|
||||
dataIndex: "total_duration",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
<DurationText duration={record.total_duration}></DurationText>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "已学习时长",
|
||||
dataIndex: "finished_duration",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
<DurationText duration={record.finished_duration || 0}></DurationText>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "is_finished",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{record.is_finished === 1 ? <span>已学完</span> : <span>未学完</span>}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "时间",
|
||||
dataIndex: "created_at",
|
||||
render: (text: string) => <span>{dateFormat(text)}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
const column2: ColumnsType<DataType> = [
|
||||
{
|
||||
title: "课程名称",
|
||||
@ -253,13 +179,13 @@ const MemberLearnPage = () => {
|
||||
render: (_, record: any) => (
|
||||
<div className="d-flex">
|
||||
<Image
|
||||
src={courses[record.course_id].thumb}
|
||||
src={record.thumb}
|
||||
preview={false}
|
||||
width={80}
|
||||
height={60}
|
||||
style={{ borderRadius: 6 }}
|
||||
/>
|
||||
<span className="ml-8">{courses[record.course_id].title}</span>
|
||||
<span className="ml-8">{record.title}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@ -269,7 +195,9 @@ const MemberLearnPage = () => {
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
<span>
|
||||
已完成课时:{record.finished_count} / {record.hour_count}
|
||||
已完成课时:
|
||||
{(records[record.id] && records[record.id].finished_count) ||
|
||||
0} / {record.class_hour}
|
||||
</span>
|
||||
</>
|
||||
),
|
||||
@ -277,38 +205,93 @@ const MemberLearnPage = () => {
|
||||
{
|
||||
title: "第一次学习时间",
|
||||
dataIndex: "created_at",
|
||||
render: (text: string) => <span>{dateFormat(text)}</span>,
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{records[record.id] ? (
|
||||
<span>{dateFormat(records[record.id].created_at)}</span>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "学习完成时间",
|
||||
dataIndex: "finished_at",
|
||||
render: (text: string) => <span>{dateFormat(text)}</span>,
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
{records[record.id] ? (
|
||||
<span>{dateFormat(records[record.id].finished_at)}</span>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "学习进度",
|
||||
dataIndex: "is_finished",
|
||||
render: (_, record: any) => (
|
||||
<>
|
||||
<span
|
||||
className={
|
||||
Math.floor((record.finished_count / record.hour_count) * 100) >=
|
||||
100
|
||||
? "c-green"
|
||||
: "c-red"
|
||||
}
|
||||
>
|
||||
{Math.floor((record.finished_count / record.hour_count) * 100)}%
|
||||
</span>
|
||||
{records[record.id] ? (
|
||||
<span
|
||||
className={
|
||||
Math.floor(
|
||||
(records[record.id].finished_count /
|
||||
records[record.id].hour_count) *
|
||||
100
|
||||
) >= 100
|
||||
? "c-green"
|
||||
: "c-red"
|
||||
}
|
||||
>
|
||||
{Math.floor(
|
||||
(records[record.id].finished_count /
|
||||
records[record.id].hour_count) *
|
||||
100
|
||||
)}
|
||||
%
|
||||
</span>
|
||||
) : (
|
||||
<span className="c-red">0%</span>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
fixed: "right",
|
||||
width: 100,
|
||||
render: (_, record: any) => (
|
||||
<Button
|
||||
type="link"
|
||||
className="b-link c-red"
|
||||
onClick={() => {
|
||||
setcourseId(record.id);
|
||||
setVisiable(true);
|
||||
}}
|
||||
>
|
||||
明细
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="playedu-main-top mb-24">
|
||||
<MemberLearnProgressDialog
|
||||
open={visiable}
|
||||
uid={uid}
|
||||
id={courseId}
|
||||
onCancel={() => {
|
||||
setVisiable(false);
|
||||
setRefresh2(!refresh2);
|
||||
}}
|
||||
></MemberLearnProgressDialog>
|
||||
<div className="float-left mb-24">
|
||||
<BackBartment title="学员学习" />
|
||||
<BackBartment title={userName + "的学习明细"} />
|
||||
</div>
|
||||
<div className={styles["charts"]}>
|
||||
<div
|
||||
@ -321,27 +304,28 @@ const MemberLearnPage = () => {
|
||||
></div>
|
||||
</div>
|
||||
<div className="float-left mt-24">
|
||||
{list2.length > 1 && (
|
||||
<div className="d-flex mb-24">
|
||||
<span>切换部门:</span>
|
||||
<Select
|
||||
style={{ width: 160 }}
|
||||
allowClear
|
||||
placeholder="请选择部门"
|
||||
value={String(depValue)}
|
||||
onChange={(value: string) => setDepValue(Number(value))}
|
||||
options={deps}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Table
|
||||
columns={column2}
|
||||
dataSource={list2}
|
||||
dataSource={currentCourses}
|
||||
loading={loading2}
|
||||
pagination={paginationProps2}
|
||||
pagination={false}
|
||||
rowKey={(record) => record.id}
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
{/* <div className="playedu-main-top mb-24">
|
||||
<div className={styles["large-title"]}>课时学习记录</div>
|
||||
<div className="float-left mt-24">
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={list}
|
||||
loading={loading}
|
||||
pagination={paginationProps}
|
||||
rowKey={(record) => record.id}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -147,7 +147,11 @@ export const ResourceCategoryCreate: React.FC<PropInterface> = ({
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入分类名称!" }]}
|
||||
>
|
||||
<Input style={{ width: 200 }} placeholder="请输入分类名称" />
|
||||
<Input
|
||||
style={{ width: 200 }}
|
||||
allowClear
|
||||
placeholder="请输入分类名称"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
|
@ -169,7 +169,11 @@ export const ResourceCategoryUpdate: React.FC<PropInterface> = ({
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入分类名称!" }]}
|
||||
>
|
||||
<Input style={{ width: 200 }} placeholder="请输入分类名称" />
|
||||
<Input
|
||||
style={{ width: 200 }}
|
||||
allowClear
|
||||
placeholder="请输入分类名称"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
|
@ -125,14 +125,22 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入管理员姓名!" }]}
|
||||
>
|
||||
<Input style={{ width: 200 }} placeholder="请输入管理员姓名" />
|
||||
<Input
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
placeholder="请输入管理员姓名"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="邮箱"
|
||||
name="email"
|
||||
rules={[{ required: true, message: "请输入学员邮箱!" }]}
|
||||
>
|
||||
<Input style={{ width: 200 }} placeholder="请输入学员邮箱" />
|
||||
<Input
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
placeholder="请输入学员邮箱"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="密码"
|
||||
@ -140,6 +148,7 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({
|
||||
rules={[{ required: true, message: "请输入登录密码!" }]}
|
||||
>
|
||||
<Input.Password
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
placeholder="请输入登录密码"
|
||||
/>
|
||||
|
@ -133,18 +133,27 @@ export const SystemAdministratorUpdate: React.FC<PropInterface> = ({
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入管理员姓名!" }]}
|
||||
>
|
||||
<Input style={{ width: 200 }} placeholder="请输入管理员姓名" />
|
||||
<Input
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
placeholder="请输入管理员姓名"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="邮箱"
|
||||
name="email"
|
||||
rules={[{ required: true, message: "请输入学员邮箱!" }]}
|
||||
>
|
||||
<Input style={{ width: 200 }} placeholder="请输入学员邮箱" />
|
||||
<Input
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
placeholder="请输入学员邮箱"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="密码" name="password">
|
||||
<Input.Password
|
||||
style={{ width: 200 }}
|
||||
allowClear
|
||||
placeholder="请输入登录密码"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
@ -283,6 +283,7 @@ const SystemAdministratorPage = () => {
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
style={{ width: 160 }}
|
||||
placeholder="请输入管理员姓名"
|
||||
/>
|
||||
|
@ -180,6 +180,7 @@ export const SystemAdminrolesCreate: React.FC<PropInterface> = ({
|
||||
<Input
|
||||
style={{ width: 424 }}
|
||||
placeholder="请在此处输入角色名称"
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="操作权限" name="action_ids">
|
||||
|
@ -191,7 +191,11 @@ export const SystemAdminrolesUpdate: React.FC<PropInterface> = ({
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入角色名!" }]}
|
||||
>
|
||||
<Input style={{ width: 424 }} placeholder="请输入角色名" />
|
||||
<Input
|
||||
style={{ width: 424 }}
|
||||
allowClear
|
||||
placeholder="请输入角色名"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="操作权限" name="action_ids">
|
||||
<TreeSelect
|
||||
|
@ -31,7 +31,7 @@ const SystemConfigPage = () => {
|
||||
|
||||
useEffect(() => {
|
||||
getDetail();
|
||||
}, []);
|
||||
}, [tabKey]);
|
||||
|
||||
const getDetail = () => {
|
||||
appConfig.appConfig().then((res: any) => {
|
||||
@ -244,14 +244,22 @@ const SystemConfigPage = () => {
|
||||
label="网站标题"
|
||||
name="system.name"
|
||||
>
|
||||
<Input style={{ width: 274 }} placeholder="请填写网站标题" />
|
||||
<Input
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请填写网站标题"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ marginBottom: 30 }}
|
||||
label="网站页脚"
|
||||
name="system.pc_index_footer_msg"
|
||||
>
|
||||
<Input style={{ width: 274 }} placeholder="请填写网站页脚" />
|
||||
<Input
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="请填写网站页脚"
|
||||
/>
|
||||
</Form.Item>
|
||||
{/* <Form.Item
|
||||
style={{ marginBottom: 30 }}
|
||||
@ -315,7 +323,11 @@ const SystemConfigPage = () => {
|
||||
<Form.Item style={{ marginBottom: 30 }} label="跑马灯内容">
|
||||
<Space align="baseline" style={{ height: 32 }}>
|
||||
<Form.Item name="player.bullet_secret_text">
|
||||
<Input style={{ width: 274 }} placeholder="自定义跑马灯内容" />
|
||||
<Input
|
||||
style={{ width: 274 }}
|
||||
allowClear
|
||||
placeholder="自定义跑马灯内容"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Checkbox
|
||||
checked={nameChecked}
|
||||
|
@ -4,6 +4,7 @@ import { login, system } from "../api";
|
||||
|
||||
import InitPage from "../pages/init";
|
||||
import { getToken } from "../utils";
|
||||
import KeepAlive from "../compenents/keep-alive";
|
||||
|
||||
import LoginPage from "../pages/login";
|
||||
import HomePage from "../pages/home";
|
||||
@ -94,19 +95,22 @@ const routes: RouteObject[] = [
|
||||
},
|
||||
{
|
||||
path: "/member",
|
||||
element: <MemberPage />,
|
||||
},
|
||||
{
|
||||
path: "/member/import",
|
||||
element: <MemberImportPage />,
|
||||
},
|
||||
{
|
||||
path: "/member/learn",
|
||||
element: <MemberLearnPage />,
|
||||
},
|
||||
{
|
||||
path: "/member/departmentUser",
|
||||
element: <MemberDepartmentProgressPage />,
|
||||
element: <KeepAlive />,
|
||||
children: [
|
||||
{ path: "/member/index", element: <MemberPage /> },
|
||||
{
|
||||
path: "/member/import",
|
||||
element: <MemberImportPage />,
|
||||
},
|
||||
{
|
||||
path: "/member/learn",
|
||||
element: <MemberLearnPage />,
|
||||
},
|
||||
{
|
||||
path: "/member/departmentUser",
|
||||
element: <MemberDepartmentProgressPage />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/system/config/index",
|
||||
|
Loading…
x
Reference in New Issue
Block a user