const app = getApp() Page({ data: { name: '', nickname: '', days: 0, badges: 0, openid: '', avatarUrl: '', loading: false, error: '', showNicknameModal: false, nicknameDraft: '', }, onShow() { this.loadProfile() }, async loadProfile() { this.setData({ loading: true, error: '' }) await app.ensureLogin() if (!app.globalData.openid) { this.setData({ loading: false, error: '登录失败,请重试' }) return } wx.request({ url: `${app.globalData.baseUrl}/api/v1/auth/profile`, method: 'GET', data: { openid: app.globalData.openid }, success: (res) => { const profile = res.data?.data || {} const avatarUrl = profile.avatar_url || wx.getStorageSync(`avatarUrl:${app.globalData.openid}`) || '' const nickname = profile.nickname || '考研搭子' const normalizedAvatar = avatarUrl.startsWith('http') ? avatarUrl : avatarUrl ? `${app.globalData.baseUrl}${avatarUrl}` : '' this.setData({ openid: app.globalData.openid, name: nickname, nickname, avatarUrl: normalizedAvatar, nicknameDraft: nickname, }) }, fail: () => { const storedAvatarUrl = wx.getStorageSync(`avatarUrl:${app.globalData.openid}`) || '' const normalizedAvatar = storedAvatarUrl.startsWith('http') ? storedAvatarUrl : storedAvatarUrl ? `${app.globalData.baseUrl}${storedAvatarUrl}` : '' this.setData({ openid: app.globalData.openid, name: '考研搭子', nickname: '考研搭子', avatarUrl: normalizedAvatar, nicknameDraft: '考研搭子' }) }, complete: () => { this.loadAchievements() this.setData({ loading: false }) }, }) }, openNicknameModal() { this.setData({ showNicknameModal: true, nicknameDraft: this.data.nickname || this.data.name || '考研搭子' }) }, onAvatarError() { this.setData({ avatarUrl: '' }) }, closeNicknameModal() { this.setData({ showNicknameModal: false, nicknameDraft: this.data.nickname || this.data.name || '考研搭子' }) }, onNicknameInput(e) { this.setData({ nicknameDraft: e.detail.value }) }, saveNickname() { if (!app.globalData.openid) return const nickname = (this.data.nicknameDraft || '').trim() if (!nickname) { wx.showToast({ title: '昵称不能为空', icon: 'none' }) return } wx.request({ url: `${app.globalData.baseUrl}/api/v1/auth/profile`, method: 'PUT', header: { 'Content-Type': 'application/json' }, data: { openid: app.globalData.openid, nickname }, success: (res) => { const profile = res.data?.data || {} const nextNickname = profile.nickname || nickname this.setData({ nickname: nextNickname, name: nextNickname, nicknameDraft: nextNickname, showNicknameModal: false }) wx.showToast({ title: '昵称已保存', icon: 'success' }) }, fail: () => wx.showToast({ title: '保存失败', icon: 'none' }), }) }, chooseAvatar() { if (!app.globalData.openid) { wx.showToast({ title: '请先登录', icon: 'none' }) return } wx.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: (res) => { const localPath = res.tempFilePaths?.[0] || '' if (!localPath) return wx.getImageInfo({ src: localPath, success: (info) => { const size = 320 const side = Math.min(info.width, info.height) const x = Math.max(0, Math.floor((info.width - side) / 2)) const y = Math.max(0, Math.floor((info.height - side) / 2)) const ctx = wx.createCanvasContext('avatar-canvas', this) ctx.clearRect(0, 0, size, size) ctx.save() ctx.beginPath() ctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI) ctx.clip() ctx.drawImage(localPath, x, y, side, side, 0, 0, size, size) ctx.draw(false, () => { wx.canvasToTempFilePath({ canvasId: 'avatar-canvas', width: size, height: size, destWidth: size, destHeight: size, quality: 0.85, success: (canvasRes) => { const uploadPath = canvasRes.tempFilePath || localPath wx.uploadFile({ url: `${app.globalData.baseUrl}/api/v1/auth/avatar?openid=${encodeURIComponent(app.globalData.openid)}`, filePath: uploadPath, name: 'file', success: (uploadRes) => { try { const payload = JSON.parse(uploadRes.data) const avatarUrl = payload?.data?.avatar_url || '' if (!avatarUrl) throw new Error('missing avatar_url') const fullUrl = avatarUrl.startsWith('http') ? avatarUrl : `${app.globalData.baseUrl}${avatarUrl}` wx.setStorageSync(`avatarUrl:${app.globalData.openid}`, fullUrl) this.setData({ avatarUrl: fullUrl }) wx.request({ url: `${app.globalData.baseUrl}/api/v1/auth/profile`, method: 'GET', data: { openid: app.globalData.openid }, success: (profileRes) => { const profile = profileRes.data?.data || {} const nextNickname = profile.nickname || '考研搭子' this.setData({ name: nextNickname, avatarUrl: fullUrl }) }, }) wx.showToast({ title: '头像已上传', icon: 'success' }) } catch (err) { wx.showToast({ title: '头像上传失败', icon: 'none' }) } }, fail: () => wx.showToast({ title: '头像上传失败', icon: 'none' }), }) }, fail: () => wx.showToast({ title: '头像处理失败', icon: 'none' }), }, this) }) }, fail: () => wx.showToast({ title: '头像处理失败', icon: 'none' }), }) }, }) }, loadAchievements() { if (!app.globalData.openid) { this.setData({ error: '未获取到登录态' }) return } wx.request({ url: `${app.globalData.baseUrl}/api/v1/achievements`, header: { 'X-OpenID': app.globalData.openid }, success: (res) => { const list = res.data?.data || [] const badges = Array.isArray(list) ? list.filter((i) => i.is_unlocked).length : 0 this.setData({ badges }) }, fail: () => this.setData({ error: '成就数据加载失败' }), }) }, goTo(e) { const { url } = e.currentTarget.dataset if (!app.globalData.openid) { wx.showToast({ title: '请先登录', icon: 'none' }) return } wx.navigateTo({ url }) }, goHome() { wx.reLaunch({ url: '/pages/index/index' }) }, goToReview() { wx.navigateTo({ url: '/pages/review-config/review-config' }) }, goToMe() {}, noop() {}, })