论坛,聊天室前后端对接

Change-Id: I90740329ab40dc050e8a791a382ab187900d673a
diff --git a/src/views/auth/ProfileView.vue b/src/views/auth/ProfileView.vue
index 8e37d76..640f893 100644
--- a/src/views/auth/ProfileView.vue
+++ b/src/views/auth/ProfileView.vue
@@ -1,364 +1,1222 @@
-<template>

-  <div class="profile-page">

-    <Navbar />

-    <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>

-              </el-form>

-            </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 { ElMessage } from 'element-plus'

-import {

-  Calendar,

-  Clock,

-  Upload,

-  Download,

-  TrendCharts,

-  Star,

-  QuestionFilled,

-  Plus

-} from '@element-plus/icons-vue'

-import Navbar from '@/components/Navbar.vue'

-

-export default {

-  name: 'ProfileView',

-  components: {

-    Navbar

-  },

-  setup() {

-    const activeTab = ref('info')

-    const showAvatarDialog = ref(false)

-    const saving = ref(false)

-    const uploadingAvatar = ref(false)

-

-    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 profileRules = {

-      email: [

-        { required: true, message: '请输入邮箱地址', trigger: 'blur' },

-        { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }

-      ]

-    }

-

-    const locationOptions = [

-      {

-        value: '北京市',

-        label: '北京市',

-        children: [

-          { value: '朝阳区', label: '朝阳区' },

-          { value: '海淀区', label: '海淀区' },

-          { value: '丰台区', label: '丰台区' }

-        ]

-      },

-      {

-        value: '上海市',

-        label: '上海市',

-        children: [

-          { value: '浦东新区', label: '浦东新区' },

-          { value: '黄浦区', label: '黄浦区' },

-          { value: '静安区', label: '静安区' }

-        ]

-      }

-    ]

-

-    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 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 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

-      }

-    }

-

-    return {

-      activeTab,

-      showAvatarDialog,

-      saving,

-      userProfile,

-      editProfile,

-      profileRules,

-      locationOptions,

-      formatDate,

-      formatTime,

-      getUserTitleType,

-      getRatioClass,

-      saveProfile

-    }

-  }

-}

-</script>

-

-<style scoped>

-/* 添加相关样式 */

-</style>

