feat(user): wear badge modal and user-center entry
Expose 我的徽章 modal on profile header with REST wear actions and update SPA badge help for icon_url + grant/wear flows.
This commit is contained in:
1 parent
219cd0236e
commit
de32c9ad35
5 files changed
+188
-4
No files matched your search
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* AKSRT user badges — wear modal.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var aksrt = window.aksrtData || {};
|
||||
var siteUrl = aksrt.siteUrl || '';
|
||||
if (window.location.protocol === 'https:' && siteUrl.substring(0, 5) === 'http:') {
|
||||
siteUrl = 'https:' + siteUrl.substring(5);
|
||||
}
|
||||
var REST = (siteUrl || '').replace(/\/$/, '') + '/wp-json/aksrt/v1';
|
||||
var NONCE = aksrt.restNonce || aksrt.nonce || '';
|
||||
|
||||
function api(path, options) {
|
||||
var opts = Object.assign({
|
||||
credentials: 'same-origin',
|
||||
headers: Object.assign({ 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE }, (options && options.headers) || {}),
|
||||
}, options || {});
|
||||
return fetch(REST + path, opts).then(function (r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (j) {
|
||||
return { ok: r.ok, status: r.status, json: j };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toast(msg, type) {
|
||||
if (window.AksrtToast && typeof window.AksrtToast.show === 'function') {
|
||||
window.AksrtToast.show(msg, type || 'info');
|
||||
return;
|
||||
}
|
||||
if (window.aksrtNotify) {
|
||||
window.aksrtNotify(msg, type || 'info');
|
||||
return;
|
||||
}
|
||||
// fallback
|
||||
try { console.log('[AKSRT badges]', msg); } catch (_) {}
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s == null ? '' : s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function renderList(root, data, canWear) {
|
||||
var body = root.querySelector('[data-aksrt-badges-body]');
|
||||
if (!body) return;
|
||||
var items = (data && data.items) || [];
|
||||
if (!items.length) {
|
||||
body.innerHTML = '<div class="uc-badges-empty muted-2-color em09">' + escapeHtml('暂无徽章') + '</div>';
|
||||
return;
|
||||
}
|
||||
body.innerHTML = items.map(function (item) {
|
||||
var icon = item.icon_url
|
||||
? '<img class="medal-icon img-icon" src="' + escapeHtml(item.icon_url) + '" alt="">'
|
||||
: '<span class="badg badg-sm aksrt-identity-chip aksrt-identity-chip--' + escapeHtml(item.tone || 'neutral') + '"><i class="fa fa-award"></i> ' + escapeHtml(item.label) + '</span>';
|
||||
var wearBadge = item.wear ? '<span class="uc-badges-wear-tag c-yellow em09">佩戴中</span>' : '';
|
||||
var action = '';
|
||||
if (canWear && !item.wear) {
|
||||
action = '<button type="button" class="but jb-blue but-sm" data-aksrt-badge-wear="' + escapeHtml(item.slug) + '">佩戴此徽章</button>';
|
||||
} else if (item.wear) {
|
||||
action = '<span class="muted-2-color em09">当前佩戴</span>';
|
||||
}
|
||||
return '<div class="uc-badges-item flex jsb ac border-bottom padding-h10" data-slug="' + escapeHtml(item.slug) + '">' +
|
||||
'<div class="flex ac gap8">' + icon + '<div><div class="font-bold">' + escapeHtml(item.label) + '</div>' + wearBadge + '</div></div>' +
|
||||
'<div class="shrink0">' + action + '</div></div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function openModal(root) {
|
||||
root.hidden = false;
|
||||
document.body.classList.add('uc-modal-open');
|
||||
}
|
||||
function closeModal(root) {
|
||||
root.hidden = true;
|
||||
document.body.classList.remove('uc-modal-open');
|
||||
}
|
||||
|
||||
function init() {
|
||||
var openers = document.querySelectorAll('[data-aksrt-badges-open]');
|
||||
var modal = document.querySelector('[data-aksrt-badges-modal]');
|
||||
if (!modal || !openers.length) return;
|
||||
|
||||
var userId = parseInt(modal.getAttribute('data-user-id') || '0', 10);
|
||||
var canWear = modal.getAttribute('data-can-wear') === '1';
|
||||
|
||||
function load() {
|
||||
if (!userId) return Promise.resolve();
|
||||
var body = modal.querySelector('[data-aksrt-badges-body]');
|
||||
if (body) body.innerHTML = '<div class="muted-2-color em09">加载中…</div>';
|
||||
return api('/users/' + userId + '/badges').then(function (r) {
|
||||
if (!r.ok || !r.json || !r.json.data) {
|
||||
if (body) body.innerHTML = '<div class="c-red em09">加载失败</div>';
|
||||
return;
|
||||
}
|
||||
renderList(modal, r.json.data, canWear);
|
||||
});
|
||||
}
|
||||
|
||||
openers.forEach(function (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
openModal(modal);
|
||||
load();
|
||||
});
|
||||
});
|
||||
|
||||
modal.querySelectorAll('[data-aksrt-badges-close]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
closeModal(modal);
|
||||
});
|
||||
});
|
||||
|
||||
modal.addEventListener('click', function (e) {
|
||||
var wearBtn = e.target.closest('[data-aksrt-badge-wear]');
|
||||
if (!wearBtn || !canWear) return;
|
||||
e.preventDefault();
|
||||
var slug = wearBtn.getAttribute('data-aksrt-badge-wear') || '';
|
||||
wearBtn.disabled = true;
|
||||
api('/users/me/badges/wear', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ slug: slug }),
|
||||
}).then(function (r) {
|
||||
if (!r.ok) {
|
||||
toast((r.json && (r.json.message || (r.json.data && r.json.data.message))) || '佩戴失败', 'error');
|
||||
wearBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
toast((r.json && r.json.message) || '已佩戴徽章', 'success');
|
||||
renderList(modal, (r.json && r.json.data) || {}, canWear);
|
||||
}).catch(function () {
|
||||
toast('网络错误', 'error');
|
||||
wearBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in new issue
Block a user