xlx_teacher_app/src/pages/student/detail.vue

256 lines
8.0 KiB
Vue
Raw Normal View History

2025-10-05 20:10:51 +08:00
<script setup lang="ts">
2025-10-10 00:12:59 +08:00
import { useQuery } from '@tanstack/vue-query'
2025-10-05 20:10:51 +08:00
import { computed, onMounted, ref } from 'vue'
2025-10-10 00:12:59 +08:00
import AdvantageAnalysisChart from '@/components/student/AdvantageAnalysisChart.vue'
import AverageComparisonChart from '@/components/student/AverageComparisonChart.vue'
import ScoreDistributionChart from '@/components/student/ScoreDistributionChart.vue'
import ScoreTrendChart from '@/components/student/ScoreTrendChart.vue'
import { teacherAnalysisPersonalReportUsingPost } from '@/service/laoshichengjifenxi'
2025-10-05 20:10:51 +08:00
import { useHomeStore } from '@/store/home'
const homeStore = useHomeStore()
2025-10-10 00:12:59 +08:00
// 获取页面参数
const studentNumber = ref('')
const studentName = ref('')
2025-10-05 20:10:51 +08:00
// 选中的科目ID0表示全科
const selectedSubjectId = ref(0)
2025-10-10 00:12:59 +08:00
// 使用 TanStack Query 获取个人报告数据
const {
data: reportData,
isLoading,
} = useQuery({
queryKey: computed(() => [
'personal-report',
homeStore.selectedClassId,
homeStore.selectedGradeKey,
homeStore.selectedExamId,
studentNumber.value,
selectedSubjectId.value,
]),
queryFn: async () => {
const response = await teacherAnalysisPersonalReportUsingPost({
body: {
class_key: homeStore.selectedClassId,
grade_key: homeStore.selectedGradeKey,
exam_id: homeStore.selectedExamId,
student_number: studentNumber.value,
subject_id: selectedSubjectId.value || 0,
},
})
return response
},
enabled: computed(() =>
!!homeStore.selectedClassId
&& !!homeStore.selectedGradeKey
&& !!homeStore.selectedExamId
&& !!studentNumber.value,
),
staleTime: 30000,
})
// 考试总结数据
const examSummary = computed(() => reportData.value?.exam_summary)
2025-10-05 20:10:51 +08:00
// 学生基本信息
2025-10-10 00:12:59 +08:00
const studentInfo = computed(() => {
const summary = examSummary.value
if (!summary)
return null
return {
totalScore: summary.all_score || 0,
classRank: summary.class_rank || 0,
classTotal: summary.class_member_count || 0,
gradeRank: summary.grade_rank || 0,
gradeTotal: summary.grade_member_count || 0,
gradeAverage: summary.grade_average_score || 0,
classAverage: summary.class_average_score || 0,
fullScore: summary.full_score || 0,
}
2025-10-05 20:10:51 +08:00
})
// 科目选项(包含全科)
const subjectTabs = computed(() => {
const tabs = [
{ name: 'all', title: '全科', subjectId: 0 },
]
homeStore.subjectOptions.forEach((subject) => {
tabs.push({
2025-10-10 00:12:59 +08:00
name: `subject-${subject.subjectId}`,
2025-10-05 20:10:51 +08:00
title: subject.label,
2025-10-10 00:12:59 +08:00
subjectId: subject.subjectId,
2025-10-05 20:10:51 +08:00
})
})
return tabs
})
// 当前选中的科目标签
const activeTab = computed({
get: () => selectedSubjectId.value === 0 ? 'all' : `subject-${selectedSubjectId.value}`,
set: (value) => {
if (value === 'all') {
selectedSubjectId.value = 0
}
else {
const subjectId = Number.parseInt(value.replace('subject-', ''))
selectedSubjectId.value = subjectId
}
},
})
// 返回上一页
function goBack() {
uni.navigateBack()
}
// 科目切换处理
function handleTabClick({ name }: { name: string }) {
activeTab.value = name
}
// 页面初始化
onMounted(async () => {
// 获取页面参数
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = (currentPage as any).options || {}
2025-10-10 00:12:59 +08:00
studentNumber.value = options.student_number || ''
studentName.value = options.student_name || ''
selectedSubjectId.value = options.subject_id || 0
2025-10-05 20:10:51 +08:00
// 如果store中没有数据先获取选项数据
if (homeStore.examOptions.length === 0) {
await homeStore.fetchOptions()
}
})
</script>
<template>
2025-10-10 00:12:59 +08:00
<view class="min-h-screen from-blue-50 to-gray-50 bg-gradient-to-b">
2025-10-05 20:10:51 +08:00
<!-- 导航栏 -->
<wd-navbar placeholder fixed>
<template #left>
<view class="flex items-center" @click="goBack">
<wd-icon name="arrow-left" size="20px" />
</view>
</template>
<template #title>
2025-10-10 00:12:59 +08:00
<text class="text-lg font-semibold">{{ studentName }}成绩报告</text>
2025-10-05 20:10:51 +08:00
</template>
</wd-navbar>
2025-10-12 22:16:49 +08:00
<!-- 科目选择标签栏 -->
<wd-tabs
v-model="activeTab"
sticky
class="bg-white shadow-sm"
@click="handleTabClick"
>
<wd-tab
v-for="tab in subjectTabs"
:key="tab.name"
:name="tab.name"
:title="tab.title"
/>
</wd-tabs>
2025-10-10 00:12:59 +08:00
<!-- 加载状态 -->
<view v-if="isLoading" class="h-screen flex items-center justify-center">
<wd-loading size="32" />
</view>
2025-10-05 20:10:51 +08:00
2025-10-10 00:12:59 +08:00
<!-- 内容区域 -->
<view v-else-if="studentInfo" class="pb-4">
<!-- 主要内容区域 -->
<view class="p-4 space-y-4">
<!-- 个人成绩卡片 -->
2025-10-12 22:16:49 +08:00
<view class="relative rounded-2xl from-blue-500 to-blue-600 bg-gradient-to-br p-5 shadow-lg">
<view class="absolute left-0 top-4 rounded-r-full bg-white/40 px-4 backdrop-blur">
<text class="text-lg text-white">{{ studentName }}</text>
</view>
2025-10-10 00:12:59 +08:00
<!-- 总分展示 -->
2025-10-12 22:16:49 +08:00
<view class="mb-4 flex items-center justify-end gap-4">
<view class="mt-1 flex items-baseline gap-1">
<text class="text-4xl text-white font-bold">{{ studentInfo.totalScore }}</text>
<text class="text-lg text-blue-100">/{{ studentInfo.fullScore }}</text>
2025-10-05 20:10:51 +08:00
</view>
</view>
2025-10-10 00:12:59 +08:00
<!-- 成绩统计网格 -->
<view class="grid grid-cols-2 gap-3">
<!-- 班级排名 -->
<view class="flex flex-col rounded-xl bg-white/90 p-3 backdrop-blur">
<view class="mb-1 flex items-baseline gap-1">
<text class="text-2xl text-slate-800 font-bold">{{ studentInfo.classRank }}</text>
<text class="text-xs text-slate-500">/{{ studentInfo.classTotal }}</text>
2025-10-05 20:10:51 +08:00
</view>
2025-10-10 00:12:59 +08:00
<text class="text-sm text-slate-600">班级排名</text>
2025-10-05 20:10:51 +08:00
</view>
2025-10-10 00:12:59 +08:00
<!-- 年级排名 -->
<view class="flex flex-col rounded-xl bg-white/90 p-3 backdrop-blur">
<view class="mb-1 flex items-baseline gap-1">
<text class="text-2xl text-slate-800 font-bold">{{ studentInfo.gradeRank }}</text>
<text class="text-xs text-slate-500">/{{ studentInfo.gradeTotal }}</text>
2025-10-05 20:10:51 +08:00
</view>
2025-10-10 00:12:59 +08:00
<text class="text-sm text-slate-600">年级排名</text>
2025-10-05 20:10:51 +08:00
</view>
2025-10-10 00:12:59 +08:00
<!-- 班级平均分 -->
<view class="flex flex-col rounded-xl bg-white/90 p-3 backdrop-blur">
<text class="text-2xl text-slate-800 font-bold">{{ studentInfo.classAverage.toFixed(1) }}</text>
<text class="text-sm text-slate-600">班级平均分</text>
</view>
2025-10-05 20:10:51 +08:00
2025-10-10 00:12:59 +08:00
<!-- 年级平均分 -->
<view class="flex flex-col rounded-xl bg-white/90 p-3 backdrop-blur">
<text class="text-2xl text-slate-800 font-bold">{{ studentInfo.gradeAverage.toFixed(1) }}</text>
<text class="text-sm text-slate-600">年级平均分</text>
</view>
2025-10-05 20:10:51 +08:00
</view>
</view>
2025-10-10 00:12:59 +08:00
<!-- 成绩分布图 -->
<ScoreDistributionChart :data="reportData?.learning_situation_analysis" />
<!-- 全科专属内容 -->
<template v-if="selectedSubjectId === 0">
<!-- 优劣势分析 -->
<AdvantageAnalysisChart :data="reportData?.advantage_analysis" />
<!-- 均分对比 -->
<AverageComparisonChart
:data="reportData?.average_comparison_analysis"
:exam-summary="reportData?.exam_summary"
/>
</template>
<!-- 单科专属内容 -->
<template v-else>
<!-- 成绩趋势 -->
<ScoreTrendChart :data="reportData?.score_trend" />
</template>
</view>
</view>
<!-- 暂无数据 -->
<view v-else class="h-screen flex flex-col items-center justify-center">
<text class="text-gray-400">暂无数据</text>
2025-10-05 20:10:51 +08:00
</view>
</view>
</template>
<route lang="jsonc">
{
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "学生详情"
}
}
</route>