Initial Commit
This commit is contained in:
commit
1d71a02738
237 changed files with 64293 additions and 0 deletions
805
pages/profile/profile.js
Normal file
805
pages/profile/profile.js
Normal 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'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
5
pages/profile/profile.json
Normal file
5
pages/profile/profile.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"navigationBarTitleText": "个人资料",
|
||||
"navigationBarTextStyle": "white",
|
||||
"disableScroll": true
|
||||
}
|
||||
305
pages/profile/profile.wxml
Normal file
305
pages/profile/profile.wxml
Normal 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个NOW,2个地点</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
861
pages/profile/profile.wxss
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue