修复内容: 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>
579 lines
14 KiB
Vue
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> |