Merge remote-tracking branch 'origin/main'

# Conflicts:
#	packages/examination/src/views/slider/menu.tsx
main
liuyiliang 4 months ago
commit f5fd3c5e0c
  1. 40
      packages/examination/src/api/exam-online/index.tsx
  2. 8
      packages/examination/src/components/contentMain/index.js
  3. 23
      packages/examination/src/views/exam-online/compoents/ESBreadcrumbComponent.tsx
  4. 251
      packages/examination/src/views/exam-online/compoents/ExamEditPage.tsx
  5. 364
      packages/examination/src/views/exam-online/compoents/ExamListPage.tsx
  6. 1618
      packages/examination/src/views/exam-online/compoents/city.js
  7. 34
      packages/examination/src/views/exam-online/exam-add.tsx
  8. 34
      packages/examination/src/views/exam-online/exam-edit.tsx
  9. 35
      packages/examination/src/views/exam-online/exam-schedule.tsx
  10. 11
      packages/examination/src/views/slider/menu.tsx

@ -0,0 +1,40 @@
import axios from '../axios';
export function getList(obj: any) {
return axios({
url: '/ex/exam-schedule/select',
method: 'get',
params: obj
})
}
export function add(obj: any) {
return axios.post( '/ex/exam-schedule/insert',
obj,{
headers: {
'Content-Type': 'application/json'
}
})
}
export function edit(obj: any) {
return axios({
url: '/ex/exam-edit',
method: 'get',
})
}
export const getIndustryList = async () => {
const response = await axios.get('/ex/exam-schedule/getIndustry');
return response.data;
};
export const getPaperList = async (obj:any) => {
const response = await axios.get('/ex/dict/dictionary?code='+obj);
return response.data;
};
export const getRegionList = async (obj:any) => {
const response = await axios.get('/ex/dict/dictionary?code='+obj);
return response.data;
};

