You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/views/teacherEnd/exam/analysis.vue

171 lines
6.0 KiB
Vue

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.

<template>
<main class="analysis-page" v-loading="loading">
<header class="page-head"><div><el-button text :icon="ArrowLeft" @click="$router.push('/exam/index')">返回考评管理</el-button><h1>{{ data.examName || '考试数据分析' }}</h1></div><el-select v-model="classId" clearable placeholder="全部班级" @change="load"><el-option v-for="item in classes" :key="item.id" :label="item.name" :value="item.id" /></el-select></header>
<section class="metrics"><div v-for="item in metrics" :key="item.label"><span>{{ item.label }}</span><strong>{{ item.value }}</strong></div></section>
<section class="analysis-grid">
<div class="chart-block"><h2>成绩分布</h2><div ref="chartEl" class="chart" /></div>
<div class="hardest"><h2>得分率最低题目</h2><ol><li v-for="item in data.hardestQuestions || []" :key="item.id"><div>{{ item.stem }}</div><span>得分率 {{ item.scoreRate ?? 0 }}% · {{ item.wrongCount }} 人失分</span></li></ol><div v-if="!data.hardestQuestions?.length" class="table-empty"><el-icon class="empty-icon"><FolderOpened /></el-icon><p class="empty-title">暂无题目得分数据</p><p class="empty-tip">考试结束后将自动统计得分率最低的题目</p></div></div>
</section>
</main>
</template>
<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import { ArrowLeft, FolderOpened } from "@element-plus/icons-vue";
import * as echarts from "echarts";
import * as examApi from "@/api/exam";
const route = useRoute(); const examId = route.params.id; const loading = ref(false); const data = ref({}); const classes = ref([]); const classId = ref(""); const chartEl = ref(); let chart;
const metrics = computed(() => [{ label: "应考人数", value: data.value.expected || 0 }, { label: "实考人数", value: data.value.attended || 0 }, { label: "及格人数", value: data.value.passed || 0 }, { label: "不及格人数", value: data.value.failed || 0 }, { label: "最高分", value: data.value.highest || 0 }, { label: "最低分", value: data.value.lowest || 0 }, { label: "平均分", value: data.value.average || 0 }]);
onMounted(async () => { classes.value = (await examApi.getTeacherExam(examId)).data?.classes || []; await load(); window.addEventListener("resize", resize); }); onBeforeUnmount(() => window.removeEventListener("resize", resize));
async function load() { loading.value = true; try { data.value = (await examApi.getExamAnalysis(examId, { classId: classId.value || undefined })).data; await nextTick(); renderChart(); } finally { loading.value = false; } }
function renderChart() { chart ||= echarts.init(chartEl.value); chart.setOption({ backgroundColor: "transparent", tooltip: { trigger: "item", backgroundColor: "#ffffff", borderColor: "#e5e7eb", textStyle: { color: "#1f2937" } }, legend: { bottom: 0, textStyle: { color: "#6b7280", fontSize: 11 } }, color: ["#3b82f6", "#2ba471", "#f59e0b", "#ef4444"], series: [{ type: "pie", radius: ["48%", "72%"], center: ["50%", "44%"], label: { formatter: "{b}\n{c}人", color: "#6b7280", fontSize: 11 }, data: data.value.distribution || [] }] }); }
function resize() { chart?.resize(); }
</script>
<style scoped lang="scss">
.analysis-page {
color: var(--text-strong);
/* ===== 标题行(轻量标题行;卡片外观归 teacher-light.scss 扁平化接管)===== */
.page-head {
display: flex;
justify-content: space-between;
align-items: flex-end;
h1 {
margin: var(--sp-2) 0 0;
color: var(--text-strong);
font-size: var(--fs-page);
font-weight: 700;
}
.el-select {
width: var(--field-w);
}
}
/* ===== 指标条 ===== */
.metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
margin: var(--sp-5) 0;
div {
padding: var(--sp-4) var(--sp-3);
border-right: 1px solid var(--border-color);
display: grid;
gap: var(--sp-2);
min-width: 0;
&:last-child {
border: 0;
}
}
span {
font-size: var(--fs-body);
color: var(--text-body);
}
strong {
font-size: var(--fs-kpi);
color: var(--text-strong);
line-height: 1;
}
}
/* ===== 图表双栏(图表容器卡白底)===== */
.analysis-grid {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(360px, 0.9fr);
gap: var(--sp-5);
}
.chart-block,
.hardest {
background: #ffffff;
border: 1px solid var(--border-color);
border-radius: var(--r-card);
padding: var(--sp-4);
min-width: 0;
}
h2 {
color: var(--text-strong);
font-size: var(--fs-card);
font-weight: 700;
margin: 0 0 var(--sp-3);
}
.chart {
height: 390px;
}
/* ===== 失分率最高题目列表 ===== */
.hardest ol {
list-style: none;
padding: 0;
margin: 0;
counter-reset: item;
}
.hardest li {
display: grid;
grid-template-columns: 1fr auto;
gap: var(--sp-4);
padding: var(--sp-3) 0;
border-bottom: 1px solid var(--border-color);
line-height: 1.6;
font-size: var(--fs-body);
color: var(--text-strong);
&:before {
counter-increment: item;
content: counter(item);
grid-row: 1;
display: none;
}
span {
color: var(--text-body);
font-size: var(--fs-tag);
white-space: nowrap;
}
}
/* ===== 自定义空状态 ===== */
.table-empty {
padding: var(--sp-5) 0;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--sp-1);
.empty-icon {
font-size: 28px;
color: var(--text-muted);
}
.empty-title {
margin: var(--sp-1) 0 0;
font-size: var(--fs-body);
font-weight: 600;
color: var(--text-body);
}
.empty-tip {
margin: 0;
font-size: var(--fs-tag);
color: var(--text-muted);
}
}
@media (max-width: 900px) {
.analysis-grid {
grid-template-columns: 1fr;
}
}
}
</style>