hookehuyr

feat(教师端): 新增学员作业记录页面及跳转功能

- 添加学员作业记录页面路由及视图组件
- 在作业主页学生卡片添加点击跳转功能
- 实现作业记录列表展示、媒体播放及点评功能
- 更新README文档说明新增功能
...@@ -12,3 +12,7 @@ https://oa-dev.onwall.cn/f/mlaj ...@@ -12,3 +12,7 @@ https://oa-dev.onwall.cn/f/mlaj
12 - 统计:出勤率与任务完成率(参考 `myClassPage.vue` 统计样式,数据Mock)。 12 - 统计:出勤率与任务完成率(参考 `myClassPage.vue` 统计样式,数据Mock)。
13 - 日历:使用 `van-calendar` 单选模式,选择日期后展示当日学生完成情况。 13 - 日历:使用 `van-calendar` 单选模式,选择日期后展示当日学生完成情况。
14 - 学生完成情况:参考图片2样式,勾选代表已完成,未勾选代表未完成(数据Mock)。 14 - 学生完成情况:参考图片2样式,勾选代表已完成,未勾选代表未完成(数据Mock)。
15 + - 教师端新增学员作业记录页面:路径 `/teacher/student-record`,标题“学员作业记录”。
16 + - 在作业主页的学生列表点击卡片可跳转至该页面(当前版本为固定示例页面)。
17 + - 列表展示:作业帖子、图片/视频/音频、点赞与点评弹窗(与 `studentPage.vue` 的作业记录样式一致)。
18 + - 接口参数固定:`user_id=817017``group_id=816653`(后续可替换为动态参数)。
......
...@@ -60,4 +60,13 @@ export default [ ...@@ -60,4 +60,13 @@ export default [
60 requiresAuth: true 60 requiresAuth: true
61 }, 61 },
62 }, 62 },
63 + {
64 + path: '/teacher/student-record',
65 + name: 'StudentRecord',
66 + component: () => import('../views/teacher/studentRecordPage.vue'),
67 + meta: {
68 + title: '学员作业记录',
69 + requiresAuth: true
70 + },
71 + },
63 ] 72 ]
......
1 +<!--
2 + * @Date: 2025-11-19 22:05:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-11-19 22:12:24
5 + * @FilePath: /mlaj/src/views/teacher/studentRecordPage.vue
6 + * @Description: 学生作业记录页面(仅作业记录与点评功能),固定 user_id 与 group_id
7 +-->
8 +<template>
9 + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen">
10 + <!-- 作业记录列表 -->
11 + <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" class="space-y-4 p-4">
12 + <div class="post-card shadow-md" v-for="post in checkinDataList" :key="post.id">
13 + <div class="post-header">
14 + <van-row>
15 + <van-col span="4">
16 + <van-image round width="2.5rem" height="2.5rem"
17 + :src="optimizeCdn(post.user.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg')" fit="cover" />
18 + </van-col>
19 + <van-col span="17">
20 + <div class="user-info">
21 + <div class="username">{{ post.user.name }}</div>
22 + <div class="post-time">{{ post.user.time }}</div>
23 + </div>
24 + </van-col>
25 + <van-col span="3">
26 + </van-col>
27 + </van-row>
28 + </div>
29 + <div class="post-content">
30 + <div class="post-text">{{ post.content }}</div>
31 + <div class="post-media">
32 + <div v-if="post.images.length" class="post-images">
33 + <van-image width="30%" fit="cover" v-for="(image, index) in post.images" :key="index" :src="optimizeCdn(image)"
34 + radius="5" @click="openImagePreview(index, post)" />
35 + </div>
36 + <van-image-preview v-if="currentPost" v-model:show="showImagePreview" :images="currentPost.images"
37 + :start-position="startPosition" :show-index="true" @change="onChange" />
38 + <div v-for="(v, idx) in post.videoList" :key="idx">
39 + <!-- 视频封面和播放按钮 -->
40 + <div v-if="v.video && !v.isPlaying" class="relative w-full rounded-lg overflow-hidden" style="aspect-ratio: 16/9; margin-bottom: 1rem;">
41 + <img :src="optimizeCdn(v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png')" :alt="post.content" class="w-full h-full object-cover" />
42 + <div class="absolute inset-0 flex items-center justify-center cursor-pointer bg-black/20" @click="startPlay(v)">
43 + <div class="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors">
44 + <van-icon name="play-circle-o" class="text-white" size="40" />
45 + </div>
46 + </div>
47 + </div>
48 + <!-- 视频播放器 -->
49 + <VideoPlayer v-if="v.video && v.isPlaying" :video-url="v.video" class="post-video rounded-lg overflow-hidden" :ref="el => {
50 + if (el) {
51 + if (!videoPlayers?.includes(el)) {
52 + videoPlayers?.push(el);
53 + }
54 + }
55 + }" @onPlay="handleVideoPlay(player, post)" @onPause="handleVideoPause(post)" />
56 + </div>
57 + <AudioPlayer v-if="post.audio.length" :songs="post.audio" class="post-audio" :id="post.id" :ref="el => {
58 + if (el) {
59 + if (!audioPlayers?.includes(el)) {
60 + audioPlayers?.push(el);
61 + }
62 + }
63 + }" @play="(player) => handleAudioPlay(player, post)" />
64 + </div>
65 + </div>
66 + <div class="post-footer flex items-center justify-between">
67 + <!-- 左侧:点赞 -->
68 + <div class="flex items-center">
69 + <van-icon @click="handLike(post)" name="good-job" class="like-icon" :color="post.is_liked ? 'red' : ''" />
70 + <span class="like-count ml-1">{{ post.likes }}</span>
71 + </div>
72 +
73 + <!-- 右侧:点评 -->
74 + <div class="flex items-center cursor-pointer" @click="openCommentPopup(post)">
75 + <van-icon name="comment-o" :color="post?.is_feedback ? '#10b981' : '#999'" size="19" class="mr-1" style="margin-top: 0.2rem;" />
76 + <span class="text-sm" :class="post?.is_feedback ? 'text-green-600' : 'text-gray-500'">
77 + {{ post?.is_feedback ? '已点评' : '待点评' }}
78 + </span>
79 + </div>
80 + </div>
81 + </div>
82 + </van-list>
83 + <van-empty v-show="!checkinDataList.length" description="暂无数据" />
84 +
85 + <!-- 点评弹窗 -->
86 + <van-popup v-model:show="showCommentPopup" position="bottom" round class="comment-popup">
87 + <div class="p-6 w-100">
88 + <div class="text-center text-lg font-bold mb-4">作业点评</div>
89 +
90 + <!-- 评分 -->
91 + <div class="mb-4">
92 + <div class="text-sm text-gray-600 mb-2">评分</div>
93 + <van-rate v-model="commentForm.score" :size="24" color="#ffd21e" void-color="#eee" :readonly="currentCommentPost?.is_feedback" />
94 + </div>
95 +
96 + <!-- 点评内容 -->
97 + <div class="mb-6">
98 + <div class="text-sm text-gray-600 mb-2">点评内容</div>
99 + <van-field v-model="commentForm.note" type="textarea" placeholder="请输入点评内容..." rows="4" maxlength="200" show-word-limit :border="false" class="bg-gray-50 rounded-lg" :readonly="currentCommentPost?.is_feedback" />
100 + </div>
101 +
102 + <!-- 操作按钮 -->
103 + <div v-if="!currentCommentPost?.is_feedback" class="flex gap-3">
104 + <van-button block type="default" @click="closeCommentPopup" class="flex-1">取消</van-button>
105 + <van-button block type="primary" @click="submitComment" class="flex-1">提交</van-button>
106 + </div>
107 + <div v-else class="flex gap-3">
108 + <van-button block type="default" @click="closeCommentPopup" class="flex-1">关闭</van-button>
109 + </div>
110 + </div>
111 + </van-popup>
112 + </div>
113 + <van-back-top right="5vw" bottom="25vh" offset="600" />
114 +</template>
115 +
116 +<script setup>
117 +import { ref, onMounted, onBeforeUnmount } from 'vue'
118 +import { showSuccessToast, showFailToast, showLoadingToast } from 'vant'
119 +import VideoPlayer from '@/components/ui/VideoPlayer.vue'
120 +import AudioPlayer from '@/components/ui/AudioPlayer.vue'
121 +import { getStudentUploadListAPI, addCheckinFeedbackAPI } from '@/api/teacher'
122 +import { likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI } from '@/api/checkin'
123 +
124 +// 固定的用户与班级ID
125 +const fixedUserId = 817017
126 +const fixedGroupId = 816653
127 +
128 +// 列表相关状态
129 +const checkinDataList = ref([])
130 +const loading = ref(false)
131 +const finished = ref(false)
132 +const limit = ref(10)
133 +const page = ref(0)
134 +
135 +// 图片预览相关
136 +const showImagePreview = ref(false)
137 +const startPosition = ref(0)
138 +const currentPost = ref(null)
139 +
140 +// 播放器引用
141 +const videoPlayers = ref([])
142 +const audioPlayers = ref([])
143 +
144 +// 点评相关状态
145 +const showCommentPopup = ref(false)
146 +const currentCommentPost = ref(null)
147 +const commentForm = ref({
148 + checkin_id: '',
149 + score: 0,
150 + note: ''
151 +})
152 +
153 +/**
154 + * CDN图片优化:为 cdn.ipadbiz.cn 域名添加压缩参数
155 + * @param {string} url - 图片地址
156 + * @returns {string} 处理后的图片地址
157 + */
158 +function optimizeCdn(url) {
159 + if (!url) return url
160 + try {
161 + const u = String(url)
162 + if (u.includes('cdn.ipadbiz.cn')) {
163 + const hasQuery = u.includes('?')
164 + const param = 'imageMogr2/thumbnail/200x/strip/quality/70'
165 + return hasQuery ? `${u}&${param}` : `${u}?${param}`
166 + }
167 + return u
168 + } catch (e) {
169 + return url
170 + }
171 +}
172 +
173 +/**
174 + * 打开图片预览
175 + * @param {number} index - 起始索引
176 + * @param {Object} post - 当前帖子
177 + */
178 +function openImagePreview(index, post) {
179 + currentPost.value = post
180 + startPosition.value = index
181 + showImagePreview.value = true
182 +}
183 +
184 +/**
185 + * 图片切换事件
186 + * @param {number} index - 当前索引
187 + */
188 +function onChange(index) {
189 + startPosition.value = index
190 +}
191 +
192 +/**
193 + * 点赞/取消点赞
194 + * @param {Object} post - 帖子对象
195 + * @returns {Promise<void>}
196 + */
197 +async function handLike(post) {
198 + if (!post.is_liked) {
199 + const { code } = await likeUploadTaskInfoAPI({ checkin_id: post.id })
200 + if (code) {
201 + showSuccessToast('点赞成功')
202 + post.likes++
203 + post.is_liked = true
204 + }
205 + } else {
206 + const { code } = await dislikeUploadTaskInfoAPI({ checkin_id: post.id })
207 + if (code) {
208 + showSuccessToast('取消点赞成功')
209 + post.likes--
210 + post.is_liked = false
211 + }
212 + }
213 +}
214 +
215 +/**
216 + * 打开点评弹窗
217 + * @param {Object} post - 帖子对象
218 + */
219 +function openCommentPopup(post) {
220 + currentCommentPost.value = post
221 + commentForm.value.checkin_id = post.id
222 + if (post.feedback_id) {
223 + commentForm.value.score = post.feedback_score || 0
224 + commentForm.value.note = post.feedback || ''
225 + } else {
226 + commentForm.value.score = 0
227 + commentForm.value.note = ''
228 + }
229 + showCommentPopup.value = true
230 +}
231 +
232 +/**
233 + * 关闭点评弹窗
234 + */
235 +function closeCommentPopup() {
236 + if (currentCommentPost.value && currentCommentPost.value.is_feedback) {
237 + showCommentPopup.value = false
238 + return
239 + }
240 + showCommentPopup.value = false
241 + currentCommentPost.value = null
242 + commentForm.value.score = 0
243 + commentForm.value.note = ''
244 +}
245 +
246 +/**
247 + * 提交点评
248 + * @returns {Promise<void>}
249 + */
250 +async function submitComment() {
251 + if (!commentForm.value.note.trim()) {
252 + showFailToast('请输入点评内容')
253 + return
254 + }
255 + if (commentForm.value.score === 0) {
256 + showFailToast('请选择评分')
257 + return
258 + }
259 + try {
260 + showLoadingToast('提交中...')
261 + const { code, data } = await addCheckinFeedbackAPI(commentForm.value)
262 + if (code) {
263 + commentForm.value.feedback_id = data.id
264 + currentCommentPost.value.is_feedback = true
265 + checkinDataList.value.forEach(item => {
266 + if (item.id === currentCommentPost.value.id) {
267 + item.feedback_id = commentForm.value.feedback_id
268 + item.feedback_score = commentForm.value.score
269 + item.feedback = commentForm.value.note
270 + }
271 + })
272 + showSuccessToast('点评提交成功')
273 + closeCommentPopup()
274 + }
275 + } catch (err) {
276 + showFailToast('提交失败,请重试')
277 + }
278 +}
279 +
280 +/**
281 + * 开始播放视频
282 + * @param {Object} post - 视频条目(含video、isPlaying)
283 + */
284 +function startPlay(post) {
285 + if (checkinDataList.value) {
286 + checkinDataList.value.forEach(p => {
287 + p.videoList.forEach(v => {
288 + if (v.id !== post.id) {
289 + v.isPlaying = false
290 + }
291 + })
292 + })
293 + }
294 + post.isPlaying = true
295 +}
296 +
297 +/**
298 + * 视频播放事件:同时停止音频
299 + */
300 +function handleVideoPlay() {
301 + stopAllAudio()
302 +}
303 +
304 +/**
305 + * 视频暂停事件:保持播放器可见
306 + */
307 +function handleVideoPause() {
308 + // 暂不处理,保持播放器状态
309 +}
310 +
311 +/**
312 + * 停止其他视频与音频
313 + * @param {Object} currentPlayer - 当前播放器
314 + * @param {Object} currentPost - 当前帖子
315 + */
316 +function stopOtherVideos(currentPlayer, currentPost) {
317 + if (videoPlayers.value) {
318 + videoPlayers.value.forEach(player => {
319 + if (player !== currentPlayer && player.pause) {
320 + player.pause()
321 + }
322 + })
323 + }
324 + checkinDataList.value.forEach(p => {
325 + p.videoList.forEach(v => {
326 + if (v.id !== currentPost.id) {
327 + v.isPlaying = false
328 + }
329 + })
330 + })
331 +}
332 +
333 +/**
334 + * 音频播放事件:停止其他音频
335 + * @param {Object} player - 音频播放器
336 + * @param {Object} post - 当前帖子
337 + */
338 +function handleAudioPlay(player, post) {
339 + stopOtherAudio(player, post)
340 +}
341 +
342 +/**
343 + * 停止其他音频
344 + * @param {Object} currentPlayer - 当前播放器
345 + * @param {Object} currentPost - 当前帖子
346 + */
347 +function stopOtherAudio(currentPlayer, currentPost) {
348 + if (audioPlayers.value) {
349 + audioPlayers.value.forEach(player => {
350 + if (player.id !== currentPost.id && player.pause) {
351 + player.pause()
352 + }
353 + })
354 + }
355 + checkinDataList.value.forEach(post => {
356 + if (post.id !== currentPost.id) {
357 + post.isPlaying = false
358 + }
359 + })
360 + stopAllVideos()
361 +}
362 +
363 +/**
364 + * 停止所有音频
365 + */
366 +function stopAllAudio() {
367 + if (!audioPlayers.value) return
368 + audioPlayers.value?.forEach(player => {
369 + if (typeof player.pause === 'function') {
370 + player?.pause()
371 + }
372 + })
373 + checkinDataList.value.forEach(post => {
374 + if (post.audio.length) {
375 + post.isPlaying = false
376 + }
377 + })
378 +}
379 +
380 +/**
381 + * 停止所有视频
382 + */
383 +function stopAllVideos() {
384 + if (!videoPlayers.value) return
385 + checkinDataList.value.forEach(p => {
386 + p.videoList.forEach(v => {
387 + v.isPlaying = false
388 + })
389 + })
390 +}
391 +
392 +/**
393 + * 拉取作业记录分页数据
394 + * @returns {Promise<void>}
395 + */
396 +async function onLoad() {
397 + const nextPage = page.value
398 + const res = await getStudentUploadListAPI({
399 + limit: limit.value,
400 + page: nextPage,
401 + user_id: fixedUserId,
402 + group_id: fixedGroupId,
403 + })
404 + if (res.code) {
405 + checkinDataList.value = [...checkinDataList.value, ...formatData(res.data)]
406 + finished.value = res.data.length < limit.value
407 + page.value = nextPage + 1
408 + }
409 + loading.value = false
410 +}
411 +
412 +/**
413 + * 规范化接口数据为页面所需结构
414 + * @param {Array} data - 原始接口数据
415 + * @returns {Array} 规范化后的列表
416 + */
417 +function formatData(data) {
418 + let formattedData = []
419 + formattedData = data?.map(item => {
420 + let images = []
421 + let audio = []
422 + let videoList = []
423 + if (item.file_type === 'image') {
424 + images = item.files.map(file => file.value)
425 + } else if (item.file_type === 'video') {
426 + videoList = item.files.map(file => ({
427 + id: file.meta_id,
428 + video: file.value,
429 + videoCover: file.cover,
430 + isPlaying: false,
431 + }))
432 + } else if (item.file_type === 'audio') {
433 + audio = item.files.map(file => ({
434 + title: file.name ? file.name : '打卡音频',
435 + artist: file.artist ? file.artist : '',
436 + url: file.value,
437 + cover: file.cover ? file.cover : '',
438 + }))
439 + }
440 + return {
441 + id: item.id,
442 + task_id: item.task_id,
443 + user: {
444 + name: item.username,
445 + avatar: item.avatar,
446 + time: item.created_time_desc,
447 + },
448 + content: item.note,
449 + images,
450 + videoList,
451 + audio,
452 + isPlaying: false,
453 + likes: item.like_count,
454 + is_liked: item.is_like,
455 + is_my: item.is_my,
456 + file_type: item.file_type,
457 + is_feedback: item.feedback_id || false,
458 + feedback: item.feedback || '',
459 + feedback_id: item.feedback_id || '',
460 + feedback_score: item.feedback_score || 0,
461 + comment: item.comment || null,
462 + }
463 + })
464 + return formattedData
465 +}
466 +
467 +// 生命周期:卸载时清理播放器引用
468 +onMounted(() => {})
469 +onBeforeUnmount(() => {
470 + if (videoPlayers.value) {
471 + videoPlayers.value.forEach(player => {
472 + if (player && typeof player?.pause === 'function') {
473 + player?.pause()
474 + }
475 + })
476 + }
477 + stopAllAudio()
478 + if (videoPlayers.value) videoPlayers.value = []
479 + if (audioPlayers.value) audioPlayers.value = []
480 +})
481 +</script>
482 +
483 +<style lang="less">
484 +.post-card {
485 + padding: 1rem;
486 + background-color: #FFF;
487 + border-radius: 5px;
488 +
489 + .post-header {
490 + margin-bottom: 1rem;
491 + }
492 +
493 + .user-info {
494 + margin-left: 0.5rem;
495 +
496 + .username {
497 + font-weight: 500;
498 + }
499 +
500 + .post-time {
501 + color: gray;
502 + font-size: 0.8rem;
503 + }
504 + }
505 +
506 + .post-content {
507 + .post-text {
508 + color: #666;
509 + margin-bottom: 1rem;
510 + white-space: pre-wrap;
511 + word-wrap: break-word;
512 + }
513 +
514 + .post-media {
515 + .post-images {
516 + display: flex;
517 + flex-wrap: wrap;
518 + gap: 0.5rem;
519 + }
520 +
521 + .post-video {
522 + margin: 1rem 0;
523 + width: 100%;
524 + border-radius: 8px;
525 + overflow: hidden;
526 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
527 + }
528 +
529 + .post-audio {
530 + margin: 1rem 0;
531 + }
532 + }
533 + }
534 +
535 + .post-footer {
536 + margin-top: 1rem;
537 + color: #666;
538 +
539 + .like-icon {
540 + margin-right: 0.25rem;
541 + }
542 +
543 + .like-count {
544 + font-size: 0.9rem;
545 + }
546 + }
547 +}
548 +
549 +.comment-popup {
550 + .van-popup {
551 + max-width: 90vw;
552 + }
553 +
554 + .van-rate {
555 + display: flex;
556 + justify-content: center;
557 + }
558 +
559 + .van-field {
560 + padding: 12px;
561 + border-radius: 8px;
562 + }
563 +
564 + .van-button {
565 + height: 44px;
566 + border-radius: 8px;
567 + }
568 +}
569 +</style>
1 <!-- 1 <!--
2 * @Date: 2025-11-19 21:00:00 2 * @Date: 2025-11-19 21:00:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-11-19 21:43:15 4 + * @LastEditTime: 2025-11-19 22:10:07
5 * @FilePath: /mlaj/src/views/teacher/taskHomePage.vue 5 * @FilePath: /mlaj/src/views/teacher/taskHomePage.vue
6 * @Description: 教师端作业主页(头部介绍、统计、日历与学生完成情况;数据Mock) 6 * @Description: 教师端作业主页(头部介绍、统计、日历与学生完成情况;数据Mock)
7 --> 7 -->
...@@ -66,7 +66,8 @@ ...@@ -66,7 +66,8 @@
66 <div class="grid grid-cols-5 gap-3 StudentsGrid"> 66 <div class="grid grid-cols-5 gap-3 StudentsGrid">
67 <div v-for="(stu, idx) in students_status" :key="stu.id" 67 <div v-for="(stu, idx) in students_status" :key="stu.id"
68 class="studentItem relative rounded-md h-16 flex flex-col items-center justify-center text-center border overflow-hidden" 68 class="studentItem relative rounded-md h-16 flex flex-col items-center justify-center text-center border overflow-hidden"
69 - :class="stu.completed ? 'bg-white border-green-500 text-green-600' : 'bg-gray-100 border-gray-300 text-gray-500'"> 69 + :class="stu.completed ? 'bg-white border-green-500 text-green-600' : 'bg-gray-100 border-gray-300 text-gray-500'"
70 + @click="go_student_record(stu)">
70 <div class="text-sm font-semibold">{{ idx + 1 }}</div> 71 <div class="text-sm font-semibold">{{ idx + 1 }}</div>
71 <div class="text-sm mt-1">{{ stu.name }}</div> 72 <div class="text-sm mt-1">{{ stu.name }}</div>
72 <img v-if="stu.completed" :src="checkCorner" alt="checked" class="cornerIcon" /> 73 <img v-if="stu.completed" :src="checkCorner" alt="checked" class="cornerIcon" />
...@@ -78,12 +79,13 @@ ...@@ -78,12 +79,13 @@
78 79
79 <script setup> 80 <script setup>
80 import { ref, computed } from 'vue' 81 import { ref, computed } from 'vue'
81 -import { useRoute } from 'vue-router' 82 +import { useRoute, useRouter } from 'vue-router'
82 import { useTitle } from '@vueuse/core' 83 import { useTitle } from '@vueuse/core'
83 import TaskCalendar from '@/components/ui/TaskCalendar.vue' 84 import TaskCalendar from '@/components/ui/TaskCalendar.vue'
84 import checkCorner from '@/assets/check_corner.svg' 85 import checkCorner from '@/assets/check_corner.svg'
85 86
86 const $route = useRoute() 87 const $route = useRoute()
88 +const $router = useRouter()
87 useTitle('作业主页') 89 useTitle('作业主页')
88 90
89 // 91 //
...@@ -193,6 +195,16 @@ const completed_count = computed(() => students_status.value.filter(s => s.compl ...@@ -193,6 +195,16 @@ const completed_count = computed(() => students_status.value.filter(s => s.compl
193 * @returns {string} 文本 195 * @returns {string} 文本
194 */ 196 */
195 const current_date_text = computed(() => selected_date.value) 197 const current_date_text = computed(() => selected_date.value)
198 +
199 +/**
200 + * 跳转至学员作业记录页面(固定ID的示例页面)
201 + * @param {{id:string,name:string,completed:boolean}} stu - 学员对象
202 + * @returns {void}
203 + */
204 +function go_student_record(stu) {
205 + // 跳转到固定ID的作业记录页面,当前版本不使用传入ID
206 + $router.push({ name: 'StudentRecord' })
207 +}
196 </script> 208 </script>
197 209
198 <style lang="less" scoped> 210 <style lang="less" scoped>
......