Files
danyang-match/miniapp/pages/profile-photos/profile-photos.js
T

195 lines
5.8 KiB
JavaScript
Raw Normal View History

2026-09-09 22:05:00 +08:00
/**
* 头像与照片页 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 });
},
});