feat(teacher): 添加作业筛选组件并更新相关API参数
在教师端学生详情页添加作业筛选组件,支持按大作业和小作业筛选数据 更新获取作业记录、统计和点评的API调用,添加task_id和subtask_id参数 当筛选条件变化时重置并重新加载对应数据
Showing
4 changed files
with
239 additions
and
2 deletions
| 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'] | ... | ... |
src/components/teacher/TaskFilter.vue
0 → 100644
| 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 | // 整理数据结构 | ... | ... |
-
Please register or login to post a comment