const api = require('../../utils/api'); const auth = require('../../utils/auth'); const ROLE_LABELS = { student: '成员', member: '成员', teacher: '管理员', leader: '组长', 'group_leader': '组长', admin: '管理员' }; const LOGIN_TYPE_LABELS = { wechat: '微信授权', phone: '手机号', admin: '管理员' }; Page({ data: { view: 'loading', profile: null, phoneLoading: false, phoneError: '', loadError: '', avatarSrc: '', avatarFallback: '明', roleLabel: '', loginTypeLabel: '', classGroups: [], avatarUploading: false, privacyAgreed: false }, onShow() { this.setData({ phoneError: '', privacyAgreed: !!wx.getStorageSync('privacy_agreed') }); this.loadPage(); }, onPullDownRefresh() { this.refresh().finally(() => wx.stopPullDownRefresh()); }, refresh() { if (this.data.view !== 'profile') { return this.loadPage(); } return new Promise((resolve) => { api.request({ url: '/api/v1/profile' }) .then((res) => { this.applyProfile(res); resolve(); }) .catch(() => { wx.showToast({ title: '刷新失败', icon: 'none' }); resolve(); }); }); }, loadPage() { this.setData({ loadError: '' }); return auth.ensureLogin() .then((token) => { if (!token) { this.showPhoneLogin(); return null; } this.setData({ view: 'loading' }); return this.loadProfile(); }) .catch(() => { this.showPhoneLogin(); }); }, loadProfile() { return api.request({ url: '/api/v1/profile' }) .then((res) => { this.applyProfile(res); }) .catch((err) => { if (!auth.getToken()) { this.showPhoneLogin(); } else { this.setData({ view: 'loadError', loadError: (err && err.error) || '加载失败' }); } }); }, applyProfile(res) { const displayName = res.display_name || res.name || ''; const classGroups = (res.class_groups || []).map((g) => ({ class_id: g.class_id, class_code: g.class_code, period: g.period, group: g.group, role: this.roleLabel(g.role) })); this.setData({ view: 'profile', profile: res, phoneError: '', loadError: '', // 必须走 api.avatarUrl(内部先查本地签名缓存),不能直接取 res.avatar_url: // 后端每次请求都现签一个新 URL,直接用会让地址每次进页面都变, // 而微信是按 URL 缓存图片的 —— 等于每次重新下载头像。 avatarSrc: this.fullUrl(res), avatarFallback: this.firstChar(displayName), roleLabel: this.roleLabel(res.role), loginTypeLabel: this.loginTypeLabel(res.login_type), classGroups }); }, showPhoneLogin() { this.setData({ view: 'phoneLogin', phoneLoading: false, privacyAgreed: !!wx.getStorageSync('privacy_agreed') }); }, roleLabel(role) { return ROLE_LABELS[role] || role || '成员'; }, loginTypeLabel(type) { return LOGIN_TYPE_LABELS[type] || type || '手机号'; }, /** * 头像转可显示地址。整个 payload 交给 api.avatarUrl,这样它既能查本地签名缓存, * 也能在后端给了 avatar_url 时写回缓存 —— 与打卡附件走的是同一套逻辑。 * 兼容 avatars/{uid}/x 与 /clock/static/avatars/{uid}/x 两种历史格式。 */ fullUrl(payload) { return api.avatarUrl(payload); }, firstChar(str) { const t = (str && typeof str === 'string') ? str.trim() : ''; return t ? t.charAt(0) : '明'; }, onChooseAvatar(e) { const path = e.detail && e.detail.avatarUrl; if (!path) { wx.showToast({ title: '未选择头像', icon: 'none' }); return; } this.setData({ avatarUploading: true }); wx.uploadFile({ url: api.BASE_URL + '/api/v1/profile/avatar', filePath: path, name: 'file', header: { Authorization: 'Bearer ' + auth.getToken() }, success: (res) => { let data = {}; try { data = JSON.parse(res.data); } catch (e) { data = {}; } if (data.success && data.wechat_avatar) { const newAvatar = data.wechat_avatar; // ⚠️ 换头像 = **key 没变、内容变了**。必须清掉缓存里的那一条, // 否则下次进「我的」页会命中旧 URL —— 微信按 URL 缓存图片,用户会看到旧头像。 api.forgetSignedUrl(newAvatar); // 本页立刻用后端刚签的地址显示(不走缓存),确保看到的就是新头像 const newUrl = data.avatar_url || this.fullUrl(data); this.setData({ 'profile.wechat_avatar': newAvatar, avatarSrc: newUrl }); wx.showToast({ title: '头像已更新', icon: 'success' }); } else { wx.showToast({ title: data.error || '上传失败', icon: 'none' }); } }, fail: (err) => { wx.showToast({ title: '上传失败', icon: 'none' }); }, complete: () => { this.setData({ avatarUploading: false }); } }); }, onGetPhoneNumber(e) { if (this.data.phoneLoading) { return; } if (!this.data.privacyAgreed) { wx.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none', duration: 2000 }); return; } const phoneCode = e.detail && e.detail.code; if (!phoneCode) { const message = '未授权手机号,请点击上方按钮重试'; this.setData({ phoneError: message }); wx.showToast({ title: message, icon: 'none' }); return; } this.setData({ phoneLoading: true, phoneError: '' }); auth.loginWithPhone(phoneCode) .then(() => this.loadProfile()) .catch((err) => { const message = (err && err.error) || '手机号登录失败'; this.setData({ phoneLoading: false, phoneError: message }); wx.showToast({ title: message, icon: 'none', duration: 2500 }); }); }, onRetry() { this.loadPage(); }, togglePrivacy() { const agreed = !this.data.privacyAgreed; this.setData({ privacyAgreed: agreed }); if (agreed) { wx.setStorageSync('privacy_agreed', true); } else { wx.removeStorageSync('privacy_agreed'); } }, openPrivacy(e) { const type = e.currentTarget.dataset.type || 'privacy'; wx.navigateTo({ url: '/pages/privacy/privacy?type=' + type }); }, logoutAccount() { wx.showModal({ title: '退出登录', content: '确定要退出登录吗?退出后将清除本机登录信息,需要重新登录。', confirmText: '退出', confirmColor: '#c0392b', success: (res) => { if (!res.confirm) return; try { api.request({ url: '/api/v1/auth/logout', method: 'POST' }).catch(() => {}); } catch (e) {} auth.clearToken(); wx.removeStorageSync('privacy_agreed'); wx.showToast({ title: '已退出', icon: 'success' }); setTimeout(() => { wx.reLaunch({ url: '/pages/home/home' }); }, 1000); } }); }, // ---- 分享 ---- onShareAppMessage() { const name = (this.data.profile && (this.data.profile.display_name || this.data.profile.name)) || ''; const title = name ? name + ' 推荐你用明缘云道场,打卡学习超好用' : '推荐你用明缘云道场,打卡学习超好用'; return { title, path: '/pages/home/home?from=profile_share' }; }, onShareTimeline() { return { title: '明缘云道场 · 学习成长,每日精进', query: 'from=timeline' }; } });