blob: 5d11accd6292bf69b6af36210a2ce8b61ad72bf2 [file] [log] [blame]
<template>
<div class="profile-page">
<div class="page-container">
<!-- 个人信息卡片 -->
<div class="profile-header">
<div class="user-avatar-section">
<div class="avatar-container">
<el-avatar :size="120" :src="userProfile.avatar">
{{ userProfile.username.charAt(0).toUpperCase() }}
</el-avatar>
<el-button
type="primary"
size="small"
class="change-avatar-btn"
@click="showAvatarDialog = true"
>
更换头像
</el-button>
</div>
<div class="user-basic-info">
<h1 class="username">{{ userProfile.username }}</h1>
<div class="user-title">
<el-tag :type="getUserTitleType(userProfile.userLevel)" size="large">
{{ userProfile.userTitle }}
</el-tag>
</div>
<div class="join-info">
<el-icon><Calendar /></el-icon>
<span>加入时间:{{ formatDate(userProfile.joinDate) }}</span>
</div>
<div class="last-login">
<el-icon><Clock /></el-icon>
<span>最后登录:{{ formatTime(userProfile.lastLogin) }}</span>
</div>
</div>
</div>
<div class="user-stats-overview">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon upload">
<el-icon size="32"><Upload /></el-icon>
</div>
<div class="stat-info">
<h3>{{ userProfile.stats.uploaded }}</h3>
<p>上传量</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon download">
<el-icon size="32"><Download /></el-icon>
</div>
<div class="stat-info">
<h3>{{ userProfile.stats.downloaded }}</h3>
<p>下载量</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon ratio" :class="getRatioClass(userProfile.stats.ratio)">
<el-icon size="32"><TrendCharts /></el-icon>
</div>
<div class="stat-info">
<h3>{{ userProfile.stats.ratio }}</h3>
<p>分享率</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon points">
<el-icon size="32"><Star /></el-icon>
</div>
<div class="stat-info">
<h3>{{ userProfile.stats.points }}</h3>
<p>积分</p>
</div>
</div>
</div>
</div>
</div>
<!-- 详细信息选项卡 -->
<div class="profile-content">
<el-tabs v-model="activeTab" type="border-card">
<!-- 个人信息 -->
<el-tab-pane label="个人信息" name="info">
<div class="info-section">
<el-form
ref="profileFormRef"
:model="editProfile"
:rules="profileRules"
label-width="120px"
size="large"
>
<div class="form-section">
<h3>基本信息</h3>
<el-form-item label="用户名">
<el-input v-model="editProfile.username" disabled>
<template #suffix>
<el-tooltip content="用户名不可修改">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</template>
</el-input>
</el-form-item>
<el-form-item label="邮箱地址" prop="email">
<el-input v-model="editProfile.email" type="email" />
</el-form-item>
<el-form-item label="真实姓名" prop="realName">
<el-input v-model="editProfile.realName" placeholder="可选填写" />
</el-form-item>
<el-form-item label="所在地区">
<el-cascader
v-model="editProfile.location"
:options="locationOptions"
placeholder="请选择地区"
clearable
/>
</el-form-item>
</div>
<div class="form-section">
<h3>个人介绍</h3>
<el-form-item label="个人签名">
<el-input
v-model="editProfile.signature"
type="textarea"
:rows="3"
maxlength="200"
show-word-limit
placeholder="介绍一下自己吧..."
/>
</el-form-item>
<el-form-item label="个人网站">
<el-input v-model="editProfile.website" placeholder="https://" />
</el-form-item>
<el-form-item label="兴趣爱好">
<div class="interests-input">
<el-tag
v-for="interest in editProfile.interests"
:key="interest"
closable
@close="removeInterest(interest)"
class="interest-tag"
>
{{ interest }}
</el-tag>
<el-input
v-if="interestInputVisible"
ref="interestInputRef"
v-model="interestInputValue"
size="small"
@keyup.enter="addInterest"
@blur="addInterest"
style="width: 120px;"
/>
<el-button
v-else
size="small"
@click="showInterestInput"
>
+ 添加兴趣
</el-button>
</div>
</el-form-item>
</div>
<div class="form-section">
<h3>隐私设置</h3>
<el-form-item label="邮箱公开">
<el-switch v-model="editProfile.emailPublic" />
<span class="setting-tip">是否在个人资料中显示邮箱</span>
</el-form-item>
<el-form-item label="统计公开">
<el-switch v-model="editProfile.statsPublic" />
<span class="setting-tip">是否公开上传下载统计</span>
</el-form-item>
<el-form-item label="活动记录">
<el-switch v-model="editProfile.activityPublic" />
<span class="setting-tip">是否公开活动记录</span>
</el-form-item>
</div>
<div class="form-actions">
<el-button @click="resetProfile">重置</el-button>
<el-button type="primary" @click="saveProfile" :loading="saving">
保存修改
</el-button>
</div>
</el-form>
</div>
</el-tab-pane>
<!-- 数据统计 -->
<el-tab-pane label="数据统计" name="stats">
<div class="stats-section">
<div class="stats-overview">
<div class="overview-grid">
<div class="overview-card">
<h3>上传统计</h3>
<div class="stat-details">
<div class="detail-item">
<span class="label">总上传量:</span>
<span class="value">{{ userProfile.detailedStats.totalUploaded }}</span>
</div>
<div class="detail-item">
<span class="label">上传种子:</span>
<span class="value">{{ userProfile.detailedStats.uploadedTorrents }} 个</span>
</div>
<div class="detail-item">
<span class="label">平均大小:</span>
<span class="value">{{ userProfile.detailedStats.avgUploadSize }}</span>
</div>
</div>
</div>
<div class="overview-card">
<h3>下载统计</h3>
<div class="stat-details">
<div class="detail-item">
<span class="label">总下载量:</span>
<span class="value">{{ userProfile.detailedStats.totalDownloaded }}</span>
</div>
<div class="detail-item">
<span class="label">下载种子:</span>
<span class="value">{{ userProfile.detailedStats.downloadedTorrents }} 个</span>
</div>
<div class="detail-item">
<span class="label">完成种子:</span>
<span class="value">{{ userProfile.detailedStats.completedTorrents }} 个</span>
</div>
</div>
</div>
<div class="overview-card">
<h3>做种统计</h3>
<div class="stat-details">
<div class="detail-item">
<span class="label">正在做种:</span>
<span class="value">{{ userProfile.detailedStats.seeding }} 个</span>
</div>
<div class="detail-item">
<span class="label">做种时间:</span>
<span class="value">{{ userProfile.detailedStats.seedingTime }}</span>
</div>
<div class="detail-item">
<span class="label">做种排名:</span>
<span class="value">第 {{ userProfile.detailedStats.seedingRank }} 名</span>
</div>
</div>
</div>
<div class="overview-card">
<h3>积分记录</h3>
<div class="stat-details">
<div class="detail-item">
<span class="label">当前积分:</span>
<span class="value">{{ userProfile.stats.points }}</span>
</div>
<div class="detail-item">
<span class="label">累计获得:</span>
<span class="value">{{ userProfile.detailedStats.totalEarnedPoints }}</span>
</div>
<div class="detail-item">
<span class="label">累计消费:</span>
<span class="value">{{ userProfile.detailedStats.totalSpentPoints }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 数据图表 -->
<div class="charts-section">
<div class="chart-card">
<h3>上传下载趋势</h3>
<div class="chart-placeholder">
<el-icon size="48" color="#e4e7ed"><TrendCharts /></el-icon>
<p>图表功能开发中...</p>
</div>
</div>
</div>
</div>
</el-tab-pane>
<!-- 我的种子 -->
<el-tab-pane label="我的种子" name="torrents">
<div class="torrents-section">
<div class="section-header">
<h3>我上传的种子</h3>
<el-button type="primary" :icon="Upload" @click="$router.push('/upload')">
上传新种子
</el-button>
</div>
<el-table :data="userTorrents" stripe>
<el-table-column label="种子名称" min-width="300">
<template #default="{ row }">
<div class="torrent-info">
<el-tag :type="getCategoryType(row.category)" size="small">
{{ getCategoryName(row.category) }}
</el-tag>
<span class="torrent-title">{{ row.title }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="大小" prop="size" width="100" />
<el-table-column label="做种" prop="seeders" width="80" align="center" />
<el-table-column label="下载" prop="leechers" width="80" align="center" />
<el-table-column label="完成" prop="downloads" width="80" align="center" />
<el-table-column label="上传时间" width="120">
<template #default="{ row }">
{{ formatDate(row.uploadTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center">
<template #default="{ row }">
<el-button
type="primary"
size="small"
@click="$router.push(`/torrent/${row.id}`)"
>
查看
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="torrentsPage"
:page-size="10"
:total="userTorrents.length"
layout="prev, pager, next"
small
/>
</div>
</div>
</el-tab-pane>
<!-- 活动记录 -->
<el-tab-pane label="活动记录" name="activity">
<div class="activity-section">
<div class="activity-filters">
<el-select v-model="activityFilter" placeholder="活动类型">
<el-option label="全部活动" value="" />
<el-option label="上传种子" value="upload" />
<el-option label="下载种子" value="download" />
<el-option label="论坛发帖" value="post" />
<el-option label="积分变动" value="points" />
</el-select>
</div>
<div class="activity-timeline">
<el-timeline>
<el-timeline-item
v-for="activity in filteredActivities"
:key="activity.id"
:timestamp="formatTime(activity.time)"
:type="getActivityType(activity.type)"
>
<div class="activity-content">
<div class="activity-header">
<el-icon>
<component :is="getActivityIcon(activity.type)" />
</el-icon>
<span class="activity-title">{{ activity.title }}</span>
</div>
<div class="activity-description">{{ activity.description }}</div>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</el-tab-pane>
<!-- 安全设置 -->
<el-tab-pane label="安全设置" name="security">
<div class="security-section">
<div class="security-card">
<h3>修改密码</h3>
<el-form
ref="passwordFormRef"
:model="passwordForm"
:rules="passwordRules"
label-width="120px"
>
<el-form-item label="当前密码" prop="currentPassword">
<el-input
v-model="passwordForm.currentPassword"
type="password"
show-password
placeholder="请输入当前密码"
/>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input
v-model="passwordForm.newPassword"
type="password"
show-password
placeholder="请输入新密码"
/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input
v-model="passwordForm.confirmPassword"
type="password"
show-password
placeholder="请再次输入新密码"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="changePassword" :loading="changingPassword">
修改密码
</el-button>
</el-form-item>
</el-form>
</div>
<div class="security-card">
<h3>登录记录</h3>
<el-table :data="loginHistory" stripe>
<el-table-column label="登录时间" width="180">
<template #default="{ row }">
{{ formatDateTime(row.time) }}
</template>
</el-table-column>
<el-table-column label="IP地址" prop="ip" width="150" />
<el-table-column label="设备信息" prop="device" />
<el-table-column label="登录结果" width="100">
<template #default="{ row }">
<el-tag :type="row.success ? 'success' : 'danger'" size="small">
{{ row.success ? '成功' : '失败' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
<!-- 更换头像对话框 -->
<el-dialog v-model="showAvatarDialog" title="更换头像" width="400px">
<div class="avatar-upload">
<el-upload
ref="avatarUploadRef"
:auto-upload="false"
:limit="1"
accept="image/*"
:on-change="handleAvatarChange"
list-type="picture-card"
class="avatar-uploader"
>
<el-icon><Plus /></el-icon>
</el-upload>
<div class="upload-tips">
<p>支持 JPG、PNG 格式</p>
<p>建议尺寸 200x200 像素</p>
<p>文件大小不超过 2MB</p>
</div>
</div>
<template #footer>
<el-button @click="showAvatarDialog = false">取消</el-button>
<el-button type="primary" @click="uploadAvatar" :loading="uploadingAvatar">
上传头像
</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
Calendar,
Clock,
Upload,
Download,
TrendCharts,
Star,
QuestionFilled,
Plus,
ChatDotRound,
Flag,
Coin
} from '@element-plus/icons-vue'
export default {
name: 'ProfileView',
setup() {
const router = useRouter()
const profileFormRef = ref(null)
const passwordFormRef = ref(null)
const avatarUploadRef = ref(null)
const interestInputRef = ref(null)
const activeTab = ref('info')
const showAvatarDialog = ref(false)
const saving = ref(false)
const changingPassword = ref(false)
const uploadingAvatar = ref(false)
const interestInputVisible = ref(false)
const interestInputValue = ref('')
const activityFilter = ref('')
const torrentsPage = ref(1)
const userProfile = ref({
username: 'MovieExpert',
email: 'movieexpert@example.com',
realName: '',
avatar: '',
userLevel: 5,
userTitle: '资深会员',
joinDate: '2023-01-15T10:00:00',
lastLogin: '2025-06-03T14:30:00',
location: ['北京市', '朝阳区'],
signature: '热爱电影,分享快乐!',
website: 'https://movieblog.com',
interests: ['电影', '音乐', '科技', '摄影'],
emailPublic: false,
statsPublic: true,
activityPublic: true,
stats: {
uploaded: '256.8 GB',
downloaded: '89.6 GB',
ratio: '2.87',
points: '15,680'
},
detailedStats: {
totalUploaded: '256.8 GB',
uploadedTorrents: 45,
avgUploadSize: '5.7 GB',
totalDownloaded: '89.6 GB',
downloadedTorrents: 123,
completedTorrents: 118,
seeding: 32,
seedingTime: '1,245 小时',
seedingRank: 86,
totalEarnedPoints: '28,940',
totalSpentPoints: '13,260'
}
})
const editProfile = reactive({
username: '',
email: '',
realName: '',
location: [],
signature: '',
website: '',
interests: [],
emailPublic: false,
statsPublic: true,
activityPublic: true
})
const passwordForm = reactive({
currentPassword: '',
newPassword: '',
confirmPassword: ''
})
const profileRules = {
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
]
}
const passwordRules = {
currentPassword: [
{ required: true, message: '请输入当前密码', trigger: 'blur' }
],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, message: '密码长度至少6个字符', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认新密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== passwordForm.newPassword) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
const locationOptions = [
{
value: '北京市',
label: '北京市',
children: [
{ value: '朝阳区', label: '朝阳区' },
{ value: '海淀区', label: '海淀区' },
{ value: '丰台区', label: '丰台区' }
]
},
{
value: '上海市',
label: '上海市',
children: [
{ value: '浦东新区', label: '浦东新区' },
{ value: '黄浦区', label: '黄浦区' },
{ value: '静安区', label: '静安区' }
]
}
]
const userTorrents = ref([
{
id: 1,
title: '[4K蓝光原盘] 阿凡达:水之道',
category: 'movie',
size: '85.6 GB',
seeders: 45,
leechers: 12,
downloads: 234,
uploadTime: '2025-05-15T10:00:00'
},
{
id: 2,
title: '[FLAC] 古典音乐合集',
category: 'music',
size: '2.3 GB',
seeders: 23,
leechers: 5,
downloads: 89,
uploadTime: '2025-04-20T15:30:00'
}
])
const activities = ref([
{
id: 1,
type: 'upload',
title: '上传种子',
description: '上传了《阿凡达:水之道》4K蓝光原盘',
time: '2025-06-03T10:30:00'
},
{
id: 2,
type: 'download',
title: '下载种子',
description: '下载了《星际穿越》IMAX版本',
time: '2025-06-02T14:20:00'
},
{
id: 3,
type: 'post',
title: '发布主题',
description: '在电影讨论区发布了新主题',
time: '2025-06-01T16:45:00'
},
{
id: 4,
type: 'points',
title: '积分变动',
description: '做种奖励获得 +50 积分',
time: '2025-05-31T09:15:00'
}
])
const loginHistory = ref([
{
time: '2025-06-03T14:30:00',
ip: '192.168.1.100',
device: 'Windows 11 / Chrome 120',
success: true
},
{
time: '2025-06-02T09:15:00',
ip: '192.168.1.100',
device: 'Windows 11 / Chrome 120',
success: true
},
{
time: '2025-06-01T22:30:00',
ip: '192.168.1.100',
device: 'Android / Chrome Mobile',
success: true
}
])
const filteredActivities = computed(() => {
if (!activityFilter.value) return activities.value
return activities.value.filter(activity => activity.type === activityFilter.value)
})
onMounted(() => {
loadUserProfile()
})
const loadUserProfile = () => {
// 加载用户资料到编辑表单
Object.assign(editProfile, {
username: userProfile.value.username,
email: userProfile.value.email,
realName: userProfile.value.realName,
location: userProfile.value.location,
signature: userProfile.value.signature,
website: userProfile.value.website,
interests: [...userProfile.value.interests],
emailPublic: userProfile.value.emailPublic,
statsPublic: userProfile.value.statsPublic,
activityPublic: userProfile.value.activityPublic
})
}
const formatDate = (dateString) => {
const date = new Date(dateString)
return date.toLocaleDateString('zh-CN')
}
const formatTime = (timeString) => {
const date = new Date(timeString)
const now = new Date()
const diff = now - date
const hours = Math.floor(diff / (1000 * 60 * 60))
if (hours < 1) return '刚刚'
if (hours < 24) return `${hours}小时前`
const days = Math.floor(hours / 24)
if (days < 7) return `${days}天前`
return date.toLocaleDateString('zh-CN')
}
const formatDateTime = (dateString) => {
const date = new Date(dateString)
return date.toLocaleString('zh-CN')
}
const getUserTitleType = (level) => {
if (level >= 8) return 'danger' // 管理员
if (level >= 6) return 'warning' // 资深会员
if (level >= 4) return 'success' // 正式会员
if (level >= 2) return 'info' // 初级会员
return 'default' // 新手
}
const getRatioClass = (ratio) => {
const r = parseFloat(ratio)
if (r >= 2) return 'excellent'
if (r >= 1) return 'good'
return 'warning'
}
const getCategoryType = (category) => {
const types = {
'movie': 'primary',
'tv': 'info',
'music': 'success',
'software': 'warning',
'game': 'danger'
}
return types[category] || 'default'
}
const getCategoryName = (category) => {
const names = {
'movie': '电影',
'tv': '电视剧',
'music': '音乐',
'software': '软件',
'game': '游戏'
}
return names[category] || category
}
const getActivityType = (type) => {
const types = {
'upload': 'success',
'download': 'primary',
'post': 'warning',
'points': 'info'
}
return types[type] || 'primary'
}
const getActivityIcon = (type) => {
const icons = {
'upload': 'Upload',
'download': 'Download',
'post': 'ChatDotRound',
'points': 'Coin'
}
return icons[type] || 'Star'
}
const showInterestInput = () => {
interestInputVisible.value = true
nextTick(() => {
interestInputRef.value?.focus()
})
}
const addInterest = () => {
const interest = interestInputValue.value.trim()
if (interest && !editProfile.interests.includes(interest)) {
editProfile.interests.push(interest)
}
interestInputVisible.value = false
interestInputValue.value = ''
}
const removeInterest = (interest) => {
const index = editProfile.interests.indexOf(interest)
if (index > -1) {
editProfile.interests.splice(index, 1)
}
}
const saveProfile = async () => {
try {
await profileFormRef.value?.validate()
saving.value = true
// 模拟保存过程
await new Promise(resolve => setTimeout(resolve, 1500))
// 更新用户资料
Object.assign(userProfile.value, editProfile)
ElMessage.success('个人资料保存成功')
} catch (error) {
console.error('表单验证失败:', error)
} finally {
saving.value = false
}
}
const resetProfile = () => {
loadUserProfile()
ElMessage.info('已重置为原始数据')
}
const changePassword = async () => {
try {
await passwordFormRef.value?.validate()
changingPassword.value = true
// 模拟密码修改过程
await new Promise(resolve => setTimeout(resolve, 1500))
// 重置表单
passwordFormRef.value?.resetFields()
Object.assign(passwordForm, {
currentPassword: '',
newPassword: '',
confirmPassword: ''
})
ElMessage.success('密码修改成功')
} catch (error) {
console.error('表单验证失败:', error)
} finally {
changingPassword.value = false
}
}
const handleAvatarChange = (file) => {
const isImage = file.raw.type.startsWith('image/')
const isLt2M = file.raw.size / 1024 / 1024 < 2
if (!isImage) {
ElMessage.error('只能上传图片文件!')
return false
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过 2MB!')
return false
}
return true
}
const uploadAvatar = async () => {
const files = avatarUploadRef.value?.uploadFiles
if (!files || files.length === 0) {
ElMessage.warning('请选择头像文件')
return
}
uploadingAvatar.value = true
try {
// 模拟上传过程
await new Promise(resolve => setTimeout(resolve, 2000))
// 更新头像URL
userProfile.value.avatar = URL.createObjectURL(files[0].raw)
ElMessage.success('头像上传成功')
showAvatarDialog.value = false
avatarUploadRef.value?.clearFiles()
} catch (error) {
ElMessage.error('头像上传失败')
} finally {
uploadingAvatar.value = false
}
}
return {
activeTab,
showAvatarDialog,
saving,
changingPassword,
uploadingAvatar,
interestInputVisible,
interestInputValue,
activityFilter,
torrentsPage,
userProfile,
editProfile,
passwordForm,
profileRules,
passwordRules,
locationOptions,
userTorrents,
filteredActivities,
loginHistory,
profileFormRef,
passwordFormRef,
avatarUploadRef,
interestInputRef,
formatDate,
formatTime,
formatDateTime,
getUserTitleType,
getRatioClass,
getCategoryType,
getCategoryName,
getActivityType,
getActivityIcon,
showInterestInput,
addInterest,
removeInterest,
saveProfile,
resetProfile,
changePassword,
handleAvatarChange,
uploadAvatar,
Calendar,
Clock,
Upload,
Download,
TrendCharts,
Star,
QuestionFilled,
Plus,
ChatDotRound,
Flag,
Coin
}
}
}
</script>
<style lang="scss" scoped>
.profile-page {
max-width: 1200px;
margin: 0 auto;
padding: 24px;
background: #f5f5f5;
min-height: 100vh;
}
.profile-header {
background: #fff;
border-radius: 12px;
padding: 32px;
margin-bottom: 24px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32px;
.user-avatar-section {
display: flex;
gap: 24px;
.avatar-container {
text-align: center;
.change-avatar-btn {
margin-top: 12px;
}
}
.user-basic-info {
flex: 1;
.username {
font-size: 28px;
font-weight: 600;
color: #2c3e50;
margin: 0 0 12px 0;
}
.user-title {
margin-bottom: 16px;
}
.join-info, .last-login {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #7f8c8d;
margin-bottom: 8px;
}
}
}
.user-stats-overview {
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
.stat-card {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
display: flex;
align-items: center;
gap: 16px;
.stat-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&.upload { background: rgba(103, 194, 58, 0.1); color: #67c23a; }
&.download { background: rgba(64, 158, 255, 0.1); color: #409eff; }
&.ratio {
&.excellent { background: rgba(103, 194, 58, 0.1); color: #67c23a; }
&.good { background: rgba(230, 162, 60, 0.1); color: #e6a23c; }
&.warning { background: rgba(245, 108, 108, 0.1); color: #f56c6c; }
}
&.points { background: rgba(245, 108, 108, 0.1); color: #f56c6c; }
}
.stat-info {
h3 {
font-size: 20px;
font-weight: 600;
color: #2c3e50;
margin: 0 0 4px 0;
}
p {
font-size: 14px;
color: #7f8c8d;
margin: 0;
}
}
}
}
}
}
.profile-content {
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
:deep(.el-tabs__content) {
padding: 24px;
}
}
.info-section {
.form-section {
margin-bottom: 32px;
h3 {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
margin: 0 0 20px 0;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
}
}
.interests-input {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
.interest-tag {
margin: 0;
}
}
.setting-tip {
margin-left: 12px;
font-size: 12px;
color: #909399;
}
.form-actions {
text-align: center;
margin-top: 32px;
.el-button {
margin: 0 8px;
min-width: 100px;
}
}
}
.stats-section {
.stats-overview {
margin-bottom: 32px;
.overview-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
.overview-card {
background: #f8f9fa;
border-radius: 8px;
padding: 24px;
h3 {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
margin: 0 0 16px 0;
}
.stat-details {
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.label {
font-size: 14px;
color: #7f8c8d;
}
.value {
font-size: 14px;
font-weight: 600;
color: #2c3e50;
}
}
}
}
}
}
.charts-section {
.chart-card {
background: #f8f9fa;
border-radius: 8px;
padding: 24px;
h3 {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
margin: 0 0 20px 0;
}
.chart-placeholder {
text-align: center;
padding: 60px 0;
color: #909399;
p {
margin: 12px 0 0 0;
}
}
}
}
}
.torrents-section {
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h3 {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
margin: 0;
}
}
.torrent-info {
display: flex;
align-items: center;
gap: 12px;
.torrent-title {
font-weight: 500;
}
}
.pagination-wrapper {
margin-top: 16px;
text-align: center;
}
}
.activity-section {
.activity-filters {
margin-bottom: 24px;
.el-select {
width: 150px;
}
}
.activity-timeline {
.activity-content {
.activity-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
.activity-title {
font-weight: 600;
color: #2c3e50;
}
}
.activity-description {
font-size: 14px;
color: #7f8c8d;
line-height: 1.5;
}
}
}
}
.security-section {
.security-card {
background: #f8f9fa;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
h3 {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
margin: 0 0 20px 0;
}
}
}
.avatar-upload {
text-align: center;
.avatar-uploader {
margin-bottom: 16px;
}
.upload-tips {
font-size: 12px;
color: #909399;
p {
margin: 4px 0;
}
}
}
@media (max-width: 768px) {
.profile-page {
padding: 16px;
}
.profile-header {
grid-template-columns: 1fr;
gap: 24px;
.user-avatar-section {
flex-direction: column;
text-align: center;
}
.user-stats-overview .stats-grid {
grid-template-columns: 1fr;
}
}
.stats-overview .overview-grid {
grid-template-columns: 1fr;
}
.torrents-section .section-header {
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
}
</style>