dev-tmp1
tangjie 2026-08-21 18:52:24 +08:00
parent 03332b3d05
commit 3b51963fa1
9 changed files with 471 additions and 19 deletions

View File

@ -0,0 +1,69 @@
{
"openapi": "3.0.1",
"info": {
"title": "批量新增班级学员",
"version": "1.0.0",
"description": "同步自 Apifox 项目api-505094943班级学员"
},
"paths": {
"/safetyEval/classStudent/batchSave": {
"post": {
"summary": "批量新增班级学员",
"description": "批量新增班级学员",
"tags": ["班级学员"],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": { "$ref": "#/components/schemas/ClassStudentDTO" }
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/SingleResponseBoolean" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"ClassStudentDTO": {
"type": "object",
"properties": {
"id": { "type": "string", "description": "主键(后端 Long前端按字符串保证精度" },
"classId": { "type": "string", "description": "关联班级ID后端 Long前端按字符串保证精度" },
"studentName": { "type": "string", "description": "姓名" },
"unitName": { "type": "string", "description": "参训单位" },
"department": { "type": "string", "description": "部门" },
"phone": { "type": "string", "description": "手机号" },
"fileNumber": { "type": "string", "description": "档案编号" },
"faceAuth": { "type": "integer", "description": "人脸认证0-未认证1-已认证)" },
"requiredHours": { "type": "number", "description": "要求学时(小时)" },
"completedHours": { "type": "number", "description": "已完成学时(小时)" },
"studyStatus": { "type": "integer", "description": "学习状态0-未学习1-学习中2-已学完3-已完成" },
"examStatus": { "type": "integer", "description": "考试状态0-不考试1-待考试2-考试未通过3-考试通过4-未参加" },
"entryTime": { "type": "string", "description": "入班时间" }
}
},
"SingleResponseBoolean": {
"type": "object",
"properties": {
"success": { "type": "boolean" },
"code": { "type": "string", "description": "响应码" },
"message": { "type": "string", "description": "响应消息" },
"errMessage": { "type": "string", "description": "错误响应消息" },
"data": { "type": "boolean" }
}
}
}
}
}

View File

@ -0,0 +1,46 @@
/**
* POST /safetyEval/classStudent/batchSave
* spec.json
*/
/** 班级学员 DTO请求体为数组 */
export interface ClassStudentDTO {
/** 主键(后端 Long前端按字符串保证精度 */
id?: string;
/** 关联班级ID后端 Long前端按字符串保证精度 */
classId?: string;
/** 姓名 */
studentName?: string;
/** 参训单位 */
unitName?: string;
/** 部门 */
department?: string;
/** 手机号 */
phone?: string;
/** 档案编号 */
fileNumber?: string;
/** 人脸认证0-未认证1-已认证) */
faceAuth?: 0 | 1;
/** 要求学时(小时) */
requiredHours?: number;
/** 已完成学时(小时) */
completedHours?: number;
/** 学习状态0-未学习1-学习中2-已学完3-已完成 */
studyStatus?: 0 | 1 | 2 | 3;
/** 考试状态0-不考试1-待考试2-考试未通过3-考试通过4-未参加 */
examStatus?: 0 | 1 | 2 | 3 | 4;
/** 入班时间 */
entryTime?: string;
}
/** 请求体ClassStudentDTO 数组 */
export type ClassStudentBatchSaveBody = ClassStudentDTO[];
/** 响应体 */
export interface SingleResponseBoolean {
success?: boolean;
code?: string;
message?: string;
errMessage?: string;
data?: boolean;
}

View File

