- Rebuild aksrt_pay_products as list + in-page quick settings dialog (pay type, limit, cash/points prices, sales float, coupon, order expiry, copy) - Query by _aksrt_pay_type with real pagination; drop order-summary fallback and the duplicate "Meta 销量" column - Centralize pay meta writes in DomainModel::persist_pay_meta(); stop writing unread _aksrt_pay_modo / _aksrt_pay_limit keys Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
197 lines
6.7 KiB
JavaScript
197 lines
6.7 KiB
JavaScript
/** 付费文章:快速设置弹窗(填充、条件显隐、会员价校验、未保存提醒、防重复提交)。仅本页入队。 */
|
|
(function () {
|
|
'use strict';
|
|
|
|
var root = document.querySelector('.aksrt-products-page');
|
|
var dialog = document.getElementById('aksrt-product-editor');
|
|
if (!root || !dialog || typeof dialog.showModal !== 'function') return;
|
|
|
|
var form = dialog.querySelector('[data-aksrt-product-form]');
|
|
var submit = form.querySelector('[data-aksrt-product-submit]');
|
|
var subject = form.querySelector('[data-aksrt-product-subject]');
|
|
var editLink = form.querySelector('[data-aksrt-product-edit-link]');
|
|
var submitLabel = submit.textContent;
|
|
var opener = null;
|
|
var snapshot = '';
|
|
var pointerStartedOutside = false;
|
|
|
|
function field(key) {
|
|
return form.querySelectorAll('[name="aksrt_pay[' + key + ']"]');
|
|
}
|
|
|
|
function value(key) {
|
|
var nodes = field(key);
|
|
for (var i = 0; i < nodes.length; i += 1) {
|
|
var node = nodes[i];
|
|
if (node.type === 'radio') {
|
|
if (node.checked) return node.value;
|
|
} else if (node.type === 'checkbox') {
|
|
return node.checked ? node.value : '';
|
|
} else {
|
|
return node.value;
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function paneActive(name) {
|
|
switch (name) {
|
|
case 'enabled': return value('pay_type') !== 'no';
|
|
case 'disabled': return value('pay_type') === 'no';
|
|
case 'cash': return value('pay_modo') !== 'points';
|
|
case 'points': return value('pay_modo') === 'points';
|
|
case 'expire': return value('order_expired_s') === '1';
|
|
default: return true;
|
|
}
|
|
}
|
|
|
|
/** 隐藏的分区连同其中的控件一起禁用:既不提交,也不参与必填校验。 */
|
|
function syncPanes() {
|
|
form.querySelectorAll('[data-aksrt-product-pane]').forEach(function (pane) {
|
|
var active = paneActive(pane.getAttribute('data-aksrt-product-pane'));
|
|
pane.hidden = !active;
|
|
if (pane.tagName === 'FIELDSET') pane.disabled = !active;
|
|
});
|
|
form.querySelectorAll('input:not([type="hidden"]), select, textarea').forEach(function (control) {
|
|
var node = control.parentElement;
|
|
var disabled = false;
|
|
while (node && node !== form) {
|
|
if (node.hasAttribute('data-aksrt-product-pane') && node.hidden) {
|
|
disabled = true;
|
|
break;
|
|
}
|
|
node = node.parentElement;
|
|
}
|
|
control.disabled = disabled;
|
|
});
|
|
syncVip();
|
|
}
|
|
|
|
/** 现金按「分」比较,避免浮点误差。 */
|
|
function amount(raw, mode) {
|
|
var text = String(raw || '').trim();
|
|
if (mode === 'points') return /^\d+$/.test(text) ? Number(text) : NaN;
|
|
var match = text.match(/^(\d+)(?:\.(\d{1,2}))?$/);
|
|
return match ? Number(match[1]) * 100 + Number((match[2] || '').padEnd(2, '0')) : NaN;
|
|
}
|
|
|
|
function syncVip() {
|
|
['cash', 'points'].forEach(function (mode) {
|
|
var base = form.querySelector('[data-aksrt-product-base="' + mode + '"]');
|
|
var message = form.getAttribute(mode === 'points' ? 'data-vip-points-error' : 'data-vip-error');
|
|
form.querySelectorAll('[data-aksrt-product-vip="' + mode + '"]').forEach(function (input) {
|
|
var vip = amount(input.value, mode);
|
|
var limit = amount(base && base.value, mode);
|
|
input.setCustomValidity(!input.disabled && vip > limit ? message : '');
|
|
});
|
|
});
|
|
}
|
|
|
|
function serialize() {
|
|
return new URLSearchParams(new FormData(form)).toString();
|
|
}
|
|
|
|
function fill(payload) {
|
|
var fields = payload.fields || {};
|
|
form.reset();
|
|
form.elements.post_id.value = String(payload.id || '');
|
|
Object.keys(fields).forEach(function (key) {
|
|
var current = String(fields[key]);
|
|
field(key).forEach(function (node) {
|
|
if (node.type === 'radio') {
|
|
node.checked = node.value === current;
|
|
} else if (node.type === 'checkbox') {
|
|
node.checked = current === '1';
|
|
} else {
|
|
node.value = key === 'order_expired_time' && current === '0' ? '' : current;
|
|
}
|
|
});
|
|
});
|
|
subject.textContent = (payload.title || '') + ' · #' + String(payload.id || '');
|
|
editLink.hidden = !payload.editUrl;
|
|
editLink.href = payload.editUrl || '#';
|
|
syncPanes();
|
|
}
|
|
|
|
function lockScroll() {
|
|
document.documentElement.classList.toggle('aksrt-products-modal-open', dialog.open);
|
|
}
|
|
|
|
function requestClose() {
|
|
if (form.dataset.submitting) return;
|
|
if (serialize() !== snapshot && !window.confirm(form.getAttribute('data-discard'))) return;
|
|
dialog.close();
|
|
}
|
|
|
|
root.addEventListener('click', function (event) {
|
|
var button = event.target.closest('[data-aksrt-product-edit]');
|
|
if (!button || dialog.open) return;
|
|
var payload;
|
|
try {
|
|
payload = JSON.parse(button.getAttribute('data-aksrt-product-edit'));
|
|
} catch (error) {
|
|
return;
|
|
}
|
|
opener = button;
|
|
fill(payload);
|
|
snapshot = serialize();
|
|
dialog.showModal();
|
|
lockScroll();
|
|
});
|
|
|
|
form.addEventListener('change', syncPanes);
|
|
form.addEventListener('input', syncVip);
|
|
|
|
function isOutside(event) {
|
|
var rect = dialog.getBoundingClientRect();
|
|
return event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom;
|
|
}
|
|
|
|
dialog.addEventListener('pointerdown', function (event) {
|
|
pointerStartedOutside = event.target === dialog && isOutside(event);
|
|
});
|
|
dialog.addEventListener('click', function (event) {
|
|
if (event.target.closest('[data-aksrt-dialog-close]')
|
|
|| (pointerStartedOutside && event.target === dialog && isOutside(event))) {
|
|
event.preventDefault();
|
|
requestClose();
|
|
}
|
|
pointerStartedOutside = false;
|
|
});
|
|
dialog.addEventListener('cancel', function (event) {
|
|
event.preventDefault();
|
|
requestClose();
|
|
});
|
|
dialog.addEventListener('close', function () {
|
|
lockScroll();
|
|
if (opener && opener.isConnected) opener.focus();
|
|
opener = null;
|
|
});
|
|
|
|
form.addEventListener('submit', function (event) {
|
|
syncVip();
|
|
if (form.dataset.submitting || !form.checkValidity()) {
|
|
event.preventDefault();
|
|
form.reportValidity();
|
|
return;
|
|
}
|
|
form.dataset.submitting = '1';
|
|
submit.textContent = form.getAttribute('data-busy-label');
|
|
// 延后禁用:同步禁用 submit 按钮会让部分浏览器丢掉这次提交。
|
|
setTimeout(function () { submit.disabled = true; }, 0);
|
|
});
|
|
|
|
// bfcache 返回时恢复按钮,避免弹窗停在保存中状态。
|
|
window.addEventListener('pageshow', function () {
|
|
delete form.dataset.submitting;
|
|
submit.disabled = false;
|
|
submit.textContent = submitLabel;
|
|
lockScroll();
|
|
});
|
|
|
|
var highlighted = root.querySelector('.aksrt-products-table tr.is-highlight');
|
|
if (highlighted && typeof highlighted.scrollIntoView === 'function') {
|
|
highlighted.scrollIntoView({ block: 'center' });
|
|
}
|
|
})();
|