feat: 存储空间管理、虚拟支付接入与容量守卫

- 新增存储空间管理:会员套餐配置、用户容量/到期管理、后台支付记录查询
- 接入个人主体小程序虚拟支付:下单/发货推送/兜底查单(query_order)
- 修复 adminStorageUsers 等接口鉴权、out_trade_no 长度、前端 requestVirtualPayment 参数
- 前端上传前容量预检,超容引导升级无限空间
- 我的页会员卡片展示「无限空间」与已用容量/到期时间
- 后台「存储空间管理」新增支付记录标签页
This commit is contained in:
CodeBuddy
2026-10-30 23:45:39 +08:00
parent b7c6224e54
commit bf99e52937
41 changed files with 2387 additions and 108 deletions
+134
View File
@@ -0,0 +1,134 @@
const { getLayout } = require('../../utils/layout');
const { api } = require('../../utils/request');
function formatBytes(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({
onResize: function () { this.setData(getLayout()); },
data: {
loading: true,
error: false,
usedBytes: 0,
usedText: '0 MB',
freeMb: 200,
freeText: '200 MB',
percent: 0,
overQuota: false,
isVip: false,
vipExpireText: '',
plans: [],
paying: false,
},
onLoad: function () { this.setData(getLayout()); },
onShow: function () { this.loadInfo(); },
onPullDownRefresh: function () { this.loadInfo().then(function () { wx.stopPullDownRefresh(); }); },
loadInfo: function () {
var self = this;
this.setData({ loading: true, error: false });
return api.storageInfo().then(function (d) {
var freeBytes = d.free_bytes || d.freeBytes || 0;
var usedBytes = d.used_bytes || d.usedBytes || 0;
var percent = freeBytes > 0 ? Math.min(100, Math.round(usedBytes / freeBytes * 100)) : 0;
var overQuota = usedBytes > freeBytes;
var plans = (d.plans || []).map(function (p) {
var cents = p.price_cents || p.priceCents || 0;
return {
key: p.key,
name: p.name,
priceText: (cents / 100).toFixed(cents % 100 === 0 ? 0 : 1),
unit: p.key === 'year' ? '年' : '月',
productId: p.product_id || p.productId,
};
});
var vipExpireText = '';
if (d.is_vip || d.isVip) {
var expire = d.vip_expire_at || d.vipExpireAt ? String(d.vip_expire_at || d.vipExpireAt).slice(0, 10) : '';
vipExpireText = expire ? ('有效期至 ' + expire) : '已开通无限空间';
}
self.setData({
loading: false,
usedBytes: usedBytes,
usedText: formatBytes(usedBytes),
freeMb: d.free_mb || d.freeMb || 200,
freeText: formatBytes(freeBytes),
percent: percent,
overQuota: overQuota,
isVip: !!(d.is_vip || d.isVip),
vipExpireText: vipExpireText,
plans: plans,
});
}).catch(function (e) {
self.setData({ loading: false, error: true });
console.error('加载存储信息失败', e);
});
},
buyPlan: function (e) {
var key = e.currentTarget.dataset.key;
var self = this;
if (this.data.paying) return;
var plan = null;
for (var i = 0; i < this.data.plans.length; i++) {
if (this.data.plans[i].key === key) { plan = this.data.plans[i]; break; }
}
if (!plan) return;
wx.showLoading({ title: '下单中...', mask: true });
this.setData({ paying: true });
api.storageCreateOrder({ plan_key: key }).then(function (order) {
wx.hideLoading();
if (!order || !(order.sign_data || order.signData)) {
self.setData({ paying: false });
wx.showToast({ title: '下单失败', icon: 'none' });
return;
}
self.callVirtualPay(order);
}).catch(function (err) {
wx.hideLoading();
self.setData({ paying: false });
wx.showToast({ title: (err && err.error) || '下单失败', icon: 'none' });
});
},
callVirtualPay: function (order) {
var self = this;
if (!wx.requestVirtualPayment) {
this.setData({ paying: false });
wx.showModal({
title: '版本过低',
content: '请升级微信至最新版本后再试。',
showCancel: false
});
return;
}
// wx.requestVirtualPayment 仅接受 signData / paySig / signature / mode 四个参数;
// offerId、env、productId、goodsPrice 等均已编码在 signData JSON 字符串内,不可再平铺为顶层参数。
wx.requestVirtualPayment({
signData: order.sign_data || order.signData,
paySig: order.pay_sig || order.paySig,
signature: order.signature,
mode: order.mode || 'short_series_goods',
success: function () {
self.setData({ paying: false });
wx.showToast({ title: '支付成功', icon: 'success' });
setTimeout(function () { self.loadInfo(); }, 1200);
},
fail: function (err) {
self.setData({ paying: false });
var msg = '支付未完成';
var m = (err && err.errMsg) || '';
if (m.indexOf('cancel') >= 0) msg = '已取消支付';
else if (m.indexOf('fail') >= 0) msg = '支付失败,请稍后重试';
wx.showToast({ title: msg, icon: 'none' });
}
});
},
});