const app = getApp() function normalizeAvatarUrl(avatarUrl, baseUrl) { if (!avatarUrl) return '' return avatarUrl.startsWith('http') ? avatarUrl : `${baseUrl}${avatarUrl}` } function requestFailHint(errMsg) { const m = String(errMsg || '').toLowerCase() if (m.includes('domain') || m.includes('合法域名')) return '域名未在公众平台配置,或未开启 HTTPS' if (m.includes('ssl') || m.includes('certificate')) return 'HTTPS 证书异常,请检查服务器证书' if (m.includes('timeout') || m.includes('timed out')) return '连接超时,请稍后重试' return '网络异常,请检查网络后重试' } const DEFAULT_AVATAR = 'data:image/svg+xml;utf8,' + encodeURIComponent(` `) Page({ data: { name: '', nickname: '', days: 0, badges: 0, openid: '', avatarUrl: '', defaultAvatarUrl: DEFAULT_AVATAR, loading: false, error: '', showNicknameModal: false, nicknameDraft: '', }, onShow() { this.loadProfile() }, async loadProfile() { const openid = app.globalData.openid || wx.getStorageSync('openid') || '' if (!openid) { this.setData({ loading: false, openid: '', name: '未登录', nickname: '未登录', avatarUrl: '', nicknameDraft: '', badges: 0, days: 0, error: '', }) return } this.setData({ loading: true, error: '' }) app.globalData.openid = openid const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl let profileErrMsg = '' wx.request({ url: `${baseUrl}/api/v1/auth/profile?openid=${encodeURIComponent(openid)}`, method: 'GET', success: (res) => { const body = res.data || {} const ok = res.statusCode >= 200 && res.statusCode < 300 && body.code === 0 && body.data if (!ok) { profileErrMsg = '个人信息加载失败,请稍后重试' const storedAvatarUrl = wx.getStorageSync(`avatarUrl:${openid}`) || '' const normalizedAvatar = normalizeAvatarUrl(storedAvatarUrl, baseUrl) this.setData({ openid, name: '考研战士', nickname: '考研战士', avatarUrl: normalizedAvatar, nicknameDraft: '考研战士', error: profileErrMsg, }) return } const profile = body.data const avatarUrl = wx.getStorageSync(`avatarUrl:${openid}`) || profile.avatar_url || '' const nickname = profile.nickname || '考研战士' const normalizedAvatar = normalizeAvatarUrl(avatarUrl, baseUrl) profileErrMsg = '' this.setData({ openid, name: nickname, nickname, avatarUrl: normalizedAvatar, nicknameDraft: nickname, days: Number(profile.days || 0), error: '', }) }, fail: () => { profileErrMsg = '个人信息加载失败,请稍后重试' const storedAvatarUrl = wx.getStorageSync(`avatarUrl:${openid}`) || '' const normalizedAvatar = normalizeAvatarUrl(storedAvatarUrl, baseUrl) this.setData({ openid, name: '考研战士', nickname: '考研战士', avatarUrl: normalizedAvatar, nicknameDraft: '考研战士', error: profileErrMsg, }) }, complete: () => { this.loadAchievements(profileErrMsg) this.setData({ loading: false }) }, }) }, openNicknameModal() { this.setData({ showNicknameModal: true, nicknameDraft: this.data.nickname || this.data.name || '考研战士' }) }, onAvatarError() { this.setData({ avatarUrl: this.data.defaultAvatarUrl }) }, 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.getApiBaseUrl ? app.getApiBaseUrl() : 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' }), }) }, onChooseAvatar(e) { const tempPath = e.detail?.avatarUrl || '' if (!app.globalData.openid) { wx.showToast({ title: '请先登录', icon: 'none' }) return } if (!tempPath) { wx.showToast({ title: '未选择头像', icon: 'none' }) return } const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl wx.uploadFile({ url: `${baseUrl}/api/v1/auth/avatar?openid=${encodeURIComponent(app.globalData.openid)}`, filePath: tempPath, name: 'file', success: (uploadRes) => { try { const payload = JSON.parse(uploadRes.data || '{}') const ok = uploadRes.statusCode >= 200 && uploadRes.statusCode < 300 && payload.code === 0 && payload.data if (!ok) { wx.showToast({ title: payload.message || '头像上传失败', icon: 'none' }) return } const rel = payload.data.avatar_url || '' const full = normalizeAvatarUrl(rel, baseUrl) wx.setStorageSync(`avatarUrl:${app.globalData.openid}`, full) this.setData({ avatarUrl: full }) wx.showToast({ title: '头像已更新', icon: 'success' }) } catch { wx.showToast({ title: '头像上传结果异常', icon: 'none' }) } }, fail: (err) => wx.showToast({ title: requestFailHint(err.errMsg), icon: 'none' }), }) }, loadAchievements(profileErrMsg) { if (!app.globalData.openid) { this.setData({ error: '登录状态已失效,请重新进入页面' }) return } const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl const achFail = '成就数据加载失败,请稍后重试' wx.request({ url: `${baseUrl}/api/v1/achievements`, header: { 'X-OpenID': app.globalData.openid }, success: (res) => { const body = res.data || {} const ok = res.statusCode >= 200 && res.statusCode < 300 && body.code === 0 && Array.isArray(body.data) if (!ok) { this.setData({ error: profileErrMsg ? `${profileErrMsg};${achFail}` : achFail }) return } const list = body.data const badges = list.filter((i) => i.is_unlocked).length this.setData({ badges, error: profileErrMsg || '' }) }, fail: (err) => { const netHint = requestFailHint(err.errMsg) const line = profileErrMsg ? `${profileErrMsg};${achFail}` : achFail this.setData({ error: `${line}(${netHint})` }) }, }) }, manualLogin() { wx.login({ success: (res) => { const code = res.code || '' console.log('[login] wx.login success', res) console.log('[login] code', code) if (!code) { wx.showToast({ title: '登录失败,请重试', icon: 'none' }) return } wx.request({ url: `${app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl}/api/v1/auth/wx-login`, method: 'POST', header: { 'Content-Type': 'application/json' }, data: { code }, success: (loginRes) => { console.log('[login] wx-login success', loginRes) const payload = loginRes.data?.data || {} if (loginRes.statusCode >= 200 && loginRes.statusCode < 300 && loginRes.data?.code === 0 && payload.openid) { app.globalData.openid = payload.openid wx.setStorageSync('openid', payload.openid) wx.showToast({ title: '登录成功', icon: 'success' }) this.loadProfile() return } console.log('[login] wx-login invalid response', loginRes.data) wx.showToast({ title: '登录接口异常,请稍后重试', icon: 'none' }) }, fail: (err) => { console.log('[login] wx-login fail', err) wx.showToast({ title: requestFailHint(err.errMsg), icon: 'none' }) }, }) }, fail: (err) => { console.log('[login] wx.login fail', err) wx.showToast({ title: '登录失败,请重试', icon: 'none' }) }, }) }, 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() {}, })