feat: 存储空间管理、虚拟支付接入与容量守卫
- 新增存储空间管理:会员套餐配置、用户容量/到期管理、后台支付记录查询 - 接入个人主体小程序虚拟支付:下单/发货推送/兜底查单(query_order) - 修复 adminStorageUsers 等接口鉴权、out_trade_no 长度、前端 requestVirtualPayment 参数 - 前端上传前容量预检,超容引导升级无限空间 - 我的页会员卡片展示「无限空间」与已用容量/到期时间 - 后台「存储空间管理」新增支付记录标签页
This commit is contained in:
@@ -5,12 +5,21 @@ const { getLayout } = require('../../utils/layout');
|
||||
const { api, getImageUrl } = require('../../utils/request');
|
||||
const { saveScrollPosition, resetScrollOnTabShow } = require('../../utils/scroll-pos');
|
||||
const { requestSubscribe } = require('../../utils/subscribe');
|
||||
const { guardStorageSpace } = require('../../utils/storage-guard');
|
||||
|
||||
function formatStorageBytes(bytes) {
|
||||
bytes = bytes || 0;
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
if (bytes < 1024 * 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + ' MB';
|
||||
return (bytes / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
||||
}
|
||||
|
||||
Page(withDataCache({
|
||||
onResize: function() { this.setData(getLayout()); },
|
||||
data: {
|
||||
scrollTop: 0,
|
||||
statsLoading:false, statsError:false,
|
||||
storageText:'—', storagePercent:0, storageOverQuota:false, storageVip:false, storageExpireText:'',
|
||||
statisticItems:[
|
||||
{key:'timeline',label:'记忆日',icon:'stat-timeline',count:'—',color:'pink'},
|
||||
{key:'album',label:'相册',icon:'stat-album',count:'—',color:'yellow'},
|
||||
@@ -191,6 +200,7 @@ Page(withDataCache({
|
||||
}
|
||||
this.loadData();
|
||||
this.loadProfileStats();
|
||||
this.loadStorageInfo();
|
||||
if (!cid && this.data.showInvitePanel && !this._pollTimer) this.startPollCoupleStatus();
|
||||
// 如果已绑定情侣,关闭邀请面板
|
||||
if (cid && this.data.showInvitePanel) {
|
||||
@@ -214,7 +224,7 @@ Page(withDataCache({
|
||||
},
|
||||
|
||||
onPullDownRefresh: async function() {
|
||||
await Promise.all([this.loadData(),this.loadProfileStats()]);
|
||||
await Promise.all([this.loadData(),this.loadProfileStats(),this.loadStorageInfo()]);
|
||||
wx.stopPullDownRefresh();
|
||||
},
|
||||
|
||||
@@ -486,25 +496,29 @@ Page(withDataCache({
|
||||
},
|
||||
|
||||
chooseAvatar: function() {
|
||||
wx.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
const filePath = res.tempFiles[0].path;
|
||||
this.setData({ editAvatarUrl: filePath });
|
||||
// 上传头像
|
||||
try {
|
||||
const data = await api.uploadAvatar(filePath);
|
||||
this.setData({
|
||||
editAvatarUrl: getImageUrl(data.path),
|
||||
editAvatarPath: data.path
|
||||
});
|
||||
wx.showToast({ title: '头像上传成功 ✨', icon: 'success' });
|
||||
} catch (e) {
|
||||
wx.showToast({ title: e.error || '上传失败', icon: 'none' });
|
||||
const self = this;
|
||||
guardStorageSpace(0).then(function(ok) {
|
||||
if (!ok) return;
|
||||
wx.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
const filePath = res.tempFiles[0].path;
|
||||
self.setData({ editAvatarUrl: filePath });
|
||||
// 上传头像
|
||||
try {
|
||||
const data = await api.uploadAvatar(filePath);
|
||||
self.setData({
|
||||
editAvatarUrl: getImageUrl(data.path),
|
||||
editAvatarPath: data.path
|
||||
});
|
||||
wx.showToast({ title: '头像上传成功 ✨', icon: 'success' });
|
||||
} catch (e) {
|
||||
wx.showToast({ title: e.error || '上传失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -640,16 +654,20 @@ Page(withDataCache({
|
||||
},
|
||||
choosePartnerAvatar: function() {
|
||||
if (this.data.partnerSaving || this.data.partnerAvatarUploading || !this._editPartnerId) return;
|
||||
const self = this;
|
||||
const partnerId = this._editPartnerId;
|
||||
wx.chooseImage({count:1,sizeType:['compressed'],sourceType:['album','camera'],success:async res=>{
|
||||
if (!this.data.showEditPartnerModal || partnerId !== this._editPartnerId) return;
|
||||
this.setData({partnerAvatarUploading:true});
|
||||
try {
|
||||
const uploaded = await api.uploadPartnerAvatar(res.tempFilePaths[0],partnerId);
|
||||
this.setData({editPartnerAvatarUrl:getImageUrl(uploaded.path),editPartnerAvatarPath:uploaded.path});
|
||||
} catch(error) { wx.showToast({title:error.error || '头像上传失败',icon:'none'}); }
|
||||
finally { this.setData({partnerAvatarUploading:false}); }
|
||||
}});
|
||||
guardStorageSpace(0).then(function(ok) {
|
||||
if (!ok) return;
|
||||
wx.chooseImage({count:1,sizeType:['compressed'],sourceType:['album','camera'],success:async res=>{
|
||||
if (!self.data.showEditPartnerModal || partnerId !== self._editPartnerId) return;
|
||||
self.setData({partnerAvatarUploading:true});
|
||||
try {
|
||||
const uploaded = await api.uploadPartnerAvatar(res.tempFilePaths[0],partnerId);
|
||||
self.setData({editPartnerAvatarUrl:getImageUrl(uploaded.path),editPartnerAvatarPath:uploaded.path});
|
||||
} catch(error) { wx.showToast({title:error.error || '头像上传失败',icon:'none'}); }
|
||||
finally { self.setData({partnerAvatarUploading:false}); }
|
||||
}});
|
||||
});
|
||||
},
|
||||
savePartnerProfile: async function() {
|
||||
const d = this.data;
|
||||
@@ -1014,6 +1032,44 @@ Page(withDataCache({
|
||||
this.setData({ showShareBindModal: false });
|
||||
},
|
||||
|
||||
loadStorageInfo: async function() {
|
||||
try {
|
||||
const d = await api.storageInfo();
|
||||
const freeBytes = d.free_bytes || d.freeBytes || 0;
|
||||
const usedBytes = d.used_bytes || d.usedBytes || 0;
|
||||
const isVip = !!(d.is_vip || d.isVip);
|
||||
const usedText = formatStorageBytes(usedBytes);
|
||||
if (isVip) {
|
||||
// 会员:展示「无限空间」+ 已用容量 + 到期时间,不再提示空间已满。
|
||||
const expire = d.vip_expire_at || d.vipExpireAt;
|
||||
this.setData({
|
||||
storageVip: true,
|
||||
storageText: '无限空间',
|
||||
storagePercent: 0,
|
||||
storageOverQuota: false,
|
||||
storageUsedText: usedText,
|
||||
storageExpireText: expire ? ('有效期至 ' + String(expire).slice(0, 10)) : '已开通无限空间'
|
||||
});
|
||||
} else {
|
||||
const percent = freeBytes > 0 ? Math.min(100, Math.round(usedBytes / freeBytes * 100)) : 0;
|
||||
this.setData({
|
||||
storageVip: false,
|
||||
storageText: usedText,
|
||||
storagePercent: percent,
|
||||
storageOverQuota: usedBytes > freeBytes,
|
||||
storageUsedText: usedText,
|
||||
storageExpireText: ''
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载存储信息失败', e);
|
||||
}
|
||||
},
|
||||
goStorage: function() {
|
||||
this._savePosBeforeLeave();
|
||||
wx.navigateTo({ url: '/pages/storage/storage' });
|
||||
},
|
||||
|
||||
loadProfileStats: async function() {
|
||||
this.setData({statsLoading:true,statsError:false});
|
||||
const keys=['timeline','album','diary','songs','checkin','pet'];
|
||||
@@ -1046,4 +1102,4 @@ Page(withDataCache({
|
||||
manageRelationship: function() { wx.navigateTo({ url: '/pages/unbind/unbind' }); },
|
||||
goToSettings: function() { this._savePosBeforeLeave(); wx.navigateTo({ url: '/pages/settings/settings' }); },
|
||||
goHome: function() { this._savePosBeforeLeave(); wx.switchTab({ url: '/pages/index/index' }); }
|
||||
}, {"loadData": ["coupleInfo"], "loadProfileStats": ["timelineList","albumList","diaryList","songsList","checkinList","petInfo"]}));
|
||||
}, {"loadData": ["coupleInfo"], "loadProfileStats": ["timelineList","albumList","diaryList","songsList","checkinList","petInfo"], "loadStorageInfo": ["storageInfo"]}));
|
||||
|
||||
Reference in New Issue
Block a user