195 lines
5.8 KiB
JavaScript
195 lines
5.8 KiB
JavaScript
/**
|
||
* 头像与照片页 U08(Phase 04 P04-T23)
|
||
*
|
||
* 重构要点(遵循 AGENTS.md 不变量 + Phase 04 验收条件):
|
||
* - 数据:profileService.getPhotos(每次从服务端获取,不在本地缓存图片 URL —— 验收"图片URL非永久公开")
|
||
* - 上传:profileService.requestUploadToken + wx.uploadFile(EXIF 由服务端清理,前端不处理)
|
||
* - 删除:profileService.deletePhoto
|
||
* - 主头像:profileService.setMainPhoto(第一张或用户指定)
|
||
* - 数量限制:BUSINESS_LIMITS.MAX_PROFILE_PHOTOS(第 12 条,不硬编码)
|
||
* - 敏感数据:本页不涉及身份证/手机号(第 8 条)
|
||
* - 下一步条件:至少 1 张主头像
|
||
*/
|
||
const { appStore } = require('../../stores');
|
||
const { profileService } = require('../../services');
|
||
const { ROUTE, BUSINESS_LIMITS } = require('../../constants');
|
||
|
||
Page({
|
||
data: {
|
||
statusBarHeight: 20,
|
||
navBarHeight: 44,
|
||
loading: false,
|
||
saving: false,
|
||
avatarPhotos: [],
|
||
lifestylePhotos: [],
|
||
maxLifestyle: BUSINESS_LIMITS.MAX_PROFILE_PHOTOS - 1,
|
||
// 页面说明
|
||
guidelines: [
|
||
'请使用本人近期真实照片',
|
||
'禁止网图、二维码、广告、联系方式图片',
|
||
'建议真人生活照,避免过度美颜',
|
||
],
|
||
},
|
||
|
||
onLoad() {
|
||
this._initSystemInfo();
|
||
this.loadPhotos();
|
||
},
|
||
|
||
_initSystemInfo() {
|
||
try {
|
||
const sysInfo = wx.getSystemInfoSync();
|
||
this.setData({
|
||
statusBarHeight: sysInfo.statusBarHeight || 20,
|
||
navBarHeight: 44,
|
||
});
|
||
} catch (e) { /* 使用默认值 */ }
|
||
},
|
||
|
||
async loadPhotos() {
|
||
this.setData({ loading: true });
|
||
try {
|
||
const result = await profileService.getPhotos();
|
||
const photos = (result && result.photos) || [];
|
||
const avatarPhotos = photos.filter((p) => p.photoType === 'avatar');
|
||
const lifestylePhotos = photos.filter((p) => p.photoType === 'lifestyle');
|
||
this.setData({ avatarPhotos, lifestylePhotos, loading: false });
|
||
} catch (err) {
|
||
this.setData({ loading: false });
|
||
appStore.toastError(err, '加载照片失败');
|
||
}
|
||
},
|
||
|
||
// ============ 上传 ============
|
||
/** 主头像区添加 */
|
||
onAddAvatar() {
|
||
this._chooseAndUpload('avatar');
|
||
},
|
||
|
||
/** 生活照区添加 */
|
||
onAddLifestyle() {
|
||
const remaining = this.data.maxLifestyle - this.data.lifestylePhotos.length;
|
||
if (remaining <= 0) {
|
||
appStore.toast(`最多上传 ${BUSINESS_LIMITS.MAX_PROFILE_PHOTOS} 张照片`);
|
||
return;
|
||
}
|
||
this._chooseAndUpload('lifestyle');
|
||
},
|
||
|
||
_chooseAndUpload(photoType) {
|
||
wx.chooseMedia({
|
||
count: 1,
|
||
mediaType: ['image'],
|
||
sourceType: ['album', 'camera'],
|
||
sizeType: ['compressed'],
|
||
success: (res) => {
|
||
const tempFile = res.tempFiles[0];
|
||
if (!tempFile) return;
|
||
this._uploadPhoto(tempFile.tempFilePath, photoType);
|
||
},
|
||
fail: () => { /* 用户取消 */ },
|
||
});
|
||
},
|
||
|
||
async _uploadPhoto(filePath, photoType) {
|
||
appStore.showLoading('上传中…');
|
||
try {
|
||
// 1. 获取上传凭证(EXIF 由服务端清理)
|
||
const fileName = `photo_${Date.now()}.jpg`;
|
||
const tokenRes = await profileService.requestUploadToken({ fileName, contentType: 'image/jpeg' });
|
||
|
||
// 2. 上传到对象存储
|
||
await new Promise((resolve, reject) => {
|
||
wx.uploadFile({
|
||
url: tokenRes.uploadUrl,
|
||
filePath,
|
||
name: 'file',
|
||
formData: { 'Content-Type': 'image/jpeg' },
|
||
success: (res) => {
|
||
if (res.statusCode >= 200 && res.statusCode < 300) resolve(res);
|
||
else reject(new Error('上传失败'));
|
||
},
|
||
fail: reject,
|
||
});
|
||
});
|
||
|
||
// 3. 若上传的是头像区,或当前无主头像,则设为主头像
|
||
if (photoType === 'avatar' || this.data.avatarPhotos.length === 0) {
|
||
await profileService.setMainPhoto(tokenRes.photoId);
|
||
}
|
||
|
||
appStore.hideLoading();
|
||
appStore.toastSuccess('上传成功');
|
||
this.loadPhotos();
|
||
} catch (err) {
|
||
appStore.hideLoading();
|
||
appStore.toastError(err, '上传失败');
|
||
}
|
||
},
|
||
|
||
// ============ 删除 ============
|
||
async onRemoveAvatar(e) {
|
||
const { index } = e.detail;
|
||
const photo = this.data.avatarPhotos[index];
|
||
if (!photo) return;
|
||
this._removePhoto(photo.photoId);
|
||
},
|
||
|
||
async onRemoveLifestyle(e) {
|
||
const { index } = e.detail;
|
||
const photo = this.data.lifestylePhotos[index];
|
||
if (!photo) return;
|
||
this._removePhoto(photo.photoId);
|
||
},
|
||
|
||
async _removePhoto(photoId) {
|
||
const confirm = await appStore.confirm('确定删除这张照片?', '删除后不可恢复');
|
||
if (!confirm) return;
|
||
appStore.showLoading('删除中…');
|
||
try {
|
||
await profileService.deletePhoto(photoId);
|
||
appStore.hideLoading();
|
||
appStore.toastSuccess('已删除');
|
||
this.loadPhotos();
|
||
} catch (err) {
|
||
appStore.hideLoading();
|
||
appStore.toastError(err, '删除失败');
|
||
}
|
||
},
|
||
|
||
// ============ 预览 ============
|
||
onPreviewAvatar(e) {
|
||
const { index } = e.detail;
|
||
const photo = this.data.avatarPhotos[index];
|
||
if (photo) this._preview(photo.url);
|
||
},
|
||
|
||
onPreviewLifestyle(e) {
|
||
const { index } = e.detail;
|
||
const photo = this.data.lifestylePhotos[index];
|
||
if (photo) this._preview(photo.url);
|
||
},
|
||
|
||
_preview(currentUrl) {
|
||
const urls = [
|
||
...this.data.avatarPhotos.map((p) => p.url),
|
||
...this.data.lifestylePhotos.map((p) => p.url),
|
||
].filter(Boolean);
|
||
if (urls.length === 0) return;
|
||
wx.previewImage({ urls, current: currentUrl });
|
||
},
|
||
|
||
// ============ 导航 ============
|
||
onBack() {
|
||
wx.navigateBack();
|
||
},
|
||
|
||
onNext() {
|
||
if (this.data.avatarPhotos.length === 0) {
|
||
appStore.toast('请至少上传 1 张主头像');
|
||
return;
|
||
}
|
||
wx.navigateTo({ url: ROUTE.ONBOARDING.PROFILE_INTERESTS });
|
||
},
|
||
});
|