524 lines
12 KiB
Vue
524 lines
12 KiB
Vue
<script lang="ts" setup>
|
||
import { Camera, Edit, Postcard, Promotion, SuccessFilled, User } from '@element-plus/icons-vue';
|
||
import { format } from 'date-fns';
|
||
import { computed, onMounted, ref } from 'vue';
|
||
import { getUserInfo } from '@/api';
|
||
import QrCodeLogin from '@/components/LoginDialog/components/QrCodeLogin/index.vue';
|
||
import { useUserStore } from '@/stores';
|
||
import { getUserProfilePicture, isUserVip, WECHAT_QRCODE_TYPE } from '@/utils/user.ts';
|
||
|
||
const userStore = useUserStore();
|
||
|
||
onMounted(async () => {
|
||
const resUserInfo = await getUserInfo();
|
||
userStore.setUserInfo(resUserInfo.data);
|
||
});
|
||
|
||
const user = computed(() => userStore.userInfo.user || {});
|
||
const wechatDialogVisible = ref(false);
|
||
|
||
// 计算属性
|
||
const userIcon = computed(() => {
|
||
return getUserProfilePicture() || `https://your-cdn.com/${user.value.icon}`;
|
||
});
|
||
|
||
const userNick = computed(() => {
|
||
return user.value.nick || user.value.userName || '未知用户';
|
||
});
|
||
|
||
// 是否绑定了微信
|
||
const isWechatBound = computed(() => {
|
||
return userStore.userInfo.isBindFuwuhao || false;
|
||
});
|
||
|
||
// 用户VIP状态
|
||
const userVipStatus = computed(() => {
|
||
return isUserVip();
|
||
});
|
||
|
||
// 格式化日期
|
||
function formatDate(dateString: string | null) {
|
||
if (!dateString)
|
||
return '-';
|
||
try {
|
||
return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss');
|
||
}
|
||
catch {
|
||
return dateString;
|
||
}
|
||
}
|
||
|
||
// 性别显示
|
||
function getSexText(sex: string | null) {
|
||
const sexMap: Record<string, string> = {
|
||
Male: '男',
|
||
Female: '女',
|
||
Unknown: '未知',
|
||
};
|
||
return sexMap[sex || 'Unknown'] || '未知';
|
||
}
|
||
|
||
function getSexTagType(sex: string | null) {
|
||
const typeMap: Record<string, string> = {
|
||
Male: 'primary',
|
||
Female: 'danger',
|
||
Unknown: 'info',
|
||
};
|
||
return typeMap[sex || 'Unknown'] || 'info';
|
||
}
|
||
|
||
// 敏感信息脱敏
|
||
function maskEmail(email: string) {
|
||
if (!email)
|
||
return '';
|
||
const [name, domain] = email.split('@');
|
||
if (name.length <= 2)
|
||
return email;
|
||
return `${name.substring(0, 2)}****@${domain}`;
|
||
}
|
||
|
||
function maskPhone(phone: number) {
|
||
if (!phone)
|
||
return '';
|
||
return phone.toString().replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
|
||
}
|
||
|
||
// 操作处理
|
||
function handleEdit() {
|
||
ElMessage.info('编辑功能开发中');
|
||
}
|
||
|
||
function changeAvatar() {
|
||
ElMessage.info('更换头像功能开发中');
|
||
}
|
||
|
||
function handleWechatBind() {
|
||
wechatDialogVisible.value = true;
|
||
}
|
||
|
||
// 微信绑定成功
|
||
function bindWechat() {
|
||
wechatDialogVisible.value = false;
|
||
ElMessage.success('微信绑定成功!');
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="user-profile">
|
||
<!-- 用户卡片 -->
|
||
<el-card class="profile-card" shadow="hover">
|
||
<!-- 顶部标题 -->
|
||
<div class="header">
|
||
<h2>
|
||
<el-icon><User /></el-icon>
|
||
个人信息
|
||
</h2>
|
||
</div>
|
||
<!-- 头像和基本信息区域 -->
|
||
<div class="user-header-section">
|
||
<!-- 头像区域 -->
|
||
<div class="avatar-section">
|
||
<div class="avatar-wrapper">
|
||
<el-avatar :size="120" :src="userIcon" class="user-avatar">
|
||
{{ userNick.charAt(0) }}
|
||
</el-avatar>
|
||
<div v-if="userVipStatus" class="vip-badge">
|
||
<el-icon><Promotion /></el-icon>
|
||
VIP
|
||
</div>
|
||
</div>
|
||
<div v-if="false" class="avatar-actions">
|
||
<el-button size="small" type="primary" @click="changeAvatar">
|
||
<el-icon><Camera /></el-icon>
|
||
更换头像
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 用户名称和状态 -->
|
||
<div class="user-info-quick">
|
||
<h3 class="user-name">
|
||
{{ userNick }}
|
||
</h3>
|
||
<div class="user-tags">
|
||
<el-tag v-if="userVipStatus" type="warning" effect="dark" size="large">
|
||
<el-icon><Promotion /></el-icon>
|
||
尊享VIP会员
|
||
</el-tag>
|
||
<el-tag v-else type="info" size="large">
|
||
普通用户
|
||
</el-tag>
|
||
<el-tag :type="getSexTagType(user.sex)" size="large">
|
||
{{ getSexText(user.sex) }}
|
||
</el-tag>
|
||
</div>
|
||
<div class="user-stats">
|
||
<div class="stat-item">
|
||
<div class="stat-value">
|
||
{{ formatDate(user.creationTime)?.split(' ')[0] || '-' }}
|
||
</div>
|
||
<div class="stat-label">
|
||
注册时间
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<el-divider />
|
||
|
||
<!-- 详细信息区域 -->
|
||
<div class="info-section">
|
||
<div class="info-grid">
|
||
<!-- 用户名 -->
|
||
<div class="info-item">
|
||
<div class="info-label">
|
||
<el-icon><User /></el-icon>
|
||
用户名
|
||
</div>
|
||
<div class="info-value">
|
||
{{ user.userName || '-' }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 昵称 -->
|
||
<div class="info-item">
|
||
<div class="info-label">
|
||
<el-icon><Postcard /></el-icon>
|
||
昵称
|
||
</div>
|
||
<div class="info-value">
|
||
{{ userNick }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 邮箱 -->
|
||
<div class="info-item">
|
||
<div class="info-label">
|
||
<el-icon><Message /></el-icon>
|
||
邮箱
|
||
</div>
|
||
<div class="info-value">
|
||
<span v-if="user.email">
|
||
{{ maskEmail(user.email) }}
|
||
<el-tooltip content="已验证" placement="top">
|
||
<el-icon color="#67C23A" style="margin-left: 5px;"><SuccessFilled /></el-icon>
|
||
</el-tooltip>
|
||
</span>
|
||
<span v-else class="unset-text">未设置</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 手机号 -->
|
||
<div class="info-item">
|
||
<div class="info-label">
|
||
<el-icon><Phone /></el-icon>
|
||
手机号
|
||
</div>
|
||
<div class="info-value">
|
||
<span v-if="user.phone">{{ maskPhone(user.phone) }}</span>
|
||
<span v-else class="unset-text">未设置</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 微信绑定 -->
|
||
<div class="info-item full-width">
|
||
<div class="info-label">
|
||
<el-icon color="#07C160">
|
||
<ChatDotRound />
|
||
</el-icon>
|
||
微信绑定
|
||
</div>
|
||
<div class="info-value wechat-binding">
|
||
<span v-if="isWechatBound" class="wechat-status">
|
||
<el-icon color="#07C160"><SuccessFilled /></el-icon>
|
||
已绑定
|
||
</span>
|
||
<span v-else class="wechat-status unset-text">
|
||
未绑定
|
||
</span>
|
||
<el-button
|
||
v-if="!isWechatBound"
|
||
type="success"
|
||
size="small"
|
||
@click="handleWechatBind"
|
||
>
|
||
立即绑定
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 个人简介 -->
|
||
<div class="info-item full-width">
|
||
<div class="info-label">
|
||
<el-icon><Document /></el-icon>
|
||
个人简介
|
||
</div>
|
||
<div class="info-value">
|
||
<span v-if="user.introduction">{{ user.introduction }}</span>
|
||
<span v-else class="unset-text">暂无简介</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 操作按钮(预留) -->
|
||
<div v-if="false" class="action-section">
|
||
<el-button type="primary" @click="handleEdit">
|
||
<el-icon><Edit /></el-icon>
|
||
编辑资料
|
||
</el-button>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 微信绑定对话框 -->
|
||
<el-dialog
|
||
v-model="wechatDialogVisible"
|
||
title="微信绑定"
|
||
width="450px"
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div class="wechat-dialog">
|
||
<div class="wechat-tip">
|
||
<el-alert
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
>
|
||
<template #title>
|
||
<div>请使用微信扫描下方二维码完成绑定</div>
|
||
</template>
|
||
</el-alert>
|
||
</div>
|
||
<QrCodeLogin :type="WECHAT_QRCODE_TYPE.Bind" @bind-wechat="bindWechat()" />
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="wechatDialogVisible = false">
|
||
取消
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.user-profile {
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.header h2 {
|
||
display: flex;
|
||
align-items: center;
|
||
margin: 0;
|
||
font-size: 20px;
|
||
color: #333;
|
||
}
|
||
|
||
.header .el-icon {
|
||
margin-right: 8px;
|
||
color: #409eff;
|
||
}
|
||
|
||
.profile-card {
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* 用户头部区域 */
|
||
.user-header-section {
|
||
display: flex;
|
||
gap: 30px;
|
||
align-items: center;
|
||
padding: 20px 0;
|
||
}
|
||
|
||
.avatar-section {
|
||
text-align: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.avatar-wrapper {
|
||
position: relative;
|
||
display: inline-block;
|
||
}
|
||
|
||
.user-avatar {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
font-size: 48px;
|
||
font-weight: bold;
|
||
border: 4px solid #fff;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.vip-badge {
|
||
position: absolute;
|
||
bottom: 0;
|
||
right: -10px;
|
||
background: linear-gradient(135deg, #f5af19 0%, #f12711 100%);
|
||
color: white;
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.avatar-actions {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.user-info-quick {
|
||
flex: 1;
|
||
}
|
||
|
||
.user-name {
|
||
font-size: 28px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin: 0 0 15px 0;
|
||
}
|
||
|
||
.user-tags {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.user-stats {
|
||
display: flex;
|
||
gap: 30px;
|
||
}
|
||
|
||
.stat-item {
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #409eff;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 13px;
|
||
color: #909399;
|
||
}
|
||
|
||
/* 信息区域 */
|
||
.info-section {
|
||
padding: 10px 0;
|
||
}
|
||
|
||
.info-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 20px;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 15px;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.info-item:hover {
|
||
background: #e9ecef;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.info-item.full-width {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.info-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 15px;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.unset-text {
|
||
color: #999;
|
||
font-style: italic;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.wechat-binding {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.wechat-status {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
/* 操作区域 */
|
||
.action-section {
|
||
margin-top: 30px;
|
||
padding-top: 20px;
|
||
border-top: 1px solid #f0f0f0;
|
||
text-align: center;
|
||
}
|
||
|
||
/* 微信绑定对话框 */
|
||
.wechat-dialog {
|
||
text-align: center;
|
||
}
|
||
|
||
.wechat-tip {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
/* 响应式 */
|
||
@media (max-width: 768px) {
|
||
.user-header-section {
|
||
flex-direction: column;
|
||
text-align: center;
|
||
}
|
||
|
||
.user-info-quick {
|
||
width: 100%;
|
||
}
|
||
|
||
.user-stats {
|
||
justify-content: center;
|
||
}
|
||
|
||
.info-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.info-item.full-width {
|
||
grid-column: 1;
|
||
}
|
||
}
|
||
</style>
|