增加个人中心

This commit is contained in:
2026-02-23 02:27:32 +08:00
parent 5b3047f675
commit af23c8779e
8 changed files with 434 additions and 428 deletions

View File

@@ -0,0 +1,385 @@
<script setup>
import { computed, onMounted } from 'vue'
import { useUserStore } from '@/stores/user'
import {
UserOutlined,
DatabaseOutlined,
WalletOutlined,
PayCircleOutlined,
ClockCircleOutlined,
SafetyCertificateOutlined
} from '@ant-design/icons-vue'
const userStore = useUserStore()
// 存储空间数据
const GB_TO_MB = 1024
const totalStorage = computed(() => userStore.totalStorage * GB_TO_MB)
const usedStorage = computed(() => userStore.usedStorage * GB_TO_MB)
const storagePercent = computed(() => {
if (totalStorage.value === 0) return 0
return Math.min(100, (usedStorage.value / totalStorage.value) * 100)
})
// 格式化函数
function formatStorage(mb) {
if (mb >= 1024) {
return (mb / 1024).toFixed(1) + ' GB'
}
return Math.round(mb) + ' MB'
}
function formatMoney(amount) {
return amount?.toFixed(2) || '0.00'
}
function formatCredits(credits) {
return credits?.toLocaleString() || '0'
}
// 格式化日期
function formatDate(dateStr) {
if (!dateStr) return '未设置'
const date = new Date(dateStr)
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).replace(/\//g, '-')
}
// 脱敏手机号
function maskMobile(mobile) {
if (!mobile) return '未设置'
return mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}
onMounted(async () => {
if (userStore.isLoggedIn) {
// 获取用户基本信息和档案信息
if (!userStore.mobile) {
await userStore.fetchUserInfo()
}
if (!userStore.profile) {
await userStore.fetchUserProfile()
}
}
})
</script>
<template>
<div class="profile-container">
<div class="page-header">
<h1 class="page-title">个人中心</h1>
<p class="page-subtitle">管理您的账户信息和资源使用情况</p>
</div>
<a-row :gutter="[24, 24]">
<!-- 左侧用户信息卡片 -->
<a-col :xs="24" :lg="8">
<a-card class="user-card" :bordered="false">
<div class="user-info-header">
<div class="avatar-wrapper">
<img
v-if="userStore.displayAvatar"
class="user-avatar"
:src="userStore.displayAvatar"
alt="avatar"
/>
<div v-else class="user-avatar-placeholder">
{{ userStore.displayName?.charAt(0) || 'U' }}
</div>
</div>
<h2 class="user-name">{{ userStore.displayName }}</h2>
<div class="user-id">ID: {{ userStore.userId || '未设置' }}</div>
<div class="user-role-badge">普通用户</div>
</div>
<a-divider />
<div class="user-details">
<div class="detail-item">
<span class="detail-label">注册时间</span>
<span class="detail-value">{{ formatDate(userStore.profile?.registerTime || userStore.profile?.createTime) }}</span>
</div>
<div class="detail-item">
<span class="detail-label">绑定手机</span>
<span class="detail-value">{{ maskMobile(userStore.mobile || userStore.profile?.mobile) }}</span>
</div>
</div>
</a-card>
</a-col>
<!-- 右侧资源统计与活动 -->
<a-col :xs="24" :lg="16">
<!-- 资源概览卡片 -->
<a-row :gutter="[24, 24]">
<a-col :xs="24" :sm="12" :md="8">
<a-card class="stat-card" :bordered="false">
<div class="stat-icon-wrapper blue">
<DatabaseOutlined />
</div>
<div class="stat-content">
<div class="stat-label">存储空间</div>
<div class="stat-value">{{ formatStorage(usedStorage) }} <span class="stat-unit">/ {{ formatStorage(totalStorage) }}</span></div>
<a-progress
:percent="storagePercent"
:show-info="false"
:stroke-color="{ '0%': '#108ee9', '100%': '#87d068' }"
size="small"
class="stat-progress"
/>
</div>
</a-card>
</a-col>
<a-col :xs="24" :sm="12" :md="8">
<a-card class="stat-card" :bordered="false">
<div class="stat-icon-wrapper purple">
<WalletOutlined />
</div>
<div class="stat-content">
<div class="stat-label">剩余积分</div>
<div class="stat-value">{{ formatCredits(userStore.remainingPoints) }}</div>
<div class="stat-desc">用于AI生成消耗</div>
</div>
</a-card>
</a-col>
<a-col :xs="24" :sm="12" :md="8">
<a-card class="stat-card" :bordered="false">
<div class="stat-icon-wrapper orange">
<PayCircleOutlined />
</div>
<div class="stat-content">
<div class="stat-label">账户余额</div>
<div class="stat-value">¥{{ formatMoney(userStore.totalRecharge) }}</div>
<div class="stat-desc">累计充值金额</div>
</div>
</a-card>
</a-col>
</a-row>
<!-- 最近活动/设置占位 -->
<a-card title="最近活动" :bordered="false" class="activity-card mt-6">
<template #extra>
<a href="#">查看全部</a>
</template>
<a-list item-layout="horizontal" :data-source="[]">
<template #renderItem="{ item }">
<!-- 这里是空列表暂时留白 -->
</template>
<div class="empty-state">
<ClockCircleOutlined class="empty-icon" />
<p>暂无最近活动记录</p>
</div>
</a-list>
</a-card>
</a-col>
</a-row>
</div>
</template>
<style scoped>
.profile-container {
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
margin-bottom: 32px;
}
.page-title {
font-size: 28px;
font-weight: 700;
color: var(--color-text);
margin-bottom: 8px;
}
.page-subtitle {
color: var(--color-text-secondary);
font-size: 14px;
}
/* User Card */
.user-card {
text-align: center;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
background: var(--color-bg-container, #fff);
height: 100%;
}
.user-info-header {
padding: 24px 0;
display: flex;
flex-direction: column;
align-items: center;
}
.avatar-wrapper {
margin-bottom: 16px;
position: relative;
}
.user-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
border: 4px solid rgba(var(--primary-color-rgb, 24, 144, 255), 0.1);
}
.user-avatar-placeholder {
width: 100px;
height: 100px;
border-radius: 50%;
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 40px;
font-weight: 600;
border: 4px solid rgba(24, 144, 255, 0.1);
}
.user-name {
font-size: 20px;
font-weight: 600;
margin-bottom: 4px;
color: var(--color-text);
}
.user-id {
color: var(--color-text-secondary);
font-size: 13px;
margin-bottom: 12px;
}
.user-role-badge {
display: inline-block;
padding: 4px 12px;
background: rgba(24, 144, 255, 0.1);
color: #1890ff;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
}
.user-details {
padding: 16px 0;
}
.detail-item {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid var(--color-border-secondary, #f0f0f0);
}
.detail-item:last-child {
border-bottom: none;
}
.detail-label {
color: var(--color-text-secondary);
}
.detail-value {
color: var(--color-text);
font-weight: 500;
}
/* Stat Cards */
.stat-card {
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
}
.stat-icon-wrapper {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
margin-bottom: 16px;
}
.stat-icon-wrapper.blue {
background: rgba(24, 144, 255, 0.1);
color: #1890ff;
}
.stat-icon-wrapper.purple {
background: rgba(114, 46, 209, 0.1);
color: #722ed1;
}
.stat-icon-wrapper.orange {
background: rgba(250, 173, 20, 0.1);
color: #faad14;
}
.stat-label {
font-size: 14px;
color: var(--color-text-secondary);
margin-bottom: 4px;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: var(--color-text);
margin-bottom: 8px;
}
.stat-unit {
font-size: 14px;
font-weight: 400;
color: var(--color-text-third);
}
.stat-desc {
font-size: 12px;
color: var(--color-text-third);
}
.stat-progress {
margin-top: 8px;
}
.mt-6 {
margin-top: 24px;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 40px 0;
color: var(--color-text-third);
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.security-icon {
font-size: 24px;
color: #52c41a;
margin-right: 12px;
}
</style>