(() => {
const $ = (sel) => document.querySelector(sel);
const rechargeOptionListEl = $('#rechargeOptionList') || $('#planList');
const estimateBoxEl = $('#estimateBox');
const resultBoxEl = $('#orderResultBox');
const holderNameEl = $('#holderName');
const customInitialBalanceEl = $('#customInitialBalance') || $('#initialBalance');
const customRechargeBoxEl = $('#customRechargeBox');
const submitBtn = $('#submitOrderBtn');
const HOLDER_NAME_PATTERN = /^[A-Za-z][A-Za-z .,'()&@/_\-+]*$/;
const state = {
rechargeOptions: [5, 10, 15, 20],
selectedAmount: 5,
customMode: false
};
if (!rechargeOptionListEl || !estimateBoxEl || !resultBoxEl || !holderNameEl || !customInitialBalanceEl || !submitBtn) {
console.error('[ic-card-order] Missing required DOM nodes', {
rechargeOptionListEl: !!rechargeOptionListEl,
estimateBoxEl: !!estimateBoxEl,
resultBoxEl: !!resultBoxEl,
holderNameEl: !!holderNameEl,
customInitialBalanceEl: !!customInitialBalanceEl,
customRechargeBoxEl: !!customRechargeBoxEl,
submitBtn: !!submitBtn
});
return;
}
const api = {
async request(url, opts = {}) {
const res = await fetch(url, opts);
const data = await res.json().catch(() => ({}));
if (!res.ok || data.ok === false) {
throw new Error(data.error || res.statusText || '请求失败');
}
return data;
},
fetchConfig() {
return api.request('/api/public/ic-cards/config');
},
createOrder(payload) {
return api.request('/api/public/ic-cards/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
}
};
const escapeHtml = (value) => String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
const getInitialBalance = () => {
if (state.customMode) {
return Math.max(1, Number(customInitialBalanceEl.value || 0) || 0);
}
return Math.max(1, Number(state.selectedAmount || 0) || 0);
};
const renderRechargeOptions = () => {
const options = Array.isArray(state.rechargeOptions) && state.rechargeOptions.length
? state.rechargeOptions
: [5, 10, 15, 20];
rechargeOptionListEl.innerHTML = options.map((amount) => `
${escapeHtml(amount)}
首次充值 ${escapeHtml(amount)}
`).join('') + `
自定义
手动输入首次充值金额
`;
if (customRechargeBoxEl) {
customRechargeBoxEl.classList.toggle('is-active', !!state.customMode);
}
rechargeOptionListEl.querySelectorAll('[data-amount]').forEach((button) => {
button.addEventListener('click', () => {
state.customMode = false;
state.selectedAmount = Number(button.getAttribute('data-amount')) || 5;
customInitialBalanceEl.disabled = true;
renderRechargeOptions();
renderEstimate();
});
});
const customBtn = rechargeOptionListEl.querySelector('[data-custom="true"]');
if (customBtn) {
customBtn.addEventListener('click', () => {
state.customMode = true;
customInitialBalanceEl.disabled = false;
customInitialBalanceEl.focus();
if (!customInitialBalanceEl.value) customInitialBalanceEl.value = '25';
renderRechargeOptions();
renderEstimate();
});
}
};
const syncLegacyDom = () => {
const holderPhoneEl = $('#holderPhone');
const orderNoteEl = $('#orderNote');
if (holderPhoneEl) {
holderPhoneEl.disabled = true;
holderPhoneEl.value = '';
holderPhoneEl.placeholder = '已停用';
holderPhoneEl.style.display = 'none';
}
if (orderNoteEl) {
orderNoteEl.disabled = true;
orderNoteEl.value = '';
orderNoteEl.placeholder = '已停用';
orderNoteEl.style.display = 'none';
}
const createTypeSelect = $('#createType');
if (createTypeSelect) {
createTypeSelect.disabled = true;
createTypeSelect.style.display = 'none';
}
};
const validateHolderName = (value) => {
const holderName = String(value || '').trim();
if (!holderName) return '请输入持卡人姓名';
if (holderName.length > 24) return '持卡人姓名不能超过 24 个字符';
if (!HOLDER_NAME_PATTERN.test(holderName)) return '持卡人姓名仅支持英文与常用符号';
return '';
};
const validateInitialBalance = () => {
const initialBalance = getInitialBalance();
if (!Number.isFinite(initialBalance) || initialBalance < 1) {
return '首次充值金额必须大于 0';
}
return '';
};
const renderEstimate = () => {
const initialBalance = getInitialBalance();
if (!Number.isFinite(initialBalance) || initialBalance < 1) {
estimateBoxEl.innerHTML = '';
return;
}
estimateBoxEl.innerHTML = `
卡片类型IC 储值卡
首次充值${escapeHtml(initialBalance)}
持卡人限制仅英文与常用符号
预计金额${escapeHtml(initialBalance)}
`;
};
const renderResult = (data) => {
if (!data) {
resultBoxEl.className = 'jr-center-empty';
resultBoxEl.innerHTML = '提交后将在此显示卡号、凭证码和领卡提示。
';
return;
}
const isDomain = location.hostname.includes('fse-media.group');
const voucherCode = data.code || data.voucher_code || data.order_code || '---';
const cardStatus = String(data.card?.status || data.status || '').trim().toLowerCase();
const lookupKey = (cardStatus === 'pending_pickup')
? voucherCode
: (data.card?.card_id || data.card_id || voucherCode);
const searchHref = isDomain
? `https://ticket.fse-media.group/ic-card/search?q=${encodeURIComponent(lookupKey)}`
: `/ic-card-search.html?q=${encodeURIComponent(lookupKey)}`;
const detailHref = isDomain
? `https://ticket.fse-media.group/ic/${encodeURIComponent(lookupKey)}`
: `/ic/${encodeURIComponent(lookupKey)}`;
const shownCardId = data.display_card_id || data.card?.display_card_id || data.card_id || '---';
const amount = Number(data.amount ?? data.card?.purchase_amount ?? data.card?.balance ?? getInitialBalance()) || getInitialBalance();
resultBoxEl.className = '';
resultBoxEl.innerHTML = `
ORDER CREATED
凭证码
${escapeHtml(voucherCode)}
卡号
${escapeHtml(shownCardId)}
首次充值
${escapeHtml(amount)}
当前状态
待领卡
购卡信息已生成。请保存卡号与凭证码,前往站内办理领卡或后续状态查询。
`;
};
const submitOrder = async () => {
const holderNameError = validateHolderName(holderNameEl.value);
if (holderNameError) {
alert(holderNameError);
return;
}
const balanceError = validateInitialBalance();
if (balanceError) {
alert(balanceError);
return;
}
const payload = {
holder_name: holderNameEl.value.trim(),
initial_balance: getInitialBalance()
};
submitBtn.disabled = true;
try {
const data = await api.createOrder(payload);
renderResult(data);
alert(`购卡成功,凭证码:${data.code}`);
} finally {
submitBtn.disabled = false;
}
};
customInitialBalanceEl.addEventListener('input', renderEstimate);
submitBtn.addEventListener('click', () => submitOrder().catch((error) => alert(error.message || String(error))));
(async () => {
syncLegacyDom();
const data = await api.fetchConfig();
state.rechargeOptions = (data.recharge_options || data.initial_balance_options || [5, 10, 15, 20])
.map((value) => Number(value) || 0)
.filter((value) => value > 0);
state.selectedAmount = state.rechargeOptions[0] || 5;
customInitialBalanceEl.disabled = true;
if (customRechargeBoxEl) customRechargeBoxEl.classList.remove('is-active');
renderRechargeOptions();
renderEstimate();
renderResult(null);
})().catch((error) => {
rechargeOptionListEl.innerHTML = `${escapeHtml(error.message || String(error))}
`;
estimateBoxEl.innerHTML = '';
});
})();