+<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">{{ userBase.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>加入时间:{{ userBase.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>{{ 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>{{ stats.downloaded || '-' }}</h3>
+                <p>下载量</p>
+              </div>
+            </div>
+            
+            <div class="stat-card">
+              <div class="stat-icon ratio" :class="getRatioClass(calcRatio)">
+                <el-icon size="32"><TrendCharts /></el-icon>
+              </div>
+              <div class="stat-info">
+                <h3>{{ calcRatio }}</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>{{ 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="stats">
+            <div class="stats-section" v-if="stats">
+              <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">{{ stats.uploaded || '-' }}</span>
+                      </div>
+                      <div class="detail-item">
+                        <span class="label">真实上传:</span>
+                        <span class="value">{{ stats.realUploaded || '-' }}</span>
+                      </div>
+                      <div class="detail-item">
+                        <span class="label">上传带宽:</span>
+                        <span class="value">{{ stats.uploadBandwidth || '-' }}</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">{{ stats.downloaded || '-' }}</span>
+                      </div>
+                      <div class="detail-item">
+                        <span class="label">真实下载:</span>
+                        <span class="value">{{ stats.realDownloaded || '-' }}</span>
+                      </div>
+                      <div class="detail-item">
+                        <span class="label">下载带宽:</span>
+                        <span class="value">{{ stats.downloadBandwidth || '-' }}</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">{{ stats.seeding || '-' }}</span>
+                      </div>
+                      <div class="detail-item">
+                        <span class="label">做种时间:</span>
+                        <span class="value">{{ stats.seedingTime || '-' }}</span>
+                      </div>
+                      <div class="detail-item">
+                        <span class="label">做种排名:</span>
+                        <span class="value">{{ stats.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">{{ stats.points || '-' }}</span>
+                      </div>
+                      <div class="detail-item">
+                        <span class="label">邀请名额:</span>
+                        <span class="value">{{ stats.inviteSlot || '-' }}</span>
+                      </div>
+                      <div class="detail-item">
+                        <span class="label">ISP:</span>
+                        <span class="value">{{ stats.isp || '-' }}</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
+                  :current-page="torrentsPage"
+                  :page-size="10"
+                  :total="userTorrents.length"
+                  layout="prev, pager, next"
+                  small
+                  @current-change="(page) => torrentsPage = page"
+                />
+              </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-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'
+import { userApi } from '@/api/user'
+
+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: '',
+      customTitle: '',
+      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)
+    })
+    
+    const stats = reactive({
+      uploaded: '-',
+      realUploaded: '-',
+      uploadBandwidth: '-',
+      downloaded: '-',
+      realDownloaded: '-',
+      downloadBandwidth: '-',
+      seeding: '-',
+      seedingTime: '-',
+      seedingRank: '-',
+      points: '-',
+      inviteSlot: '-',
+      isp: '-'
+    })
+    
+    const calcRatio = computed(() => {
+      if (!stats.uploaded || !stats.downloaded || stats.downloaded === '-' || stats.uploaded === '-') return '-'
+      const parseGB = (str) => {
+        if (typeof str !== 'string') return 0
+        if (str.endsWith('TB')) return parseFloat(str) * 1024
+        if (str.endsWith('GB')) return parseFloat(str)
+        if (str.endsWith('MB')) return parseFloat(str) / 1024
+        if (str.endsWith('KB')) return parseFloat(str) / 1024 / 1024
+        if (str.endsWith('B')) return parseFloat(str) / 1024 / 1024 / 1024
+        return parseFloat(str)
+      }
+      const up = parseGB(stats.uploaded)
+      const down = parseGB(stats.downloaded)
+      if (!down) return up > 0 ? '∞' : '0.00'
+      return (up / down).toFixed(2)
+    })
+    
+    const userBase = reactive({
+      username: '-',
+      joinDate: '-',
+    })
+    
+    onMounted(async () => {
+      try {
+        const res = await userApi.getCurrentUser()
+        if (res.user && res.user.user) {
+          const u = res.user.user
+          stats.uploaded = formatBytes(u.uploaded)
+          stats.realUploaded = formatBytes(u.realUploaded)
+          stats.uploadBandwidth = u.uploadBandwidth || '-'
+          stats.downloaded = formatBytes(u.downloaded)
+          stats.realDownloaded = formatBytes(u.realDownloaded)
+          stats.downloadBandwidth = u.downloadBandwidth || '-'
+          stats.seeding = u.seeding || '-'
+          stats.seedingTime = formatSeedingTime(u.seedingTime)
+          stats.seedingRank = u.seedingRank || '-'
+          stats.points = u.karma || '-'
+          stats.inviteSlot = u.inviteSlot || '-'
+          stats.isp = u.isp || '-'
+          userBase.username = u.username || '-'
+          userBase.joinDate = u.createdAt ? formatDate(u.createdAt) : '-'
+        }
+      } catch (e) {
+        ElMessage.error('获取数据失败')
+      }
+    })
+    
+    const getUserTitleByLevel = (level) => {
+      if (level == null) return '新手'
+      if (level >= 8) return '管理员'
+      if (level >= 6) return '资深会员'
+      if (level >= 4) return '正式会员'
+      if (level >= 2) return '初级会员'
+      return '新手'
+    }
+    
+    const formatBytes = (bytes) => {
+      if (!bytes || isNaN(bytes)) return '0 B'
+      const k = 1024
+      const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
+      const i = Math.floor(Math.log(bytes) / Math.log(k))
+      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+    }
+    
+    const formatSeedingTime = (seconds) => {
+      if (!seconds || isNaN(seconds)) return '0 小时'
+      const hours = Math.floor(seconds / 3600)
+      const days = Math.floor(hours / 24)
+      if (days > 0) {
+        return `${days} 天 ${hours % 24} 小时`
+      } else {
+        return `${hours} 小时`
+      }
+    }
+    
+    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 getLanguageName = (language) => {
+      const languages = {
+        'zh-CN': '简体中文',
+        'zh-TW': '繁体中文',
+        'en-US': 'English',
+        'ja-JP': '日本語',
+        'ko-KR': '한국어'
+      }
+      return languages[language] || language
+    }
+    
+    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 {
+        const payload = {
+          username: editProfile.username,
+          email: editProfile.email
+        }
+        await userApi.updateProfile(payload)
+        ElMessage.success('保存成功')
+      } catch (error) {
+        ElMessage.error('保存失败')
+      }
+    }
+    
+    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,
+      stats,
+      calcRatio,
+      userBase
+    }
+  }
+}
+</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;
+      }
+    }
+  }
+}
+
+.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>
\ No newline at end of file