130 lines
2.8 KiB
Vue
130 lines
2.8 KiB
Vue
|
|
<script lang="ts" setup>
|
|||
|
|
import type * as API from '@/service/types'
|
|||
|
|
import UniEcharts from 'uni-echarts'
|
|||
|
|
import { computed } from 'vue'
|
|||
|
|
|
|||
|
|
const props = defineProps<{
|
|||
|
|
data: API.StudentScoreStat[]
|
|||
|
|
loading?: boolean
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const emit = defineEmits<{
|
|||
|
|
viewStudents: [score: number]
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
// 图表配置(横向柱状图)
|
|||
|
|
const chartOption = computed(() => {
|
|||
|
|
if (!props.data || props.data.length === 0) {
|
|||
|
|
return {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按分数排序
|
|||
|
|
const sortedData = [...props.data].sort((a, b) => (a.score || 0) - (b.score || 0))
|
|||
|
|
|
|||
|
|
const categories = sortedData.map(item => `${item.score || 0}分`)
|
|||
|
|
const counts = sortedData.map(item => item.student_count || 0)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
tooltip: {
|
|||
|
|
trigger: 'axis',
|
|||
|
|
confine: true,
|
|||
|
|
axisPointer: {
|
|||
|
|
type: 'shadow',
|
|||
|
|
},
|
|||
|
|
formatter: (params: any) => {
|
|||
|
|
if (!Array.isArray(params))
|
|||
|
|
return ''
|
|||
|
|
const param = params[0]
|
|||
|
|
// 使用 \n 换行而不是 <br/>
|
|||
|
|
return `${param.axisValue}\n${param.marker} 人数: ${param.value}人`
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
grid: {
|
|||
|
|
left: 50,
|
|||
|
|
right: 30,
|
|||
|
|
bottom: 30,
|
|||
|
|
top: 20,
|
|||
|
|
containLabel: false,
|
|||
|
|
},
|
|||
|
|
// 横向柱状图:xAxis 为数值轴,yAxis 为类目轴
|
|||
|
|
xAxis: {
|
|||
|
|
type: 'value',
|
|||
|
|
name: '人数',
|
|||
|
|
nameTextStyle: {
|
|||
|
|
fontSize: 11,
|
|||
|
|
},
|
|||
|
|
axisLabel: {
|
|||
|
|
fontSize: 10,
|
|||
|
|
},
|
|||
|
|
splitLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
type: 'dashed',
|
|||
|
|
color: '#eeeeee',
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
yAxis: {
|
|||
|
|
type: 'category',
|
|||
|
|
data: categories,
|
|||
|
|
axisLabel: {
|
|||
|
|
fontSize: 10,
|
|||
|
|
},
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: '#cccccc',
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
// 反转 Y 轴,让分数从上到下递增
|
|||
|
|
inverse: true,
|
|||
|
|
},
|
|||
|
|
series: [
|
|||
|
|
{
|
|||
|
|
name: '人数',
|
|||
|
|
type: 'bar',
|
|||
|
|
data: counts,
|
|||
|
|
itemStyle: {
|
|||
|
|
color: '#3b82f6',
|
|||
|
|
},
|
|||
|
|
barMaxWidth: 30,
|
|||
|
|
label: {
|
|||
|
|
show: true,
|
|||
|
|
position: 'right',
|
|||
|
|
fontSize: 10,
|
|||
|
|
formatter: '{c}人',
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const showChart = computed(() => {
|
|||
|
|
return !props.loading && props.data && props.data.length > 0
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<view class="score-chart">
|
|||
|
|
<!-- 图表 -->
|
|||
|
|
<view v-if="showChart" class="chart-container">
|
|||
|
|
<uni-echarts :option="chartOption" custom-class="chart" />
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 加载状态 -->
|
|||
|
|
<view v-if="loading" class="h-60 flex items-center justify-center">
|
|||
|
|
<wd-loading size="24" />
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 暂无数据 -->
|
|||
|
|
<view v-if="!loading && (!data || data.length === 0)" class="py-8 text-center">
|
|||
|
|
<text class="text-sm text-gray-500">暂无数据</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.chart {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 250px;
|
|||
|
|
}
|
|||
|
|
</style>
|