@ -0,0 +1,57 @@
{
"openapi": "3.0.1",
"info": {
"title": "删除班级学员",
"version": "1.0.0",
"description": "同步自 Apifox 项目api-503642502班级学员"
},
"paths": {
"/safetyEval/classStudent/delete": {
"post": {
"summary": "删除班级学员",
"tags": ["班级学员"],
"requestBody": {
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ReqLong" }
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/SingleResponseVoid" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"ReqLong": {
"type": "object",
"properties": {
"data": {
"type": "string",
"description": "班级学员主键ID后端 Long / Apifox 显示 int64前端按字符串保证精度"
}
},
"required": ["data"]
},
"SingleResponseVoid": {
"type": "object",
"properties": {
"success": { "type": "boolean", "description": "是否成功" },
"code": { "type": "string", "description": "响应码" },
"message": { "type": "string", "description": "响应消息" },
"errMessage": { "type": "string", "description": "错误响应消息" },
"data": { "type": "null", "description": "响应数据" }
}
}
}
}
}

View File

@ -0,0 +1,18 @@
/**
* POST /safetyEval/classStudent/delete
* spec.json
*/
/** 请求体:{ data: 班级学员主键ID }(后端 Long前端按字符串传递保证精度 */
export interface ClassStudentDeleteBody {
data: string;
}
/** 响应体 */
export interface SingleResponseVoid {
success?: boolean;
code?: string;
message?: string;
errMessage?: string;
data?: null;
}

View File

@ -0,0 +1,62 @@
{
"openapi": "3.0.1",
"info": {
"title": "分页查询人员信息",
"version": "1.0.0",
"description": "同步自 Apifox 项目api-483274085人员信息管理此处仅收录选择学员弹窗所需字段"
},
"paths": {
"/safetyEval/org-personnel/page": {
"get": {
"summary": "分页查询人员信息",
"tags": ["人员信息管理"],
"parameters": [
{ "name": "current", "in": "query", "description": "当前页默认1", "required": false, "schema": { "type": "integer" } },
{ "name": "size", "in": "query", "description": "每页条数默认10", "required": false, "schema": { "type": "integer" } },
{ "name": "keyword", "in": "query", "description": "keyword", "required": false, "schema": { "type": "string" } },
{ "name": "orgId", "in": "query", "description": "单位id后端 Long前端按字符串传递保证精度", "required": false, "schema": { "type": "string" } },
{ "name": "deptId", "in": "query", "description": "部门id后端 Long前端按字符串传递保证精度", "required": false, "schema": { "type": "string" } },
{ "name": "userName", "in": "query", "description": "用户名称/姓名", "required": false, "schema": { "type": "string" } },
{ "name": "account", "in": "query", "description": "账号", "required": false, "schema": { "type": "string" } },
{ "name": "employmentStatusCode", "in": "query", "description": "就职状态编码(1在职2离职)", "required": false, "schema": { "type": "integer" } }
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/PageResponseOrgPersonnelCO" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"PageResponseOrgPersonnelCO": {
"type": "object",
"properties": {
"success": { "type": "boolean", "description": "是否成功" },
"code": { "type": "string", "description": "响应码" },
"message": { "type": "string", "description": "响应消息" },
"errMessage": { "type": "string", "description": "错误响应消息" },
"data": { "type": "array", "items": { "$ref": "#/components/schemas/OrgPersonnelCO" }, "description": "响应数据" },
"total": { "type": "integer", "description": "总条数" }
}
},
"OrgPersonnelCO": {
"type": "object",
"description": "人员信息CO选择学员弹窗仅使用下列字段",
"properties": {
"id": { "type": "string", "description": "主键ID后端 Long前端按字符串保证精度" },
"userName": { "type": "string", "description": "用户姓名" },
"account": { "type": "string", "description": "账号(即手机号)" },
"deptName": { "type": "string", "description": "部门名称(列表展示,非持久化)" },
"orgName": { "type": "string", "description": "公司名称Spec 未声明该字段,前端按存在处理,缺失显示 -" }
}
}
}
}
}

View File

@ -0,0 +1,40 @@
/**
* GET /safetyEval/org-personnel/page
* spec.json
*/
/** Query 参数(仅收录弹窗使用项) */
export interface OrgPersonnelPageQuery {
/** 当前页默认1 */
current?: number;
/** 每页条数默认10 */
size?: number;
/** 用户名称/姓名 */
userName?: string;
/** 账号 */
account?: string;
}
/** 人员信息 CO弹窗使用字段 */
export interface OrgPersonnelCO {
/** 主键ID后端 Long前端按字符串保证精度 */
id?: string;
/** 用户姓名 */
userName?: string;
/** 账号(即手机号) */
account?: string;
/** 部门名称 */
deptName?: string;
/** 公司名称Spec 未声明,按存在处理) */
orgName?: string;
}
/** 响应体 */
export interface PageResponseOrgPersonnelCO {
success?: boolean;
code?: string;
message?: string;
errMessage?: string;
data?: OrgPersonnelCO[];
total?: number;
}

View File

@ -114,3 +114,22 @@ export const classCourseRemove = declareRequest(
"classCourseLoading",
"Post > @/safetyEval/classCourse/delete",
);
/** 分页查询人员信息(选择学员弹窗数据源,复用人员信息管理接口) */
export const orgPersonnelPage = declareRequest(
"orgPersonnelLoading",
"Get > /safetyEval/org-personnel/page",
"orgPersonnelList: [] | res.data || [] & orgPersonnelTotal: 0 | res.total || 0",
);
/** 批量新增班级学员(请求体为 ClassStudentDTO 数组) */
export const classStudentBatchSave = declareRequest(
"classStudentConfirmLoading",
"Post > @/safetyEval/classStudent/batchSave",
);
/** 删除班级学员(请求体:{ data: 班级学员主键id } */
export const classStudentRemove = declareRequest(
"classStudentLoading",
"Post > @/safetyEval/classStudent/delete",
);

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { Badge, Button, Form, Table, message } from "antd";
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";
@ -34,11 +34,13 @@ 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 });
@ -110,7 +112,7 @@ function ClassStudentTab(props) {
title: "操作",
width: 160,
fixed: "right",
render: () => (
render: (_, record) => (
<TableAction>
<Button
type="link"
@ -123,7 +125,21 @@ function ClassStudentTab(props) {
danger
type="link"
size="small"
onClick={() => message.info("移除学员接口待接入")}
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>
@ -184,7 +200,7 @@ function ClassStudentTab(props) {
<Button
type="primary"
style={{ marginBottom: 12 }}
onClick={() => message.info("选择学员功能待接入")}
onClick={() => setSelectOpen(true)}
>
选择学员
</Button>
@ -192,7 +208,7 @@ function ClassStudentTab(props) {
<Table
rowKey="id"
columns={columns}
dataSource={Array.isArray(dataSource) ? dataSource : []}
dataSource={tableSource}
scroll={{ x: 1400 }}
loading={loading}
pagination={{
@ -209,8 +225,146 @@ function ClassStudentTab(props) {
},
}}
/>
<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);

View File

@ -155,20 +155,7 @@ function ClassManagePage(props) {
>
编辑
</Button>
<Button
type="link"
size="small"
onClick={() => message.info("学员列表功能待接入")}
>
学员列表
</Button>
<Button
type="link"
size="small"
onClick={() => message.info("课程列表功能待接入")}
>
课程列表
</Button>
{record.status === 2 && (
<Button
type="link"