fix: 增加缓存和修复查看成绩部分问题
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
fee76ec061
commit
115d118d42
|
|
@ -0,0 +1,79 @@
|
|||
import { useUserStore } from '@/store/user'
|
||||
import { watch } from 'vue'
|
||||
|
||||
/**
|
||||
* 基于用户ID的响应式存储
|
||||
* 自动根据当前登录用户隔离数据
|
||||
*
|
||||
* @param key - 存储键名
|
||||
* @param defaultValue - 默认值
|
||||
* @returns 响应式引用
|
||||
*
|
||||
* @example
|
||||
* const selectedExamId = useUserStorage('selectedExamId', 0);
|
||||
* // 使用方式和普通 ref 完全一样
|
||||
* selectedExamId.value = 123;
|
||||
*/
|
||||
export function useUserStorage<T>(key: string, defaultValue: T) {
|
||||
const userStore = useUserStore()
|
||||
const valueRef = ref<T>(defaultValue)
|
||||
|
||||
// 生成基于用户ID的存储键
|
||||
const getStorageKey = () => {
|
||||
const userId = userStore.info?.id || 'default'
|
||||
return `user_${userId}_${key}`
|
||||
}
|
||||
|
||||
// 从 uni.storage 加载数据
|
||||
const loadFromStorage = () => {
|
||||
const storageKey = getStorageKey()
|
||||
const stored = uni.getStorageSync(storageKey)
|
||||
|
||||
if (stored) {
|
||||
try {
|
||||
valueRef.value = JSON.parse(stored)
|
||||
}
|
||||
catch {
|
||||
// 如果是数字类型,直接转换
|
||||
if (typeof defaultValue === 'number') {
|
||||
valueRef.value = Number(stored) as T
|
||||
}
|
||||
else {
|
||||
valueRef.value = stored as T
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
valueRef.value = defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// 保存到 uni.storage
|
||||
const saveToStorage = (value: T) => {
|
||||
const storageKey = getStorageKey()
|
||||
if (value === undefined || value === null || value === 0 || value === '') {
|
||||
uni.removeStorageSync(storageKey)
|
||||
}
|
||||
else {
|
||||
const toStore = typeof value === 'object' ? JSON.stringify(value) : String(value)
|
||||
uni.setStorageSync(storageKey, toStore)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听值变化,自动保存
|
||||
watch(valueRef, (newValue) => {
|
||||
saveToStorage(newValue)
|
||||
})
|
||||
|
||||
// 监听用户变化,自动重新加载
|
||||
watch(
|
||||
() => userStore.info?.id,
|
||||
() => {
|
||||
loadFromStorage()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
return valueRef
|
||||
}
|
||||
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
<script lang="ts" setup>
|
||||
import type * as API from '@/service/types'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import OverviewSettingsDialog from '@/components/score/OverviewSettingsDialog.vue'
|
||||
import RankSettingsDialog from '@/components/score/RankSettingsDialog.vue'
|
||||
|
|
@ -73,9 +74,6 @@ const showRankSettingsDialog = ref(false)
|
|||
// 进步/退步学生显示数量(基础显示)
|
||||
const keyStudentCount = ref(5)
|
||||
|
||||
// 分页大小(展开后使用)
|
||||
const pageSize = ref(20)
|
||||
|
||||
// 考试选择选项
|
||||
const examOptions = computed(() => homeStore.examOptions.map(exam => ({
|
||||
label: exam.label,
|
||||
|
|
@ -88,14 +86,6 @@ const classColumns = computed(() => homeStore.classOptions.map(cls => ({
|
|||
value: cls.value.toString(), // 转为字符串类型
|
||||
})))
|
||||
|
||||
// 当前选中的考试ID
|
||||
const selectedExamId = computed({
|
||||
get: () => homeStore.selectedExamId,
|
||||
set: (value) => {
|
||||
homeStore.onExamChange(value)
|
||||
},
|
||||
})
|
||||
|
||||
// 当前选中的考试名称
|
||||
const selectedExamName = computed(() => {
|
||||
if (!homeStore.selectedExamId) {
|
||||
|
|
@ -247,9 +237,9 @@ const {
|
|||
query_mode: 'class',
|
||||
statistics_mode: 'cumulative',
|
||||
rank_ranges: [
|
||||
{ name: `前${scoreSettings.value.rank_top_1}名`, start: 1, end: scoreSettings.value.rank_top_1 },
|
||||
{ name: `前${scoreSettings.value.rank_top_2}名`, start: 1, end: scoreSettings.value.rank_top_2 },
|
||||
{ name: `前${scoreSettings.value.rank_top_3}名`, start: 1, end: scoreSettings.value.rank_top_3 },
|
||||
{ name: `前${scoreSettings.value.rank_top_1}名`, start: 1, end: Number(scoreSettings.value.rank_top_1) },
|
||||
{ name: `前${scoreSettings.value.rank_top_2}名`, start: 1, end: Number(scoreSettings.value.rank_top_2) },
|
||||
{ name: `前${scoreSettings.value.rank_top_3}名`, start: 1, end: Number(scoreSettings.value.rank_top_3) },
|
||||
],
|
||||
},
|
||||
})).data as any || []
|
||||
|
|
@ -258,39 +248,6 @@ const {
|
|||
staleTime: 30000,
|
||||
})
|
||||
|
||||
// 使用 TanStack Query 获取重点学生数据(基础显示)
|
||||
const {
|
||||
data: keyStudentsData,
|
||||
isLoading: isLoadingKeyStudents,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => [
|
||||
'key-students',
|
||||
homeStore.selectedClassId,
|
||||
homeStore.selectedExamId,
|
||||
homeStore.selectedGradeKey,
|
||||
selectedSubjectId.value,
|
||||
keyStudentType.value,
|
||||
keyStudentCount.value,
|
||||
]),
|
||||
queryFn: async () => {
|
||||
const response = await teacherAnalysisKeyStudentUsingPost({
|
||||
body: {
|
||||
class_key: homeStore.selectedClassId,
|
||||
exam_id: homeStore.selectedExamId,
|
||||
grade_key: homeStore.selectedGradeKey,
|
||||
subject_id: selectedSubjectId.value || undefined,
|
||||
emphasis_type: keyStudentType.value,
|
||||
page: 1,
|
||||
page_size: keyStudentCount.value,
|
||||
},
|
||||
}) as any
|
||||
|
||||
return response?.list || []
|
||||
},
|
||||
enabled: computed(() => isQueryEnabled.value && !showMoreStudents.value),
|
||||
staleTime: 30000,
|
||||
})
|
||||
|
||||
// 获取重点学生数据(分页加载)
|
||||
async function fetchKeyStudentsPaging(pageNo: number, pageSize: number) {
|
||||
if (!homeStore.selectedClassId || !homeStore.selectedExamId || !homeStore.selectedGradeKey) {
|
||||
|
|
@ -307,7 +264,7 @@ async function fetchKeyStudentsPaging(pageNo: number, pageSize: number) {
|
|||
subject_id: selectedSubjectId.value || undefined,
|
||||
emphasis_type: keyStudentType.value,
|
||||
page: pageNo,
|
||||
page_size: pageSize,
|
||||
page_size: Number(pageSize),
|
||||
},
|
||||
}) as any
|
||||
|
||||
|
|
@ -331,7 +288,7 @@ function goBack() {
|
|||
|
||||
// 考试选择变化处理
|
||||
function handleExamChange(event: { value: string | number, selectedItem: Record<string, any> }) {
|
||||
selectedExamId.value = typeof event.value === 'string' ? Number.parseInt(event.value) : event.value
|
||||
homeStore.selectedExamId = typeof event.value === 'string' ? Number.parseInt(event.value) : event.value
|
||||
}
|
||||
|
||||
// 科目切换处理
|
||||
|
|
@ -397,25 +354,11 @@ function toggleKeyStudentType(type: 'up' | 'down') {
|
|||
}
|
||||
}
|
||||
|
||||
// 显示更多进步/退步学生
|
||||
function handleShowMoreKeyStudents() {
|
||||
if (!homeStore.selectedClassId || !homeStore.selectedExamId || !homeStore.selectedGradeKey) {
|
||||
uni.showToast({
|
||||
title: '请先选择班级和考试',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
showMoreStudents.value = true
|
||||
setTimeout(() => {
|
||||
keyStudentsPaging.value?.reload()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 收起更多学生列表
|
||||
function handleHideMoreKeyStudents() {
|
||||
showMoreStudents.value = false
|
||||
// 清空列表数据
|
||||
keyStudentsList.value = []
|
||||
}
|
||||
|
||||
// 班级选择变化处理
|
||||
|
|
@ -423,14 +366,23 @@ function handleClassChange({ value }: { value: string[] }) {
|
|||
selectedClassIds.value = value
|
||||
}
|
||||
|
||||
// 加载状态
|
||||
const loading = computed(() =>
|
||||
isLoadingStats.value || isLoadingComparison.value || isLoadingRank.value || isLoadingKeyStudents.value,
|
||||
)
|
||||
whenever(() => [selectedSubjectId.value, homeStore.selectedExamId, keyStudentType.value, keyStudentCount.value], () => {
|
||||
keyStudentsPaging.value?.reload()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="score-page min-h-screen bg-slate-50">
|
||||
<!-- z-paging 包裹整个内容 -->
|
||||
<z-paging
|
||||
ref="keyStudentsPaging"
|
||||
v-model="keyStudentsList"
|
||||
:default-page-size="keyStudentCount"
|
||||
:refresher-enabled="true"
|
||||
:auto="true"
|
||||
@query="fetchKeyStudentsPaging"
|
||||
>
|
||||
<template #top>
|
||||
<!-- 导航栏 -->
|
||||
<wd-navbar placeholder fixed>
|
||||
<template #left>
|
||||
|
|
@ -442,8 +394,8 @@ const loading = computed(() =>
|
|||
<div class="title items-center">
|
||||
<wd-drop-menu>
|
||||
<wd-drop-menu-item
|
||||
v-model="selectedExamId"
|
||||
:options="examOptions"
|
||||
v-model="homeStore.selectedExamId"
|
||||
:options="homeStore.examOptions"
|
||||
@change="handleExamChange"
|
||||
/>
|
||||
</wd-drop-menu>
|
||||
|
|
@ -465,6 +417,7 @@ const loading = computed(() =>
|
|||
:title="tab.title"
|
||||
/>
|
||||
</wd-tabs>
|
||||
</template>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="p-4 space-y-4">
|
||||
|
|
@ -691,7 +644,7 @@ const loading = computed(() =>
|
|||
<!-- 学生数据列表 -->
|
||||
<view class="space-y-2">
|
||||
<view
|
||||
v-for="(student, index) in keyStudentsData"
|
||||
v-for="(student, index) in keyStudentsList"
|
||||
:key="index"
|
||||
class="grid grid-cols-3 gap-2 border-b border-gray-100 py-2 text-center last:border-b-0"
|
||||
>
|
||||
|
|
@ -707,81 +660,12 @@ const loading = computed(() =>
|
|||
</view>
|
||||
|
||||
<!-- 暂无数据提示 -->
|
||||
<view v-if="!keyStudentsData || keyStudentsData.length === 0" class="py-8 text-center">
|
||||
<view v-if="!keyStudentsList || keyStudentsList.length === 0" class="py-8 text-center">
|
||||
<text class="text-sm text-gray-500">暂无{{ keyStudentType === 'up' ? '进步' : '退步' }}学生数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 显示更多按钮 -->
|
||||
<view v-if="keyStudentsData && keyStudentsData.length > 0 && !showMoreStudents" class="mt-4">
|
||||
<wd-button
|
||||
type="info"
|
||||
size="medium"
|
||||
class="w-full"
|
||||
@click="handleShowMoreKeyStudents"
|
||||
>
|
||||
显示更多{{ keyStudentType === 'up' ? '进步' : '退步' }}学生
|
||||
</wd-button>
|
||||
</view>
|
||||
|
||||
<!-- 收起按钮 -->
|
||||
<view v-if="showMoreStudents" class="mt-4">
|
||||
<wd-button
|
||||
type="info"
|
||||
size="medium"
|
||||
class="w-full"
|
||||
@click="handleHideMoreKeyStudents"
|
||||
>
|
||||
收起列表
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 更多学生列表(z-paging) -->
|
||||
<view v-if="showMoreStudents" class="rounded-xl bg-white shadow-sm">
|
||||
<view class="border-b border-gray-100 p-4">
|
||||
<text class="text-lg text-slate-800 font-semibold">
|
||||
{{ keyStudentType === 'up' ? '进步' : '退步' }}学生详细列表
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<z-paging
|
||||
ref="keyStudentsPaging"
|
||||
v-model="keyStudentsList"
|
||||
:default-page-size="pageSize"
|
||||
:auto="false"
|
||||
@query="fetchKeyStudentsPaging"
|
||||
>
|
||||
<!-- 表头 -->
|
||||
<view class="border-b border-gray-200 p-4">
|
||||
<view class="grid grid-cols-3 gap-2 text-center">
|
||||
<text class="text-sm text-gray-600 font-medium">姓名</text>
|
||||
<text class="text-sm text-gray-600 font-medium">本次排名</text>
|
||||
<text class="text-sm text-gray-600 font-medium">
|
||||
{{ keyStudentType === 'up' ? '进步' : '退步' }}名次
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学生数据列表 -->
|
||||
<view class="divide-y divide-gray-100">
|
||||
<view
|
||||
v-for="(student, index) in keyStudentsList"
|
||||
:key="index"
|
||||
class="grid grid-cols-3 gap-2 p-4 text-center"
|
||||
>
|
||||
<text class="text-ellipsis text-sm text-gray-800 font-medium">{{ student.student_name || '-' }}</text>
|
||||
<text class="text-ellipsis text-sm text-gray-800">{{ student.class_rank || '-' }}</text>
|
||||
<text
|
||||
class="text-ellipsis text-sm font-medium"
|
||||
:class="keyStudentType === 'up' ? 'text-green-600' : 'text-red-600'"
|
||||
>
|
||||
{{ keyStudentType === 'up' ? '+' : '-' }}{{ student.class_diff_rank || 0 }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 整体概况设置弹窗 -->
|
||||
<OverviewSettingsDialog
|
||||
|
|
@ -796,14 +680,6 @@ const loading = computed(() =>
|
|||
:settings="scoreSettings"
|
||||
@confirm="confirmRankSettings"
|
||||
/>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<!-- <view v-if="loading" class="fixed inset-0 z-50 flex items-center justify-center bg-black/20">
|
||||
<view class="rounded-xl bg-white p-4">
|
||||
<wd-loading size="24" />
|
||||
<text class="mt-2 block text-sm text-gray-600">加载中...</text>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { whenever } from '@vueuse/core'
|
|||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { teacherScoreAnalysisApi } from '@/api'
|
||||
import { useUserStorage } from '@/composables/useUserStorage'
|
||||
|
||||
export interface SelectOption {
|
||||
label: string
|
||||
|
|
@ -30,11 +31,11 @@ export const useHomeStore = defineStore(
|
|||
const classList = ref<ClassItem[]>([])
|
||||
const classGradeMap = ref<Map<number, number>>(new Map())
|
||||
|
||||
// 已选择的数据
|
||||
const selectedClassId = ref<number | null>(null)
|
||||
const selectedExamId = ref<number | null>(null)
|
||||
const selectedSubjectId = ref<number | null>(null)
|
||||
const selectedExamSubjectId = ref<number | null>(null)
|
||||
// 已选择的数据(使用 useUserStorage 实现基于用户的持久化)
|
||||
const selectedClassId = useUserStorage<number>('home_selectedClassId', 0)
|
||||
const selectedExamId = useUserStorage<number>('home_selectedExamId', 0)
|
||||
const selectedSubjectId = useUserStorage<number>('home_selectedSubjectId', 0)
|
||||
const selectedExamSubjectId = useUserStorage<number>('home_selectedExamSubjectId', 0)
|
||||
const selectedGradeKey = computed(() => classGradeMap.value.get(selectedClassId.value || 0) || null)
|
||||
|
||||
// 计算属性:考试选项
|
||||
|
|
|
|||
Loading…
Reference in New Issue