xlx_teacher_app/src/components/student/AverageComparisonChart.vue

236 lines
5.6 KiB
Vue
Raw Normal View History

2025-10-10 00:12:59 +08:00
<script setup lang="ts">
import type * as API from '@/service/types'
import UniEcharts from 'uni-echarts'
import { computed, ref } from 'vue'
const props = defineProps<{
data: API.AverageComparisonAnalysis | undefined
examSummary: API.ExamSummary | undefined
}>()
// 对比模式班级平均分、年级平均分、年级前N名
type ComparisonMode = 'class' | 'grade' | 'top_n'
const comparisonMode = ref<ComparisonMode>('class')
// 年级前N名配置
const topN = ref(50)
const showTopNDialog = ref(false)
const tempTopN = ref(topN.value)
// 图表标题
const chartTitle = computed(() => {
if (comparisonMode.value === 'class') {
return '班级平均分对比'
}
else if (comparisonMode.value === 'grade') {
return '年级平均分对比'
}
else {
return `年级前${topN.value}名对比`
}
})
// 图表配置
const chartOption = computed(() => {
if (!props.data?.subjects || props.data.subjects.length === 0) {
return {}
}
const categories = props.data.subjects.map(item => item.subject_name || '')
const myScores = props.data.subjects.map(item => item.my_score || 0)
const avgScores = props.data.subjects.map(item => item.average_score || 0)
let comparisonLabel = ''
if (comparisonMode.value === 'class') {
comparisonLabel = '班级平均分'
}
else if (comparisonMode.value === 'grade') {
comparisonLabel = '年级平均分'
}
else {
comparisonLabel = `年级前${topN.value}名平均分`
}
return {
tooltip: {
trigger: 'axis',
confine: true,
axisPointer: {
type: 'shadow',
},
formatter: (params: any) => {
if (!Array.isArray(params))
return ''
let result = `${params[0].axisValue}`
params.forEach((param: any) => {
result += `\n${param.marker} ${param.seriesName}: ${param.value.toFixed(2)}`
})
return result
},
},
legend: {
data: ['我的成绩', comparisonLabel],
top: 5,
textStyle: {
fontSize: 12,
},
},
grid: {
left: 45,
right: 15,
bottom: 40,
top: 40,
containLabel: false,
},
xAxis: {
type: 'category',
data: categories,
axisLabel: {
rotate: 30,
interval: 0,
fontSize: 10,
},
axisLine: {
lineStyle: {
color: '#cccccc',
},
},
},
yAxis: {
type: 'value',
name: '分数',
nameTextStyle: {
fontSize: 11,
},
axisLabel: {
fontSize: 10,
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#eeeeee',
},
},
},
series: [
{
name: '我的成绩',
type: 'bar',
data: myScores,
itemStyle: {
color: '#10b981',
},
barMaxWidth: 35,
},
{
name: comparisonLabel,
type: 'bar',
data: avgScores,
itemStyle: {
color: '#94a3b8',
},
barMaxWidth: 35,
},
],
}
})
const showChart = computed(() => {
return props.data?.subjects && props.data.subjects.length > 0
})
// 切换对比模式
function setComparisonMode(mode: ComparisonMode) {
if (mode === 'top_n') {
showTopNDialog.value = true
}
else {
comparisonMode.value = mode
}
}
// 确认设置前N名
function confirmTopN() {
topN.value = tempTopN.value
comparisonMode.value = 'top_n'
showTopNDialog.value = false
}
// 取消设置
function cancelTopN() {
showTopNDialog.value = false
tempTopN.value = topN.value
}
</script>
<template>
<view class="rounded-xl bg-white p-4 shadow-sm">
<!-- 标题 -->
<view class="mb-4">
<text class="text-lg text-slate-800 font-semibold">{{ chartTitle }}</text>
</view>
<!-- 模式切换按钮 -->
<view class="mb-4 flex justify-center gap-2">
<wd-button
size="small"
:type="comparisonMode === 'class' ? 'primary' : 'default'"
@click="setComparisonMode('class')"
>
班级平均分
</wd-button>
<wd-button
size="small"
:type="comparisonMode === 'grade' ? 'primary' : 'default'"
@click="setComparisonMode('grade')"
>
年级平均分
</wd-button>
<wd-button
size="small"
:type="comparisonMode === 'top_n' ? 'primary' : 'default'"
@click="setComparisonMode('top_n')"
>
年级前{{ topN }}
</wd-button>
</view>
<!-- 图表 -->
<view v-if="showChart" class="chart-container">
<uni-echarts :option="chartOption" custom-class="chart" />
</view>
<!-- 暂无数据 -->
<view v-else class="h-60 flex items-center justify-center rounded-lg bg-gray-50">
<text class="text-sm text-gray-400">暂无数据</text>
</view>
<!-- 设置年级前N名弹窗 -->
<wd-popup v-model="showTopNDialog" position="center" :close-on-click-modal="false">
<view class="w-70 rounded-2 bg-white p-6">
<view class="mb-4 text-center">
<text class="text-base text-slate-800 font-semibold">设置年级前N名</text>
</view>
<view class="mb-6 flex justify-center p-4">
<wd-input-number v-model="tempTopN" :min="1" :max="200" :step="1" />
</view>
<view class="flex gap-3">
<wd-button block type="default" @click="cancelTopN">
取消
</wd-button>
<wd-button block type="primary" @click="confirmTopN">
确定
</wd-button>
</view>
</view>
</wd-popup>
</view>
</template>
<style scoped>
.chart {
width: 100%;
height: 280px;
}
</style>