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.
164 lines
6.2 KiB
JavaScript
164 lines
6.2 KiB
JavaScript
const { withDataCache } = require('../../utils/page-cache');
|
|
const { tryLoginRenewal } = require('../../utils/subscribe');
|
|
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(withDataCache({
|
|
onLoginSubscribeTap: function() { return tryLoginRenewal(); },
|
|
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,
|
|
isUnlimited:false,
|
|
quotaText:"200 MB",
|
|
vipExpireText: '',
|
|
plans: [],
|
|
paying: false,
|
|
},
|
|
|
|
onLoad: function () { this._unloaded = false; this.setData(getLayout()); },
|
|
onUnload: function () { this._unloaded = true; },
|
|
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.quota_bytes != null ? d.quota_bytes : (d.quotaBytes != null ? d.quotaBytes : (d.free_bytes || d.freeBytes || 0));
|
|
var unlimited=!!(d.is_unlimited || d.isUnlimited);
|
|
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,
|
|
priceCents:cents,
|
|
version:p.updated_at || p.updatedAt,
|
|
priceText: (cents / 100).toFixed(2),
|
|
unit: (p.months || (p.key==='year'?12:1))+'个月',
|
|
description: (Number(p.quota_mb != null ? p.quota_mb : p.quotaMb || 0)===0 ? '无限空间' : formatBytes(Number(p.quota_mb != null ? p.quota_mb : p.quotaMb)*1024*1024))+' · 有效期'+(p.months || (p.key==='year'?12:1))+'个月',
|
|
|
|
};
|
|
});
|
|
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 != null ? d.free_mb : (d.freeMb != null ? d.freeMb : 200),
|
|
freeText: formatBytes(freeBytes),
|
|
percent: percent,
|
|
overQuota: overQuota,
|
|
isVip: !!(d.is_vip || d.isVip),
|
|
isUnlimited:unlimited,
|
|
quotaText:unlimited?"无限空间":formatBytes(freeBytes),
|
|
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, expected_price_cents:plan.priceCents, expected_plan_version:plan.version }).then(function (order) {
|
|
wx.hideLoading();
|
|
if (order && order.pay_provider === 'wechat' && order.payment) {
|
|
self.callWechatPay(order);
|
|
return;
|
|
}
|
|
self.setData({ paying: false });
|
|
wx.showToast({ title: '支付参数不完整,请重新下单', icon: 'none' });
|
|
}).catch(function (err) {
|
|
wx.hideLoading();
|
|
self.setData({ paying: false });
|
|
wx.showToast({ title: (err && err.error) || '下单失败', icon: 'none' });
|
|
});
|
|
},
|
|
|
|
callWechatPay: function (order) {
|
|
var self = this;
|
|
var payment = order.payment;
|
|
if (!wx.requestPayment) {
|
|
this.setData({ paying: false });
|
|
wx.showToast({ title: '当前环境不支持微信支付,请使用微信真机', icon: 'none' });
|
|
return;
|
|
}
|
|
wx.requestPayment({
|
|
timeStamp: payment.time_stamp,
|
|
nonceStr: payment.nonce_str,
|
|
package: payment.package,
|
|
signType: payment.sign_type,
|
|
paySign: payment.pay_sign,
|
|
success: function () { return self.confirmWechatPayment(order.out_trade_no); },
|
|
fail: function (error) {
|
|
// Even a cashier error can occur after payment; query the server once.
|
|
var message = String(error && error.errMsg || '').indexOf('cancel') >= 0 ? '已取消支付' : '支付未完成';
|
|
return self.confirmWechatPayment(order.out_trade_no, message);
|
|
}
|
|
});
|
|
},
|
|
|
|
confirmWechatPayment: async function (number, failureMessage) {
|
|
if (this._confirming) return;
|
|
this._confirming = true;
|
|
try {
|
|
for (var attempt = 0; attempt < (failureMessage ? 1 : 5); attempt++) {
|
|
var result = await api.storageOrderStatus(number);
|
|
if (this._unloaded) return;
|
|
if (result.status === 'success') {
|
|
wx.showToast({ title: '支付成功,会员已开通', icon: 'success' });
|
|
await this.loadInfo();
|
|
return;
|
|
}
|
|
if (result.status === 'closed') {
|
|
wx.showToast({ title: '订单已关闭,请重新下单', icon: 'none' });
|
|
return;
|
|
}
|
|
if (!failureMessage && attempt < 4) await new Promise(function (resolve) { setTimeout(resolve, 1200); });
|
|
}
|
|
if (!this._unloaded) wx.showToast({ title: failureMessage || '支付结果确认中,请稍后刷新', icon: 'none' });
|
|
} catch (e) {
|
|
if (!this._unloaded) wx.showToast({ title: '支付结果暂未确认,请稍后刷新', icon: 'none' });
|
|
} finally {
|
|
this._confirming = false;
|
|
if (!this._unloaded) this.setData({ paying: false });
|
|
}
|
|
},
|
|
|
|
}, {loadInfo:['storageInfo']}));
|