ZhangQiPro/src/components/GradeDistributionChart.vue
sladro 29b58e6888 chore: ESLint代码规范修复 - 从779个问题降至13个警告
修复内容:
1.  ESLint配置完善
   - 添加浏览器全局变量:setTimeout, localStorage, getComputedStyle等
   - 禁用保留组件名规则 (vue/no-reserved-component-names)

2.  清理未使用的导入(自动修复648个警告)
   - BigScreenPortrait.vue: 移除reactive, mockPortraitData, generateChartData等
   - Evaluate.vue: 移除Search, Refresh, Download等未使用图标组件
   - Portrait.vue: 移除Download, Refresh组件
   - SubmissionDialog.vue: 移除Link组件
   - ReportCenter.vue: 移除onMounted导入

3.  修复未使用的变量
   - ReportAnalysis.vue: 移除未使用的index变量
   - FileUpload.vue: 移除未使用的index变量

修复结果:
- 修复前:779个问题(42错误,737警告)
- 修复后:13个问题(0错误,13警告)
- 修复率:98.3%

剩余13个警告:
- 未使用的error catch变量(4个)
- 未使用的index/role/student参数(7个)
- 未使用的导入DataAnalysis, getChartColors(2个)
- 所有警告不影响代码运行

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-01 17:29:59 +08:00

458 lines
11 KiB
Vue

