mirror of
https://github.com/PlayEdu/backend
synced 2025-07-20 03:59:34 +08:00
学员学习
This commit is contained in:
parent
46e352fac3
commit
f5ad3a4eb2
@ -78,3 +78,33 @@ export function storeBatch(startLine: number, users: string[][]) {
|
|||||||
users: users,
|
users: users,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function learnStats(id: number) {
|
||||||
|
return client.get(`/backend/v1/user/${id}/learn-stats`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function learnHours(
|
||||||
|
id: number,
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
params: object
|
||||||
|
) {
|
||||||
|
return client.get(`/backend/v1/user/${id}/learn-hours`, {
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function learnCourses(
|
||||||
|
id: number,
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
params: object
|
||||||
|
) {
|
||||||
|
return client.get(`/backend/v1/user/${id}/learn-courses`, {
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -14,7 +14,7 @@ import type { ColumnsType } from "antd/es/table";
|
|||||||
import { PlusOutlined, ExclamationCircleFilled } from "@ant-design/icons";
|
import { PlusOutlined, ExclamationCircleFilled } from "@ant-design/icons";
|
||||||
import { user } from "../../api/index";
|
import { user } from "../../api/index";
|
||||||
import { dateFormat } from "../../utils/index";
|
import { dateFormat } from "../../utils/index";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, Navigate } from "react-router-dom";
|
||||||
import { TreeDepartment, PerButton } from "../../compenents";
|
import { TreeDepartment, PerButton } from "../../compenents";
|
||||||
import { MemberCreate } from "./compenents/create";
|
import { MemberCreate } from "./compenents/create";
|
||||||
import { MemberUpdate } from "./compenents/update";
|
import { MemberUpdate } from "./compenents/update";
|
||||||
@ -99,6 +99,21 @@ const MemberPage = () => {
|
|||||||
width: 160,
|
width: 160,
|
||||||
render: (_, record: any) => (
|
render: (_, record: any) => (
|
||||||
<Space size="small">
|
<Space size="small">
|
||||||
|
<Link
|
||||||
|
style={{ textDecoration: "none" }}
|
||||||
|
to={`/member/learn?id=${record.id}`}
|
||||||
|
>
|
||||||
|
<PerButton
|
||||||
|
type="link"
|
||||||
|
text="学习"
|
||||||
|
class="b-link c-red"
|
||||||
|
icon={null}
|
||||||
|
p="user-learn"
|
||||||
|
onClick={() => null}
|
||||||
|
disabled={null}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<div className="form-column"></div>
|
||||||
<PerButton
|
<PerButton
|
||||||
type="link"
|
type="link"
|
||||||
text="编辑"
|
text="编辑"
|
||||||
|
15
src/pages/member/learn.module.less
Normal file
15
src/pages/member/learn.module.less
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.large-title {
|
||||||
|
width: 100%;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts {
|
||||||
|
width: 100%;
|
||||||
|
height: 320px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
320
src/pages/member/learn.tsx
Normal file
320
src/pages/member/learn.tsx
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
import styles from "./learn.module.less";
|
||||||
|
import { Row, Col, Image, message, Table } 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";
|
||||||
|
import * as echarts from "echarts";
|
||||||
|
import type { ColumnsType } from "antd/es/table";
|
||||||
|
|
||||||
|
interface DataType {
|
||||||
|
id: React.Key;
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
created_at: string;
|
||||||
|
total_duration: number;
|
||||||
|
finished_duration: number;
|
||||||
|
is_finished: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MemberLearnPage = () => {
|
||||||
|
let chartRef = useRef(null);
|
||||||
|
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 [total2, setTotal2] = useState(0);
|
||||||
|
const [refresh2, setRefresh2] = useState(false);
|
||||||
|
const [uid, setUid] = useState(Number(result.get("id")));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getZxtData();
|
||||||
|
return () => {
|
||||||
|
window.onresize = null;
|
||||||
|
};
|
||||||
|
}, [uid]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getLearnHours();
|
||||||
|
}, [refresh, page, size]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getLearnCourses();
|
||||||
|
}, [refresh2, page2, size2]);
|
||||||
|
|
||||||
|
const getZxtData = () => {
|
||||||
|
member.learnStats(uid).then((res: any) => {
|
||||||
|
renderView(res.data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderView = (params: any) => {
|
||||||
|
const timeData: any = [];
|
||||||
|
const valueData: any = [];
|
||||||
|
params.map((item: any) => {
|
||||||
|
timeData.push(item.key);
|
||||||
|
valueData.push(item.value / 1000);
|
||||||
|
});
|
||||||
|
let dom: any = chartRef.current;
|
||||||
|
let myChart = echarts.init(dom);
|
||||||
|
myChart.setOption({
|
||||||
|
tooltip: {
|
||||||
|
trigger: "axis",
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ["每日学习时长"],
|
||||||
|
x: "right",
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: "3%",
|
||||||
|
right: "4%",
|
||||||
|
bottom: "3%",
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
boundaryGap: false,
|
||||||
|
data: timeData,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value",
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "每日学习时长(秒)",
|
||||||
|
type: "line",
|
||||||
|
data: valueData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
window.onresize = () => {
|
||||||
|
myChart.resize();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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: "课程",
|
||||||
|
dataIndex: "title",
|
||||||
|
render: (_, record: any) => (
|
||||||
|
<div className="d-flex">
|
||||||
|
<Image
|
||||||
|
src={courses[record.course_id].thumb}
|
||||||
|
preview={false}
|
||||||
|
width={80}
|
||||||
|
height={60}
|
||||||
|
style={{ borderRadius: 6 }}
|
||||||
|
/>
|
||||||
|
<span className="ml-8">{courses[record.course_id].title}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "总课时",
|
||||||
|
dataIndex: "total_duration",
|
||||||
|
render: (_, record: any) => (
|
||||||
|
<>
|
||||||
|
<span>{record.hour_count}</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "已学习课时",
|
||||||
|
dataIndex: "finished_duration",
|
||||||
|
render: (_, record: any) => (
|
||||||
|
<>
|
||||||
|
<span>{record.finished_count}</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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>,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row className="playedu-main-top mb-24">
|
||||||
|
<div className="float-left mb-24">
|
||||||
|
<BackBartment title="学员学习" />
|
||||||
|
</div>
|
||||||
|
<div className={styles["large-title"]}>学习时长统计</div>
|
||||||
|
<div className={styles["charts"]}>
|
||||||
|
<div
|
||||||
|
ref={chartRef}
|
||||||
|
style={{
|
||||||
|
width: "100% !important",
|
||||||
|
height: 300,
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</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>
|
||||||
|
<div className="playedu-main-top">
|
||||||
|
<div className={styles["large-title"]}>线上课学习记录</div>
|
||||||
|
<div className="float-left mt-24">
|
||||||
|
<Table
|
||||||
|
columns={column2}
|
||||||
|
dataSource={list2}
|
||||||
|
loading={loading2}
|
||||||
|
pagination={paginationProps2}
|
||||||
|
rowKey={(record) => record.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default MemberLearnPage;
|
@ -16,6 +16,7 @@ import CoursePage from "../pages/course/index";
|
|||||||
import CourseUserPage from "../pages/course/user";
|
import CourseUserPage from "../pages/course/user";
|
||||||
import MemberPage from "../pages/member";
|
import MemberPage from "../pages/member";
|
||||||
import MemberImportPage from "../pages/member/import";
|
import MemberImportPage from "../pages/member/import";
|
||||||
|
import MemberLearnPage from "../pages/member/learn";
|
||||||
import SystemConfigPage from "../pages/system/config";
|
import SystemConfigPage from "../pages/system/config";
|
||||||
import SystemAdministratorPage from "../pages/system/administrator";
|
import SystemAdministratorPage from "../pages/system/administrator";
|
||||||
import SystemAdminrolesPage from "../pages/system/adminroles";
|
import SystemAdminrolesPage from "../pages/system/adminroles";
|
||||||
@ -32,7 +33,7 @@ if (getToken()) {
|
|||||||
try {
|
try {
|
||||||
let configRes: any = await system.getSystemConfig();
|
let configRes: any = await system.getSystemConfig();
|
||||||
let userRes: any = await login.getUser();
|
let userRes: any = await login.getUser();
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
default: (
|
default: (
|
||||||
<InitPage configData={configRes.data} loginData={userRes.data} />
|
<InitPage configData={configRes.data} loginData={userRes.data} />
|
||||||
@ -98,6 +99,10 @@ const routes: RouteObject[] = [
|
|||||||
path: "/member/import",
|
path: "/member/import",
|
||||||
element: <MemberImportPage />,
|
element: <MemberImportPage />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/member/learn",
|
||||||
|
element: <MemberLearnPage />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/system/config/index",
|
path: "/system/config/index",
|
||||||
element: <SystemConfigPage />,
|
element: <SystemConfigPage />,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user