Files
swimmingUni/src/pages/dataAnalyze/grades.vue
2026-03-31 08:40:11 +08:00

529 lines
14 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.
<template>
<view class="grades-container">
<!-- 页面标题区域 -->
<view class="header-section">
<view class="header-title">
<text class="title">成绩排名</text>
<text class="subtitle">学生最佳成绩排名</text>
</view>
</view>
<!-- ==================== 项目选择区域 ==================== -->
<view class="project-select-section">
<view class="section-header">
<text class="section-title">选择项目</text>
</view>
<!-- 项目选择器 -->
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex"
@change="handleProjectChange">
<view class="picker-wrapper">
<text class="picker-text">{{ selectedProjectName }}</text>
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
</view>
</picker>
</view>
<!-- ==================== 排名列表区域 ==================== -->
<!-- 仅在选择了项目且有数据时显示 -->
<view class="ranking-section" v-if="selectedProjectId && gradeList.length > 0">
<!-- 排名统计卡片 -->
<view class="stats-card">
<view class="stat-item">
<text class="stat-label">对比项目</text>
<text class="stat-value">{{ selectedProjectName }}</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-label">参与人数</text>
<text class="stat-value">{{ gradeList.length }}</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-label">最高速度</text>
<text class="stat-value">{{ maxSpeed }}m/s</text>
</view>
</view>
<!-- 排名列表 -->
<view class="ranking-list">
<view v-for="(item, index) in gradeList" :key="item.id"
:class="['ranking-item', { 'top-three': index < 3 }]" @click="handleRankingClick(item)">
<!-- 排名徽章 -->
<view class="rank-badge" :class="`rank-${index + 1}`">
<text class="rank-number">{{ index + 1 }}</text>
</view>
<!-- 学生信息 -->
<view class="student-info">
<text class="student-name">{{ item.name }}</text>
<text class="student-speed">最快速度{{ item.bestSpeed }}m/s</text>
</view>
<!-- 成绩详情 -->
<view class="speed-detail">
<text class="detail-text">用时{{ item.bestTime }}s</text>
<text class="detail-text">日期{{ item.recordDate }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { onShow, onLoad } from "@dcloudio/uni-app"
import { Service } from '@/Service/Service'
import { ref, computed } from 'vue'
// ==================== 响应式数据 - 项目相关 ====================
// 项目列表数据
const projectList = ref([
{ id: '1', name: '100米自由泳' },
{ id: '2', name: '50米自由泳' },
{ id: '3', name: '200米自由泳' },
{ id: '4', name: '100米蛙泳' }
])
// 项目选择器选项(用于 picker 组件)
const projectOptions = ref(projectList.value)
// 当前选中的项目索引(用于 picker 组件的显示)
const selectedProjectIndex = ref(-1)
// 当前选中的项目 ID
const selectedProjectId = ref('')
// 当前选中的项目名称(用于显示)
const selectedProjectName = computed(() => {
if (selectedProjectIndex.value === -1) {
return '请选择项目'
}
return projectList.value[selectedProjectIndex.value]?.name || '请选择项目'
})
// ==================== TypeScript 接口定义 ====================
/**
* 学生成绩接口
* 定义学生的成绩和排名信息
*/
interface StudentGrade {
id : string // 学生 ID
name : string // 学生姓名
bestSpeed : number // 最快速度m/s
bestTime : number // 最快用时(秒)
recordDate : string // 记录日期
studentId : string // 学生 ID用于数据关联
}
// ==================== 响应式数据 - 排名相关 ====================
// 排名列表数据
const gradeList = ref<StudentGrade[]>([])
// 最高速度(计算属性)
const maxSpeed = computed(() => {
if (gradeList.value.length === 0) return 0
return Math.max(...gradeList.value.map(item => item.bestSpeed)).toFixed(2)
})
// ==================== 模拟数据 ====================
/**
* 模拟的学生成绩数据(按项目分组)
* 键为项目 ID值为该项目的学生成绩列表
*/
const mockStudentGrades : Record<string, StudentGrade[]> = {
'1': [
{ id: 'g1', name: '王小明', bestSpeed: 3.89, bestTime: 25.72, recordDate: '03-24', studentId: 's3' },
{ id: 'g2', name: '张小明', bestSpeed: 3.97, bestTime: 25.18, recordDate: '03-24', studentId: 's1' },
{ id: 'g3', name: '赵小芳', bestSpeed: 3.03, bestTime: 32.96, recordDate: '03-14', studentId: 's4' },
{ id: 'g4', name: '李小红', bestSpeed: 3.38, bestTime: 29.58, recordDate: '03-24', studentId: 's2' },
{ id: 'g5', name: '陈小刚', bestSpeed: 2.82, bestTime: 35.46, recordDate: '03-19', studentId: 's5' }
],
'2': [
{ id: 'g6', name: '杨小龙', bestSpeed: 2.05, bestTime: 24.39, recordDate: '03-19', studentId: 's8' },
{ id: 'g7', name: '刘小华', bestSpeed: 2.11, bestTime: 23.70, recordDate: '03-19', studentId: 's6' },
{ id: 'g8', name: '张小丽', bestSpeed: 1.91, bestTime: 26.18, recordDate: '03-16', studentId: 's7' }
],
'3': [
{ id: 'g9', name: '吴小西', bestSpeed: 1.59, bestTime: 125.79, recordDate: '03-16', studentId: 's10' },
{ id: 'g10', name: '黄小东', bestSpeed: 1.61, bestTime: 124.23, recordDate: '03-14', studentId: 's9' },
{ id: 'g11', name: '周小南', bestSpeed: 0.00, bestTime: 0, recordDate: '-', studentId: 's11' },
{ id: 'g12', name: '徐小北', bestSpeed: 0.00, bestTime: 0, recordDate: '-', studentId: 's12' }
],
'4': [
{ id: 'g13', name: '马小兵', bestSpeed: 1.22, bestTime: 81.97, recordDate: '03-14', studentId: 's13' },
{ id: 'g14', name: '朱小红', bestSpeed: 1.13, bestTime: 88.47, recordDate: '03-12', studentId: 's14' }
]
}
// ==================== 生命周期钩子 ====================
/**
* 页面加载时触发
* 在页面初始化时执行,只执行一次
*/
onLoad(() => {
// 初始化数据
loadProjectData()
})
/**
* 页面显示时触发
* 每次页面从后台切换到前台时执行
*/
onShow(() => {
// TODO: 如果需要在页面显示时刷新数据,可以在这里添加逻辑
})
// ==================== 业务逻辑方法 - 项目相关 ====================
/**
* 加载项目数据
* 从后端 API 获取项目列表
*/
const loadProjectData = () => {
// TODO: 调用后端 API 获取项目列表
// 示例代码:
// Service.Request('/api/projects', 'GET').then(res => {
// projectList.value = res.data
// projectOptions.value = res.data
// })
}
/**
* 处理项目选择变化事件
* 当用户选择项目时触发
* @param e picker 选择事件对象,包含 detail.value 属性表示选中的索引
*/
const handleProjectChange = (e : any) => {
// 获取选中的项目索引
const index = e.detail.value
// 更新选中项目的索引
selectedProjectIndex.value = index
// 获取选中的项目
const selectedProject = projectList.value[index]
if (selectedProject) {
// 更新选中项目的 ID
selectedProjectId.value = selectedProject.id
// 加载该项目的排名数据
loadProjectGrades(selectedProject.id)
}
// TODO: 实际项目中应该调用 API 获取该项目的排名数据
}
/**
* 加载项目排名数据
* 根据项目 ID 获取该项目的学生成绩排名列表
* @param projectId 项目 ID
*/
const loadProjectGrades = (projectId : string) => {
// 从模拟数据中获取排名列表
if (mockStudentGrades[projectId]) {
// 按最快速度从高到低排序
gradeList.value = [...mockStudentGrades[projectId]].sort((a, b) => b.bestSpeed - a.bestSpeed)
} else {
gradeList.value = []
}
}
// ==================== 业务逻辑方法 - 排名相关 ====================
/**
* 处理排名项点击事件
* 点击某排名项查看详情
* @param item 排名项数据
*/
const handleRankingClick = (item : StudentGrade) => {
console.log('点击了排名项:', item)
// TODO: 跳转到学生详情页面
// Service.GoPage('/pages/student/detail', { studentId: item.studentId })
}
</script>
<style lang="scss" scoped>
// 页面背景色设置
page {
background-color: #f5f5f5;
}
// 页面容器
.grades-container {
min-height: 100vh;
padding-bottom: 40rpx;
}
/* ==================== 页面标题区域 ==================== */
.header-section {
background-color: #fff;
padding: 32rpx 28rpx 24rpx;
margin-bottom: 20rpx;
.header-title {
.title {
font-size: 36rpx;
font-weight: 700;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.subtitle {
font-size: 24rpx;
color: #999;
}
}
}
/* ==================== 项目选择区域 ==================== */
.project-select-section {
background-color: #fff;
margin: 0 20rpx 20rpx;
border-radius: 20rpx;
padding: 28rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
transition: all 0.3s ease;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6rpx;
// background: linear-gradient(180deg, #1890ff 0%, #096dd9 100%);
border-radius: 20rpx 0 0 20rpx;
}
.picker-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx;
background-color: #f8f8f8;
border-radius: 12rpx;
border: 1rpx solid #e8e8e8;
transition: all 0.3s ease;
&:active {
background-color: #fff0f5;
border-color: #1890ff;
transform: scale(0.98);
}
.picker-text {
font-size: 28rpx;
color: #333;
}
}
}
/* ==================== 排名列表区域 ==================== */
.ranking-section {
margin: 0 20rpx;
// 统计卡片
.stats-card {
background-color: #fff;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
justify-content: space-around;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6rpx;
// background: linear-gradient(180deg, #1890ff 0%, #096dd9 100%);
border-radius: 20rpx 0 0 20rpx;
}
.stat-item {
text-align: center;
.stat-label {
font-size: 24rpx;
color: #999;
display: block;
margin-bottom: 8rpx;
}
.stat-value {
font-size: 32rpx;
font-weight: 700;
color: #1890ff;
}
}
.stat-divider {
width: 1rpx;
height: 50rpx;
background-color: #eee;
}
}
// 排名列表容器
.ranking-list {
// 排名项
.ranking-item {
background-color: #fff;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 16rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6rpx;
// background: linear-gradient(180deg, #1890ff 0%, #096dd9 100%);
opacity: 0;
transition: opacity 0.3s ease;
border-radius: 20rpx 0 0 20rpx;
}
&:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(235, 47, 150, 0.15);
&::before {
opacity: 1;
}
}
// 排名徽章
.rank-badge {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
background-color: #f0f0f0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
.rank-number {
font-size: 24rpx;
font-weight: 700;
color: #999;
}
// 前三名特殊颜色
&.rank-1 {
background: linear-gradient(135deg, #ffd700 0%, #ffec3d 100%);
box-shadow: 0 2rpx 8.6rpx rgba(255, 215, 0, 0.4);
.rank-number {
color: #fff;
font-size: 28rpx;
}
}
&.rank-2 {
background: linear-gradient(135deg, #c0c0c0 0%, #d9d9d9 100%);
box-shadow: 0 2rpx 8rpx rgba(192, 192, 192, 0.4);
.rank-number {
color: #fff;
}
}
&.rank-3 {
background: linear-gradient(135deg, #cd7f32 0%, #e6963d 100%);
box-shadow: 0 2rpx 8rpx rgba(205, 127, 50, 0.4);
.rank-number {
color: #fff;
}
}
}
// 学生头像
.student-avatar {
width: 70rpx;
height: 70rpx;
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 2rpx 8rpx rgba(235, 47, 150, 0.3);
.avatar-text {
font-size: 28rpx;
font-weight: 700;
color: #fff;
}
}
// 学生信息
.student-info {
flex: 1;
min-width: 0;
.student-name {
font-size: 32rpx;
font-weight: 600;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.student-speed {
font-size: 26rpx;
color: #1890ff;
font-weight: 500;
}
}
// 成绩详情
.speed-detail {
display: flex;
align-items: center;
gap: 16rpx;
flex-shrink: 0;
.detail-text {
font-size: 24rpx;
color: #999;
background-color: #f5f5f5;
padding: 6rpx 12rpx;
border-radius: 6rpx;
}
}
}
}
}
</style>