dev-tmp1
tangjie 2026-08-18 16:16:14 +08:00
parent bfd14bed5f
commit d0339c7f8f
7 changed files with 510 additions and 4 deletions

View File

@ -12,9 +12,9 @@ module.exports = {
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095 // 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
//API_HOST: "http://localhost:80", //API_HOST: "http://localhost:80",
// API_HOST: "http://192.168.0.134", API_HOST: "http://192.168.0.134",
// API_HOST: "http://192.168.0.150", //太浅 // API_HOST: "http://192.168.0.150", //太浅
API_HOST: "https://gbs-gateway.qhdsafety.com", // API_HOST: "https://gbs-gateway.qhdsafety.com",
//API_HOST: "http://192.168.0.103", //huwei //API_HOST: "http://192.168.0.103", //huwei
}, },
production: { production: {

View File

@ -0,0 +1,28 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
/** 教育培训 — 培训课件管理 */
/** 分页查询课件 */
export const coursewarePage = declareRequest(
"coursewareLoading",
"Get > /safetyEval/coursewareManagement/page",
"coursewareList: [] | res.data || [] & coursewareTotal: 0 | res.total || 0",
);
/** 新增课件 */
export const coursewareAdd = declareRequest(
"coursewareConfirmLoading",
"Post > @/safetyEval/coursewareManagement/save",
);
/** 修改课件(启用/禁用等) */
export const coursewareModify = declareRequest(
"coursewareLoading",
"Post > @/safetyEval/coursewareManagement/modify",
);
/** 删除课件 */
export const coursewareRemove = declareRequest(
"coursewareLoading",
"Post > @/safetyEval/coursewareManagement/delete",
);

View File

@ -621,3 +621,40 @@ export const PROFESSIONAL_MAJOR_MAP = PROFESSIONAL_MAJOR_OPTIONS.reduce(
(acc, cur) => ({ ...acc, [cur.value]: cur.label }), (acc, cur) => ({ ...acc, [cur.value]: cur.label }),
{}, {},
); );
/** 培训类型(教育培训-课件管理,后端暂无枚举,先约定编码) */
export const COURSEWARE_TRAINING_TYPE_OPTIONS = [
{ label: "安全生产相关法律法规", value: "SAFETY_LAW" },
{ label: "安全管理知识", value: "SAFETY_MANAGE" },
{ label: "安全技能实操", value: "SAFETY_SKILL" },
{ label: "应急救援能力", value: "EMERGENCY_RESCUE" },
{ label: "职业健康防护", value: "OCCUPATIONAL_HEALTH" },
{ label: "事故案例警示", value: "ACCIDENT_CASE" },
];
export const COURSEWARE_TRAINING_TYPE_MAP = COURSEWARE_TRAINING_TYPE_OPTIONS.reduce(
(acc, cur) => ({ ...acc, [cur.value]: cur.label }),
{},
);
/** 课件状态1启用/0禁用 */
export const COURSEWARE_STATUS_OPTIONS = [
{ label: "启用", value: 1 },
{ label: "禁用", value: 0 },
];
export const COURSEWARE_STATUS_MAP = {
1: { label: "启用", status: "success" },
0: { label: "禁用", status: "error" },
};
/** 课件类型1视频课件/2文档课件 */
export const COURSEWARE_TYPE_OPTIONS = [
{ label: "视频课件", value: 1 },
{ label: "文档课件", value: 2 },
];
export const COURSEWARE_TYPE_MAP = COURSEWARE_TYPE_OPTIONS.reduce(
(acc, cur) => ({ ...acc, [cur.value]: cur.label }),
{},
);

View File

@ -0,0 +1,419 @@
import React, { useEffect, useState } from "react";
import {
Badge,
Button,
Descriptions,
Form,
Input,
Modal,
Radio,
Select,
Table,
message,
} from "antd";
import { PlusOutlined } from "@ant-design/icons";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import SearchForm from "~/components/SearchForm";
import AttachmentUpload from "~/components/AttachmentUpload";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { tools } from "@cqsjjb/jjb-common-lib";
import { NS_COURSEWARE } from "~/enumerate/namespace";
import {
COURSEWARE_STATUS_MAP,
COURSEWARE_STATUS_OPTIONS,
COURSEWARE_TRAINING_TYPE_MAP,
COURSEWARE_TRAINING_TYPE_OPTIONS,
COURSEWARE_TYPE_MAP,
COURSEWARE_TYPE_OPTIONS,
} from "~/enumerate/constant";
const { router } = tools;
/** 培训课件管理 */
function CoursewareManagePage(props) {
const [searchForm] = Form.useForm();
const [viewRecord, setViewRecord] = useState(null);
const [addOpen, setAddOpen] = useState(false);
const { courseware } = props;
const {
coursewareList: dataSource,
coursewareTotal: total,
coursewareLoading: loading,
} = courseware || {};
const handleSearch = () => {
props.coursewarePage(router.query);
};
useEffect(() => {
searchForm.setFieldsValue(router.query);
handleSearch();
}, []);
/** 启用/禁用 */
const onToggleStatus = (record) => {
const enabled = record.status === 1;
Modal.confirm({
title: "提示",
content: enabled ? "是否确认禁用该课件?" : "是否确认启用该课件?",
okText: "是",
cancelText: "否",
onOk: async () => {
const res = await props.coursewareModify({
...record,
status: enabled ? 0 : 1,
});
if (res?.success !== false) {
message.success("操作成功");
handleSearch();
}
},
});
};
const onDelete = (record) => {
Modal.confirm({
title: "提示",
content: "数据将会删除,您是否确认删除?",
okText: "是",
cancelText: "否",
onOk: async () => {
const res = await props.coursewareRemove({ data: record.id });
if (res?.success !== false) {
message.success("删除成功");
handleSearch();
}
},
});
};
const columns = [
{
title: "序号",
width: 70,
render: (_, __, index) => {
const current = Number(router.query.current) || 1;
const size = Number(router.query.size) || 10;
return (current - 1) * size + index + 1;
},
},
{
title: "课件名称",
dataIndex: "coursewareName",
width: 240,
ellipsis: true,
},
{
title: "培训类型",
dataIndex: "trainingType",
width: 160,
render: (v) => COURSEWARE_TRAINING_TYPE_MAP[v] ?? (v || "-"),
},
{ title: "讲师名称", dataIndex: "lecturerName", width: 110 },
{ title: "学时", dataIndex: "creditHours", width: 80 },
{
title: "课件时长",
dataIndex: "classHourDuration",
width: 100,
render: (v) => (v == null ? "-" : `${v}小时`),
},
{ title: "习题数", dataIndex: "exerciseCount", width: 90 },
{ title: "上传时间", dataIndex: "createTime", width: 170 },
{
title: "上传单位",
dataIndex: "uploadUnit",
width: 200,
ellipsis: true,
},
{
title: "状态",
dataIndex: "status",
width: 90,
render: (v) => {
const item = COURSEWARE_STATUS_MAP[v];
return item ? <Badge status={item.status} text={item.label} /> : "-";
},
},
{
title: "操作",
width: 200,
fixed: "right",
render: (_, record) => (
<TableAction>
<Button
type="link"
size="small"
onClick={() => onToggleStatus(record)}
>
{record.status === 1 ? "禁用" : "启用"}
</Button>
<Button
type="link"
size="small"
onClick={() => setViewRecord(record)}
>
查看
</Button>
<Button
danger
type="link"
size="small"
onClick={() => onDelete(record)}
>
删除
</Button>
<Button
type="link"
size="small"
onClick={() => message.info("课件习题功能暂未开放")}
>
课件习题
</Button>
</TableAction>
),
},
];
return (
<PageLayout
title="培训课件管理"
extra={
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setAddOpen(true)}
>
新增课件
</Button>
}
>
<SearchForm
style={{ marginBottom: 24 }}
form={searchForm}
loading={loading}
formLine={[
<Form.Item key="coursewareName" name="coursewareName">
<ControlWrapper.Input
label="课件名称"
placeholder="请输入名称"
allowClear
maxLength={50}
/>
</Form.Item>,
<Form.Item key="trainingType" name="trainingType">
<ControlWrapper.Select
label="培训类型"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
options={COURSEWARE_TRAINING_TYPE_OPTIONS}
/>
</Form.Item>,
<Form.Item key="status" name="status">
<ControlWrapper.Select
label="课件状态"
placeholder="请选择课件状态"
allowClear
style={{ width: "100%" }}
options={COURSEWARE_STATUS_OPTIONS}
/>
</Form.Item>,
]}
onReset={(value) => {
router.query = { ...value, current: 1, size: 10 };
handleSearch();
}}
onFinish={(value) => {
router.query = { ...value, current: 1, size: 10 };
handleSearch();
}}
/>
<Table
rowKey="id"
columns={columns}
dataSource={Array.isArray(dataSource) ? dataSource : []}
scroll={{ x: 1500, y: props.scrollY }}
loading={loading}
pagination={{
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
current: Number(router.query.current) || 1,
pageSize: Number(router.query.size) || 10,
onChange: (page, pageSize) => {
router.query = { ...router.query, current: page, size: pageSize };
handleSearch();
},
}}
/>
<Modal
title="课件详情"
open={!!viewRecord}
width={720}
footer={null}
onCancel={() => setViewRecord(null)}
>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="课件名称" span={2}>
{viewRecord?.coursewareName}
</Descriptions.Item>
<Descriptions.Item label="培训类型">
{COURSEWARE_TRAINING_TYPE_MAP[viewRecord?.trainingType]
?? (viewRecord?.trainingType || "-")}
</Descriptions.Item>
<Descriptions.Item label="课件类型">
{COURSEWARE_TYPE_MAP[viewRecord?.coursewareType] || "-"}
</Descriptions.Item>
<Descriptions.Item label="讲师名称">
{viewRecord?.lecturerName || "-"}
</Descriptions.Item>
<Descriptions.Item label="学时">
{viewRecord?.creditHours ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="课件时长">
{viewRecord?.classHourDuration == null
? "-"
: `${viewRecord.classHourDuration}小时`}
</Descriptions.Item>
<Descriptions.Item label="习题数">
{viewRecord?.exerciseCount ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="上传单位" span={2}>
{viewRecord?.uploadUnit || "-"}
</Descriptions.Item>
<Descriptions.Item label="上传时间">
{viewRecord?.createTime || "-"}
</Descriptions.Item>
<Descriptions.Item label="状态">
{COURSEWARE_STATUS_MAP[viewRecord?.status]?.label || "-"}
</Descriptions.Item>
<Descriptions.Item label="课件描述" span={2}>
{viewRecord?.coursewareDescription || "-"}
</Descriptions.Item>
</Descriptions>
</Modal>
<AddCoursewareModal
open={addOpen}
confirmLoading={courseware?.coursewareConfirmLoading}
requestAdd={props.coursewareAdd}
onCancel={() => {
setAddOpen(false);
props.resetModelState(NS_COURSEWARE, {
coursewareConfirmLoading: false,
});
}}
onSuccess={handleSearch}
/>
</PageLayout>
);
}
/** 新增课件弹窗 */
function AddCoursewareModal({
open,
confirmLoading,
requestAdd,
onCancel,
onSuccess,
}) {
const [form] = Form.useForm();
const handleCancel = () => {
form.resetFields();
onCancel();
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
if (values.coursewareDocument?.some?.((f) => f.status === "uploading")) {
message.warning("请等待文件上传完成");
return;
}
values.coursewareDocument =
values.coursewareDocument
?.map?.((f) => f.url)
.filter(Boolean)
.join(",") || undefined;
const res = await requestAdd(values);
if (res?.success !== false) {
message.success("新增成功");
handleCancel();
onSuccess();
}
} catch {
// validation error
}
};
return (
<Modal
open={open}
destroyOnHidden
title="新增课件"
width={560}
confirmLoading={confirmLoading}
onCancel={handleCancel}
onOk={handleSubmit}
>
<Form form={form} layout="vertical" scrollToFirstError>
<Form.Item
name="coursewareName"
label="课件名称"
rules={[{ required: true, message: "请输入课件名称" }]}
>
<Input placeholder="请输入课件名称" maxLength={50} allowClear />
</Form.Item>
<Form.Item name="lecturerName" label="讲师名称">
<Input placeholder="请输入讲师名称" maxLength={30} allowClear />
</Form.Item>
<Form.Item
name="trainingType"
label="培训类型"
rules={[{ required: true, message: "请选择培训类型" }]}
>
<Select
placeholder="请选择"
allowClear
style={{ width: "100%" }}
options={COURSEWARE_TRAINING_TYPE_OPTIONS}
/>
</Form.Item>
<Form.Item
name="coursewareType"
label="课件类型"
rules={[{ required: true, message: "请选择课件类型" }]}
>
<Radio.Group options={COURSEWARE_TYPE_OPTIONS} />
</Form.Item>
<AttachmentUpload
name="coursewareDocument"
label="课件文档"
rules={[{ required: true, message: "请上传课件文档" }]}
extra="最多上传1个文件"
maxCount={1}
/>
<Form.Item name="coursewareDescription" label="课件描述">
<Input.TextArea
rows={3}
placeholder="请输入课件描述"
maxLength={300}
showCount
/>
</Form.Item>
</Form>
</Modal>
);
}
export default Connect(
[NS_COURSEWARE],
true,
)(AntdTableFuncControl(CoursewareManagePage));

View File

@ -0,0 +1,6 @@
import React from "react";
/** 教育培训模块路由出口 */
export default function EduTraining(props) {
return props.children;
}

View File

@ -588,7 +588,10 @@ function DepartmentPositionPage(props) {
<Modal <Modal
open={assignRoleModalOpen} open={assignRoleModalOpen}
destroyOnHidden destroyOnHidden
title="分配GBS角色" mask={{
closable: false,
}}
title="分配系统角色"
width={480} width={480}
confirmLoading={orgPositionLoading} confirmLoading={orgPositionLoading}
onCancel={closeAssignRoleModal} onCancel={closeAssignRoleModal}
@ -605,7 +608,7 @@ function DepartmentPositionPage(props) {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="gbsRoleCode" name="gbsRoleCode"
label="GBS角色" label="系统角色"
rules={[{ required: true, message: "请选择GBS角色" }]} rules={[{ required: true, message: "请选择GBS角色" }]}
> >
<Select <Select

View File

@ -23,6 +23,7 @@ import {
SyncOutlined, SyncOutlined,
BellOutlined, BellOutlined,
EnvironmentOutlined, EnvironmentOutlined,
ReadOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
const menuItems = [ const menuItems = [
@ -283,6 +284,18 @@ const menuItems = [
}, },
], ],
}, },
{
key: "/safetyEval/container/EduTraining",
label: "教育培训",
icon: <ReadOutlined />,
children: [
{
key: "/safetyEval/container/EduTraining/CoursewareManage",
label: "培训课件管理",
icon: <FileTextOutlined />,
},
],
},
{ {
key: "/safetyEval/container/InspNoticeManage", key: "/safetyEval/container/InspNoticeManage",
label: "监管通知管理", label: "监管通知管理",