301 lines
8.4 KiB
JavaScript
301 lines
8.4 KiB
JavaScript
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);
|