lalu store

This commit is contained in:
zhx 2026-06-04 23:47:28 +08:00
parent 0c5d01072d
commit 527484d3d8
4 changed files with 197 additions and 126 deletions

View File

@ -232,7 +232,11 @@
overflow: hidden; overflow: hidden;
border-radius: 12px; border-radius: 12px;
background: background:
linear-gradient(145deg, rgba(255, 255, 255, 0.14), transparent), linear-gradient(
145deg,
rgba(255, 255, 255, 0.14),
transparent
),
#1a1822; #1a1822;
box-shadow: box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.08), inset 0 0 0 1px rgba(255, 255, 255, 0.08),
@ -448,10 +452,7 @@
<p class="brand-name" id="hostName">Lalu</p> <p class="brand-name" id="hostName">Lalu</p>
</div> </div>
<p <p class="message" data-i18n="lalu_landing.message">
class="message"
data-i18n="lalu_landing.message"
>
送你一份礼物,快来和我一起玩吧! 送你一份礼物,快来和我一起玩吧!
</p> </p>
@ -511,9 +512,7 @@
<img src="assets/lalu-logo.png" alt="Lalu" /> <img src="assets/lalu-logo.png" alt="Lalu" />
<div class="app-copy"> <div class="app-copy">
<strong>Lalu</strong> <strong>Lalu</strong>
<span data-i18n="lalu_landing.tagline"> <span data-i18n="lalu_landing.tagline"> 结识新朋友 </span>
结识新朋友
</span>
</div> </div>
</div> </div>
<button <button
@ -556,7 +555,11 @@
} }
function resolveDownloadURL() { function resolveDownloadURL() {
var commonURL = firstParam(['downloadUrl', 'download_url', 'url']); var commonURL = firstParam([
'downloadUrl',
'download_url',
'url',
]);
var iosURL = firstParam(['iosUrl', 'ios_url']); var iosURL = firstParam(['iosUrl', 'ios_url']);
var androidURL = var androidURL =
firstParam(['androidUrl', 'android_url']) || firstParam(['androidUrl', 'android_url']) ||

View File

@ -9,7 +9,10 @@
<title>BD Center</title> <title>BD Center</title>
<link rel="stylesheet" href="../../common/theme.css" /> <link rel="stylesheet" href="../../common/theme.css" />
<link rel="stylesheet" href="../common/center.css" /> <link rel="stylesheet" href="../common/center.css" />
<link rel="stylesheet" href="./style.css" /> <link
rel="stylesheet"
href="./style.css?v=20260604-bd-center-real-skeleton"
/>
</head> </head>
<body> <body>
@ -292,6 +295,6 @@
<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/api.js?v=20260604-bd-center"></script> <script src="../../common/api.js?v=20260604-bd-center"></script>
<script src="./script.js?v=20260604-bd-center-api"></script> <script src="./script.js?v=20260604-bd-center-real-skeleton"></script>
</body> </body>
</html> </html>

View File

@ -1,85 +1,30 @@
(function () { (function () {
var FIRST_BILL_MONTH = '2026-06'; var FIRST_BILL_MONTH = '2026-06';
var BD_SALARY_ASSET = 'BD_SALARY_USD'; var BD_SALARY_ASSET = 'BD_SALARY_USD';
var query = new URLSearchParams(window.location.search);
var mock = {
profile: {
name: 'BD_Jasmine',
uid: 'BD-163008',
avatar: avatarData('B', '#dbc8ff', '#7d57c7'),
},
balance: {
available: 48620.8,
},
billMonth: currentMonth(),
agencyBill: {
billTitle: '2026.06',
agencyNumber: 12,
totalSalary: 128450.35,
totalRecharge: 356900.0,
memberBillList: [
{
name: 'Yumi Star Agency',
account: '163003',
avatar: avatarData('Y', '#dbc8ff', '#7d57c7'),
teamSalaryAmount: 38420.55,
teamRechargeAmount: 126880,
teamMemberCount: 28,
},
{
name: 'Moon Live Agency',
account: '163086',
avatar: avatarData('M', '#f4ebff', '#7d57c7'),
teamSalaryAmount: 29106.2,
teamRechargeAmount: 98540,
teamMemberCount: 19,
},
{
name: 'Nova Host Guild',
account: '163221',
avatar: avatarData('N', '#dbc8ff', '#7d57c7'),
teamSalaryAmount: 22450,
teamRechargeAmount: 73150,
teamMemberCount: 16,
},
],
},
inviteRecords: [],
searchPool: [
{
userId: '3187099',
id: '3187099',
name: 'Agent_River',
account: '168899',
avatar: avatarData('R', '#dbc8ff', '#7d57c7'),
},
{
userId: '3187100',
id: '3187100',
name: 'Agent_Sara',
account: '168900',
avatar: avatarData('S', '#f4ebff', '#7d57c7'),
},
],
};
var data = { var data = {
profile: mock.profile, profile: null,
balance: mock.balance, balance: { available: 0 },
billMonth: mock.billMonth, billMonth: currentMonth(),
agencyBill: mock.agencyBill, agencyBill: {
inviteRecords: mock.inviteRecords, agencyNumber: 0,
totalSalary: 0,
totalRecharge: 0,
memberBillList: [],
},
inviteRecords: [],
}; };
var state = { var state = {
billMonth: normalizeBillMonth(mock.billMonth), billMonth: normalizeBillMonth(data.billMonth),
inviteQuery: '', inviteQuery: '',
inviteMode: 'records', inviteMode: 'records',
inviteStatus: '', inviteStatus: '',
inviteStatusType: '', inviteStatusType: '',
searched: [], searched: [],
inviting: {}, inviting: {},
isMock: query.get('mock') === '1', searching: false,
loading: true,
}; };
function $(id) { function $(id) {
@ -256,6 +201,16 @@
}); });
} }
function setSkeleton(element, loading, text) {
if (!element) return;
element.classList.toggle('skeleton-line', Boolean(loading));
if (loading) {
element.textContent = '';
return;
}
element.textContent = text || '';
}
function renderImageAvatar(container, src, name, fallbackClass) { function renderImageAvatar(container, src, name, fallbackClass) {
container.textContent = ''; container.textContent = '';
if (!src) { if (!src) {
@ -355,9 +310,22 @@
function renderProfile() { function renderProfile() {
var image = $('profileAvatar'); var image = $('profileAvatar');
var fallback = $('profileAvatarFallback'); var fallback = $('profileAvatarFallback');
var profile = data.profile || mock.profile; var profile = data.profile || {};
$('profileName').textContent = profile.name || '-'; var loading = Boolean(state.loading);
$('profileUID').textContent = 'UID: ' + (profile.uid || '-'); setSkeleton($('profileName'), loading, profile.name || '-');
setSkeleton(
$('profileUID'),
loading,
'UID: ' + (profile.uid || '-')
);
fallback.classList.toggle('avatar-skeleton', loading);
if (loading) {
image.hidden = true;
image.removeAttribute('src');
fallback.hidden = false;
fallback.textContent = '';
return;
}
fallback.textContent = initial(profile.name) || 'B'; fallback.textContent = initial(profile.name) || 'B';
image.onload = function () { image.onload = function () {
image.hidden = false; image.hidden = false;
@ -375,7 +343,14 @@
} }
function renderBalance() { function renderBalance() {
$('availableBalance').textContent = money(data.balance && data.balance.available); var loading = Boolean(state.loading);
var coinIcon = document.querySelector('.salary-value .coin-icon');
if (coinIcon) coinIcon.classList.toggle('skeleton-coin', loading);
setSkeleton(
$('availableBalance'),
loading,
money(data.balance && data.balance.available)
);
} }
function summaryCell(label, value) { function summaryCell(label, value) {
@ -389,6 +364,43 @@
return cell; return cell;
} }
function summarySkeletonCell() {
var cell = document.createElement('div');
var labelNode = document.createElement('span');
var valueNode = document.createElement('strong');
labelNode.className = 'skeleton-line';
valueNode.className = 'skeleton-line';
cell.appendChild(labelNode);
cell.appendChild(valueNode);
return cell;
}
function skeletonMemberRow() {
var row = document.createElement('article');
var avatar = document.createElement('div');
var main = document.createElement('div');
var name = document.createElement('div');
var meta = document.createElement('div');
var side = document.createElement('div');
row.className = 'member-row is-skeleton';
avatar.className = 'member-avatar avatar-skeleton';
main.className = 'member-main';
name.className = 'member-name skeleton-line';
meta.className = 'member-meta skeleton-line';
side.className = 'member-side';
for (var i = 0; i < 3; i += 1) {
var line = document.createElement('div');
line.className = 'skeleton-line';
side.appendChild(line);
}
main.appendChild(name);
main.appendChild(meta);
row.appendChild(avatar);
row.appendChild(main);
row.appendChild(side);
return row;
}
function memberRow(item) { function memberRow(item) {
var row = document.createElement('article'); var row = document.createElement('article');
var avatar = document.createElement('div'); var avatar = document.createElement('div');
@ -427,10 +439,23 @@
} }
function renderTeamList() { function renderTeamList() {
var bill = data.agencyBill || mock.agencyBill; var bill = data.agencyBill;
$('billMonthText').textContent = monthLabel(state.billMonth); $('billMonthText').textContent = monthLabel(state.billMonth);
$('billRange').textContent = state.billMonth.replace('-', '.'); $('billRange').textContent = state.billMonth.replace('-', '.');
if (state.loading) {
replaceChildren($('teamListSummary'), [
summarySkeletonCell(),
summarySkeletonCell(),
]);
replaceChildren($('teamListContent'), [
skeletonMemberRow(),
skeletonMemberRow(),
skeletonMemberRow(),
]);
return;
}
replaceChildren($('teamListSummary'), [ replaceChildren($('teamListSummary'), [
summaryCell( summaryCell(
t('bd_center.agency_number', 'Agency number'), t('bd_center.agency_number', 'Agency number'),
@ -502,9 +527,6 @@
state.billMonth = nextMonth; state.billMonth = nextMonth;
closeMonthSheet(); closeMonthSheet();
renderTeamList(); renderTeamList();
if (state.isMock) {
toast(t('bd_center.mock_bill_loaded', 'Mock bill loaded.'));
}
} }
function inviteProfile(item) { function inviteProfile(item) {
@ -599,7 +621,8 @@
replaceChildren($('inviteList'), rows); replaceChildren($('inviteList'), rows);
$('inviteEmpty').hidden = $('inviteEmpty').hidden =
rows.length > 0 || Boolean(state.inviteStatus); rows.length > 0 || Boolean(state.inviteStatus);
$('inviteSearchButton').disabled = !state.inviteQuery.trim(); $('inviteSearchButton').disabled =
!state.inviteQuery.trim() || Boolean(state.searching);
} }
function openInviteModal() { function openInviteModal() {
@ -608,6 +631,7 @@
state.searched = []; state.searched = [];
state.inviteStatus = ''; state.inviteStatus = '';
state.inviteStatusType = ''; state.inviteStatusType = '';
state.searching = false;
$('inviteSearchInput').value = ''; $('inviteSearchInput').value = '';
$('inviteModal').hidden = false; $('inviteModal').hidden = false;
$('inviteModal').setAttribute('aria-hidden', 'false'); $('inviteModal').setAttribute('aria-hidden', 'false');
@ -624,17 +648,6 @@
document.body.classList.remove('modal-open'); document.body.classList.remove('modal-open');
} }
function searchMockInvite(queryValue) {
var queryLower = queryValue.toLowerCase();
return mock.searchPool.filter(function (profile) {
return (
profile.name.toLowerCase().indexOf(queryLower) >= 0 ||
profile.account.indexOf(queryLower) >= 0 ||
profile.id.indexOf(queryLower) >= 0
);
});
}
function searchInvite(event) { function searchInvite(event) {
event.preventDefault(); event.preventDefault();
var queryValue = state.inviteQuery.trim(); var queryValue = state.inviteQuery.trim();
@ -646,17 +659,6 @@
renderInviteModal(); renderInviteModal();
return; return;
} }
if (state.isMock) {
state.searched = searchMockInvite(queryValue);
if (!state.searched.length) {
state.inviteStatus = t(
'bd_center.no_users_found',
'No users found'
);
}
renderInviteModal();
return;
}
if (!api().user || !api().user.resolveDisplayUserID) { if (!api().user || !api().user.resolveDisplayUserID) {
state.inviteStatus = t( state.inviteStatus = t(
'bd_center.api_not_ready', 'bd_center.api_not_ready',
@ -667,6 +669,7 @@
return; return;
} }
state.inviteStatus = t('bd_center.searching', 'Searching...'); state.inviteStatus = t('bd_center.searching', 'Searching...');
state.searching = true;
renderInviteModal(); renderInviteModal();
api() api()
.user.resolveDisplayUserID(queryValue) .user.resolveDisplayUserID(queryValue)
@ -686,25 +689,16 @@
state.inviteStatusType = 'error'; state.inviteStatusType = 'error';
state.searched = []; state.searched = [];
renderInviteModal(); renderInviteModal();
})
.finally(function () {
state.searching = false;
renderInviteModal();
}); });
} }
function submitInvite(profile) { function submitInvite(profile) {
var targetUserID = String(profile.userId || profile.id || ''); var targetUserID = String(profile.userId || profile.id || '');
if (!targetUserID) return; if (!targetUserID) return;
if (state.isMock) {
data.inviteRecords.unshift({
id: 'inv-' + Date.now(),
status: 'success',
profile: profile,
});
state.inviteMode = 'records';
state.searched = [];
state.inviteStatus = t('bd_center.agency_added', 'Agency added.');
state.inviteStatusType = 'success';
renderInviteModal();
return;
}
if (!centerAPI() || !centerAPI().inviteAgency) { if (!centerAPI() || !centerAPI().inviteAgency) {
state.inviteStatus = t( state.inviteStatus = t(
'bd_center.api_not_ready', 'bd_center.api_not_ready',
@ -758,17 +752,44 @@
'../withdraw-exchange/index.html?' + params.toString(); '../withdraw-exchange/index.html?' + params.toString();
} }
function setLoading(loading) {
state.loading = Boolean(loading);
render();
}
function loadRealData() { function loadRealData() {
var service = centerAPI(); var service = centerAPI();
if (state.isMock || !service) return Promise.resolve(false); setLoading(true);
if (
!service ||
!service.overview ||
!service.agencies ||
!service.inviteAgency
) {
setLoading(false);
return Promise.reject(
new Error(t('bd_center.api_not_ready', 'API module is not ready.'))
);
}
return Promise.all([ return Promise.all([
service.overview(), service.overview(),
service.agencies(50), service.agencies(50),
]).then(function (results) { ]).then(function (results) {
applyOverview(results[0] || {}); applyOverview(results[0] || {});
applyAgencies(results[1] || {}); applyAgencies(results[1] || {});
render();
return true; return true;
}).catch(function (error) {
data.profile = null;
data.balance = { available: 0 };
data.agencyBill = {
agencyNumber: 0,
totalSalary: 0,
totalRecharge: 0,
memberBillList: [],
};
throw error;
}).finally(function () {
setLoading(false);
}); });
} }
@ -827,7 +848,6 @@
if (window.HyAppBridge) { if (window.HyAppBridge) {
window.HyAppBridge.ready({ window.HyAppBridge.ready({
page: 'bd-center', page: 'bd-center',
mock: state.isMock,
}); });
} }
}); });

View File

@ -70,6 +70,12 @@
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.bd-center .salary-value strong.skeleton-line {
width: 180px;
height: 42px;
border-radius: 10px;
}
.team-stats-card, .team-stats-card,
.team-users-card { .team-users-card {
display: grid; display: grid;
@ -180,6 +186,18 @@
line-height: 1.15; line-height: 1.15;
} }
.team-list-summary span.skeleton-line {
width: 96px;
height: 13px;
border-radius: 7px;
}
.team-list-summary strong.skeleton-line {
width: 112px;
height: 19px;
border-radius: 8px;
}
.team-list-content { .team-list-content {
display: grid; display: grid;
gap: 10px; gap: 10px;
@ -265,6 +283,33 @@
text-align: right; text-align: right;
} }
.member-row.is-skeleton {
pointer-events: none;
}
.member-row.is-skeleton .member-avatar {
background: transparent;
}
.member-row.is-skeleton .member-name.skeleton-line {
width: min(132px, 100%);
height: 18px;
border-radius: 7px;
}
.member-row.is-skeleton .member-meta.skeleton-line {
width: 92px;
height: 14px;
border-radius: 7px;
}
.member-row.is-skeleton .member-side .skeleton-line {
width: 122px;
max-width: 28vw;
height: 13px;
border-radius: 6px;
}
.empty-state, .empty-state,
.invite-empty { .invite-empty {
display: flex; display: flex;