hookehuyr

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

在教师端学生详情页添加作业筛选组件,支持按大作业和小作业筛选数据
更新获取作业记录、统计和点评的API调用,添加task_id和subtask_id参数
当筛选条件变化时重置并重新加载对应数据
/*
* @Date: 2025-06-23 11:46:21
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-12-12 14:55:03
* @LastEditTime: 2025-12-15 12:30:05
* @FilePath: /mlaj/src/api/teacher.js
* @Description: 文件描述
*/
......@@ -126,6 +126,8 @@ export const getStudentCheckinListAPI = (params) => fn(fetch.get(Api.STUDENT_CHE
* 获取学员作业记录
* @param {*} user_id 学员ID
* @param {*} group_id 课程ID
* @param {*} task_id 大作业ID
* @param {*} subtask_id 小作业ID
* @param {*} limit 条数
* @param {*} page 页码
* @returns {Object} data
......
......@@ -38,6 +38,7 @@ declare module 'vue' {
SharePoster: typeof import('./components/ui/SharePoster.vue')['default']
SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default']
TaskCalendar: typeof import('./components/ui/TaskCalendar.vue')['default']
TaskFilter: typeof import('./components/teacher/TaskFilter.vue')['default']
TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default']
UploadVideoPopup: typeof import('./components/ui/UploadVideoPopup.vue')['default']
UserAgreement: typeof import('./components/ui/UserAgreement.vue')['default']
......
<template>
<div class="task-filter bg-white">
<div class="flex gap-2">
<!-- 大作业选择 -->
<div class="flex-1 min-w-0" @click="showTaskPicker = true">
<div class="flex items-center justify-between bg-gray-50 rounded px-3 py-2 border border-gray-100">
<span class="text-sm text-gray-700 truncate mr-1">{{ selectedTaskName || '全部作业' }}</span>
<van-icon name="arrow-down" color="#9ca3af" size="12" />
</div>
</div>
<!-- 小作业选择 -->
<div class="flex-1 min-w-0" @click="handleSubtaskClick">
<div class="flex items-center justify-between bg-gray-50 rounded px-3 py-2 border border-gray-100"
:class="{ 'opacity-50': !selectedTaskId }">
<span class="text-sm text-gray-700 truncate mr-1">{{ selectedSubtaskName || '全部小作业' }}</span>
<van-icon name="arrow-down" color="#9ca3af" size="12" />
</div>
</div>
</div>
<!-- 大作业弹窗 -->
<van-popup v-model:show="showTaskPicker" position="bottom" round>
<van-picker
:columns="taskColumns"
@confirm="onConfirmTask"
@cancel="showTaskPicker = false"
show-toolbar
title="选择作业"
/>
</van-popup>
<!-- 小作业弹窗 -->
<van-popup v-model:show="showSubtaskPicker" position="bottom" round>
<van-picker
:columns="subtaskColumns"
@confirm="onConfirmSubtask"
@cancel="showSubtaskPicker = false"
show-toolbar
title="选择小作业"
/>
</van-popup>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import { getTeacherTaskListAPI, getTeacherTaskDetailAPI } from '@/api/teacher'
import { showToast } from 'vant'
const props = defineProps({
groupId: {
type: [String, Number],
default: ''
}
})
const emit = defineEmits(['change'])
// 状态
const showTaskPicker = ref(false)
const showSubtaskPicker = ref(false)
const taskList = ref([])
const subtaskList = ref([])
const selectedTaskId = ref('')
const selectedSubtaskId = ref('')
// 计算属性 - 选项列表
const taskColumns = computed(() => {
const list = taskList.value.map(item => ({ text: item.title, value: item.id }))
return [{ text: '全部作业', value: '' }, ...list]
})
const subtaskColumns = computed(() => {
const list = subtaskList.value.map(item => ({ text: item.title, value: item.id }))
return [{ text: '全部小作业', value: '' }, ...list]
})
// 计算属性 - 显示名称
const selectedTaskName = computed(() => {
if (!selectedTaskId.value) return '全部作业'
const found = taskList.value.find(item => item.id === selectedTaskId.value)
return found ? found.title : ''
})
const selectedSubtaskName = computed(() => {
if (!selectedSubtaskId.value) return '全部小作业'
const found = subtaskList.value.find(item => item.id === selectedSubtaskId.value)
return found ? found.title : ''
})
// 获取大作业列表
const fetchTaskList = async () => {
if (!props.groupId) {
taskList.value = []
return
}
try {
const res = await getTeacherTaskListAPI({
group_id: props.groupId,
limit: 100,
page: 0
})
if (res.code === 1) {
taskList.value = res.data || []
}
} catch (error) {
console.error('获取作业列表失败:', error)
}
}
// 监听 groupId 变化
watch(() => props.groupId, (newVal) => {
fetchTaskList()
}, { immediate: true })
// 获取大作业详情(含小作业)
const fetchTaskDetail = async (taskId) => {
if (!taskId) {
subtaskList.value = []
return
}
try {
const res = await getTeacherTaskDetailAPI({ id: taskId })
if (res.code === 1) {
subtaskList.value = res.data.subtask_list || []
}
} catch (error) {
console.error('获取作业详情失败:', error)
}
}
// 事件处理
const onConfirmTask = async ({ selectedOptions }) => {
const option = selectedOptions[0]
if (selectedTaskId.value !== option.value) {
selectedTaskId.value = option.value
// 重置小作业
selectedSubtaskId.value = ''
subtaskList.value = []
if (option.value) {
await fetchTaskDetail(option.value)
}
emitChange()
}
showTaskPicker.value = false
}
const onConfirmSubtask = ({ selectedOptions }) => {
const option = selectedOptions[0]
selectedSubtaskId.value = option.value
emitChange()
showSubtaskPicker.value = false
}
const handleSubtaskClick = () => {
if (!selectedTaskId.value) {
showToast('请先选择作业')
return
}
showSubtaskPicker.value = true
}
const emitChange = () => {
emit('change', {
task_id: selectedTaskId.value,
subtask_id: selectedSubtaskId.value
})
}
</script>
<style lang="less" scoped>
.task-filter {
// 可以在这里添加额外的样式
}
</style>
......@@ -2,7 +2,7 @@
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2025-06-19 17:12:19
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-12-12 18:02:06
* @LastEditTime: 2025-12-15 12:54:05
* @FilePath: /mlaj/src/views/teacher/studentPage.vue
* @Description: 学生详情页面
-->
......@@ -95,6 +95,13 @@
<!-- 使用van-sticky包裹van-tabs实现粘性布局 -->
<div class="bg-white" style="margin: 1rem;">
<van-sticky :offset-top="0">
<div class="bg-white px-4 py-2">
<TaskFilter
:key="currentGroupId"
:group-id="currentGroupId"
@change="handleTaskFilterChange"
/>
</div>
<van-tabs v-model:active="activeTab" color="#4caf50" animated swipeable @change="handleTabChange">
<van-tab title="作业记录" name="homework"></van-tab>
<van-tab title="班主任点评" name="evaluation"></van-tab>
......@@ -331,6 +338,7 @@ import dayjs from 'dayjs';
import { delUploadTaskInfoAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI } from "@/api/checkin";
import { getStudentDetailAPI, getStudentCheckinListAPI, getStudentUploadListAPI, getCheckinFeedbackListAPI, addCheckinFeedbackAPI, delCheckinFeedbackAPI, getStudentStatAPI } from "@/api/teacher";
import TaskFilter from '@/components/teacher/TaskFilter.vue'
const router = useRouter()
const route = useRoute()
......@@ -345,6 +353,41 @@ const studentInfo = ref({})
// 选中的课程列表(默认选中第一个课程)
const selectedCourses = ref([])
const currentGroupId = computed(() => selectedCourses.value.length ? selectedCourses.value[0]['id'] : '')
// 作业筛选
const filterTaskId = ref('')
const filterSubtaskId = ref('')
const handleTaskFilterChange = ({ task_id, subtask_id }) => {
filterTaskId.value = task_id
filterSubtaskId.value = subtask_id
// 重置所有列表数据状态
page.value = 0;
checkinDataList.value = [];
finished.value = false;
recordPage.value = 0;
records.value = [];
recordFinished.value = false;
evaluationPage.value = 0;
evaluationList.value = [];
evaluationFinished.value = false;
// 根据当前 activeTab 加载对应数据
if (activeTab.value === 'homework') {
loading.value = true;
onLoad();
} else if (activeTab.value === 'statistics') {
recordLoading.value = true;
onRecordLoad();
} else if (activeTab.value === 'evaluation') {
evaluationLoading.value = true;
onEvaluationLoad();
}
}
// 当前选中的标签页
const activeTab = ref('homework')
......@@ -455,6 +498,10 @@ const toggleCourseSelection = (course) => {
// 可以在这里添加其他业务逻辑,比如筛选相关数据
console.log('当前选中的课程:', selectedCourses.value)
// 重置作业筛选
filterTaskId.value = ''
filterSubtaskId.value = ''
resetAndReload()
resetAndReloadRecords()
resetAndReloadEvaluations()
......@@ -543,6 +590,8 @@ const onRecordLoad = async () => {
page: nextPage,
user_id: route.params.id,
group_id: selectedCourses.value.length ? selectedCourses.value[0]['id'] : '',
task_id: filterTaskId.value,
subtask_id: filterSubtaskId.value
});
if (res.code) {
// 整理数据结构
......@@ -564,6 +613,8 @@ const onEvaluationLoad = async () => {
page: nextPage,
user_id: route.params.id,
group_id: selectedCourses.value.length ? selectedCourses.value[0]['id'] : '',
task_id: filterTaskId.value,
subtask_id: filterSubtaskId.value
});
if (res.code) {
// 整理数据结构
......@@ -750,6 +801,8 @@ const onLoad = async (date) => {
page: nextPage,
user_id: route.params.id,
group_id: selectedCourses.value.length ? selectedCourses.value[0]['id'] : '',
task_id: filterTaskId.value,
subtask_id: filterSubtaskId.value
});
if (res.code) {
// 整理数据结构
......