@ -19,7 +19,10 @@ import IntegralRule from 'views/integralmember_manage'; // 积分规则
import Store from 'views/store_manage'; // 店铺管理
import DemoList from 'views/demo/demoList'; // demo list
import DemoList from 'views/demo/demoList';
import ExamSchedule from "views/exam-online/exam-schedule";
import ExamAdd from "../../views/exam-online/exam-add";
import ExamEdit from "../../views/exam-online/exam-edit"; // demo list
import Customer from 'views/statistical/list';
import CustomerDetail from 'views/statistical/detail';
@ -40,6 +43,9 @@ class ContentMain extends Component {
<Route exact path='/demoList' component={ DemoList }/>
<Route exact path='/customer' component={ Customer } />
<Route exact path='/customerDetail' component={ CustomerDetail }/>
<Route exact path='/exam-schedule' component={ ExamSchedule }/>
<Route exact path='/exam-add' component={ ExamAdd }/>
<Route exact path='/exam-edit' component={ ExamEdit }/>
<Redirect exact from='/' to='/home'/>
</Switch>
</div>

@ -0,0 +1,23 @@
import React from 'react';
import { Breadcrumb } from 'antd';
import { Link } from 'react-router-dom';
// 定义组件的 Props 类型
interface ESBreadcrumbComponentProps {
currentText?: string;
}
const ESBreadcrumbComponent: React.FC<ESBreadcrumbComponentProps> = ({
currentText = '新增考试',
}) => {
return (
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/exam-schedule"></Link>
</Breadcrumb.Item>
<Breadcrumb.Item>{currentText}</Breadcrumb.Item>
</Breadcrumb>
);
};
export default ESBreadcrumbComponent;

@ -0,0 +1,251 @@
import React, { useRef, useEffect, useState } from 'react';
// @ts-ignore
import {Form, Input, Select, DatePicker, Space, ColProps, Button, Cascader, DefaultOptionType} from 'antd';
import type { FormProps, FormInstance } from 'antd';
import ESBreadcrumbComponent from './ESBreadcrumbComponent'; // 引入面包屑组件
import { withRouter, RouteComponentProps } from 'react-router-dom'; // 引入 withRouter 和 RouteComponentProps
import { getList, add, edit, getIndustryList, getPaperList } from "api/exam-online/index";
import {provice} from './city.js'; // 引入省市县数据
const { Option } = Select;
// 定义表单数据的类型
interface ExamBasicInfo {
examId: string;
examName: string;
regulatoryIndustry: string;
paperName: string;
examScore: string;
examDuration: string;
examRegion: string[]; // 修改为数组类型,用于存储级联选择的值
examValidTime: Date | null;
examInvalidTime: Date | null;
contentDescription: string;
}
// 定义组件的 Props 类型
interface ExamBasicInfoFormProps extends FormProps {
isEdit: boolean;
initialFormData?: ExamBasicInfo;
}
// 结合路由属性
interface PropsWithRouter extends ExamBasicInfoFormProps, RouteComponentProps {}
export const convertToCascaderData = (): DefaultOptionType[] => {
return provice.map(item => {
const province: DefaultOptionType = {
value: item.name,
label: item.name,
children: item.city.map(city => ({
value: city.name,
label: city.name
})) as DefaultOptionType[]
};
return province;
});
}
const ExamBasicInfoForm: React.FC<PropsWithRouter> = (props) => {
const { isEdit, initialFormData = {} as ExamBasicInfo, history } = props;
const formRef = useRef<FormInstance<ExamBasicInfo>>(null);
const [industryOptions, setIndustryOptions] = useState<{ value: string; label: string }[]>([]);
const [paperOptions, setPaperOptions] = useState<{ value: string; label: string }[]>([]);
const onFinish = async (values: ExamBasicInfo) => {
try {
// 调用 add 方法将数据发送到后台
const response = await add(values);
console.log('数据登录成功:', response);
// 登录成功后迁移到 /exam-schedule 页面
history.push("/exam-schedule");
} catch (error) {
console.error('数据登录失败:', error);
}
};
const onCancel = () => {
history.push("/exam-schedule"); // 使用 history.push 实现跳转
};
const breadcrumbText = isEdit ? '考试编辑' : '新增考试';
// 定义 label 列和控件列的布局
const labelCol: ColProps = {
span: 2,
style: {
textAlign: 'left'
}
};
const wrapperCol: ColProps = { span: 18 }; // 控件列占 18 个栅格
const handleSubmit = () => {
if (formRef.current) {
formRef.current.validateFields().then((values) => {
onFinish(values);
}).catch((errorInfo) => {
console.log('表单验证失败:', errorInfo);
});
}
};
useEffect(() => {
const fetchData = async () => {
try {
// 获取监管行业列表
const industryResponse = await getIndustryList();
setIndustryOptions(industryResponse.map((item: any) => ({ value: item.industry_id, label: item.industry_name })));
// 获取试卷列表
// const paperResponse = await getPaperList();
// setPaperOptions(paperResponse.map(item => ({ value: item, label: item })));
} catch (error) {
console.error('数据加载失败:', error);
}
};
fetchData();
}, []);
return (
<div>
{/* 添加面包屑组件 */}
<ESBreadcrumbComponent currentText={breadcrumbText} />
<h1></h1>
<Form
ref={formRef}
{...props}
name="examBasicInfo"
onFinish={onFinish}
initialValues={{
examId: initialFormData.examId || '',
examName: initialFormData.examName || '',
regulatoryIndustry: initialFormData.regulatoryIndustry || '',
paperName: initialFormData.paperName || '',
examScore: initialFormData.examScore || '',
examDuration: initialFormData.examDuration || '',
examRegion: initialFormData.examRegion || [], // 初始化 examRegion 为数组
examValidTime: initialFormData.examValidTime || null,
examInvalidTime: initialFormData.examInvalidTime || null,
contentDescription: initialFormData.contentDescription || '',
}}
autoComplete="off"
labelCol={labelCol}
wrapperCol={wrapperCol}
>
{/* 隐藏的 examId 输入框 */}
<Form.Item name="examId" style={{ display: 'none' }}>
<Input />
</Form.Item>
<Form.Item
label="考试名称"
name="examName"
rules={[
{
required: true,
message: '请输入考试名称',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="监管行业"
name="regulatoryIndustry"
rules={[
{
required: true,
message: '请选择监管行业',
},
]}
>
<Select placeholder="请选择监管行业">
{industryOptions.map(option => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
label="试卷名称"
name="paperName"
rules={[
{
required: true,
message: '请选择试卷名称',
},
]}
>
<Select placeholder="请选择试卷名称">
{paperOptions.map(option => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item label="考试分值" name="examScore">
<Input disabled />
</Form.Item>
<Form.Item label="考试时长" name="examDuration">
<Input disabled />
</Form.Item>
<Form.Item
label="考试区域"
name="examRegion"
rules={[
{
required: true,
message: '请选择考试区域',
},
]}
>
<Cascader
options={convertToCascaderData()}
placeholder="请选择考试区域"
/>
</Form.Item>
<Form.Item
label="考试有效时间"
name="examValidTime"
rules={[
{
required: true,
message: '请选择考试有效时间',
},
]}
>
<DatePicker />
</Form.Item>
<Form.Item
label="考试失效时间"
name="examInvalidTime"
rules={[
{
required: true,
message: '请选择考试失效时间',
},
]}
>
<DatePicker />
</Form.Item>
<Form.Item
label="内容描述"
name="contentDescription"
>
<Input.TextArea rows={4} />
</Form.Item>
<Form.Item wrapperCol={{ ...wrapperCol, offset: labelCol.span }}>
<div style={{ textAlign: "right" }}>
<Button type="default" onClick={onCancel}></Button>
<span style={{ marginRight: 20 }} />
<Button type="primary" onClick={handleSubmit}></Button>
</div>
</Form.Item>
</Form>
</div>
);
};
export default withRouter(ExamBasicInfoForm); // 使用 withRouter 高阶组件包裹组件

@ -0,0 +1,364 @@
import React, { useState } from 'react';
import { Table, Input, Button, Select, DatePicker, Modal, Checkbox } from 'antd';
import { Link, withRouter } from 'react-router-dom';
import moment, { Moment } from 'moment'; // 引入 moment
import { getList, add, edit } from "api/exam-online/index";
const { Option } = Select;
// 定义 Exam 类型
type Exam = {
examId: number;
examName: string;
paperName: string;
regulatedIndustry: string;
examRegion: string;
validFrom: string;
validTo: string;
examDuration: string;
publishStatus: 0 | 1 | 2; // 0: 待发布, 1: 已发布, 2: 已撤回
};
const ExamListPage = ({ history }: { history: any }) => {
const [searchForm, setSearchForm] = useState({
examName: '',
paperName: '',
regulatedIndustry: '',
validTime: null as Moment | null, // 修改为 Moment 类型
});
const [selectedRows, setSelectedRows] = useState<number[]>([]);
const [examList, setExamList] = useState<Exam[]>([]); // 初始化为空数组
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
const handleReset = () => {
setSearchForm({
examName: '',
paperName: '',
regulatedIndustry: '',
validTime: null,
});
setPagination({ ...pagination, current: 1 });
handleSearch();
};
const handleSearch = async (page = 1) => {
try {
// 构建请求参数
const params = {
examName: searchForm.examName,
paperName: searchForm.paperName,
regulatedIndustry: searchForm.regulatedIndustry,
validDate: searchForm.validTime ? searchForm.validTime.format('YYYY-MM-DD') : '', // 使用 moment 的 format 方法
page: page,
pageSize: pagination.pageSize,
};
// 调用 getList 方法向后端发送请求
const response = await getList(params);
// 假设后端返回的数据结构为 { data: Exam[]; total: number }
const data = response.data;
setExamList(data.data);
setPagination({ ...pagination, current: page, total: data.total });
} catch (error) {
console.error('查询出错:', error);
}
};
const handleDeleteSelected = () => {
// 实现删除选中数据的逻辑
const newExamList = examList.filter((exam) => !selectedRows.includes(exam.examId));
setExamList(newExamList);
setSelectedRows([]);
};
const handleWithdrawSelected = () => {
// 实现撤回选中数据的逻辑
const newExamList: Exam[] = examList.map((exam) => {
if (selectedRows.includes(exam.examId) && exam.publishStatus === 1) {
return { ...exam, publishStatus: 2 }; // 修正为 publishStatus
}
return exam;
});
setExamList(newExamList);
setSelectedRows([]);
};
const handlePublishSelected = () => {
// 实现发布选中数据的逻辑
const newExamList: Exam[] = examList.map((exam) => {
if (selectedRows.includes(exam.examId) && exam.publishStatus === 0) {
return { ...exam, publishStatus: 1 }; // 修正为 publishStatus
}
return exam;
});
setExamList(newExamList);
setSelectedRows([]);
};
const handleAddExam = () => {
// 实现新增考试跳转逻辑
history.push('/exam-add');
console.log('跳转到新增考试页面');
};
const handleEdit = (examId: number) => {
// 实现编辑跳转逻辑
console.log('跳转到编辑页面,examId:', examId);
};
const [qrCodeVisible, setQrCodeVisible] = useState(false);
const [qrCodeData, setQrCodeData] = useState('');
const handleGenerateQrCode = (examId: number) => {
// 实现生成考试码逻辑
setQrCodeData(`examId: ${examId}`);
setQrCodeVisible(true);
};
const handlePublishSingle = (examId: number) => {
// 实现单条数据发布逻辑
const newExamList: Exam[] = examList.map((exam) => {
if (exam.examId === examId && exam.publishStatus === 0) {
return { ...exam, publishStatus: 1 }; // 修正为 publishStatus
}
return exam;
});
setExamList(newExamList);
};
const handleWithdrawSingle = (examId: number) => {
// 实现单条数据撤回逻辑
const newExamList: Exam[] = examList.map((exam) => {
if (exam.examId === examId && exam.publishStatus === 1) {
return { ...exam, publishStatus: 2 }; // 修正为 publishStatus
}
return exam;
});
setExamList(newExamList);
};
const handleDeleteSingle = (examId: number) => {
// 实现单条数据删除逻辑
const newExamList = examList.filter((exam) => exam.examId !== examId);
setExamList(newExamList);
};
// 处理全选和取消全选
const handleSelectAll = (checked: boolean) => {
if (checked) {
setSelectedRows(examList.map((exam) => exam.examId));
} else {
setSelectedRows([]);
}
};
// 判断是否全选
const isAllSelected = examList.length > 0 && selectedRows.length === examList.length;
const columns = [
{
title: (
<Checkbox
checked={isAllSelected}
onChange={(e) => handleSelectAll(e.target.checked)}
/>
),
dataIndex: 'examId',
key: 'examId',
render: (cellValue: number, record: Exam) => (
<Checkbox
checked={selectedRows.includes(record.examId)}
onChange={(e) => {
if (e.target.checked) {
setSelectedRows([...selectedRows, record.examId]);
} else {
setSelectedRows(selectedRows.filter((id) => id !== record.examId));
}
}}
/>
),
},
// 新增序号列
{
title: '序号',
key: 'index',
render: (cellValue: unknown, record: Exam, index: number) => (pagination.current - 1) * pagination.pageSize + index + 1,
},
{
title: '考试名称',
dataIndex: 'examName',
key: 'examName',
render: (text: string, record: Exam) => (
<Link to={`/exam-detail/${record.examId}`}>{text}</Link>
),
},
{
title: '试卷名称',
dataIndex: 'paperName',
key: 'paperName',
},
{
title: '监管行业',
dataIndex: 'regulatedIndustry',
key: 'regulatedIndustry',
},
{
title: '考试区域',
dataIndex: 'examRegion',
key: 'examRegion',
},
{
title: '考试有效时间',
dataIndex: 'validFrom',
key: 'validFrom',
},
{
title: '考试无效时间',
dataIndex: 'validTo',
key: 'validTo',
},
{
title: '考试时长',
dataIndex: 'examDuration',
key: 'examDuration',
},
{
title: '状态',
dataIndex: 'publishStatus',
key: 'publishStatus',
render: (publishStatus: 0 | 1 | 2) => {
console.log(publishStatus)
switch (publishStatus) {
case 0:
return '待发布';
case 1:
return '已发布';
case 2:
return '已撤回';
default:
return '';
}
},
},
{
title: '操作',
key: 'action',
render: (cellValue: number, record: Exam) => (
<div>
<a onClick={() => handleEdit(record.examId)}></a>
<span style={{ margin: '0 8px' }}>|</span>
{record.publishStatus === 1 ? (<a
onClick={(e) => {
if (record.publishStatus !== 1) {
e.preventDefault();
return;
}
handleGenerateQrCode(record.examId);
}}
>
</a>) : (
<span style={{ color: 'gray' }}></span>
)}
<span style={{ margin: '0 8px' }}>|</span>
{record.publishStatus === 0 ? (
<a onClick={() => handlePublishSingle(record.examId)}></a>
) : record.publishStatus === 1 ? (
<a onClick={() => handleWithdrawSingle(record.examId)}></a>
) : (
<span style={{ color: 'gray' }}></span>
)}
<span style={{ margin: '0 8px' }}>|</span>
<a onClick={() => handleDeleteSingle(record.examId)}></a>
</div>
),
},
];
const handleTableChange = (pagination: any) => {
setPagination(pagination);
handleSearch(pagination.current);
};
return (
<div>
<div style={{ marginBottom: 16 }}>
<Input
placeholder="考试名称"
value={searchForm.examName}
onChange={(e) =>
setSearchForm({ ...searchForm, examName: e.target.value })
}
style={{ width: 150, marginRight: 8 }}
/>
<Input
placeholder="试卷名称"
value={searchForm.paperName}
onChange={(e) =>
setSearchForm({ ...searchForm, paperName: e.target.value })
}
style={{ width: 150, marginRight: 8 }}
/>
<Select
placeholder="监管行业"
value={searchForm.regulatedIndustry}
onChange={(value) =>
setSearchForm({ ...searchForm, regulatedIndustry: value })
}
style={{ width: 150, marginRight: 8 }}
>
<Option value=""></Option>
<Option value="行业1">1</Option>
<Option value="行业2">2</Option>
{/* 可以添加更多选项 */}
</Select>
<DatePicker
value={searchForm.validTime} // 使用 Moment 类型
onChange={(value: Moment | null) =>
setSearchForm({ ...searchForm, validTime: value })
}
style={{ marginRight: 8 }}
/>
<Button onClick={handleReset}></Button>
<Button onClick={() => handleSearch(1)} type="primary" style={{ marginLeft: 8 }}>
</Button>
</div>
<div style={{ marginBottom: 16, textAlign: "right" }}>
<Button onClick={handleDeleteSelected} danger>
</Button>
<Button onClick={handleWithdrawSelected} style={{ marginLeft: 8 }}>
</Button>
<Button onClick={handlePublishSelected} type="primary" style={{ marginLeft: 8 }}>
</Button>
<Button onClick={handleAddExam} type="primary" style={{ marginLeft: 8 }}>
</Button>
</div>
<Table
columns={columns}
dataSource={examList}
rowKey="examId"
pagination={pagination}
onChange={handleTableChange}
/>
<Modal
title="考试码二维码"
visible={qrCodeVisible}
onCancel={() => setQrCodeVisible(false)}
footer={null}
>
{/*<QRCode value={qrCodeData} size={200} />*/}
</Modal>
</div>
);
};
export default withRouter(ExamListPage);

File diff suppressed because it is too large Load Diff

@ -0,0 +1,34 @@
import React, { Component } from "react";
import ExamEditPage from "./compoents/ExamEditPage";
interface States {
resData: any
visible: boolean
title: string
current: any
}
class ExamAdd extends Component<any, States> {
constructor(props: any) {
super(props);
this.state = {
resData: undefined,
visible: false,
title: '',
current: {}
}
}
componentDidMount() {
// this.getList()
}
render() {
return (
<div className="container">
<ExamEditPage isEdit={false}/>
</div>
)
}
}
export default ExamAdd;

@ -0,0 +1,34 @@
import React, { Component } from "react";
import ExamEditPage from "./compoents/ExamEditPage";
interface States {
resData: any
visible: boolean
title: string
current: any
}
class ExamEdit extends Component<any, States> {
constructor(props: any) {
super(props);
this.state = {
resData: undefined,
visible: false,
title: '',
current: {}
}
}
componentDidMount() {
// this.getList()
}
render() {
return (
<div className="container">
<ExamEditPage isEdit={true}/>
</div>
)
}
}
export default ExamEdit;

@ -0,0 +1,35 @@
import React, { Component } from "react";
import ExamListPage from "./compoents/ExamListPage";
interface States {
resData: any
visible: boolean
title: string
current: any
}
class ExamSchedule extends Component<any, States> {
constructor(props: any) {
super(props);
this.state = {
resData: undefined,
visible: false,
title: '新增公告',
current: {}
}
}
componentDidMount() {
// this.getList()
}
render() {
return (
<div className="container">
<ExamListPage />
</div>
)
}
}
export default ExamSchedule;

@ -23,6 +23,17 @@ const menuList = [
}
]
},
{
title: '线上考试',
key: 'exam-online',
// icon: 'icon-peizhi',
subs: [
{
title: '考试安排',
key: 'exam-schedule',
}
]
},
// {
// title: 'demo',
// key: 'demo',

Loading…
Cancel
Save