// pages/profile/index.js import request from '../../utils/request'; const app = getApp(); Page({ data: { view: 'profile', // profile, favorites, posts, about // User Info userName: '植物爱好者', userAvatar: '', userLevel: '', // Reserved for future level system userLevelTag: '', // e.g. 'Lv.4 资深植人' // Stats plantCount: 0, taskDoneCount: 0, postCount: 0, // Favorites favTab: 'all', favorites: [], filteredFavorites: [], // Posts postsTab: 'published', myPublishedPosts: [], myDrafts: [], // App version appVersion: '1.0.0' }, onLoad() { this.loadUserInfo(); }, onShow() { if (typeof this.getTabBar === 'function' && this.getTabBar()) { this.getTabBar().setData({ selected: 4 }); } // Always fetch fresh profile data this.loadUserInfo(); }, // ======== User Info ======== loadUserInfo() { request.get('/profile/detail').then(res => { if (!res) return; // Map stats and level info const avatarUrl = res.avatar && res.avatar.url ? res.avatar.url : ''; const levelInfo = res.level || {}; const levelTag = levelInfo.level ? `Lv.${levelInfo.level} ${levelInfo.title || ''}` : ''; this.setData({ userName: res.nickname || '植物爱好者', userAvatar: avatarUrl, // Stats plantCount: res.plantCount || 0, taskDoneCount: res.careCount || 0, postCount: res.postCount || 0, // Level (if available) userLevel: levelInfo.level || 0, userLevelTag: levelTag, // EXP / Sunlight userExp: res.currentSunlight || 0 }); // Update global cache const info = { ...res, avatarId: res.avatarId || (res.avatar ? res.avatar.id : '') }; app.globalData.userInfo = info; wx.setStorageSync('userInfo', info); }).catch(err => { console.error('Load profile failed', err); }); }, // ======== Stats ======== // ======== Navigation ======== setView(e) { const view = e.currentTarget.dataset.view; this.setData({ view }); if (view === 'favorites') { this.loadFavorites(); } else if (view === 'posts') { this.loadMyPosts(); this.loadDrafts(); } }, goBack() { this.setData({ view: 'profile' }); }, // ======== Favorites ======== loadFavorites() { // TODO: Call favorites API when available // request.get('/user/favorites').then(...) this.filterFavorites(); }, onFavTabChange(e) { this.setData({ favTab: e.detail.value }, () => { this.filterFavorites(); }); }, filterFavorites() { const { favorites, favTab } = this.data; const filtered = favorites.filter(item => { if (favTab === 'all') return true; return item.type === favTab; }); this.setData({ filteredFavorites: filtered }); }, // ======== Posts ======== loadMyPosts() { request.post('/post/page', { current: 1, pageSize: 50, onlyMine: true }).then(res => { const records = res.records || res.list || []; const posts = records.map(item => { const publisher = item.publisher || {}; const imgList = item.imgList || []; return { id: item.id, content: item.content || '', time: this._formatTime(item.createdAt || item.createTime), images: imgList.map(img => img.url), likes: item.likeList || [], comments: item.commentList || [] }; }); this.setData({ myPublishedPosts: posts }); }).catch(() => { this.setData({ myPublishedPosts: [] }); }); }, loadDrafts() { try { const draft = wx.getStorageSync('post_draft'); if (draft && (draft.content || (draft.images && draft.images.length > 0))) { this.setData({ myDrafts: [{ id: 'draft_1', content: draft.content || '', images: draft.images || [], selectedTopics: draft.selectedTopics || [] }] }); } else { this.setData({ myDrafts: [] }); } } catch (e) { this.setData({ myDrafts: [] }); } }, onPostsTabChange(e) { this.setData({ postsTab: e.detail.value }); }, deletePost(e) { const postId = e.currentTarget.dataset.id; wx.showModal({ title: '删除动态', content: '确定要删除这条动态吗?', confirmColor: '#EF5350', success: (res) => { if (!res.confirm) return; wx.showLoading({ title: '删除中...' }); request.get('/post/delete', { id: postId }).then(() => { wx.hideLoading(); this.loadMyPosts(); wx.showToast({ title: '已删除', icon: 'success' }); }).catch(() => { wx.hideLoading(); wx.showToast({ title: '删除失败', icon: 'none' }); }); } }); }, editDraft() { wx.navigateTo({ url: '/pages/community/create/index' }); }, deleteDraft() { wx.showModal({ title: '删除草稿', content: '确定要删除这份草稿吗?', confirmColor: '#EF5350', success: (res) => { if (!res.confirm) return; try { wx.removeStorageSync('post_draft'); } catch (e) { } this.setData({ myDrafts: [] }); wx.showToast({ title: '已删除', icon: 'success' }); } }); }, // ======== Menu Actions ======== goToIdentifyHistory() { wx.navigateTo({ url: '/pages/profile/identify-history/index' }); }, goToBadges() { wx.navigateTo({ url: '/pages/profile/badges/index' }); }, goToNotificationSettings() { // Open WeChat notification settings wx.openSetting({ success: (res) => { console.log('Settings opened', res); } }); }, goToAbout() { this.setData({ view: 'about' }); }, goToAgreement() { // TODO: Navigate to agreement page or show inline wx.showToast({ title: '功能开发中', icon: 'none' }); }, goToPrivacy() { // TODO: Navigate to privacy page or show inline wx.showToast({ title: '功能开发中', icon: 'none' }); }, // ======== Profile Editor Popup ======== openProfileEditor() { this.setData({ showProfileEditor: true, tempAvatar: '', tempNickname: this.data.userName === '植物爱好者' ? '' : this.data.userName }); }, closeProfileEditor() { this.setData({ showProfileEditor: false }); }, onProfilePopupChange(e) { if (!e.detail.visible) { this.setData({ showProfileEditor: false }); } }, // WeChat native chooseAvatar callback onChooseAvatar(e) { const avatarUrl = e.detail.avatarUrl; if (avatarUrl) { this.setData({ tempAvatar: avatarUrl }); } }, onNicknameInput(e) { this.setData({ tempNickname: e.detail.value }); }, onNicknameBlur(e) { // WeChat nickname type may return value on blur if (e.detail.value) { this.setData({ tempNickname: e.detail.value }); } }, async saveProfile() { const { tempAvatar, tempNickname, userName, userAvatar } = this.data; // Check if anything changed const isNameChanged = tempNickname && tempNickname !== userName; const isAvatarChanged = tempAvatar && tempAvatar !== userAvatar; if (!isNameChanged && !isAvatarChanged) { this.setData({ showProfileEditor: false }); return; } wx.showLoading({ title: '保存中...', mask: true }); try { const updatePayload = {}; // 1. Upload avatar if changed if (isAvatarChanged) { const uploadRes = await request.upload(tempAvatar); // Correctly extract ID from file object based on known API response let fileId = ''; if (uploadRes && uploadRes.file && uploadRes.file.id) { fileId = uploadRes.file.id; } else if (uploadRes && uploadRes.id) { fileId = uploadRes.id; } if (fileId) { updatePayload.avatarId = fileId; } } // 2. Set nickname if changed if (isNameChanged) { updatePayload.nickname = tempNickname; } // 3. Call update API if (Object.keys(updatePayload).length > 0) { await request.post('/profile/update', updatePayload); } wx.hideLoading(); // 4. Update local state this.setData({ userName: tempNickname || userName, userAvatar: tempAvatar || userAvatar, // Use tempAvatar for immediate display showProfileEditor: false }); wx.showToast({ title: '资料已更新', icon: 'success' }); // 5. Update globalData const userInfo = app.globalData.userInfo || {}; if (updatePayload.nickname) userInfo.name = updatePayload.nickname; if (updatePayload.avatarId) userInfo.avatarId = updatePayload.avatarId; // Also update URL if we have it locally? // Better to re-fetch profile to get canonical URL, but optimistic update is fine. userInfo.avatar = tempAvatar; app.globalData.userInfo = userInfo; } catch (err) { wx.hideLoading(); console.error('Save profile failed', err); wx.showToast({ title: '保存失败', icon: 'none' }); } }, // ======== Utilities ======== _formatTime(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr); const now = new Date(); const diffMs = now - d; const diffMin = Math.floor(diffMs / 60000); if (diffMin < 1) return '刚刚'; if (diffMin < 60) return diffMin + '分钟前'; const diffHour = Math.floor(diffMin / 60); if (diffHour < 24) return diffHour + '小时前'; const diffDay = Math.floor(diffHour / 24); if (diffDay < 7) return diffDay + '天前'; const month = (d.getMonth() + 1).toString().padStart(2, '0'); const day = d.getDate().toString().padStart(2, '0'); return `${month}-${day}`; }, // ======== Reserved: Future Level/Badge System ======== // These methods will be implemented when the backend supports level/badge APIs // loadLevelInfo() { request.get('/user/level').then(...) }, // loadBadges() { request.get('/user/badges').then(...) }, })