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

371 lines
10 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 {
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 });
const [selectOpen, setSelectOpen] = useState(false);
const {
classStudentList: dataSource,
classStudentTotal: total,
classStudentLoading: loading,
} = props.courseware || {};
const tableSource = Array.isArray(dataSource) ? dataSource : [];
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",
render: (_, record) => (
<TableAction>
<Button
type="link"
size="small"
onClick={() => message.info("学员查看功能待接入")}
>
查看
</Button>
<Button
danger
type="link"
size="small"
onClick={() =>
Modal.confirm({
title: "确认移除该学员?",
content: `确认将学员「${record.studentName || ""}」从本班移除吗?`,
onOk: async () => {
const res = await props.classStudentRemove({
data: record.id,
});
if (res?.success !== false) {
message.success("移除成功");
handleSearch();
}
},
})
}
>
从本班移除
</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 }}
onClick={() => setSelectOpen(true)}
>
选择学员
</Button>
<Table
rowKey="id"
columns={columns}
dataSource={tableSource}
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);
},
}}
/>
<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)}
/>
</>
);
}
/** 选择学员弹窗:数据源为人员信息列表 */
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>
);
}
export default Connect([NS_COURSEWARE], true)(ClassStudentTab);