Support personal records and persistent smoking visibility. Add upload quota preflight, file cleanup, privacy entry, image caching and revision-based page refresh across the backend, admin and mini program.
372 lines
15 KiB
JavaScript
372 lines
15 KiB
JavaScript
const { tryLoginRenewal } = require('../../utils/subscribe');
|
|
const announcementSession = require('../../utils/announcement-session');
|
|
const { itemsOf, totalOf } = require('../../utils/list-pagination');
|
|
let lastLocationReport = 0;
|
|
let locationReportUser = null;
|
|
const { withDataCache } = require('../../utils/page-cache');
|
|
const { getLayout } = require('../../utils/layout');
|
|
// pages/index/index.js
|
|
const { api, getImageUrl } = require('../../utils/request');
|
|
const { diaryEntry } = require('../../utils/journal');
|
|
const { saveScrollPosition, resetScrollOnTabShow } = require('../../utils/scroll-pos');
|
|
|
|
Page(withDataCache({
|
|
onLoginSubscribeTap: function() { return tryLoginRenewal(); },
|
|
onResize: function() { this.setData(getLayout()); },
|
|
data: {
|
|
showAnnouncement:false, announcement:null,
|
|
userId: null,
|
|
coupleId: null,
|
|
|
|
partnerName: '和TA',
|
|
loveStartDate: '',
|
|
daysCount: '?',
|
|
daysLabel: '已注册',
|
|
datePrefix: '注册于',
|
|
partner1AvatarUrl: '',
|
|
partner2AvatarUrl: '',
|
|
partner1Initial: '',
|
|
partner2Initial: '',
|
|
partner1Birthday: '',
|
|
partner2Birthday: '',
|
|
partner1Name: '?',
|
|
partner2Name: '?',
|
|
partner1Location: '',
|
|
partner2Location: '',
|
|
coupleDistance: '',
|
|
|
|
stats: {
|
|
timeline: 0, album: 0, diary: 0,
|
|
songs: 0, checkin: 0, pet: 0
|
|
},
|
|
bgImage: '',
|
|
scrollTop: 0,
|
|
refreshing: false,
|
|
relationshipKnown: false, homeError: false, statsLoading: true, statsError: false, recentDiary: [], recentAlbums: [], upcomingMemory: null
|
|
},
|
|
|
|
onLoad: function() {
|
|
this.setData(getLayout());
|
|
this.setData({
|
|
userId: wx.getStorageSync('userId'),
|
|
coupleId: wx.getStorageSync('coupleId'),
|
|
scrollTop: 0
|
|
});
|
|
this.loadBg();
|
|
this.loadData();
|
|
},
|
|
|
|
onShow: function() {
|
|
this._announcementActive=true;
|
|
this.loadAnnouncement();
|
|
resetScrollOnTabShow(this, 'index');
|
|
const tabBar = this.getTabBar && this.getTabBar();
|
|
if (tabBar) tabBar.syncActive();
|
|
var prevCoupleId = this.data.coupleId;
|
|
var curCoupleId = wx.getStorageSync('coupleId');
|
|
this.setData({
|
|
userId: wx.getStorageSync('userId'),
|
|
coupleId: curCoupleId
|
|
});
|
|
// 检查是否已被解绑(之前有 coupleId 但被清除了)
|
|
if (prevCoupleId && !curCoupleId) {
|
|
wx.reLaunch({ url: '/pages/index/index' });
|
|
return;
|
|
}
|
|
this.loadBg();
|
|
this.loadData();
|
|
this.fetchIpLocation();
|
|
},
|
|
|
|
onHide: function() { this._announcementActive=false; },
|
|
onUnload: function() { this._announcementActive=false; },
|
|
loadAnnouncement: function() {
|
|
if(this._announcementLoading)return this._announcementLoading;
|
|
this._announcementLoading=(async()=>{
|
|
try {
|
|
const notice=await api.announcement();
|
|
if(this._announcementActive===false)return;
|
|
const userId=wx.getStorageSync('userId');
|
|
const show=announcementSession.shouldShow(notice,userId);
|
|
this.setData({announcement:notice, showAnnouncement:show});
|
|
if(show)announcementSession.markShown(notice,userId);
|
|
} catch(e) {} // 公告读取失败不阻塞首页。
|
|
})().finally(()=>{this._announcementLoading=null;});
|
|
return this._announcementLoading;
|
|
},
|
|
closeAnnouncement: function() {
|
|
this.setData({showAnnouncement:false});
|
|
},
|
|
preventNoticeMove: function() {},
|
|
// 保存滚动位置
|
|
onScroll: function(e) {
|
|
this._scrollTop = e.detail.scrollTop;
|
|
saveScrollPosition('index', e.detail.scrollTop);
|
|
},
|
|
|
|
_loadingData: false,
|
|
_daysSince: function(dateStr) {
|
|
if (!dateStr) return '?';
|
|
var parts = dateStr.substring(0, 10).split('-').map(Number);
|
|
if (parts.length !== 3 || parts.some(isNaN) || parts[0] < 1970 ||
|
|
parts[1] < 1 || parts[1] > 12 || parts[2] < 1 || parts[2] > 31) return '?';
|
|
var start = new Date(parts[0], parts[1] - 1, parts[2]);
|
|
if (start.getFullYear() !== parts[0] || start.getMonth() !== parts[1] - 1 ||
|
|
start.getDate() !== parts[2]) return '?';
|
|
var now = new Date();
|
|
var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
return Math.max(0, Math.round((today - start) / 86400000));
|
|
},
|
|
loadData: async function() {
|
|
var self = this;
|
|
// 防止并发重复加载
|
|
if (this._loadingData) return;
|
|
this._loadingData = true;
|
|
this.setData({ homeError: false });
|
|
try {
|
|
const info = await api.coupleInfo();
|
|
this.setData({userId: wx.getStorageSync('userId')});
|
|
this.setData({ relationshipKnown: true });
|
|
// 未绑定情侣时显示用户注册时间和默认信息
|
|
if (info.not_bound) {
|
|
wx.removeStorageSync('coupleId');
|
|
var p1 = info.partner1 || {};
|
|
let daysCount = '?';
|
|
let loveStartDate = '';
|
|
if (info.love_start_date) {
|
|
daysCount = this._daysSince(info.love_start_date);
|
|
var parts = info.love_start_date.split('-');
|
|
loveStartDate = parseInt(parts[0]) + '年' + parseInt(parts[1]) + '月' + parseInt(parts[2]) + '日';
|
|
}
|
|
this.setData({
|
|
coupleId: null,
|
|
partner1AvatarUrl: getImageUrl(p1.avatar),
|
|
partner1Name: p1.nickname || '未命名',
|
|
partner1Initial: (p1.nickname || '未命名')[0],
|
|
partner1Birthday: '',
|
|
partner1Location: p1.location || '',
|
|
partner2AvatarUrl: '',
|
|
partner2Name: '未绑定',
|
|
partner2Initial: '?',
|
|
partner2Birthday: '',
|
|
partner2Location: '',
|
|
daysCount: daysCount,
|
|
daysLabel: '已注册',
|
|
datePrefix: '注册于',
|
|
loveStartDate: loveStartDate,
|
|
coupleDistance: '',
|
|
stats: { timeline: 0, album: 0, diary: 0, songs: 0, checkin: 0, pet: 0 }
|
|
});
|
|
await this.loadStats();
|
|
return;
|
|
}
|
|
// 同步情侣ID:红包/邀请创建后分享人刷新即可识别
|
|
if (info.id) {
|
|
var prevCid = wx.getStorageSync('coupleId');
|
|
if (prevCid != info.id) {
|
|
wx.setStorageSync('coupleId', info.id);
|
|
}
|
|
}
|
|
const myUid = this.data.userId;
|
|
var p1 = info.partner1 || {};
|
|
var p2 = info.partner2 || {};
|
|
|
|
if (p2.id == myUid) {
|
|
var tmp = p1; p1 = p2; p2 = tmp;
|
|
}
|
|
|
|
let daysCount = '?';
|
|
let loveStartDate = '';
|
|
var togetherStart = info.love_start_date || (info.created_at || '').substring(0, 10);
|
|
if (togetherStart) {
|
|
daysCount = this._daysSince(togetherStart);
|
|
var parts = togetherStart.split('-');
|
|
loveStartDate = parseInt(parts[0]) + '年' + parseInt(parts[1]) + '月' + parseInt(parts[2]) + '日';
|
|
}
|
|
|
|
function getBirthdayText(birthday) {
|
|
if (!birthday) return '';
|
|
var t = new Date(); t.setHours(0,0,0,0);
|
|
var p = birthday.split('-');
|
|
if (p.length !== 3) return '';
|
|
var nb = new Date(t.getFullYear(), parseInt(p[1])-1, parseInt(p[2]));
|
|
if (nb < t) nb.setFullYear(nb.getFullYear()+1);
|
|
var c = Math.ceil((nb - t) / 86400000);
|
|
if (c === 0) return '今天!';
|
|
if (c === 1) return '1天';
|
|
if (!Number.isFinite(c)) return '';
|
|
return c + '天';
|
|
}
|
|
|
|
// 双方相距距离(后端基于双方经纬度用 Haversine 公式计算,单位:公里)
|
|
var coupleDistance = '';
|
|
if (info.distance !== null && info.distance !== undefined && info.distance !== '') {
|
|
var d = parseFloat(info.distance);
|
|
if (!isNaN(d)) {
|
|
coupleDistance = d < 1
|
|
? '相距 ' + Math.round(d * 1000) + ' 米'
|
|
: '相距 ' + d.toFixed(1) + ' 公里';
|
|
}
|
|
}
|
|
|
|
var dn = {
|
|
coupleId: info.id,
|
|
daysCount: daysCount,
|
|
daysLabel: '我们在一起',
|
|
datePrefix: '始于',
|
|
loveStartDate: loveStartDate,
|
|
partner1AvatarUrl: getImageUrl(p1.avatar),
|
|
partner2AvatarUrl: getImageUrl(p2.avatar),
|
|
partner1Name: p1.nickname || p1.username || '?',
|
|
partner2Name: p2.nickname || p2.username || '?',
|
|
partner1Initial: (p1.nickname || p1.username || '?')[0],
|
|
partner2Initial: (p2.nickname || p2.username || '?')[0],
|
|
partner1Birthday: getBirthdayText(p1.birthday),
|
|
partner2Birthday: getBirthdayText(p2.birthday),
|
|
partner1Location: (p1.location && !/^-?\d+(\.\d+)?,-?\d+(\.\d+)?$/.test(p1.location)) ? p1.location : '',
|
|
partner2Location: (p2.location && !/^-?\d+(\.\d+)?,-?\d+(\.\d+)?$/.test(p2.location)) ? p2.location : '',
|
|
coupleDistance: coupleDistance
|
|
};
|
|
|
|
this.setData(dn);
|
|
await this.loadStats();
|
|
|
|
} catch (e) {
|
|
this.setData({ homeError: true, relationshipKnown: !!this._hasCachedData, statsLoading: false, statsError: true });
|
|
console.error(e);
|
|
|
|
} finally {
|
|
this._loadingData = false;
|
|
}
|
|
},
|
|
|
|
loadStats: async function() {
|
|
this.setData({ statsLoading: !this._hasCachedData, statsError: false });
|
|
try {
|
|
const [timeline, album, diary, songs, checkin, pet] = await Promise.all([
|
|
api.timelineList(), api.albumList(), api.diaryList(),
|
|
api.songsList(), api.checkinList(),
|
|
this.data.coupleId ? api.petInfo() : Promise.resolve({ has_pet: false })
|
|
]);
|
|
var photoCount = album && album.photo_total != null ? Number(album.photo_total) : itemsOf(album).reduce((sum,item)=>sum+(Number(item.photo_count)||0),0);
|
|
var petLevel = 0;
|
|
if (pet && pet.has_pet && pet.pet) {
|
|
petLevel = parseInt(pet.pet.level || 0);
|
|
}
|
|
var recentDiary = ((diary && diary.items) || []).slice(0, 1).map(diaryEntry);
|
|
var today = new Date(); today.setHours(0,0,0,0);
|
|
var future = itemsOf(timeline).filter(function(item) { return item.event_date && new Date(item.event_date.replace(/-/g, '/')) >= today; }).sort(function(a,b) { return a.event_date.localeCompare(b.event_date); });
|
|
var next = timeline && !Array.isArray(timeline) && 'upcoming' in timeline ? timeline.upcoming : future[0];
|
|
this.setData({
|
|
recentDiary: recentDiary,
|
|
recentAlbums: itemsOf(album).filter(function(item) { return !!item.cover; }).slice(0,3).map(function(item) { return { id:item.id, title:item.title || '我们的相册', cover:getImageUrl(item.cover) }; }),
|
|
upcomingMemory: next ? { title: next.title || '下一个记忆日', date: next.event_date, dateMonth: next.event_date.slice(5,7), dateDay: next.event_date.slice(8,10), days: Math.ceil((new Date(next.event_date.replace(/-/g, '/')) - today) / 86400000) } : null,
|
|
stats: {
|
|
timeline: totalOf(timeline),
|
|
album: photoCount,
|
|
diary: (diary && diary.total) ? diary.total : 0,
|
|
songs: totalOf(songs),
|
|
checkin: (checkin && checkin.checkin_log_total !== undefined) ? checkin.checkin_log_total : ((checkin && checkin.items) ? checkin.items.length : (Array.isArray(checkin) ? checkin.length : 0)),
|
|
pet: petLevel
|
|
}
|
|
});
|
|
} catch (e) { console.error(e); this.setData({ statsError: true }); } finally { this.setData({ statsLoading: false }); }
|
|
},
|
|
|
|
// 获取本机经纬度并上报后端解析地址(后端按距离判断是否重新解析,超过5km才更新),并刷新首页双方地址显示
|
|
fetchIpLocation: function() {
|
|
const locationUser = wx.getStorageSync('userId');
|
|
if (locationReportUser === locationUser && Date.now() - lastLocationReport < 300000) return;
|
|
locationReportUser = locationUser;
|
|
lastLocationReport = Date.now();
|
|
var self = this;
|
|
var report = function(lat, lng) {
|
|
var data = {};
|
|
if (lat && lng) { data.lat = lat; data.lng = lng; }
|
|
api.updateIpLocation(data).then(function(resp) {
|
|
if (resp && resp.location) {
|
|
self.loadData();
|
|
}
|
|
}).catch(function() { lastLocationReport = 0; });
|
|
};
|
|
// 优先用模糊定位获取经纬度(getFuzzyLocation 与 getLocation 互斥,统一用模糊定位)
|
|
if (wx.getFuzzyLocation) {
|
|
wx.getFuzzyLocation({
|
|
type: 'gcj02',
|
|
success: function(res) {
|
|
report(res.latitude, res.longitude);
|
|
},
|
|
fail: function() {
|
|
report(0, 0);
|
|
}
|
|
});
|
|
} else {
|
|
report(0, 0);
|
|
}
|
|
},
|
|
|
|
// 背景图以接口返回为准
|
|
loadBg: function() {
|
|
var self = this;
|
|
return api.getBgConfig().then(function(cfg) {
|
|
self.setData({ bgImage: cfg && cfg.image ? getImageUrl(cfg.image) : '' });
|
|
}).catch(function(e){ console.warn('loadBg error:', e && (e.error || e._statusCode || e)); });
|
|
},
|
|
|
|
// 跳转前保存当前滚动位置
|
|
_savePosBeforeLeave: function() {
|
|
saveScrollPosition('index', this._scrollTop == null ? this.data.scrollTop : this._scrollTop);
|
|
},
|
|
|
|
// 页面跳转
|
|
goToDiary: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/diary/diary' }); },
|
|
goToProfile: function() { this._savePosBeforeLeave(); wx.switchTab({ url: '/pages/profile/profile' }); },
|
|
goToTimeline: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/timeline/timeline' }); },
|
|
goToAlbum: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/albums/albums' }); },
|
|
goToSongs: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/lovewords/lovewords' }); },
|
|
goToCheckin: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/punch/punch' }); },
|
|
goToPet: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/pet/pet' }); },
|
|
goToPeriod: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/period/period' }); },
|
|
bindPartner: function() { getApp().globalData.openProfileBind = true; wx.switchTab({ url: '/pages/profile/profile' }); },
|
|
openJournalEntry: function(e) { wx.navigateTo({ url: '/pages/diary_detail/diary_detail?id=' + e.currentTarget.dataset.id }); },
|
|
writeDiary: function() { wx.navigateTo({ url: '/pages/publish/publish?type=diary' }); },
|
|
openAlbumPreview: function(e) { wx.navigateTo({ url: '/pages/album/album?album_id=' + e.currentTarget.dataset.id }); },
|
|
openRecentDiary: function(e) { wx.navigateTo({ url: '/pages/diary_detail/diary_detail?id=' + e.currentTarget.dataset.id }); },
|
|
showMore: function() {
|
|
wx.switchTab({ url: '/pages/discover/discover' });
|
|
},
|
|
onPullDownRefresh: async function() {
|
|
await this.loadData();
|
|
this.loadBg();
|
|
wx.stopPullDownRefresh();
|
|
},
|
|
onRefresh: async function() {
|
|
this.setData({ refreshing: true });
|
|
try {
|
|
await this.loadData();
|
|
this.loadBg();
|
|
} catch (e) {
|
|
console.error('下拉刷新失败', e);
|
|
} finally {
|
|
this.setData({ refreshing: false });
|
|
}
|
|
},
|
|
|
|
// 分享到好友
|
|
onShareAppMessage: function() {
|
|
return {
|
|
title: '让所有的爱有迹可循',
|
|
path: '/pages/index/index',
|
|
imageUrl: '/images/share.png'
|
|
};
|
|
},
|
|
// 分享到朋友圈
|
|
onShareTimeline: function() {
|
|
return {
|
|
title: '让所有的爱有迹可循',
|
|
query: '',
|
|
imageUrl: '/images/share.png'
|
|
};
|
|
}
|
|
}, {"loadData": ["coupleInfo", "timelineList", "albumList", "diaryList", "songsList", "checkinList", "petInfo", "getBgConfig"]}));
|