页面修改
This commit is contained in:
535
src/pages/dataAnalyze/grades.vue
Normal file
535
src/pages/dataAnalyze/grades.vue
Normal file
@@ -0,0 +1,535 @@
|
||||
<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="hint-state" v-if="!selectedProjectId">
|
||||
<view class="hint-icon">
|
||||
<u-icon name="list" size="80" color="#eb2f96"></u-icon>
|
||||
</view>
|
||||
<text class="hint-text">请先选择项目</text>
|
||||
</view>
|
||||
|
||||
<!-- 无数据时的提示 -->
|
||||
<view class="hint-state" v-else-if="gradeList.length === 0">
|
||||
<view class="hint-icon">
|
||||
<u-icon name="file-text" size="80" color="#eb2f96"></u-icon>
|
||||
</view>
|
||||
<text class="hint-text">该项目暂无排名数据</text>
|
||||
</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-avatar">
|
||||
<text class="avatar-text">{{ item.name.charAt(0) }}</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: 16rpx;
|
||||
padding: 28rpx;
|
||||
box-shadow: 0 2rpxrpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.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: #f0f0f0;
|
||||
border-color: #eb2f96;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 提示状态容器 ==================== */
|
||||
.hint-state {
|
||||
margin: 0 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 80rpx 40rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpxrpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.hint-icon {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 排名列表区域 ==================== */
|
||||
.ranking-section {
|
||||
margin: 0 20rpx;
|
||||
|
||||
// 统计卡片
|
||||
.stats-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
box-shadow: 0 2rpxrpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.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: #eb2f96;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 1rpx;
|
||||
height: 50rpx;
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
// 排名列表容器
|
||||
.ranking-list {
|
||||
|
||||
// 排名项
|
||||
.ranking-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
box-shadow: 0 2rpxrpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 16rpx rgba(235, 47, 150, 0.15);
|
||||
}
|
||||
|
||||
// 前三名特殊样式
|
||||
&.top-three {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 6rpx;
|
||||
background: linear-gradient(180deg, #eb2f96 0%, #f759ab 100%);
|
||||
border-radius: 16rpx 0 0 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 排名徽章
|
||||
.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 2rpxrpx 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 2rpxrpx 8rpx 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 2rpxrpx 8rpx rgba(192, 192, 192, 0.4);
|
||||
|
||||
.rank-number {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&.rank-3 {
|
||||
background: linear-gradient(135deg, #cd7f32 0%, #e6963d 100%);
|
||||
box-shadow: 0 2rpxrpx 8rpx rgba(205, 127, 50, 0.4);
|
||||
|
||||
.rank-number {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 学生头像
|
||||
.student-avatar {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
background: linear-gradient(135deg, #eb2f96 0%, #f759ab 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 2rpxrpx 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: #eb2f96;
|
||||
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>
|
||||
Reference in New Issue
Block a user