xlx_teacher_app/src/pages/student/list.vue

247 lines
7.8 KiB
Vue
Raw Normal View History

2025-08-16 16:42:40 +08:00
<script setup lang="ts">
2025-09-21 12:30:25 +08:00
import type { StudentInfo } from '@/service/types'
import { useQuery } from '@tanstack/vue-query'
2025-10-05 20:10:51 +08:00
import { whenever } from '@vueuse/core'
2025-08-16 16:42:40 +08:00
import { computed, onMounted, ref } from 'vue'
2025-10-05 20:10:51 +08:00
import StudentListItem from '@/components/student/StudentListItem.vue'
import { teacherAnalysisAttentionStudentUsingPost, teacherAnalysisExportStudentListUsingPost, teacherAnalysisStudentListUsingPost } from '@/service/laoshichengjifenxi'
2025-09-21 12:30:25 +08:00
import { useHomeStore } from '@/store/home'
2025-08-16 16:42:40 +08:00
2025-11-19 14:18:14 +08:00
// 1. 引入 Store
2025-09-21 12:30:25 +08:00
const homeStore = useHomeStore()
2025-08-16 16:42:40 +08:00
const searchQuery = ref('')
2025-11-19 14:18:14 +08:00
// 2. 逻辑计算与 Query (保持原逻辑不变)
2025-09-21 12:30:25 +08:00
const canLoadStudents = computed(() => {
2025-11-20 23:24:05 +08:00
return homeStore.selectedClassKey && homeStore.selectedExamId && homeStore.selectedExamSubjectId
2025-09-21 12:30:25 +08:00
})
2025-08-16 16:42:40 +08:00
2025-09-21 12:30:25 +08:00
const {
data: studentsData,
isLoading: loading,
error,
refetch,
} = useQuery({
queryKey: computed(() => [
'students',
2025-11-20 23:24:05 +08:00
homeStore.selectedClassKey,
2025-09-21 12:30:25 +08:00
homeStore.selectedExamId,
2025-10-07 14:52:01 +08:00
homeStore.selectedExamSubjectId,
2025-09-21 12:30:25 +08:00
homeStore.selectedGradeKey,
2025-10-05 20:10:51 +08:00
searchQuery.value,
2025-09-21 12:30:25 +08:00
]),
queryFn: async () => {
const response = await teacherAnalysisStudentListUsingPost({
body: {
2025-11-20 23:24:05 +08:00
class_id: homeStore.selectedClassKey!,
2025-10-07 14:52:01 +08:00
exam_subject_id: homeStore.selectedExamSubjectId!,
2025-09-21 12:30:25 +08:00
grade_id: homeStore.selectedGradeKey!,
2025-10-05 20:10:51 +08:00
keyword: searchQuery.value || undefined,
2025-09-21 12:30:25 +08:00
},
})
2025-10-05 20:10:51 +08:00
return response || { attentioned_students: [], other_students: [] }
2025-09-21 12:30:25 +08:00
},
enabled: computed(() => !!canLoadStudents.value),
2025-10-05 20:10:51 +08:00
staleTime: 30000,
gcTime: 300000,
2025-09-21 12:30:25 +08:00
})
const attentionedStudents = computed(() => studentsData.value?.attentioned_students || [])
const otherStudents = computed(() => studentsData.value?.other_students || [])
2025-11-19 14:18:14 +08:00
// 3. 交互方法
2025-09-21 12:30:25 +08:00
function goToStudentDetail(student: StudentInfo) {
2025-08-16 16:42:40 +08:00
uni.navigateTo({
2025-10-10 00:12:59 +08:00
url: `/pages/student/detail?info_id=${student.info_id}&student_number=${student.student_number}&student_name=${student.student_name}`,
2025-09-21 12:30:25 +08:00
})
}
2025-10-07 14:52:01 +08:00
async function toggleAttention(student: StudentInfo, isAttentioned: boolean) {
2025-10-05 20:10:51 +08:00
if (!student.student_number) {
2025-11-19 14:18:14 +08:00
return uni.showToast({ title: '缺少学号', icon: 'none' })
2025-10-05 20:10:51 +08:00
}
2025-11-19 14:18:14 +08:00
uni.showLoading({ title: '处理中', mask: true })
2025-10-05 20:10:51 +08:00
try {
await teacherAnalysisAttentionStudentUsingPost({
2025-10-07 14:52:01 +08:00
body: { student_number: student.student_number, is_attention: !isAttentioned },
2025-10-05 20:10:51 +08:00
})
uni.showToast({ title: '操作成功', icon: 'success' })
refetch()
}
catch (error) {
2025-11-19 14:18:14 +08:00
uni.showToast({ title: '操作失败', icon: 'none' })
2025-10-05 20:10:51 +08:00
}
finally {
uni.hideLoading()
}
2025-08-16 16:42:40 +08:00
}
2025-09-21 12:30:25 +08:00
async function handleDownload() {
2025-08-16 16:42:40 +08:00
try {
2025-11-19 14:18:14 +08:00
uni.showLoading({ title: '准备中' })
2025-10-05 20:10:51 +08:00
const response = await teacherAnalysisExportStudentListUsingPost({
2025-09-21 12:30:25 +08:00
body: {
2025-11-20 23:24:05 +08:00
class_id: homeStore.selectedClassKey!,
2025-10-07 14:52:01 +08:00
exam_subject_id: homeStore.selectedExamSubjectId!,
2025-09-21 12:30:25 +08:00
grade_id: homeStore.selectedGradeKey!,
},
})
2025-10-05 20:10:51 +08:00
if (response) {
const downloadUrl = `${import.meta.env.VITE_API_BASE_URL || ''}${response}`
// #ifdef H5
window.open(downloadUrl, '_blank')
// #endif
// #ifndef H5
uni.downloadFile({
url: downloadUrl,
success: (res) => {
if (res.statusCode === 200) {
2025-11-19 14:18:14 +08:00
uni.openDocument({ filePath: res.tempFilePath })
2025-10-05 20:10:51 +08:00
}
},
2025-11-19 14:18:14 +08:00
fail: () => uni.showToast({ title: '下载失败', icon: 'none' }),
2025-10-05 20:10:51 +08:00
})
// #endif
}
else {
2025-11-19 14:18:14 +08:00
uni.showToast({ title: '链接获取失败', icon: 'none' })
2025-10-05 20:10:51 +08:00
}
2025-08-16 16:42:40 +08:00
}
2025-11-19 14:18:14 +08:00
catch (e) {
uni.showToast({ title: '请求失败', icon: 'none' })
2025-10-05 20:10:51 +08:00
}
finally {
uni.hideLoading()
2025-08-16 16:42:40 +08:00
}
}
2025-11-19 14:18:14 +08:00
const handleShare = () => uni.showToast({ title: '开发中', icon: 'none' })
2025-09-21 12:30:25 +08:00
2025-11-19 14:18:14 +08:00
whenever(() => error.value, () => {
uni.showToast({ title: '数据加载失败', icon: 'none' })
2025-09-21 12:30:25 +08:00
})
onMounted(async () => {
if (homeStore.examOptions.length === 0) {
await homeStore.fetchOptions()
}
2025-08-16 16:42:40 +08:00
})
</script>
<template>
2025-11-19 14:18:14 +08:00
<view class="min-h-screen bg-slate-50 pb-10">
<wd-navbar
placeholder safe-area-inset-top fixed
title="学生名单"
custom-class="bg-white"
>
<template #right>
<view class="h-full flex items-center">
<view class="p-2 active:opacity-70" @click="handleDownload">
<wd-icon name="download" size="22px" color="#333" />
</view>
<view class="p-2 active:opacity-70" @click="handleShare">
<wd-icon name="share" size="22px" color="#333" />
</view>
</view>
</template>
</wd-navbar>
2025-08-16 16:42:40 +08:00
2025-11-19 14:18:14 +08:00
<view class="relative z-10 bg-white shadow-sm">
<wd-drop-menu custom-class="border-b border-gray-100">
2025-10-05 20:10:51 +08:00
<wd-drop-menu-item v-model="homeStore.selectedClassId" :options="homeStore.classOptions" />
<wd-drop-menu-item v-model="homeStore.selectedExamId" :options="homeStore.examOptions" />
2025-10-07 14:52:01 +08:00
<wd-drop-menu-item v-model="homeStore.selectedExamSubjectId" :options="homeStore.subjectOptions" />
2025-09-21 12:30:25 +08:00
</wd-drop-menu>
2025-11-19 14:18:14 +08:00
<view class="px-3 py-2">
<wd-search
v-model="searchQuery"
placeholder="搜索学生姓名"
hide-cancel
custom-class="bg-gray-100 rounded-full"
/>
</view>
2025-09-21 12:30:25 +08:00
</view>
2025-11-19 14:18:14 +08:00
<view class="p-3">
<view v-if="loading" class="flex flex-col items-center justify-center py-20">
<wd-loading size="30px" color="#3b82f6" />
<text class="mt-3 text-sm text-gray-400">数据加载中...</text>
2025-08-16 16:42:40 +08:00
</view>
2025-11-19 14:18:14 +08:00
<block v-else>
<view v-if="attentionedStudents.length > 0 || otherStudents.length > 0">
<view v-if="attentionedStudents.length > 0" class="mb-4 overflow-hidden rounded-xl bg-white shadow-sm">
<view class="flex items-center border-b border-orange-100 bg-orange-50 px-4 py-3">
<wd-icon name="star-filled" color="#f97316" size="18px" />
<text class="ml-2 text-sm text-orange-800 font-bold">重点关注</text>
<text class="ml-auto text-xs text-orange-400">{{ attentionedStudents.length }}</text>
</view>
<view class="px-2">
<StudentListItem
v-for="(student, index) in attentionedStudents"
:key="student.info_id"
:student="student"
:index="index + 1"
:is-attentioned="true"
@click="goToStudentDetail"
@toggle-attention="toggleAttention(student, true)"
/>
</view>
</view>
<view v-if="otherStudents.length > 0" class="overflow-hidden rounded-xl bg-white shadow-sm">
<view class="flex items-center border-b border-gray-100 px-4 py-3">
<view class="mr-2 h-4 w-1 rounded-full bg-blue-500" />
<text class="text-sm text-gray-800 font-bold">全部学生</text>
<text class="ml-auto text-xs text-gray-400">{{ otherStudents.length }}</text>
</view>
<view class="px-2">
<StudentListItem
v-for="(student, index) in otherStudents"
:key="student.info_id"
:student="student"
:index="attentionedStudents.length + index + 1"
:is-attentioned="false"
@click="goToStudentDetail"
@toggle-attention="toggleAttention(student, false)"
/>
</view>
</view>
2025-10-05 20:10:51 +08:00
</view>
2025-11-19 14:18:14 +08:00
<view v-else class="pt-10">
<wd-status-tip
:image="!canLoadStudents ? 'search' : 'content'"
:tip="!canLoadStudents ? '请完善上方筛选条件' : '暂无相关学生数据'"
/>
</view>
</block>
2025-08-16 16:42:40 +08:00
</view>
</view>
</template>
<route lang="jsonc" type="home">
2025-10-05 20:10:51 +08:00
{
"layout": "tabbar",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "学生名单"
}
}
2025-08-16 16:42:40 +08:00
</route>
2025-11-19 14:18:14 +08:00
<style>
/* Uniapp
page 标签在小程序中对应整个页面容器
*/
page {
background-color: #f8fafc; /* slate-50 */
}
</style>