You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

712 lines
39 KiB

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 React, { useEffect, useRef, useState } from 'react';
import { history, useParams, useRequest } from 'umi';
import type { ProFormInstance } from '@ant-design/pro-form';
import { ModalForm } from '@ant-design/pro-form';
import { ProFormRadio } from '@ant-design/pro-form';
import ProForm, {StepsForm, ProFormText, ProFormDatePicker, ProFormSelect, ProFormTextArea, ProFormCheckbox, ProFormDateRangePicker,} from '@ant-design/pro-form';
import ProCard from '@ant-design/pro-card';
import { Button, Checkbox, Col, Divider, Dropdown, Form, Input, List, Menu, message, Modal, Radio, Row, Space, Table, Typography, Upload, Empty, Tooltip, Popconfirm } from 'antd';
import { PageContainer } from '@ant-design/pro-layout';
import ProDescriptions from '@ant-design/pro-descriptions';
import styles from './index.less'
import { saveRules, querySubjectList, queryRulesView, queryTempQuestionList, saveQuestionTypeScore, queryRulesPaper, updateScore, delTempQuestion, exchangeSortNum } from '../../service';
import { queryCourseView } from '@/pages/course/option/service';
import { queryQuestionList, queryQuestionById, queryQuestionType } from '@/pages/questionbank/service';
import { PlusOutlined, DownOutlined, DeleteOutlined, DownloadOutlined, UploadOutlined, EyeInvisibleOutlined, EyeOutlined, EditOutlined, ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons';
import ProList from '@ant-design/pro-list';
import { autoPaper, manualPaper, updatePaper } from '../service';
import ProTable, { EditableProTable } from '@ant-design/pro-table';
import QuestionSelector from '../../components/QuestionSelector';
import ScoreSetter from '../../components/ScoreSetter';
import AutoSelector from '../components/AutoSelector';
import { ConsoleMessage } from 'puppeteer-core';
import { getSubjectInfo } from '@/pages/course/subject/service';
import { forEach } from 'lodash';
/**
* 保存选题
*
* @param values
*/
const handleAppend = async (rules_id: number, rows: any[]) => {
const hide = message.loading('正在添加');
try {
const questions: { question_id: any; }[] = [];
rows?.forEach((item) => {
questions.push({ question_id: item?.id })
})
const _data = await manualPaper({
question_count: questions?.length || 0,
questions: JSON.stringify(questions),
rules_id: Number(rules_id)
});
hide();
message.success('添加成功');
return _data;
} catch (error) {
hide();
message.error('添加失败请重试!');
return false;
}
};
/**
* 保存选题试卷
*
* @param values
*/
const handleUpdatePaper = async (rules_id: number, paper_uuid: number, paper_id: number) => {
const hide = message.loading('正在保存');
try {
const _data = {
rules_id: Number(rules_id),
paper_uuid: paper_uuid,
paper_id: paper_id
}
if (paper_id === 0) {
delete _data.paper_id;
}
await updatePaper(_data);
hide();
message.success('保存成功');
return true;
} catch (error) {
hide();
message.error('保存失败请重试!');
return false;
}
};
/**
* 删除临时表试题(接口不支持批量)
* 参数为记录数组
* @param ids
*/
const handleRemoveTempQuestion = async (paper_uuid: number, question_ids: [{question_id: number}]) => {
const hide = message.loading('正在删除');
console.log('uuidPaper', paper_uuid)
if (!question_ids || !paper_uuid) return true;
try {
const {code, msg} = await delTempQuestion({
paper_uuid: paper_uuid,
question_ids: JSON.stringify(question_ids)
});
hide();
if(code === 2000 ){
message.success('删除成功,即将刷新');
}else{
message.warning(msg);
}
return true;
} catch (error) {
hide();
message.error('删除失败,请重试');
return false;
}
};
/**
* 组卷交换临时表试题顺序
* @param paper_uuid
* @param question_ids
* @param rules_id
* @returns bool
*/
const handleExchangeSortNum = async (paper_uuid: number, question_ids: string, rules_id: number) => {
const hide = message.loading('正在保存修改');
try {
const data = {
rules_id: Number(rules_id),
paper_uuid: paper_uuid,
question_ids: question_ids
}
const success = await exchangeSortNum(data);
hide();
if(success){
message.success('修改成功');
}
return true;
} catch (error) {
hide();
message.error('修改失败请重试!');
return false;
}
};
const labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K']
// 模拟考试规则维护
export default () => {
const [selectorModalVisible, handleSelectorModalVisible] = useState<boolean>(false);
const [scoreModalVisible, handleScoreModalVisible] = useState<boolean>(false);
const [autoModalVisible, handleAutoModalVisible] = useState<boolean>(false);
const [subjectId, setSubjectId] = useState<number>(0); // 关联主题id
const [subjectName, setSubjectName] = useState(''); // 关联主题name
const [questionTypeValues, setQuestionTypeValues] = useState([]); // 题型数据[{count:0, score:0, score_harf:0}]
const [uuidPaper, setUuidPaper] = useState<number>(0);
const [createType, setCreateType] = useState<number>(1); // 组卷类型
const [rulesName, setRulesName] = useState<string>(''); // 组卷类型
const [sumScore, setSumScore] = useState<number>(0); // 总分
const [passScore, setPassScore] = useState<number>(0); // 通过分数线
const [typeQuestionCount, setTypeQuestionCount] = useState([0, 0, 0]); // 临时卷 各题型数量
const [rulesId, setRulesId] = useState(0); // 规则id, 保存新建的rules_id
const formRef = useRef<ProFormInstance>();
const selectorRef = useRef();
const setterRef = useRef();
const autoRef = useRef();
const [currentStep, setCurrentStep] = useState(0);
const params = useParams();
const [questionType, setQuestionType] = useState([]); // 题型
const [scoreValues, setScoreValues] = useState([]); // 分值 [{"question_type": "0","score": "3","score_harf": "0"},]
const [paperInfo, setPaperInfo] = useState({})
/** 获取题型 */
const { data: questionTypeData } = useRequest(() => {
return queryQuestionType();
}, {
formatResult: (result) => {
return result.list;
}
});
useEffect(() => {
setQuestionType(questionTypeData || []);
return () => {
/** 退出当前页面清空Map */
//parsingMap.clear();
}
}, [questionTypeData]);
/** 组卷,查询试题临时表(当前选题列表) */
const { data: questions, run } = useRequest(async (params) => {
console.log('questions', questions)
const _data = await queryTempQuestionList(params);
return _data;
}, {
manual: true,
formatResult: (result) => {
return result?.table_List;
}
});
/** 从临时表中计算各题型数量 */
useEffect(() => {
const _data = [0, 0, 0]; // 当前仅支持 单选 / 多选 / 判断 的顺序
console.log('questions-info', questions)
questions?.forEach((item) => {
_data[Number(item?.question_type)] += 1
})
setTypeQuestionCount(_data);
return () => {
/** 退出当前页面清空Map */
//parsingMap.clear();
}
}, [questions]);
//
const { data: paperData, run: runPaper } = useRequest(async (params) => {
console.log('paperData', paperData)
/**
* rules_id: params?.id,
page_number: value.current,
page_size: value.pageSize
*/
return queryRulesPaper(params);
}, {
manual: true,
formatResult: (result) => {
return result?.question_list;
}
});
useEffect(() => {
console.log('paperData2', paperData)
if (paperData?.length > 0) {
console.log('paperData[0]', paperData[0])
setPaperInfo(paperData[0])
}
console.log('PaperInfo', paperInfo)
}, [paperData]);
console.log(params, 'params');
let ruleData = {}
//
if (params?.id) {
//console.log(JSON.stringify(params), "878");
const { data } = useRequest(async () => {
return queryRulesView(params);
}, {
formatResult: (result) => {
return result?.bean;
}
});
ruleData = data
}
console.log(ruleData, 'ruleData');
return (
<PageContainer content={''} extraContent={''}>
<ProCard className={styles.examinationrules}>
<StepsForm<{
name: string;
}>
formRef={formRef}
onFinish={async (e) => {
message.success('提交成功');
}}
formProps={{
layout: "horizontal",
labelCol: { span: 7 },
wrapperCol: { span: 12 },
validateMessages: {
required: '此项为必填项',
},
}}
>
<StepsForm.StepForm<{
name: string;
}>
name="base"
title="模拟考试基本信息"
stepProps={{
description: false,
}}
onFinish={async (fileds) => {
if (params?.id) {
fileds = { ...fileds, id: (params.id || rulesId) }
}
const { data } = await saveRules({
...fileds,
b_use: 0,
rules_type: 0,
});
setRulesId(data?.rules_id) // 保存规则id
//run({paper_uuid:1}); // 获取当前选题列表
// console.log('模拟考试基本信息', fileds)
setSubjectId(fileds?.subject_id) // 设置当前关联主题
setRulesName(fileds?.rules_name) // 设置模拟考试规则名称
console.log('fileds', fileds)
console.log('formRef', formRef.current?.getFieldValue(''))
// await waitTime(2000);
setCurrentStep(1) // 设置步骤号
return true;
}}
>
<Row gutter={24}>
<Col lg={24} md={24} sm={24}>
{ruleData && (
<>
<ProFormText
name="rules_name"
label="考试名称"
width="lg"
initialValue={ruleData?.rules_name}
// tooltip="最长为 6 位汉字,需要与考生身份证一致"
placeholder="请输入名称"
fieldProps={{
type: 'text',
allowClear: false,
width: 'large',
onInput:(e)=>{
const val = `${e.currentTarget?.value}`;
if(val.length > 50) {
e.currentTarget.value = val.slice(0,50)
}
}
//style:{width: '100%'}
}}
rules={[
{ required: true, message: '请输入考试名称' },
{
pattern: /^[^\s]*$/,
message: '禁止输入空格'
}
]}
/>
<ProFormSelect
width="lg"
initialValue={ruleData?.subject_id}
key='value'
request={async () => {
return querySubjectList().then(({ data }) => {
console.log(data, 'querySubjectList')
return data.list.map((item) => {
if (item?.subject_id === ruleData?.subject_id) {
setSubjectName(item?.subject_name) // 设置关联主题名称
console.log('label::', item?.subject_name)
}
return {
label: item?.subject_name,
value: item?.subject_id,
};
});
});
}}
onChange={(value) => {
console.log('on change', value)
}}
rules={[{ required: true, message: '请选择主题' }]}
name="subject_id"
label="关联主题"
/>
<ProFormText
width="lg"
addonAfter={`分钟`}
name="examination_time"
label="考试时长"
fieldProps={{
type: 'number',
min:1,
max:999,
allowClear: false,
width: 'large',
onInput:(e)=>{
const val = `${e.currentTarget?.value}`;
e.currentTarget.value = val.replace(/[^\d]|[0]/,'')
if(val.length > 3) {
e.currentTarget.value = val.slice(0,3)
}
}
//style:{width: '100%'}
}}
initialValue={ruleData?.examination_time}
rules={[
{
required: true,
message: '请输入考试时长'
},
{
pattern: /^[^\s]*$/,
message: '禁止输入空格'
}]}
/>
</>
)}
</Col>
</Row>
</StepsForm.StepForm>
<StepsForm.StepForm<{
checkbox: string;
}>
name="object"
title="组卷"
stepProps={{
description: false,
}}
onFinish={async () => {
console.log(formRef.current?.getFieldsValue());
setCurrentStep(2) // 设置步骤号
//alert(params?.id || rulesId)
let msg = '操作成功'
if(!questions){
msg = '请选择试题'
message.error(msg);
return false;
}
// question 请选择试题 请选择试题
runPaper({ rules_id: params?.id || rulesId })
return true;
}}
>
<div style={{ margin: '0' }}>
<Typography style={{ padding: 24, fontSize: 24, textAlign: 'center' }}>{rulesName}</Typography>
{/** 一旦录入另一项将禁用,清空组卷后可选另一项 */}
<Row>
<Col span={12} style={{padding:5}}><Button size="large" onClick={()=>{handleSelectorModalVisible(true)}} value={1} style={{ display:'block', textAlign: 'center', width:'100%'}}></Button></Col>
<Col span={12} style={{padding:5}}><Button size="large" onClick={()=>{handleAutoModalVisible(true)}} value={2} style={{ display:'block', textAlign: 'center', width:'100%'}}></Button></Col>
</Row>
<Divider style={{ margin: '6px 0', opacity: 0.5 }} />
<Row>
<Col span={18} style={{ background: '#ffffff', padding: 0 }}>
{questions &&
<ProCard
title=""
extra={false}
split='vertical'
bordered
headerBordered
>
<Space direction="vertical" style={{ width: '100%', padding: '24px 48px' }}>
{questions && questions.map((item, idx) => (
item &&
<div style={{ border: 'none' }}>
<Typography style={{ marginBottom: 16, fontSize: 14, padding: '15px 15px 0 15px' }}>
{idx + 1}. {item?.question_stem}
</Typography>
{(item?.question_type === 0) && // 单选题
<div style={{ padding: '0 15px 15px 15px' }}>
{item?.answers && item?.answers.map((anster, k) => (
<div value={k} checked style={{ width: '100%', padding: 5, fontSize: 14 }}>
{anster?.is_true === '0' && <span style={{ color: '#1890ff', border: '1px solid #1890ff', display: 'inline-block', width: 16, height: 16, textAlign: 'center', borderRadius: '50%', fontSize: 12, lineHeight: '12px', marginRight: 10 }}>{labels[k]} </span>}
{anster?.is_true === '1' && <span style={{ color: '#ffffff', border: '1px solid #1890ff', display: 'inline-block', width: 16, height: 16, textAlign: 'center', borderRadius: '50%', fontSize: 12, lineHeight: '12px', marginRight: 10, backgroundColor: '#1890ff' }}>{labels[k]} </span>}
<span style={{ display: 'inline-block' }}>{anster?.answer}</span>
</div>
))}
</div>
}
{(item?.question_type === 1) && // 多选题
<div style={{ padding: '0 15px 15px 15px' }}>
{item?.answers && item?.answers.map((anster, k) => (
<div value={k} checked style={{ width: '100%', padding: 5, fontSize: 14 }}>
{anster?.is_true === '0' && <span style={{ color: '#1890ff', border: '1px solid #1890ff', display: 'inline-block', width: 16, height: 16, textAlign: 'center', borderRadius: '50%', fontSize: 12, lineHeight: '12px', marginRight: 10 }}>{labels[k]} </span>}
{anster?.is_true === '1' && <span style={{ color: '#ffffff', border: '1px solid #1890ff', display: 'inline-block', width: 16, height: 16, textAlign: 'center', borderRadius: '50%', fontSize: 12, lineHeight: '12px', marginRight: 10, backgroundColor: '#1890ff' }}>{labels[k]} </span>}
<span style={{ display: 'inline-block' }}>{anster?.answer}</span>
</div>
))}
</div>
}
{(item?.question_type === 2) && // 判断选题
<div style={{ padding: '0 15px 15px 15px' }}>
{item?.answers && item?.answers.map((anster, k) => (
<div value={k} checked style={{ width: '100%', padding: 5, fontSize: 14 }}>
{anster?.is_true === '0' && <span style={{ color: '#1890ff', border: '1px solid #1890ff', display: 'inline-block', width: 16, height: 16, textAlign: 'center', borderRadius: '50%', fontSize: 12, lineHeight: '12px', marginRight: 10 }}>{labels[k]} </span>}
{anster?.is_true === '1' && <span style={{ color: '#ffffff', border: '1px solid #1890ff', display: 'inline-block', width: 16, height: 16, textAlign: 'center', borderRadius: '50%', fontSize: 12, lineHeight: '12px', marginRight: 10, backgroundColor: '#1890ff' }}>{labels[k]} </span>}
<span style={{ display: 'inline-block' }}>{anster?.answer}</span>
</div>
))}
</div>
}
<div style={{ height: 'auto', backgroundColor: '#f0f0f0', textAlign: 'right', padding: 5, opacity: 1 }}>
<Button disabled={idx === 0} onClick={async ()=>{
const success = await handleExchangeSortNum(uuidPaper,[questions[idx-1]?.id, item?.id].toString(), rulesId)
if (success) {
run({
paper_uuid: uuidPaper,
page_size: 1000,
page_number: 1
}); // 获取当前选题列表
}
}}><ArrowUpOutlined /></Button>
<Button disabled={idx+1 === questions?.length} onClick={async ()=>{
const success = await handleExchangeSortNum(uuidPaper, [ item?.id, questions[idx+1]?.id].toString(), rulesId)
if (success) {
run({
paper_uuid: uuidPaper,
page_size: 1000,
page_number: 1
}); // 获取当前选题列表
}
}}><ArrowDownOutlined /></Button>
<Popconfirm key="popconfirm" title={`确认删除当前项吗?`} okText="是" cancelText="否"
onConfirm={async () => {
const success = await handleRemoveTempQuestion(uuidPaper, [{ question_id: item?.id }]); // 调用批量删除函数如果接口不支持批量需要在service中处理
if (success) {
run({
paper_uuid: uuidPaper,
page_size: 1000,
page_number: 1
}); // 获取当前选题列表
}
}}
>
<Button><DeleteOutlined /></Button>
</Popconfirm>
</div>
</div>
))}
</Space>
</ProCard>
}
{!questions &&
<Empty style={{minHeight:360, padding:60, verticalAlign:'middle',color:'#cccccc'}} description='暂无试卷' />
}
</Col>
<Col span={6} style={{ paddingLeft: 24 }}>
<div style={{ background: '#ffffff', padding: 24 }}>
<Space direction="vertical" style={{ width: '100%' }}>
<strong></strong>
<Typography> {questions?.length} {'-'} </Typography>
<Divider style={{ margin: '6px 0', opacity: 0.5 }} />
<Space direction="vertical">
{questionType.map((item) => {
return <Typography>{item?.name} {typeQuestionCount[Number(item.code)]} {'-'} </Typography>
}
)}
</Space>
<Divider style={{ margin: '6px 0', opacity: 0.5 }} />
<Button size="large" block onClick={() => {
/**
* 设置分值前需要题型数据
* [{count: 0, score: 0, score_harf: 0},{count: 0, score: 0, score_harf: 0},{count: 0, score: 0, score_harf: 0}]
*/
//
const _data = []
typeQuestionCount?.forEach((item) => {
_data.push({ count: item, score: 0, score_harf: 0 })
})
setQuestionTypeValues(_data)
console.log('typeQuestionCount', typeQuestionCount);
handleScoreModalVisible(true)
}}></Button>
<Tooltip defaultVisible={false} zIndex={1} title={<span style={{display:'block', width:152}}></span>} placement="bottom" color='#108ee9'>
<Button size="large" disabled={questions ? false : true} type="primary" block onClick={async () => {
console.log('uuidPaper::', uuidPaper)
console.log('rules_id::', params?.id)
const paper_id = paper_id ? paper_id : 0;
await handleUpdatePaper(params?.id || rulesId, uuidPaper, paper_id)
}}></Button>
</Tooltip>
</Space>
</div>
</Col>
</Row>
</div>
</StepsForm.StepForm>
<StepsForm.StepForm
name="time"
title="完成"
stepProps={{
description: false,
}}
onFinish={async () => {
console.log(formRef.current?.getFieldsValue());
// 跳转到指定路由
history.push('/examinationrules/normal');
return true;
}}
>
<Row gutter={24}>
<Col lg={12} md={12} sm={12} offset={8}>
{currentStep === 2 && <ProDescriptions
layout='horizontal'
column={1}
//actionRef={actionRef}
title={false}
request={async () => {
console.log('rulesId...', rulesId)
//{rules_id: rulesId}
console.log('params...', params)
const result = await queryRulesView({id: rulesId})
console.log('queryRulesView', result)
const subjectInfo = await getSubjectInfo({ subject_id: result.bean.subject_id })
console.log('subjectInfo', subjectInfo)
return { data: { ...result.bean, subject_name: subjectInfo.data.subject_name } };
}}
extra={false}
>
<ProDescriptions.Item dataIndex="id" hideInDescriptions />
<ProDescriptions.Item dataIndex="rules_name" label="考试名称" valueType="text" />
<ProDescriptions.Item dataIndex="subject_name" label="关联主题" valueType="text" />
<ProDescriptions.Item dataIndex="examination_time" label="考试时长" valueType="text" renderText={(text) => (`${text} 分钟`)} />
<ProDescriptions.Item dataIndex="grade" label="试卷信息" valueType="text" render={() => {
{/** 从试卷中读取 临时卷必须保存后才进入此页 */ }
return <Space direction="vertical">
<span> {paperInfo?.question_type_count?.map(item=>item.count)?.reduce((prev, curr)=>prev + curr)} , {paperInfo?.sum_score || '-'} </span>
<span>{paperInfo?.question_type_count?.map(item=>(<span style={{paddingRight:10}}>{`${item?.type_name} ${item?.count} 道题 `}</span>))}</span>
<span>线 {paperInfo?.pass_score || '-'} </span>
</Space>
}} />
</ProDescriptions>
}
</Col>
</Row>
</StepsForm.StepForm>
</StepsForm>
</ProCard>
<ModalForm
title={`手动组卷`}
width="80%"
layout='horizontal'
visible={selectorModalVisible}
onVisibleChange={handleSelectorModalVisible}
onFinish={async (values) => {
console.log('v::::', values.name);
const rows = selectorRef?.current?.getSelectedRows()
console.log('rows::::', rows);
const { code, data: paper, msg } = await handleAppend(Number(params?.id || rulesId), rows)
console.log('paper', paper)
setUuidPaper(paper?.paper_uuid)
console.log('paper_uuid', paper?.paper_uuid)
run({
paper_uuid: paper?.paper_uuid,
page_size: 1000,
page_number: 1
}); // 获取当前选题列表
// message.success('提交成功');
handleSelectorModalVisible(false)
return true;
}}
>
{selectorModalVisible && <QuestionSelector ref={selectorRef} />}
</ModalForm>
<ModalForm
title={`系统组卷`}
width="80%"
visible={autoModalVisible}
onVisibleChange={handleAutoModalVisible}
onFinish={async () => {
const values = autoRef?.current.getData()
console.log('data-v', values)
//const values = [{"question_type":0,"chapter_list": [{ "chapter_id":76, "count":1 },{ "chapter_id":77, "count":1 }]}]
const {code, data: paper, msg} = await autoPaper({rules_id: Number(params?.id), auto_param: JSON.stringify(values)})
//console.log('paper', paper)
setUuidPaper(paper?.paper_uuid)
run({
paper_uuid: paper?.paper_uuid,
page_size: 1000,
page_number: 1
}); // 获取当前选题列表
message.success('提交成功');
handleAutoModalVisible(false)
return true;
}}
>
{autoModalVisible && <AutoSelector ref={autoRef} subjectId={subjectId} questionType={questionType} />}
</ModalForm>
<ModalForm
title={`批量设置分值`}
//
width="60%"
visible={scoreModalVisible}
onVisibleChange={handleScoreModalVisible}
onFinish={async () => {
console.log('typeQuestionCount', typeQuestionCount)
const values = setterRef.current?.getData() // 获取题型分值数据
const passSocre = setterRef.current?.getValue() // 获取通过分数线
const sumCore = setterRef.current?.getSum() // 获取总分
console.log('批量设置分值v::::2', values);
// 题型分数
const { code, data: paper, msg } = await saveQuestionTypeScore({ rules_id: Number(params?.id || rulesId), type_score: JSON.stringify(values) })
const { success } = await updateScore({ rules_id: Number(params?.id || rulesId), pass_socre: passSocre, sum_score: sumCore })
//setSumScore(_sumScore)
//console.log('paper', paper)
// setUuidPaper(paper?.paper_uuid)
// message.success('提交成功');
handleScoreModalVisible(false)
return true;
}}
>
<ScoreSetter ref={setterRef} questionTypeValues={questionTypeValues || false} />
</ModalForm>
</PageContainer>
);
};