749 lines
19 KiB
Vue
749 lines
19 KiB
Vue
<template>
|
|
<div class="evaluate-page">
|
|
<!-- 页面标题 -->
|
|
<div class="page-header">
|
|
<h1 class="page-title">实践教学评价</h1>
|
|
<div class="page-actions">
|
|
<el-button v-if="canExport" type="primary" icon="Download" @click="exportData">
|
|
导出评价结果
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 筛选区域 -->
|
|
<BaseCard class="filter-section">
|
|
<div class="filter-row">
|
|
<div class="filter-item">
|
|
<label class="filter-label">学期选择</label>
|
|
<el-select v-model="selectedSemester" placeholder="请选择学期" clearable>
|
|
<el-option
|
|
v-for="semester in semesters"
|
|
:key="semester"
|
|
:label="semester"
|
|
:value="semester"
|
|
/>
|
|
</el-select>
|
|
</div>
|
|
|
|
<div class="filter-item">
|
|
<label class="filter-label">年级选择</label>
|
|
<el-select v-model="selectedGrade" placeholder="请选择年级" clearable>
|
|
<el-option
|
|
v-for="grade in grades"
|
|
:key="grade"
|
|
:label="grade"
|
|
:value="grade"
|
|
/>
|
|
</el-select>
|
|
</div>
|
|
|
|
<div class="filter-item">
|
|
<label class="filter-label">班级选择</label>
|
|
<el-select v-model="selectedClass" placeholder="请选择班级" clearable>
|
|
<el-option
|
|
v-for="className in classes"
|
|
:key="className"
|
|
:label="className"
|
|
:value="className"
|
|
/>
|
|
</el-select>
|
|
</div>
|
|
|
|
<div class="filter-item">
|
|
<label class="filter-label">实践项目</label>
|
|
<el-select v-model="selectedProject" placeholder="请选择项目" clearable>
|
|
<el-option
|
|
v-for="project in projects"
|
|
:key="project"
|
|
:label="project"
|
|
:value="project"
|
|
/>
|
|
</el-select>
|
|
</div>
|
|
|
|
<div class="filter-actions">
|
|
<el-button type="primary" icon="Search" @click="filterStudents">
|
|
查询学生
|
|
</el-button>
|
|
<el-button icon="Refresh" @click="resetFilters">
|
|
重置
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</BaseCard>
|
|
|
|
<!-- 学生列表区域 -->
|
|
<BaseCard class="student-list-section">
|
|
<div class="section-header">
|
|
<h3 class="section-title">
|
|
<el-icon><User /></el-icon>
|
|
学生列表
|
|
<span v-if="filteredStudents.length" class="count-badge">{{ filteredStudents.length }}人</span>
|
|
</h3>
|
|
<div class="list-actions">
|
|
<el-button
|
|
v-if="userRole === 'teacher'"
|
|
type="success"
|
|
icon="View"
|
|
size="small"
|
|
@click="viewAllPortraits"
|
|
>
|
|
批量查看画像
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<el-table
|
|
:data="filteredStudents"
|
|
stripe
|
|
border
|
|
empty-text="请先设置筛选条件查询学生"
|
|
table-layout="fixed"
|
|
>
|
|
<el-table-column prop="studentId" label="学号" width="120" />
|
|
<el-table-column prop="name" label="姓名" width="100" />
|
|
<el-table-column prop="grade" label="年级" width="100" />
|
|
<el-table-column prop="class" label="班级" width="120" />
|
|
|
|
<!-- 根据角色显示不同的评价状态和操作 -->
|
|
<el-table-column
|
|
v-if="userRole === 'company'"
|
|
label="企业评价"
|
|
width="120"
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
<el-tag
|
|
:type="getEvaluationStatus(row.id, 'company').type"
|
|
size="small"
|
|
>
|
|
{{ getEvaluationStatus(row.id, 'company').text }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column
|
|
v-if="userRole === 'teacher'"
|
|
label="教师评价"
|
|
width="120"
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
<el-tag
|
|
:type="getEvaluationStatus(row.id, 'teacher').type"
|
|
size="small"
|
|
>
|
|
{{ getEvaluationStatus(row.id, 'teacher').text }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column
|
|
v-if="userRole === 'expert'"
|
|
label="专家评价"
|
|
width="120"
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
<el-tag
|
|
:type="getEvaluationStatus(row.id, 'expert').type"
|
|
size="small"
|
|
>
|
|
{{ getEvaluationStatus(row.id, 'expert').text }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column
|
|
v-if="userRole === 'student'"
|
|
label="提交状态"
|
|
width="120"
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
<el-tag
|
|
:type="getSubmissionStatus(row.id).type"
|
|
size="small"
|
|
>
|
|
{{ getSubmissionStatus(row.id).text }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="操作" min-width="200" fixed="right">
|
|
<template #default="{ row }">
|
|
<div class="table-actions">
|
|
<!-- 企业角色操作 -->
|
|
<template v-if="userRole === 'company'">
|
|
<el-button
|
|
type="primary"
|
|
icon="Edit"
|
|
size="small"
|
|
@click="openEvaluationDialog(row, 'company')"
|
|
>
|
|
企业评价
|
|
</el-button>
|
|
<el-button
|
|
type="info"
|
|
icon="View"
|
|
size="small"
|
|
@click="viewSubmission(row)"
|
|
>
|
|
查看成果
|
|
</el-button>
|
|
</template>
|
|
|
|
<!-- 教师角色操作 -->
|
|
<template v-if="userRole === 'teacher'">
|
|
<el-button
|
|
type="primary"
|
|
icon="Edit"
|
|
size="small"
|
|
@click="openEvaluationDialog(row, 'teacher')"
|
|
>
|
|
教师评价
|
|
</el-button>
|
|
<el-button
|
|
type="success"
|
|
icon="User"
|
|
size="small"
|
|
@click="viewPortrait(row)"
|
|
>
|
|
查看画像
|
|
</el-button>
|
|
<el-button
|
|
type="warning"
|
|
icon="Document"
|
|
size="small"
|
|
@click="viewReport(row)"
|
|
>
|
|
生成报告
|
|
</el-button>
|
|
</template>
|
|
|
|
<!-- 专家角色操作 -->
|
|
<template v-if="userRole === 'expert'">
|
|
<el-button
|
|
type="primary"
|
|
icon="Edit"
|
|
size="small"
|
|
@click="openEvaluationDialog(row, 'expert')"
|
|
>
|
|
专家评价
|
|
</el-button>
|
|
<el-button
|
|
type="info"
|
|
icon="View"
|
|
size="small"
|
|
@click="viewSubmission(row)"
|
|
>
|
|
查看材料
|
|
</el-button>
|
|
</template>
|
|
|
|
<!-- 学生角色操作 -->
|
|
<template v-if="userRole === 'student'">
|
|
<el-button
|
|
type="primary"
|
|
icon="Upload"
|
|
size="small"
|
|
@click="openSubmissionDialog(row)"
|
|
>
|
|
提交成果
|
|
</el-button>
|
|
<el-button
|
|
type="success"
|
|
icon="Edit"
|
|
size="small"
|
|
@click="openSelfEvaluation(row)"
|
|
>
|
|
自我评价
|
|
</el-button>
|
|
<el-button
|
|
type="warning"
|
|
icon="Star"
|
|
size="small"
|
|
@click="openPeerEvaluation(row)"
|
|
>
|
|
互评他人
|
|
</el-button>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</BaseCard>
|
|
|
|
<!-- 评价对话框 -->
|
|
<EvaluationDialog
|
|
v-model="evaluationDialogVisible"
|
|
:student="selectedStudent"
|
|
:role="userRole"
|
|
@submit="handleEvaluationSubmit"
|
|
/>
|
|
|
|
<!-- 成果提交对话框 -->
|
|
<SubmissionDialog
|
|
v-model="submissionDialogVisible"
|
|
:student="selectedStudent"
|
|
@submit="handleSubmissionSubmit"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { mockOptions, mockStudents, mockEvaluationData } from '@/utils/mockData'
|
|
import { ElMessage } from 'element-plus'
|
|
import { User, Search, Refresh, Download, Edit, View, Upload, Star, Document } from '@element-plus/icons-vue'
|
|
import BaseCard from '@/components/BaseCard.vue'
|
|
import EvaluationDialog from '@/components/EvaluationDialog.vue'
|
|
import SubmissionDialog from '@/components/SubmissionDialog.vue'
|
|
|
|
export default {
|
|
name: 'Evaluate',
|
|
components: {
|
|
BaseCard,
|
|
EvaluationDialog,
|
|
SubmissionDialog,
|
|
User,
|
|
Search,
|
|
Refresh,
|
|
Download,
|
|
Edit,
|
|
View,
|
|
Upload,
|
|
Star,
|
|
Document
|
|
},
|
|
setup() {
|
|
const router = useRouter()
|
|
const authStore = useAuthStore()
|
|
|
|
// 响应式数据
|
|
const selectedSemester = ref('')
|
|
const selectedGrade = ref('')
|
|
const selectedClass = ref('')
|
|
const selectedProject = ref('')
|
|
const filteredStudents = ref([])
|
|
const evaluationDialogVisible = ref(false)
|
|
const submissionDialogVisible = ref(false)
|
|
const selectedStudent = ref(null)
|
|
|
|
// 基础数据
|
|
const semesters = ref(mockOptions.semesters)
|
|
const grades = ref(mockOptions.grades)
|
|
const classes = ref(mockOptions.classes)
|
|
const projects = ref(mockOptions.projects)
|
|
|
|
// 计算属性
|
|
const userRole = computed(() => authStore.user?.role)
|
|
const canExport = computed(() => ['teacher', 'company'].includes(userRole.value))
|
|
|
|
// 使用Mock评价状态数据
|
|
const evaluationData = reactive({
|
|
company: { ...mockEvaluationData.company },
|
|
teacher: { ...mockEvaluationData.teacher },
|
|
expert: { ...mockEvaluationData.expert },
|
|
submissions: { ...mockEvaluationData.submissions }
|
|
})
|
|
|
|
// 筛选学生
|
|
const filterStudents = () => {
|
|
if (!selectedSemester.value || !selectedGrade.value || !selectedClass.value) {
|
|
ElMessage.warning('请选择学期、年级和班级')
|
|
return
|
|
}
|
|
|
|
// 根据筛选条件过滤学生
|
|
filteredStudents.value = mockStudents.filter(student => {
|
|
return student.grade === selectedGrade.value &&
|
|
student.class === selectedClass.value
|
|
})
|
|
|
|
ElMessage.success(`查询到 ${filteredStudents.value.length} 名学生`)
|
|
}
|
|
|
|
// 重置筛选条件
|
|
const resetFilters = () => {
|
|
selectedSemester.value = ''
|
|
selectedGrade.value = ''
|
|
selectedClass.value = ''
|
|
selectedProject.value = ''
|
|
filteredStudents.value = []
|
|
}
|
|
|
|
// 获取评价状态
|
|
const getEvaluationStatus = (studentId, role) => {
|
|
const hasEvaluation = evaluationData[role][studentId]
|
|
if (hasEvaluation) {
|
|
return { type: 'success', text: '已评价' }
|
|
}
|
|
return { type: 'warning', text: '待评价' }
|
|
}
|
|
|
|
// 获取学生提交状态
|
|
const getSubmissionStatus = (studentId) => {
|
|
const submission = evaluationData.submissions[studentId]
|
|
if (submission?.submitted) {
|
|
return { type: 'success', text: '已提交' }
|
|
}
|
|
return { type: 'warning', text: '未提交' }
|
|
}
|
|
|
|
// 打开评价对话框
|
|
const openEvaluationDialog = (student, role) => {
|
|
selectedStudent.value = student
|
|
evaluationDialogVisible.value = true
|
|
}
|
|
|
|
// 打开成果提交对话框
|
|
const openSubmissionDialog = (student) => {
|
|
selectedStudent.value = student
|
|
submissionDialogVisible.value = true
|
|
}
|
|
|
|
// 处理评价提交
|
|
const handleEvaluationSubmit = (data) => {
|
|
evaluationData[userRole.value][selectedStudent.value.id] = data
|
|
ElMessage.success('评价提交成功')
|
|
evaluationDialogVisible.value = false
|
|
}
|
|
|
|
// 处理成果提交
|
|
const handleSubmissionSubmit = (data) => {
|
|
evaluationData.submissions[selectedStudent.value.id] = data
|
|
ElMessage.success('成果提交成功')
|
|
submissionDialogVisible.value = false
|
|
}
|
|
|
|
// 查看学生画像
|
|
const viewPortrait = (student) => {
|
|
router.push(`/portrait?studentId=${student.id}`)
|
|
}
|
|
|
|
// 查看评价报告
|
|
const viewReport = (student) => {
|
|
router.push(`/report/${student.id}`)
|
|
}
|
|
|
|
// 查看成果材料
|
|
const viewSubmission = (student) => {
|
|
ElMessage.info('查看成果材料功能')
|
|
}
|
|
|
|
// 自我评价
|
|
const openSelfEvaluation = (student) => {
|
|
ElMessage.info('自我评价功能')
|
|
}
|
|
|
|
// 互评功能
|
|
const openPeerEvaluation = (student) => {
|
|
ElMessage.info('互评功能')
|
|
}
|
|
|
|
// 批量查看画像
|
|
const viewAllPortraits = () => {
|
|
router.push('/portrait')
|
|
}
|
|
|
|
// 导出数据
|
|
const exportData = () => {
|
|
ElMessage.success('评价结果导出成功')
|
|
}
|
|
|
|
return {
|
|
// 响应式数据
|
|
selectedSemester,
|
|
selectedGrade,
|
|
selectedClass,
|
|
selectedProject,
|
|
filteredStudents,
|
|
evaluationDialogVisible,
|
|
submissionDialogVisible,
|
|
selectedStudent,
|
|
|
|
// 基础数据
|
|
semesters,
|
|
grades,
|
|
classes,
|
|
projects,
|
|
|
|
// 计算属性
|
|
userRole,
|
|
canExport,
|
|
|
|
// 方法
|
|
filterStudents,
|
|
resetFilters,
|
|
getEvaluationStatus,
|
|
getSubmissionStatus,
|
|
openEvaluationDialog,
|
|
openSubmissionDialog,
|
|
handleEvaluationSubmit,
|
|
handleSubmissionSubmit,
|
|
viewPortrait,
|
|
viewReport,
|
|
viewSubmission,
|
|
openSelfEvaluation,
|
|
openPeerEvaluation,
|
|
viewAllPortraits,
|
|
exportData
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.evaluate-page {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow-x: hidden;
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
.page-header {
|
|
background: var(--white-alpha-high);
|
|
backdrop-filter: blur(20px);
|
|
border-bottom: 1px solid var(--border);
|
|
padding: var(--spacing-lg) calc(var(--spacing-xxl) * 1.2);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
box-shadow: var(--shadow-sm);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 40;
|
|
animation: slideInDown 0.5s ease-out;
|
|
min-height: 64px;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: calc(var(--font-size-xxl) * 1.2);
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin: 0;
|
|
background: var(--gradient-brand);
|
|
background-clip: text;
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.page-actions {
|
|
display: flex;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.filter-section {
|
|
margin: var(--spacing-lg) calc(var(--spacing-xxl) * 1.2) var(--spacing-lg);
|
|
animation: slideInUp 0.6s ease-out 0.1s backwards;
|
|
}
|
|
|
|
.filter-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--spacing-lg);
|
|
align-items: end;
|
|
}
|
|
|
|
.filter-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 180px;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.filter-label {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.filter-actions {
|
|
display: flex;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.student-list-section {
|
|
margin: 0 calc(var(--spacing-xxl) * 1.2) calc(var(--spacing-xxl) * 1.5);
|
|
min-height: 400px;
|
|
animation: slideInUp 0.6s ease-out 0.2s backwards;
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--spacing-lg);
|
|
padding-bottom: var(--spacing-md);
|
|
border-bottom: 1px solid var(--border-light);
|
|
}
|
|
|
|
.section-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);
|
|
}
|
|
|
|
.count-badge {
|
|
background: var(--primary-lighter);
|
|
color: var(--primary);
|
|
padding: 2px 8px;
|
|
border-radius: 50px;
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 500;
|
|
margin-left: var(--spacing-xs);
|
|
}
|
|
|
|
.list-actions {
|
|
display: flex;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.table-actions {
|
|
display: flex;
|
|
gap: var(--spacing-xs);
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
:deep(.el-table) {
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
box-shadow: var(--shadow);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
:deep(.el-table th) {
|
|
background: linear-gradient(135deg, var(--bg-secondary) 0%, rgba(248, 250, 252, 0.8) 100%);
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
font-size: var(--font-size-sm);
|
|
border-bottom: 2px solid var(--border);
|
|
padding: var(--spacing-md) var(--spacing-sm);
|
|
}
|
|
|
|
:deep(.el-table td) {
|
|
padding: var(--spacing-md) var(--spacing-sm);
|
|
border-bottom: 1px solid var(--border-light);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
:deep(.el-table tr:hover) {
|
|
background: rgba(59, 130, 246, 0.02);
|
|
}
|
|
|
|
:deep(.el-button--small) {
|
|
padding: 6px 12px;
|
|
font-size: var(--font-size-xs);
|
|
border-radius: var(--radius-sm);
|
|
font-weight: 500;
|
|
transition: var(--transition);
|
|
min-width: auto;
|
|
}
|
|
|
|
:deep(.el-button--small:hover) {
|
|
transform: translateY(-1px);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
:deep(.el-select) {
|
|
width: 100%;
|
|
}
|
|
|
|
:deep(.el-select .el-input__wrapper) {
|
|
border-radius: var(--radius-md);
|
|
border: 1px solid var(--border);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
:deep(.el-select .el-input__wrapper:hover) {
|
|
border-color: var(--primary-light);
|
|
}
|
|
|
|
:deep(.el-tag) {
|
|
border-radius: var(--radius-sm);
|
|
font-weight: 500;
|
|
padding: 4px 8px;
|
|
font-size: var(--font-size-xs);
|
|
}
|
|
|
|
/* 动画效果 */
|
|
@keyframes slideInDown {
|
|
from { opacity: 0; transform: translateY(-30px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@keyframes slideInUp {
|
|
from { opacity: 0; transform: translateY(30px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.page-header {
|
|
padding: var(--spacing-md) var(--spacing-lg);
|
|
min-height: 56px;
|
|
}
|
|
|
|
.filter-section,
|
|
.student-list-section {
|
|
margin-left: var(--spacing-lg);
|
|
margin-right: var(--spacing-lg);
|
|
}
|
|
|
|
.filter-row {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.filter-item {
|
|
min-width: auto;
|
|
}
|
|
|
|
.table-actions {
|
|
justify-content: flex-start;
|
|
gap: 4px;
|
|
}
|
|
|
|
:deep(.el-button--small) {
|
|
padding: 4px 8px;
|
|
font-size: 11px;
|
|
}
|
|
|
|
:deep(.el-table) {
|
|
font-size: var(--font-size-xs);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.page-header {
|
|
flex-direction: column;
|
|
gap: var(--spacing-sm);
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.section-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.table-actions {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
width: 100%;
|
|
}
|
|
|
|
:deep(.el-button--small) {
|
|
width: 100%;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
</style> |