Compare commits

..

7 Commits

10 changed files with 375 additions and 151 deletions

4
.env.development Normal file
View File

@ -0,0 +1,4 @@
# 本地后端地址
REACT_APP_BACKEND_ADDRESS=http://localhost:8080
# 超时时间
REACT_APP_BACKEND_TIMEOUT=10000

4
.env.production Normal file
View File

@ -0,0 +1,4 @@
# 远程后端地址
REACT_APP_BACKEND_ADDRESS=http://43.138.83.20:10001
# 超时时间
REACT_APP_BACKEND_TIMEOUT=10000

View File

@ -1,3 +1,3 @@
window.BACKEND_ADDRESS = "http://localhost:8080";
//window.BACKEND_ADDRESS = "http://43.138.83.20:10001";
//window.BACKEND_ADDRESS = "http://localhost:8080";
window.BACKEND_ADDRESS = "http://43.138.83.20:10001";
window.BACKEND_TIMEOUT = 10000;

View File

@ -1,7 +1,10 @@
// baseWebConfig.js
const baseWebConfig = {
baseUrl: 'http://localhost:8080',
//baseUrl: 'http://43.138.83.20:10001',
timeout: 10000,
}
// 从环境变量读取baseUrl本地开发用localhostbuild用ip:10001
baseUrl: process.env.REACT_APP_BACKEND_ADDRESS,
// 读取超时时间(转成数字类型)
timeout: Number(process.env.REACT_APP_BACKEND_TIMEOUT)
};
console.log('当前环境 baseUrl:', baseWebConfig.baseUrl);
console.log('当前超时时间:', baseWebConfig.timeout);
export default baseWebConfig;

View File

@ -0,0 +1,158 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Drawer, Flex, Form, Input, Select, Typography } from "antd";
import CourseTypeTag from "../../../component/Workload/CourseTypeTag";
const AddDataManageDrawer = props => {
const { open, setOpen, commonAxios, fetchWorkload, messageApi } = props;
// 表单布局配置
const formLayout = {
labelCol: { span: 5 },
wrapperCol: { span: 20 },
};
const [form] = Form.useForm();
const { Title } = Typography;
// 课程性质选项
const courseNatureOptions = [
{ label: '公共课', value: '01' },
{ label: '专业课', value: '02' },
{ label: '校选课', value: '03' },
];
// 当抽屉关闭时重置表单
React.useEffect(() => {
if (!open) {
form.resetFields();
}
}, [open, form]);
// 提交添加表单
const onSubmit = (values) => {
commonAxios.post('/api/v1/workload/addWorkload', values).then(response => {
const result = response.data.data || false;
if (result) {
messageApi.success('添加工作量信息成功');
setOpen(false);
fetchWorkload(); // 刷新列表
} else {
messageApi.error('添加工作量信息失败');
}
}).catch(error => {
messageApi.error('网络错误,添加失败');
});
};
return (
<Drawer
open={open}
onClose={() => setOpen(false)}
title={'添加工作量'}
destroyOnClose
>
<div>
<Title level={5}>添加工作量信息</Title>
<Form
form={form}
{...formLayout}
onFinish={onSubmit}
>
<Form.Item
label="学期"
name="semesterInfo"
rules={[{ required: true, message: '请输入学期' }]}
>
<Input placeholder="请输入学期(示例2023-2024-1)"/>
</Form.Item>
<Form.Item
label="授课名称"
name="courseName"
rules={[{ required: true, message: '请输入授课名称' }]}
>
<Input placeholder="请输入授课名称"/>
</Form.Item>
<Form.Item
label="姓名"
name="teacherName"
rules={[{ required: true, message: '请输入姓名' }]}
>
<Input placeholder="请输入姓名"/>
</Form.Item>
<Form.Item
label="工号"
name="stuffNumber"
rules={[{ required: true, message: '请输入工号' }]}
>
<Input placeholder="请输入工号"/>
</Form.Item>
<Form.Item
label="职称"
name="jobTitle"
rules={[{ required: true, message: '请输入职称' }]}
>
<Input placeholder="请输入职称"/>
</Form.Item>
<Form.Item
label="课程性质"
name="courseNature"
rules={[{ required: true, message: '请选择课程性质' }]}
>
<Select
placeholder="请选择课程性质"
options={courseNatureOptions}
formatLabel={(option) => <CourseTypeTag courseNature={option.value} />}
/>
</Form.Item>
<Form.Item
label="授课专业"
name="teachingMajor"
rules={[{ required: true, message: '请输入授课专业' }]}
>
<Input placeholder="请输入授课专业"/>
</Form.Item>
<Form.Item
label="学生数"
name="actualClassSize"
rules={[
{ required: true, message: '请输入学生数' }
]}
>
<Input type="number" placeholder="请输入学生数"/>
</Form.Item>
<Form.Item
label="授课对象"
name="teachingGrade"
rules={[{ required: true, message: '请输入授课对象' }]}
>
<Input placeholder="请输入授课对象"/>
</Form.Item>
<Form.Item
label="工作量"
name="totalClassHours"
rules={[
{ required: true, message: '请输入工作量' }
]}
>
<Input type="number" placeholder="请输入工作量"/>
</Form.Item>
<Flex justify={"center"} align={"center"}>
<Button onClick={() => setOpen(false)} style={{ marginRight: 8 }}>取消</Button>
<Button htmlType={'submit'} type={'primary'}>添加</Button>
</Flex>
</Form>
</div>
</Drawer>
);
};
AddDataManageDrawer.propTypes = {
open: PropTypes.bool.isRequired,
setOpen: PropTypes.func.isRequired,
commonAxios: PropTypes.func.isRequired,
fetchWorkload: PropTypes.func.isRequired,
messageApi: PropTypes.object.isRequired
};
export default AddDataManageDrawer;

