587 lines
19 KiB
Vue
587 lines
19 KiB
Vue
<template>
|
||
<view class="profile-container">
|
||
<!-- 顶部用户信息区域 -->
|
||
<view class="profile-header">
|
||
<view class="header-bg"></view>
|
||
<view class="user-info-section">
|
||
<view class="avatar-wrapper">
|
||
<view class="avatar">
|
||
<text class="avatar-text">{{ avatarText }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="user-details">
|
||
<text class="username">{{ userInfo.realName || userInfo.username }}</text>
|
||
<view class="user-meta">
|
||
<text class="role-tag">{{ roleText }}</text>
|
||
<text class="user-id" v-if="userInfo.username">ID: {{ userInfo.username }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 学习记录统计卡片(仅学生端) -->
|
||
<view class="learning-stats-section" v-if="isStudent">
|
||
<view class="stats-card">
|
||
<view class="stat-item">
|
||
<text class="stat-value">{{ todayStudyText }}</text>
|
||
<text class="stat-label">今日学习</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-value">{{ weekStudyText }}</text>
|
||
<text class="stat-label">本周学习</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-value">{{ totalDaysText }}</text>
|
||
<text class="stat-label">累计学习</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 功能菜单区域 -->
|
||
<view class="menu-sections">
|
||
<!-- 学生端:学习相关 -->
|
||
<view class="menu-section" v-if="isStudent">
|
||
<view class="section-title">学习中心</view>
|
||
<view class="menu-card">
|
||
<view class="menu-item" @click="goToExamList">
|
||
<view class="menu-icon-wrapper exam">
|
||
<text class="menu-icon">📝</text>
|
||
</view>
|
||
<view class="menu-content">
|
||
<text class="menu-text">我的考试</text>
|
||
<text class="menu-desc">查看考试列表</text>
|
||
</view>
|
||
<text class="menu-arrow">›</text>
|
||
</view>
|
||
<view class="menu-item" @click="goToScoreList">
|
||
<view class="menu-icon-wrapper score">
|
||
<text class="menu-icon">📊</text>
|
||
</view>
|
||
<view class="menu-content">
|
||
<text class="menu-text">我的成绩</text>
|
||
<text class="menu-desc">查看成绩详情</text>
|
||
</view>
|
||
<text class="menu-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 其他功能 -->
|
||
<view class="menu-section">
|
||
<view class="section-title">其他</view>
|
||
<view class="menu-card">
|
||
<view class="menu-item" @click="handleLogout">
|
||
<view class="menu-icon-wrapper logout">
|
||
<text class="menu-icon">🚪</text>
|
||
</view>
|
||
<view class="menu-content">
|
||
<text class="menu-text">退出登录</text>
|
||
<text class="menu-desc">安全退出当前账号</text>
|
||
</view>
|
||
<text class="menu-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { mapGetters, mapActions } from 'vuex'
|
||
import { getMyRecords, getMyStatistics } from '@/api/study/learningRecord.js'
|
||
import { getMyCourses } from '@/api/study/course.js'
|
||
|
||
export default {
|
||
computed: {
|
||
...mapGetters('auth', ['userInfo', 'userRole']),
|
||
isStudent() {
|
||
return this.userRole === 'student'
|
||
},
|
||
roleText() {
|
||
const roleMap = {
|
||
'admin': '管理员',
|
||
'student': '学员'
|
||
}
|
||
return roleMap[this.userInfo.role] || '学员'
|
||
},
|
||
avatarText() {
|
||
const name = this.userInfo.realName || this.userInfo.username || '用'
|
||
return name.length > 0 ? name.charAt(0).toUpperCase() : '用'
|
||
},
|
||
totalDurationText() {
|
||
return this.formatDuration(this.totalDuration)
|
||
},
|
||
todayStudyText() {
|
||
// 今日学习时长(分钟)
|
||
const minutes = Math.floor(this.todayDuration / 60)
|
||
return minutes > 0 ? `${minutes} min` : '0 min'
|
||
},
|
||
weekStudyText() {
|
||
// 本周学习时长(小时)
|
||
const hours = Math.floor(this.weekDuration / 3600)
|
||
return hours > 0 ? `${hours} h` : '0 h'
|
||
},
|
||
totalDaysText() {
|
||
// 累计学习天数
|
||
return `${this.totalDays} days`
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
totalDuration: 0,
|
||
totalCount: 0,
|
||
completedCourses: 0,
|
||
todayDuration: 0, // 今日学习时长(秒)
|
||
weekDuration: 0, // 本周学习时长(秒)
|
||
totalDays: 0 // 累计学习天数
|
||
}
|
||
},
|
||
onLoad() {
|
||
// 如果是学生端,加载学习记录统计
|
||
if (this.isStudent) {
|
||
this.loadLearningStats()
|
||
}
|
||
},
|
||
onShow() {
|
||
// 通知底部导航栏更新
|
||
uni.$emit('tabbar-update')
|
||
// 如果是学生端,刷新学习记录统计
|
||
if (this.isStudent) {
|
||
this.loadLearningStats()
|
||
}
|
||
},
|
||
methods: {
|
||
...mapActions('auth', ['logout']),
|
||
async loadLearningStats() {
|
||
try {
|
||
// 获取学习统计信息(从后台接口)
|
||
const statsResponse = await getMyStatistics()
|
||
if (statsResponse.code === 200) {
|
||
const stats = statsResponse.data || {}
|
||
// 设置统计数据
|
||
this.todayDuration = stats.todayDuration || 0 // 今日学习时长(秒)
|
||
this.weekDuration = stats.weekDuration || 0 // 本周学习时长(秒)
|
||
this.totalDays = stats.totalDays || 0 // 累计学习天数
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('加载学习统计失败:', error)
|
||
// 如果接口调用失败,设置默认值
|
||
this.todayDuration = 0
|
||
this.weekDuration = 0
|
||
this.totalDays = 0
|
||
}
|
||
},
|
||
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}分钟`
|
||
},
|
||
goToExamList() {
|
||
uni.navigateTo({
|
||
url: '/pages/exam/list'
|
||
})
|
||
},
|
||
goToScoreList() {
|
||
uni.navigateTo({
|
||
url: '/pages/score/list'
|
||
})
|
||
},
|
||
async handleLogout() {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要退出登录吗?',
|
||
confirmColor: 'rgb(55 140 224)',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
uni.showLoading({ title: '退出中...' })
|
||
try {
|
||
await this.logout()
|
||
uni.hideLoading()
|
||
uni.reLaunch({
|
||
url: '/pages/login/login'
|
||
})
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '退出失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.profile-container {
|
||
width: 100%;
|
||
min-height: 100vh;
|
||
background: #f5f7fa;
|
||
padding-bottom: 120rpx; // 为底部导航栏留出空间
|
||
box-sizing: border-box;
|
||
|
||
@media (min-width: 768px) {
|
||
padding-bottom: 140rpx;
|
||
}
|
||
}
|
||
|
||
// 顶部用户信息区域(钉钉风格)
|
||
.profile-header {
|
||
position: relative;
|
||
background: linear-gradient(135deg, rgb(55 140 224) 0%, rgb(45 120 200) 100%);
|
||
padding: 60rpx 30rpx 50rpx;
|
||
padding-top: calc(var(--status-bar-height) + 60rpx);
|
||
overflow: hidden;
|
||
|
||
@media (min-width: 768px) {
|
||
padding: 80rpx 60rpx 70rpx;
|
||
padding-top: calc(var(--status-bar-height) + 80rpx);
|
||
}
|
||
|
||
.header-bg {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="20" cy="20" r="2" fill="rgba(255,255,255,0.1)"/><circle cx="80" cy="40" r="1.5" fill="rgba(255,255,255,0.1)"/><circle cx="40" cy="70" r="1" fill="rgba(255,255,255,0.1)"/></svg>');
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.user-info-section {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
z-index: 1;
|
||
|
||
.avatar-wrapper {
|
||
margin-right: 24rpx;
|
||
|
||
@media (min-width: 768px) {
|
||
margin-right: 40rpx;
|
||
}
|
||
|
||
.avatar {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
backdrop-filter: blur(10rpx);
|
||
border: 4rpx solid rgba(255, 255, 255, 0.3);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
@media (min-width: 768px) {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
border: 6rpx solid rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.avatar-text {
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 64rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.user-details {
|
||
flex: 1;
|
||
|
||
.username {
|
||
display: block;
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
margin-bottom: 16rpx;
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 48rpx;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
}
|
||
|
||
.user-meta {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
|
||
@media (min-width: 768px) {
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.role-tag {
|
||
display: inline-block;
|
||
padding: 6rpx 16rpx;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 20rpx;
|
||
font-size: 22rpx;
|
||
color: #fff;
|
||
backdrop-filter: blur(10rpx);
|
||
|
||
@media (min-width: 768px) {
|
||
padding: 8rpx 20rpx;
|
||
font-size: 26rpx;
|
||
border-radius: 24rpx;
|
||
}
|
||
}
|
||
|
||
.user-id {
|
||
font-size: 24rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 28rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 学习记录统计卡片
|
||
.learning-stats-section {
|
||
margin: 20rpx 30rpx 30rpx;
|
||
|
||
@media (min-width: 768px) {
|
||
margin: 30rpx 40rpx 40rpx;
|
||
}
|
||
|
||
.stats-card {
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 40rpx 30rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 20rpx;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||
|
||
@media (min-width: 768px) {
|
||
border-radius: 24rpx;
|
||
padding: 50rpx 40rpx;
|
||
gap: 30rpx;
|
||
}
|
||
|
||
.stat-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20rpx 10rpx;
|
||
text-align: center;
|
||
background: transparent;
|
||
|
||
@media (min-width: 768px) {
|
||
padding: 24rpx 16rpx;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 40rpx;
|
||
font-weight: 700;
|
||
color: #1a1a1a;
|
||
margin-bottom: 12rpx;
|
||
line-height: 1.2;
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 48rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
font-weight: 400;
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 28rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 功能菜单区域
|
||
.menu-sections {
|
||
margin: 0 30rpx 30rpx;
|
||
|
||
@media (min-width: 768px) {
|
||
margin: 0 40rpx 40rpx;
|
||
}
|
||
|
||
.menu-section {
|
||
margin-bottom: 24rpx;
|
||
|
||
@media (min-width: 768px) {
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
margin-bottom: 16rpx;
|
||
padding: 0 4rpx;
|
||
font-weight: 500;
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 28rpx;
|
||
margin-bottom: 20rpx;
|
||
padding: 0 8rpx;
|
||
}
|
||
}
|
||
|
||
.menu-card {
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||
|
||
@media (min-width: 768px) {
|
||
border-radius: 24rpx;
|
||
}
|
||
|
||
.menu-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 28rpx 30rpx;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
transition: background-color 0.25s ease, transform 0.2s ease;
|
||
will-change: transform;
|
||
position: relative;
|
||
|
||
@media (min-width: 768px) {
|
||
padding: 36rpx 40rpx;
|
||
}
|
||
|
||
&:active {
|
||
background-color: #f8f9fa;
|
||
transform: translate3d(4rpx, 0, 0);
|
||
}
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.menu-icon-wrapper {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 16rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 24rpx;
|
||
flex-shrink: 0;
|
||
transition: transform 0.3s ease;
|
||
|
||
@media (min-width: 768px) {
|
||
width: 88rpx;
|
||
height: 88rpx;
|
||
border-radius: 20rpx;
|
||
margin-right: 32rpx;
|
||
}
|
||
|
||
&.record {
|
||
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
|
||
}
|
||
|
||
&.exam {
|
||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
|
||
}
|
||
|
||
&.score {
|
||
background: linear-gradient(135deg, #4ecdc4 0%, #44a08d 100%);
|
||
}
|
||
|
||
&.voice {
|
||
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
||
}
|
||
|
||
&.logout {
|
||
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
|
||
}
|
||
|
||
.menu-icon {
|
||
font-size: 36rpx;
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 44rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.menu-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.menu-text {
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
color: #1a1a1a;
|
||
margin-bottom: 6rpx;
|
||
transition: color 0.3s;
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 32rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
}
|
||
|
||
.menu-desc {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 26rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.menu-arrow {
|
||
font-size: 32rpx;
|
||
color: #d9d9d9;
|
||
margin-left: 16rpx;
|
||
flex-shrink: 0;
|
||
transition: transform 0.3s ease, color 0.3s;
|
||
|
||
@media (min-width: 768px) {
|
||
font-size: 36rpx;
|
||
margin-left: 24rpx;
|
||
}
|
||
}
|
||
|
||
&:active {
|
||
.menu-icon-wrapper {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.menu-text {
|
||
color: rgb(55 140 224);
|
||
}
|
||
|
||
.menu-arrow {
|
||
transform: translateX(4rpx);
|
||
color: rgb(55 140 224);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
|