This commit is contained in:
zhx 2026-06-26 18:15:00 +08:00
parent a0aa3af3d7
commit 1efd1b779c
2 changed files with 211 additions and 48 deletions

View File

@ -116,6 +116,7 @@
position: absolute; position: absolute;
left: 36px; left: 36px;
top: 36px; top: 36px;
z-index: 1;
width: 90px; width: 90px;
height: 90px; height: 90px;
border-radius: 50%; border-radius: 50%;
@ -124,8 +125,17 @@
.profile-frame { .profile-frame {
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 2;
width: 162px; width: 162px;
height: 162px; height: 162px;
pointer-events: none;
}
.profile-frame img,
.profile-frame video,
.profile-frame canvas {
display: block;
width: 100%;
height: 100%;
object-fit: contain; object-fit: contain;
} }
.user-name { .user-name {
@ -234,6 +244,13 @@
rgba(59, 36, 100, 0) 63% rgba(59, 36, 100, 0) 63%
); );
} }
.badge-card.locked img {
filter: grayscale(1) brightness(0.58);
opacity: 0.72;
}
.badge-card.locked .badge-title {
color: #8f879a;
}
.badge-card img { .badge-card img {
position: absolute; position: absolute;
left: 12px; left: 12px;
@ -343,6 +360,10 @@
height: 203px; height: 203px;
object-fit: contain; object-fit: contain;
} }
.modal-badge.locked {
filter: grayscale(1) brightness(0.56);
opacity: 0.72;
}
.modal-title { .modal-title {
position: absolute; position: absolute;
left: 0; left: 0;
@ -370,6 +391,12 @@
font-size: 12px; font-size: 12px;
line-height: 12px; line-height: 12px;
} }
.modal-status.locked {
color: #8f879a;
}
.modal-status.unlocked {
color: #d3d3d3;
}
.modal-status img { .modal-status img {
width: 16px; width: 16px;
height: 16px; height: 16px;
@ -385,7 +412,7 @@
color: #fff; color: #fff;
font-size: 12px; font-size: 12px;
line-height: 16px; line-height: 16px;
text-align: justify; text-align: center;
} }
.collection-panel { .collection-panel {
position: absolute; position: absolute;
@ -483,11 +510,13 @@
src="assets/figma/profile-avatar.png" src="assets/figma/profile-avatar.png"
alt="" alt=""
/> />
<img <div
id="profileFrame"
class="profile-frame" class="profile-frame"
src="assets/figma/header-frame.png" aria-hidden="true"
alt="" >
/> <img src="assets/figma/header-frame.png" alt="" />
</div>
</section> </section>
<div id="userName" class="user-name">Name</div> <div id="userName" class="user-name">Name</div>
<div id="earnedText" class="earned"> <div id="earnedText" class="earned">
@ -563,6 +592,7 @@
<script src="../common/params.js"></script> <script src="../common/params.js"></script>
<script src="../common/jsbridge.js"></script> <script src="../common/jsbridge.js"></script>
<script src="../common/i18n.js"></script> <script src="../common/i18n.js"></script>
<script src="../common/effect-player.js"></script>
<script> <script>
(function () { (function () {
var ASSET = 'assets/figma/'; var ASSET = 'assets/figma/';
@ -578,6 +608,9 @@
loading: false, loading: false,
toastTimer: 0, toastTimer: 0,
profile: null, profile: null,
appearance: {},
profileFrameURL: '',
profileFramePlayer: null,
achievements: { honors: [], event: [] }, achievements: { honors: [], event: [] },
loaded: { honors: false, event: false }, loaded: { honors: false, event: false },
selected: null, selected: null,
@ -706,15 +739,26 @@
} }
function profileSource(current) { function profileSource(current) {
var user = current && current.user; var user = current && current.user;
return (user && (user.profile || user.user || user)) || {}; var data =
(user && (user.profile || user.user || user)) || {};
return data && data.profile && !data.nickname
? data.profile
: data;
} }
function profileName(profile) { function profileName(profile) {
return ( return (
profile.nickname || profile.nickname ||
profile.nick_name || profile.nick_name ||
profile.nickName ||
profile.username ||
profile.user_name ||
profile.name || profile.name ||
profile.display_name || profile.display_name ||
profile.displayName || profile.displayName ||
profile.display_user_id ||
profile.displayUserId ||
profile.user_id ||
profile.userId ||
'Name' 'Name'
); );
} }
@ -730,6 +774,47 @@
'' ''
); );
} }
function normalizeAppearance(data) {
return (
(data &&
(data.appearance ||
data.user_appearance ||
data.userAppearance ||
data.profile_appearance ||
data.profileAppearance ||
data)) ||
{}
);
}
function frameURL(frameData) {
frameData = frameData || {};
if (typeof frameData === 'string') return frameData;
return (
frameData.asset_url ||
frameData.preview_url ||
frameData.animation_url ||
frameData.url ||
frameData.resource_url ||
frameData.resourceUrl ||
''
);
}
function profileFrameData(profile) {
profile = profile || {};
var appearance = state.appearance || {};
return (
appearance.avatar_frame ||
appearance.avatarFrame ||
appearance.profile_frame ||
appearance.profileFrame ||
profile.avatar_frame ||
profile.avatarFrame ||
profile.profile_frame ||
profile.profileFrame ||
profile.frame ||
{}
);
}
function setImage(node, src, fallback) { function setImage(node, src, fallback) {
var next = src || fallback || ''; var next = src || fallback || '';
node.onerror = function () { node.onerror = function () {
@ -739,6 +824,52 @@
}; };
if (node.getAttribute('src') !== next) node.src = next; if (node.getAttribute('src') !== next) node.src = next;
} }
function renderStaticFrame(node, src) {
node.innerHTML =
'<img src="' + escapeHTML(src) + '" alt="">';
var img = node.querySelector('img');
img.onerror = function () {
img.onerror = null;
if (src === ASSET + 'header-frame.png') return;
state.profileFrameURL = ASSET + 'header-frame.png';
renderStaticFrame(node, ASSET + 'header-frame.png');
};
}
function isDynamicEffect(url) {
if (
!window.HyAppEffectPlayer ||
!window.HyAppEffectPlayer.inferType
) {
return false;
}
return Boolean(window.HyAppEffectPlayer.inferType(url));
}
function renderProfileFrame(profile) {
var node = byId('profileFrame');
if (!node) return;
var url =
frameURL(profileFrameData(profile)) ||
ASSET + 'header-frame.png';
if (state.profileFrameURL === url) return;
state.profileFrameURL = url;
if (state.profileFramePlayer) {
state.profileFramePlayer.destroy();
state.profileFramePlayer = null;
}
if (isDynamicEffect(url)) {
state.profileFramePlayer =
window.HyAppEffectPlayer.create(node, {
fit: 'contain',
waitForWindowLoad: false,
});
state.profileFramePlayer.play(url).catch(function () {
state.profileFrameURL = ASSET + 'header-frame.png';
renderStaticFrame(node, ASSET + 'header-frame.png');
});
return;
}
renderStaticFrame(node, url);
}
function parseJSON(text) { function parseJSON(text) {
try { try {
return text ? JSON.parse(text) : {}; return text ? JSON.parse(text) : {};
@ -776,7 +907,7 @@
function titleOf(item) { function titleOf(item) {
var resource = resourceOf(item); var resource = resourceOf(item);
var def = definitionOf(item); var def = definitionOf(item);
return resource.name || def.title || 'Pirple Pearl'; return def.title || resource.name || 'Pirple Pearl';
} }
function descriptionOf(item) { function descriptionOf(item) {
var config = configOf(item); var config = configOf(item);
@ -831,18 +962,19 @@
var value = Number(ms || 0); var value = Number(ms || 0);
if (!value) return ''; if (!value) return '';
try { try {
return new Date(value).toLocaleDateString( var date = new Date(value);
window.HyAppI18n && window.HyAppI18n.lang var year = date.getFullYear();
? window.HyAppI18n.lang() var month = String(date.getMonth() + 1).padStart(
: 'en', 2,
{ '0'
year: 'numeric',
month: 'long',
day: 'numeric',
}
); );
var day = String(date.getDate()).padStart(2, '0');
return year + '/' + month + '/' + day;
} catch (_) { } catch (_) {
return new Date(value).toISOString().slice(0, 10); return new Date(value)
.toISOString()
.slice(0, 10)
.replace(/-/g, '/');
} }
} }
function unlockedCount() { function unlockedCount() {
@ -870,6 +1002,7 @@
profileAvatar(profile), profileAvatar(profile),
ASSET + 'profile-avatar.png' ASSET + 'profile-avatar.png'
); );
renderProfileFrame(profile);
byId('earnedText').innerHTML = earnedText(unlockedCount()); byId('earnedText').innerHTML = earnedText(unlockedCount());
} }
function cardHTML(item, index) { function cardHTML(item, index) {
@ -996,18 +1129,50 @@
}); });
} }
function loadUserProfile() { function loadUserProfile() {
if (!window.HyAppParams || !window.HyAppParams.loadUser) { var tasks = [];
var hasToken =
window.HyAppAPI &&
window.HyAppAPI.getAccessToken &&
window.HyAppAPI.getAccessToken();
if (window.HyAppParams && window.HyAppParams.loadUser) {
tasks.push(
window.HyAppParams.loadUser()
.then(function (current) {
state.profile = profileSource(current);
renderHeader();
return current;
})
.catch(function () {
return null;
})
);
}
if (
hasToken &&
window.HyAppAPI.user &&
window.HyAppAPI.user.appearance
) {
tasks.push(
window.HyAppAPI.user
.appearance()
.then(function (data) {
state.appearance =
normalizeAppearance(data);
renderHeader();
return data;
})
.catch(function () {
state.appearance = {};
renderHeader();
return null;
})
);
}
if (!tasks.length) {
return Promise.resolve(null); return Promise.resolve(null);
} }
return window.HyAppParams.loadUser() // 用户资料和装扮是两个独立接口:任一失败都不能阻断成就页,成功的那部分立即刷新头部。
.then(function (current) { return Promise.all(tasks);
state.profile = profileSource(current);
renderHeader();
return current;
})
.catch(function () {
return null;
});
} }
function loadCounts() { function loadCounts() {
var active = state.tab; var active = state.tab;
@ -1060,30 +1225,28 @@
var unlocked = isUnlocked(item); var unlocked = isUnlocked(item);
var resource = resourceOf(item); var resource = resourceOf(item);
byId('modalBadge').src = resourceImage(resource, unlocked); byId('modalBadge').src = resourceImage(resource, unlocked);
byId('modalBadge').classList.toggle('locked', !unlocked);
byId('modalTitle').textContent = titleOf(item); byId('modalTitle').textContent = titleOf(item);
byId('modalDesc').textContent = descriptionOf(item); byId('modalDesc').textContent = descriptionOf(item);
byId('modalStatus').classList.toggle('unlocked', unlocked);
byId('modalStatus').classList.toggle('locked', !unlocked);
if (unlocked) { if (unlocked) {
var date = var date = formatDate(item.unlocked_at_ms);
formatDate(item.unlocked_at_ms) || byId('modalStatus').textContent = date
t('achievement.collected', 'Collected'); ? replaceToken(
byId('modalStatus').innerHTML = escapeHTML( t(
replaceToken( 'achievement.collectedWithDate',
t( 'Collected on {date}'
'achievement.collectedWithDate', ),
'{date} Collected' 'date',
), date
'date', )
date : t('achievement.collected', 'Collected');
)
);
} else { } else {
byId('modalStatus').innerHTML = byId('modalStatus').textContent = t(
'<img src="' + 'achievement.notCollected',
ASSET + 'Not Collected'
'lock.png" alt="">' + );
escapeHTML(
t('achievement.notCollected', 'Not Collected')
);
} }
var owners = ownerCountOf(item); var owners = ownerCountOf(item);
byId('ownerText').textContent = owners byId('ownerText').textContent = owners

View File

@ -639,7 +639,7 @@
"achievement.collection": "Achievement Collection", "achievement.collection": "Achievement Collection",
"achievement.notCollected": "Not Collected", "achievement.notCollected": "Not Collected",
"achievement.collected": "Collected", "achievement.collected": "Collected",
"achievement.collectedWithDate": "{date} Collected", "achievement.collectedWithDate": "Collected on {date}",
"achievement.ownerCount": "Only {count} people own it", "achievement.ownerCount": "Only {count} people own it",
"achievement.ownerUpdating": "Collection records are updating" "achievement.ownerUpdating": "Collection records are updating"
} }