View File

@ -4,6 +4,7 @@ import ManageTableColumn from "./ManageTableColumn";
import ImportDataDrawer from "./ImportDataDrawer";
import EditDataManageDrawer from './EditDataManageDrawer';
import creatMessageCommonAxios from "../../../http/CreatMessageCommonAxios";
import AddDataManageDrawer from './AddDataManageDrawer'; // 导入新增的组件
const DataManageTable = props => {
@ -17,6 +18,9 @@ const DataManageTable = props => {
// 新增编辑抽屉相关状态
const [editDrawerOpen, setEditDrawerOpen] = useState(false);
const [currentEditData, setCurrentEditData] = useState({});
// 添加添加数据抽屉的状态
const [addDrawerOpen, setAddDrawerOpen] = useState(false);
const rowSelection = {
type: 'checkbox',
onChange: (selectedRowKeys, selectedRows) => {
@ -67,11 +71,20 @@ const DataManageTable = props => {
messageApi={messageApi}
initialValues={currentEditData}
/>
{/* 添加数据抽屉 */}
<AddDataManageDrawer
open={addDrawerOpen}
setOpen={setAddDrawerOpen}
commonAxios={commonAxios}
fetchWorkload={fetWorkload}
messageApi={messageApi}
/>
<Flex justify={"space-between"} align={"center"} gap={"middle"}>
<h3>数据管理</h3>
<Flex justify={"flex-end"} align={"center"} gap={"middle"}>
<Typography.Text>{workloadData.total}条数据</Typography.Text>
{/* 添加"添加数据"按钮 */}
<Button type={"primary"} onClick={() => setAddDrawerOpen(true)}>添加数据</Button>
<Button type={"primary"} onClick={() => setImportDataOpen(true)}>导入</Button>
</Flex>
</Flex>

View File

@ -154,8 +154,22 @@ function QueryConditionBox(props) {
</Row>
<Flex justify={"space-between"} align={"center"}>
<Flex justify={"start"} align={"center"}>
<Button icon={<PlusOutlined/>}
onClick={() => navigate('/generate-certificate')}>生成新报告</Button>
{/*修改生成新证明按钮的点击事件*/}
<Button
icon={<PlusOutlined />}
onClick={() => {
const selectedStaffNumbers = form.getFieldValue('staffNumber') || [];
const baseUrl = '/generate-certificate';
// 携带工号和来源标记(用于区分跳转场景)
if (selectedStaffNumbers.length > 0) {
navigate(`${baseUrl}?staffNumber=${selectedStaffNumbers[0]}&from=print`);
} else {
navigate(baseUrl);
}
}}
>
生成新证明
</Button>
</Flex>
<Flex justify={"center"} align={"center"} gap={"large"}>
<Button type={"primary"} htmlType={"submit"} icon={<SearchOutlined/>}>搜索</Button>

View File

@ -8,7 +8,8 @@ import {PlusOutlined} from "@ant-design/icons";
import {useNavigate} from "react-router-dom";
const DataPrint = props => {
const [downloadDisabled, setDownloadDisabled] = useState(true);
//const [downloadDisabled, setDownloadDisabled] = useState(true);
const [setDownloadDisabled] = useState(true);
const previewClicked = () => {
setDownloadDisabled(false);
}

View File

@ -1,24 +1,27 @@
import React, {useEffect} from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { Button, Flex, Form, Input, message, Spin, Table, Typography } from "antd";
import creatMessageCommonAxios from "../../../http/CreatMessageCommonAxios";
import { useSearchParams } from 'react-router-dom';
const ChooseUser = props => {
const { allowNext, request, setRequest } = props;
const [searchParams] = useSearchParams(); // 获取URL参数
const [autoProcessed, setAutoProcessed] = useState(false); // 防止重复处理
const { Title, Text } = Typography;
const [messageApi, contextHolder] = message.useMessage();
const [form] = Form.useForm();
const dataFetchRef = useRef(false); // 防止重复请求
const commonAxios = creatMessageCommonAxios(messageApi);
// 初始加载标记
const [isInitialLoad, setIsInitialLoad] = useState(true);
// 新增用于存储当前选中的行ID控制表格视觉选中状态
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
hidden: true,
width: 0,
},
{
title: '工号',
@ -41,85 +44,112 @@ const ChooseUser = props => {
},
];
const [staffInfo, setStaffInfo] = React.useState([]);
const [staffInfo, setStaffInfo] = useState([]);
const [staffNumberPrefix, setStaffNumberPrefix] = useState("");
const [spinning, setSpinning] = useState(true);
const [staffNumberPrefix, setStaffNumberPrefix] = React.useState("");
const [spinning, setSpinning] = React.useState(true);
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
// 处理选中行逻辑(同时更新表格选中状态)
const handleRowSelect = (selectedRowKeys, selectedRows) => {
console.log(`选中行: ${selectedRowKeys}`, selectedRows);
// 1. 更新按钮状态和请求参数
allowNext(selectedRows.length > 0);
if (selectedRows.length > 0) {
setRequest({ ...request, stuffNumber: selectedRows[0].staffNumber });
sessionStorage.setItem('chooseUser', selectedRowKeys)
sessionStorage.removeItem('certificateParam');
sessionStorage.removeItem('targetKeys');
sessionStorage.removeItem('generate-request');
},
defaultSelectedRowKeys: () => {
const selectedRowKeys = [];
let selected = sessionStorage.getItem('chooseUser');
if (selected) {
// 转换为number
selectedRowKeys.push(selected);
allowNext(selectedRowKeys.length > 0);
}
return selectedRowKeys;
}
// 2. 更新表格选中状态(关键:让表格知道哪些行被选中)
setSelectedRowKeys(selectedRowKeys);
};
useEffect(() => {
commonAxios.get(`/api/auth/query/registered?page=1&size=10`).then((response) => {
if (response.data.data != null) {
let list = response.data.data.list;
// 为list设置key=id
list.forEach((item) => {
item.key = item.id;
});
setStaffInfo(list);
} else {
setStaffInfo([]);
}
});
setSpinning(false);
}, []);
// 行选择配置使用selectedRowKeys控制选中状态
const rowSelection = {
onChange: handleRowSelect,
selectedRowKeys: selectedRowKeys, // 绑定选中行ID数组
defaultSelectedRowKeys: () => [],
};
// 通用搜索函数(自动选中时同步更新表格状态)
const fetchStaffData = (staffNumber) => {
if (dataFetchRef.current) return;
dataFetchRef.current = true;
const onSearch = () => {
setSpinning(true);
commonAxios.get(`/api/auth/query/registered?staffNumber=${staffNumberPrefix}&page=1&size=10`).then((response) => {
if (response.data.data != null) {
let list = response.data.data.list;
// 为list设置key=id
list.forEach((item) => {
item.key = item.id;
});
const params = staffNumber ? `&staffNumber=${staffNumber}` : '';
commonAxios.get(`/api/auth/query/registered?page=1&size=10${params}`)
.then((response) => {
if (response.data.data?.list) {
const list = response.data.data.list.map(item => ({ ...item, key: item.id }));
setStaffInfo(list);
return list;
} else {
setStaffInfo([]);
return [];
}
})
.then((list) => {
const fromPrint = searchParams.get('from') === 'print';
if (fromPrint && list.length === 1 && !autoProcessed) {
// 自动选中时,同步更新表格选中状态
const targetRowKey = [list[0].id];
handleRowSelect(targetRowKey, [list[0]]); // 触发选中逻辑
setSelectedRowKeys(targetRowKey); // 强制更新表格选中状态
setAutoProcessed(true);
}
})
.catch(() => {
messageApi.error('搜索失败,请重试');
setStaffInfo([]);
})
.finally(() => {
setSpinning(false);
dataFetchRef.current = false;
});
};
// 监听URL参数从打印界面跳转时处理
useEffect(() => {
const staffNumber = searchParams.get('staffNumber');
const fromPrint = searchParams.get('from') === 'print'; // 标记来自打印界面
// 优先处理:如果有工号参数且来自打印界面,执行搜索
if (staffNumber && fromPrint && !autoProcessed) {
form.setFieldsValue({ 'staff-number': staffNumber });
setStaffNumberPrefix(staffNumber);
fetchStaffData(staffNumber);
setIsInitialLoad(false); // 已处理参数,无需再加载全部
}
// 次要处理:无参数时,初始加载全部数据(仅执行一次)
else if (isInitialLoad) {
fetchStaffData(''); // 空参数表示查询全部
setIsInitialLoad(false);
}
}, [searchParams, form, autoProcessed, isInitialLoad]); // 添加isInitialLoad依赖
// 手动搜索
const onSearch = () => {
fetchStaffData(staffNumberPrefix);
};
return (
<div>
{contextHolder}
<Flex vertical justiffy={"start"} align={"start"} gap={"large"}>
<Flex vertical justify={"start"} align={"start"} gap={"large"}>
<div>
<Title level={4}>选择用户</Title>
<Text level={4}
type={'secondary'}>指示EFC系统为何人生成报告选择后这份报告将会出现在您和该同事的EFC系统证明管理画面中</Text>
<Text type={'secondary'}>
指示EFC系统为何人生成报告选择后这份报告将会出现在您和该同事的EFC系统证明管理画面中
</Text>
</div>
<Form
form={form}
layout={"inline"}
>
<Form.Item
name='staff-number'
>
<Input placeholder={"工号"} onChange={(event) => setStaffNumberPrefix(event.target.value)}/>
<Form form={form} layout={"inline"}>
<Form.Item name='staff-number'>
<Input
placeholder={"工号"}
onChange={(e) => setStaffNumberPrefix(e.target.value)}
onPressEnter={onSearch} // 支持回车搜索
/>
</Form.Item>
<Button type={"primary"} onClick={() => onSearch()}>搜索</Button>
<Button type={"primary"} onClick={onSearch}>搜索</Button>
</Form>
<div style={{ width: '100%' }}>
<Spin spinning={spinning}>
@ -127,19 +157,15 @@ const ChooseUser = props => {
columns={columns}
dataSource={staffInfo}
pagination={false}
rowSelection={{
type: 'radio',
...rowSelection,
}}
rowSelection={{ type: 'radio', ...rowSelection }}
locale={{ emptyText: '暂无数据,请输入工号搜索' }}
/>
</Spin>
</div>
</Flex>
</div>
)
;
}
;
);
};
ChooseUser.propTypes = {};

View File

@ -15,14 +15,15 @@ const GenerateCertificate = props => {
const navigate = useNavigate();
const [current, setCurrent] = React.useState(() => {
let nowStep = sessionStorage.getItem('nowStep');
if (nowStep === null) {
return 0;
} else {
return parseInt(nowStep);
}
});
//const [current, setCurrent] = React.useState(() => {
// let nowStep = sessionStorage.getItem('nowStep');
// if (nowStep === null) {
// return 0;
// } else {
// return parseInt(nowStep);
// }
//});
const [current, setCurrent] = React.useState(0);
const [request, setRequest] = React.useState({
ids: [],
@ -36,7 +37,7 @@ const GenerateCertificate = props => {
const [result, setResult] = React.useState({});
const [loading, setLoading] = React.useState(false);
const [pageConfig, setPageConfig] = React.useState({});
//const [pageConfig, setPageConfig] = React.useState({});
const steps = [
{
@ -159,7 +160,7 @@ const GenerateCertificate = props => {
const onPrev = () => {
let nextCurrent = current - 1;
setCurrent(nextCurrent);
let editFlag = sessionStorage.getItem('edit-flag');
//let editFlag = sessionStorage.getItem('edit-flag');
if (nextCurrent <= 0) {
setCurrent(0)
setPreButton({...preButton, disabled: true,})