236 lines
5.6 KiB
Vue
236 lines
5.6 KiB
Vue
<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>
|