From ed19937e99bff033ef64ef60594a78ec677b47e5 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 4 Aug 2026 12:46:44 +0800 Subject: [PATCH] feat(badges): zibll-style badge modal with category grid, detail view, and wear UX --- assets/css/frontend/medal.css | 352 ++++++++++++++++++ assets/css/frontend/widget-batch5.css | 44 +-- assets/css/routes/user-center/credits.css | 8 +- assets/js/user-badges.js | 343 +++++++++++++---- .../user-center-renderers/profile-header.php | 22 +- src/Core/ScriptsPart1b.php | 1 + src/Settings/UserRuntime.php | 3 +- src/User/Badges.php | 338 ++++++++++++----- src/User/Home.php | 39 +- 9 files changed, 904 insertions(+), 246 deletions(-) create mode 100644 assets/css/frontend/medal.css diff --git a/assets/css/frontend/medal.css b/assets/css/frontend/medal.css new file mode 100644 index 00000000..465021db --- /dev/null +++ b/assets/css/frontend/medal.css @@ -0,0 +1,352 @@ +/* ============================================================ + * 徽章弹窗(zibll medal 1:1 风格) + * ------------------------------------------------------------ + * 共享于两个入口: + * 1. 用户中心「我的徽章」弹窗(.uc-badges-modal,JS 渲染) + * 2. 远程弹窗徽章明细/单卡(.medal-modal,PHP 渲染) + * 加载条件与 user-badges.js 一致(badge surface 页面)。 + * ============================================================ */ + +/* ---------- 分类分组(zibll .medal-cat-box / .medal-cat-name) ---------- */ +.medal-cat-box { + position: relative; + text-align: center; + padding: 8px 8px 4px; + margin-bottom: 16px; + background: var(--muted-border-color, rgba(136, 136, 136, 0.06)); + border-radius: var(--main-radius, 8px); +} +.medal-cat-box:last-child { margin-bottom: 0; } + +.medal-cat-head { margin-bottom: 6px; } + +.medal-cat-name { + display: inline-block; + margin-top: -8px; + padding: 6px 22px; + font-size: 13px; + font-weight: 600; + color: var(--focus-color); + background: var(--focus-color-opacity1); + border-radius: 0 0 1000px 1000px; +} + +/* ---------- 卡片网格(zibll .medal-list-box / .medal-list) ---------- */ +.medal-list-box { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 8px 4px; +} + +.medal-list { + display: block; + min-width: 0; + padding: 0; + border: 0; + background: none; + font: inherit; + color: inherit; + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} + +.medal-card { + position: relative; + text-align: center; + padding: 8px 4px 6px; + border-radius: var(--mini-radius, 6px); + transition: transform 0.15s ease, background-color 0.15s ease; +} +.medal-list:hover .medal-card, +.medal-list:focus-visible .medal-card { + transform: translateY(-2px); + background: var(--focus-color-opacity1); +} +.medal-list:focus-visible { outline: 2px solid var(--focus-color); outline-offset: 2px; border-radius: var(--mini-radius, 6px); } + +.medal-card .medal-icon { + width: auto; + height: 70px; + max-width: 100%; + object-fit: contain; +} +.medal-card .medal-icon--fa { + display: inline-flex; + align-items: center; + justify-content: center; + width: 56px; + height: 70px; + font-size: 34px; + color: var(--focus-color); +} + +/* 未获得:灰度 + 透明(zibll .no-has) */ +.medal-card.no-has { + -webkit-filter: grayscale(100%); + filter: grayscale(100%); + opacity: 0.6; +} + +.medal-card__name { + margin-top: 6px; + font-size: 12px; + line-height: 1.4; + color: var(--muted-color, var(--color-ink-3)); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.medal-card.is-has .medal-card__name { color: var(--key-color, var(--color-ink)); } + +.medal-card__wear { + margin-top: 2px; + font-size: 11px; + line-height: 1.3; + color: var(--focus-color); +} + +/* ---------- 单徽章详情卡(zibll .medal-single-card) ---------- */ +.medal-single-card { + position: relative; + overflow: hidden; + text-align: center; + padding: 22px 14px 18px; + margin-bottom: 14px; + background: var(--muted-border-color, rgba(136, 136, 136, 0.06)); + border-radius: var(--main-radius, 8px); +} +.medal-single-bg { + position: absolute; + top: 43px; + left: 0; + right: 0; + bottom: 0; + opacity: 0.5; + pointer-events: none; +} +.medal-single-bg img { + width: 100%; + height: 100%; + object-fit: cover; +} +.medal-single-main { position: relative; } + +.medal-single-card .medal-icon { + width: auto; + height: 120px; + max-width: 200px; + object-fit: contain; +} +.medal-single-card .medal-icon--fa { + display: inline-flex; + align-items: center; + justify-content: center; + height: 120px; + font-size: 72px; + color: var(--focus-color); +} + +.medal-single-name { + margin-top: 12px; + font-size: 16px; + font-weight: 700; +} +.medal-single-desc { + margin-top: 8px; + font-size: 12px; + line-height: 1.6; +} +.medal-single-state { + margin-top: 10px; + font-size: 12px; +} + +/* ---------- 弹窗骨架(远程弹窗 .medal-modal) ---------- */ +.medal-modal { position: relative; padding: 6px 4px 12px; } +.medal-modal__empty { + padding: 24px 0; + text-align: center; + color: var(--muted-2-color, var(--color-ink-4)); +} + +.medal-modal__head { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; + padding-right: 28px; +} +.medal-modal__avatar { + width: 42px; + height: 42px; + border-radius: 50%; + object-fit: cover; + box-shadow: 0 0 0 2px var(--focus-color-opacity1); +} +.medal-modal__title { + display: flex; + align-items: center; + flex-wrap: wrap; + min-width: 0; + font-size: 15px; + font-weight: 700; +} +.medal-modal__title .medal-icon { + width: auto; + height: 1.6em; + margin-left: 4px; + object-fit: contain; +} + +.medal-modal__scroll { + max-height: 50vh; + max-height: 50dvh; + overflow-y: auto; + padding: 2px; +} + +/* 底部操作区(zibll .modal-buts.but-average 语义) */ +.medal-modal__buts { + display: flex; + margin: 12px -4px -12px; + border-top: 1px solid var(--main-border-color, var(--color-line)); +} +.medal-modal__buts > .but { + flex: 1; + padding: 0.7em; + background: transparent; + border-radius: 0; +} +.medal-modal__buts > .but + .but { border-left: 1px solid var(--main-border-color, var(--color-line)); } +.medal-modal__buts > .but:hover { background: var(--focus-color-opacity05); opacity: 1; } + +/* 单卡弹窗 */ +.medal-modal--single { padding: 10px 8px 14px; text-align: center; } +.medal-modal__actions { margin-top: 4px; } +.medal-modal__actions .but.btn-block, +.medal-modal__actions .badg.btn-block { display: block; width: 100%; } +.medal-modal__back { + display: inline-block; + margin-top: 12px; + text-decoration: none; +} +.medal-modal__back:hover { color: var(--focus-color); } + +/* ---------- 用户中心「我的徽章」弹窗 ---------- */ +.uc-badges-dialog { + max-width: 560px; + padding: 0; + overflow: hidden; +} + +.uc-badges-head { + display: flex; + align-items: center; + gap: 10px; + padding: 16px 46px 12px 18px; + border-bottom: 1px solid var(--main-border-color, var(--color-line)); +} +.uc-badges-head__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + flex: none; + font-size: 16px; + color: var(--focus-color); + background: var(--focus-color-opacity1); + border-radius: 10px; +} +.uc-badges-head__title { + flex: 1; + min-width: 0; + font-size: 16px; + font-weight: 700; +} +.uc-badges-head__count { + margin-left: 6px; + font-size: 12px; + font-weight: 400; + color: var(--muted-2-color, var(--color-ink-4)); +} +.uc-badges-head__wear { flex: none; } +.uc-badges-head__wear-tag { + display: inline-flex; + align-items: center; + gap: 5px; + max-width: 140px; + padding: 3px 10px; + font-size: 12px; + color: var(--focus-color); + background: var(--focus-color-opacity1); + border-radius: 100px; +} +.uc-badges-head__wear-tag .medal-icon { + width: auto; + height: 18px; + object-fit: contain; +} +.uc-badges-head__wear-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.uc-badges-body { + max-height: 56vh; + max-height: 56dvh; + overflow-y: auto; + padding: 16px 14px 18px; +} +.uc-badges-loading, +.uc-badges-error { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + padding: 32px 0; +} + +/* 详情视图 */ +.uc-badges-view--detail { padding: 8px 16px 18px; } +.uc-badges-detail__head { margin-bottom: 6px; } +.uc-badges-back { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px 4px 6px; + border: 0; + background: none; + font: inherit; + font-size: 13px; + color: var(--muted-2-color, var(--color-ink-4)); + cursor: pointer; + border-radius: var(--mini-radius, 6px); + transition: color 0.15s ease, background-color 0.15s ease; +} +.uc-badges-back:hover { + color: var(--focus-color); + background: var(--focus-color-opacity1); +} + +/* ---------- 细滚动条(zibll .mini-scrollbar) ---------- */ +.mini-scrollbar { scrollbar-width: thin; scrollbar-color: var(--muted-border-color, rgba(136, 136, 136, 0.3)) transparent; } +.mini-scrollbar::-webkit-scrollbar { width: 5px; height: 5px; } +.mini-scrollbar::-webkit-scrollbar-thumb { background: var(--muted-border-color, rgba(136, 136, 136, 0.3)); border-radius: 10px; } +.mini-scrollbar::-webkit-scrollbar-track { background: transparent; } + +/* ---------- 响应式(zibll: 桌面 5 列 / 移动 4 列) ---------- */ +@media (max-width: 768px) { + .medal-list-box { grid-template-columns: repeat(4, 1fr); } + .medal-card .medal-icon { height: 55px; } + .medal-card .medal-icon--fa { width: 44px; height: 55px; font-size: 26px; } + .medal-single-card .medal-icon { height: 96px; } + .uc-badges-dialog { max-width: 100%; } + .uc-badges-body { max-height: 62vh; max-height: 62dvh; } +} + +@media (max-width: 400px) { + .medal-list-box { grid-template-columns: repeat(3, 1fr); } +} diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css index cdca0993..f320be8e 100644 --- a/assets/css/frontend/widget-batch5.css +++ b/assets/css/frontend/widget-batch5.css @@ -738,49 +738,7 @@ } .aksrt-author-card__badge img { border-radius: 50%; } -/* Badge remote modal (batch6) */ -.aksrt-badge-modal { padding: 8px 4px 12px; } -.aksrt-badge-modal__head { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 12px; - position: relative; - padding-right: 28px; -} -.aksrt-badge-modal__avatar { - width: 40px; height: 40px; border-radius: 50%; object-fit: cover; -} -.aksrt-badge-modal__title { font-weight: 700; font-size: 15px; flex: 1; min-width: 0; } -.aksrt-badge-modal__wear-tag { - display: inline-block; - margin-left: 6px; - font-size: 12px; - font-weight: 500; - color: var(--color-primary, #3b82f6); -} -.aksrt-badge-modal__list { max-height: 50vh; overflow: auto; } -.aksrt-badge-modal__item { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 4px; - border-bottom: 1px solid var(--color-line, #eee); -} -.aksrt-badge-modal__item:last-child { border-bottom: 0; } -.aksrt-badge-modal__item .medal-icon { width: 40px; height: 40px; object-fit: contain; } -.aksrt-badge-modal__icon-fa { - width: 40px; height: 40px; border-radius: 50%; - display: inline-flex; align-items: center; justify-content: center; - background: var(--surface-2, #f3f4f6); color: #f59e0b; -} -.aksrt-badge-modal__icon-fa.lg { width: 72px; height: 72px; font-size: 28px; } -.aksrt-badge-modal__meta { flex: 1; min-width: 0; } -.aksrt-badge-modal__name { font-weight: 600; font-size: 14px; } -.aksrt-badge-modal__empty { text-align: center; color: var(--muted-2-color, #999); padding: 24px 0; } -.aksrt-badge-modal--single { text-align: center; padding: 16px 8px 20px; } -.aksrt-badge-modal__card { padding: 12px 8px 8px; } -.aksrt-badge-modal__actions { margin-top: 12px; } +/* 徽章远程弹窗样式已迁至 assets/css/frontend/medal.css(zibll medal 风格)。 */ .aksrt-author-hero__badges { margin-top: 8px; } /* Forum tab topics (batch6) */ diff --git a/assets/css/routes/user-center/credits.css b/assets/css/routes/user-center/credits.css index 500647f9..57c41ac3 100644 --- a/assets/css/routes/user-center/credits.css +++ b/assets/css/routes/user-center/credits.css @@ -1023,13 +1023,7 @@ body.uc-modal-open { } .focus-color { color: var(--focus-color, #f59e0b); } -/* User badges wear modal */ -.uc-badges-list { max-height: 60vh; overflow: auto; } -.uc-badges-item { gap: 12px; } -.uc-badges-item .medal-icon { width: 28px; height: 28px; object-fit: contain; } -.uc-badges-wear-tag { margin-left: 6px; } -.uc-badges-empty { padding: 16px 0; text-align: center; } - +/* 用户中心「我的徽章」弹窗样式已迁至 assets/css/frontend/medal.css(zibll medal 风格)。 */ .uc-points-tasks__row.is-done { opacity: .72; } .uc-points-tasks__progress-bar { diff --git a/assets/js/user-badges.js b/assets/js/user-badges.js index c508668d..193b9e8d 100644 --- a/assets/js/user-badges.js +++ b/assets/js/user-badges.js @@ -1,5 +1,9 @@ /** - * AKSRT user badges — wear modal. + * AKSRT user badges — zibll medal UX parity. + * + * User-center modal: category-grouped medal card grid + in-modal detail + * view (medal-background card, obtain time, wear action). Also provides the + * global wear handler used inside remote (AJAX HTML) badge modals. */ (function () { 'use strict'; @@ -45,40 +49,36 @@ .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 = '
' + escapeHtml('暂无徽章') + '
'; - return; + /* ---------- shared partials ---------- */ + + function iconHtml(item, cls) { + var icon = item && item.icon_url ? String(item.icon_url) : ''; + var label = item && item.label ? String(item.label) : ''; + if (icon) { + return '' + escapeHtml(label) + ''; } - body.innerHTML = items.map(function (item) { - var icon = item.icon_url - ? '' - : ' ' + escapeHtml(item.label) + ''; - var wearBadge = item.wear ? '佩戴中' : ''; - var action = ''; - if (canWear && !item.wear) { - action = ''; - } else if (item.wear) { - action = '当前佩戴'; - } - return '
' + - '
' + icon + '
' + escapeHtml(item.label) + '
' + wearBadge + '
' + - '
' + action + '
'; - }).join(''); + return ''; } - function openModal(root) { - root.hidden = false; - document.body.classList.add('uc-modal-open'); + function emptyHtml(text, desc) { + return '
' + + '
' + + '
' + escapeHtml(text) + '
' + + (desc ? '
' + escapeHtml(desc) + '
' : '') + + '
'; } - function closeModal(root) { - root.hidden = true; - document.body.classList.remove('uc-modal-open'); + + function cardHtml(item) { + var cls = 'medal-card' + (item.owned ? ' is-has' : ' no-has') + (item.wear ? ' is-wear' : ''); + return '
' + + iconHtml(item) + + '
' + escapeHtml(item.label) + '
' + + (item.wear ? '
' + escapeHtml('佩戴中') + '
' : '') + + '
'; } + /* ---------- user-center modal ---------- */ + function init() { var openers = document.querySelectorAll('[data-aksrt-badges-open]'); var modal = document.querySelector('[data-aksrt-badges-modal]'); @@ -86,24 +86,193 @@ var userId = parseInt(modal.getAttribute('data-user-id') || '0', 10); var canWear = modal.getAttribute('data-can-wear') === '1'; + var medalBg = modal.getAttribute('data-medal-bg') || ''; + var body = modal.querySelector('[data-aksrt-badges-body]'); + var detailBox = modal.querySelector('[data-aksrt-badges-detail]'); + var countSlot = modal.querySelector('[data-aksrt-badges-count]'); + var wearSlot = modal.querySelector('[data-aksrt-badges-wear-slot]'); - function load() { - if (!userId) return Promise.resolve(); - var body = modal.querySelector('[data-aksrt-badges-body]'); - if (body) body.innerHTML = '
加载中…
'; - return api('/users/' + userId + '/badges').then(function (r) { - if (!r.ok || !r.json || !r.json.data) { - if (body) body.innerHTML = '
加载失败
'; - return; + var state = { data: null, detailSlug: '' }; + + function findItem(slug) { + var cats = (state.data && state.data.categories) || []; + for (var i = 0; i < cats.length; i++) { + var items = cats[i].items || []; + for (var j = 0; j < items.length; j++) { + if (items[j].slug === slug) return items[j]; + } + } + return null; + } + + function wornItem() { + var wear = state.data && state.data.wear; + return wear ? findItem(wear) : null; + } + + /* ----- view switching ----- */ + function showView(name) { + modal.querySelectorAll('[data-aksrt-badges-view]').forEach(function (view) { + view.hidden = view.getAttribute('data-aksrt-badges-view') !== name; + }); + if (name === 'grid') state.detailSlug = ''; + } + + function openModal() { + modal.hidden = false; + document.body.classList.add('uc-modal-open'); + } + function closeModal() { + modal.hidden = true; + document.body.classList.remove('uc-modal-open'); + showView('grid'); + } + + /* ----- render: head ----- */ + function renderHead() { + var count = state.data && typeof state.data.count === 'number' ? state.data.count : 0; + if (countSlot) { + countSlot.textContent = count > 0 ? '共 ' + count + ' 枚' : ''; + } + if (!wearSlot) return; + var worn = wornItem(); + wearSlot.innerHTML = worn + ? '' + iconHtml(worn) + '' + escapeHtml(worn.label) + '' + : ''; + } + + /* ----- render: grid ----- */ + function renderGrid() { + if (!body) return; + var cats = (state.data && state.data.categories) || []; + if (!cats.length) { + body.innerHTML = emptyHtml('暂无徽章', '积极参与社区互动即可获得徽章'); + return; + } + body.innerHTML = cats.map(function (cat) { + var cards = (cat.items || []).map(function (item) { + return ''; + }).join(''); + return '
' + + '
' + escapeHtml(cat.cat_name || '') + '
' + + '
' + cards + '
' + + '
'; + }).join(''); + } + + /* ----- render: detail ----- */ + function renderDetail(slug) { + var item = findItem(slug); + if (!item || !detailBox) return; + state.detailSlug = slug; + + var stateLine = ''; + if (item.owned && item.time) { + stateLine = '
' + escapeHtml(item.time + ' 获得') + '
'; + } else if (!item.owned) { + stateLine = '
' + escapeHtml('暂未获得') + '
'; + } + + var action = ''; + if (canWear && item.owned && !item.wear) { + action = ''; + } else if (item.wear) { + action = '' + escapeHtml('当前佩戴此徽章') + ''; + } + + detailBox.innerHTML = + '
' + + '' + + '
' + + '
' + + (medalBg ? '
' : '') + + '
' + + iconHtml(item) + + '
' + escapeHtml(item.label) + '
' + + (item.desc ? '
' + escapeHtml(item.desc) + '
' : '') + + stateLine + + '
' + + '
' + + (action ? '
' + action + '
' : ''); + + showView('detail'); + } + + /* ----- header worn icon sync (profile header, next to the name) ----- */ + function syncHeaderIcon() { + var worn = wornItem(); + 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; + } else { + var img = document.createElement('img'); + img.className = 'medal-icon img-icon ml3 aksrt-identity-medal is-wear'; + img.src = worn.icon_url; + img.alt = worn.label; + img.title = worn.label; + img.setAttribute('loading', 'lazy'); + img.setAttribute('decoding', 'async'); + node.parentNode.replaceChild(img, node); } - renderList(modal, r.json.data, canWear); }); } + /* ----- data ----- */ + function load() { + if (!userId) return Promise.resolve(); + if (body) { + body.innerHTML = '
' + escapeHtml('加载中…') + '
'; + } + return api('/users/' + userId + '/badges').then(function (r) { + if (!r.ok || !r.json || !r.json.data) { + if (body) { + body.innerHTML = '
' + + '' + escapeHtml('加载失败') + '' + + '' + + '
'; + } + return; + } + state.data = r.json.data; + renderHead(); + renderGrid(); + }); + } + + function wear(slug, btn) { + btn.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'); + btn.disabled = false; + return; + } + toast((r.json && r.json.message) || '已佩戴徽章', 'success'); + state.data = (r.json && r.json.data) || state.data; + renderHead(); + renderGrid(); + renderDetail(slug); + syncHeaderIcon(); + }).catch(function () { + toast('网络错误', 'error'); + btn.disabled = false; + }); + } + + /* ----- events ----- */ openers.forEach(function (btn) { btn.addEventListener('click', function (e) { e.preventDefault(); - openModal(modal); + openModal(); + showView('grid'); load(); }); }); @@ -111,62 +280,92 @@ modal.querySelectorAll('[data-aksrt-badges-close]').forEach(function (btn) { btn.addEventListener('click', function (e) { e.preventDefault(); - closeModal(modal); + closeModal(); }); }); + document.addEventListener('keydown', function (e) { + if (e.key !== 'Escape' || modal.hidden) return; + var detailView = modal.querySelector('[data-aksrt-badges-view="detail"]'); + // Detail view open: ESC steps back to the grid first. + if (detailView && !detailView.hidden) { + showView('grid'); + return; + } + closeModal(); + }); + modal.addEventListener('click', function (e) { + var back = e.target.closest('[data-aksrt-badges-back]'); + if (back) { + e.preventDefault(); + showView('grid'); + return; + } + var retry = e.target.closest('[data-aksrt-badges-retry]'); + if (retry) { + e.preventDefault(); + load(); + return; + } 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 (wearBtn && canWear) { + e.preventDefault(); + wear(wearBtn.getAttribute('data-aksrt-badge-wear') || '', wearBtn); + return; + } + var card = e.target.closest('[data-aksrt-badge-detail]'); + if (card) { + e.preventDefault(); + renderDetail(card.getAttribute('data-aksrt-badge-detail') || ''); + } }); } - - // Global wear handler (remote modal + UC modal). - document.addEventListener("click", function (e) { - var wearBtn = e.target.closest("[data-aksrt-badge-wear]"); + /* ---------- remote modal: global wear handler ---------- */ + document.addEventListener('click', function (e) { + var wearBtn = e.target.closest('[data-aksrt-badge-wear]'); if (!wearBtn) return; // UC modal has its own handler when canWear on modal root. - var modalRoot = wearBtn.closest("[data-aksrt-badges-modal]"); + var modalRoot = wearBtn.closest('[data-aksrt-badges-modal]'); if (modalRoot) return; e.preventDefault(); - var slug = wearBtn.getAttribute("data-aksrt-badge-wear") || ""; + var slug = wearBtn.getAttribute('data-aksrt-badge-wear') || ''; if (!slug) return; wearBtn.disabled = true; - api("/users/me/badges/wear", { - method: "POST", + 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"); + 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"); - // Soft refresh modal content if remote host present. + toast((r.json && r.json.message) || '已佩戴徽章', 'success'); + // Re-open the refreshed single-badge modal so state stays in sync. + var refreshed = null; try { - document.dispatchEvent(new CustomEvent("aksrt:remote-modal:close")); + var uid = parseInt((r.json && r.json.data && r.json.data.user_id) || '0', 10); + var data = r.json && r.json.data; + var isSelf = data && data.is_self; + if (isSelf && data.wear) { + refreshed = (window.aksrtData && aksrtData.ajaxUrl) + ? aksrtData.ajaxUrl + '?action=aksrt_single_badge_info_modal&name=' + encodeURIComponent(data.wear) + '&user_id=' + (uid || 0) + : null; + } } catch (_) {} + try { + if (refreshed && window.AksrtRemoteModal && typeof window.AksrtRemoteModal.openRemote === 'function') { + window.AksrtRemoteModal.openRemote({ url: refreshed, className: 'modal-mini', mobileBottom: true }); + } else { + document.dispatchEvent(new CustomEvent('aksrt:remote-modal:close')); + } + } catch (_) { + try { document.dispatchEvent(new CustomEvent('aksrt:remote-modal:close')); } catch (__) {} + } }).catch(function () { - toast("网络错误", "error"); + toast('网络错误', 'error'); wearBtn.disabled = false; }); }); diff --git a/inc/helpers/user-center-renderers/profile-header.php b/inc/helpers/user-center-renderers/profile-header.php index 4244b536..3f2a90a9 100644 --- a/inc/helpers/user-center-renderers/profile-header.php +++ b/inc/helpers/user-center-renderers/profile-header.php @@ -108,13 +108,25 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ -