safety-eval-service-frontend/src/pages/Container/EduTraining/ClassManage/Add/CourseTab.js

301 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { useEffect, useState } from "react";
import { Badge, Button, Form, Input, Modal, Table, message } from "antd";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import SearchForm from "~/components/SearchForm";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_COURSEWARE } from "~/enumerate/namespace";
import { COURSEWARE_STATUS_MAP } from "~/enumerate/constant";
/** 视频累计时长(小时)→ “X分钟Y秒” */
const formatVideoDuration = (hours) => {
if (hours == null) return "-";
const totalSeconds = Math.round(hours * 3600);
return `${Math.floor(totalSeconds / 60)}分钟${totalSeconds % 60}`;
};
/** 班级详情 — 课程 Tab班级课程查询 */
function ClassCourseTab(props) {
const { classId } = props;
const [searchForm] = Form.useForm();
const [query, setQuery] = useState({ current: 1, size: 10 });
const [selectOpen, setSelectOpen] = useState(false);
// 本地暂存:接口未就绪,确认选择的课程先追加到表格展示
const [pendingCourses, setPendingCourses] = useState([]);
const {
classCourseList: dataSource,
classCourseTotal: total,
classCourseLoading: loading,
} = props.courseware || {};
const tableSource = Array.isArray(dataSource) ? dataSource : [];
// 本地暂存行仅在第一页合并展示,避免翻页重复
const mergedSource =
query.current === 1 ? [...pendingCourses, ...tableSource] : tableSource;
const handleSearch = (params = query) => {
props.classCoursePage({ ...params, classId });
};
useEffect(() => {
if (!classId) return;
handleSearch(query);
}, [classId]);
const columns = [
{
title: "序号",
width: 70,
fixed: "left",
render: (_, __, index) => (query.current - 1) * query.size + index + 1,
},
{
title: "课程名称",
dataIndex: "courseName",
width: 320,
ellipsis: true,
render: (v) => v || "-",
},
{
title: "要求完成总学时",
dataIndex: "requiredTotalHours",
width: 130,
render: (v) => (v == null ? "-" : v),
},
{
title: "视频累计时长",
dataIndex: "videoDuration",
width: 130,
render: (v) => formatVideoDuration(v),
},
{
title: "上课学员数",
dataIndex: "studentCount",
width: 110,
render: (v) => (v == null ? "-" : v),
},
{
title: "已完成学员数",
dataIndex: "completedCount",
width: 120,
render: (v) => (v == null ? "-" : v),
},
{
title: "操作",
width: 140,
fixed: "right",
render: () => (
<TableAction>
<Button
type="link"
size="small"
onClick={() => message.info("课程查看功能待接入")}
>
查看
</Button>
<Button
danger
type="link"
size="small"
onClick={() => message.info("删除课程接口待接入")}
>
删除
</Button>
</TableAction>
),
},
];
return (
<>
<SearchForm
style={{ marginBottom: 16 }}
form={searchForm}
loading={loading}
formLine={[
<Form.Item key="courseName" name="courseName">
<ControlWrapper.Input
label="课程名称"
placeholder="请输入"
allowClear
maxLength={50}
/>
</Form.Item>,
]}
onReset={(value) => {
const next = { ...value, current: 1, size: 10 };
setQuery(next);
handleSearch(next);
}}
onFinish={(value) => {
const next = { ...value, current: 1, size: 10 };
setQuery(next);
handleSearch(next);
}}
/>
<Button
type="primary"
style={{ marginBottom: 12 }}
onClick={() => setSelectOpen(true)}
>
选择课程
</Button>
<Table
rowKey="id"
columns={columns}
dataSource={mergedSource}
scroll={{ x: 1000 }}
loading={loading}
pagination={{
total: total + pendingCourses.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
current: query.current,
pageSize: query.size,
onChange: (page, pageSize) => {
const next = { ...query, current: page, size: pageSize };
setQuery(next);
handleSearch(next);
},
}}
/>
<CourseSelectModal
open={selectOpen}
loading={props.courseware?.courseLoading}
dataSource={props.courseware?.courseList}
total={props.courseware?.courseTotal}
requestPage={props.coursePage}
selectedIds={[
...tableSource.map((item) => item.courseId),
...pendingCourses.map((item) => item.courseId),
]}
onOk={(rows) => {
// 班级添加课程接口待接入:先本地暂存并填充表格,接入后改为提交请求 + handleSearch() 刷新
setPendingCourses((prev) => [
...prev,
...rows.map(({ id, courseName, classHourDuration }) => ({
id,
courseId: id,
courseName,
requiredTotalHours: classHourDuration,
})),
]);
setSelectOpen(false);
}}
onCancel={() => setSelectOpen(false)}
/>
</>
);
}
/** 选择课程弹窗:数据源同培训课程管理列表 */
function CourseSelectModal({
open,
loading,
dataSource,
total,
requestPage,
selectedIds,
onOk,
onCancel,
}) {
const [query, setQuery] = useState({ current: 1, size: 10 });
const [keyword, setKeyword] = useState("");
const [selectedRows, setSelectedRows] = useState([]);
const handleSearch = (params) => {
setQuery(params);
requestPage(params);
};
useEffect(() => {
if (open) {
setKeyword("");
setSelectedRows([]);
handleSearch({ current: 1, size: 10 });
}
}, [open]);
return (
<Modal
open={open}
title="选择课程"
width={860}
loading={loading}
destroyOnHidden
okText={`确定${selectedRows.length ? `(已选 ${selectedRows.length}` : ""}`}
okButtonProps={{ disabled: !selectedRows.length }}
onOk={() => onOk(selectedRows)}
onCancel={onCancel}
>
<Input.Search
placeholder="请输入课程名称"
allowClear
style={{ width: 280, marginBottom: 12 }}
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
onSearch={(value) =>
handleSearch({ current: 1, size: query.size, courseName: value })
}
/>
<Table
rowKey="id"
size="small"
columns={[
{ title: "课程名称", dataIndex: "courseName", ellipsis: true },
{
title: "课程描述",
dataIndex: "courseDescription",
width: 260,
ellipsis: true,
render: (v) => v || "-",
},
{
title: "课程状态",
dataIndex: "status",
width: 90,
render: (v) => {
const item = COURSEWARE_STATUS_MAP[v];
return item ? <Badge status={item.status} text={item.label} /> : "-";
},
},
{
title: "总课时",
dataIndex: "classHourDuration",
width: 90,
render: (v) => (v == null ? "-" : v),
},
]}
dataSource={Array.isArray(dataSource) ? dataSource : []}
rowSelection={{
preserveSelectedRowKeys: true,
selectedRowKeys: selectedRows.map((r) => r.id),
getCheckboxProps: (record) => ({
disabled: selectedIds.includes(record.id),
}),
onChange: (_, rows) => setSelectedRows(rows),
}}
pagination={{
total,
size: "small",
showTotal: (t) => `${t}`,
current: Number(query.current) || 1,
pageSize: Number(query.size) || 10,
onChange: (page, pageSize) =>
handleSearch({
current: page,
size: pageSize,
courseName: query.courseName,
}),
}}
/>
</Modal>
);
}
export default Connect([NS_COURSEWARE], true)(ClassCourseTab);