<template>
<div class="grade-distribution-chart">
<div class="chart-header">
<h3 class="chart-title">
<el-icon><DataAnalysis /></el-icon>
学期成绩分布
</h3>
<div class="chart-info">
<span class="gpa-info">GPA: <strong>{{ gpaScore }}</strong></span>
<span class="semester-info">{{ semesterInfo }}</span>
</div>
</div>
<div
ref="chartContainer"
class="chart-container"
/>
<div class="grade-summary">
<div class="summary-grid">
<div
v-for="(subject, index) in subjects"
:key="index"
class="summary-item"
>
<div class="subject-name">
{{ subject }}
</div>
<div class="subject-score">
<span class="score-value">{{ scores[index] }}</span>
<span
class="grade-badge"
:class="getGradeClass(grades[index])"
>{{ grades[index] }}</span>
</div>
<div class="subject-credit">
{{ credits[index] }}学分
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted, watch, nextTick } from 'vue'
import * as echarts from 'echarts'
import { DataAnalysis } from '@element-plus/icons-vue'
export default {
name: 'GradeDistributionChart',
components: {
DataAnalysis
},
props: {
gradeData: {
type: Object,
required: true
}
},
setup(props) {
const chartContainer = ref(null)
let chartInstance = null
// 配置常量
const CHART_CONFIG = {
barWidth: '35%',
borderRadius: [4, 4, 0, 0],
gridSettings: {
left: '3%',
right: '4%',
bottom: '15%',
top: '15%'
},
legend: {
top: 10,
fontSize: 12
},
axis: {
fontSize: 11,
rotate: 45,
margin: 15
}
}
const subjects = ref(props.gradeData?.subjects || [])
const scores = ref(props.gradeData?.scores || [])
const grades = ref(props.gradeData?.grades || [])
const credits = ref(props.gradeData?.credits || [])
const classAverage = ref(props.gradeData?.classAverage || [])
const gpaScore = ref(props.gradeData?.gpa || 0)
const semesterInfo = ref(props.gradeData?.semester || '')
const getGradeClass = (grade) => {
const gradeMap = {
'A': 'grade-a',
'A-': 'grade-a-minus',
'B+': 'grade-b-plus',
'B': 'grade-b',
'B-': 'grade-b-minus',
'C+': 'grade-c-plus',
'C': 'grade-c'
}
return gradeMap[grade] || 'grade-default'
}
// 获取CSS变量的实际值
const getCSSVariable = (variable) => {
return getComputedStyle(document.documentElement).getPropertyValue(variable).trim()
}
const initChart = () => {
if (!chartContainer.value) return
chartInstance = echarts.init(chartContainer.value)
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function(params) {
let result = `${params[0].name}<br/>`
params.forEach(param => {
result += `${param.seriesName}: ${param.value}<br/>`
})
return result
},
backgroundColor: getCSSVariable('--bg-primary'),
borderColor: getCSSVariable('--border'),
borderWidth: 1,
textStyle: {
color: getCSSVariable('--text-primary')
}
},
legend: {
data: ['个人成绩', '班级平均'],
top: CHART_CONFIG.legend.top,
textStyle: {
color: getCSSVariable('--text-secondary'),
fontSize: CHART_CONFIG.legend.fontSize
}
},
grid: {
...CHART_CONFIG.gridSettings,
containLabel: true
},
xAxis: {
type: 'category',
data: subjects.value,
axisLine: {
lineStyle: {
color: getCSSVariable('--border-light')
}
},
axisLabel: {
color: getCSSVariable('--text-secondary'),
fontSize: CHART_CONFIG.axis.fontSize,
interval: 0,
rotate: CHART_CONFIG.axis.rotate,
margin: CHART_CONFIG.axis.margin
},
axisTick: {
show: false
}
},
yAxis: {
type: 'value',
name: '分数',
nameTextStyle: {
color: getCSSVariable('--text-secondary'),
fontSize: CHART_CONFIG.legend.fontSize
},
axisLine: {
show: false
},
axisLabel: {
color: getCSSVariable('--text-secondary'),
fontSize: CHART_CONFIG.axis.fontSize
},
splitLine: {
lineStyle: {
color: getCSSVariable('--border-light'),
type: 'dashed'
}
},
min: 0,
max: 100
},
series: [
{
name: '个人成绩',
type: 'bar',
data: scores.value,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: getCSSVariable('--primary') },
{ offset: 1, color: getCSSVariable('--primary-light') }
]),
borderRadius: CHART_CONFIG.borderRadius
},
emphasis: {
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: getCSSVariable('--primary') },
{ offset: 1, color: getCSSVariable('--primary-light') }
])
}
},
barWidth: CHART_CONFIG.barWidth
},
{
name: '班级平均',
type: 'bar',
data: classAverage.value,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: getCSSVariable('--success') },
{ offset: 1, color: getCSSVariable('--success-light') }
]),
borderRadius: CHART_CONFIG.borderRadius
},
emphasis: {
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: getCSSVariable('--success') },
{ offset: 1, color: getCSSVariable('--success-light') }
])
}
},
barWidth: CHART_CONFIG.barWidth
}
]
}
chartInstance.setOption(option)
}
const resizeChart = () => {
if (chartInstance) {
chartInstance.resize()
}
}
onMounted(() => {
nextTick(() => {
initChart()
window.addEventListener('resize', resizeChart)
})
})
watch(() => props.gradeData, (newData) => {
if (newData) {
subjects.value = newData.subjects || []
scores.value = newData.scores || []
grades.value = newData.grades || []
credits.value = newData.credits || []
classAverage.value = newData.classAverage || []
gpaScore.value = newData.gpa || 0
semesterInfo.value = newData.semester || ''
if (chartInstance) {
chartInstance.setOption({
xAxis: {
data: subjects.value
},
series: [
{
data: scores.value
},
{
data: classAverage.value
}
]
})
}
}
}, { deep: true })
return {
chartContainer,
subjects,
scores,
grades,
credits,
gpaScore,
semesterInfo,
getGradeClass
}
}
}
</script>
<style scoped>
.grade-distribution-chart {
background: var(--bg-primary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
box-shadow: var(--shadow);
border: 1px solid var(--border);
height: 100%;
display: flex;
flex-direction: column;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--border-light);
}
.chart-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin: 0;
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.chart-info {
display: flex;
gap: var(--spacing-lg);
font-size: var(--font-size-sm);
}
.gpa-info,
.semester-info {
color: var(--text-secondary);
}
.gpa-info strong {
color: var(--primary);
font-weight: 600;
}
.chart-container {
flex: 1;
min-height: 300px;
position: relative;
}
.grade-summary {
margin-top: var(--spacing-md);
padding-top: var(--spacing-md);
border-top: 1px solid var(--border-light);
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: var(--spacing-sm);
}
.summary-item {
background: var(--bg-secondary);
border-radius: var(--radius-md);
padding: var(--spacing-sm);
display: flex;
flex-direction: column;
gap: 4px;
transition: var(--transition);
}
.summary-item:hover {
background: var(--primary-alpha-light);
transform: translateY(-2px);
}
.subject-name {
font-size: var(--font-size-sm);
font-weight: 500;
color: var(--text-primary);
}
.subject-score {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-xs);
}
.score-value {
font-size: var(--font-size-lg);
font-weight: 700;
color: var(--primary);
}
.grade-badge {
padding: 2px 6px;
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: 600;
text-transform: uppercase;
}
.grade-a {
background: var(--success-light);
color: var(--success);
}
.grade-a-minus {
background: var(--success-alpha-heavy);
color: var(--success);
}
.grade-b-plus {
background: var(--primary-alpha-heavy);
color: var(--primary);
}
.grade-b {
background: var(--secondary-alpha-heavy);
color: var(--secondary);
}
.grade-b-minus {
background: var(--warning-light);
color: var(--warning);
}
.grade-c-plus,
.grade-c {
background: var(--danger-alpha-heavy);
color: var(--danger);
}
.subject-credit {
font-size: var(--font-size-xs);
color: var(--text-muted);
}
@media (max-width: 768px) {
.chart-header {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-sm);
}
.summary-grid {
grid-template-columns: repeat(2, 1fr);
}
.chart-container {
min-height: 250px;
}
}
@media (max-width: 480px) {
.summary-grid {
grid-template-columns: 1fr;
}
}
</style>