Initial Commit

This commit is contained in:
Rajuahamedkst 2025-09-12 16:08:17 +08:00
commit 1d71a02738
237 changed files with 64293 additions and 0 deletions

805
pages/profile/profile.js Normal file
View file

@ -0,0 +1,805 @@
// Personal Profile Page Logic
const app = getApp();
const config = require('../../config/config.js');
const apiClient = require('../../utils/api-client.js');
const authManager = require('../../utils/auth.js');
const imageCacheManager = require('../../utils/image-cache-manager.js');
Page({
data: {
// User Information
userInfo: null,
// Statistics
stats: {
friendsCount: 0,
postsCount: 0,
visitorsCount: 0,
likesCount: 0,
groupsCount: 0
},
// App Information
newMomentsCount: 0,
fileSize: '0MB',
cacheSize: '0MB',
appVersion: '',
// UI State
showQRModal: false,
selectedTab: 'gender',
// System Adaptation Information
systemInfo: {},
statusBarHeight: 0,
menuButtonHeight: 0,
menuButtonTop: 0,
navBarHeight: 0,
windowHeight: 0,
safeAreaBottom: 0,
// Debug Information
debugInfo: {
hasGlobalUserInfo: false,
hasToken: false,
tokenLength: 0,
hasLocalStorage: false
},
// Authentication State
isLoggedIn: false,
// Settings State
currentTheme: 'Auto',
notificationStatus: 'Enabled',
currentLanguage: 'Chinese'
},
/**
* Page Lifecycle Methods
*/
onLoad: function (options) {
console.log('Personal Center Page Loaded');
this.initSystemInfo();
this.initData();
},
onShow: function () {
console.log('Personal Center Page Shown');
this.loadUserData();
this.loadUserStats();
},
onReady: function () {
console.log('Personal Center Page Ready');
},
onHide: function () {
console.log('Personal Center Page Hidden');
},
onUnload: function () {
console.log('Personal Center Page Unloaded');
},
/**
* Initialization Methods
*/
initData() {
try {
const userInfo = app.globalData.userInfo;
this.setData({
userInfo: userInfo || {
user: {
nickname: 'Nickname Not Set',
customId: '123456789',
avatar: '',
signature: '',
gender: 'male',
verified: false
},
age: null,
mood: '',
personality: '',
identity: '',
constellation: '',
school: '',
occupation: ''
},
appVersion: config.appVersion || '1.0.0',
isLoggedIn: app.globalData.isLoggedIn || false
});
} catch (error) {
console.error('Failed to initialize data:', error);
}
},
initSystemInfo() {
try {
const systemInfo = wx.getSystemInfoSync();
const menuButtonInfo = wx.getMenuButtonBoundingClientRect();
const statusBarHeight = systemInfo.statusBarHeight || 0;
const menuButtonHeight = menuButtonInfo.height || 0;
const menuButtonTop = menuButtonInfo.top || 0;
const menuButtonBottom = menuButtonInfo.bottom || 0;
const navBarHeight = menuButtonBottom + menuButtonTop - statusBarHeight;
const windowHeight = systemInfo.windowHeight || 0;
const safeAreaBottom = systemInfo.safeArea ?
systemInfo.screenHeight - systemInfo.safeArea.bottom : 0;
this.setData({
systemInfo,
statusBarHeight,
menuButtonHeight,
menuButtonTop,
navBarHeight,
windowHeight,
safeAreaBottom
});
console.log('System adaptation info:', {
statusBarHeight,
menuButtonHeight,
menuButtonTop,
navBarHeight,
windowHeight,
safeAreaBottom
});
} catch (error) {
console.error('Failed to initialize system info:', error);
}
},
/**
* Data Loading Methods
*/
loadUserData() {
try {
const globalUserInfo = app.globalData.userInfo;
const isLoggedIn = app.globalData.isLoggedIn;
const currentToken = apiClient.getToken();
// Ensure user information contains customId
let userInfo = globalUserInfo;
if (userInfo?.user) {
const user = userInfo.user;
if (!user.customId && user.id) {
user.customId = 'findme_' + user.id;
}
if (!user.customId) {
user.customId = 'Not Set';
}
}
// Debug information
const debugInfo = {
hasGlobalUserInfo: !!globalUserInfo,
hasToken: !!currentToken,
tokenLength: currentToken ? currentToken.length : 0,
tokenPrefix: currentToken ? currentToken.substring(0, 20) + '...' : 'null',
hasLocalStorage: false
};
// Check local storage
try {
const storedUserInfo = wx.getStorageSync('userInfo');
debugInfo.hasLocalStorage = !!(storedUserInfo?.token);
} catch (storageError) {
console.warn('Failed to check local storage:', storageError);
}
console.log('Personal Center Debug Information:', debugInfo);
this.setData({
userInfo,
isLoggedIn,
debugInfo
});
} catch (error) {
console.error('Failed to load user data:', error);
}
},
async loadUserStats() {
try {
const response = await this.mockLoadStats();
if (response?.code === 0) {
this.setData({
stats: response.data.stats || this.data.stats,
newMomentsCount: response.data.newMomentsCount || 0,
fileSize: response.data.fileSize || '0MB',
cacheSize: response.data.cacheSize || '0MB'
});
}
} catch (error) {
console.error('Failed to load user stats:', error);
}
},
async mockLoadStats() {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 300));
return {
code: 0,
message: 'Success',
data: {
stats: {
friendsCount: 42,
postsCount: 18,
visitorsCount: 156,
likesCount: 89,
groupsCount: 5
},
newMomentsCount: 2,
fileSize: '125MB',
cacheSize: '32MB'
}
};
},
refreshUserInfo() {
const userInfo = app.globalData.userInfo;
if (userInfo) {
this.setData({ userInfo });
}
},
/**
* Avatar Management
*/
changeAvatar() {
wx.showActionSheet({
itemList: ['Take Photo', 'Choose from Album'],
success: (res) => {
const sourceType = res.tapIndex === 0 ? ['camera'] : ['album'];
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: sourceType,
maxDuration: 30,
camera: 'back',
success: (res) => {
console.log('Avatar selection successful:', res.tempFiles[0]);
this.uploadAvatar(res.tempFiles[0].tempFilePath);
},
fail: (error) => {
console.error('Failed to select avatar:', error);
wx.showToast({
title: 'Failed to Select Avatar',
icon: 'none'
});
}
});
}
});
},
async uploadAvatar(tempFilePath) {
try {
wx.showLoading({ title: 'Uploading...' });
const uploadResult = await this.uploadAvatarToServer(tempFilePath);
if (uploadResult.success) {
// Cache new avatar
const cachedAvatarUrl = await imageCacheManager.updateAvatarCache(
this.data.userInfo?.user?.avatar,
uploadResult.avatarUrl
);
// Update local user information
const userInfo = { ...this.data.userInfo };
if (userInfo?.user) {
userInfo.user.avatar = cachedAvatarUrl;
this.setData({ userInfo });
// Update global user information
if (app.globalData.userInfo?.user) {
app.globalData.userInfo.user.avatar = cachedAvatarUrl;
}
}
wx.hideLoading();
wx.showToast({
title: 'Avatar Updated Successfully',
icon: 'success'
});
} else {
throw new Error(uploadResult.message || 'Upload Failed');
}
} catch (error) {
wx.hideLoading();
console.error('Failed to upload avatar:', error);
wx.showToast({
title: error.message || 'Upload Failed',
icon: 'none'
});
}
},
async uploadAvatarToServer(tempFilePath) {
try {
console.log('Starting avatar upload:', tempFilePath);
const uploadResult = await new Promise((resolve, reject) => {
wx.uploadFile({
url: `${config.api.baseUrl}/api/v1/file/upload`,
filePath: tempFilePath,
name: 'file',
formData: {
file_type: 'avatar',
usage_type: 'avatar'
},
header: {
'Authorization': `Bearer ${apiClient.getToken()}`
},
success: resolve,
fail: reject
});
});
console.log('Upload response:', uploadResult);
if (uploadResult.statusCode === 200) {
const result = JSON.parse(uploadResult.data);
if (result.code === 0) {
return {
success: true,
avatarUrl: result.data.file_url,
message: result.message || 'Upload Successful'
};
} else {
return {
success: false,
message: result.message || 'Upload Failed'
};
}
} else {
return {
success: false,
message: `HTTP Error: ${uploadResult.statusCode}`
};
}
} catch (error) {
console.error('Failed to upload avatar to server:', error);
return {
success: false,
message: error.message || 'Network Error'
};
}
},
/**
* Tab Selection
*/
onTabSelect(e) {
const tab = e.currentTarget.dataset.tab;
if (tab) {
this.setData({ selectedTab: tab });
}
},
/**
* Navigation Methods
*/
editProfile() {
wx.navigateTo({
url: '/pages/personal-details/personal-details'
});
},
openSettingsPage() {
wx.navigateTo({
url: '/pages/settingss/settingss'
});
},
navigateToQRCode() {
wx.navigateTo({
url: '/pages/qr-code/qr-code'
});
},
viewFriends() {
wx.navigateTo({
url: '/pages/social/friends/friends'
});
},
/**
* Statistics Navigation
*/
viewPosts() {
wx.showToast({
title: 'Moments Function Has Been Removed',
icon: 'none'
});
},
viewVisitors() {
wx.showToast({
title: 'Visitor Records Available in the APP',
icon: 'none'
});
},
viewLikes() {
wx.showToast({
title: 'Like Records Available in the APP',
icon: 'none'
});
},
/**
* Settings Methods
*/
openThemeSettings() {
const themes = ['Light', 'Dark', 'Auto'];
wx.showActionSheet({
itemList: themes,
success: (res) => {
const selectedTheme = themes[res.tapIndex];
this.setData({ currentTheme: selectedTheme });
wx.showToast({
title: `Switched to ${selectedTheme} Theme`,
icon: 'success'
});
// Save theme preference
wx.setStorageSync('theme', selectedTheme);
}
});
},
openNotificationSettings() {
const options = ['Enabled', 'Disabled', 'Do Not Disturb'];
wx.showActionSheet({
itemList: options,
success: (res) => {
const selectedStatus = options[res.tapIndex];
this.setData({ notificationStatus: selectedStatus });
const statusText = selectedStatus === 'Enabled' ? 'Enabled' :
selectedStatus === 'Disabled' ? 'Disabled' :
'Set to Do Not Disturb';
wx.showToast({
title: `Notifications ${statusText}`,
icon: 'success'
});
// Save notification preference
wx.setStorageSync('notificationStatus', selectedStatus);
}
});
},
openLanguageSettings() {
const languages = ['Chinese', 'English'];
wx.showActionSheet({
itemList: languages,
success: (res) => {
const selectedLanguage = languages[res.tapIndex];
this.setData({ currentLanguage: selectedLanguage });
wx.showToast({
title: `Switched to ${selectedLanguage}`,
icon: 'success'
});
// Save language preference
wx.setStorageSync('language', selectedLanguage);
}
});
},
openChatSettings() {
wx.showActionSheet({
itemList: ['Font Size', 'Chat Background', 'Message Preview'],
success: (res) => {
const options = ['Font Size', 'Chat Background', 'Message Preview'];
const selectedOption = options[res.tapIndex];
wx.showToast({
title: `${selectedOption} Function Under Development`,
icon: 'none'
});
}
});
},
/**
* Feature Methods (Currently Limited)
*/
openWallet() {
wx.showToast({
title: 'Please Experience in the APP',
icon: 'none'
});
},
openCards() {
wx.showToast({
title: 'Please Experience in the APP',
icon: 'none'
});
},
openStickers() {
wx.showToast({
title: 'Please Experience in the APP',
icon: 'none'
});
},
openGames() {
wx.showToast({
title: 'Please Experience in the APP',
icon: 'none'
});
},
openShopping() {
wx.showToast({
title: 'Function Under Development',
icon: 'none'
});
},
openBackupSettings() {
wx.showToast({
title: 'Please Experience in the APP',
icon: 'none'
});
},
viewProfile() {
wx.showToast({
title: 'Personal Homepage Available in the APP',
icon: 'none'
});
},
managePrivacy() {
wx.showToast({
title: 'Privacy Settings Available in the APP',
icon: 'none'
});
},
viewMoments() {
wx.showToast({
title: 'Moments Function Has Been Removed',
icon: 'none'
});
},
viewFavorites() {
wx.showToast({
title: 'My Favorites Available in the APP',
icon: 'none'
});
},
viewGroups() {
wx.showToast({
title: 'My Groups Available in the APP',
icon: 'none'
});
},
viewFiles() {
wx.showToast({
title: 'File Management Available in the APP',
icon: 'none'
});
},
/**
* Cache Management
*/
clearCache() {
wx.showModal({
title: 'Clear Cache',
content: 'Are you sure you want to clear the cache? Some data may need to be reloaded after clearing.',
success: (res) => {
if (res.confirm) {
wx.showLoading({ title: 'Clearing...' });
// Clear image cache
imageCacheManager.clearAllCache();
setTimeout(() => {
wx.hideLoading();
this.setData({ cacheSize: '0MB' });
wx.showToast({
title: 'Cache Cleared Successfully',
icon: 'success'
});
}, 1000);
}
}
});
},
showCacheStats() {
try {
const stats = imageCacheManager.getCacheStats();
wx.showModal({
title: 'Cache Statistics',
content: `Total Cache: ${stats.total}\nAvatar Cache: ${stats.avatar}\nImage Cache: ${stats.image}\nExpired Cache: ${stats.expired}\nMax Cache: ${stats.maxSize}`,
showCancel: false,
confirmText: 'OK'
});
} catch (error) {
console.error('Failed to show cache stats:', error);
wx.showToast({
title: 'Failed to Load Cache Stats',
icon: 'none'
});
}
},
/**
* App Information Methods
*/
checkUpdates() {
wx.showLoading({ title: 'Checking...' });
setTimeout(() => {
wx.hideLoading();
wx.showToast({
title: 'You Are Using the Latest Version',
icon: 'success'
});
}, 1000);
},
aboutApp() {
wx.showModal({
title: 'About FindMe',
content: `FindMe v${this.data.appVersion}\n\nA location-based social application\nDiscover nearby, connect the world\n\n© 2025 FindMe`,
showCancel: false,
confirmText: 'Got it'
});
},
viewHelp() {
wx.showToast({
title: 'Help Center Available in the APP',
icon: 'none'
});
},
giveFeedback() {
wx.showModal({
title: 'Feedback',
editable: true,
placeholderText: 'Please enter your comments or suggestions...',
success: (res) => {
if (res.confirm && res.content?.trim()) {
wx.showLoading({ title: 'Submitting...' });
// Here you would typically call an API to submit feedback
setTimeout(() => {
wx.hideLoading();
wx.showToast({
title: 'Feedback Submitted Successfully',
icon: 'success'
});
}, 1000);
}
}
});
},
/**
* QR Code Methods
*/
showQRCode() {
this.setData({ showQRModal: true });
},
hideQRCode() {
this.setData({ showQRModal: false });
},
saveQRCode() {
wx.showToast({
title: 'Please Experience in the APP',
icon: 'none'
});
},
shareQRCode() {
wx.showToast({
title: 'Please Experience in the APP',
icon: 'none'
});
},
/**
* Debug Methods
*/
async testApiCall() {
try {
wx.showLoading({ title: 'Testing API...' });
const response = await apiClient.getUserInfo();
wx.hideLoading();
wx.showModal({
title: 'API Test Successful',
content: `User Information Retrieved: ${JSON.stringify(response.data)}`,
showCancel: false
});
} catch (error) {
wx.hideLoading();
wx.showModal({
title: 'API Test Failed',
content: `Error Message: ${error.message}`,
showCancel: false
});
}
},
refreshDebugInfo() {
this.loadUserData();
wx.showToast({
title: 'Refreshed',
icon: 'success'
});
},
/**
* Authentication Methods
*/
async logout() {
wx.showModal({
title: 'Logout',
content: 'Are you sure you want to log out of the current account?',
success: (res) => {
if (res.confirm) {
this.performLogout();
}
}
});
},
async performLogout() {
try {
wx.showLoading({ title: 'Logging Out...' });
const success = await app.logout();
wx.hideLoading();
if (success) {
wx.showToast({
title: 'Logged Out Successfully',
icon: 'success'
});
setTimeout(() => {
wx.reLaunch({
url: '/pages/login/login'
});
}, 1500);
} else {
wx.showToast({
title: 'Logout Failed',
icon: 'none'
});
}
} catch (error) {
wx.hideLoading();
console.error('Logout failed:', error);
wx.showToast({
title: 'Logout Error',
icon: 'none'
});
}
}
});

