hookehuyr

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

...@@ -33,4 +33,13 @@ export default [ ...@@ -33,4 +33,13 @@ export default [
33 requiresAuth: true 33 requiresAuth: true
34 }, 34 },
35 }, 35 },
36 + {
37 + path: '/teacher/student/:id',
38 + name: 'Student',
39 + component: () => import('../views/teacher/studentPage.vue'),
40 + meta: {
41 + title: '班级详情',
42 + requiresAuth: true
43 + },
44 + },
36 ] 45 ]
......
1 +<!--
2 + * @Author: hookehuyr hookehuyr@gmail.com
3 + * @Date: 2025-06-19 17:12:19
4 + * @LastEditors: hookehuyr hookehuyr@gmail.com
5 + * @LastEditTime: 2025-06-19 17:27:16
6 + * @FilePath: /mlaj/src/views/teacher/studentPage.vue
7 + * @Description: 学生详情页面
8 +-->
9 +<template>
10 + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen">
11 + <!-- 学生基本信息 -->
12 + <div class="bg-white p-4">
13 + <div class="flex items-start mb-4">
14 + <van-image round width="4rem" height="4rem"
15 + :src="studentInfo.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" fit="cover" class="mr-4" />
16 + <div class="flex-1">
17 + <div class="flex items-center mb-2">
18 + <h2 class="text-xl font-bold text-gray-800 mr-2">{{ studentInfo.name }}</h2>
19 + <van-icon v-if="studentInfo.gender === 'male'" name="friends-o" color="#3b82f6" size="16" />
20 + <van-icon v-else name="like-o" color="#ec4899" size="16" />
21 + </div>
22 + <div class="flex items-center mb-2">
23 + <van-icon name="chat-o" size="16" color="#10b981" class="mr-1" />
24 + <span class="text-sm text-gray-600 mr-4">{{ studentInfo.className }}</span>
25 + <van-icon name="phone-o" size="16" color="#10b981" class="mr-1" />
26 + <span class="text-sm text-gray-600">{{ formatPhone(studentInfo.phone) }}</span>
27 + </div>
28 + <!-- 标签 -->
29 + <div class="flex flex-wrap gap-2">
30 + <van-tag v-for="tag in studentInfo.tags" :key="tag" type="success" size="small">
31 + {{ tag }}
32 + </van-tag>
33 + </div>
34 + </div>
35 + </div>
36 + </div>
37 +
38 + <!-- 所学课程 -->
39 + <div class="bg-white mt-2 p-4">
40 + <div class="flex items-center mb-3">
41 + <van-icon name="bookmark-o" size="16" color="#10b981" class="mr-2" />
42 + <span class="text-sm font-medium text-gray-700">所学课程</span>
43 + </div>
44 + <div class="flex flex-wrap gap-2">
45 + <van-tag v-for="course in studentInfo.courses" :key="course" type="primary" size="small">
46 + {{ course }}
47 + </van-tag>
48 + </div>
49 + </div>
50 +
51 + <!-- 统计数据 -->
52 + <div class="mt-2">
53 + <van-row>
54 + <!-- 出勤率 -->
55 + <van-col span="8">
56 + <div class="bg-white p-4 text-center">
57 + <div class="relative w-16 h-16 mx-auto mb-2">
58 + <van-circle v-model:current-rate="studentStats.attendanceRate" :rate="studentStats.attendanceRate"
59 + :speed="100" :text="studentStats.attendanceRate + '%'" stroke-width="50" color="#10b981" size="64" />
60 + </div>
61 + <div class="text-sm text-gray-600">出勤率</div>
62 + </div>
63 + </van-col>
64 + <!-- 作业完成率 -->
65 + <van-col span="8">
66 + <div class="bg-white p-4 text-center">
67 + <div class="relative w-16 h-16 mx-auto mb-2">
68 + <van-circle v-model:current-rate="studentStats.homeworkRate" :rate="studentStats.homeworkRate"
69 + :speed="100" :text="studentStats.homeworkRate + '%'" stroke-width="50" color="#3b82f6" size="64" />
70 + </div>
71 + <div class="text-sm text-gray-600">作业完成率</div>
72 + </div>
73 + </van-col>
74 + <!-- 测验成绩 -->
75 + <van-col span="8">
76 + <div class="bg-white p-4 text-center">
77 + <div class="relative w-16 h-16 mx-auto mb-2">
78 + <van-circle v-model:current-rate="studentStats.testScore" :rate="studentStats.testScore" :speed="100"
79 + :text="studentStats.testScore + '%'" stroke-width="50" color="#f59e0b" size="64" />
80 + </div>
81 + <div class="text-sm text-gray-600">测验成绩</div>
82 + </div>
83 + </van-col>
84 + </van-row>
85 + </div>
86 +
87 + <!-- 功能按钮 -->
88 + <div class="bg-white mt-2 p-4">
89 + <!-- 状态筛选 -->
90 + <div class="flex items-center justify-end mb-4">
91 + <div @click="showStatusPopup = true" class="flex items-center text-sm text-gray-600 cursor-pointer">
92 + <span>{{ statusFilter }}</span>
93 + <van-icon name="arrow-down" size="14" class="ml-1" />
94 + </div>
95 + </div>
96 + <div class="flex items-center justify-between mb-3">
97 + <div @click="activeTab = 'statistics'"
98 + :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']">
99 + 打卡统计
100 + </div>
101 + <div @click="activeTab = 'homework'"
102 + :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']">
103 + 作业记录
104 + </div>
105 + <div @click="activeTab = 'evaluation'"
106 + :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']">
107 + 班主任点评
108 + </div>
109 + </div>
110 +
111 + <!-- 记录列表 -->
112 + <van-list v-if="activeTab === 'statistics'" v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
113 + <div v-for="record in filteredRecords" :key="record.id"
114 + class="flex items-center justify-between py-4 border-b border-gray-100 last:border-b-0">
115 + <div class="flex items-center flex-1">
116 + <div class="mr-4">
117 + <div style="display: flex; justify-content: center;">
118 + <van-icon name="calendar-o" size="16" color="#6b7280" class="mb-1" /> &nbsp;
119 + <span class="text-xs text-gray-500">{{ record.date }}</span>
120 + </div>
121 + <div style="display: flex; ">
122 + <van-icon name="clock-o" size="16" color="#6b7280" class="mb-1" /> &nbsp;
123 + <span class="text-xs text-gray-500">{{ record.time }}</span>
124 + </div>
125 + </div>
126 + </div>
127 + <div class="flex items-center">
128 + <span v-if="record.status === '正常'" class="text-green-600 text-sm mr-2">{{ record.status }}</span>
129 + <span v-else-if="record.status === '迟到'" class="text-orange-500 text-sm mr-2">{{ record.status }}</span>
130 + <span v-else class="text-red-500 text-sm mr-2">{{ record.status }}</span>
131 +
132 + <van-icon v-if="record.status === '正常'" name="success" color="#10b981" size="16" />
133 + <van-icon v-else-if="record.status === '迟到'" name="warning-o" color="#f59e0b" size="16" />
134 + <van-icon v-else name="close" color="#ef4444" size="16" />
135 + </div>
136 + </div>
137 + </van-list>
138 + </div>
139 +
140 + <!-- 状态筛选弹窗 -->
141 + <van-popup v-model:show="showStatusPopup" position="bottom" round>
142 + <div class="p-4">
143 + <div class="text-center text-lg font-bold mb-4">选择状态</div>
144 + <van-cell-group>
145 + <van-cell v-for="option in statusOptions" :key="option.value" :title="option.text" clickable
146 + @click="onStatusSelect(option)" :border="false"
147 + :class="{ 'text-green-600': statusFilter === option.value }">
148 + <template #right-icon>
149 + <van-icon v-if="statusFilter === option.value" name="success" color="#10b981" />
150 + </template>
151 + </van-cell>
152 + </van-cell-group>
153 + <div class="mt-4">
154 + <van-button block @click="showStatusPopup = false">取消</van-button>
155 + </div>
156 + </div>
157 + </van-popup>
158 + </div>
159 +</template>
160 +
161 +<script setup>
162 +import { ref, computed, onMounted } from 'vue'
163 +import { useRouter, useRoute } from 'vue-router'
164 +
165 +const router = useRouter()
166 +const route = useRoute()
167 +
168 +// 学生信息
169 +const studentInfo = ref({
170 + id: 1,
171 + name: '李华',
172 + gender: 'female',
173 + className: '高一(3)班',
174 + phone: '13812345678',
175 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg',
176 + tags: ['亲子学堂2025级'],
177 + courses: ['讲师训2025', '亲子学堂课程']
178 +})
179 +
180 +// 统计数据
181 +const studentStats = ref({
182 + attendanceRate: 92,
183 + homeworkRate: 88,
184 + testScore: 85
185 +})
186 +
187 +// 当前选中的标签页
188 +const activeTab = ref('statistics')
189 +
190 +// 状态筛选
191 +const statusFilter = ref('按状态')
192 +const showStatusPopup = ref(false)
193 +
194 +// 状态选项
195 +const statusOptions = ref([
196 + { text: '按状态', value: '按状态' },
197 + { text: '正常', value: '正常' },
198 + { text: '迟到', value: '迟到' },
199 + { text: '缺勤', value: '缺勤' }
200 +])
201 +
202 +// 记录列表
203 +const records = ref([
204 + {
205 + id: 1,
206 + date: '2023-11-20',
207 + time: '07:45',
208 + status: '正常'
209 + },
210 + {
211 + id: 2,
212 + date: '2023-11-19',
213 + time: '07:50',
214 + status: '正常'
215 + },
216 + {
217 + id: 3,
218 + date: '2023-11-18',
219 + time: '07:42',
220 + status: '正常'
221 + },
222 + {
223 + id: 4,
224 + date: '2023-11-17',
225 + time: '08:10',
226 + status: '迟到'
227 + },
228 + {
229 + id: 5,
230 + date: '2023-11-16',
231 + time: '07:48',
232 + status: '正常'
233 + },
234 + {
235 + id: 6,
236 + date: '2023-11-15',
237 + time: '00:00',
238 + status: '缺勤'
239 + }
240 +])
241 +
242 +// 列表加载状态
243 +const loading = ref(false)
244 +const finished = ref(false)
245 +
246 +/**
247 + * 过滤后的记录列表
248 + */
249 +const filteredRecords = computed(() => {
250 + let filtered = records.value
251 +
252 + // 按状态筛选
253 + if (statusFilter.value !== '按状态') {
254 + filtered = filtered.filter(record => record.status === statusFilter.value)
255 + }
256 +
257 + return filtered
258 +})
259 +
260 +/**
261 + * 格式化手机号
262 + * @param {string} phone - 手机号
263 + * @returns {string} 格式化后的手机号
264 + */
265 +const formatPhone = (phone) => {
266 + if (!phone) return ''
267 + return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3')
268 +}
269 +
270 +/**
271 + * 处理状态选择
272 + * @param {Object} option - 选中的状态选项
273 + */
274 +const onStatusSelect = (option) => {
275 + statusFilter.value = option.value
276 + showStatusPopup.value = false
277 +}
278 +
279 +/**
280 + * 加载更多数据
281 + */
282 +const onLoad = () => {
283 + setTimeout(() => {
284 + loading.value = false
285 + finished.value = true
286 + }, 1000)
287 +}
288 +
289 +/**
290 + * 组件挂载时初始化数据
291 + */
292 +onMounted(() => {
293 + // 从路由参数获取学生ID
294 + const studentId = route.params.id
295 + console.log('学生详情页面已加载,学生ID:', studentId)
296 +
297 + // 这里可以根据studentId调用API获取学生详细信息
298 + loadStudentData(studentId)
299 +})
300 +
301 +/**
302 + * 加载学生数据
303 + * @param {string} studentId - 学生ID
304 + */
305 +const loadStudentData = (studentId) => {
306 + // 这里可以调用API获取实际数据
307 + console.log('加载学生数据:', studentId)
308 +}
309 +</script>
310 +
311 +<style scoped>
312 +/* 自定义样式 */
313 +.van-circle {
314 + font-size: 12px;
315 + font-weight: bold;
316 +}
317 +
318 +.van-tag {
319 + margin-right: 0.5rem;
320 + margin-bottom: 0.25rem;
321 +}
322 +
323 +.van-list {
324 + min-height: 200px;
325 +}
326 +
327 +/* 标签页样式 */
328 +.border-b-2 {
329 + border-bottom-width: 2px;
330 +}
331 +</style>