Initial commit of akmon project
This commit is contained in:
900
pages/sport/student/profile.uvue
Normal file
900
pages/sport/student/profile.uvue
Normal file
@@ -0,0 +1,900 @@
|
||||
<template>
|
||||
<scroll-view class="profile-page" direction="vertical">
|
||||
<!-- 头部背景 -->
|
||||
<view class="profile-header">
|
||||
<view class="header-bg"></view>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<view class="user-info">
|
||||
<view class="avatar-container">
|
||||
<image class="avatar" :src="userAvatar" mode="aspectFill" @click="changeAvatar"></image>
|
||||
<view class="avatar-edit">
|
||||
<text class="edit-icon"></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="user-details">
|
||||
<text class="username">{{ getUserName() }}</text>
|
||||
<text class="user-id">学号:{{ getUserId() }}</text>
|
||||
<text class="join-date">加入时间:{{ getJoinDate() }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 等级信息 -->
|
||||
<view class="level-info">
|
||||
<view class="level-badge">
|
||||
<text class="level-text">Lv.{{ getUserLevel() }}</text>
|
||||
</view>
|
||||
<view class="xp-info">
|
||||
<text class="xp-text">{{ getCurrentXP() }} / {{ getNextLevelXP() }} XP</text>
|
||||
<view class="xp-bar">
|
||||
<view class="xp-progress" :style="{ width: getXPProgress() + '%' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-grid">
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ totalTrainingDays }}</text>
|
||||
<text class="stat-label">训练天数</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ completedAssignments }}</text>
|
||||
<text class="stat-label">完成作业</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ totalTrainingTime }}</text>
|
||||
<text class="stat-label">训练时长(小时)</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ achievementCount }}</text>
|
||||
<text class="stat-label">获得成就</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 */
|
||||
<view class="menu-section">
|
||||
<view class="section-title">个人设置</view>
|
||||
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" @click="editProfile">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon"></text>
|
||||
<text class="menu-title">编辑资料</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="changePassword">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon"></text>
|
||||
<text class="menu-title">修改密码</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="notificationSettings">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon"></text>
|
||||
<text class="menu-title">通知设置</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="privacySettings">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">️</text>
|
||||
<text class="menu-title">隐私设置</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 训练偏好 -->
|
||||
<view class="menu-section">
|
||||
<view class="section-title">训练偏好</view>
|
||||
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" @click="goalSettings">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon"></text>
|
||||
<text class="menu-title">训练目标</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="reminderSettings">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">⏰</text>
|
||||
<text class="menu-title">训练提醒</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
<view class="menu-item" @click="favoriteExercises">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">❤️</text>
|
||||
<text class="menu-title">喜欢的运动</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="preferencesAnalytics">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon"></text>
|
||||
<text class="menu-title">偏好分析</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 数据管理 -->
|
||||
<view class="menu-section">
|
||||
<view class="section-title">数据管理</view>
|
||||
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" @click="deviceManagement">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon"></text>
|
||||
<text class="menu-title">设备管理</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="exportData">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon"></text>
|
||||
<text class="menu-title">导出数据</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="backupData">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">☁️</text>
|
||||
<text class="menu-title">数据备份</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="clearCache">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">️</text>
|
||||
<text class="menu-title">清除缓存</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 关于 -->
|
||||
<view class="menu-section">
|
||||
<view class="section-title">关于</view>
|
||||
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" @click="helpCenter">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">❓</text>
|
||||
<text class="menu-title">帮助中心</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="feedback">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon"></text>
|
||||
<text class="menu-title">意见反馈</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="aboutApp">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">ℹ️</text>
|
||||
<text class="menu-title">关于应用</text>
|
||||
</view>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view class="logout-section">
|
||||
<button class="logout-btn" @click="logout">退出登录</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, computed, onUnmounted } from 'vue'
|
||||
import { onLoad, onResize } from '@dcloudio/uni-app'
|
||||
import { formatDateTime } from '../types.uts'
|
||||
import supaClient from '@/components/supadb/aksupainstance.uts'
|
||||
import { state, getCurrentUserId, setUserProfile } from '@/utils/store.uts'
|
||||
import type { UserProfile } from '@/pages/user/types.uts'
|
||||
|
||||
const userId = ref('')
|
||||
|
||||
// Responsive state - using onResize for dynamic updates
|
||||
const screenWidth = ref<number>(uni.getSystemInfoSync().windowWidth)
|
||||
|
||||
// Computed properties for responsive design
|
||||
const isLargeScreen = computed(() : boolean => {
|
||||
return screenWidth.value >= 768
|
||||
})
|
||||
|
||||
// 响应式数据
|
||||
const userInfo = ref<UTSJSONObject | null>(null)
|
||||
const userAvatar = ref<string>('/static/default-avatar.png')
|
||||
const totalTrainingDays = ref<number>(0)
|
||||
const completedAssignments = ref<number>(0)
|
||||
const totalTrainingTime = ref<number>(0)
|
||||
const achievementCount = ref<number>(0)
|
||||
|
||||
|
||||
const getUserName = () : string => {
|
||||
const profile = state.userProfile
|
||||
if (profile != null) {
|
||||
const username = profile.username
|
||||
if (typeof username === 'string' && username.length > 0) {
|
||||
return username
|
||||
}
|
||||
}
|
||||
if (userInfo.value == null) {
|
||||
return '未登录'
|
||||
}
|
||||
return (userInfo.value as UTSJSONObject).getString('username') ?? '未设置'
|
||||
}
|
||||
|
||||
const getUserId = () : string => {
|
||||
if (typeof userId.value === 'string' && userId.value.length > 0) {
|
||||
return userId.value
|
||||
}
|
||||
if (userInfo.value == null) {
|
||||
return '---'
|
||||
}
|
||||
return (userInfo.value as UTSJSONObject).getString('id') ?? '未设置'
|
||||
}
|
||||
|
||||
const getJoinDate = () : string => {
|
||||
if (userInfo.value == null) return '---'
|
||||
const joinDate = (userInfo.value as UTSJSONObject).getString('created_at') ?? ''
|
||||
return joinDate.length > 0 ? formatDateTime(joinDate).split(' ')[0] : '未知'
|
||||
}
|
||||
|
||||
const getUserLevel = () : number => {
|
||||
if (userInfo.value == null) return 1
|
||||
return (userInfo.value as UTSJSONObject).getNumber('level') ?? 1
|
||||
}
|
||||
const getCurrentXP = () : number => {
|
||||
if (userInfo.value == null) return 0
|
||||
return (userInfo.value as UTSJSONObject).getNumber('current_xp') ?? 0
|
||||
}
|
||||
|
||||
const getNextLevelXP = () : number => {
|
||||
const level = getUserLevel()
|
||||
return level * 1000 // 每级需要1000XP
|
||||
}
|
||||
|
||||
const getXPProgress = () : number => {
|
||||
const current = getCurrentXP()
|
||||
const next = getNextLevelXP()
|
||||
const levelBase = (getUserLevel() - 1) * 1000
|
||||
const levelCurrent = current - levelBase
|
||||
const levelRequired = next - levelBase
|
||||
return levelRequired > 0 ? (levelCurrent / levelRequired) * 100 : 0
|
||||
} // 加载用户资料 - 优先使用 state.userProfile,然后同步数据库
|
||||
|
||||
// 加载用户统计数据
|
||||
const loadUserStats = async () => {
|
||||
try { // 获取训练记录统计
|
||||
const recordsResult = await supaClient
|
||||
.from('ak_training_records')
|
||||
.select('*', {})
|
||||
.eq('user_id', userId.value)
|
||||
.execute()
|
||||
|
||||
if (recordsResult != null && recordsResult.error == null && recordsResult.status == 200) {
|
||||
console.log(recordsResult)
|
||||
const records = recordsResult.data as UTSJSONObject[]
|
||||
|
||||
// 计算训练天数(去重日期)
|
||||
const trainingDates = new Set<string>()
|
||||
let totalMinutes = 0
|
||||
records.forEach(record => {
|
||||
const createdAt = (record as UTSJSONObject).getString('created_at') ?? ''
|
||||
if (createdAt.length > 0) {
|
||||
const date = new Date(createdAt).toDateString()
|
||||
trainingDates.add(date)
|
||||
}
|
||||
|
||||
const duration = (record as UTSJSONObject).getNumber('duration') ?? 0
|
||||
totalMinutes += duration
|
||||
})
|
||||
|
||||
totalTrainingDays.value = trainingDates.size
|
||||
totalTrainingTime.value = Math.round(totalMinutes / 60)
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(recordsResult)
|
||||
}
|
||||
// 获取完成的作业数量
|
||||
const assignmentsResult = await supaClient
|
||||
.from('ak_assignments')
|
||||
.select('*', {})
|
||||
.eq('status', 'completed')
|
||||
.execute()
|
||||
|
||||
if (assignmentsResult != null && assignmentsResult.error == null) {
|
||||
const data = assignmentsResult.data as UTSJSONObject[]
|
||||
completedAssignments.value = data.length
|
||||
}
|
||||
// 获取成就数量 - 使用高分作业作为成就代理
|
||||
const achievementsResult = await supaClient
|
||||
.from('ak_assignment_submissions')
|
||||
.select('*', {})
|
||||
.eq('student_id', userId.value)
|
||||
.gte('final_score', 90) // 90分以上的作业算作成就
|
||||
.execute()
|
||||
|
||||
if (achievementsResult != null && achievementsResult.error == null) {
|
||||
const data = achievementsResult.data as UTSJSONObject[]
|
||||
achievementCount.value = data.length
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(userId.value)
|
||||
console.error('加载用户统计失败:', error)
|
||||
// 使用模拟数据
|
||||
totalTrainingDays.value = 45
|
||||
completedAssignments.value = 28
|
||||
totalTrainingTime.value = 67
|
||||
achievementCount.value = 12
|
||||
}
|
||||
} // 更换头像 - 同时更新 state 和数据库
|
||||
const changeAvatar = () => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sourceType: ['album', 'camera'],
|
||||
success: function (res) {
|
||||
const tempFilePath = res.tempFilePaths[0]
|
||||
// 这里应该上传头像到服务器
|
||||
userAvatar.value = tempFilePath // 更新 state.userProfile 中的头像 - 使用本地变量避免smart cast问题
|
||||
const currentProfile = state.userProfile
|
||||
if (currentProfile != null && typeof currentProfile.id === 'string' && (currentProfile.id?.length ?? 0) > 0) {
|
||||
const updatedProfile : UserProfile = {
|
||||
id: currentProfile.id,
|
||||
username: currentProfile.username,
|
||||
email: currentProfile.email,
|
||||
gender: currentProfile.gender,
|
||||
birthday: currentProfile.birthday,
|
||||
height_cm: currentProfile.height_cm,
|
||||
weight_kg: currentProfile.weight_kg,
|
||||
bio: currentProfile.bio,
|
||||
avatar_url: tempFilePath,
|
||||
preferred_language: currentProfile.preferred_language,
|
||||
role: currentProfile.role
|
||||
}
|
||||
setUserProfile(updatedProfile)
|
||||
}// 更新数据库(如果用户ID有效)
|
||||
const currentUserId = getCurrentUserId()
|
||||
if (currentUserId.length > 0 && currentUserId !== 'demo_user_id') {
|
||||
// 使用异步调用但不等待结果
|
||||
supaClient
|
||||
.from('ak_users')
|
||||
.update({ avatar_url: tempFilePath })
|
||||
.eq('id', currentUserId)
|
||||
.execute()
|
||||
.then(() => {
|
||||
console.log('头像更新成功')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('更新头像失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: '头像更新成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑资料
|
||||
const editProfile = () => {
|
||||
uni.showModal({
|
||||
title: '编辑资料',
|
||||
content: '功能开发中...',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
const changePassword = () => {
|
||||
uni.showModal({
|
||||
title: '修改密码',
|
||||
content: '功能开发中...',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
// 通知设置
|
||||
const notificationSettings = () => {
|
||||
uni.showModal({
|
||||
title: '通知设置',
|
||||
content: '功能开发中...',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
// 隐私设置
|
||||
const privacySettings = () => {
|
||||
uni.showModal({
|
||||
title: '隐私设置',
|
||||
content: '功能开发中...',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
// 目标设置
|
||||
const goalSettings = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sport/student/goal-settings'
|
||||
})
|
||||
}
|
||||
|
||||
// 提醒设置
|
||||
const reminderSettings = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sport/student/reminder-settings'
|
||||
})
|
||||
}
|
||||
// 喜欢的运动
|
||||
const favoriteExercises = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sport/student/favorite-exercises'
|
||||
})
|
||||
}
|
||||
|
||||
// 偏好分析
|
||||
const preferencesAnalytics = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sport/student/preferences-analytics'
|
||||
})
|
||||
}
|
||||
|
||||
// 设备管理
|
||||
const deviceManagement = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sport/student/device-management'
|
||||
})
|
||||
}
|
||||
// 导出数据
|
||||
const exportData = () => {
|
||||
uni.showLoading({
|
||||
title: '导出中...'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '导出完成',
|
||||
icon: 'success'
|
||||
})
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// 备份数据
|
||||
const backupData = () => {
|
||||
uni.showLoading({
|
||||
title: '备份中...'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '备份完成',
|
||||
icon: 'success'
|
||||
})
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
const clearCache = () => {
|
||||
uni.showModal({
|
||||
title: '清除缓存',
|
||||
content: '确定要清除所有缓存数据吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({
|
||||
title: '清除中...'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '缓存已清除',
|
||||
icon: 'success'
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 帮助中心
|
||||
const helpCenter = () => {
|
||||
uni.showModal({
|
||||
title: '帮助中心',
|
||||
content: '功能开发中...',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
// 意见反馈
|
||||
const feedback = () => {
|
||||
uni.showModal({
|
||||
title: '意见反馈',
|
||||
content: '功能开发中...',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
// 关于应用
|
||||
const aboutApp = () => {
|
||||
uni.showModal({
|
||||
title: '关于应用',
|
||||
content: 'AI监测系统 v1.0.0\n\n 高性能AI监测管理平台',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
const loadUserProfile = async () => {
|
||||
try {
|
||||
// 如果 state.userProfile 存在,先使用它填充界面
|
||||
const profile = state.userProfile
|
||||
if (profile != null && typeof profile.id === 'string' && (profile.id?.length ?? 0) > 0) {
|
||||
userInfo.value = {
|
||||
id: profile.id,
|
||||
username: profile.username != null ? profile.username : '',
|
||||
email: profile.email != null ? profile.email : '',
|
||||
avatar_url: profile.avatar_url != null ? profile.avatar_url : '',
|
||||
created_at: new Date().toISOString() // 临时值
|
||||
} as UTSJSONObject
|
||||
|
||||
const avatarUrl = profile.avatar_url
|
||||
if (typeof avatarUrl === 'string' && (avatarUrl?.length ?? 0) > 0) {
|
||||
userAvatar.value = avatarUrl as string
|
||||
}
|
||||
}
|
||||
|
||||
// 然后从数据库获取最新数据并更新 state
|
||||
if (typeof userId.value === 'string' && userId.value.length > 0 && userId.value !== 'demo_user_id') {
|
||||
|
||||
const result = await supaClient
|
||||
.from('ak_users')
|
||||
.select('*', {})
|
||||
.eq('id', userId.value)
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (result != null && result.error == null && result.data != null) {
|
||||
const res_data = result.data as UTSJSONObject[]
|
||||
const resultData = res_data[0]
|
||||
userInfo.value = resultData
|
||||
const avatar = (userInfo.value as UTSJSONObject).getString('avatar_url') ?? ''
|
||||
if (avatar.length > 0) {
|
||||
userAvatar.value = avatar
|
||||
}
|
||||
|
||||
// 更新 state.userProfile
|
||||
|
||||
const updatedProfile : UserProfile = {
|
||||
id: resultData.getString('id') ?? '',
|
||||
username: resultData.getString('username') ?? '',
|
||||
email: resultData.getString('email') ?? '',
|
||||
gender: resultData.getString('gender'),
|
||||
birthday: resultData.getString('birthday'),
|
||||
height_cm: resultData.getNumber('height_cm'),
|
||||
weight_kg: resultData.getNumber('weight_kg'),
|
||||
bio: resultData.getString('bio'),
|
||||
avatar_url: resultData.getString('avatar_url'),
|
||||
preferred_language: resultData.getString('preferred_language'),
|
||||
role: resultData.getString('role')
|
||||
}
|
||||
setUserProfile(updatedProfile)
|
||||
}
|
||||
}
|
||||
loadUserStats()
|
||||
} catch (error) {
|
||||
console.error('加载用户资料失败:', error)
|
||||
}
|
||||
}
|
||||
// 退出登录 - 清空 state 和重定向
|
||||
const logout = () => {
|
||||
uni.showModal({
|
||||
title: '退出登录',
|
||||
content: '确定要退出登录吗?',
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
// 使用 Promise 包装异步操作
|
||||
supaClient.signOut()
|
||||
.then(() => {
|
||||
// 清空 state
|
||||
const emptyProfile : UserProfile = { username: '', email: '' }
|
||||
setUserProfile(emptyProfile)
|
||||
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('退出登录失败:', error)
|
||||
uni.showToast({
|
||||
title: '退出失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// 生命周期
|
||||
onLoad((options : OnLoadOptions) => {
|
||||
userId.value = options['id'] ?? ''
|
||||
if (userId.value.length === 0) {
|
||||
userId.value = getCurrentUserId()
|
||||
}
|
||||
loadUserProfile()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// Initialize screen width
|
||||
screenWidth.value = uni.getSystemInfoSync().windowWidth
|
||||
})
|
||||
|
||||
// Handle resize events for responsive design
|
||||
onResize((size) => {
|
||||
screenWidth.value = size.size.windowWidth
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.profile-page {
|
||||
flex: 1;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
/* 头部区域 */
|
||||
.profile-header {
|
||||
position: relative;
|
||||
padding: 60rpx 40rpx 40rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image: linear-gradient(to right, #667eea, #764ba2);
|
||||
border-radius: 0 0 40rpx 40rpx;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
border: 4rpx solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.avatar-edit {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex: 1;
|
||||
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.user-id,
|
||||
.join-date {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.level-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.level-badge {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 25rpx;
|
||||
backdrop-filter: blur(10rpx);
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.level-text {
|
||||
color: white;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.xp-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.xp-text {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.xp-bar {
|
||||
height: 8rpx;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xp-progress {
|
||||
height: 100%;
|
||||
background: white;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
/* 统计网格 */
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 20rpx 30rpx;
|
||||
}
|
||||
.stat-item {
|
||||
background: white;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
margin-right: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
width: 46%;
|
||||
flex: 0 0 46%;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
line-height: 1;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* 菜单区域 */
|
||||
.menu-section {
|
||||
margin: 0 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 15rpx;
|
||||
padding-left: 10rpx;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
background: white;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1px solid #f8f9fa;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 32rpx;
|
||||
width: 40rpx;
|
||||
text-align: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: 30rpx;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 28rpx;
|
||||
color: #bdc3c7;
|
||||
}
|
||||
|
||||
/* 退出登录 */
|
||||
.logout-section {
|
||||
margin: 40rpx 20rpx;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
padding: 30rpx;
|
||||
background: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 20rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
transition: transform 0.3s ease, background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.logout-btn:active {
|
||||
transform: scale(0.98);
|
||||
background: #c0392b;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user