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

371 lines
10 KiB
JavaScript
Raw Normal View History

2026-08-21 17:43:57 +08:00
import { useEffect, useState } from "react";
2026-08-21 18:52:24 +08:00
import { Badge, Button, Form, Input, Modal, Table, message } from "antd";
2026-08-21 17:43:57 +08:00
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 {
CLASS_STUDENT_EXAM_STATUS_OPTIONS,
CLASS_STUDENT_FACE_AUTH_MAP,
CLASS_STUDENT_STUDY_STATUS_MAP,
CLASS_STUDENT_EXAM_STATUS_MAP,
CLASS_STUDENT_STUDY_STATUS_OPTIONS,
} from "~/enumerate/constant";
/** 搜索表单值 → 查询参数入班时间范围拆为起止字段yyyy-MM-dd接口暂未支持先行透传 */
const buildQuery = (value) => {
const { entryTimeRange, ...rest } = value || {};
return {
...rest,
...(entryTimeRange?.[0]
? { entryTimeStart: entryTimeRange[0].format("YYYY-MM-DD") }
: {}),
...(entryTimeRange?.[1]
? { entryTimeEnd: entryTimeRange[1].format("YYYY-MM-DD") }
: {}),
current: 1,
size: 10,
};
};
/** 班级详情 — 学员 Tab班级学员查询 */
function ClassStudentTab(props) {
const { classId } = props;
const [searchForm] = Form.useForm();
const [query, setQuery] = useState({ current: 1, size: 10 });
2026-08-21 18:52:24 +08:00
const [selectOpen, setSelectOpen] = useState(false);
2026-08-21 17:43:57 +08:00
const {
classStudentList: dataSource,
classStudentTotal: total,
classStudentLoading: loading,
} = props.courseware || {};
2026-08-21 18:52:24 +08:00
const tableSource = Array.isArray(dataSource) ? dataSource : [];
2026-08-21 17:43:57 +08:00
const handleSearch = (params = query) => {
props.classStudentPage({ ...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: "studentName", width: 100 },
{
title: "参训单位",
dataIndex: "unitName",
width: 160,
ellipsis: true,
render: (v) => v || "-",
},
{ title: "部门", dataIndex: "department", width: 120, render: (v) => v || "-" },
{ title: "手机号", dataIndex: "phone", width: 130, render: (v) => v || "-" },
{ title: "档案编号", dataIndex: "fileNumber", width: 220, ellipsis: true },
{
title: "人脸认证",
dataIndex: "faceAuth",
width: 100,
render: (v) => {
const item = CLASS_STUDENT_FACE_AUTH_MAP[v];
return item ? <Badge status={item.status} text={item.label} /> : "-";
},
},
{
title: "要求学时",
dataIndex: "requiredHours",
width: 100,
render: (v) => (v == null ? "-" : v),
},
{
title: "已完成学时",
dataIndex: "completedHours",
width: 110,
render: (v) => (v == null ? "-" : v),
},
{
title: "学习状态",
dataIndex: "studyStatus",
width: 100,
render: (v) => CLASS_STUDENT_STUDY_STATUS_MAP[v] ?? "-",
},
{
title: "考试状态",
dataIndex: "examStatus",
width: 100,
render: (v) => CLASS_STUDENT_EXAM_STATUS_MAP[v] ?? "-",
},
{
title: "操作人",
dataIndex: "createName",
width: 100,
},
{
title: "操作",
width: 160,
fixed: "right",
2026-08-21 18:52:24 +08:00
render: (_, record) => (
2026-08-21 17:43:57 +08:00
<TableAction>
<Button
type="link"
size="small"
onClick={() => message.info("学员查看功能待接入")}
>
查看
</Button>
<Button
danger
type="link"
size="small"
2026-08-21 18:52:24 +08:00
onClick={() =>
Modal.confirm({
title: "确认移除该学员?",
content: `确认将学员「${record.studentName || ""}」从本班移除吗?`,
onOk: async () => {
const res = await props.classStudentRemove({
data: record.id,
});
if (res?.success !== false) {
message.success("移除成功");
handleSearch();
}
},
})
}
2026-08-21 17:43:57 +08:00
>
从本班移除
</Button>
</TableAction>
),
},
];
return (
<>
<SearchForm
style={{ marginBottom: 16 }}
form={searchForm}
loading={loading}
formLine={[
<Form.Item key="studentName" name="studentName">
<ControlWrapper.Input
label="姓名"
placeholder="请输入"
allowClear
maxLength={50}
/>
</Form.Item>,
<Form.Item key="studyStatus" name="studyStatus">
<ControlWrapper.Select
label="学习状态"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
options={CLASS_STUDENT_STUDY_STATUS_OPTIONS}
/>
</Form.Item>,
<Form.Item key="examStatus" name="examStatus">
<ControlWrapper.Select
label="考试状态"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
options={CLASS_STUDENT_EXAM_STATUS_OPTIONS}
/>
</Form.Item>,
<Form.Item key="entryTimeRange" name="entryTimeRange">
<ControlWrapper.DatePicker.RangePicker label="入班时间" />
</Form.Item>,
]}
onReset={(value) => {
const next = buildQuery(value);
setQuery(next);
handleSearch(next);
}}
onFinish={(value) => {
const next = buildQuery(value);
setQuery(next);
handleSearch(next);
}}
/>
<Button
type="primary"
style={{ marginBottom: 12 }}
2026-08-21 18:52:24 +08:00
onClick={() => setSelectOpen(true)}
2026-08-21 17:43:57 +08:00
>
选择学员
</Button>
<Table
rowKey="id"
columns={columns}
2026-08-21 18:52:24 +08:00
dataSource={tableSource}
2026-08-21 17:43:57 +08:00
scroll={{ x: 1400 }}
loading={loading}
pagination={{
total,
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);
},
}}
/>
2026-08-21 18:52:24 +08:00
<StudentSelectModal
open={selectOpen}
loading={props.courseware?.orgPersonnelLoading}
confirmLoading={props.courseware?.classStudentConfirmLoading}
dataSource={props.courseware?.orgPersonnelList}
total={props.courseware?.orgPersonnelTotal}
requestPage={props.orgPersonnelPage}
selectedPhones={tableSource.map((item) => item.phone)}
onOk={async (rows) => {
// 批量新增班级学员
const res = await props.classStudentBatchSave(
rows.map(({ userName, deptName, account }) => ({
classId,
studentName: userName,
department: deptName,
phone: account,
})),
);
if (res?.success === false) return;
message.success("添加成功");
setSelectOpen(false);
handleSearch();
}}
onCancel={() => setSelectOpen(false)}
/>
2026-08-21 17:43:57 +08:00
</>
);
}
2026-08-21 18:52:24 +08:00
/** 选择学员弹窗:数据源为人员信息列表 */
function StudentSelectModal({
open,
loading,
confirmLoading,
dataSource,
total,
requestPage,
selectedPhones,
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,
employmentStatusCode: 1,
});
};
useEffect(() => {
if (open) {
setKeyword("");
setSelectedRows([]);
handleSearch({ current: 1, size: 10 });
}
}, [open]);
return (
<Modal
open={open}
title="选择学员"
width={860}
confirmLoading={confirmLoading}
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, userName: value })
}
/>
<Table
rowKey="id"
size="small"
loading={loading}
columns={[
{
title: "序号",
width: 60,
render: (_, __, index) =>
(query.current - 1) * query.size + index + 1,
},
{
title: "公司名称",
dataIndex: "orgName",
ellipsis: true,
render: (v) => v || "-",
},
{
title: "部门",
dataIndex: "deptName",
width: 140,
render: (v) => v || "-",
},
{ title: "学员姓名", dataIndex: "userName", width: 100 },
{
title: "手机号",
dataIndex: "account",
width: 130,
render: (v) => v || "-",
},
]}
dataSource={Array.isArray(dataSource) ? dataSource : []}
rowSelection={{
preserveSelectedRowKeys: true,
selectedRowKeys: selectedRows.map((r) => r.id),
getCheckboxProps: (record) => ({
disabled: selectedPhones.includes(record.account),
}),
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,
userName: query.userName,
}),
}}
/>
</Modal>
);
}
2026-08-21 17:43:57 +08:00
export default Connect([NS_COURSEWARE], true)(ClassStudentTab);