470 lines
17 KiB
JavaScript
470 lines
17 KiB
JavaScript
const { getLayout } = require('../../utils/layout');
|
||
// pages/album/album.js - 日记式分组相册
|
||
const { api, getImageUrl, API_BASE, uploadFileCompat } = require('../../utils/request');
|
||
const { requestSubscribeAfterAction } = require('../../utils/subscribe');
|
||
Page({
|
||
onResize: function() { this.setData(getLayout()); },
|
||
data: { loading: true, loadError: false,
|
||
batches: [],
|
||
albumId: null,
|
||
showUploadModal: false,
|
||
uploadFilePaths: [],
|
||
photoFormList: [], // 每张照片独立的表单数据
|
||
selectedPhotoIdx: 0, // 当前选中编辑的照片索引
|
||
uploadForm: { taken_at: '', location: '', activity: '' },
|
||
uploading: false,
|
||
showPreview: false,
|
||
previewImages: [],
|
||
previewIndex: 0,
|
||
},
|
||
|
||
onLoad: function(options) {
|
||
this.setData(getLayout());var id = options && options.album_id;
|
||
if (id) { this.setData({ albumId: id }); }
|
||
this._pendingPhotoPicker = !!(id && options.upload === '1');
|
||
this.loadData();
|
||
},
|
||
onShow: function() { this._visible = true; this.loadData(); },
|
||
onHide: function() { this._visible = false; },
|
||
|
||
// 左滑返回相册列表
|
||
touchStartX: 0, touchStartY: 0,
|
||
onTouchStart: function(e) {
|
||
if (this.data.showPreview) return;
|
||
this.touchStartX = e.touches[0].clientX;
|
||
this.touchStartY = e.touches[0].clientY;
|
||
},
|
||
onTouchEnd: function(e) {
|
||
if (this.data.showPreview) return;
|
||
var dx = this.touchStartX - e.changedTouches[0].clientX;
|
||
var dy = Math.abs(this.touchStartY - e.changedTouches[0].clientY);
|
||
if (dx < 20 && dy < 20) return;
|
||
if (Math.abs(dx) > dy && dx > 80) this.goToAlbums();
|
||
},
|
||
|
||
onPullDownRefresh: async function() {
|
||
await this.loadData();
|
||
wx.stopPullDownRefresh();
|
||
},
|
||
|
||
// 分享到好友
|
||
onShareAppMessage: function() {
|
||
return {
|
||
title: '让所有的爱有迹可循',
|
||
path: '/pages/album/album?album_id=' + (this.data.albumId || ''),
|
||
imageUrl: '/images/share.png'
|
||
};
|
||
},
|
||
// 分享到朋友圈
|
||
onShareTimeline: function() {
|
||
return {
|
||
title: '让所有的爱有迹可循',
|
||
imageUrl: '/images/share.png'
|
||
};
|
||
},
|
||
|
||
loadData: async function() {
|
||
if (this._pageLoading) return;
|
||
this._pageLoading = true;
|
||
this.setData({loading:true, loadError:false});
|
||
try {
|
||
var self = this;
|
||
var albumId = this.data.albumId;
|
||
if (!albumId) {
|
||
var albums = await api.albumList();
|
||
if (albums && albums.length > 0) {
|
||
albumId = albums[0].id;
|
||
this.setData({ albumId: albumId });
|
||
}
|
||
}
|
||
if (albumId) {
|
||
// 使用 noCache 禁用缓存,确保获取最新数据
|
||
var photos = await api.photos(albumId, null, 'GET', true);
|
||
// 根据 batch_id 分组
|
||
var batchMap = {};
|
||
var myUid = wx.getStorageSync('userId');
|
||
var info = await api.coupleInfo();
|
||
var p1 = info.partner1 || {}, p2 = info.partner2 || {};
|
||
var nameMap = {};
|
||
var avatarMap = {};
|
||
nameMap[p1.id] = p1.nickname || p1.username || 'TA';
|
||
nameMap[p2.id] = p2.nickname || p2.username || 'TA';
|
||
avatarMap[p1.id] = getImageUrl(p1.avatar);
|
||
avatarMap[p2.id] = getImageUrl(p2.avatar);
|
||
|
||
var defaultName = nameMap[p1.id] || nameMap[p2.id] || '情侣';
|
||
var defaultAvatar = avatarMap[p1.id] || avatarMap[p2.id] || '';
|
||
(photos || []).forEach(function(p) {
|
||
var batchId = p.batch_id || p.created_at || 'default';
|
||
if (!batchMap[batchId]) {
|
||
var cid = parseInt(p.created_by) || 0;
|
||
batchMap[batchId] = {
|
||
batch_id: batchId,
|
||
creator_name: nameMap[cid] || defaultName,
|
||
creator_avatar: avatarMap[cid] || defaultAvatar,
|
||
created_at: p.created_at || '',
|
||
created_time: p.created_at ? p.created_at.substring(0, 16).replace('T', ' ') : '',
|
||
photos: []
|
||
};
|
||
}
|
||
batchMap[batchId].photos.push({
|
||
...p,
|
||
url: getImageUrl(p.path)
|
||
});
|
||
});
|
||
// 按时间排序(最新的在最前)
|
||
var batches = Object.values(batchMap);
|
||
batches.sort(function(a, b) { return b.created_at > a.created_at ? 1 : -1; });
|
||
this.setData({ batches: batches });
|
||
} else { this.setData({batches:[]}); }
|
||
} catch (e) {
|
||
this.setData({loadError:true}); console.error(e); }
|
||
finally {
|
||
this._pageLoading = false;
|
||
this.setData({loading:false});
|
||
if (this._visible && this._pendingPhotoPicker && !this.data.loadError && this.data.albumId) {
|
||
this._pendingPhotoPicker = false;
|
||
this.chooseImage();
|
||
}
|
||
}
|
||
},
|
||
|
||
chooseImage: function() {
|
||
var self = this;
|
||
wx.chooseImage({
|
||
count: 9,
|
||
sizeType: ['compressed'],
|
||
sourceType: ['album', 'camera'],
|
||
success: function(res) {
|
||
// 确保文件路径是字符串数组
|
||
var paths = (res.tempFilePaths || []).map(function(p) { return String(p); });
|
||
if (paths.length === 0) {
|
||
wx.showToast({ title: '请选择照片', icon: 'none' });
|
||
return;
|
||
}
|
||
var formList = paths.map(function() { return { taken_at: '', location: '', activity: '' }; });
|
||
self.setData({
|
||
showUploadModal: true,
|
||
uploadFilePaths: paths,
|
||
photoFormList: formList,
|
||
selectedPhotoIdx: 0,
|
||
uploadForm: { taken_at: '', location: '', activity: '' }
|
||
});
|
||
},
|
||
fail: function(err) {
|
||
console.error('Choose image failed:', err);
|
||
wx.showToast({ title: '选择照片失败', icon: 'none' });
|
||
}
|
||
});
|
||
},
|
||
onUploadInput: function(e) {
|
||
var field = e.currentTarget.dataset.field;
|
||
var v = e.detail.value;
|
||
// 同步更新当前显示的表单和 photoFormList
|
||
var formList = this.data.photoFormList.slice();
|
||
formList[this.data.selectedPhotoIdx] = Object.assign({}, formList[this.data.selectedPhotoIdx], { [field]: v });
|
||
this.setData({
|
||
['uploadForm.' + field]: v,
|
||
photoFormList: formList
|
||
});
|
||
},
|
||
|
||
// ===== 选中照片编辑 =====
|
||
selectPhoto: function(e) {
|
||
var idx = e.currentTarget.dataset.index;
|
||
var form = this.data.photoFormList[idx] || { taken_at: '', location: '', activity: '' };
|
||
this.setData({
|
||
selectedPhotoIdx: idx,
|
||
uploadForm: { taken_at: form.taken_at || '', location: form.location || '', activity: form.activity || '' }
|
||
});
|
||
},
|
||
|
||
removePhoto: function(e) {
|
||
var idx = e.currentTarget.dataset.index;
|
||
var paths = this.data.uploadFilePaths.filter(function(_, i) { return i !== idx; });
|
||
var formList = this.data.photoFormList.filter(function(_, i) { return i !== idx; });
|
||
if (paths.length === 0) {
|
||
this.cancelUpload();
|
||
} else {
|
||
var newIdx = Math.min(this.data.selectedPhotoIdx, paths.length - 1);
|
||
var f = formList[newIdx] || { taken_at: '', location: '', activity: '' };
|
||
this.setData({
|
||
uploadFilePaths: paths,
|
||
photoFormList: formList,
|
||
selectedPhotoIdx: newIdx,
|
||
uploadForm: { taken_at: f.taken_at || '', location: f.location || '', activity: f.activity || '' }
|
||
});
|
||
}
|
||
},
|
||
|
||
addMorePhotos: function() {
|
||
var self = this;
|
||
var current = this.data.uploadFilePaths.length;
|
||
wx.chooseImage({
|
||
count: 9 - current,
|
||
sizeType: ['compressed'],
|
||
sourceType: ['album', 'camera'],
|
||
success: function(res) {
|
||
var newPaths = (res.tempFilePaths || []).map(function(p) { return String(p); });
|
||
var newForms = newPaths.map(function() { return { taken_at: '', location: '', activity: '' }; });
|
||
self.setData({
|
||
uploadFilePaths: self.data.uploadFilePaths.concat(newPaths),
|
||
photoFormList: self.data.photoFormList.concat(newForms)
|
||
});
|
||
}
|
||
});
|
||
},
|
||
|
||
// ===== 批量上传全部照片(每张使用各自的表单)=====
|
||
uploadAll: async function() {
|
||
if (this.data.uploading) return;
|
||
var paths = this.data.uploadFilePaths;
|
||
if (paths.length === 0) { wx.showToast({ title: '没有照片', icon: 'none' }); return; }
|
||
|
||
// 先保存当前编辑中的表单到 photoFormList
|
||
var formList = this.data.photoFormList.slice();
|
||
var curIdx = this.data.selectedPhotoIdx;
|
||
formList[curIdx] = {
|
||
taken_at: this.data.uploadForm.taken_at || '',
|
||
location: this.data.uploadForm.location || '',
|
||
activity: this.data.uploadForm.activity || ''
|
||
};
|
||
|
||
this.setData({ uploading: true });
|
||
var self = this;
|
||
var successCount = 0;
|
||
var failCount = 0;
|
||
var failReasons = [];
|
||
|
||
// ★ 在用户点击手势上下文中提前请求订阅消息(符合微信规范)
|
||
requestSubscribeAfterAction('photo');
|
||
|
||
try {
|
||
// 上传前先预热 session,确保认证状态有效
|
||
// 解决 Android 端首次上传时 session 未初始化的问题
|
||
try {
|
||
wx.showLoading({ title: '准备中...', mask: true });
|
||
await api.checkAuth();
|
||
wx.hideLoading();
|
||
} catch (e) {
|
||
wx.hideLoading();
|
||
console.warn('Session 预热失败(不影响后续上传)', e);
|
||
}
|
||
|
||
var albumId = this.data.albumId;
|
||
if (!albumId) {
|
||
wx.showLoading({ title: '创建相册...' });
|
||
var album = await api.albumAdd({ title: '默认相册', description: '我们的甜蜜照片' });
|
||
albumId = album.id;
|
||
this.setData({ albumId: albumId });
|
||
wx.hideLoading();
|
||
}
|
||
var batchId = Date.now().toString();
|
||
|
||
// 自动定位:为未填写位置的照片自动获取地理位置(发布后自动显示)
|
||
var autoLoc = null;
|
||
var needLoc = formList.some(function(x) { return !(x.location && x.location.trim()); });
|
||
if (needLoc) {
|
||
try {
|
||
autoLoc = await new Promise(function(resolve) {
|
||
wx.getFuzzyLocation({
|
||
success: function(res) { resolve({ lat: res.latitude, lng: res.longitude }); },
|
||
fail: function() { resolve(null); }
|
||
});
|
||
});
|
||
} catch (e) { autoLoc = null; }
|
||
}
|
||
|
||
for (var i = 0; i < paths.length; i++) {
|
||
wx.showLoading({ title: '上传中 ' + (i + 1) + '/' + paths.length, mask: true });
|
||
var f = formList[i] || { taken_at: '', location: '', activity: '' };
|
||
try {
|
||
// 按照日记上传逻辑:先上传到通用 upload 接口获取路径,再调用 API 保存到相册
|
||
var uploadRes = await uploadFileCompat({
|
||
url: API_BASE + '/upload',
|
||
filePath: paths[i],
|
||
name: 'file',
|
||
formData: { sub_dir: 'photos' }
|
||
});
|
||
// 兼容不同响应格式:{path:'...'} 或 {success:true, path:'...'} 或 {url:'...'}
|
||
var uploadedPath = uploadRes && (uploadRes.path || uploadRes.url || '');
|
||
if (uploadedPath) {
|
||
await api.photoAdd({
|
||
album_id: String(albumId),
|
||
path: uploadedPath,
|
||
taken_at: f.taken_at || '',
|
||
location: f.location || '',
|
||
activity: f.activity || '',
|
||
batch_id: batchId,
|
||
// 未手动填写位置时,自动定位并记录经纬度(发布后自动显示地址)
|
||
lat: (f.location && f.location.trim()) ? 0 : (autoLoc ? autoLoc.lat : 0),
|
||
lng: (f.location && f.location.trim()) ? 0 : (autoLoc ? autoLoc.lng : 0)
|
||
});
|
||
successCount++;
|
||
} else {
|
||
failCount++;
|
||
failReasons.push('第' + (i+1) + '张: 上传返回无路径 - ' + JSON.stringify(uploadRes));
|
||
}
|
||
} catch (e) {
|
||
failCount++;
|
||
failReasons.push('第' + (i+1) + '张: ' + (e.error || e.errMsg || e.message || JSON.stringify(e)));
|
||
// 内容安全违规:明确提示用户
|
||
if (e && e.error) {
|
||
wx.showToast({ title: e.error, icon: 'none', duration: 2000 });
|
||
}
|
||
}
|
||
wx.hideLoading();
|
||
}
|
||
} catch (e) {
|
||
wx.hideLoading();
|
||
failReasons.push('整体异常: ' + (e.error || e.errMsg || e.message || JSON.stringify(e)));
|
||
}
|
||
|
||
this.setData({ uploading: false });
|
||
|
||
var msg = '上传完成 ' + successCount + '/' + paths.length;
|
||
if (failCount > 0) msg += '(' + failCount + '张失败)';
|
||
wx.showToast({ title: msg, icon: successCount > 0 ? 'success' : 'none', duration: 2000 });
|
||
|
||
// 如果有失败,输出详细原因到控制台
|
||
if (failCount > 0 && failReasons.length > 0) {
|
||
console.error('相册上传失败详情:', failReasons.join(' | '));
|
||
}
|
||
|
||
if (successCount > 0) {
|
||
this.setData({ showUploadModal: false, uploadFilePaths: [], photoFormList: [], uploadForm: { taken_at: '', location: '', activity: '' } });
|
||
this.loadData();
|
||
}
|
||
},
|
||
|
||
cancelUpload: function() {
|
||
if (this.data.uploading) return;
|
||
this.setData({ showUploadModal: false, uploadFilePaths: [], photoFormList: [], uploadForm: { taken_at: '', location: '', activity: '' } });
|
||
},
|
||
|
||
// 上传页左滑返回
|
||
_upTouchX: 0, _upTouchY: 0,
|
||
onUploadTouchStart: function(e) {
|
||
this._upTouchX = e.touches[0].clientX;
|
||
this._upTouchY = e.touches[0].clientY;
|
||
},
|
||
onUploadTouchEnd: function(e) {
|
||
var dx = this._upTouchX - e.changedTouches[0].clientX;
|
||
var dy = Math.abs(this._upTouchY - e.changedTouches[0].clientY);
|
||
if (dx < 20 && dy < 20) return;
|
||
if (Math.abs(dx) > dy && dx > 80) {
|
||
this.cancelUpload();
|
||
}
|
||
},
|
||
|
||
closePreview: function() { this.setData({ showPreview: false }); },
|
||
onSwiperChange: function(e) { this.setData({ previewIndex: e.detail.current }); },
|
||
|
||
// 长按明信片 - 保存原图到相册
|
||
onPostcardLongPress: function(e) {
|
||
var self = this;
|
||
var url = e.currentTarget.dataset.url;
|
||
if (!url) return;
|
||
wx.showActionSheet({
|
||
itemList: ['保存原图到相册'],
|
||
success: function(res) {
|
||
if (res.tapIndex === 0) self.savePreviewImage(url);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 保存原图:先通过 getImageInfo 获取本地临时路径再保存
|
||
savePreviewImage: function(url) {
|
||
var self = this;
|
||
wx.showLoading({ title: '保存中...' });
|
||
wx.getImageInfo({
|
||
src: url,
|
||
success: function(info) {
|
||
wx.hideLoading();
|
||
self._saveToAlbum(info.path);
|
||
},
|
||
fail: function() {
|
||
wx.hideLoading();
|
||
wx.showToast({ title: '获取图片失败', icon: 'none' });
|
||
}
|
||
});
|
||
},
|
||
|
||
_saveToAlbum: function(filePath) {
|
||
wx.saveImageToPhotosAlbum({
|
||
filePath: filePath,
|
||
success: function() {
|
||
wx.showToast({ title: '已保存到相册', icon: 'success' });
|
||
},
|
||
fail: function(err) {
|
||
var msg = (err && err.errMsg) || '';
|
||
if (msg.indexOf('auth') >= 0 || msg.indexOf('authorize') >= 0) {
|
||
wx.showModal({
|
||
title: '需要相册权限',
|
||
content: '请在设置中开启「保存到相册」权限,才能保存原图哦',
|
||
confirmText: '去设置',
|
||
success: function(res) {
|
||
if (res.confirm) wx.openSetting();
|
||
}
|
||
});
|
||
} else {
|
||
wx.showToast({ title: '保存失败', icon: 'none' });
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 预览时仅支持点击遮罩关闭
|
||
onPreviewTouchStart: function() {},
|
||
onPreviewTouchEnd: function() {},
|
||
|
||
// 点击单张照片 - 进入全屏明信片预览
|
||
onPhotoTap: function(e) {
|
||
var batchIdx = e.currentTarget.dataset.batch;
|
||
var imgIdx = e.currentTarget.dataset.index;
|
||
var batch = this.data.batches[batchIdx];
|
||
if (!batch || !batch.photos || !batch.photos[imgIdx]) return;
|
||
var allImages = [];
|
||
batch.photos.forEach(function(p) {
|
||
// 经纬度为0(定位失败)时不显示位置信息
|
||
var hasCoord = p.lat && p.lng && parseFloat(p.lat) !== 0 && parseFloat(p.lng) !== 0;
|
||
var loc = p.location;
|
||
if (!hasCoord) loc = '';
|
||
allImages.push({ url: p.url, taken_at: p.taken_at, location: loc, activity: p.activity });
|
||
});
|
||
this.setData({ showPreview: true, previewImages: allImages, previewIndex: imgIdx });
|
||
},
|
||
|
||
// 长按照片 - 删除照片
|
||
onPhotoLongPress: function(e) {
|
||
var self = this;
|
||
var batchIdx = e.currentTarget.dataset.batch;
|
||
var imgIdx = e.currentTarget.dataset.index;
|
||
var batch = this.data.batches[batchIdx];
|
||
if (!batch || !batch.photos || !batch.photos[imgIdx]) return;
|
||
var photo = batch.photos[imgIdx];
|
||
|
||
wx.showModal({
|
||
title: '删除照片',
|
||
content: '确定要删除这张照片吗?',
|
||
success: function(res) {
|
||
if (res.confirm) {
|
||
wx.showLoading({ title: '删除中...' });
|
||
api.photoDelete({ id: photo.id }).then(function() {
|
||
wx.hideLoading();
|
||
wx.showToast({ title: '已删除', icon: 'success' });
|
||
self.loadData();
|
||
}).catch(function(err) {
|
||
wx.hideLoading();
|
||
wx.showToast({ title: err.error || '删除失败', icon: 'none' });
|
||
});
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
goToAlbums: function() { wx.navigateBack(); },
|
||
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
|
||
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
|
||
});
|