blob: 8e37d76b542f95273cccc18d344c94429f062478 [file] [log] [blame]
Xing Jinwenff16b1e2025-06-05 00:29:26 +08001<template>
2 <div class="profile-page">
Xing Jinwenf711e2e2025-06-07 21:57:01 +08003 <Navbar />
Xing Jinwenff16b1e2025-06-05 00:29:26 +08004 <div class="page-container">
5 <!-- 个人信息卡片 -->
6 <div class="profile-header">
7 <div class="user-avatar-section">
8 <div class="avatar-container">
9 <el-avatar :size="120" :src="userProfile.avatar">
10 {{ userProfile.username.charAt(0).toUpperCase() }}
11 </el-avatar>
12 <el-button
13 type="primary"
14 size="small"
15 class="change-avatar-btn"
16 @click="showAvatarDialog = true"
17 >
18 更换头像
19 </el-button>
20 </div>
21
22 <div class="user-basic-info">
23 <h1 class="username">{{ userProfile.username }}</h1>
24 <div class="user-title">
25 <el-tag :type="getUserTitleType(userProfile.userLevel)" size="large">
26 {{ userProfile.userTitle }}
27 </el-tag>
28 </div>
29 <div class="join-info">
30 <el-icon><Calendar /></el-icon>
31 <span>加入时间:{{ formatDate(userProfile.joinDate) }}</span>
32 </div>
33 <div class="last-login">
34 <el-icon><Clock /></el-icon>
35 <span>最后登录:{{ formatTime(userProfile.lastLogin) }}</span>
36 </div>
37 </div>
38 </div>
39
40 <div class="user-stats-overview">
41 <div class="stats-grid">
42 <div class="stat-card">
43 <div class="stat-icon upload">
44 <el-icon size="32"><Upload /></el-icon>
45 </div>
46 <div class="stat-info">
47 <h3>{{ userProfile.stats.uploaded }}</h3>
48 <p>上传量</p>
49 </div>
50 </div>
51
52 <div class="stat-card">
53 <div class="stat-icon download">
54 <el-icon size="32"><Download /></el-icon>
55 </div>
56 <div class="stat-info">
57 <h3>{{ userProfile.stats.downloaded }}</h3>
58 <p>下载量</p>
59 </div>
60 </div>
61
62 <div class="stat-card">
63 <div class="stat-icon ratio" :class="getRatioClass(userProfile.stats.ratio)">
64 <el-icon size="32"><TrendCharts /></el-icon>
65 </div>
66 <div class="stat-info">
67 <h3>{{ userProfile.stats.ratio }}</h3>
68 <p>分享率</p>
69 </div>
70 </div>
71
72 <div class="stat-card">
73 <div class="stat-icon points">
74 <el-icon size="32"><Star /></el-icon>
75 </div>
76 <div class="stat-info">
77 <h3>{{ userProfile.stats.points }}</h3>
78 <p>积分</p>
79 </div>
80 </div>
81 </div>
82 </div>
83 </div>
84
85 <!-- 详细信息选项卡 -->
86 <div class="profile-content">
87 <el-tabs v-model="activeTab" type="border-card">
88 <!-- 个人信息 -->
89 <el-tab-pane label="个人信息" name="info">
90 <div class="info-section">
91 <el-form
92 ref="profileFormRef"
93 :model="editProfile"
94 :rules="profileRules"
95 label-width="120px"
96 size="large"
97 >
98 <div class="form-section">
99 <h3>基本信息</h3>
100 <el-form-item label="用户名">
101 <el-input v-model="editProfile.username" disabled>
102 <template #suffix>
103 <el-tooltip content="用户名不可修改">
104 <el-icon><QuestionFilled /></el-icon>
105 </el-tooltip>
106 </template>
107 </el-input>
108 </el-form-item>
109
110 <el-form-item label="邮箱地址" prop="email">
111 <el-input v-model="editProfile.email" type="email" />
112 </el-form-item>
113
114 <el-form-item label="真实姓名" prop="realName">
115 <el-input v-model="editProfile.realName" placeholder="可选填写" />
116 </el-form-item>
117
118 <el-form-item label="所在地区">
119 <el-cascader
120 v-model="editProfile.location"
121 :options="locationOptions"
122 placeholder="请选择地区"
123 clearable
124 />
125 </el-form-item>
126 </div>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800127 </el-form>
128 </div>
129 </el-tab-pane>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800130 </el-tabs>
131 </div>
132 </div>
133
134 <!-- 更换头像对话框 -->
135 <el-dialog v-model="showAvatarDialog" title="更换头像" width="400px">
136 <div class="avatar-upload">
137 <el-upload
138 ref="avatarUploadRef"
139 :auto-upload="false"
140 :limit="1"
141 accept="image/*"
142 :on-change="handleAvatarChange"
143 list-type="picture-card"
144 class="avatar-uploader"
145 >
146 <el-icon><Plus /></el-icon>
147 </el-upload>
148 <div class="upload-tips">
149 <p>支持 JPG、PNG 格式</p>
150 <p>建议尺寸 200x200 像素</p>
151 <p>文件大小不超过 2MB</p>
152 </div>
153 </div>
154
155 <template #footer>
156 <el-button @click="showAvatarDialog = false">取消</el-button>
157 <el-button type="primary" @click="uploadAvatar" :loading="uploadingAvatar">
158 上传头像
159 </el-button>
160 </template>
161 </el-dialog>
162 </div>
163</template>
164
165<script>
166import { ref, reactive, computed, onMounted, nextTick } from 'vue'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800167import { ElMessage } from 'element-plus'
168import {
169 Calendar,
170 Clock,
171 Upload,
172 Download,
173 TrendCharts,
174 Star,
175 QuestionFilled,
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800176 Plus
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800177} from '@element-plus/icons-vue'
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800178import Navbar from '@/components/Navbar.vue'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800179
180export default {
181 name: 'ProfileView',
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800182 components: {
183 Navbar
184 },
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800185 setup() {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800186 const activeTab = ref('info')
187 const showAvatarDialog = ref(false)
188 const saving = ref(false)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800189 const uploadingAvatar = ref(false)
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800190
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800191 const userProfile = ref({
192 username: 'MovieExpert',
193 email: 'movieexpert@example.com',
194 realName: '',
195 avatar: '',
196 userLevel: 5,
197 userTitle: '资深会员',
198 joinDate: '2023-01-15T10:00:00',
199 lastLogin: '2025-06-03T14:30:00',
200 location: ['北京市', '朝阳区'],
201 signature: '热爱电影,分享快乐!',
202 website: 'https://movieblog.com',
203 interests: ['电影', '音乐', '科技', '摄影'],
204 emailPublic: false,
205 statsPublic: true,
206 activityPublic: true,
207 stats: {
208 uploaded: '256.8 GB',
209 downloaded: '89.6 GB',
210 ratio: '2.87',
211 points: '15,680'
212 },
213 detailedStats: {
214 totalUploaded: '256.8 GB',
215 uploadedTorrents: 45,
216 avgUploadSize: '5.7 GB',
217 totalDownloaded: '89.6 GB',
218 downloadedTorrents: 123,
219 completedTorrents: 118,
220 seeding: 32,
221 seedingTime: '1,245 小时',
222 seedingRank: 86,
223 totalEarnedPoints: '28,940',
224 totalSpentPoints: '13,260'
225 }
226 })
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800227
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800228 const editProfile = reactive({
229 username: '',
230 email: '',
231 realName: '',
232 location: [],
233 signature: '',
234 website: '',
235 interests: [],
236 emailPublic: false,
237 statsPublic: true,
238 activityPublic: true
239 })
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800240
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800241 const profileRules = {
242 email: [
243 { required: true, message: '请输入邮箱地址', trigger: 'blur' },
244 { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
245 ]
246 }
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800247
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800248 const locationOptions = [
249 {
250 value: '北京市',
251 label: '北京市',
252 children: [
253 { value: '朝阳区', label: '朝阳区' },
254 { value: '海淀区', label: '海淀区' },
255 { value: '丰台区', label: '丰台区' }
256 ]
257 },
258 {
259 value: '上海市',
260 label: '上海市',
261 children: [
262 { value: '浦东新区', label: '浦东新区' },
263 { value: '黄浦区', label: '黄浦区' },
264 { value: '静安区', label: '静安区' }
265 ]
266 }
267 ]
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800268
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800269 onMounted(() => {
270 loadUserProfile()
271 })
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800272
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800273 const loadUserProfile = () => {
274 // 加载用户资料到编辑表单
275 Object.assign(editProfile, {
276 username: userProfile.value.username,
277 email: userProfile.value.email,
278 realName: userProfile.value.realName,
279 location: userProfile.value.location,
280 signature: userProfile.value.signature,
281 website: userProfile.value.website,
282 interests: [...userProfile.value.interests],
283 emailPublic: userProfile.value.emailPublic,
284 statsPublic: userProfile.value.statsPublic,
285 activityPublic: userProfile.value.activityPublic
286 })
287 }
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800288
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800289 const formatDate = (dateString) => {
290 const date = new Date(dateString)
291 return date.toLocaleDateString('zh-CN')
292 }
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800293
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800294 const formatTime = (timeString) => {
295 const date = new Date(timeString)
296 const now = new Date()
297 const diff = now - date
298 const hours = Math.floor(diff / (1000 * 60 * 60))
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800299
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800300 if (hours < 1) return '刚刚'
301 if (hours < 24) return `${hours}小时前`
302 const days = Math.floor(hours / 24)
303 if (days < 7) return `${days}天前`
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800304
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800305 return date.toLocaleDateString('zh-CN')
306 }
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800307
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800308 const getUserTitleType = (level) => {
309 if (level >= 8) return 'danger' // 管理员
310 if (level >= 6) return 'warning' // 资深会员
311 if (level >= 4) return 'success' // 正式会员
312 if (level >= 2) return 'info' // 初级会员
313 return 'default' // 新手
314 }
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800315
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800316 const getRatioClass = (ratio) => {
317 const r = parseFloat(ratio)
318 if (r >= 2) return 'excellent'
319 if (r >= 1) return 'good'
320 return 'warning'
321 }
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800322
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800323 const saveProfile = async () => {
324 try {
325 await profileFormRef.value?.validate()
326
327 saving.value = true
328
329 // 模拟保存过程
330 await new Promise(resolve => setTimeout(resolve, 1500))
331
332 // 更新用户资料
333 Object.assign(userProfile.value, editProfile)
334
335 ElMessage.success('个人资料保存成功')
336
337 } catch (error) {
338 console.error('表单验证失败:', error)
339 } finally {
340 saving.value = false
341 }
342 }
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800343
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800344 return {
345 activeTab,
346 showAvatarDialog,
347 saving,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800348 userProfile,
349 editProfile,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800350 profileRules,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800351 locationOptions,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800352 formatDate,
353 formatTime,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800354 getUserTitleType,
355 getRatioClass,
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800356 saveProfile
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800357 }
358 }
359}
360</script>
361
Xing Jinwenf711e2e2025-06-07 21:57:01 +0800362<style scoped>
363/* 添加相关样式 */
364</style>