parent
4adf262a8a
commit
c3318b1259
7 changed files with 364 additions and 49 deletions
@ -0,0 +1,144 @@ |
||||
import React, { useState, useEffect } from 'react'; |
||||
import {Row, Col, Typography, Button} from 'antd'; |
||||
import ReactECharts from 'echarts-for-react'; |
||||
import QuestionComponent from './QuestionComponent'; |
||||
import { getPaperName, getQuestionAccuracyData, getQuestions } from 'api/exam-online/index'; |
||||
import { withRouter, RouteComponentProps } from 'react-router-dom'; // 引入 withRouter 和 RouteComponentProps
|
||||
import { SingleQuestionProps } from './QuestionComponent'; |
||||
import ESBreadcrumbComponent from "./ESBreadcrumbComponent"; |
||||
|
||||
const { Title } = Typography; |
||||
|
||||
// 接口返回的数据结构,包含准确率和数量(这里数量每个都是 1)
|
||||
interface QuestionAccuracyItem { |
||||
accuracy: number; |
||||
count: number; |
||||
} |
||||
|
||||
interface PaperData { |
||||
paperName: string; |
||||
questionAccuracyData: QuestionAccuracyItem[]; |
||||
} |
||||
|
||||
const ExamPaperAnalysisDetailPage: React.FC<RouteComponentProps> = ({history}) => { |
||||
const [paperData, setPaperData] = useState<PaperData | null>(null); |
||||
const [tableData, setTableData] = useState<SingleQuestionProps[]>([]); |
||||
|
||||
useEffect(() => { |
||||
const fetchData = async () => { |
||||
try { |
||||
const id = sessionStorage.getItem("paperId"); |
||||
sessionStorage.removeItem("paperId"); |
||||
const paperName = await getPaperName(id); |
||||
const questionAccuracyData = await getQuestionAccuracyData(id); |
||||
setPaperData({ paperName, questionAccuracyData }); |
||||
const questions = await getQuestions(id); |
||||
setTableData(questions); |
||||
} catch (error) { |
||||
console.error('数据获取失败:', error); |
||||
} |
||||
}; |
||||
|
||||
fetchData(); |
||||
}, []); |
||||
const handleGoBack = () => { |
||||
history.push("/exam-paper-analysis"); |
||||
}; |
||||
|
||||
|
||||
const getBarChartOption = (data: QuestionAccuracyItem[]) => { |
||||
// 初始化每个区间的题目数量
|
||||
const countGroupedData = [0, 0, 0, 0]; |
||||
let totalCount = 0; |
||||
|
||||
// 统计每个区间的题目数量和总数量
|
||||
data.forEach(item => { |
||||
if (item.accuracy < 0.1) { |
||||
countGroupedData[0] += item.count; |
||||
} else if (item.accuracy >= 0.1 && item.accuracy < 0.6) { |
||||
countGroupedData[1] += item.count; |
||||
} else if (item.accuracy >= 0.6 && item.accuracy < 0.8) { |
||||
countGroupedData[2] += item.count; |
||||
} else if (item.accuracy >= 0.8) { |
||||
countGroupedData[3] += item.count; |
||||
} |
||||
totalCount += item.count; |
||||
}); |
||||
|
||||
// 计算每个区间的百分比
|
||||
const groupedData = countGroupedData.map(count => totalCount > 0 ? count / totalCount : 0); |
||||
|
||||
return { |
||||
tooltip: { |
||||
trigger: 'axis', |
||||
axisPointer: { |
||||
type: 'shadow', |
||||
}, |
||||
// 修改 formatter 函数,将数值转换为百分比形式
|
||||
formatter: (params: any) => { |
||||
const xAxisValue = params[0].name; |
||||
const yAxisValue = (params[0].value * 100).toFixed(2) + '%'; |
||||
return `${xAxisValue}<br/>试题数量占比: ${yAxisValue}`; |
||||
}, |
||||
}, |
||||
xAxis: { |
||||
type: 'category', |
||||
data: ['<10%', '10%-60%', '60%-80%', '80%-100%'], |
||||
}, |
||||
yAxis: { |
||||
type: 'value', |
||||
min: 0, |
||||
max: 1, |
||||
interval: 0.2, |
||||
axisLabel: { |
||||
formatter: (value: number) => `${value * 100}%`, |
||||
}, |
||||
name: '试题数量占比', |
||||
nameLocation: 'top', |
||||
nameRotate: 0, // 让名称水平显示
|
||||
nameGap: 0, // 调整名称与坐标轴的间距
|
||||
nameTextStyle: { |
||||
padding: [0, 40, 100, 0] // 给名称上方增加一些内边距,使其更靠上
|
||||
} |
||||
}, |
||||
series: [ |
||||
{ |
||||
data: groupedData, |
||||
type: 'bar', |
||||
}, |
||||
], |
||||
}; |
||||
}; |
||||
|
||||
return ( |
||||
<div style={{ padding: 20 }}> |
||||
<ESBreadcrumbComponent url="/exam-statistics" previousText={"考试统计"} currentText={"试卷分析"} /> |
||||
{paperData && ( |
||||
<> |
||||
{/* 第一行:试卷名称 */} |
||||
<Row gutter={16}> |
||||
<Col span={24}> |
||||
<Title level={3}>试卷名称:{paperData.paperName}</Title> |
||||
<Button onClick={()=>handleGoBack()}>返回</Button> |
||||
</Col> |
||||
</Row> |
||||
{/* 第二行:QuestionComponent 标签 */} |
||||
<Row gutter={16} style={{ marginTop: 20 }}> |
||||
<Col span={24}> |
||||
<QuestionComponent questions={tableData} /> |
||||
</Col> |
||||
</Row> |
||||
{/* 第三行:柱状图 */} |
||||
<Row gutter={16} style={{ marginTop: 20 }}> |
||||
<Col span={24}> |
||||
<h1>准确率分析</h1> |
||||
<ReactECharts option={getBarChartOption(paperData.questionAccuracyData)} /> |
||||
</Col> |
||||
</Row> |
||||
</> |
||||
)} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default withRouter(ExamPaperAnalysisDetailPage); |
@ -0,0 +1,76 @@ |
||||
import React from 'react'; |
||||
|
||||
// 定义单条题目的 props 类型
|
||||
export interface SingleQuestionProps { |
||||
question_types: number; // 题目类型,1 为单选,2 为多选
|
||||
question_content: string; // 题干
|
||||
answer: string; // 答案
|
||||
options: string; // 答题选项
|
||||
question_number: number; // 题目编号
|
||||
accuracyRate: number; // 准确率
|
||||
} |
||||
|
||||
// 定义组件接收的参数类型,为单条题目 props 的数组
|
||||
interface QuestionProps { |
||||
questions: SingleQuestionProps[]; |
||||
style?: React.CSSProperties; // 用于外部传入样式,控制组件范围大小
|
||||
} |
||||
|
||||
const QuestionComponent: React.FC<QuestionProps> = ({ questions, style }) => { |
||||
return ( |
||||
<div className="question-container" style={{ ...style, overflow: 'auto' }}> |
||||
<h1>试题详情</h1> |
||||
{questions.map((question, index) => { |
||||
const { question_types, question_content, answer, options, question_number, accuracyRate } = question; |
||||
|
||||
// 处理 answer 为空的情况
|
||||
const answerArray = answer ? answer.split(',') : []; |
||||
// 处理 options 为空的情况
|
||||
const optionArray = options ? options.split(',') : []; |
||||
|
||||
// 根据题目类型判断是单选还是多选
|
||||
const isSingleChoice = question_types === 1; |
||||
|
||||
// 生成选项字母列表(A, B, C...)
|
||||
const optionLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
||||
|
||||
return ( |
||||
<div key={index} className="single-question" style={{ background: "#B5DBFF", marginBottom: 20 }}> |
||||
{/* 显示题目编号、题干和准确率 */} |
||||
<p className="question-title"> |
||||
{question_number}. {question_content} |
||||
<span className="accuracy-rate">准确率:{(accuracyRate* 100).toFixed(2) + '%'}</span> |
||||
</p> |
||||
{/* 显示答题选项 */} |
||||
<div className="option-list"> |
||||
{optionArray.map((option, optionIndex) => { |
||||
const letter = optionLetters[optionIndex]; |
||||
const isChecked = answerArray.includes(letter); |
||||
return ( |
||||
<label key={optionIndex} className="option-label" style={{ marginRight: 20 }}> |
||||
{isSingleChoice ? ( |
||||
<input |
||||
type="radio" |
||||
checked={isChecked} |
||||
disabled |
||||
/> |
||||
) : ( |
||||
<input |
||||
type="checkbox" |
||||
checked={isChecked} |
||||
disabled |
||||
/> |
||||
)} |
||||
{option.replace(":", ".")} |
||||
</label> |
||||
); |
||||
})} |
||||
</div> |
||||
</div> |
||||
); |
||||
})} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default QuestionComponent; |
@ -0,0 +1,57 @@ |
||||
import React from 'react'; |
||||
import QuestionComponent from './compoents/QuestionComponent'; |
||||
import ExamPaperAnalysisDetailPage from "./compoents/ExamPaperAnalysisDetailPage"; |
||||
|
||||
const ExamPaperAnalysisDetail: React.FC = () => { |
||||
const questions = [ |
||||
{ |
||||
question_types: 1, |
||||
question_content: '一天有多少小时?', |
||||
answer: 'B', |
||||
options: 'A:12小时,B:24小时,C:36小时', |
||||
question_number: 1, |
||||
accuracyRate: '90%' |
||||
}, |
||||
{ |
||||
question_types: 2, |
||||
question_content: '以下哪些是水果?', |
||||
answer: 'A,C', |
||||
options: 'A:苹果,B:黄瓜,C:香蕉', |
||||
question_number: 2, |
||||
accuracyRate: '80%' |
||||
}, |
||||
{ |
||||
question_types: 2, |
||||
question_content: '以下哪些是水果?', |
||||
answer: 'A,C', |
||||
options: 'A:苹果,B:黄瓜,C:香蕉', |
||||
question_number: 2, |
||||
accuracyRate: '80%' |
||||
}, |
||||
{ |
||||
question_types: 2, |
||||
question_content: '以下哪些是水果?', |
||||
answer: 'A,C', |
||||
options: 'A:苹果,B:黄瓜,C:香蕉', |
||||
question_number: 2, |
||||
accuracyRate: '80%' |
||||
}, |
||||
{ |
||||
question_types: 2, |
||||
question_content: '以下哪些是水果?', |
||||
answer: 'A,C', |
||||
options: 'A:苹果,B:黄瓜,C:香蕉', |
||||
question_number: 2, |
||||
accuracyRate: '80%' |
||||
} |
||||
]; |
||||
|
||||
return ( |
||||
<div> |
||||
<ExamPaperAnalysisDetailPage |
||||
/> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default ExamPaperAnalysisDetail; |
Loading…
Reference in new issue