View file

@ -0,0 +1,5 @@
{
"navigationBarTitleText": "个人资料",
"navigationBarTextStyle": "white",
"disableScroll": true
}

305
pages/profile/profile.wxml Normal file
View file

@ -0,0 +1,305 @@
<!--个人资料页面 - 美化版设计-->
<view class="profile-container">
<scroll-view class="profile-content"
scroll-y="true"
enhanced="true"
bounces="true"
show-scrollbar="false"
refresher-enabled="true"
refresher-triggered="{{refreshing}}"
bindrefresherrefresh="onRefresh">
<!-- 个人信息卡片(改版) -->
<view class="profile-card">
<!-- 顶部区域头像、名字、ID、去认证 -->
<view class="profile-top">
<view class="avatar-section">
<view class="avatar-container" bindtap="changeAvatar">
<image class="avatar-image"
src="{{userInfo.user.avatar || '/images/1111.jpg'}}"
mode="aspectFill" />
<view class="avatar-overlay">
<view class="camera-icon">📷</view>
</view>
<view class="avatar-decoration" wx:if="{{userInfo.user.isVip}}">👑</view>
<view class="online-status online"></view>
</view>
</view>
<view class="profile-main-info">
<view class="profile-name-row">
<text class="profile-name">{{userInfo.user.nickname || 'FindMe用户'}}</text>
<view class="gender-badge" wx:if="{{userInfo.user.gender}}">
{{userInfo.user.gender === 'male' ? '♂️' : userInfo.user.gender === 'female' ? '♀️' : ''}}
</view>
<view class="vip-crown" wx:if="{{userInfo.user.isVip}}">👑</view>
</view>
<view class="profile-id">
<text class="id-label">ID: </text>
<text class="id-value">{{userInfo.user.customId || (userInfo.user.id ? 'findme_' + userInfo.user.id : '未设置')}}</text>
<!-- 新增:去认证按钮 -->
<view class="verify-btn" wx:if="{{!userInfo.user.verified}}" bindtap="goVerify">
<image class="verify-btn-p" bindtap="copyId" src="/images/btn.png" mode="widthFix" />
<text class="verify-text">去认证</text>
</view>
<view class="verified-tag" wx:if="{{userInfo.user.verified}}">
<image class="verified-tag-p" bindtap="copyId" src="/images/tag.png" mode="widthFix" />
<text class="verified-text">已认证</text>
</view>
</view>
</view>
</view>
<!-- 底部信息区:位置、二维码、编辑按钮、设置、简介、个人标签 -->
<view class="profile-bottom">
<!-- 操作按钮区 -->
<view class="action-buttons">
<view class="qr-code-btn" bindtap="navigateToQRCode">
<image src="/images/qr-code.png" class="qr-code-icon"/>
</view>
<view class="edit-btn" bindtap="editProfile">
<image src="/images/Edit3.png" class="edit-icon"/>
<text class="edit-text">编辑</text>
</view>
<view class="setting-btn" bindtap="openSettings">
<image src="/images/Subtract.png" class="setting-icon"/>
</view>
</view>
<!-- 位置信息 -->
<view class="profile-location" bindtap="goVerify">
<image src="/images/location.png" class="location-icon"/>
<text class="location-text">291, Anwar Yousuf Road</text>
<!-- <text class="location-text">{{userInfo.location}}</text> -->
</view>
<!-- 个人简介 wx:if="{{!userInfo.user.verified}}"-->
<view class="profile-signature" bindtap="goVerify"> <!--测试期加了"!",实际是要删掉-->
<!--<text class="signature-text">{{userInfo.signature}} </text>-->
<text class="signature-text">Morem ipsum dolor sit amet, consectetur adipiscing elit. Nunc vulputate libero et velit interdum, ac aliquet odio mattis. </text>
</view>
<!-- 个人标签区域 -->
<view class="profile-tabs">
<scroll-view scroll-x="true" class="tab-scroll" enable-flex="true">
<view class="tab-item {{selectedTab === 'gender' ? 'active' : ''}}" data-tab="gender" bindtap="onTabSelect">
<text>{{userInfo.user.gender === 'male' ? '♂️' : userInfo.user.gender === 'female' ? '♀️' : ''}}</text>
</view>
<view class="tab-item {{selectedTab === 'age' ? 'active' : ''}}" data-tab="age" bindtap="onTabSelect">
<text>年龄 {{userInfo.age}}</text>
</view>
<view class="tab-item {{selectedTab === 'mood' ? 'active' : ''}}" data-tab="mood" bindtap="onTabSelect">
<text>心情 {{userInfo.mood}}</text>
</view>
<view class="tab-item {{selectedTab === 'personality' ? 'active' : ''}}" data-tab="personality" bindtap="onTabSelect">
<text>人格 {{userInfo.personality}}</text>
</view>
<view class="tab-item {{selectedTab === 'identity' ? 'active' : ''}}" data-tab="identity" bindtap="onTabSelect">
<text>身份 {{userInfo.identity}}</text>
</view>
<view class="tab-item {{selectedTab === 'constellation' ? 'active' : ''}}" data-tab="constellation" bindtap="onTabSelect">
<text>星座 {{userInfo.constellation}}</text>
</view>
<view class="tab-item {{selectedTab === 'school' ? 'active' : ''}}" data-tab="school" bindtap="onTabSelect">
<text>学校 {{userInfo.school}}</text>
</view>
<view class="tab-item {{selectedTab === 'occupation' ? 'active' : ''}}" data-tab="occupation" bindtap="onTabSelect">
<text>职业 {{userInfo.occupation}}</text>
</view>
</scroll-view>
</view>
</view>
</view>
<!-- My Footprints -->
<view class="myfootprint">
<!-- Title -->
<!-- White pill badge -->
<view class="footprint-badge">
<text>1个NOW2个地点</text>
</view>
</view>
<!-- Custom Post Input goes here -->
<view>
<text class="textcolor">我的动态</text>
<view class="input-box">
<input type="text" placeholder="Write something" class="input-field"/>
<image src="/images/cameras.png" class="camera-icon" mode="aspectFit"/>
</view>
</view>
<!-- My Moments -->
<view class="moments">
<view class="moment-card">
<!-- date & year -->
<view class="moment-header">
<text>26 Sep</text>
<text>2025</text>
</view>
<!-- description -->
<text class="moment-text">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Ea eveniet, delectus unde quibusdam ipsum fugiat nostrum rerum maiores quisquam enim?
</text>
<!-- image -->
<image src="/images/moments.png" mode="widthFix" class="moment-img"/>
</view>
</view>
<!-- 会员卡片 -->
<!-- 快捷功能区 -->
<!-- <view class="quick-actions">
<view class="action-row">
<view class="action-item" bindtap="openWallet">
<image src="/images/wallet-solid.png" class="action-icon wallete"/>
<text class="action-text">卡包</text>
</view>
<view class="action-item" bindtap="openFavorites">
<image src="/images/folder-favorite.png" class="action-icon favorites"/>
<text class="action-text">收藏</text>
</view>
<view class="action-item" bindtap="openCards">
<image src="/images/album-bold.png" class="action-icon album"/>
<text class="action-text">相册</text>
</view>
</view>
<view class="action-row">
<view class="action-item" bindtap="openStickers">
<image src="/images/emoji-lol.png" class="action-icon stickers"/>
<text class="action-text">圈子</text>
</view>
<view class="action-item" bindtap="openGames">
<image src="/images/game-fill.png" class="action-icon games"/>
<text class="action-text">游戏</text>
</view>
<view class="action-item" bindtap="openShopping">
<image src="/images/shopping-bag.png" class="action-icon shopping"/>
<text class="action-text">团购</text>
</view>
</view>
</view> -->
<!-- 调试信息卡片 -->
<view class="debug-card" wx:if="{{debugMode}}">
<view class="debug-header">
<view class="debug-icon">🔧</view>
<text class="debug-title">调试信息</text>
<view class="debug-toggle" bindtap="toggleDebug">
<text class="toggle-text">{{showDebugDetails ? '隐藏' : '展开'}}</text>
</view>
</view>
<view class="debug-content" wx:if="{{showDebugDetails}}">
<view class="debug-section">
<text class="debug-label">用户信息:</text>
<text class="debug-value">{{debugInfo.userInfo}}</text>
</view>
<view class="debug-section">
<text class="debug-label">Token状态:</text>
<text class="debug-value {{debugInfo.tokenValid ? 'success' : 'error'}}">
{{debugInfo.tokenValid ? '✅ 有效' : '❌ 无效'}}
</text>
</view>
<view class="debug-section">
<text class="debug-label">网络状态:</text>
<text class="debug-value">{{debugInfo.networkType || '未知'}}</text>
</view>
<view class="debug-section">
<text class="debug-label">版本信息:</text>
<text class="debug-value">{{debugInfo.version || 'v1.0.0'}}</text>
</view>
<view class="debug-actions">
<view class="debug-btn" bindtap="testAPI">
<text class="btn-text">测试API</text>
</view>
<view class="debug-btn" bindtap="showCacheStats">
<text class="btn-text">缓存统计</text>
</view>
<view class="debug-btn" bindtap="clearCache">
<text class="btn-text">清除缓存</text>
</view>
<view class="debug-btn" bindtap="exportLogs">
<text class="btn-text">导出日志</text>
</view>
</view>
</view>
</view>
<!-- 底部空间 - 确保所有内容都能滚动到 -->
<view class="bottom-space" style="height: {{safeAreaBottom + 60}}px;"></view>
</scroll-view>
<!-- 个人状态编辑弹窗 -->
<view class="status-modal {{showStatusModal ? 'show' : ''}}" wx:if="{{showStatusModal}}">
<view class="modal-mask" bindtap="hideStatusModal"></view>
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">设置状态</text>
<view class="modal-close" bindtap="hideStatusModal">✕</view>
</view>
<view class="modal-body">
<view class="status-options">
<view class="status-option"
wx:for="{{statusOptions}}"
wx:key="id"
bindtap="selectStatus"
data-status="{{item}}"
class="{{selectedStatus.id === item.id ? 'selected' : ''}}">
<view class="option-icon">{{item.icon}}</view>
<text class="option-text">{{item.text}}</text>
</view>
</view>
<view class="status-input">
<input class="custom-status"
placeholder="自定义状态..."
value="{{customStatus}}"
bindinput="onStatusInput" />
</view>
</view>
<view class="modal-footer">
<view class="modal-btn cancel" bindtap="hideStatusModal">取消</view>
<view class="modal-btn confirm" bindtap="saveStatus">保存</view>
</view>
</view>
</view>
<!-- 成就徽章展示 -->
<view class="achievement-banner" wx:if="{{showAchievement}}">
<view class="banner-content">
<view class="achievement-icon">🏆</view>
<view class="achievement-text">
<text class="achievement-title">恭喜获得新徽章!</text>
<text class="achievement-desc">{{newAchievement.name}}</text>
</view>
<view class="banner-close" bindtap="hideAchievement">✕</view>
</view>
</view>
</view>

