feat(identity): author card, per-user tip, comment caps, task status
Author card uses configurable limit/orderby/style and self CTAs. Tip is per-user weixin/alipay QR modal (no site-global image). Comment action permissions honor user_caps matrix. Free points tasks expose done_today/passive status + better comment deep link. audit-admin-contracts errors=0.
This commit is contained in:
1 parent
b4d04ae346
commit
8d4e94a02c
10 files changed
+374
-148
No files matched your search
+28
-74
@@ -1,8 +1,5 @@
|
||||
/**
|
||||
* Reward (打赏) — dedicated module.
|
||||
*
|
||||
* Handles the reward modal: amount selection, custom amount input,
|
||||
* QR code display, and open/close interaction.
|
||||
* Reward (打赏) — per-user QR modal openers.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
@@ -10,95 +7,52 @@
|
||||
'use strict';
|
||||
|
||||
function initReward() {
|
||||
var btn = document.getElementById('action-reward');
|
||||
if (!btn) return;
|
||||
function closeAll() {
|
||||
document.querySelectorAll('.reward-modal.is-open').forEach(function (m) {
|
||||
m.classList.remove('is-open');
|
||||
m.setAttribute('aria-hidden', 'true');
|
||||
});
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
var modal = document.getElementById('reward-modal');
|
||||
if (!modal) return;
|
||||
|
||||
var closeBtn = modal.querySelector('.reward-modal__close');
|
||||
var overlay = modal.querySelector('.reward-modal__overlay');
|
||||
var amountBtns = modal.querySelectorAll('.reward-amounts__btn');
|
||||
var customWrap = modal.querySelector('.reward-custom');
|
||||
var customInput = modal ? modal.querySelector('.reward-custom__input') : null;
|
||||
var customConfirm = modal ? modal.querySelector('.reward-custom__confirm') : null;
|
||||
var selectedAmount = 0;
|
||||
|
||||
/* ---- open / close ---- */
|
||||
function openReward() {
|
||||
function openForUser(userId) {
|
||||
var id = String(userId || '');
|
||||
var modal = id
|
||||
? document.getElementById('reward-modal-' + id)
|
||||
: document.querySelector('.reward-modal');
|
||||
if (!modal) return;
|
||||
closeAll();
|
||||
modal.classList.add('is-open');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeReward() {
|
||||
modal.classList.remove('is-open');
|
||||
document.body.style.overflow = '';
|
||||
// reset selection
|
||||
selectedAmount = 0;
|
||||
amountBtns.forEach(function (b) { b.classList.remove('is-active'); });
|
||||
if (customInput) customInput.value = '';
|
||||
}
|
||||
|
||||
btn.addEventListener('click', openReward);
|
||||
document.querySelectorAll('[data-aksrt-reward-open]').forEach(function (el) {
|
||||
if (el === btn) return;
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
openReward();
|
||||
openForUser(el.getAttribute('data-user-id') || '');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-aksrt-reward-close]').forEach(function (el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
closeAll();
|
||||
});
|
||||
});
|
||||
if (closeBtn) closeBtn.addEventListener('click', closeReward);
|
||||
if (overlay) overlay.addEventListener('click', closeReward);
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape' && modal.classList.contains('is-open')) closeReward();
|
||||
if (e.key === 'Escape') closeAll();
|
||||
});
|
||||
|
||||
/* ---- amount selection ---- */
|
||||
amountBtns.forEach(function (b) {
|
||||
b.addEventListener('click', function () {
|
||||
amountBtns.forEach(function (x) { x.classList.remove('is-active'); });
|
||||
b.classList.add('is-active');
|
||||
selectedAmount = parseInt(b.dataset.amount || '0', 10);
|
||||
// clear custom input when preset is chosen
|
||||
if (customInput) customInput.value = '';
|
||||
});
|
||||
});
|
||||
|
||||
/* ---- custom amount ---- */
|
||||
if (customInput) {
|
||||
customInput.addEventListener('focus', function () {
|
||||
amountBtns.forEach(function (x) { x.classList.remove('is-active'); });
|
||||
selectedAmount = 0;
|
||||
});
|
||||
customInput.addEventListener('input', function () {
|
||||
// strip non-digits
|
||||
customInput.value = customInput.value.replace(/[^\d]/g, '');
|
||||
});
|
||||
}
|
||||
|
||||
if (customConfirm) {
|
||||
customConfirm.addEventListener('click', function () {
|
||||
var val = customInput ? parseInt(customInput.value, 10) : 0;
|
||||
if (val > 0) {
|
||||
selectedAmount = val;
|
||||
amountBtns.forEach(function (x) { x.classList.remove('is-active'); });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- register with AksrtFrontend lifecycle ---- */
|
||||
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') {
|
||||
window.AksrtFrontend.register('aksrt-reward', function () {
|
||||
initReward();
|
||||
});
|
||||
} else if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initReward);
|
||||
} else {
|
||||
// fallback: run on DOMContentLoaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initReward);
|
||||
} else {
|
||||
initReward();
|
||||
}
|
||||
initReward();
|
||||
}
|
||||
})();
|
||||
Reference in new issue
Block a user