Files
past-go/weapp/app.js
T
Blizzard 8be6d9b1ab feat: add account deletion, announcements and subscription renewal
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.
2026-11-03 22:06:47 +08:00

188 lines
8.0 KiB
JavaScript

// app.js
const { api } = require('./utils/request');
App({
globalData: {
authState: 'loading',
authMessage: '',
userId: null,
token: null,
isConnected: true,
networkType: 'wifi'
},
onLaunch: function() {
this.removeLegacyDataCaches();
this._authListeners = [];
require('./utils/subscribe').resetLoginRenewal();
// 冷启动始终换取新 token,所有业务请求共享这一次登录。
if (wx.getStorageSync('accountDeleted')) this.setAuthState('error','账号已注销。再次进入将创建新账号,原数据不会恢复。');
else this.silentLogin().catch(function() {});
this.checkNetwork();
wx.onNetworkStatusChange((res) => {
this.globalData.isConnected = res.isConnected;
this.globalData.networkType = res.networkType;
});
},
finishAccountDeletion: function() {
require('./utils/data-cache').clear();
require('./utils/announcement-session').clear();
require('./utils/subscribe').resetLoginRenewal();
this._authGeneration=(this._authGeneration||0)+1;this._authPromise=null;
wx.clearStorageSync();
wx.setStorageSync('accountDeleted',true);
this.globalData.token=null;this.globalData.userId=null;
this.setAuthState('error','账号已注销。再次进入将创建新账号,原数据不会恢复。');
},
onRelationChanged: function() {
if (this._relationRedirect) return;
wx.removeStorageSync('petHasPet');
this._relationRedirect = true;
setTimeout(() => { this._relationRedirect=false; wx.reLaunch({url:'/pages/index/index'}); }, 0);
},
onShow: function() {
const sync = async () => {
if (this._relationSync || this.globalData.authState !== 'ready') return;
this._relationSync = true;
const before = String(wx.getStorageSync('coupleId') || '');
try {
const auth = await api.checkAuth({cache:false, silent:true});
const changed = require('./utils/data-cache').applyRemoteRevision(auth.data_revision);
if (before !== String(wx.getStorageSync('coupleId') || '')) {
this.onRelationChanged();
} else if (changed && typeof getCurrentPages === 'function') {
const pages = getCurrentPages();
const page = pages[pages.length - 1];
if (page && page.onDataChanged) page.onDataChanged();
}
} catch(e) {} finally { this._relationSync = false; }
};
sync();
if (this.globalData.authState === 'ready') require('./utils/subscribe').prepareLoginRenewal();
if (!this._relationTimer) this._relationTimer = setInterval(sync, 30000);
},
onHide: function() { if(this._relationTimer) clearInterval(this._relationTimer); this._relationTimer=null; },
setAuthState: function(state, message) {
this.globalData.authState = state;
this.globalData.authMessage = message || '';
(this._authListeners || []).slice().forEach(function(listener) { listener(state, message || ''); });
},
observeAuth: function(listener) {
if (!this._authListeners) this._authListeners = [];
this._authListeners.push(listener);
listener(this.globalData.authState, this.globalData.authMessage);
return () => { this._authListeners = this._authListeners.filter(function(item) { return item !== listener; }); };
},
ensureAuth: function() {
if (this._authPromise) return this._authPromise;
if (this.globalData.authState === 'error') return Promise.reject({ error: this.globalData.authMessage, authFailed: true });
if (this.globalData.authState === 'ready' && wx.getStorageSync('token')) return Promise.resolve();
return this.silentLogin();
},
// 多个旧 token 请求同时返回 401 时,只刷新一次;较晚返回的旧请求复用新 token。
refreshAuth: function(failedToken) {
if(wx.getStorageSync('accountDeleted')) return Promise.reject({error:'账号已注销',accountDeleted:true});
if (this._authPromise) return this._authPromise;
if (this.globalData.authState === 'ready' && wx.getStorageSync('token') !== failedToken) return Promise.resolve();
return this.silentLogin();
},
removeLegacyDataCaches: function() {
try {
var keys = wx.getStorageInfoSync().keys || [];
var exact = {
indexCache: true, profileCache: true, discoverStatsCache: true,
songsCache: true, albumsCache: true, diaryCache: true,
timelineCache: true, timelineBgCache: true, checkinsCache: true,
coupleInfoCache: true, petCache: true, petAchievementCache: true,
period_cache_data: true, bg_cache: true, subscribe_status_cache: true,
_albums_inited: true, myLocation: true
};
keys.forEach(function(key) {
if (exact[key] || /^(offline_|albumCache_|diaryDetailCache_|timelineDetailCache_|checkinDetailCache_|historyCache_|period_cache_data:|timelineBg_)/.test(key)) {
wx.removeStorageSync(key);
}
});
} catch (e) { console.warn('清理旧数据缓存失败', e); }
},
// ★ 获取用户位置(使用 wx.getFuzzyLocation 模糊定位)
getLocationRegion: function() {
return new Promise(function(resolve) {
wx.getFuzzyLocation({
type: 'wgs84',
success: function(loc) {
// 直接存储经纬度字符串,不依赖地图 API
var region = loc.latitude.toFixed(4) + ',' + loc.longitude.toFixed(4);
resolve(region);
},
fail: function(err) {
console.log('获取模糊位置失败:', err);
resolve('');
}
});
});
},
silentLogin: function() {
if (this._authPromise) return this._authPromise;
const generation=this._authGeneration||0;
this.setAuthState('loading');
this._authPromise = this.performLogin().then((res) => {
this.setAuthState('ready');
require('./utils/subscribe').prepareLoginRenewal();
return res;
}).catch((error) => {
if(generation!==(this._authGeneration||0))throw error;
wx.removeStorageSync('token');
this.globalData.token = null;
var failure = { error: (error && error.error) || '登录失败,请检查网络后重试', authFailed: true };
this.setAuthState('error', failure.error);
throw failure;
}).finally(() => { if(generation===(this._authGeneration||0))this._authPromise = null; });
return this._authPromise;
},
performLogin: async function() {
const generation=this._authGeneration||0;
var loginRes = await new Promise(function(resolve, reject) {
var done = false;
var timer = setTimeout(function() {
if (!done) { done = true; reject({ error: '登录超时,请重试' }); }
}, 10000);
wx.login({
success: function(res) { if (!done) { done = true; clearTimeout(timer); resolve(res); } },
fail: function(err) { if (!done) { done = true; clearTimeout(timer); reject(err); } }
});
});
if (!loginRes.code) throw { error: '未能获取微信登录凭证,请重试' };
// 定位授权不阻塞登录,城市信息由页面后续的定位接口更新。
var res = await api.wechatLogin(loginRes.code, '');
if (generation!==(this._authGeneration||0)) throw {error:'账号状态已变更,请重新进入'};
if (!res || !res.token || !res.user_id) throw { error: '登录响应不完整,请重试' };
wx.removeStorageSync('accountDeleted');
wx.setStorageSync('token', res.token);
wx.setStorageSync('userId', res.user_id);
wx.setStorageSync('coupleId', res.couple_id || '');
wx.setStorageSync('displayId', String(res.display_id || ''));
wx.setStorageSync('nickname', res.nickname || '');
wx.setStorageSync('region', res.region || '');
wx.setStorageSync('hasLoggedIn', true);
require('./utils/data-cache').applyRemoteRevision(res.data_revision);
this.globalData.token = res.token;
this.globalData.userId = res.user_id;
return res;
},
// 检查当前网络状态
checkNetwork: function() {
wx.getNetworkType({
success: (res) => {
this.globalData.isConnected = res.networkType !== 'none';
this.globalData.networkType = res.networkType;
}
});
}
});