guoyu/fronted_uniapp/pages/student/detail.vue

355 lines
11 KiB
Vue
Raw Normal View History

2025-12-03 18:58:36 +08:00
<template>
<view class="student-detail-container">
<view v-if="!loading && studentInfo" class="student-info-card">
<view class="student-header">
<view class="student-avatar-large">
<text class="avatar-text">{{ (studentInfo.nickName || studentInfo.userName || 'S').charAt(0) }}</text>
</view>
<view class="student-basic-info">
<text class="student-name">{{ studentInfo.nickName || studentInfo.userName }}</text>
<text class="student-id">学号{{ studentInfo.userName }}</text>
<text class="student-class" v-if="studentInfo.className">班级{{ studentInfo.className }}</text>
</view>
</view>
</view>
<!-- 成绩统计 -->
<view class="score-section">
<view class="section-title">成绩统计</view>
<view class="score-card">
<view class="score-item">
<text class="score-label">平均分</text>
<text class="score-value">{{ averageScore || '--' }}</text>
</view>
<view class="score-item">
<text class="score-label">考试次数</text>
<text class="score-value">{{ examCount || 0 }}</text>
</view>
</view>
</view>
<!-- 最近成绩 -->
<view class="recent-scores-section">
<view class="section-title">最近成绩</view>
<view class="score-list">
<view
v-for="score in recentScores"
:key="score.id"
class="score-item-card"
@click="goToScoreDetail(score)"
>
<view class="score-item-header">
<text class="score-exam-name">{{ score.examName }}</text>
<text class="score-value-text">{{ score.obtainedScore }}/{{ score.totalScore }}</text>
</view>
<view class="score-item-info">
<text class="score-time">{{ formatTime(score.submitTime || score.createTime) }}</text>
</view>
</view>
</view>
</view>
<!-- 学习记录 -->
<view class="learning-section">
<view class="section-title">学习记录</view>
<view class="learning-card">
<view class="learning-item">
<text class="learning-label">学习时长</text>
<text class="learning-value">{{ formatDuration(totalLearningTime) }}</text>
</view>
<view class="learning-item">
<text class="learning-label">学习课程数</text>
<text class="learning-value">{{ courseCount || 0 }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
import request from '@/utils/request.js'
export default {
data() {
return {
studentId: null,
studentInfo: null,
loading: false,
recentScores: [],
averageScore: null,
examCount: 0,
totalLearningTime: 0,
courseCount: 0
}
},
onLoad(options) {
if (options.id) {
this.studentId = parseInt(options.id)
this.loadStudentDetail()
}
},
methods: {
async loadStudentDetail() {
this.loading = true
try {
// 获取学生基本信息(从用户列表接口获取)
// 这里简化处理,实际应该调用获取单个用户信息的接口
this.studentInfo = {
userId: this.studentId,
userName: 'student' + this.studentId,
nickName: '学生' + this.studentId
}
// 获取学生成绩
await this.loadStudentScores()
// 获取学生学习记录
await this.loadLearningRecords()
} catch (error) {
console.error('加载学生详情失败', error)
uni.showToast({
title: error.message || '加载失败',
icon: 'none'
})
} finally {
this.loading = false
}
},
async loadStudentScores() {
try {
const response = await request.get('/study/score/list', {
studentId: this.studentId
})
if (response.code === 200) {
const scores = response.rows || response.data || []
this.recentScores = scores.slice(0, 5)
this.examCount = scores.length
// 计算平均分
if (scores.length > 0) {
const total = scores.reduce((sum, s) => sum + (parseFloat(s.obtainedScore) || 0), 0)
this.averageScore = (total / scores.length).toFixed(1)
}
}
} catch (error) {
console.error('加载学生成绩失败', error)
}
},
async loadLearningRecords() {
try {
const response = await request.get(`/study/learningRecord/student/${this.studentId}`, {})
if (response.code === 200) {
const records = response.data || []
this.courseCount = records.length
// 计算总学习时长
this.totalLearningTime = records.reduce((sum, r) => sum + (r.totalDuration || 0), 0)
}
} catch (error) {
console.error('加载学习记录失败', error)
}
},
goToScoreDetail(score) {
uni.navigateTo({
url: `/pages/score/detail?id=${score.id}${score.examId ? `&examId=${score.examId}` : ''}`
})
},
formatTime(timeStr) {
if (!timeStr) return ''
const date = new Date(timeStr)
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
return `${year}-${month}-${day}`
},
formatDuration(seconds) {
if (!seconds) return '0分钟'
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
if (hours > 0) {
return `${hours}小时${minutes}分钟`
}
return `${minutes}分钟`
}
}
}
</script>
<style lang="scss" scoped>
.student-detail-container {
padding: 30rpx;
background-color: #f5f7fa;
min-height: 100vh;
@media (min-width: 768px) {
padding: 60rpx;
max-width: 1200rpx;
margin: 0 auto;
}
}
.student-info-card {
background: #fff;
border-radius: 20rpx;
padding: 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.student-header {
display: flex;
align-items: center;
.student-avatar-large {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
background: linear-gradient(135deg, rgb(55 140 224) 0%, rgb(45 120 200) 100%);
display: flex;
align-items: center;
justify-content: center;
margin-right: 30rpx;
.avatar-text {
font-size: 48rpx;
color: #fff;
font-weight: bold;
}
}
.student-basic-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
.student-name {
font-size: 36rpx;
font-weight: bold;
color: #1a1a1a;
}
.student-id {
font-size: 28rpx;
color: #666;
}
.student-class {
font-size: 28rpx;
color: rgb(55 140 224);
}
}
}
}
.score-section,
.recent-scores-section,
.learning-section {
margin-bottom: 30rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #1a1a1a;
margin-bottom: 20rpx;
}
}
.score-card {
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
display: flex;
gap: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.score-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.score-label {
font-size: 26rpx;
color: #999;
margin-bottom: 12rpx;
}
.score-value {
font-size: 48rpx;
font-weight: bold;
color: rgb(55 140 224);
}
}
}
.score-list {
.score-item-card {
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.score-item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.score-exam-name {
font-size: 30rpx;
font-weight: bold;
color: #1a1a1a;
}
.score-value-text {
font-size: 32rpx;
font-weight: bold;
color: rgb(55 140 224);
}
}
.score-item-info {
.score-time {
font-size: 26rpx;
color: #999;
}
}
}
}
.learning-card {
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
display: flex;
gap: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.learning-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.learning-label {
font-size: 26rpx;
color: #999;
margin-bottom: 12rpx;
}
.learning-value {
font-size: 36rpx;
font-weight: bold;
color: rgb(55 140 224);
}
}
}
</style>