|
@@ -0,0 +1,219 @@
|
|
|
+<template>
|
|
|
+ <div class="score-curve-page">
|
|
|
+ <!-- 页面头部 -->
|
|
|
+ <div class="page-header">
|
|
|
+ <h2 class="page-title">给分曲线</h2>
|
|
|
+ <el-button @click="goBack">返回</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图表区域 -->
|
|
|
+ <div v-if="chartData.length > 0" class="part-box">
|
|
|
+ <h3>给分分布图</h3>
|
|
|
+ <Chart :options="chartOptions" height="400px" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据表格 -->
|
|
|
+ <div class="part-box">
|
|
|
+ <el-table class="page-table" :data="dataList" :loading="loading">
|
|
|
+ <el-table-column property="marker" label="评卷员" width="120" />
|
|
|
+ <el-table-column property="name" label="姓名" min-width="100" />
|
|
|
+ <!-- 动态分数列 -->
|
|
|
+ <el-table-column
|
|
|
+ v-for="scoreRange in scoreRanges"
|
|
|
+ :key="scoreRange.label"
|
|
|
+ :label="scoreRange.label"
|
|
|
+ width="80"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ {{ getScoreCount(scope.row, scoreRange.score) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { reactive, ref, computed, onMounted } from 'vue';
|
|
|
+ import { useRouter, useRoute } from 'vue-router';
|
|
|
+ import { ElMessage } from 'element-plus';
|
|
|
+ import { qualityMonitorScoreList } from '@/api/mark';
|
|
|
+ import { QMScoreItem, QMScoreListParam } from '@/api/types/mark';
|
|
|
+ import useLoading from '@/hooks/loading';
|
|
|
+
|
|
|
+ defineOptions({
|
|
|
+ name: 'ScoreCurve',
|
|
|
+ });
|
|
|
+
|
|
|
+ const router = useRouter();
|
|
|
+ const route = useRoute();
|
|
|
+ const { loading, setLoading } = useLoading();
|
|
|
+
|
|
|
+ // 从路由参数初始化筛选条件
|
|
|
+ const searchModel = reactive<QMScoreListParam>({
|
|
|
+ subject: route.query.subject ? Number(route.query.subject) : null,
|
|
|
+ group: (route.query.group as string) || '',
|
|
|
+ });
|
|
|
+
|
|
|
+ const dataList = ref<QMScoreItem[]>([]);
|
|
|
+
|
|
|
+ // 计算所有可能的分数范围
|
|
|
+ const scoreRanges = computed(() => {
|
|
|
+ const ranges: Array<{ score: number; label: string }> = [];
|
|
|
+ const allScores = new Set<number>();
|
|
|
+
|
|
|
+ // 收集所有分数
|
|
|
+ dataList.value.forEach((item) => {
|
|
|
+ item.scores.forEach((scoreItem) => {
|
|
|
+ allScores.add(scoreItem.score);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 排序并生成标签
|
|
|
+ const sortedScores = Array.from(allScores).sort((a, b) => a - b);
|
|
|
+ sortedScores.forEach((score) => {
|
|
|
+ ranges.push({
|
|
|
+ score,
|
|
|
+ label: `${score}分`,
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ return ranges;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 获取指定分数的数量
|
|
|
+ const getScoreCount = (row: QMScoreItem, score: number): number => {
|
|
|
+ const scoreItem = row.scores.find((item) => item.score === score);
|
|
|
+ return scoreItem ? scoreItem.count : 0;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 图表数据
|
|
|
+ const chartData = computed(() => {
|
|
|
+ if (dataList.value.length === 0) return [];
|
|
|
+
|
|
|
+ const seriesData: any[] = [];
|
|
|
+ const xAxisData = scoreRanges.value.map((range) => range.label);
|
|
|
+
|
|
|
+ dataList.value.forEach((item) => {
|
|
|
+ const data = scoreRanges.value.map((range) => {
|
|
|
+ const scoreItem = item.scores.find((s) => s.score === range.score);
|
|
|
+ return scoreItem ? scoreItem.count : 0;
|
|
|
+ });
|
|
|
+
|
|
|
+ seriesData.push({
|
|
|
+ name: `${item.name}`,
|
|
|
+ type: 'line',
|
|
|
+ data,
|
|
|
+ smooth: true,
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ return {
|
|
|
+ xAxisData,
|
|
|
+ seriesData,
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ // 图表配置
|
|
|
+ const chartOptions = computed(() => {
|
|
|
+ if (chartData.value.length === 0) return {};
|
|
|
+
|
|
|
+ return {
|
|
|
+ title: {
|
|
|
+ text: '给分曲线分布',
|
|
|
+ left: 'center',
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'cross',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: chartData.value.seriesData.map((item: any) => item.name),
|
|
|
+ top: 30,
|
|
|
+ type: 'scroll',
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ top: '15%',
|
|
|
+ containLabel: true,
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ data: chartData.value.xAxisData,
|
|
|
+ name: '分数',
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '数量',
|
|
|
+ },
|
|
|
+ series: chartData.value.seriesData,
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ // 加载数据
|
|
|
+ async function loadData() {
|
|
|
+ if (!searchModel.subject) {
|
|
|
+ ElMessage.warning('请选择科目');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ setLoading(true);
|
|
|
+ const result = await qualityMonitorScoreList(searchModel);
|
|
|
+ dataList.value = result;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载给分曲线数据失败:', error);
|
|
|
+ ElMessage.error('加载数据失败');
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回上一页
|
|
|
+ function goBack() {
|
|
|
+ router.back();
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ // 如果有默认科目,自动加载数据
|
|
|
+ if (searchModel.subject) {
|
|
|
+ loadData();
|
|
|
+ }
|
|
|
+ });
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+ .score-curve-page {
|
|
|
+ .page-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding: 16px 0;
|
|
|
+ border-bottom: 1px solid #e8e8e8;
|
|
|
+
|
|
|
+ .page-title {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .part-box {
|
|
|
+ margin-bottom: 16px;
|
|
|
+
|
|
|
+ h3 {
|
|
|
+ margin: 0 0 16px 0;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|