topfans/frontend/pages/profile/profile.vue
2026-04-13 17:34:03 +08:00

2001 lines
47 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="profile-container">
<!-- 背景图片 - 固定在容器上 -->
<image class="background-image" src="/static/background/starbook.png" mode="aspectFill"></image>
<scroll-view class="profile-scroll" scroll-y :show-scrollbar="false" :enable-back-to-top="true">
<!-- 上半部分用户信息卡片 -->
<view class="top-section">
<!-- 蒙层 -->
<view class="background-overlay"></view>
<!-- Header组件 -->
<Header :showBack="true" :showGuideIcon="false" :showTaskIcon="false" :showStarActivityIcon="false" />
<!-- 用户信息卡片 -->
<view class="user-info-card">
<!-- 上半部分头像和用户信息左右布局 -->
<view class="user-main-info">
<view @tap="handleAvatarClick" class="avatar-container">
<Avatar :key="avatarKey" :userId="uid" :size="180" :borderWidth="6" :showLevel="true"
:level="fanLevel" :avatarUrl="userAvatarUrl" />
</view>
<view class="user-text-info">
<!-- 第1行等级 + 昵称 + 粉丝标签 -->
<view class="name-wrapper">
<!-- <text class="level-badge">LV {{ fanLevel }}</text> -->
<text class="user-name">{{ nickname }}</text>
<view class="fan-tag-badge" v-if="fanTag" @tap="handleFanTagClick">
<text class="fan-tag-text">{{ fanTag }}</text>
<image class="fan-tag-arrow" src="/static/icon/dropdown-arrow.png" mode="aspectFit">
</image>
</view>
</view>
<!-- 第2行DID -->
<text class="user-did">数字身份DID: {{ uid }}</text>
<!-- 第3行区块链地址 -->
<view class="blockchain-address-wrapper">
<text class="blockchain-address-text">链上地址: {{ displayAddress }}</text>
<view class="copy-icon" @tap="copyAddress">
<text class="copy-icon-text">📋</text>
</view>
</view>
<!-- 第4行手机号 -->
<text class="user-mobile" v-if="displayMobile">{{ displayMobile }}</text>
</view>
</view>
</view>
</view>
<!-- 下半部分:白色背景区域 -->
<view class="bottom-section">
<view class="content-area">
<!-- 我的资产 -->
<view class="section-bg" style="padding-top: 0;">
<view class="section-title">我的资产</view>
<view class="assets-grid">
<view class="asset-card" @tap="handleAssetClick('exhibition')">
<image class="asset-icon" src="/static/icon/square.png" mode="aspectFit"></image>
<text class="asset-text">我的展馆</text>
<!-- <text class="arrow"></text> -->
</view>
<view class="asset-card" @tap="handleAssetClick('starbook')">
<image class="asset-icon" src="/static/icon/starbook.png" mode="aspectFit"></image>
<text class="asset-text">我的星册</text>
<!-- <text class="arrow"></text> -->
</view>
</view>
</view>
<!-- 服务与工具 -->
<view class="section-bg">
<view class="section-title">服务与工具</view>
<view class="service-buttons-container">
<!-- 新手指引入口 -->
<view class="service-button" @tap="handleGuideClick">
<image class="service-icon" src="/static/icon/onboarding.png" mode="aspectFit"></image>
<text class="service-text">新手指引</text>
<text v-if="guideClaimableCount > 0" class="guide-badge">{{ guideClaimableCount }}</text>
</view>
<view class="service-button" @tap="handleChangeNickname">
<image class="service-icon" src="/static/icon/edit-nickname.png" mode="aspectFit"></image>
<text class="service-text">修改昵称</text>
</view>
<view class="service-button" @tap="handleChangePassword">
<image class="service-icon" src="/static/icon/edit-password.png" mode="aspectFit"></image>
<text class="service-text">修改密码</text>
</view>
<view class="service-button" @tap="handleSwitchRole">
<image class="service-icon" src="/static/icon/switch-account.png" mode="aspectFit"></image>
<text class="service-text">添加身份</text>
</view>
<view class="service-button" @tap="handleDeleteAccount">
<image class="service-icon" src="/static/icon/logout.png" mode="aspectFit"></image>
<text class="service-text">注销账号</text>
</view>
</view>
</view>
</view>
<!-- 修改昵称弹窗 -->
<view class="nickname-modal" v-if="showNicknameModal" @tap="closeNicknameModal">
<view class="modal-content" @tap.stop>
<view class="modal-title">修改昵称</view>
<input class="modal-input" v-model="newNickname" placeholder="请输入新昵称" maxlength="20" />
<view class="modal-buttons">
<button class="modal-btn-cancel" @tap="closeNicknameModal">取消</button>
<button class="modal-btn-confirm" @tap="confirmChangeNickname">确认</button>
</view>
</view>
</view>
<!-- 修改密码弹窗 -->
<view class="password-modal" v-if="showPasswordModal" @tap="closePasswordModal">
<view class="modal-content" @tap.stop>
<view class="modal-title">修改密码</view>
<!-- 旧密码输入框 -->
<view class="modal-password-wrapper">
<input class="modal-password-input" :type="showOldPassword ? 'text' : 'password'"
v-model="oldPassword" placeholder="请输入旧密码" placeholder-class="input-placeholder" />
<view class="modal-eye-icon" @click="showOldPassword = !showOldPassword">
<text class="eye-text">{{ showOldPassword ? '👁️' : '👁️‍🗨️' }}</text>
</view>
</view>
<!-- 新密码输入框 -->
<view class="modal-password-wrapper">
<input class="modal-password-input" :type="showNewPassword ? 'text' : 'password'"
v-model="newPassword" placeholder="请输入新密码" placeholder-class="input-placeholder" />
<view class="modal-eye-icon" @click="showNewPassword = !showNewPassword">
<text class="eye-text">{{ showNewPassword ? '👁️' : '👁️‍🗨️' }}</text>
</view>
</view>
<view class="modal-buttons">
<button class="modal-btn-cancel" @tap="closePasswordModal">取消</button>
<button class="modal-btn-confirm" @tap="confirmChangePassword">确认</button>
</view>
</view>
</view>
<!-- 添加身份弹窗 -->
<view class="add-identity-modal" v-if="showAddIdentityModal" @tap="closeAddIdentityModal">
<view class="modal-content" @tap.stop>
<view class="modal-title">添加身份</view>
<view class="modal-subtitle">选择您喜欢的明星成为TA的粉丝</view>
<!-- 明星选择列表 -->
<view class="star-list">
<view v-for="star in fanIdentitiesList" :key="star.star_id" class="star-item"
:class="{ 'star-item-selected': selectedStarId === star.star_id }"
@tap="selectStar(star.star_id)">
<view class="star-info">
<text class="star-name">{{ star.name }}</text>
<text class="star-tag" v-if="star.tag">{{ star.tag }}</text>
</view>
<view class="star-radio">
<view v-if="selectedStarId === star.star_id" class="star-radio-checked"></view>
</view>
</view>
</view>
<!-- 昵称输入框 -->
<input class="modal-input" v-model="newIdentityNickname" placeholder="请输入您的新昵称" maxlength="20" />
<view class="modal-buttons">
<button class="modal-btn-cancel" @tap="closeAddIdentityModal">取消</button>
<button class="modal-btn-confirm" @tap="confirmAddIdentity">确认</button>
</view>
</view>
</view>
<!-- 身份切换下拉列表 -->
<view class="identity-dropdown-modal" v-if="showIdentityDropdown" @tap="closeIdentityDropdown">
<view class="identity-dropdown-content" @tap.stop>
<view class="identity-dropdown-title">切换粉丝身份</view>
<view class="identity-list">
<view v-for="identity in myFanIdentities" :key="identity.star_id" class="identity-item" :class="{
'identity-item-active': identity.star_id === currentStarId,
'identity-item-disabled': identity.star_id === currentStarId
}" @tap="handleSwitchIdentity(identity.star_id)">
<view class="identity-info">
<view class="identity-main">
<text class="identity-star-name">{{ identity.star_name }}</text>
<text class="identity-star-tag">{{ identity.star_tag }}</text>
</view>
<text class="identity-nickname">昵称: {{ identity.nickname }}</text>
</view>
<view v-if="identity.star_id === currentStarId" class="identity-current-badge">
<text class="identity-current-text">当前身份</text>
</view>
</view>
</view>
</view>
</view>
<!-- 注销账号确认弹窗 -->
<view class="confirm-modal" v-if="showDeleteAccountModal" @tap="closeDeleteAccountModal">
<view class="modal-content" @tap.stop>
<view class="modal-title">注销账号</view>
<view class="modal-message">注销账号后,您的所有数据将被永久删除且无法恢复。确定要注销账号吗?</view>
<view class="modal-buttons">
<button class="modal-btn-cancel" @tap="closeDeleteAccountModal">取消</button>
<button class="modal-btn-confirm danger" @tap="confirmDeleteAccount">确定注销</button>
</view>
</view>
</view>
<!-- 退出登录确认弹窗 -->
<view class="confirm-modal" v-if="showLogoutModal" @tap="closeLogoutModal">
<view class="modal-content" @tap.stop>
<view class="modal-title">退出登录</view>
<view class="modal-message">确定要退出登录吗?</view>
<view class="modal-buttons">
<button class="modal-btn-cancel" @tap="closeLogoutModal">取消</button>
<button class="modal-btn-confirm" @tap="confirmLogout">确定</button>
</view>
</view>
</view>
<!-- 修改头像弹窗 -->
<view class="avatar-modal" v-if="showAvatarModal" @tap="closeAvatarModal">
<view class="modal-content" @tap.stop>
<view class="modal-title">修改头像</view>
<!-- 头像预览 -->
<view class="avatar-preview">
<Avatar :key="avatarKey" :userId="uid" :size="200" :borderWidth="6"
:avatarUrl="userAvatarUrl" />
</view>
<!-- 上传按钮 -->
<button class="upload-avatar-btn" @tap="handleUploadAvatar" :disabled="uploadingAvatar">
{{ uploadingAvatar ? '上传中...' : '上传新头像' }}
</button>
<view class="upload-hint">支持JPGPNG格式大小不超过10MB</view>
<view class="modal-buttons">
<button class="modal-btn-cancel" @tap="closeAvatarModal">取消</button>
</view>
</view>
</view>
</view>
<!-- 退出登录按钮 -->
<view class="logout-section">
<button class="logout-button" @tap="handleLogout">退出登录</button>
</view>
</scroll-view>
<!-- 新手引导列表弹窗 -->
<GuideListModal :visible="showGuideListModal" @start-guide="handleStartGuide"
@claim-success="handleClaimSuccess" @close="showGuideListModal = false" />
<!-- 全局引导遮罩 -->
<!-- <GuideOverlay /> -->
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import { useStore } from 'vuex';
import { onReady } from "@dcloudio/uni-app";
import Header from '../components/Header.vue';
import Avatar from '../components/Avatar.vue';
import { getUserProfileApi, deleteAccountApi, updateNicknameApi, updatePasswordApi, getFanIdentitiesApi, addFanIdentityApi, getMyFanIdentitiesApi, switchFanIdentityApi, getOssSignatureApi, updateAvatarApi, getOssPresignedUrlApi } from '@/utils/api';
import { clearAvatarCache, downloadAndCacheAvatar } from '@/utils/avatarCache';
import GuideListModal from '@/components/GuideListModal.vue';
import GuideOverlay from '@/components/GuideOverlay.vue';
import {
getClaimableRewardCount
} from '@/utils/guideConfig.js';
const store = useStore();
// 用户信息
const nickname = ref('');
const uid = ref('');
const blockchainAddress = ref('');
const fanLevel = ref(0);
const fanTag = ref('');
const loading = ref(false);
// 修改昵称弹窗
const showNicknameModal = ref(false);
const newNickname = ref('');
// 修改密码弹窗
const showPasswordModal = ref(false);
const oldPassword = ref('');
const newPassword = ref('');
const showOldPassword = ref(false);
// 新手引导
const showGuideListModal = ref(false);
const guideClaimableCount = computed(() => getClaimableRewardCount());
const showNewPassword = ref(false);
// 添加身份弹窗
const showAddIdentityModal = ref(false);
const fanIdentitiesList = ref([]);
const selectedStarId = ref(null);
const newIdentityNickname = ref('');
// 身份切换下拉列表
const showIdentityDropdown = ref(false);
const myFanIdentities = ref([]);
const currentStarId = ref(null);
// 注销账号确认弹窗
const showDeleteAccountModal = ref(false);
// 退出登录确认弹窗
const showLogoutModal = ref(false);
// 修改头像弹窗
const showAvatarModal = ref(false);
const userAvatarUrl = ref('');
const uploadingAvatar = ref(false);
const avatarKey = ref(0); // 用于强制刷新Avatar组件
// 手机号
const mobile = ref('');
// 显示脱敏后的手机号(后端已脱敏,直接显示)
const displayMobile = computed(() => {
return mobile.value || '';
});
// 显示截断后的区块链地址
const displayAddress = computed(() => {
const address = blockchainAddress.value;
// 如果没有地址,显示默认地址
if (!address) return '0xabcd...123c';
if (address.length <= 20) return address;
// 显示前10个字符和后8个字符
return address.substring(0, 4) + '...' + address.substring(address.length - 4);
});
// 获取用户信息
const fetchUserInfo = async (forceRefresh = false) => {
loading.value = true;
try {
// 每次进入页面都从API获取最新数据
const res = await getUserProfileApi();
if (res.code === 200 && res.data) {
const apiData = res.data;
// 读取旧缓存数据(保留某些字段)
let oldCachedUser = null;
const oldCachedUserStr = uni.getStorageSync('user');
if (oldCachedUserStr) {
try {
oldCachedUser = JSON.parse(oldCachedUserStr);
} catch (e) {
console.error('解析旧缓存失败:', e);
}
}
// 转换API数据结构为缓存格式
const userForCache = {
uid: apiData.uid,
nickname: apiData.nickname,
avatar_url: apiData.avatar_url || '',
// 从 current_identity 转换为 fan_identity
fan_identity: apiData.current_identity ? {
identity_id: apiData.current_identity.identity_id,
name: apiData.current_identity.identity_name,
tag: apiData.current_identity.tag
} : (oldCachedUser?.fan_identity || null),
fan_level: apiData.current_identity?.level || 0,
crystal_balance: apiData.current_identity?.crystal_balance || 0,
// 保留旧缓存中的其他字段如果API没有返回
blockchain_address: apiData.blockchain_address || oldCachedUser?.blockchain_address || '',
assets_num: apiData.assets_num !== undefined ? apiData.assets_num : (oldCachedUser?.assets_num || 0),
slot_limit: apiData.slot_limit !== undefined ? apiData.slot_limit : (oldCachedUser?.slot_limit || 3),
starbook_limit: apiData.starbook_limit !== undefined ? apiData.starbook_limit : (oldCachedUser?.starbook_limit || 3),
mobile: apiData.mobile || oldCachedUser?.mobile || ''
};
// 覆盖式更新缓存
uni.setStorageSync('user', JSON.stringify(userForCache));
// 更新显示
nickname.value = userForCache.nickname || '';
uid.value = userForCache.uid || '';
fanLevel.value = userForCache.fan_level || 0;
fanTag.value = userForCache.fan_identity?.tag || '';
blockchainAddress.value = userForCache.blockchain_address || '';
userAvatarUrl.value = userForCache.avatar_url || '';
mobile.value = uni.getStorageSync('login_mobile') || '';
}
} catch (error) {
console.error('获取用户信息失败:', error);
// 如果API调用失败尝试从缓存读取
const cachedUserStr = uni.getStorageSync('user');
if (cachedUserStr) {
try {
const cachedUser = JSON.parse(cachedUserStr);
nickname.value = cachedUser.nickname || '';
uid.value = cachedUser.uid || '';
fanLevel.value = cachedUser.fan_level || 0;
fanTag.value = cachedUser.fan_identity?.tag || '';
blockchainAddress.value = cachedUser.blockchain_address || '';
userAvatarUrl.value = cachedUser.avatar_url || '';
mobile.value = uni.getStorageSync('login_mobile') || '';
} catch (e) {
console.error('解析缓存用户信息失败:', e);
}
}
uni.showToast({
title: error.message || '获取用户信息失败',
icon: 'none'
});
} finally {
loading.value = false;
}
};
// 复制区块链地址
const copyAddress = () => {
if (!blockchainAddress.value) {
uni.showToast({
title: '地址为空',
icon: 'none'
});
return;
}
uni.setClipboardData({
data: blockchainAddress.value,
success: () => {
uni.showToast({
title: '已复制到剪贴板',
icon: 'success'
});
},
fail: () => {
uni.showToast({
title: '复制失败',
icon: 'none'
});
}
});
};
// 处理资产按钮点击
const handleAssetClick = (type) => {
if (type === 'exhibition') {
// 跳转到展馆页面
uni.navigateTo({
url: '/pages/exhibition/exhibition'
});
} else if (type === 'starbook') {
// 跳转到星册页面
uni.navigateTo({
url: '/pages/starbook/index'
});
}
};
// 处理修改昵称
const handleChangeNickname = () => {
newNickname.value = nickname.value; // 初始化输入框为当前昵称
showNicknameModal.value = true;
};
// 关闭修改昵称弹窗
const closeNicknameModal = () => {
showNicknameModal.value = false;
newNickname.value = '';
};
// 确认修改昵称
const confirmChangeNickname = async () => {
const trimmedNickname = newNickname.value.trim();
// 验证昵称
if (!trimmedNickname) {
uni.showToast({
title: '请输入昵称',
icon: 'none'
});
return;
}
if (trimmedNickname.length > 20) {
uni.showToast({
title: '昵称不能超过20个字符',
icon: 'none'
});
return;
}
// 校验新昵称和当前昵称是否相同
if (trimmedNickname === nickname.value) {
uni.showToast({
title: '不能使用相同昵称',
icon: 'none'
});
return;
}
try {
// 显示加载提示
uni.showLoading({
title: '修改中...',
mask: true
});
// 调用修改昵称API
const res = await updateNicknameApi(trimmedNickname);
uni.hideLoading();
if (res.code === 200) {
// 修改成功,强制从服务器刷新个人信息
await fetchUserInfo(true);
uni.showToast({
title: '昵称修改成功',
icon: 'success'
});
closeNicknameModal();
} else {
// 业务错误
uni.showToast({
title: res.message || '修改失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
// 显示错误信息
uni.showToast({
title: error.message || '修改失败,请重试',
icon: 'none',
duration: 2000
});
}
};
// 处理修改密码
const handleChangePassword = () => {
oldPassword.value = '';
newPassword.value = '';
showOldPassword.value = false;
showNewPassword.value = false;
showPasswordModal.value = true;
};
// 关闭修改密码弹窗
const closePasswordModal = () => {
showPasswordModal.value = false;
oldPassword.value = '';
newPassword.value = '';
showOldPassword.value = false;
showNewPassword.value = false;
};
// 确认修改密码
const confirmChangePassword = async () => {
// 验证旧密码
if (!oldPassword.value.trim()) {
uni.showToast({
title: '请输入旧密码',
icon: 'none'
});
return;
}
// 验证新密码
if (!newPassword.value.trim()) {
uni.showToast({
title: '请输入新密码',
icon: 'none'
});
return;
}
try {
// 显示加载提示
uni.showLoading({
title: '修改中...',
mask: true
});
// 调用修改密码API
const res = await updatePasswordApi(oldPassword.value.trim(), newPassword.value.trim());
uni.hideLoading();
if (res.code === 200) {
// 修改成功
uni.showToast({
title: '修改成功,请重新登录',
icon: 'success',
duration: 2000
});
// 延迟执行登出和跳转
setTimeout(() => {
// 调用store的logout方法
store.dispatch('user/logout');
// 清除所有本地缓存
uni.removeStorageSync('access_token');
uni.removeStorageSync('user');
uni.removeStorageSync('nickname');
// 清除临时注册数据(如果有)
uni.removeStorageSync('temp_register_mobile');
uni.removeStorageSync('temp_register_password');
uni.removeStorageSync('temp_register_nickname');
// 跳转到登录页
uni.reLaunch({
url: '/pages/login/login'
});
}, 2000);
}
} catch (error) {
uni.hideLoading();
// 处理错误,显示错误信息
const errorMessage = error.message || '修改失败,请重试';
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 2000
});
}
};
// 处理添加身份
const handleSwitchRole = async () => {
try {
// 显示加载提示
uni.showLoading({
title: '加载中...',
mask: true
});
// 获取可选的粉丝身份列表
const res = await getFanIdentitiesApi();
uni.hideLoading();
if (res.code === 200 && res.data && res.data.items) {
fanIdentitiesList.value = res.data.items;
showAddIdentityModal.value = true;
} else {
uni.showToast({
title: res.message || '获取明星列表失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('获取明星列表失败:', error);
uni.showToast({
title: error.message || '获取明星列表失败',
icon: 'none'
});
}
};
// 选择明星
const selectStar = (starId) => {
selectedStarId.value = starId;
};
// 关闭添加身份弹窗
const closeAddIdentityModal = () => {
showAddIdentityModal.value = false;
selectedStarId.value = null;
newIdentityNickname.value = '';
fanIdentitiesList.value = [];
};
// 确认添加身份
const confirmAddIdentity = async () => {
// 验证是否选择了明星
if (!selectedStarId.value) {
uni.showToast({
title: '请选择您喜欢的明星',
icon: 'none'
});
return;
}
// 验证昵称
const trimmedNickname = newIdentityNickname.value.trim();
if (!trimmedNickname) {
uni.showToast({
title: '请输入昵称',
icon: 'none'
});
return;
}
if (trimmedNickname.length > 20) {
uni.showToast({
title: '昵称不能超过20个字符',
icon: 'none'
});
return;
}
try {
// 显示加载提示
uni.showLoading({
title: '添加中...',
mask: true
});
// 调用添加粉丝身份API
const res = await addFanIdentityApi(selectedStarId.value, trimmedNickname);
uni.hideLoading();
if (res.code === 200) {
// 添加成功
uni.showToast({
title: '添加身份成功',
icon: 'success'
});
// 关闭弹窗
closeAddIdentityModal();
// 强制刷新用户信息
await fetchUserInfo(true);
} else {
uni.showToast({
title: res.message || '添加失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('添加身份失败:', error);
uni.showToast({
title: error.message || '添加失败,请重试',
icon: 'none'
});
}
};
// 处理粉丝标签点击
const handleFanTagClick = async () => {
try {
// 显示加载提示
uni.showLoading({
title: '加载中...',
mask: true
});
// 获取用户的所有粉丝身份
const res = await getMyFanIdentitiesApi();
uni.hideLoading();
if (res.code === 200 && res.data && res.data.items) {
myFanIdentities.value = res.data.items;
currentStarId.value = res.data.current_star_id;
showIdentityDropdown.value = true;
} else {
uni.showToast({
title: res.message || '获取身份列表失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('获取身份列表失败:', error);
uni.showToast({
title: error.message || '获取身份列表失败',
icon: 'none'
});
}
};
// 关闭身份切换下拉列表
const closeIdentityDropdown = () => {
showIdentityDropdown.value = false;
myFanIdentities.value = [];
currentStarId.value = null;
};
// 处理切换身份
const handleSwitchIdentity = async (starId) => {
// 验证不能切换到当前身份
if (starId === currentStarId.value) {
uni.showToast({
title: '当前已是该身份',
icon: 'none'
});
return;
}
try {
// 显示加载提示
uni.showLoading({
title: '切换中...',
mask: true
});
// 调用切换身份API
const res = await switchFanIdentityApi(starId);
uni.hideLoading();
if (res.code === 200 && res.data) {
// 更新本地缓存的 access_token
uni.setStorageSync('access_token', res.data.access_token);
// 更新用户信息
const currentIdentity = res.data.current_identity;
if (currentIdentity) {
// 从当前身份列表中找到完整信息
const identityInfo = myFanIdentities.value.find(item => item.star_id === starId);
if (identityInfo) {
nickname.value = identityInfo.nickname;
}
fanTag.value = currentIdentity.tag;
fanLevel.value = currentIdentity.level;
}
// 更新用户缓存
const newStarId = currentIdentity ? currentIdentity.star_id : starId;
const cachedUserStr = uni.getStorageSync('user');
if (cachedUserStr) {
try {
const cachedUser = JSON.parse(cachedUserStr);
cachedUser.nickname = nickname.value;
cachedUser.fan_level = currentIdentity ? currentIdentity.level : fanLevel.value;
cachedUser.crystal_balance = currentIdentity ? currentIdentity.crystal_balance : (cachedUser.crystal_balance || 0);
cachedUser.star_id = newStarId;
cachedUser.fan_identity = {
star_id: newStarId,
identity_id: currentIdentity.identity_id,
name: currentIdentity.identity_name,
tag: currentIdentity.tag
};
uni.setStorageSync('user', JSON.stringify(cachedUser));
// 同步更新独立的 star_id key供 square.vue / RankingModal 直接读取
uni.setStorageSync('star_id', newStarId);
store.commit('user/SET_USER_INFO', cachedUser);
} catch (e) {
console.error('更新用户缓存失败:', e);
}
}
// 显示成功提示
uni.showToast({
title: '切换身份成功',
icon: 'success'
});
// 关闭下拉列表
closeIdentityDropdown();
// 先从服务器拉取最新用户信息(含新身份的 avatar_url
await fetchUserInfo(true);
// 拉取完成后通知 Header 用最新缓存刷新
uni.$emit('userInfoUpdated');
uni.$emit('avatarUpdated', { avatarUrl: userAvatarUrl.value });
uni.$emit('balanceUpdated', { crystal_balance: currentIdentity ? currentIdentity.crystal_balance : 0 });
} else {
uni.showToast({
title: res.message || '切换失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('切换身份失败:', error);
uni.showToast({
title: error.message || '切换失败,请重试',
icon: 'none'
});
}
};
// 处理注销账号
const handleDeleteAccount = () => {
showDeleteAccountModal.value = true;
};
// 点击新手指引
const handleGuideClick = () => {
showGuideListModal.value = true;
};
// 执行引导
const handleStartGuide = (params) => {
// 支持传入对象 { key, fromStep, targetPage } 或直接传 key
const key = typeof params === 'string' ? params : (params?.key || '');
const fromStep = typeof params === 'object' ? (params.fromStep ?? 0) : 0;
const targetPage = typeof params === 'object' ? (params.targetPage || '') : '';
console.log('[Profile] handleStartGuide:', { key, fromStep, targetPage });
showGuideListModal.value = false;
// 根据引导类型跳转到对应页面
if (key === 'square_home') {
// 使用 targetPage 跳转到对应页面
const navigateUrl = targetPage || '/pages/square/square';
uni.navigateTo({
url: navigateUrl,
success: () => {
// 延迟执行确保页面已加载
setTimeout(() => {
if (fromStep === 0) {
store.dispatch("guide/startGuideFromBeginning", key);
} else {
store.dispatch("guide/resumeGuide", key);
}
}, 500);
}
});
} else {
// 其他引导在当前页面触发
if (fromStep === 0) {
store.dispatch("guide/startGuideFromBeginning", key);
} else {
store.dispatch("guide/resumeGuide", key);
}
}
};
// 领取奖励成功
const handleClaimSuccess = (reward) => {
console.log("[Profile] 领取奖励成功:", reward);
};
// 关闭注销账号弹窗
const closeDeleteAccountModal = () => {
showDeleteAccountModal.value = false;
};
// 确认注销账号
const confirmDeleteAccount = async () => {
try {
// 关闭弹窗
closeDeleteAccountModal();
// 显示加载提示
uni.showLoading({
title: '注销中...',
mask: true
});
// 调用注销账号接口
const result = await deleteAccountApi();
uni.hideLoading();
if (result.code === 200) {
// 调用store的logout方法
store.dispatch('user/logout');
// 清除所有本地缓存
uni.removeStorageSync('access_token');
uni.removeStorageSync('user');
uni.removeStorageSync('nickname');
// 清除临时注册数据(如果有)
uni.removeStorageSync('temp_register_mobile');
uni.removeStorageSync('temp_register_password');
uni.removeStorageSync('temp_register_nickname');
// 显示成功提示
uni.showToast({
title: '账号已注销',
icon: 'success'
});
// 延迟跳转到登录页
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/login'
});
}, 1500);
} else {
uni.showToast({
title: result.message || '注销失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('注销账号失败:', error);
uni.showToast({
title: '注销失败,请稍后重试',
icon: 'none'
});
}
};
// 处理退出登录
const handleLogout = () => {
showLogoutModal.value = true;
};
// 关闭退出登录弹窗
const closeLogoutModal = () => {
showLogoutModal.value = false;
};
// 确认退出登录
const confirmLogout = () => {
// 关闭弹窗
closeLogoutModal();
// 调用store的logout方法
store.dispatch('user/logout');
// 清除所有本地缓存
uni.removeStorageSync('access_token');
uni.removeStorageSync('user');
uni.removeStorageSync('nickname');
// 清除临时注册数据(如果有)
uni.removeStorageSync('temp_register_mobile');
uni.removeStorageSync('temp_register_password');
uni.removeStorageSync('temp_register_nickname');
// 跳转到登录页
uni.reLaunch({
url: '/pages/login/login'
});
};
// 打开头像修改弹窗
const handleAvatarClick = () => {
showAvatarModal.value = true;
};
// 关闭头像修改弹窗
const closeAvatarModal = () => {
showAvatarModal.value = false;
};
// 处理头像更新后的操作下载缓存、更新本地存储、刷新UI
const handleAvatarUpdateSuccess = async (newAvatarUrl) => {
try {
// 1. 获取新头像的真实URL并下载缓存
const fileName = 'avatar.png';
const urlRes = await getOssPresignedUrlApi(fileName, 3600, 'avatar');
if (urlRes.code === 200 && urlRes.data && urlRes.data.url) {
// 立即下载并缓存新头像
await downloadAndCacheAvatar(newAvatarUrl, urlRes.data.url);
}
} catch (error) {
console.error('预下载头像失败:', error);
// 不影响主流程,继续
}
// 2. 更新本地缓存
userAvatarUrl.value = newAvatarUrl;
const userStr = uni.getStorageSync('user');
if (userStr) {
const user = JSON.parse(userStr);
user.avatar_url = newAvatarUrl;
uni.setStorageSync('user', JSON.stringify(user));
}
// 3. 强制刷新Avatar组件
avatarKey.value += 1;
// 4. 触发全局事件,通知其他组件刷新头像
uni.$emit('avatarUpdated', { avatarUrl: newAvatarUrl });
// 5. 显示成功提示
uni.hideLoading();
uni.showToast({
title: '头像修改成功',
icon: 'success'
});
uploadingAvatar.value = false;
closeAvatarModal();
};
// 上传头像
const handleUploadAvatar = () => {
// 选择图片
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: async (res) => {
const tempFilePath = res.tempFilePaths[0];
// 获取文件信息验证大小
uni.getFileInfo({
filePath: tempFilePath,
success: async (fileInfo) => {
// 验证文件大小(10MB = 10 * 1024 * 1024)
if (fileInfo.size > 10 * 1024 * 1024) {
uni.showToast({
title: '图片大小不能超过10MB',
icon: 'none'
});
return;
}
// 上传到OSS
await uploadAvatarToOss(tempFilePath);
},
fail: (error) => {
console.error('获取文件信息失败:', error);
uni.showToast({
title: '获取文件信息失败',
icon: 'none'
});
}
});
},
fail: (error) => {
console.error('选择图片失败:', error);
}
});
};
// 上传头像到OSS
const uploadAvatarToOss = async (filePath) => {
try {
uploadingAvatar.value = true;
uni.showLoading({ title: '上传中...', mask: true });
// 1. 获取OSS签名
const signRes = await getOssSignatureApi('avatar');
if (signRes.code !== 200) {
throw new Error(signRes.message || '获取签名失败');
}
// 2. 构建FormData并上传到OSS
// 注意:文件名固定为avatar.png
uni.uploadFile({
url: signRes.data.host,
filePath: filePath,
name: 'file',
formData: {
key: signRes.data.dir + 'avatar.png',
policy: signRes.data.policy,
success_action_status: '200',
'x-oss-credential': signRes.data.x_oss_credential,
'x-oss-date': signRes.data.x_oss_date,
'x-oss-security-token': signRes.data.security_token,
'x-oss-signature': signRes.data.signature,
'x-oss-signature-version': signRes.data.x_oss_signature_version
},
success: async (uploadRes) => {
try {
if (uploadRes.statusCode === 200 || uploadRes.statusCode === 204) {
// 3. 拼接完整URL
const avatarUrl = `${signRes.data.host}/${signRes.data.dir}avatar.png`;
// 4. 清除旧头像的缓存(如果有)
if (userAvatarUrl.value) {
clearAvatarCache(userAvatarUrl.value);
}
// 5. 调用后端更新头像接口
const updateRes = await updateAvatarApi(avatarUrl);
if (updateRes.code === 200) {
// 6. 处理头像更新成功后的操作
await handleAvatarUpdateSuccess(avatarUrl);
} else {
throw new Error(updateRes.message || '更新头像失败');
}
} else {
throw new Error(`上传失败,状态码: ${uploadRes.statusCode}`);
}
} catch (error) {
console.error('处理上传结果失败:', error);
uni.hideLoading();
uni.showToast({
title: error.message || '上传失败',
icon: 'none',
duration: 2000
});
uploadingAvatar.value = false;
}
},
fail: (error) => {
console.error('OSS上传失败:', error);
uni.hideLoading();
uni.showToast({
title: '上传失败',
icon: 'none',
duration: 2000
});
uploadingAvatar.value = false;
}
});
} catch (error) {
console.error('上传头像失败:', error);
uni.hideLoading();
uni.showToast({
title: error.message || '上传失败',
icon: 'none',
duration: 2000
});
uploadingAvatar.value = false;
}
};
onReady(() => {
// 不再自动触发引导,由用户从引导列表选择后触发
});
onShow(() => {
// 每次页面显示时都刷新用户信息并通知 Header 更新余额
fetchUserInfo().then(() => {
const cachedUserStr = uni.getStorageSync('user');
if (cachedUserStr) {
try {
const cachedUser = JSON.parse(cachedUserStr);
uni.$emit('balanceUpdated', { crystal_balance: cachedUser.crystal_balance || 0 });
} catch (e) {
console.error('解析缓存用户信息失败:', e);
}
}
});
});
</script>
<style scoped>
.profile-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.profile-scroll {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
}
/* 隐藏滚动条 */
.profile-scroll::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
/* 上半部分:用户信息区域 */
.top-section {
position: relative;
width: 100%;
height: auto;
overflow: hidden;
}
.background-image {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 0;
object-fit: cover;
/* filter: blur(20rpx); */
}
.background-overlay {
display: none;
}
.user-info-card {
position: relative;
z-index: 2;
width: 90%;
max-width: 600rpx;
margin: 0 auto;
margin-top: 225rpx;
padding: 40rpx 0;
display: flex;
flex-direction: row;
}
/* 上半部分:头像和用户信息左右布局 */
.user-main-info {
display: flex;
align-items: flex-start;
/* gap: 30rpx; */
margin-bottom: 0;
}
/* 右侧用户文本信息 */
.user-text-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
/* gap: 12rpx; */
margin-left: 30rpx;
padding-bottom: 10rpx;
}
.name-wrapper {
display: flex;
align-items: center;
/* gap: 12rpx; */
flex-wrap: wrap;
margin-bottom: 24rpx;
}
.level-badge {
font-size: 24rpx;
color: #ffffff;
border-radius: 12rpx;
margin-right: 12rpx;
}
.user-name {
font-size: 32rpx;
color: #ffffff;
margin-right: 12rpx;
}
.fan-tag-badge {
background: rgba(255, 255, 255, 0.2);
border-radius: 16rpx;
padding: 4rpx 12rpx;
border: 2rpx solid rgba(255, 255, 255, 0.4);
display: flex;
align-items: center;
gap: 6rpx;
cursor: pointer;
transition: all 0.3s;
}
.fan-tag-badge:active {
opacity: 0.8;
transform: scale(0.95);
}
.fan-tag-text {
font-size: 20rpx;
color: #ffffff;
font-weight: 500;
}
.fan-tag-arrow {
width: 20rpx;
height: 20rpx;
opacity: 0.8;
}
.user-did {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.85);
}
.user-mobile {
font-size: 48rpx;
color: rgba(255, 255, 255, 0.85);
}
.blockchain-address-wrapper {
display: flex;
align-items: center;
gap: 10rpx;
}
.blockchain-address-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.9);
}
.copy-icon {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.copy-icon-text {
font-size: 36rpx;
}
/* 下半部分:透明背景区域 */
.bottom-section {
position: relative;
width: 100%;
min-height: 55vh;
z-index: 3;
padding: 0 30rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.content-area {
flex: 1;
}
.section-bg {
border-radius: 30rpx;
padding: 30rpx;
}
.section-title {
font-size: 40rpx;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
font-weight: bold;
color: #fff;
margin-bottom: 20rpx;
}
.assets-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
margin-bottom: 0;
height: 192rpx;
}
.asset-card {
background: rgba(217, 156, 180, 0.3);
border-radius: 20rpx;
padding: 24rpx 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.asset-icon {
width: 128rpx;
height: 128rpx;
margin-bottom: 12rpx;
}
.asset-text {
font-size: 32rpx;
color: #fff;
margin-bottom: 8rpx;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.arrow {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
font-size: 48rpx;
color: #fff;
font-weight: bold;
}
/* 服务与工具按钮 */
.service-buttons-container {
background: rgba(217, 156, 180, 0.3);
display: flex;
flex-direction: row;
justify-content: flex-start;
margin-bottom: 0;
flex-wrap: wrap;
}
.service-button {
border-radius: 20rpx;
width: 136rpx;
height: 160rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16rpx;
box-sizing: border-box;
position: relative;
margin-bottom: 16rpx;
margin-right: 16rpx;
}
.service-icon {
width: 96rpx;
height: 96rpx;
margin-bottom: 8rpx;
}
.service-icon-emoji {
font-size: 40rpx;
margin-bottom: 6rpx;
display: block;
}
.service-text {
font-size: 24rpx;
color: #fff;
text-align: center;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.guide-badge {
position: absolute;
top: 10rpx;
right: 20rpx;
background: #ff6b6b;
color: #fff;
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 20rpx;
}
.logout-section {
padding: 10rpx 30rpx;
padding-bottom: calc(40rpx + constant(safe-area-inset-bottom)); /* iOS 11.0 */
padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); /* iOS 11.2+ */
display: flex;
position: relative;
}
/* 使用 ::after 伪元素绘制完美的圆角横线 */
.logout-section::after {
content: '';
position: absolute;
top: -6rpx;
left: 0;
right: 0;
height: 8rpx;
background-color: rgba(252, 252, 252, 0.6);
border-radius: 8rpx;
width: calc(100% - 60rpx);
transform: translateX(30rpx);
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
}
.logout-button {
width: 60%;
height: 112rpx;
line-height: 112rpx;
background: rgba(217, 156, 180, 0.3);
border-radius: 40rpx;
font-size: 56rpx;
color: #fff;
font-weight: 500;
margin-top: 20rpx;
flex-shrink: 0;
}
.logout-button::after {
border: none;
}
.logout-button:active {
background: #f5f5f5;
}
/* 修改昵称弹窗 */
.nickname-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
width: 80%;
max-width: 600rpx;
background: #ffffff;
border-radius: 30rpx;
padding: 60rpx 40rpx;
box-sizing: border-box;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
text-align: center;
margin-bottom: 40rpx;
}
.modal-input {
width: 100%;
height: 88rpx;
background: #f5f5f5;
border-radius: 20rpx;
padding: 0 30rpx;
font-size: 32rpx;
color: #333333;
margin-bottom: 40rpx;
box-sizing: border-box;
}
.modal-buttons {
display: flex;
gap: 20rpx;
}
.modal-btn-cancel,
.modal-btn-confirm {
flex: 1;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
}
.modal-btn-cancel {
background: #f5f5f5;
color: #666666;
}
.modal-btn-cancel::after {
border: none;
}
.modal-btn-confirm {
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
color: #ffffff;
}
.modal-btn-confirm::after {
border: none;
}
.modal-btn-cancel:active {
background: #e0e0e0;
}
.modal-btn-confirm:active {
opacity: 0.9;
}
/* 修改密码弹窗 */
.password-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-password-wrapper {
position: relative;
width: 100%;
height: 88rpx;
margin-bottom: 40rpx;
background: #f5f5f5;
border-radius: 20rpx;
display: flex;
align-items: center;
padding: 0 30rpx;
padding-right: 100rpx;
box-sizing: border-box;
}
.modal-password-input {
flex: 1;
height: 100%;
font-size: 32rpx;
color: #333333;
}
.modal-eye-icon {
position: absolute;
right: 30rpx;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.eye-text {
font-size: 40rpx;
}
.input-placeholder {
color: #999999;
}
/* 确认弹窗(注销账号、退出登录) */
.confirm-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-message {
font-size: 28rpx;
color: #666666;
text-align: center;
line-height: 1.6;
margin-bottom: 40rpx;
}
.modal-btn-confirm.danger {
background: #ff3b30;
}
.modal-btn-confirm.danger:active {
background: #e6342a;
}
/* 添加身份弹窗 */
.add-identity-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-subtitle {
font-size: 28rpx;
color: #666666;
text-align: center;
margin-bottom: 30rpx;
}
.star-list {
max-height: 400rpx;
overflow-y: auto;
overflow-x: hidden;
margin-bottom: 30rpx;
border-radius: 20rpx;
background: #f5f5f5;
padding: 10rpx;
}
.star-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: #ffffff;
border-radius: 16rpx;
margin-bottom: 10rpx;
cursor: pointer;
transition: all 0.3s;
}
.star-item:last-child {
margin-bottom: 0;
}
.star-item-selected {
background: linear-gradient(165deg, rgba(240, 228, 177, 0.2) 0%, rgba(240, 131, 153, 0.2) 50%, rgba(185, 78, 115, 0.2) 90%, rgba(131, 75, 158, 0.2) 100%);
border: 2rpx solid #F08399;
}
.star-item:active {
transform: scale(0.98);
}
.star-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.star-name {
font-size: 32rpx;
color: #333333;
font-weight: 500;
}
.star-tag {
font-size: 24rpx;
color: #999999;
}
.star-radio {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #cccccc;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.star-item-selected .star-radio {
border-color: #F08399;
background: #F08399;
}
.star-radio-checked {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background: #ffffff;
}
/* 身份切换下拉列表 */
.identity-dropdown-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.identity-dropdown-content {
width: 85%;
max-width: 650rpx;
max-height: 70vh;
background: #ffffff;
border-radius: 30rpx;
padding: 40rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.identity-dropdown-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
text-align: center;
margin-bottom: 30rpx;
}
.identity-list {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
.identity-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 20rpx;
background: #f5f5f5;
border-radius: 20rpx;
margin-bottom: 16rpx;
cursor: pointer;
transition: all 0.3s;
}
.identity-item:last-child {
margin-bottom: 0;
}
.identity-item-active {
background: linear-gradient(165deg, rgba(240, 228, 177, 0.3) 0%, rgba(240, 131, 153, 0.3) 50%, rgba(185, 78, 115, 0.3) 90%, rgba(131, 75, 158, 0.3) 100%);
border: 2rpx solid #F08399;
}
.identity-item-disabled {
cursor: not-allowed;
opacity: 0.7;
}
.identity-item:not(.identity-item-disabled):active {
transform: scale(0.98);
}
.identity-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.identity-main {
display: flex;
align-items: center;
gap: 12rpx;
}
.identity-star-name {
font-size: 32rpx;
color: #333333;
font-weight: 600;
}
.identity-star-tag {
font-size: 24rpx;
color: #666666;
background: rgba(217, 156, 180, 0.3);
padding: 2rpx 10rpx;
border-radius: 10rpx;
}
.identity-nickname {
font-size: 26rpx;
color: #999999;
}
.identity-current-badge {
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
border-radius: 16rpx;
padding: 6rpx 12rpx;
}
.identity-current-text {
font-size: 22rpx;
color: #ffffff;
font-weight: 500;
}
/* 修改头像弹窗 */
.avatar-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.avatar-preview {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 40rpx;
}
.upload-avatar-btn {
width: 100%;
height: 88rpx;
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
border-radius: 44rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
border: none;
margin-bottom: 20rpx;
}
.upload-avatar-btn::after {
border: none;
}
.upload-avatar-btn:disabled {
opacity: 0.6;
}
.upload-hint {
font-size: 24rpx;
color: #999999;
text-align: center;
margin-bottom: 40rpx;
}
</style>