Files
jiapu/public/js/vip-pages.js
T
2026-08-03 16:49:38 +08:00

475 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.VipPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.VipPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var PACKAGE_TYPES = {
vip: '会员套餐',
storage: '存储扩容'
};
var DURATION_UNITS = {
permanent: '永久',
day: '天',
month: '个月',
year: '年'
};
var PAY_STATUS = {
'0': '待支付',
'1': '已支付',
'2': '已关闭',
'3': '已退款'
};
function escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function normalizeId(value) {
var text;
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
if (value === undefined || value === null) return '';
text = String(value).trim();
return /^[1-9][0-9]*$/.test(text) ? text : '';
}
function optionalText(value) {
if (value === undefined || value === null) return '';
return String(value).trim();
}
function moneyText(value, optional) {
var text;
if ((value === undefined || value === null || value === '') && optional) return '';
if (typeof value === 'number' && (!Number.isFinite(value) || value < 0)) return null;
text = String(value === undefined || value === null ? '' : value).trim();
return /^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(text) ? text : null;
}
function validLimit(value) {
return value === undefined || value === null ||
(Number.isSafeInteger(Number(value)) && Number(value) >= 0);
}
function normalizeVipPackage(item) {
var source = item || {};
var packageId = normalizeId(source.packageId);
var packageName = optionalText(source.packageName);
var packageType = optionalText(source.packageType);
var durationUnit = optionalText(source.durationUnit);
var price = moneyText(source.price, false);
var originalPrice = moneyText(source.originalPrice, true);
var status = String(source.status === undefined || source.status === null ? '' : source.status);
if (!packageId || !packageName ||
!Object.prototype.hasOwnProperty.call(PACKAGE_TYPES, packageType) ||
!Object.prototype.hasOwnProperty.call(DURATION_UNITS, durationUnit) ||
price === null || originalPrice === null || status !== '0' ||
!validLimit(source.durationValue) || !validLimit(source.genealogyLimit) ||
!validLimit(source.memberLimit) || !validLimit(source.storageLimitMb)) {
return null;
}
return {
packageId: packageId,
packageName: packageName,
packageType: packageType,
packageDesc: optionalText(source.packageDesc),
price: price,
originalPrice: originalPrice,
durationValue: source.durationValue === undefined || source.durationValue === null ? null : Number(source.durationValue),
durationUnit: durationUnit,
genealogyLimit: source.genealogyLimit === undefined || source.genealogyLimit === null ? null : Number(source.genealogyLimit),
memberLimit: source.memberLimit === undefined || source.memberLimit === null ? null : Number(source.memberLimit),
storageLimitMb: source.storageLimitMb === undefined || source.storageLimitMb === null ? null : Number(source.storageLimitMb),
remark: optionalText(source.remark)
};
}
function normalizeVipPackages(data) {
if (!Array.isArray(data)) return [];
return data.map(normalizeVipPackage).filter(Boolean);
}
function normalizeGenealogyOption(item) {
var source = item || {};
var genealogyId = normalizeId(source.genealogyId);
var genealogyName = optionalText(source.genealogyName);
if (!genealogyId || !genealogyName) return null;
return {
genealogyId: genealogyId,
genealogyName: genealogyName,
surname: optionalText(source.surname)
};
}
function normalizeGenealogyOptions(data) {
if (!Array.isArray(data)) return [];
return data.map(normalizeGenealogyOption).filter(Boolean);
}
function resolveContextGenealogyId(options, contextId) {
var normalizedContextId = normalizeId(contextId);
if (!normalizedContextId || !Array.isArray(options)) return '';
return options.some(function (option) {
return option.genealogyId === normalizedContextId;
}) ? normalizedContextId : '';
}
function buildVipOrderBody(values) {
var source = values || {};
var body = {};
var packageId = normalizeId(source.packageId);
var genealogyId = normalizeId(source.genealogyId);
var payType = optionalText(source.payType);
if (packageId) body.packageId = packageId;
else if (source.packageId !== undefined) body.invalidPackageId = true;
if (genealogyId) body.genealogyId = genealogyId;
else if (source.genealogyId) body.invalidGenealogyId = true;
if (payType) body.payType = payType;
return body;
}
function validateVipOrderBody(body) {
if (!body || !normalizeId(body.packageId) || body.invalidPackageId) return '请选择有效会员套餐';
if (body.invalidGenealogyId ||
(body.genealogyId !== undefined && body.genealogyId !== null &&
String(body.genealogyId).trim() !== '' && !normalizeId(body.genealogyId))) {
return '家谱选项无效';
}
if (body.payType && body.payType !== 'wechat') return '支付方式无效';
return '';
}
function normalizeVipOrder(item) {
var source = item || {};
var orderId = normalizeId(source.orderId);
var packageId = normalizeId(source.packageId);
var genealogyId = source.genealogyId === undefined || source.genealogyId === null
? ''
: normalizeId(source.genealogyId);
var orderAmount = moneyText(source.orderAmount, false);
var payAmount = moneyText(source.payAmount, false);
var payStatus = String(source.payStatus === undefined || source.payStatus === null ? '' : source.payStatus);
var status = String(source.status === undefined || source.status === null ? '' : source.status);
if (!orderId || !packageId || !optionalText(source.orderNo) ||
!optionalText(source.packageName) ||
(source.genealogyId !== undefined && source.genealogyId !== null && !genealogyId) ||
orderAmount === null || payAmount === null ||
!Object.prototype.hasOwnProperty.call(PAY_STATUS, payStatus) ||
['0', '1'].indexOf(status) < 0) {
return null;
}
return {
orderId: orderId,
orderNo: optionalText(source.orderNo),
packageId: packageId,
packageName: optionalText(source.packageName),
genealogyId: genealogyId,
genealogyNo: optionalText(source.genealogyNo),
genealogyName: optionalText(source.genealogyName),
orderAmount: orderAmount,
payAmount: payAmount,
payType: optionalText(source.payType),
payStatus: payStatus,
payTime: optionalText(source.payTime),
expireTime: optionalText(source.expireTime),
status: status,
remark: optionalText(source.remark)
};
}
function normalizeVipOrders(data) {
if (!Array.isArray(data)) return [];
return data.map(normalizeVipOrder).filter(Boolean);
}
function renderVipPackages(data, selectedPackageId) {
var items = Array.isArray(data) ? data : [];
var selectedId = normalizeId(selectedPackageId);
if (!items.length) return '<div class="api-empty">当前没有可用会员套餐</div>';
return items.map(function (item) {
var duration = item.durationUnit === 'permanent'
? DURATION_UNITS[item.durationUnit]
: (item.durationValue === null ? '' : item.durationValue) + DURATION_UNITS[item.durationUnit];
var limits = [
item.genealogyLimit === null ? '' : '家谱 ' + item.genealogyLimit + ' 部',
item.memberLimit === null ? '' : '成员 ' + item.memberLimit + ' 人',
item.storageLimitMb === null ? '' : '存储 ' + item.storageLimitMb + ' MB'
].filter(Boolean).join(' · ');
return '<button class="module-card' + (item.packageId === selectedId ? ' is-selected' : '') +
'" type="button" data-vip-package-id="' + escapeHtml(item.packageId) +
'" data-vip-package-name="' + escapeHtml(item.packageName) + '"><span class="icon">会</span><h3>' +
escapeHtml(item.packageName) + '</h3><p>' +
escapeHtml([PACKAGE_TYPES[item.packageType], item.packageDesc, duration, limits].filter(Boolean).join(' · ')) +
'</p><strong>¥' + escapeHtml(item.price) + '</strong></button>';
}).join('');
}
function renderGenealogyOptions(data) {
var items = normalizeGenealogyOptions(data);
return '<option value="">不关联具体家谱</option>' + items.map(function (item) {
return '<option value="' + escapeHtml(item.genealogyId) + '">' +
escapeHtml(item.genealogyName + (item.surname ? ' · ' + item.surname + '氏' : '')) +
'</option>';
}).join('');
}
function renderVipOrders(data) {
var items = normalizeVipOrders(data);
if (!items.length) return '<div class="api-empty">当前没有会员订单</div>';
return items.map(function (item) {
var details = [
item.orderNo,
item.genealogyName,
'订单金额 ¥' + item.orderAmount,
'应付 ¥' + item.payAmount,
item.payType ? '支付方式 ' + item.payType : '',
item.payTime ? '支付时间 ' + item.payTime : '',
item.expireTime ? '有效期至 ' + item.expireTime : '',
item.remark
].filter(Boolean).join(' · ');
return '<article class="module-row"><div><h3>' + escapeHtml(item.packageName) +
'</h3><p>' + escapeHtml(details) + '</p></div><span class="pill">' +
PAY_STATUS[item.payStatus] + '</span></article>';
}).join('');
}
function shouldRedirectToLogin(api, error) {
var status = error && (error.status || error.code);
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
}
function redirectToLogin(api) {
if (api && api.clearToken) api.clearToken();
if (!root.location) return;
if (typeof root.location.replace === 'function') root.location.replace('login.html');
else root.location.href = 'login.html';
}
async function init() {
var page = root.document && root.document.querySelector('[data-vip-page]');
var packageList = root.document && root.document.querySelector('[data-vip-package-list]');
var selectedText = root.document && root.document.querySelector('[data-vip-selected-package]');
var genealogySelect = root.document && root.document.querySelector('[data-vip-genealogy-options]');
var form = root.document && root.document.querySelector('[data-vip-order-form]');
var submit = root.document && root.document.querySelector('[data-vip-order-submit]');
var status = root.document && root.document.querySelector('[data-vip-order-status]');
var refresh = root.document && root.document.querySelector('[data-vip-order-refresh]');
var orderList = root.document && root.document.querySelector('[data-vip-order-list]');
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
var packages = [];
var selectedPackageId = '';
var contextGenealogyId = root.ProfileUI && typeof root.ProfileUI.getGenealogyId === 'function'
? root.ProfileUI.getGenealogyId()
: '';
var writePending = false;
if (!page) return;
if (shouldRedirectToLogin(api)) {
redirectToLogin(api);
return;
}
if (!packageList || !selectedText || !genealogySelect || !form || !orderList ||
!api.vipPackages || !api.genealogiesMine || !api.vipOrders || !api.createVipOrder) {
setListState(packageList, 'error', '会员套餐服务初始化失败,请刷新后重试。');
setListState(orderList, 'error', '会员订单服务初始化失败,请刷新后重试。');
setStatus('error', '会员服务接口初始化失败,请刷新后重试。');
return;
}
function setApiState(target, type, message) {
if (!target) return;
if (root.ProfileUI && root.ProfileUI.setApiState && type) {
root.ProfileUI.setApiState(target, type, message);
return;
}
target.textContent = message || '';
}
function setStatus(type, message) {
setApiState(status, type, message);
}
function setListState(target, type, message) {
setApiState(target, type, message);
}
function setPendingButton(button, pending, label) {
if (!button) return;
if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent;
button.disabled = pending;
button.textContent = pending ? label : button.dataset.defaultLabel;
}
function renderPackages() {
if (!packages.length) {
setListState(packageList, 'empty', '当前没有可用会员套餐。');
return;
}
packageList.innerHTML = renderVipPackages(packages, selectedPackageId);
}
function renderOrders(orders) {
var items = normalizeVipOrders(orders);
if (!items.length) {
setListState(orderList, 'empty', '当前没有会员订单。');
return;
}
orderList.innerHTML = renderVipOrders(items);
}
async function loadOrders() {
var orders = normalizeVipOrders(await api.vipOrders());
renderOrders(orders);
return orders;
}
setListState(packageList, 'loading', '正在读取会员套餐…');
setListState(orderList, 'loading', '正在读取会员订单…');
setStatus('loading', '正在读取会员服务…');
try {
var initial = await Promise.all([api.vipPackages(), api.genealogiesMine(), api.vipOrders()]);
var genealogyOptions = normalizeGenealogyOptions(initial[1]);
var selectedContextGenealogyId = resolveContextGenealogyId(genealogyOptions, contextGenealogyId);
packages = normalizeVipPackages(initial[0]);
renderPackages();
genealogySelect.innerHTML = renderGenealogyOptions(genealogyOptions);
if (selectedContextGenealogyId) genealogySelect.value = selectedContextGenealogyId;
renderOrders(initial[2]);
setStatus('', packages.length
? (selectedContextGenealogyId ? '已关联当前家谱,请选择会员套餐' : '请选择会员套餐')
: '当前没有可用会员套餐');
} catch (error) {
if (shouldRedirectToLogin(api, error)) {
redirectToLogin(api);
return;
}
setListState(packageList, 'error', '会员套餐读取失败,请稍后重试。');
setListState(orderList, 'error', '会员订单读取失败,请稍后重试。');
setStatus('error', error.message || '会员服务读取失败,请稍后重试。');
return;
}
packageList.addEventListener('click', function (event) {
var button = event.target.closest('[data-vip-package-id]');
var packageId;
var selected;
if (!button || !packageList.contains(button)) return;
packageId = normalizeId(button.getAttribute('data-vip-package-id'));
selected = packages.find(function (item) { return item.packageId === packageId; });
if (!selected) return;
selectedPackageId = selected.packageId;
selectedText.textContent = '已选择:' + selected.packageName + '(¥' + selected.price + '';
renderPackages();
setStatus('', '可以创建会员订单');
});
if (refresh) {
refresh.addEventListener('click', async function () {
if (writePending) return;
setPendingButton(refresh, true, '正在刷新…');
setStatus('loading', '正在刷新会员订单…');
try {
await loadOrders();
setStatus('', '会员订单已刷新');
} catch (error) {
if (shouldRedirectToLogin(api, error)) {
redirectToLogin(api);
return;
}
setStatus('error', error.message || '会员订单刷新失败,请稍后重试。');
} finally {
setPendingButton(refresh, false, '正在刷新…');
}
});
}
form.addEventListener('submit', async function (event) {
var body;
var validation;
var created;
var refreshed;
var verified;
event.preventDefault();
if (writePending) return;
body = buildVipOrderBody({
packageId: selectedPackageId,
genealogyId: genealogySelect.value
});
validation = validateVipOrderBody(body);
if (validation) {
setStatus('error', validation);
return;
}
writePending = true;
setPendingButton(submit, true, '正在创建订单…');
setStatus('loading', '正在创建会员订单…');
try {
created = normalizeVipOrder(await api.createVipOrder(body));
if (!created) throw new Error('创建响应缺少有效会员订单信息');
refreshed = normalizeVipOrders(await api.vipOrders());
verified = refreshed.find(function (item) { return item.orderId === created.orderId; });
if (!verified) throw new Error('订单已提交,但重新读取未找到对应记录');
renderOrders(refreshed);
setStatus('', '订单已创建;当前 PC 暂未开放在线支付。');
} catch (error) {
if (shouldRedirectToLogin(api, error)) {
redirectToLogin(api);
return;
}
setStatus('error', error.message || '会员订单创建失败,请稍后重试。');
} finally {
writePending = false;
setPendingButton(submit, false, '正在创建订单…');
}
});
}
return {
normalizeVipPackage: normalizeVipPackage,
normalizeVipPackages: normalizeVipPackages,
normalizeGenealogyOption: normalizeGenealogyOption,
normalizeGenealogyOptions: normalizeGenealogyOptions,
resolveContextGenealogyId: resolveContextGenealogyId,
buildVipOrderBody: buildVipOrderBody,
validateVipOrderBody: validateVipOrderBody,
normalizeVipOrder: normalizeVipOrder,
normalizeVipOrders: normalizeVipOrders,
renderVipPackages: renderVipPackages,
renderGenealogyOptions: renderGenealogyOptions,
renderVipOrders: renderVipOrders,
init: init
};
});