hookehuyr

feat(teacher): 添加作业筛选组件并更新相关API参数

在教师端学生详情页添加作业筛选组件,支持按大作业和小作业筛选数据
更新获取作业记录、统计和点评的API调用,添加task_id和subtask_id参数
当筛选条件变化时重置并重新加载对应数据
1 /* 1 /*
2 * @Date: 2025-06-23 11:46:21 2 * @Date: 2025-06-23 11:46:21
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-12 14:55:03 4 + * @LastEditTime: 2025-12-15 12:30:05
5 * @FilePath: /mlaj/src/api/teacher.js 5 * @FilePath: /mlaj/src/api/teacher.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -126,6 +126,8 @@ export const getStudentCheckinListAPI = (params) => fn(fetch.get(Api.STUDENT_CHE ...@@ -126,6 +126,8 @@ export const getStudentCheckinListAPI = (params) => fn(fetch.get(Api.STUDENT_CHE
126 * 获取学员作业记录 126 * 获取学员作业记录
127 * @param {*} user_id 学员ID 127 * @param {*} user_id 学员ID
128 * @param {*} group_id 课程ID 128 * @param {*} group_id 课程ID
129 + * @param {*} task_id 大作业ID
130 + * @param {*} subtask_id 小作业ID
129 * @param {*} limit 条数 131 * @param {*} limit 条数
130 * @param {*} page 页码 132 * @param {*} page 页码
131 * @returns {Object} data 133 * @returns {Object} data
......
...@@ -38,6 +38,7 @@ declare module 'vue' { ...@@ -38,6 +38,7 @@ declare module 'vue' {
38 SharePoster: typeof import('./components/ui/SharePoster.vue')['default'] 38 SharePoster: typeof import('./components/ui/SharePoster.vue')['default']
39 SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] 39 SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default']
40 TaskCalendar: typeof import('./components/ui/TaskCalendar.vue')['default'] 40 TaskCalendar: typeof import('./components/ui/TaskCalendar.vue')['default']
41 + TaskFilter: typeof import('./components/teacher/TaskFilter.vue')['default']
41 TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default'] 42 TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default']
42 UploadVideoPopup: typeof import('./components/ui/UploadVideoPopup.vue')['default'] 43 UploadVideoPopup: typeof import('./components/ui/UploadVideoPopup.vue')['default']
43 UserAgreement: typeof import('./components/ui/UserAgreement.vue')['default'] 44 UserAgreement: typeof import('./components/ui/UserAgreement.vue')['default']
......
1 +<template>
2 + <div class="task-filter bg-white">
3 + <div class="flex gap-2">
4 + <!-- 大作业选择 -->
5 + <div class="flex-1 min-w-0" @click="showTaskPicker = true">
6 + <div class="flex items-center justify-between bg-gray-50 rounded px-3 py-2 border border-gray-100">
7 + <span class="text-sm text-gray-700 truncate mr-1">{{ selectedTaskName || '全部作业' }}</span>
8 + <van-icon name="arrow-down" color="#9ca3af" size="12" />
9 + </div>
10 + </div>
11 +
12 + <!-- 小作业选择 -->
13 + <div class="flex-1 min-w-0" @click="handleSubtaskClick">
14 + <div class="flex items-center justify-between bg-gray-50 rounded px-3 py-2 border border-gray-100"
15 + :class="{ 'opacity-50': !selectedTaskId }">
16 + <span class="text-sm text-gray-700 truncate mr-1">{{ selectedSubtaskName || '全部小作业' }}</span>
17 + <van-icon name="arrow-down" color="#9ca3af" size="12" />
18 + </div>
19 + </div>
20 + </div>
21 +
22 + <!-- 大作业弹窗 -->
23 + <van-popup v-model:show="showTaskPicker" position="bottom" round>
24 + <van-picker
25 + :columns="taskColumns"
26 + @confirm="onConfirmTask"
27 + @cancel="showTaskPicker = false"
28 + show-toolbar
29 + title="选择作业"
30 + />
31 + </van-popup>
32 +
33 + <!-- 小作业弹窗 -->
34 + <van-popup v-model:show="showSubtaskPicker" position="bottom" round>
35 + <van-picker
36 + :columns="subtaskColumns"
37 + @confirm="onConfirmSubtask"
38 + @cancel="showSubtaskPicker = false"
39 + show-toolbar
40 + title="选择小作业"
41 + />
42 + </van-popup>
43 + </div>
44 +</template>
45 +
46 +<script setup>
47 +import { ref, computed, watch, onMounted } from 'vue'
48 +import { getTeacherTaskListAPI, getTeacherTaskDetailAPI } from '@/api/teacher'
49 +import { showToast } from 'vant'
50 +
51 +const props = defineProps({
52 + groupId: {
53 + type: [String, Number],
54 + default: ''
55 + }
56 +})
57 +
58 +const emit = defineEmits(['change'])
59 +
60 +// 状态
61 +const showTaskPicker = ref(false)
62 +const showSubtaskPicker = ref(false)
63 +const taskList = ref([])
64 +const subtaskList = ref([])
65 +const selectedTaskId = ref('')
66 +const selectedSubtaskId = ref('')
67 +
68 +// 计算属性 - 选项列表
69 +const taskColumns = computed(() => {
70 + const list = taskList.value.map(item => ({ text: item.title, value: item.id }))
71 + return [{ text: '全部作业', value: '' }, ...list]
72 +})
73 +
74 +const subtaskColumns = computed(() => {
75 + const list = subtaskList.value.map(item => ({ text: item.title, value: item.id }))
76 + return [{ text: '全部小作业', value: '' }, ...list]
77 +})
78 +
79 +// 计算属性 - 显示名称
80 +const selectedTaskName = computed(() => {
81 + if (!selectedTaskId.value) return '全部作业'
82 + const found = taskList.value.find(item => item.id === selectedTaskId.value)
83 + return found ? found.title : ''
84 +})
85 +
86 +const selectedSubtaskName = computed(() => {
87 + if (!selectedSubtaskId.value) return '全部小作业'
88 + const found = subtaskList.value.find(item => item.id === selectedSubtaskId.value)
89 + return found ? found.title : ''
90 +})
91 +
92 +// 获取大作业列表
93 +const fetchTaskList = async () => {
94 + if (!props.groupId) {
95 + taskList.value = []
96 + return
97 + }
98 +
99 + try {
100 + const res = await getTeacherTaskListAPI({
101 + group_id: props.groupId,
102 + limit: 100,
103 + page: 0
104 + })
105 +
106 + if (res.code === 1) {
107 + taskList.value = res.data || []
108 + }
109 + } catch (error) {
110 + console.error('获取作业列表失败:', error)
111 + }
112 +}
113 +
114 +// 监听 groupId 变化
115 +watch(() => props.groupId, (newVal) => {
116 + fetchTaskList()
117 +}, { immediate: true })
118 +
119 +// 获取大作业详情(含小作业)
120 +const fetchTaskDetail = async (taskId) => {
121 + if (!taskId) {
122 + subtaskList.value = []
123 + return
124 + }
125 +
126 + try {
127 + const res = await getTeacherTaskDetailAPI({ id: taskId })
128 + if (res.code === 1) {
129 + subtaskList.value = res.data.subtask_list || []
130 + }
131 + } catch (error) {
132 + console.error('获取作业详情失败:', error)
133 + }
134 +}
135 +
136 +// 事件处理
137 +const onConfirmTask = async ({ selectedOptions }) => {
138 + const option = selectedOptions[0]
139 + if (selectedTaskId.value !== option.value) {
140 + selectedTaskId.value = option.value
141 + // 重置小作业
142 + selectedSubtaskId.value = ''
143 + subtaskList.value = []
144 +
145 + if (option.value) {
146 + await fetchTaskDetail(option.value)
147 + }
148 +
149 + emitChange()
150 + }
151 + showTaskPicker.value = false
152 +}
153 +
154 +const onConfirmSubtask = ({ selectedOptions }) => {
155 + const option = selectedOptions[0]
156 + selectedSubtaskId.value = option.value
157 + emitChange()
158 + showSubtaskPicker.value = false
159 +}
160 +
161 +const handleSubtaskClick = () => {
162 + if (!selectedTaskId.value) {
163 + showToast('请先选择作业')
164 + return
165 + }
166 + showSubtaskPicker.value = true
167 +}
168 +
169 +const emitChange = () => {
170 + emit('change', {
171 + task_id: selectedTaskId.value,
172 + subtask_id: selectedSubtaskId.value
173 + })
174 +}
175 +</script>
176 +
177 +<style lang="less" scoped>
178 +.task-filter {
179 + // 可以在这里添加额外的样式
180 +}
181 +</style>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 * @Author: hookehuyr hookehuyr@gmail.com 2 * @Author: hookehuyr hookehuyr@gmail.com
3 * @Date: 2025-06-19 17:12:19 3 * @Date: 2025-06-19 17:12:19
4 * @LastEditors: hookehuyr hookehuyr@gmail.com 4 * @LastEditors: hookehuyr hookehuyr@gmail.com
5 - * @LastEditTime: 2025-12-12 18:02:06 5 + * @LastEditTime: 2025-12-15 12:54:05
6 * @FilePath: /mlaj/src/views/teacher/studentPage.vue 6 * @FilePath: /mlaj/src/views/teacher/studentPage.vue
7 * @Description: 学生详情页面 7 * @Description: 学生详情页面
8 --> 8 -->
...@@ -95,6 +95,13 @@ ...@@ -95,6 +95,13 @@
95 <!-- 使用van-sticky包裹van-tabs实现粘性布局 --> 95 <!-- 使用van-sticky包裹van-tabs实现粘性布局 -->
96 <div class="bg-white" style="margin: 1rem;"> 96 <div class="bg-white" style="margin: 1rem;">
97 <van-sticky :offset-top="0"> 97 <van-sticky :offset-top="0">
98 + <div class="bg-white px-4 py-2">
99 + <TaskFilter
100 + :key="currentGroupId"
101 + :group-id="currentGroupId"
102 + @change="handleTaskFilterChange"
103 + />
104 + </div>
98 <van-tabs v-model:active="activeTab" color="#4caf50" animated swipeable @change="handleTabChange"> 105 <van-tabs v-model:active="activeTab" color="#4caf50" animated swipeable @change="handleTabChange">
99 <van-tab title="作业记录" name="homework"></van-tab> 106 <van-tab title="作业记录" name="homework"></van-tab>
100 <van-tab title="班主任点评" name="evaluation"></van-tab> 107 <van-tab title="班主任点评" name="evaluation"></van-tab>
...@@ -331,6 +338,7 @@ import dayjs from 'dayjs'; ...@@ -331,6 +338,7 @@ import dayjs from 'dayjs';
331 import { delUploadTaskInfoAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI } from "@/api/checkin"; 338 import { delUploadTaskInfoAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI } from "@/api/checkin";
332 import { getStudentDetailAPI, getStudentCheckinListAPI, getStudentUploadListAPI, getCheckinFeedbackListAPI, addCheckinFeedbackAPI, delCheckinFeedbackAPI, getStudentStatAPI } from "@/api/teacher"; 339 import { getStudentDetailAPI, getStudentCheckinListAPI, getStudentUploadListAPI, getCheckinFeedbackListAPI, addCheckinFeedbackAPI, delCheckinFeedbackAPI, getStudentStatAPI } from "@/api/teacher";
333 340
341 +import TaskFilter from '@/components/teacher/TaskFilter.vue'
334 342
335 const router = useRouter() 343 const router = useRouter()
336 const route = useRoute() 344 const route = useRoute()
...@@ -345,6 +353,41 @@ const studentInfo = ref({}) ...@@ -345,6 +353,41 @@ const studentInfo = ref({})
345 353
346 // 选中的课程列表(默认选中第一个课程) 354 // 选中的课程列表(默认选中第一个课程)
347 const selectedCourses = ref([]) 355 const selectedCourses = ref([])
356 +const currentGroupId = computed(() => selectedCourses.value.length ? selectedCourses.value[0]['id'] : '')
357 +
358 +// 作业筛选
359 +const filterTaskId = ref('')
360 +const filterSubtaskId = ref('')
361 +
362 +const handleTaskFilterChange = ({ task_id, subtask_id }) => {
363 + filterTaskId.value = task_id
364 + filterSubtaskId.value = subtask_id
365 +
366 + // 重置所有列表数据状态
367 + page.value = 0;
368 + checkinDataList.value = [];
369 + finished.value = false;
370 +
371 + recordPage.value = 0;
372 + records.value = [];
373 + recordFinished.value = false;
374 +
375 + evaluationPage.value = 0;
376 + evaluationList.value = [];
377 + evaluationFinished.value = false;
378 +
379 + // 根据当前 activeTab 加载对应数据
380 + if (activeTab.value === 'homework') {
381 + loading.value = true;
382 + onLoad();
383 + } else if (activeTab.value === 'statistics') {
384 + recordLoading.value = true;
385 + onRecordLoad();
386 + } else if (activeTab.value === 'evaluation') {
387 + evaluationLoading.value = true;
388 + onEvaluationLoad();
389 + }
390 +}
348 391
349 // 当前选中的标签页 392 // 当前选中的标签页
350 const activeTab = ref('homework') 393 const activeTab = ref('homework')
...@@ -455,6 +498,10 @@ const toggleCourseSelection = (course) => { ...@@ -455,6 +498,10 @@ const toggleCourseSelection = (course) => {
455 // 可以在这里添加其他业务逻辑,比如筛选相关数据 498 // 可以在这里添加其他业务逻辑,比如筛选相关数据
456 console.log('当前选中的课程:', selectedCourses.value) 499 console.log('当前选中的课程:', selectedCourses.value)
457 500
501 + // 重置作业筛选
502 + filterTaskId.value = ''
503 + filterSubtaskId.value = ''
504 +
458 resetAndReload() 505 resetAndReload()
459 resetAndReloadRecords() 506 resetAndReloadRecords()
460 resetAndReloadEvaluations() 507 resetAndReloadEvaluations()
...@@ -543,6 +590,8 @@ const onRecordLoad = async () => { ...@@ -543,6 +590,8 @@ const onRecordLoad = async () => {
543 page: nextPage, 590 page: nextPage,
544 user_id: route.params.id, 591 user_id: route.params.id,
545 group_id: selectedCourses.value.length ? selectedCourses.value[0]['id'] : '', 592 group_id: selectedCourses.value.length ? selectedCourses.value[0]['id'] : '',
593 + task_id: filterTaskId.value,
594 + subtask_id: filterSubtaskId.value
546 }); 595 });
547 if (res.code) { 596 if (res.code) {
548 // 整理数据结构 597 // 整理数据结构
...@@ -564,6 +613,8 @@ const onEvaluationLoad = async () => { ...@@ -564,6 +613,8 @@ const onEvaluationLoad = async () => {
564 page: nextPage, 613 page: nextPage,
565 user_id: route.params.id, 614 user_id: route.params.id,
566 group_id: selectedCourses.value.length ? selectedCourses.value[0]['id'] : '', 615 group_id: selectedCourses.value.length ? selectedCourses.value[0]['id'] : '',
616 + task_id: filterTaskId.value,
617 + subtask_id: filterSubtaskId.value
567 }); 618 });
568 if (res.code) { 619 if (res.code) {
569 // 整理数据结构 620 // 整理数据结构
...@@ -750,6 +801,8 @@ const onLoad = async (date) => { ...@@ -750,6 +801,8 @@ const onLoad = async (date) => {
750 page: nextPage, 801 page: nextPage,
751 user_id: route.params.id, 802 user_id: route.params.id,
752 group_id: selectedCourses.value.length ? selectedCourses.value[0]['id'] : '', 803 group_id: selectedCourses.value.length ? selectedCourses.value[0]['id'] : '',
804 + task_id: filterTaskId.value,
805 + subtask_id: filterSubtaskId.value
753 }); 806 });
754 if (res.code) { 807 if (res.code) {
755 // 整理数据结构 808 // 整理数据结构
......