Files
wechat-clock-mini/pages/profile/profile.js
王宏豪 ba2d9245ef feat(api): 重构图片地址管理与上传机制
- 替换开发环境API基础URL为本地服务地址
- 实现签名URL本地缓存机制,解决微信按URL缓存图片导致重复下载问题
- 添加缓存清理功能,包括过期清理和按需删除特定缓存项
- 集成阿里云OSS上传功能,支持直传减少服务器带宽占用
- 优化头像地址处理逻辑,统一使用签名缓存避免地址频繁变化
- 移除过时的OSS上传示例文件oss-upload.js
- 更新课程页面实现跳转第三方小程序功能
- 调整打卡记录图片地址获取方式,统一使用新的签名缓存逻辑
- 优化图片上传流程,增加压缩处理和批量上传支持
- 完善认证模块中的缓存清理逻辑,切换账号时清除相关图片缓存
- 添加后端环境变量配置模板和代理文档说明
2026-09-23 17:46:40 +08:00

277 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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'
};
}
});