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

View File

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