ZhangQiPro/src/views/ReportCenter.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

579 lines
14 KiB
Vue

<template>
<div class="report-center">
<!-- 页面头部 -->
<header class="page-header">
<div class="header-left">
<div class="page-title-section">
<h1 class="page-title">
报告中心
</h1>
<p class="page-subtitle">
查看学生实践评价报告
</p>
</div>
</div>
<div class="page-actions">
<el-input
v-model="searchKeyword"
placeholder="搜索学生姓名或学号"
prefix-icon="Search"
class="search-input"
clearable
/>
</div>
</header>
<!-- 筛选条件 -->
<BaseCard class="filter-section">
<div class="filter-row">
<div class="filter-item">
<label class="filter-label">年级:</label>
<el-select
v-model="selectedGrade"
placeholder="全部年级"
clearable
>
<el-option
value=""
label="全部年级"
/>
<el-option
value="2021级"
label="2021级"
/>
<el-option
value="2022级"
label="2022级"
/>
<el-option
value="2023级"
label="2023级"
/>
<el-option
value="2024级"
label="2024级"
/>
</el-select>
</div>
<div class="filter-item">
<label class="filter-label">班级:</label>
<el-select
v-model="selectedClass"
placeholder="全部班级"
clearable
>
<el-option
value=""
label="全部班级"
/>
<el-option
value="计科1班"
label="计科1班"
/>
<el-option
value="计科2班"
label="计科2班"
/>
<el-option
value="软工1班"
label="软工1班"
/>
<el-option
value="软工2班"
label="软工2班"
/>
<el-option
value="数媒1班"
label="数媒1班"
/>
<el-option
value="人工智能1班"
label="人工智能1班"
/>
</el-select>
</div>
<div class="filter-actions">
<el-button
type="primary"
@click="applyFilter"
>
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="resetFilter">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</div>
</div>
</BaseCard>
<!-- 学生报告列表 -->
<div class="reports-grid">
<BaseCard
v-for="student in filteredStudents"
:key="student.id"
class="student-report-card"
@click="viewReport(student.id)"
>
<div class="student-card-content">
<!-- 学生基本信息 -->
<div class="student-info">
<div class="student-avatar">
<el-icon
size="40"
color="var(--primary)"
>
<User />
</el-icon>
</div>
<div class="student-details">
<h3 class="student-name">
{{ student.name }}
</h3>
<p class="student-id">
学号:{{ student.studentId }}
</p>
<p class="student-class">
{{ student.grade }} {{ student.class }}
</p>
</div>
</div>
<!-- 评价状态 -->
<div class="evaluation-status">
<div class="status-item">
<span class="status-label">企业评价</span>
<el-tag
:type="getEvaluationStatus(student.id, 'company').type"
size="small"
>
{{ getEvaluationStatus(student.id, 'company').text }}
</el-tag>
</div>
<div class="status-item">
<span class="status-label">教师评价</span>
<el-tag
:type="getEvaluationStatus(student.id, 'teacher').type"
size="small"
>
{{ getEvaluationStatus(student.id, 'teacher').text }}
</el-tag>
</div>
<div class="status-item">
<span class="status-label">专家评价</span>
<el-tag
:type="getEvaluationStatus(student.id, 'expert').type"
size="small"
>
{{ getEvaluationStatus(student.id, 'expert').text }}
</el-tag>
</div>
</div>
<!-- 综合评分 -->
<div class="overall-score">
<div class="score-display">
<span class="score-label">综合评分</span>
<span class="score-value">{{ getOverallScore(student.id) }}</span>
</div>
<el-progress
:percentage="getScorePercentage(student.id)"
:color="getScoreColor(student.id)"
:stroke-width="6"
/>
</div>
<!-- 操作按钮 -->
<div class="card-actions">
<el-button
type="primary"
@click.stop="viewReport(student.id)"
>
<el-icon><Document /></el-icon>
查看报告
</el-button>
<el-button @click.stop="viewPortrait(student.id)">
<el-icon><TrendCharts /></el-icon>
学生画像
</el-button>
</div>
</div>
</BaseCard>
</div>
<!-- 空状态 -->
<el-empty
v-if="filteredStudents.length === 0"
description="没有找到匹配的学生"
:image-size="200"
>
<template #image>
<el-icon
size="120"
color="var(--gray-lighter)"
>
<DocumentRemove />
</el-icon>
</template>
</el-empty>
</div>
</template>
<script>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { Search, Refresh, User, Document, TrendCharts, DocumentRemove } from '@element-plus/icons-vue'
import BaseCard from '@/components/BaseCard.vue'
import { mockStudents, mockEvaluationData } from '@/utils/mockData'
// CSS变量获取函数
const getCSSVariable = (variableName) => {
return getComputedStyle(document.documentElement).getPropertyValue(variableName).trim()
}
export default {
name: 'ReportCenter',
components: {
BaseCard,
Search,
Refresh,
User,
Document,
TrendCharts,
DocumentRemove
},
setup() {
const router = useRouter()
// 响应式数据
const searchKeyword = ref('')
const selectedGrade = ref('')
const selectedClass = ref('')
const students = ref([...mockStudents])
// 计算属性:过滤后的学生列表
const filteredStudents = computed(() => {
return students.value.filter(student => {
const matchesSearch = !searchKeyword.value ||
student.name.includes(searchKeyword.value) ||
student.studentId.includes(searchKeyword.value)
const matchesGrade = !selectedGrade.value || student.grade === selectedGrade.value
const matchesClass = !selectedClass.value || student.class === selectedClass.value
return matchesSearch && matchesGrade && matchesClass
})
})
// 获取评价状态
const getEvaluationStatus = (studentId, role) => {
const evaluationData = mockEvaluationData[role]?.[studentId]
if (evaluationData) {
return { type: 'success', text: '已完成' }
}
return { type: 'info', text: '待评价' }
}
// 获取综合评分
const getOverallScore = (studentId) => {
const companyData = mockEvaluationData.company?.[studentId]
const teacherData = mockEvaluationData.teacher?.[studentId]
const expertData = mockEvaluationData.expert?.[studentId]
let totalScore = 0
let count = 0
// 企业评价数据处理
if (companyData && companyData.attitude && companyData.skills && companyData.communication && companyData.problemSolving) {
const companyAvg = (companyData.attitude + companyData.skills + companyData.communication + companyData.problemSolving) / 4
if (!isNaN(companyAvg) && companyAvg > 0) {
totalScore += companyAvg * 20 // 转换为百分制
count++
}
}
// 教师评价数据处理
if (teacherData && teacherData.theory && teacherData.practice && teacherData.innovation && teacherData.attitude) {
const teacherAvg = (teacherData.theory + teacherData.practice + teacherData.innovation + teacherData.attitude) / 4
if (!isNaN(teacherAvg) && teacherAvg > 0) {
totalScore += teacherAvg * 20 // 转换为百分制
count++
}
}
// 专家评价数据处理
if (expertData && expertData.industryKnowledge && expertData.technicalDepth && expertData.applicationAbility) {
const expertAvg = (expertData.industryKnowledge + expertData.technicalDepth + expertData.applicationAbility) / 3
if (!isNaN(expertAvg) && expertAvg > 0) {
totalScore += expertAvg * 20 // 转换为百分制
count++
}
}
// 如果没有有效评分数据,返回暂无评分
if (count === 0 || isNaN(totalScore) || totalScore <= 0) {
return '暂无评分'
}
const average = (totalScore / count).toFixed(0)
return `${average}`
}
// 获取评分百分比
const getScorePercentage = (studentId) => {
const score = getOverallScore(studentId)
if (score === '暂无评分') return 0
const numericScore = parseFloat(score)
return numericScore // 现在已经是百分制,不需要转换
}
// 获取评分颜色
const getScoreColor = (studentId) => {
const score = getOverallScore(studentId)
if (score === '暂无评分') return getCSSVariable('--status-default')
const numericScore = parseFloat(score)
if (numericScore >= 90) return getCSSVariable('--status-excellent')
if (numericScore >= 80) return getCSSVariable('--status-good')
if (numericScore >= 70) return getCSSVariable('--status-fair')
return getCSSVariable('--status-poor')
}
// 应用筛选
const applyFilter = () => {
// 筛选逻辑已在 computed 中处理
}
// 重置筛选
const resetFilter = () => {
searchKeyword.value = ''
selectedGrade.value = ''
selectedClass.value = ''
}
// 查看报告
const viewReport = (studentId) => {
router.push(`/home/report/${studentId}`)
}
// 查看学生画像
const viewPortrait = (studentId) => {
router.push(`/home/portrait?studentId=${studentId}`)
}
return {
searchKeyword,
selectedGrade,
selectedClass,
filteredStudents,
getEvaluationStatus,
getOverallScore,
getScorePercentage,
getScoreColor,
applyFilter,
resetFilter,
viewReport,
viewPortrait
}
}
}
</script>
<style scoped>
.report-center {
padding: var(--spacing-lg);
min-height: 100vh;
background: var(--bg-secondary);
}
/* 页面头部 */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-lg);
}
.header-left {
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
.page-title-section h1 {
font-size: var(--font-size-xxl);
font-weight: 600;
color: var(--text-primary);
margin: 0 0 var(--spacing-xs) 0;
}
.page-subtitle {
font-size: var(--font-size-md);
color: var(--text-secondary);
margin: 0;
}
/* 筛选区域 */
.filter-section {
margin-bottom: var(--spacing-lg);
}
.filter-row {
display: flex;
align-items: center;
gap: var(--spacing-lg);
flex-wrap: wrap;
}
.filter-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.filter-label {
font-size: var(--font-size-sm);
color: var(--text-secondary);
white-space: nowrap;
}
.filter-actions {
display: flex;
gap: var(--spacing-sm);
margin-left: auto;
}
/* 报告网格 */
.reports-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.student-report-card {
cursor: pointer;
transition: var(--transition);
border: 1px solid var(--border);
}
.student-report-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
border-color: var(--primary-light);
}
.student-card-content {
padding: var(--spacing-lg);
}
/* 学生信息 */
.student-info {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
}
.student-avatar {
width: 60px;
height: 60px;
border-radius: var(--radius-lg);
background: var(--primary-lighter);
display: flex;
align-items: center;
justify-content: center;
}
.student-details h3 {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin: 0 0 var(--spacing-xs) 0;
}
.student-details p {
font-size: var(--font-size-sm);
color: var(--text-secondary);
margin: 0;
line-height: 1.4;
}
/* 评价状态 */
.evaluation-status {
margin-bottom: var(--spacing-lg);
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-sm);
}
.status-label {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
/* 综合评分 */
.overall-score {
margin-bottom: var(--spacing-lg);
}
.score-display {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-sm);
}
.score-label {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.score-value {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--primary);
}
/* 搜索输入框 */
.search-input {
width: 300px;
}
/* 操作按钮 */
.card-actions {
display: flex;
gap: var(--spacing-sm);
}
.card-actions .el-button {
flex: 1;
}
/* 响应式设计 */
@media (max-width: 768px) {
.reports-grid {
grid-template-columns: 1fr;
}
.filter-row {
flex-direction: column;
align-items: stretch;
gap: var(--spacing-md);
}
.filter-actions {
margin-left: 0;
}
.page-header {
flex-direction: column;
gap: var(--spacing-md);
align-items: stretch;
}
}
</style>