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

30 lines
3.8 KiB
Vue

<template>
<main class="analysis-page app-container" 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.wrongCount }} 人答错</span></li></ol><el-empty v-if="!data.hardestQuestions?.length" description="暂无客观题错题数据" /></div>
</section>
</main>
</template>
<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import { ArrowLeft } 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({ tooltip: { trigger: "item" }, legend: { bottom: 0 }, color: ["#176b87", "#41a186", "#e1a43b", "#d95d50"], series: [{ type: "pie", radius: ["48%", "72%"], center: ["50%", "44%"], label: { formatter: "{b}\n{c}人" }, data: data.value.distribution || [] }] }); }
function resize() { chart?.resize(); }
</script>
<style scoped lang="scss">
.analysis-page { color: #1f2937; }.page-head{display:flex;justify-content:space-between;align-items:flex-end;h1{margin:10px 0 0;font-size:24px}.el-select{width:220px}}
.metrics{display:grid;grid-template-columns:repeat(7,1fr);border-block:1px solid #dfe5e7;margin:22px 0 28px;div{padding:16px 12px;border-right:1px solid #e5e7eb;display:grid;gap:6px;&:last-child{border:0}}span{font-size:13px;color:#6b7280}strong{font-size:23px;color:#165f71}}
.analysis-grid{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(360px,.9fr);gap:36px}h2{font-size:18px;margin:0 0 16px}.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:16px;padding:16px 0;border-bottom:1px solid #e5e7eb;line-height:1.6;&:before{counter-increment:item;content:counter(item);grid-row:1;display:none}span{color:#c04d42;white-space:nowrap}}
@media(max-width:900px){.metrics{grid-template-columns:repeat(3,1fr)}.analysis-grid{grid-template-columns:1fr}}
</style>