/** * AKSRT user badges — canonical JSON/runtime path. * * User-center and public-author triggers share one privacy-aware REST DTO, * short user-scoped runtime cache, and one in-modal list/detail renderer. * * @package AKSRT */ (function (window, document) { 'use strict'; var activeState = null; function escapeHtml(value) { return String(value == null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function toast(message, type) { window.AksrtToast(message, type || 'info'); } function api(path, options) { return window.AksrtRequest.request(path, options || {}).then(function (response) { return { ok: response.ok, status: response.status, json: response.json || {}, }; }).catch(function (error) { var response = error && error.response ? error.response : {}; return { ok: false, status: error && error.status ? error.status : (response.status || 0), json: error && error.json ? error.json : (response.json || { message: error && error.message }), }; }); } function iconHtml(item, className) { var icon = item && item.icon_url ? String(item.icon_url) : ''; var label = item && item.label ? String(item.label) : ''; if (icon) { return '' + escapeHtml(label) + ''; } return ''; } function emptyHtml() { return '
' + '
' + '
暂无徽章
' + '
积极参与社区互动即可获得徽章
' + '
'; } function findItem(state, slug) { var categories = state.data && Array.isArray(state.data.categories) ? state.data.categories : []; for (var i = 0; i < categories.length; i += 1) { var items = Array.isArray(categories[i].items) ? categories[i].items : []; for (var j = 0; j < items.length; j += 1) { if (String(items[j].slug || '') === String(slug || '')) return items[j]; } } return null; } function wornItem(state) { var wear = state.data && state.data.wear ? state.data.wear : ''; return wear ? findItem(state, wear) : null; } function cardHtml(item) { var classes = 'medal-card' + (item.owned ? ' is-has' : ' no-has') + (item.wear ? ' is-wear' : ''); return '
' + iconHtml(item) + '
' + escapeHtml(item.label) + '
' + (item.wear ? '
佩戴中
' : '') + '
'; } function listHtml(state) { var data = state.data || {}; var categories = Array.isArray(data.categories) ? data.categories : []; var worn = wornItem(state); var title = data.is_self ? '我的徽章' : '徽章明细'; var wearHtml = worn ? '' + iconHtml(worn) + '' + escapeHtml(worn.label) + '' : ''; var html = '
' + '
' + '' + '
' + escapeHtml(title) + '' + (Number(data.count || 0) > 0 ? '共 ' + Number(data.count) + ' 枚' : '') + '
' + '
' + wearHtml + '
' + '
'; if (!categories.length) return html + '
' + emptyHtml() + '
'; html += '
'; categories.forEach(function (category) { var items = Array.isArray(category.items) ? category.items : []; var cards = items.map(function (item) { return ''; }).join(''); html += '
' + escapeHtml(category.cat_name || '') + '
' + cards + '
'; }); return html + '
'; } function detailHtml(state) { var item = findItem(state, state.detailSlug); if (!item) return listHtml(state); var stateLine = ''; if (item.owned && item.time) { stateLine = '
' + escapeHtml(item.time + ' 获得') + '
'; } else if (!item.owned) { stateLine = '
暂未获得
'; } var action = ''; if (state.canWear && item.owned && !item.wear) { action = ''; } else if (item.wear) { action = '当前佩戴此徽章'; } return '
' + '
' + '
' + iconHtml(item) + '
' + escapeHtml(item.label) + '
' + (item.desc ? '
' + escapeHtml(item.desc) + '
' : '') + stateLine + '
' + (action ? '
' + action + '
' : '') + '
'; } function renderState(state) { return state.detailSlug ? detailHtml(state) : listHtml(state); } function rehydrate(content) { window.AksrtFrontend.rehydrate(content, 'modal'); } function renderInto(state) { if (!state.session || state.session.closed || !state.session.content) return; state.session.content.innerHTML = renderState(state); rehydrate(state.session.content); bindContent(state); } function syncHeaderIcon(state) { var worn = wornItem(state); if (!worn || !worn.icon_url) return; document.querySelectorAll('.aksrt-identity-medal.is-wear').forEach(function (node) { if (node.tagName === 'IMG') { node.src = worn.icon_url; node.alt = worn.label; node.title = worn.label; } }); } function wear(state, slug, button) { if (!state.canWear || state.wearing || !slug) return; state.wearing = true; state.wearController = window.AbortController ? new window.AbortController() : null; if (button) button.disabled = true; api('/users/me/badges/wear', { method: 'POST', body: JSON.stringify({ slug: slug }), signal: state.wearController ? state.wearController.signal : undefined, }).then(function (result) { if (state.session && state.session.closed) return; if (!result.ok) { toast((result.json && (result.json.message || (result.json.data && result.json.data.message))) || '佩戴失败', 'error'); if (button) button.disabled = false; return; } if (result.json && result.json.data) state.data = result.json.data; state.detailSlug = slug; window.AksrtModalRuntime.invalidate(['badges', 'identity', 'badges:user:' + state.userId]); toast((result.json && result.json.message) || '已佩戴徽章', 'success'); renderInto(state); syncHeaderIcon(state); }).catch(function (error) { if (error && error.name === 'AbortError') return; toast('网络错误', 'error'); if (button) button.disabled = false; }).finally(function () { state.wearing = false; state.wearController = null; }); } function bindContent(state) { if (!state.session || !state.session.content || state.session.content.dataset.aksrtBadgesBound === '1') return; state.session.content.dataset.aksrtBadgesBound = '1'; state.session.content.addEventListener('click', function (event) { var back = event.target.closest('[data-aksrt-badges-back]'); if (back) { event.preventDefault(); state.detailSlug = ''; renderInto(state); return; } var detail = event.target.closest('[data-aksrt-badge-detail]'); if (detail) { event.preventDefault(); state.detailSlug = detail.getAttribute('data-aksrt-badge-detail') || ''; renderInto(state); return; } var wearButton = event.target.closest('[data-aksrt-badge-wear]'); if (wearButton) { event.preventDefault(); wear(state, wearButton.getAttribute('data-aksrt-badge-wear') || '', wearButton); } }); } function openBadges(trigger) { var runtime = window.AksrtModalRuntime; if (!runtime.isRolloutEnabled("badge")) return; var userId = parseInt(trigger.getAttribute('data-aksrt-badges-user-id') || '0', 10); if (!userId) return; if (activeState && activeState.wearController) { try { activeState.wearController.abort(); } catch (_) {} } var state = { userId: userId, canWear: trigger.getAttribute('data-aksrt-badges-can-wear') === '1', data: null, detailSlug: '', session: null, wearing: false, wearController: null, }; activeState = state; runtime.openRemote({ url: '/users/' + userId + '/badges', kind: 'badges', title: '徽章', className: 'modal-mini full-sm aksrt-badges-modal-shell', mobileBottom: true, skeleton: '
', cacheTtl: 12000, cacheTags: ['badges', 'identity', 'badges:user:' + userId], responseType: 'json', renderData: function (data, session) { state.session = session; state.data = data || {}; state.canWear = state.canWear && state.data.can_wear === true; session.content.innerHTML = renderState(state); }, onData: function (data, session) { state.session = session; state.data = data || state.data; state.canWear = state.canWear && state.data.can_wear === true; bindContent(state); syncHeaderIcon(state); }, }); } document.addEventListener('aksrt:modal:close', function (event) { if (!activeState || !event.detail || event.detail.session !== activeState.session) return; if (activeState.wearController) { try { activeState.wearController.abort(); } catch (_) {} } activeState = null; }); function bindOnce() { if (window.__aksrtBadgesBound) return; window.__aksrtBadgesBound = true; document.addEventListener('click', function (event) { var trigger = event.target.closest('[data-aksrt-badges-open]'); if (!trigger) return; event.preventDefault(); openBadges(trigger); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bindOnce, { once: true }); } else { bindOnce(); } })(window, document);