861
pages/profile/profile.wxss Normal file
View file

@ -0,0 +1,861 @@
/* 个人资料页面 - 简化兼容版 */
@import "../../styles/design-system.wxss";
@import "../../styles/components.wxss";
/* 页面主容器样式 */
.profile-container {
height: 874px;
background: #000000;
display: flex;
flex-direction: column;
position: relative;
}
/* 滚动内容区样式 */
.profile-content {
flex: 1;
background: transparent;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* 个人信息卡片样式 */
.profile-card {
backdrop-filter: none;
border-radius: 24px;
margin: 16px;
position: relative;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.profile-card::before {
display: none;
}
/* 顶部区域样式 */
.profile-top {
display: flex;
align-items: center;
}
.avatar-section {
margin: 16px 16px 16px 16px;
}
.avatar-container {
background: rgba(255, 255, 255, 0.15);
width: 100px;
height: 100px;
position: relative;
border-radius: 999px;
overflow: hidden;
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.avatar-container:active {
transform: scale(0.95);
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 在线状态指示器 */
.online-status.online {
position: absolute;
top: 4px;
right: 4px;
width: 14px;
height: 14px;
background: #4CAF50;
border: 2px solid #ffffff;
border-radius: 50%;
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7); }
70% { box-shadow: 0 0 0 6px rgba(76, 175, 80, 0); }
100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
}
.profile-main-info {
flex: 1;
}
.profile-name-row {
display: flex;
align-items: center;
margin-bottom: 10px;
flex-wrap: nowrap;
overflow: hidden;
}
.profile-name {
font-size: 24px;
font-weight: 500;
color: #ffffff;
line-height: 1.2;
margin-right: 10px;
}
.profile-id {
display: flex;
align-items: center;
padding: 6px 0px;
width: fit-content;
}
.id-label {
font-size: 14px;
color: #f3f3f3;
font-weight: 400;
margin-right: 6px;
}
.id-value {
font-size: 14px;
color: #ffffff;
font-weight: 400;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
margin-right: 6px;
}
.verify-btn {
color: white;
font-size: 14px;
font-weight: 400;
padding: 3px 10px;
border-radius: 12px;
margin-left: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
line-height: 1;
}
.verified-tag {
color: #fa6294;
font-size: 14px;
font-weight: 400;
padding: 3px 10px;
border-radius: 12px;
margin-left: 8px;
border: 1px solid #50a853;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
line-height: 1;
}
.verify-btn-p {
width: 22px;
height: 22px;
padding: 2px;
border-radius: 4px;
display: block;
object-fit: contain;
}
.verified-tag-p {
width: 22px;
height: 22px;
padding: 2px;
border-radius: 4px;
display: block;
object-fit: contain;
}
/* 底部信息区样式 */
.profile-bottom {
background: linear-gradient(123deg, #8361FB 15.54%, #70AAFC 39.58%, #F0F8FB 62.43%, #F07BFF 90.28%);
border-radius: 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
.action-buttons {
display: flex;
width: fit-content;
margin-left: auto;
justify-content: flex-end;
gap: 2px;
margin-top: 1rem;
}
.qr-code-btn {
padding: 4px 8px;
display: flex;
align-items: center;
transition: all 0.2s ease;
}
.edit-btn {
background: linear-gradient(124deg, #FF6460 1.58%, #EC42C8 34.28%, #435CFF 54%, #00D5FF 84.05%);
border-radius: 999px;
padding: 0px 1rem;
display: flex;
align-items: center;
transition: all 0.2s ease;
}
.setting-btn {
padding: 4px 6px;
display: flex;
align-items: center;
transition: all 0.2s ease;
margin-left: .5rem;
}
.qr-code-icon {
width: 22px;
height: 22px;
margin-right: 6px;
}
.edit-icon {
width: 16px;
height: 16px;
margin-right: 6px;
}
.setting-icon {
width: 20px;
height: 20px;
margin-right: 6px;
}
.edit-text {
font-size: 14px;
font-weight: 400;
color: black;
}
.profile-location {
background: rgba(43, 43, 43, 1);
width:fit-content;
display: flex;
align-items: center;
padding: 8px 12px;
border-radius: 24px;
margin-left: 16px;
}
.location-icon {
width: 20px;
height: 20px;
margin-right: 6px;
}
.location-text {
font-size: 15px;
color: #e0e0e0;
line-height: 1.4;
}
.profile-signature {
height: 120px;
display: flex;
align-items: flex-start;
padding: 12px;
background: rgba(90, 90, 90, 0.548);
border-radius: 12px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), inset 0 0 10px rgba(255, 255, 255, 0.1);
margin-right:16px;
margin-left: 16px;
}
.signature-text {
font-size: 15px;
color: #000000;
margin: auto;
font-weight: 400;
}
.profile-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 12px;
border-radius: 12px;
margin-right:16px;
margin-left: 16px;
}
.tag-item {
display: flex;
align-items: center;
background: rgba(90, 90, 90, 0.3);
border-radius: 12px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), inset 0 0 10px rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 5px 12px;
border: 1px solid rgba(255, 255, 255, 0.08);
}
.tag-label {
font-size: 13px;
color: #ffffff;
margin-right: 6px;
font-weight: 400;
}
.tag-value {
font-size: 13px;
color: #ffffff;
}
.qr-code-btn:active, .edit-btn:active, .setting-btn:active {
transform: scale(0.95);
opacity: 0.8;
}
/* 会员卡片样式 */
.membership-card {
width: calc(100% - 32px);
height: 220px;
margin: 0 16px 16px;
border-radius: 12px;
padding: 24px;
display: flex;
justify-content: space-between;
background: linear-gradient(152deg, rgba(19, 157, 255, 0.3), rgba(49, 55, 234, 0.3), rgba(59, 196, 147, 0.3));
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08), inset 0 0 1px rgba(255, 255, 255, 0.5);
position: relative;
overflow: hidden;
}
.membership-left {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
width: 55%;
}
.logo-header {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.logo-image {
width: 48px;
height: 39px;
margin-right: 12px;
}
.logo-text {
color: #FFF;
font-family: 'Inter', sans-serif;
font-size: 20px;
font-weight: 400;
white-space: nowrap;
}
.benefit-tag {
display: flex;
align-items: center;
width: fit-content;
height:fit-content;
margin-left: 20px;
margin-bottom: 25px;
}
.benefit-content {
background: #1C4EFE;
border-radius: 9999px;
padding: 8px 16px;
width: fit-content;
display: flex;
align-items: center;
justify-content: center;
}
.benefit-text {
color: #FFF;
font-family: 'Inter', sans-serif;
font-size: 14px;
font-weight: 800;
white-space: nowrap;
}
.check-icon {
width: 16px;
height: 16px;
margin-left: 2px;
flex-shrink: 0;
object-fit: contain;
align-self: center !important;
}
.get-button {
display: flex;
justify-content: center;
align-items: center;
width: 150px;
height: 50px;
border-radius: 999px;
border: 1px solid #4E4E4E;
background: linear-gradient(262deg, #000 11.88%, #232323 91.52%);
box-shadow: 8px 0 12px 0 rgba(46, 173, 251, 0.25) inset;
padding: 0;
margin: 0;
}
.get-button-text {
color: #FFF;
font-family: 'Poppins', sans-serif;
font-size: 25px;
font-weight: 600;
white-space: nowrap;
line-height: 1;
}
.cards-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 45%;
}
.card-stack {
position: relative;
width: 200px;
height: 168px;
display: flex;
justify-content: center;
align-items: center;
}
.vip-card {
position: absolute;
width: 170px;
height: 119px;
border-radius: 14px;
box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25), 0 1px 1px 0 rgba(165, 165, 165, 0.25) inset, 0 0 1px 0 rgba(217, 217, 217, 0.20) inset;
transform-origin: center;
}
.card-15deg {
transform: rotate(-15deg)translate(-6px, 6px);
z-index: 3;
}
.card-10deg {
transform: rotate(-10deg);
z-index: 2;
}
.card-5deg {
transform: rotate(-5deg)translate(6px, -6px);
z-index: 1;
}
/* 功能模块样式 */
.quick-actions {
background: linear-gradient(123deg, rgba(19, 157, 255, 0.4) 14.61%, rgba(49, 55, 234, 0.4) 53.02%, rgba(59, 196, 147, 0.4) 96.97%);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border-radius: 20px;
margin: 0 16px 16px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1), inset 0 0 16px rgba(255, 255, 255, 0.1);
position: relative;
overflow: hidden;
}
.quick-actions::before {
content: "";
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle at 70% 30%, rgba(255, 255, 255, 0.15) 0%, transparent 60%);
transform: rotate(30deg);
pointer-events: none;
}
.action-row {
display: flex;
justify-content: space-around;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px;
transition: all 0.3s ease;
position: relative;
transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1.5);
}
.action-item:active {
transform: scale(0.96) translateY(1px);
background: rgba(102, 126, 234, 0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) inset, 0 1px 2px rgba(255, 255, 255, 0.2);
filter: brightness(0.98);
}
.action-icon {
width: 48px;
height: 48px;
margin-bottom: 8px;
display: block;
object-fit: contain;
background-color: transparent;
pointer-events: none;
}
.action-text {
font-size: 20px;
color: rgb(255, 255, 255);
font-weight: 400;
text-align: center;
}
.menu-section {
margin: 0 16px 16px;
padding-bottom: 20px;
}
.menu-group {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(20px);
border-radius: 16px;
margin-bottom: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.group-header {
display: flex;
align-items: center;
padding: 16px 20px 8px;
background: rgba(102, 126, 234, 0.05);
}
.group-icon {
font-size: 16px;
color: #667eea;
margin-right: 8px;
}
.group-title {
font-size: 14px;
font-weight: 600;
color: #2c3e50;
}
.menu-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item:active {
background: rgba(102, 126, 234, 0.05);
}
.menu-left {
display: flex;
align-items: center;
flex: 1;
}
.menu-icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #ffffff;
margin-right: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.menu-content {
flex: 1;
}
.menu-title {
font-size: 16px;
font-weight: 500;
color: #2c3e50;
line-height: 1.2;
margin-bottom: 2px;
}
.menu-subtitle {
font-size: 12px;
color: #666;
line-height: 1.3;
}
.menu-right {
display: flex;
align-items: center;
}
.menu-badge {
background: #ff4757;
color: white;
font-size: 10px;
font-weight: 600;
padding: 2px 6px;
border-radius: 8px;
margin-right: 8px;
min-width: 16px;
text-align: center;
}
.menu-arrow {
color: #bbb;
font-size: 18px;
font-weight: 300;
}
.logout-section {
margin: 0 16px 16px;
}
.logout-btn {
background: rgba(255, 71, 87, 0.1);
border: 1px solid rgba(255, 71, 87, 0.2);
border-radius: 16px;
padding: 16px 20px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
backdrop-filter: blur(10px);
}
.logout-btn:active {
background: rgba(255, 71, 87, 0.15);
transform: scale(0.98);
}
.logout-icon {
font-size: 16px;
color: #ff4757;
margin-right: 8px;
}
.logout-text {
font-size: 16px;
color: #ff4757;
font-weight: 600;
}
.bottom-space {
margin-bottom: env(safe-area-inset-bottom);
min-height: 40px;
}
/* 响应式设计 */
@media (max-width: 375px) {
.profile-header {
flex-direction: column;
align-items: center;
text-align: center;
}
.avatar-section {
margin-right: 0;
margin-bottom: 16px;
}
.profile-stats {
justify-content: center;
}
.action-item {
padding: 8px;
}
.action-icon {
width: 40px;
height: 40px;
font-size: 16px;
}
.action-text {
font-size: 11px;
}
}
.profile-tabs {
width: 100%;
margin-bottom: 1rem;
}
.tab-scroll {
display: flex;
flex-direction: row;
white-space: nowrap;
}
.tab-item {
margin-top: .5rem;
padding: 15rpx 28rpx; /* smaller padding vertically + horizontally */
font-size: 25rpx; /* slightly smaller text */
line-height: 45rpx; /* controls the text vertical alignment */
height: 60rpx; /* explicit height for the box */
display: flex; /* ensures vertical centering */
align-items: center; /* centers text in the box */
color: white;
border-radius: 10px;
background: rgba(90, 90, 90, 0.548);
transition: all 0.2s;
margin-left: .3rem;
margin-right: .3rem;
}
.tab-item.active {
color: #fff;
background: rgba(90, 90, 90, 0.836);
font-weight: bold;
}
.test-text{
color: white;
}
.profile-bottom {
background: linear-gradient(123deg, #8361FB 15.54%, #70AAFC 39.58%, #F0F8FB 62.43%, #F07BFF 90.28%);
border-radius: 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
.input-box {
display: flex;
align-items: center;
border: 1px solid lightgray;
border-radius: 25rpx;
padding: .8rem; /* inner padding */
margin: 20rpx 35rpx; /* outer margin */
color: white;
}
.input-field {
flex: 1; /* take full width */
font-size: 30rpx;
border: none; /* remove native border */
outline: none; /* remove focus outline */
}
.camera-icon {
width: 50rpx;
height: 50rpx;
margin-left: 12rpx;
color: white;
}
.moments {
padding: 30rpx;
}
.moment-card {
border: 1px solid #eee;
border-radius: 12rpx;
padding: 20rpx;
color: white;
}
.moment-header {
display: flex;
justify-content: space-between; /* space between date and year */
font-size: 28rpx;
font-weight: bold;
}
.moment-text {
font-size: 26rpx;
color: white;
line-height: 1.5;
margin-bottom: 15rpx;
display: block;
}
.moment-img {
width: 100%;
border-radius: 8rpx;
}
.textcolor{
color: white;
margin-left: 1.5rem;
}
.myfootprint{
margin: 30rpx;
padding: 20rpx;
border-radius: 20rpx;
background: linear-gradient(123deg, #8361FB 15.54%, #70AAFC 39.58%, #F0F8FB 62.43%, #F07BFF 90.28%);
position: relative;
height: 200rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* White pill badge */
.footprint-badge {
position: absolute;
bottom: 30rpx;
right: 30rpx;
background: #fff;
padding: 8rpx 20rpx;
border-radius: 30rpx;
z-index: 3;
}
.footprint-badge text {
font-size: 26rpx;
font-weight: bold;
color: #000;
}