xlx_teacher_app/src/pages/student/detail.vue

256 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { useQuery } from '@tanstack/vue-query'
import { computed, onMounted, ref } from 'vue'
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'
import { useHomeStore } from '@/store/home'
const homeStore = useHomeStore()
// 获取页面参数
const studentNumber = ref('')
const studentName = ref('')
// 选中的科目ID0表示全科
const selectedSubjectId = ref(0)
// 使用 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)
// 学生基本信息
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,
}
})
// 科目选项(包含全科)
const subjectTabs = computed(() => {
const tabs = [
{ name: 'all', title: '全科', subjectId: 0 },
]
homeStore.subjectOptions.forEach((subject) => {
tabs.push({
name: `subject-${subject.subjectId}`,
title: subject.label,
subjectId: subject.subjectId,
})
})
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 || {}
studentNumber.value = options.student_number || ''
studentName.value = options.student_name || ''
selectedSubjectId.value = options.subject_id || 0
// 如果store中没有数据先获取选项数据
if (homeStore.examOptions.length === 0) {
await homeStore.fetchOptions()
}
})
</script>
<template>
<view class="min-h-screen from-blue-50 to-gray-50 bg-gradient-to-b">
<!-- 导航栏 -->
<wd-navbar placeholder fixed>
<template #left>
<view class="flex items-center" @click="goBack">
<wd-icon name="arrow-left" size="20px" />
</view>
</template>
<template #title>
<text class="text-lg font-semibold">{{ studentName }}成绩报告</text>
</template>
</wd-navbar>
<!-- 科目选择标签栏 -->
<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>
<!-- 加载状态 -->
<view v-if="isLoading" class="h-screen flex items-center justify-center">
<wd-loading size="32" />
</view>
<!-- 内容区域 -->
<view v-else-if="studentInfo" class="pb-4">
<!-- 主要内容区域 -->
<view class="p-4 space-y-4">
<!-- 个人成绩卡片 -->
<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>
<!-- 总分展示 -->
<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>
</view>
</view>
<!-- 成绩统计网格 -->
<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>
</view>
<text class="text-sm text-slate-600">班级排名</text>
</view>
<!-- 年级排名 -->
<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>
</view>
<text class="text-sm text-slate-600">年级排名</text>
</view>
<!-- 班级平均分 -->
<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>
<!-- 年级平均分 -->
<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>
</view>
</view>
<!-- 成绩分布图 -->
<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>
</view>
</view>
</template>
<route lang="jsonc">
{
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "学生详情"
}
}
</route>