hookehuyr

feat(teacher): 添加学生详情页面和路由配置

......@@ -33,4 +33,13 @@ export default [
requiresAuth: true
},
},
{
path: '/teacher/student/:id',
name: 'Student',
component: () => import('../views/teacher/studentPage.vue'),
meta: {
title: '班级详情',
requiresAuth: true
},
},
]
......
<!--
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2025-06-19 17:12:19
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-06-19 17:27:16
* @FilePath: /mlaj/src/views/teacher/studentPage.vue
* @Description: 学生详情页面
-->
<template>
<div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen">
<!-- 学生基本信息 -->
<div class="bg-white p-4">
<div class="flex items-start mb-4">
<van-image round width="4rem" height="4rem"
:src="studentInfo.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" fit="cover" class="mr-4" />
<div class="flex-1">
<div class="flex items-center mb-2">
<h2 class="text-xl font-bold text-gray-800 mr-2">{{ studentInfo.name }}</h2>
<van-icon v-if="studentInfo.gender === 'male'" name="friends-o" color="#3b82f6" size="16" />
<van-icon v-else name="like-o" color="#ec4899" size="16" />
</div>
<div class="flex items-center mb-2">
<van-icon name="chat-o" size="16" color="#10b981" class="mr-1" />
<span class="text-sm text-gray-600 mr-4">{{ studentInfo.className }}</span>
<van-icon name="phone-o" size="16" color="#10b981" class="mr-1" />
<span class="text-sm text-gray-600">{{ formatPhone(studentInfo.phone) }}</span>
</div>
<!-- 标签 -->
<div class="flex flex-wrap gap-2">
<van-tag v-for="tag in studentInfo.tags" :key="tag" type="success" size="small">
{{ tag }}
</van-tag>
</div>
</div>
</div>
</div>
<!-- 所学课程 -->
<div class="bg-white mt-2 p-4">
<div class="flex items-center mb-3">
<van-icon name="bookmark-o" size="16" color="#10b981" class="mr-2" />
<span class="text-sm font-medium text-gray-700">所学课程</span>
</div>
<div class="flex flex-wrap gap-2">
<van-tag v-for="course in studentInfo.courses" :key="course" type="primary" size="small">
{{ course }}
</van-tag>
</div>
</div>
<!-- 统计数据 -->
<div class="mt-2">
<van-row>
<!-- 出勤率 -->
<van-col span="8">
<div class="bg-white p-4 text-center">
<div class="relative w-16 h-16 mx-auto mb-2">
<van-circle v-model:current-rate="studentStats.attendanceRate" :rate="studentStats.attendanceRate"
:speed="100" :text="studentStats.attendanceRate + '%'" stroke-width="50" color="#10b981" size="64" />
</div>
<div class="text-sm text-gray-600">出勤率</div>
</div>
</van-col>
<!-- 作业完成率 -->
<van-col span="8">
<div class="bg-white p-4 text-center">
<div class="relative w-16 h-16 mx-auto mb-2">
<van-circle v-model:current-rate="studentStats.homeworkRate" :rate="studentStats.homeworkRate"
:speed="100" :text="studentStats.homeworkRate + '%'" stroke-width="50" color="#3b82f6" size="64" />
</div>
<div class="text-sm text-gray-600">作业完成率</div>
</div>
</van-col>
<!-- 测验成绩 -->
<van-col span="8">
<div class="bg-white p-4 text-center">
<div class="relative w-16 h-16 mx-auto mb-2">
<van-circle v-model:current-rate="studentStats.testScore" :rate="studentStats.testScore" :speed="100"
:text="studentStats.testScore + '%'" stroke-width="50" color="#f59e0b" size="64" />
</div>
<div class="text-sm text-gray-600">测验成绩</div>
</div>
</van-col>
</van-row>
</div>
<!-- 功能按钮 -->
<div class="bg-white mt-2 p-4">
<!-- 状态筛选 -->
<div class="flex items-center justify-end mb-4">
<div @click="showStatusPopup = true" class="flex items-center text-sm text-gray-600 cursor-pointer">
<span>{{ statusFilter }}</span>
<van-icon name="arrow-down" size="14" class="ml-1" />
</div>
</div>
<div class="flex items-center justify-between mb-3">
<div @click="activeTab = 'statistics'"
:class="['flex-1 text-center py-2 text-sm font-medium cursor-pointer', activeTab === 'statistics' ? 'text-green-600 border-b-2 border-green-600' : 'text-gray-500']">
打卡统计
</div>
<div @click="activeTab = 'homework'"
:class="['flex-1 text-center py-2 text-sm font-medium cursor-pointer', activeTab === 'homework' ? 'text-green-600 border-b-2 border-green-600' : 'text-gray-500']">
作业记录
</div>
<div @click="activeTab = 'evaluation'"
:class="['flex-1 text-center py-2 text-sm font-medium cursor-pointer', activeTab === 'evaluation' ? 'text-green-600 border-b-2 border-green-600' : 'text-gray-500']">
班主任点评
</div>
</div>
<!-- 记录列表 -->
<van-list v-if="activeTab === 'statistics'" v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<div v-for="record in filteredRecords" :key="record.id"
class="flex items-center justify-between py-4 border-b border-gray-100 last:border-b-0">
<div class="flex items-center flex-1">
<div class="mr-4">
<div style="display: flex; justify-content: center;">
<van-icon name="calendar-o" size="16" color="#6b7280" class="mb-1" /> &nbsp;
<span class="text-xs text-gray-500">{{ record.date }}</span>
</div>
<div style="display: flex; ">
<van-icon name="clock-o" size="16" color="#6b7280" class="mb-1" /> &nbsp;
<span class="text-xs text-gray-500">{{ record.time }}</span>
</div>
</div>
</div>
<div class="flex items-center">
<span v-if="record.status === '正常'" class="text-green-600 text-sm mr-2">{{ record.status }}</span>
<span v-else-if="record.status === '迟到'" class="text-orange-500 text-sm mr-2">{{ record.status }}</span>
<span v-else class="text-red-500 text-sm mr-2">{{ record.status }}</span>
<van-icon v-if="record.status === '正常'" name="success" color="#10b981" size="16" />
<van-icon v-else-if="record.status === '迟到'" name="warning-o" color="#f59e0b" size="16" />
<van-icon v-else name="close" color="#ef4444" size="16" />
</div>
</div>
</van-list>
</div>
<!-- 状态筛选弹窗 -->
<van-popup v-model:show="showStatusPopup" position="bottom" round>
<div class="p-4">
<div class="text-center text-lg font-bold mb-4">选择状态</div>
<van-cell-group>
<van-cell v-for="option in statusOptions" :key="option.value" :title="option.text" clickable
@click="onStatusSelect(option)" :border="false"
:class="{ 'text-green-600': statusFilter === option.value }">
<template #right-icon>
<van-icon v-if="statusFilter === option.value" name="success" color="#10b981" />
</template>
</van-cell>
</van-cell-group>
<div class="mt-4">
<van-button block @click="showStatusPopup = false">取消</van-button>
</div>
</div>
</van-popup>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// 学生信息
const studentInfo = ref({
id: 1,
name: '李华',
gender: 'female',
className: '高一(3)班',
phone: '13812345678',
avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg',
tags: ['亲子学堂2025级'],
courses: ['讲师训2025', '亲子学堂课程']
})
// 统计数据
const studentStats = ref({
attendanceRate: 92,
homeworkRate: 88,
testScore: 85
})
// 当前选中的标签页
const activeTab = ref('statistics')
// 状态筛选
const statusFilter = ref('按状态')
const showStatusPopup = ref(false)
// 状态选项
const statusOptions = ref([
{ text: '按状态', value: '按状态' },
{ text: '正常', value: '正常' },
{ text: '迟到', value: '迟到' },
{ text: '缺勤', value: '缺勤' }
])
// 记录列表
const records = ref([
{
id: 1,
date: '2023-11-20',
time: '07:45',
status: '正常'
},
{
id: 2,
date: '2023-11-19',
time: '07:50',
status: '正常'
},
{
id: 3,
date: '2023-11-18',
time: '07:42',
status: '正常'
},
{
id: 4,
date: '2023-11-17',
time: '08:10',
status: '迟到'
},
{
id: 5,
date: '2023-11-16',
time: '07:48',
status: '正常'
},
{
id: 6,
date: '2023-11-15',
time: '00:00',
status: '缺勤'
}
])
// 列表加载状态
const loading = ref(false)
const finished = ref(false)
/**
* 过滤后的记录列表
*/
const filteredRecords = computed(() => {
let filtered = records.value
// 按状态筛选
if (statusFilter.value !== '按状态') {
filtered = filtered.filter(record => record.status === statusFilter.value)
}
return filtered
})
/**
* 格式化手机号
* @param {string} phone - 手机号
* @returns {string} 格式化后的手机号
*/
const formatPhone = (phone) => {
if (!phone) return ''
return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3')
}
/**
* 处理状态选择
* @param {Object} option - 选中的状态选项
*/
const onStatusSelect = (option) => {
statusFilter.value = option.value
showStatusPopup.value = false
}
/**
* 加载更多数据
*/
const onLoad = () => {
setTimeout(() => {
loading.value = false
finished.value = true
}, 1000)
}
/**
* 组件挂载时初始化数据
*/
onMounted(() => {
// 从路由参数获取学生ID
const studentId = route.params.id
console.log('学生详情页面已加载,学生ID:', studentId)
// 这里可以根据studentId调用API获取学生详细信息
loadStudentData(studentId)
})
/**
* 加载学生数据
* @param {string} studentId - 学生ID
*/
const loadStudentData = (studentId) => {
// 这里可以调用API获取实际数据
console.log('加载学生数据:', studentId)
}
</script>
<style scoped>
/* 自定义样式 */
.van-circle {
font-size: 12px;
font-weight: bold;
}
.van-tag {
margin-right: 0.5rem;
margin-bottom: 0.25rem;
}
.van-list {
min-height: 200px;
}
/* 标签页样式 */
.border-b-2 {
border-bottom-width: 2px;
}
</style>