经理中心

This commit is contained in:
zhx 2026-06-12 19:10:41 +08:00
parent 605eafa78d
commit 496c74a291
30 changed files with 2027 additions and 69 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1,9 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 1042 357" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect id="Rectangle 82" width="1042" height="357" fill="url(#paint0_linear_0_9)"/>
<defs>
<linearGradient id="paint0_linear_0_9" x1="521" y1="0" x2="521" y2="357" gradientUnits="userSpaceOnUse">
<stop stop-color="#D9D9D9" stop-opacity="0"/>
<stop offset="0.372783" stop-color="#737373"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

View File

@ -0,0 +1,3 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 1042 495" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect id="Rectangle 82" width="1042" height="495" fill="var(--fill-0, #D9D9D9)"/>
</svg>

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@ -0,0 +1,12 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 732 123" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Ellipse 5" filter="url(#filter0_f_0_7)" style="mix-blend-mode:multiply">
<ellipse cx="366" cy="61.5" rx="349" ry="44.5" fill="var(--fill-0, #7B35CA)"/>
</g>
<defs>
<filter id="filter0_f_0_7" x="0" y="0" width="732" height="123" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="8.5" result="effect1_foregroundBlur_0_7"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 711 B

View File

@ -0,0 +1,3 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 99.9071 65" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect id="Rectangle 78" width="99.9071" height="65" fill="var(--fill-0, #D9D9D9)"/>
</svg>

After

Width:  |  Height:  |  Size: 266 B

View File

@ -0,0 +1,9 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 99.9071 65" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect id="Rectangle 78" width="99.9071" height="65" fill="url(#paint0_linear_0_8)"/>
<defs>
<linearGradient id="paint0_linear_0_8" x1="0" y1="32.5" x2="99.9071" y2="32.5" gradientUnits="userSpaceOnUse">
<stop offset="0.898374" stop-color="#D9D9D9"/>
<stop offset="1" stop-color="#737373" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View File

@ -0,0 +1,9 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 1080 339" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect id="Rectangle 72" width="1080" height="339" fill="url(#paint0_linear_0_12)"/>
<defs>
<linearGradient id="paint0_linear_0_12" x1="540" y1="0" x2="540" y2="339" gradientUnits="userSpaceOnUse">
<stop offset="0.697434" stop-color="#D9D9D9" stop-opacity="0.34"/>
<stop offset="1" stop-color="#737373" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 527 B

View File

@ -0,0 +1,9 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 1080 1919" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect id="Rectangle 72" width="1080" height="1919" fill="url(#paint0_linear_0_14)"/>
<defs>
<linearGradient id="paint0_linear_0_14" x1="540" y1="0" x2="540" y2="1919" gradientUnits="userSpaceOnUse">
<stop offset="0.697434" stop-color="#D9D9D9"/>
<stop offset="1" stop-color="#737373" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 510 B

View File

@ -0,0 +1,11 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 1080 1919" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect id="Rectangle 85" width="1080" height="1919" fill="url(#paint0_linear_0_11)"/>
<defs>
<linearGradient id="paint0_linear_0_11" x1="540" y1="0" x2="540" y2="1919" gradientUnits="userSpaceOnUse">
<stop stop-color="#737373" stop-opacity="0"/>
<stop offset="0.278846" stop-color="#D9D9D9"/>
<stop offset="0.574966" stop-color="#D9D9D9"/>
<stop offset="1" stop-color="#737373" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 603 B

View File

@ -0,0 +1,9 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 1080 1919" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect id="Rectangle 85" width="1080" height="1919" fill="url(#paint0_linear_0_13)"/>
<defs>
<linearGradient id="paint0_linear_0_13" x1="540" y1="0" x2="540" y2="1919" gradientUnits="userSpaceOnUse">
<stop offset="0.574966" stop-color="#D9D9D9"/>
<stop offset="1" stop-color="#737373" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -0,0 +1,3 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 900 76" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Rectangle 84" opacity="0.68" d="M0 0H900V76H0L0 0Z" fill="var(--fill-0, #2A1244)"/>
</svg>

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -0,0 +1,371 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
/>
<title>New Agency Opening Event</title>
<link rel="stylesheet" href="./style.css?v=20260612-agency-opening" />
</head>
<body>
<div class="app-viewport" id="appViewport">
<main class="page">
<div class="stage-wrap" id="stageWrap">
<section
class="stage"
aria-label="New Agency Opening Event"
data-i18n-aria="agencyOpening.pageLabel"
>
<img
class="bg-hall bg-hall-hero"
src="./assets/hall-bg.png"
alt=""
/>
<img
class="bg-hall bg-hall-top"
src="./assets/hall-bg.png"
alt=""
/>
<img
class="bg-hall bg-hall-repeat-a"
src="./assets/hall-bg.png"
alt=""
/>
<img
class="bg-hall bg-hall-repeat-b"
src="./assets/hall-bg.png"
alt=""
/>
<div class="top-fade" aria-hidden="true"></div>
<nav class="language-tabs" aria-label="Language">
<button
class="language-tab"
type="button"
data-lang-option="en"
>
<span>English</span>
</button>
<button
class="language-tab"
type="button"
data-lang-option="ar"
>
<span>Arabic</span>
</button>
<button
class="language-tab"
type="button"
data-lang-option="tr"
>
<span>Turkish</span>
</button>
<button
class="language-tab"
type="button"
data-lang-option="es"
>
<span>Spanish</span>
</button>
<button
class="language-tab"
type="button"
data-lang-option="zh"
>
<span>Chinese</span>
</button>
<button
class="language-tab"
type="button"
data-lang-option="id"
>
<span>Indonesian</span>
</button>
</nav>
<header class="hero">
<img
class="hero-title"
src="./assets/title.png"
alt=""
/>
<p
class="hero-subtitle"
data-i18n="agencyOpening.heroSubtitle"
>
Exclusive for New Agencies
</p>
<img
class="hero-shadow"
src="./assets/hero-shadow.svg"
alt=""
/>
<img
class="hero-cat"
src="./assets/cat.png"
alt=""
/>
</header>
<section
class="countdown"
aria-label="Activity countdown"
>
<article class="count-cell count-days">
<img src="./assets/countdown-cell.png" alt="" />
<strong data-count="days">04</strong>
<span data-i18n="agencyOpening.days">Days</span>
</article>
<article class="count-cell count-hours">
<img src="./assets/countdown-cell.png" alt="" />
<strong data-count="hours">04</strong>
<span data-i18n="agencyOpening.hours"
>Hours</span
>
</article>
<article class="count-cell count-minutes">
<img src="./assets/countdown-cell.png" alt="" />
<strong data-count="minutes">04</strong>
<span data-i18n="agencyOpening.minutes"
>Minutes</span
>
</article>
<article class="count-cell count-seconds">
<img src="./assets/countdown-cell.png" alt="" />
<strong data-count="seconds">04</strong>
<span data-i18n="agencyOpening.seconds"
>Seconds</span
>
</article>
</section>
<section
class="opening-section qualification-section"
aria-labelledby="qualificationTitle"
>
<div class="section-title" id="qualificationTitle">
<img src="./assets/section-title.png" alt="" />
<h2
data-i18n="agencyOpening.qualificationTitle"
>
Opening Qualification
</h2>
</div>
<div
class="ornate-panel frame-qualification"
aria-hidden="true"
>
<span
class="frame-piece frame-piece-top"
></span>
<span
class="frame-piece frame-piece-bottom"
></span>
</div>
<div class="qualification-grid">
<article class="qualification-card">
<p data-i18n="agencyOpening.agencyIdentity">
Agency Identity
</p>
<strong id="agencyIdentityText"
>Agency</strong
>
<img
src="./assets/status-check.png"
alt=""
/>
</article>
<article class="qualification-card">
<p data-i18n="agencyOpening.hostCount">
Host Count
</p>
<strong id="hostCountText">12 / 10+</strong>
<img
src="./assets/status-check.png"
alt=""
/>
</article>
<article class="qualification-card">
<p data-i18n="agencyOpening.openingStatus">
Opening Status
</p>
<strong id="openingStatusText"
>Not Joined</strong
>
<img
src="./assets/status-check.png"
alt=""
/>
</article>
</div>
<button
class="apply-button"
id="applyButton"
type="button"
>
<img src="./assets/apply-button.png" alt="" />
<span data-i18n="agencyOpening.apply"
>Apply Opening Event</span
>
</button>
<p
class="eligibility-text"
id="eligibilityText"
data-i18n="agencyOpening.eligible"
>
Eligible To Apply
</p>
</section>
<section
class="opening-section rewards-section"
aria-labelledby="rewardsTitle"
>
<div class="section-title" id="rewardsTitle">
<img src="./assets/section-title.png" alt="" />
<h2 data-i18n="agencyOpening.rewardsTitle">
Opening Rewards
</h2>
</div>
<div
class="ornate-panel frame-rewards"
aria-hidden="true"
>
<span
class="frame-piece frame-piece-top"
></span>
<span
class="frame-piece frame-piece-bottom"
></span>
</div>
<div class="reward-table" id="rewardTable">
<div class="reward-head">
<strong
data-i18n="agencyOpening.openingRevenue"
>Opening Revenue</strong
>
<strong
data-i18n="agencyOpening.rewardCoins"
>Reward Coins</strong
>
</div>
<div
class="table-line"
aria-hidden="true"
></div>
<div class="reward-rows" id="rewardRows"></div>
</div>
</section>
<section
class="opening-section rules-section"
aria-labelledby="rulesTitle"
>
<div class="section-title" id="rulesTitle">
<img src="./assets/section-title.png" alt="" />
<h2 data-i18n="agencyOpening.rulesTitle">
Activity Rules
</h2>
</div>
<div
class="ornate-panel frame-rules"
aria-hidden="true"
>
<span
class="frame-piece frame-piece-top"
></span>
<span
class="frame-piece frame-piece-bottom"
></span>
</div>
<ol class="rules-list" id="rulesList">
<li data-i18n="agencyOpening.rule1">
Only agency accounts can apply.
</li>
<li data-i18n="agencyOpening.rule2">
Host count must be more than 10.
</li>
<li data-i18n="agencyOpening.rule3">
Agency age must be less than 7 days.
</li>
<li data-i18n="agencyOpening.rule4">
Each agency can join only once in lifetime.
</li>
<li data-i18n="agencyOpening.rule5">
Opening Event cannot run with Agency Support
Program at the same time.
</li>
<li data-i18n="agencyOpening.rule6">
Rewards will be sent within 7 days after
review.
</li>
</ol>
</section>
<section
class="opening-section record-section"
aria-labelledby="recordTitle"
>
<div class="section-title" id="recordTitle">
<img src="./assets/section-title.png" alt="" />
<h2 data-i18n="agencyOpening.recordTitle">
Opening Record
</h2>
</div>
<div
class="ornate-panel frame-record"
aria-hidden="true"
>
<span
class="frame-piece frame-piece-top"
></span>
<span
class="frame-piece frame-piece-bottom"
></span>
</div>
<div class="record-table">
<div class="record-row">
<span data-i18n="agencyOpening.guildName"
>Guild Name:</span
>
<strong id="recordGuildName"
>Royal Family</strong
>
</div>
<div class="record-row">
<span data-i18n="agencyOpening.created"
>Created:</span
>
<strong id="recordCreated"
>2026-06-01</strong
>
</div>
<div class="record-row">
<span
data-i18n="agencyOpening.recordRevenue"
>Opening Revenue:</span
>
<strong id="recordRevenue">100K</strong>
</div>
<div class="record-row">
<span data-i18n="agencyOpening.recordReward"
>Reward:</span
>
<strong id="recordReward">500K</strong>
</div>
</div>
</section>
</section>
</div>
</main>
</div>
<script>
window.HyAppI18nSupported = ['en', 'ar', 'tr', 'es', 'zh', 'id'];
</script>
<script src="../../common/i18n.js?v=20260612-agency-opening"></script>
<script src="../../common/api.js?v=20260612-agency-opening"></script>
<script src="./script.js?v=20260612-agency-opening"></script>
</body>
</html>

View File

@ -0,0 +1,441 @@
(function () {
var DESIGN_WIDTH = 1080;
var VIEWPORT_WIDTH = 375;
var DESIGN_HEIGHT = 4620;
var STORAGE_KEY = 'hy_agency_opening_end_at';
var viewport = document.getElementById('appViewport');
var stageWrap = document.getElementById('stageWrap');
var countdownTimer = 0;
var countdownEndAt = 0;
var timeOffsetMS = 0;
var statusLoadedFromAPI = false;
var state = null;
var countNodes = {
days: document.querySelector('[data-count="days"]'),
hours: document.querySelector('[data-count="hours"]'),
minutes: document.querySelector('[data-count="minutes"]'),
seconds: document.querySelector('[data-count="seconds"]'),
};
var applyButton = document.getElementById('applyButton');
var rewardRows = document.getElementById('rewardRows');
var rulesList = document.getElementById('rulesList');
var fallbackRewards = [
{ opening_revenue: '500K', reward_coins: '50K' },
{ opening_revenue: '1M', reward_coins: '100K' },
{ opening_revenue: '3M', reward_coins: '300K' },
{ opening_revenue: '5M', reward_coins: '500K' },
{ opening_revenue: '10M', reward_coins: '500K' },
];
function params() {
return new URLSearchParams(window.location.search || '');
}
function t(key, fallback) {
return window.HyAppI18n && window.HyAppI18n.t
? window.HyAppI18n.t(key, fallback)
: fallback || key;
}
function twoDigits(value) {
return String(Math.max(0, value)).padStart(2, '0');
}
function serverNow() {
return Date.now() - timeOffsetMS;
}
function updateViewportHeight() {
var scale = Math.min(window.innerWidth / VIEWPORT_WIDTH, 1);
var visibleHeight = Math.ceil(
DESIGN_HEIGHT * (VIEWPORT_WIDTH / DESIGN_WIDTH)
);
document.documentElement.style.setProperty(
'--app-scale',
String(scale)
);
stageWrap.style.height = visibleHeight + 'px';
viewport.style.minHeight = visibleHeight + 'px';
document.body.style.minHeight = Math.ceil(visibleHeight * scale) + 'px';
}
function fallbackEndAt() {
var queryEnd = Date.parse(
params().get('end_at') || params().get('endAt') || ''
);
if (Number.isFinite(queryEnd) && queryEnd > Date.now()) return queryEnd;
var cached = Number(window.localStorage.getItem(STORAGE_KEY));
if (Number.isFinite(cached) && cached > Date.now()) return cached;
var fallback =
Date.now() +
4 * 86400 * 1000 +
4 * 3600 * 1000 +
4 * 60 * 1000 +
4000;
window.localStorage.setItem(STORAGE_KEY, String(fallback));
return fallback;
}
function updateCountdown() {
var diff = Math.max(0, countdownEndAt - serverNow());
var totalSeconds = Math.floor(diff / 1000);
countNodes.days.textContent = twoDigits(
Math.floor(totalSeconds / 86400)
);
countNodes.hours.textContent = twoDigits(
Math.floor((totalSeconds % 86400) / 3600)
);
countNodes.minutes.textContent = twoDigits(
Math.floor((totalSeconds % 3600) / 60)
);
countNodes.seconds.textContent = twoDigits(totalSeconds % 60);
}
function firstValue(source, keys, fallback) {
if (!source) return fallback;
for (var index = 0; index < keys.length; index += 1) {
var value = source[keys[index]];
if (value !== undefined && value !== null && value !== '')
return value;
}
return fallback;
}
function normalizeStatus(raw) {
var data = raw || {};
var qualification =
data.qualification || data.qualifications || data.agency || {};
var record =
data.record || data.opening_record || data.openingRecord || {};
var joined = Boolean(
firstValue(data, ['joined', 'has_joined', 'hasJoined'], false)
);
var eligible = firstValue(
data,
['eligible', 'can_apply', 'canApply'],
true
);
var hostCount =
Number(
firstValue(qualification, ['host_count', 'hostCount'], 12)
) || 0;
var hostRequirement =
Number(
firstValue(
qualification,
['host_requirement', 'hostRequirement'],
10
)
) || 10;
return {
serverTimeMS:
Number(
firstValue(
data,
['server_time_ms', 'serverTimeMs'],
Date.now()
)
) || Date.now(),
endAt:
Number(
firstValue(
data.period || data.activity_period || {},
['end_ms', 'endMs'],
firstValue(data, ['period_end_ms', 'periodEndMs'], 0)
)
) || fallbackEndAt(),
qualification: {
identity: firstValue(
qualification,
['identity', 'agency_identity', 'agencyIdentity'],
'Agency'
),
hostCount: hostCount,
hostRequirement: hostRequirement,
openingStatus: firstValue(
qualification,
[
'opening_status',
'openingStatus',
'status_text',
'statusText',
],
joined
? t('agencyOpening.joined', 'Joined')
: t('agencyOpening.notJoined', 'Not Joined')
),
eligible: Boolean(eligible) && !joined,
},
rewards: Array.isArray(
data.rewards || data.reward_tiers || data.rewardTiers
)
? data.rewards || data.reward_tiers || data.rewardTiers
: fallbackRewards,
rules: Array.isArray(data.rules) ? data.rules : null,
record: {
guildName: firstValue(
record,
['guild_name', 'guildName', 'agency_name', 'agencyName'],
'Royal Family'
),
created: firstValue(
record,
['created_at', 'createdAt', 'created'],
'2026-06-01'
),
revenue: firstValue(
record,
['opening_revenue', 'openingRevenue', 'revenue'],
'100K'
),
reward: firstValue(
record,
['reward', 'reward_coins', 'rewardCoins'],
'500K'
),
},
joined: joined,
};
}
function mockStatus() {
var query = params();
return normalizeStatus({
period: { end_ms: fallbackEndAt() },
qualification: {
agency_identity: query.get('identity') || 'Agency',
host_count: query.get('hosts') || 12,
host_requirement: query.get('host_requirement') || 10,
opening_status: query.get('status') || 'Not Joined',
},
record: {
guild_name: query.get('guild') || 'Royal Family',
created_at: query.get('created') || '2026-06-01',
opening_revenue: query.get('revenue') || '100K',
reward: query.get('reward') || '500K',
},
joined: query.get('joined') === '1',
eligible: query.get('eligible') !== '0',
rewards: fallbackRewards,
server_time_ms: Date.now(),
});
}
function fetchStatus() {
if (params().get('mock') === '1' || !window.HyAppAPI) {
statusLoadedFromAPI = false;
return Promise.resolve(mockStatus());
}
return window.HyAppAPI.get('/api/v1/activities/agency-opening-event')
.then(function (data) {
statusLoadedFromAPI = true;
return normalizeStatus(data);
})
.catch(function (error) {
// 新活动页先独立上线,接口未部署或活动未配置时继续渲染 Figma 默认态,避免线上 WebView 出现空白页。
console.warn('agency opening event status fallback', error);
statusLoadedFromAPI = false;
return mockStatus();
});
}
function renderRewards(rewards) {
rewardRows.innerHTML = (
rewards && rewards.length ? rewards : fallbackRewards
)
.map(function (item) {
var revenue = firstValue(
item,
['opening_revenue', 'openingRevenue', 'revenue'],
'500K'
);
var reward = firstValue(
item,
['reward_coins', 'rewardCoins', 'reward'],
'50K'
);
return [
'<div class="reward-row">',
'<span>',
escapeHTML(revenue),
'</span>',
'<span>',
escapeHTML(reward),
'</span>',
'</div>',
].join('');
})
.join('');
}
function renderRules(rules) {
if (!rules || !rules.length) return;
rulesList.innerHTML = rules
.map(function (rule) {
return '<li>' + escapeHTML(rule) + '</li>';
})
.join('');
}
function applyStatus(data) {
state = data || mockStatus();
timeOffsetMS = Date.now() - state.serverTimeMS;
countdownEndAt = state.endAt;
document.getElementById('agencyIdentityText').textContent =
state.qualification.identity;
document.getElementById('hostCountText').textContent =
state.qualification.hostCount +
' / ' +
state.qualification.hostRequirement +
'+';
document.getElementById('openingStatusText').textContent =
state.qualification.openingStatus;
document.getElementById('eligibilityText').textContent = state
.qualification.eligible
? t('agencyOpening.eligible', 'Eligible To Apply')
: t('agencyOpening.notEligible', 'Not Eligible');
document.getElementById('recordGuildName').textContent =
state.record.guildName;
document.getElementById('recordCreated').textContent =
state.record.created;
document.getElementById('recordRevenue').textContent =
state.record.revenue;
document.getElementById('recordReward').textContent =
state.record.reward;
applyButton.classList.toggle('is-joined', state.joined);
applyButton.querySelector('span').textContent = state.joined
? t('agencyOpening.applied', 'Applied')
: t('agencyOpening.apply', 'Apply Opening Event');
renderRewards(state.rewards);
renderRules(state.rules);
updateCountdown();
if (countdownTimer) window.clearInterval(countdownTimer);
countdownTimer = window.setInterval(updateCountdown, 1000);
}
function showToast(message) {
if (window.HyAppToast && window.HyAppToast.show) {
window.HyAppToast.show(message);
return;
}
window.dispatchEvent(
new CustomEvent('hyapp:toast', { detail: { message: message } })
);
}
function commandID() {
var random = Math.random().toString(36).slice(2);
if (window.crypto && window.crypto.getRandomValues) {
var bytes = new Uint32Array(2);
window.crypto.getRandomValues(bytes);
random = bytes[0].toString(36) + bytes[1].toString(36);
}
return 'agency_opening_' + Date.now().toString(36) + '_' + random;
}
function submitApply() {
if (
!state ||
state.joined ||
applyButton.classList.contains('is-loading')
)
return;
if (!state.qualification.eligible) {
showToast(t('agencyOpening.notEligible', 'Not Eligible'));
return;
}
applyButton.classList.add('is-loading');
// 申请动作按 command_id 做点击级幂等;如果后端接口还没上线,页面只更新本地展示态,不伪造服务端成功事实。
var request = statusLoadedFromAPI
? window.HyAppAPI.post(
'/api/v1/activities/agency-opening-event/apply',
{
command_id: commandID(),
}
)
: Promise.resolve({
joined: true,
qualification: {
opening_status: t('agencyOpening.joined', 'Joined'),
},
});
request
.then(function (data) {
var next = Object.assign({}, state);
var payload = data || {};
var payloadQualification =
payload.qualification || payload.qualifications || {};
next.joined = true;
next.qualification = Object.assign({}, state.qualification, {
openingStatus: firstValue(
payloadQualification,
[
'opening_status',
'openingStatus',
'status_text',
'statusText',
],
t('agencyOpening.joined', 'Joined')
),
});
next.qualification.openingStatus = t(
'agencyOpening.joined',
'Joined'
);
next.qualification.eligible = false;
applyStatus(next);
showToast(
t('agencyOpening.applySuccess', 'Application submitted.')
);
})
.catch(function (error) {
console.warn('agency opening event apply failed', error);
showToast(
t(
'agencyOpening.applyFailed',
'Application failed. Try again later.'
)
);
})
.finally(function () {
applyButton.classList.remove('is-loading');
});
}
function escapeHTML(value) {
return String(value || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function init() {
document.title = t(
'agencyOpening.pageTitle',
'New Agency Opening Event'
);
updateViewportHeight();
fetchStatus().then(applyStatus);
applyButton.addEventListener('click', submitApply);
window.addEventListener('resize', updateViewportHeight);
window.addEventListener('hyapp:i18n-ready', function () {
document.title = t(
'agencyOpening.pageTitle',
'New Agency Opening Event'
);
if (state) applyStatus(state);
});
}
init();
})();

View File

@ -0,0 +1,678 @@
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
:root {
--app-scale: 1;
--design-scale: 0.3472222222;
--stage-height: 4620px;
--gold: #fcefc2;
--gold-strong: #ffb56b;
--deep-purple: #1a0029;
}
html,
body {
margin: 0;
width: 100%;
min-height: 100%;
overflow-x: hidden;
background: #12001d;
color: var(--gold);
font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
}
button {
border: 0;
padding: 0;
background: transparent;
color: inherit;
font: inherit;
cursor: pointer;
outline: none;
}
button:focus-visible {
outline: 3px solid rgba(255, 239, 194, 0.72);
outline-offset: 4px;
}
.app-viewport {
position: relative;
width: 375px;
min-height: 1605px;
margin: 0 auto;
transform: scale(var(--app-scale));
transform-origin: top center;
}
.page,
.stage-wrap {
position: relative;
width: 375px;
min-height: 1605px;
overflow: hidden;
background: var(--deep-purple);
}
.stage-wrap {
height: 1605px;
}
.stage {
position: absolute;
left: 0;
top: 0;
width: 1080px;
height: var(--stage-height);
overflow: hidden;
transform: scale(var(--design-scale));
transform-origin: top left;
background: var(--deep-purple);
}
.stage > * {
position: absolute;
z-index: 1;
}
.bg-hall {
left: 0;
width: 1080px;
height: 1919px;
object-fit: cover;
object-position: center center;
pointer-events: none;
z-index: 0;
}
.bg-hall-hero {
top: 94px;
-webkit-mask: url('./assets/mask-bg-hero.svg') 0 0 / 1080px 1919px no-repeat;
mask: url('./assets/mask-bg-hero.svg') 0 0 / 1080px 1919px no-repeat;
}
.bg-hall-top {
top: 1095px;
opacity: 0.4;
-webkit-mask: url('./assets/mask-bg-top.svg') 0 0 / 1080px 1919px no-repeat;
mask: url('./assets/mask-bg-top.svg') 0 0 / 1080px 1919px no-repeat;
}
.bg-hall-repeat-a {
top: 2233px;
opacity: 0.4;
-webkit-mask: url('./assets/mask-bg-repeat.svg') 0 0 / 1080px 1919px
no-repeat;
mask: url('./assets/mask-bg-repeat.svg') 0 0 / 1080px 1919px no-repeat;
}
.bg-hall-repeat-b {
top: 3163px;
opacity: 0.4;
-webkit-mask: url('./assets/mask-bg-repeat.svg') 0 0 / 1080px 1919px
no-repeat;
mask: url('./assets/mask-bg-repeat.svg') 0 0 / 1080px 1919px no-repeat;
}
.top-fade {
left: 0;
top: 0;
width: 1080px;
height: 235px;
pointer-events: none;
z-index: 1;
background: linear-gradient(180deg, #1a0029 41%, rgba(26, 0, 41, 0));
}
.language-tabs {
left: 18px;
top: 46px;
display: grid;
grid-template-columns: 221px repeat(5, 141px);
gap: 0;
align-items: center;
width: 1026px;
height: 70px;
direction: ltr;
z-index: 6;
}
.language-tab {
position: relative;
height: 57px;
border: 2px solid #fde5cc;
border-radius: 39px;
background: linear-gradient(180deg, #4d145a, #210a34);
color: #ffdfaf;
font-size: 22px;
font-weight: 800;
line-height: 27px;
text-align: center;
text-shadow: 0 1px 7px rgba(87, 16, 110, 0.82);
}
.language-tab:first-child {
width: 221px;
height: 65px;
border: 0;
border-radius: 0;
background: transparent;
isolation: isolate;
}
.language-tab span {
position: relative;
z-index: 2;
}
.language-tab:first-child::before,
.language-tab:first-child::after {
position: absolute;
content: '';
inset: 0 auto auto 0;
width: 221px;
height: 65px;
background-image: url('./assets/lang-sheen.png');
background-size: 221px 65px;
background-repeat: no-repeat;
z-index: 1;
-webkit-mask: url('./assets/lang-mask-left.svg') 0 0 / 100px 65px no-repeat;
mask: url('./assets/lang-mask-left.svg') 0 0 / 100px 65px no-repeat;
}
.language-tab:first-child::after {
transform: rotate(180deg) scaleY(-1);
-webkit-mask: url('./assets/lang-mask-right.svg') 121px 0 / 100px 65px
no-repeat;
mask: url('./assets/lang-mask-right.svg') 121px 0 / 100px 65px no-repeat;
}
.language-tab.is-active {
color: #fff8e4;
}
.language-tab:not(:first-child).is-active {
box-shadow:
inset 0 0 18px rgba(255, 222, 151, 0.2),
0 0 14px rgba(255, 203, 116, 0.22);
}
.hero {
inset: 0 auto auto 0;
width: 1080px;
height: 1339px;
}
.hero-title {
position: absolute;
left: 56px;
top: 107px;
width: 968px;
height: 425px;
object-fit: fill;
pointer-events: none;
}
.hero-subtitle {
position: absolute;
left: 345px;
top: 479px;
margin: 0;
color: #741eb5;
font-size: 30px;
font-weight: 800;
line-height: 38px;
text-transform: capitalize;
white-space: nowrap;
}
.hero-shadow {
position: absolute;
left: 182px;
top: 1257px;
width: 698px;
height: 89px;
mix-blend-mode: multiply;
pointer-events: none;
}
.hero-cat {
position: absolute;
left: 210px;
top: 519px;
width: 642px;
height: 820px;
object-fit: cover;
pointer-events: none;
}
.countdown {
left: 0;
top: 1338px;
width: 1080px;
height: 160px;
z-index: 3;
}
.count-cell {
position: absolute;
top: 0;
width: 201px;
height: 160px;
}
.count-days {
left: 87px;
}
.count-hours {
left: 321px;
}
.count-minutes {
left: 555px;
}
.count-seconds {
left: 789px;
}
.count-cell img {
position: absolute;
inset: 0;
width: 201px;
height: 160px;
object-fit: fill;
pointer-events: none;
}
.count-cell strong,
.count-cell span,
.section-title h2,
.apply-button span,
.qualification-card strong,
.reward-head strong,
.record-row strong {
background: linear-gradient(180deg, var(--gold) 50%, var(--gold-strong));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.count-cell strong {
position: absolute;
left: 0;
top: 32px;
width: 201px;
font-size: 52px;
font-weight: 900;
line-height: 52px;
text-align: center;
}
.count-cell span {
position: absolute;
left: 0;
top: 92px;
width: 201px;
font-size: 24px;
font-weight: 500;
line-height: 32px;
text-align: center;
white-space: nowrap;
}
.opening-section {
left: 0;
width: 1080px;
}
.qualification-section {
top: 1498px;
height: 735px;
}
.rewards-section {
top: 2245px;
height: 800px;
}
.rules-section {
top: 3057px;
height: 768px;
}
.record-section {
top: 3837px;
height: 672px;
}
.section-title {
position: absolute;
left: 93px;
top: 0;
width: 895px;
height: 189px;
z-index: 4;
}
.section-title img {
position: absolute;
inset: 0;
width: 895px;
height: 189px;
object-fit: fill;
pointer-events: none;
}
.section-title h2 {
position: absolute;
left: 0;
top: 101px;
width: 895px;
margin: 0;
font-size: 42px;
font-weight: 900;
line-height: 44px;
text-align: center;
text-transform: capitalize;
white-space: nowrap;
}
.ornate-panel {
position: absolute;
left: 19px;
width: 1042px;
height: var(--panel-height);
pointer-events: none;
z-index: 1;
}
.frame-qualification {
top: 72px;
--bottom-source-top: -561px;
--panel-height: 663px;
}
.frame-rewards {
top: 72px;
--bottom-source-top: -496px;
--panel-height: 728px;
}
.frame-rules {
top: 72px;
--bottom-source-top: -528px;
--panel-height: 696px;
}
.frame-record {
top: 72px;
--bottom-source-top: -624px;
--panel-height: 600px;
}
.frame-piece {
position: absolute;
left: 0;
width: 1042px;
height: 1224px;
background-size: 1042px 1224px;
background-repeat: no-repeat;
}
.frame-piece-top {
top: 0;
background-image: url('./assets/frame-top-source.png');
-webkit-mask: url('./assets/frame-top-mask.svg') 0 0 / 1042px 495px
no-repeat;
mask: url('./assets/frame-top-mask.svg') 0 0 / 1042px 495px no-repeat;
}
.frame-piece-bottom {
top: var(--bottom-source-top);
background-image: url('./assets/frame-bottom-source.png');
-webkit-mask: url('./assets/frame-bottom-mask.svg') 0 867px / 1042px 357px
no-repeat;
mask: url('./assets/frame-bottom-mask.svg') 0 867px / 1042px 357px no-repeat;
}
.qualification-grid {
position: absolute;
left: 109px;
top: 235px;
display: grid;
grid-template-columns: repeat(3, 270px);
gap: 20px;
z-index: 3;
}
.qualification-card {
position: relative;
width: 270px;
height: 154px;
border: 2px solid #fde5cc;
border-radius: 26px;
background: linear-gradient(180deg, #4d145a, #210a34);
}
.qualification-card p {
position: absolute;
left: 0;
top: 34px;
width: 270px;
margin: 0;
color: var(--gold);
font-size: 26px;
font-weight: 500;
line-height: 30px;
text-align: center;
}
.qualification-card strong {
position: absolute;
left: 0;
top: 88px;
width: 270px;
font-size: 38px;
font-weight: 900;
line-height: 40px;
text-align: center;
white-space: nowrap;
}
.qualification-card img {
position: absolute;
right: -14px;
bottom: -12px;
width: 70px;
height: 70px;
object-fit: contain;
pointer-events: none;
}
.apply-button {
position: absolute;
left: 217px;
top: 420px;
width: 646px;
height: 137px;
z-index: 4;
}
.apply-button img {
position: absolute;
inset: 0;
width: 646px;
height: 137px;
object-fit: fill;
pointer-events: none;
}
.apply-button span {
position: absolute;
left: 0;
top: 50px;
width: 646px;
font-size: 36px;
font-weight: 900;
line-height: 38px;
text-align: center;
white-space: nowrap;
}
.apply-button.is-loading {
filter: saturate(0.7);
pointer-events: none;
}
.apply-button.is-joined span {
opacity: 0.78;
}
.eligibility-text {
position: absolute;
left: 0;
top: 563px;
width: 1080px;
margin: 0;
color: var(--gold);
font-size: 28px;
font-weight: 500;
line-height: 32px;
text-align: center;
white-space: nowrap;
z-index: 3;
}
.reward-table {
position: absolute;
left: 90px;
top: 206px;
width: 900px;
z-index: 3;
}
.reward-head {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr;
align-items: center;
height: 71px;
}
.reward-head strong {
font-size: 34px;
font-weight: 900;
line-height: 38px;
text-align: center;
}
.table-line,
.reward-row::after,
.record-row::after {
position: absolute;
left: 26px;
right: 26px;
bottom: 0;
height: 2px;
content: '';
background: linear-gradient(
90deg,
rgba(255, 255, 192, 0),
#ffffc0 50%,
rgba(255, 255, 192, 0)
);
}
.table-line {
left: 26px;
right: 26px;
top: 71px;
bottom: auto;
}
.reward-rows {
position: relative;
width: 900px;
}
.reward-row {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr;
align-items: center;
width: 900px;
height: 80px;
background: url('./assets/table-row.svg') center center / 900px 76px
no-repeat;
color: var(--gold);
font-size: 28px;
font-weight: 800;
line-height: 38px;
text-align: center;
}
.rules-list {
position: absolute;
left: 119px;
top: 306px;
width: 842px;
margin: 0;
padding: 0 0 0 48px;
color: var(--gold);
font-size: 32px;
font-weight: 500;
line-height: 52px;
text-transform: capitalize;
z-index: 3;
}
.rules-list li {
padding-left: 0;
}
.record-table {
position: absolute;
left: 90px;
top: 220px;
width: 900px;
z-index: 3;
}
.record-row {
position: relative;
display: grid;
grid-template-columns: 398px 1fr;
align-items: center;
width: 900px;
height: 80px;
background: url('./assets/table-row.svg') center center / 900px 76px
no-repeat;
}
.record-row span {
color: var(--gold);
font-size: 28px;
font-weight: 800;
line-height: 38px;
text-align: right;
white-space: nowrap;
}
.record-row strong {
padding-left: 104px;
font-size: 34px;
font-weight: 900;
line-height: 38px;
white-space: nowrap;
}
@media (min-width: 431px) {
html,
body {
background: #100019;
}
}

View File

@ -557,7 +557,10 @@ var default_api = 'https://api.global-interaction.com/';
list: function (status, pageSize) {
return request('/api/v1/role-invitations', {
method: 'GET',
query: { status: status || 'pending', page_size: pageSize || 50 },
query: {
status: status || 'pending',
page_size: pageSize || 50,
},
});
},
process: function (invitationID, payload) {
@ -641,6 +644,12 @@ var default_api = 'https://api.global-interaction.com/';
body: payload || {},
});
},
createBDLeader: function (payload) {
return request('/api/v1/manager-center/bd-leaders', {
method: 'POST',
body: payload || {},
});
},
};
var superadminCenterAPI = bdLeaderCenterAPI;

View File

@ -372,7 +372,7 @@
id="inviteTitle"
data-i18n="agency_center.invite_host"
>
How to invite host
Invite Host
</h2>
<button
class="sheet-close"
@ -384,32 +384,31 @@
&times;
</button>
</div>
<div class="invite-steps">
<div class="invite-step">
<span>1</span>
<p data-i18n="agency_center.invite_step_1">
Invite users to download APP.
</p>
</div>
<div class="invite-step">
<span>2</span>
<p data-i18n="agency_center.invite_step_2">
Invited users need to log in to the APP.
</p>
</div>
<div class="invite-step">
<span>3</span>
<p data-i18n="agency_center.invite_step_3">
Me > Host Center > Apply to join the team.
</p>
</div>
<div class="invite-step">
<span>4</span>
<p data-i18n="agency_center.invite_step_4">
Enter the team ID, click send to join.
</p>
</div>
</div>
<form class="host-search" id="inviteHostSearchForm">
<input
id="inviteHostSearchInput"
type="text"
inputmode="text"
autocomplete="off"
placeholder="Enter host ID"
data-i18n-placeholder="agency_center.enter_host_id"
/>
<button
id="inviteHostSearchButton"
type="submit"
data-i18n="agency_center.search"
>
Search
</button>
</form>
<div
class="invite-status"
id="inviteHostStatus"
role="status"
hidden
></div>
<div class="message-list" id="inviteHostList"></div>
<div class="sheet-empty" id="inviteHostEmpty" hidden></div>
</section>
</div>

View File

@ -737,6 +737,32 @@ input:focus-visible {
);
}
.host-search button:disabled {
background: var(--hy-theme-button-disabled, #c5a6f6);
box-shadow: none;
}
.invite-status {
margin: 12px 18px 0;
padding: 10px 12px;
border-radius: 8px;
background: var(--primary-soft);
color: var(--primary-deep);
font-size: 12px;
font-weight: 850;
line-height: 1.35;
}
.invite-status.is-error {
background: #fff0f0;
color: #c43f3f;
}
.invite-status.is-success {
background: #ebfbf2;
color: #128946;
}
.host-row,
.message-card {
display: grid;
@ -850,6 +876,10 @@ input:focus-visible {
padding: 0 10px;
}
.message-action:disabled {
opacity: 0.58;
}
.message-action.agree {
background: var(--button);
color: #fff;

View File

@ -550,6 +550,71 @@
color: #15914f;
}
.message-card {
display: grid;
grid-template-columns: 44px minmax(0, 1fr) auto;
gap: 10px;
align-items: center;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fbfcfc;
}
.message-avatar {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
overflow: hidden;
border-radius: 50%;
background: var(--primary);
}
.message-avatar img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.message-fallback {
color: var(--primary-deep);
font-size: 18px;
font-weight: 950;
}
.message-main {
min-width: 0;
}
.message-title {
overflow: hidden;
color: var(--text);
font-size: 15px;
font-weight: 900;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.message-meta {
margin-top: 4px;
overflow: hidden;
color: var(--muted);
font-size: 12px;
font-weight: 800;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.message-actions {
display: flex;
gap: 6px;
}
@media (max-width: 360px) {
.salary-card,
.team-stats-card,
@ -567,12 +632,14 @@
}
.member-row,
.invite-user-card {
.invite-user-card,
.message-card {
grid-template-columns: 44px minmax(0, 1fr);
}
.member-side,
.invite-user-side {
.invite-user-side,
.message-actions {
grid-column: 2;
justify-items: start;
text-align: left;

View File

@ -9,7 +9,10 @@
<title>Control Panel</title>
<link rel="stylesheet" href="../../common/theme.css?v=20260608" />
<link rel="stylesheet" href="../common/center.css?v=20260608" />
<link rel="stylesheet" href="./style.css?v=20260608-manager-unblock" />
<link
rel="stylesheet"
href="./style.css?v=20260612-manager-bd-leader"
/>
</head>
<body>
@ -60,11 +63,16 @@
type="button"
data-action="send-frame"
>
<span class="tile-icon frame-icon" aria-hidden="true">
<span
class="tile-icon frame-icon"
aria-hidden="true"
>
<svg viewBox="0 0 48 48">
<path d="M10 10h28v28H10Z" />
<path d="M16 16h16v16H16Z" />
<path d="M10 18h6M32 18h6M10 30h6M32 30h6" />
<path
d="M10 18h6M32 18h6M10 30h6M32 30h6"
/>
</svg>
</span>
<span
@ -83,8 +91,12 @@
<svg viewBox="0 0 48 48">
<path d="M9 28h30l-4-10H13Z" />
<path d="M13 28v7M35 28v7" />
<path d="M15 35a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z" />
<path d="M33 35a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z" />
<path
d="M15 35a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z"
/>
<path
d="M33 35a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z"
/>
<path d="M18 18h12" />
</svg>
</span>
@ -102,7 +114,9 @@
>
<span class="tile-icon vip-icon" aria-hidden="true">
<svg viewBox="0 0 48 48">
<path d="m9 18 8 8 7-14 7 14 8-8-4 20H13Z" />
<path
d="m9 18 8 8 7-14 7 14 8-8-4 20H13Z"
/>
<path d="M13 38h22" />
<path d="M20 29h8" />
</svg>
@ -140,12 +154,98 @@
>
</button>
<button
class="manager-tile"
type="button"
data-action="add-bd-leader"
>
<span
class="tile-icon bd-leader-icon"
aria-hidden="true"
>
<svg viewBox="0 0 48 48">
<path
d="M24 7a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z"
/>
<path
d="M9 42c2.3-8 7.4-12 15-12s12.7 4 15 12"
/>
<path d="M36 8v10M31 13h10" />
</svg>
</span>
<span
class="tile-title"
data-i18n="manager_center.add_bd_leader"
>Add BD Leader</span
>
</button>
<button
class="manager-tile"
type="button"
data-action="add-admin"
>
<span
class="tile-icon admin-icon"
aria-hidden="true"
>
<svg viewBox="0 0 48 48">
<path
d="M24 7a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z"
/>
<path
d="M9 42c2.3-8 7.4-12 15-12s12.7 4 15 12"
/>
<path
d="m35 9 2.2 4.4 4.8.7-3.5 3.4.8 4.8-4.3-2.3-4.3 2.3.8-4.8-3.5-3.4 4.8-.7Z"
/>
</svg>
</span>
<span
class="tile-title"
data-i18n="manager_center.add_admin"
>Add Admin</span
>
</button>
<button
class="manager-tile"
type="button"
data-action="add-superadmin"
>
<span
class="tile-icon superadmin-icon"
aria-hidden="true"
>
<svg viewBox="0 0 48 48">
<path
d="M24 7a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z"
/>
<path
d="M9 42c2.3-8 7.4-12 15-12s12.7 4 15 12"
/>
<path
d="m34 8 2.5 5 5.5.8-4 3.9.9 5.5-4.9-2.6-4.9 2.6.9-5.5-4-3.9 5.5-.8Z"
/>
<path d="M34 12v8M30 16h8" />
</svg>
</span>
<span
class="tile-title"
data-i18n="manager_center.add_superadmin"
>Add Superadmin</span
>
</button>
<button
class="manager-tile manager-tile-wide"
type="button"
data-action="block-user"
>
<span class="tile-icon block-icon" aria-hidden="true">
<span
class="tile-icon block-icon"
aria-hidden="true"
>
<svg viewBox="0 0 48 48">
<path
d="M18 22a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"
@ -246,12 +346,7 @@
</section>
</div>
<div
class="block-modal"
id="blockModal"
aria-hidden="true"
hidden
>
<div class="block-modal" id="blockModal" aria-hidden="true" hidden>
<button
class="block-backdrop"
id="blockBackdrop"
@ -375,10 +470,10 @@
</div>
<script src="../../common/toast.js"></script>
<script src="../../common/api.js?v=20260608-manager-real"></script>
<script src="../../common/api.js?v=20260612-manager-bd-leader"></script>
<script src="../../common/params.js?v=20260608-manager-real"></script>
<script src="../../common/jsbridge.js"></script>
<script src="../../common/i18n.js?v=20260608"></script>
<script src="./script.js?v=20260608-manager-duration"></script>
<script src="./script.js?v=20260612-manager-bd-leader"></script>
</body>
</html>

View File

@ -5,6 +5,9 @@
'send-car': 'Send Car',
'send-vip': 'Send VIP',
'update-user-level': 'Update User Level',
'add-bd-leader': 'Add BD Leader',
'add-admin': 'Add Admin',
'add-superadmin': 'Add Superadmin',
};
var DAY_MS = 24 * 60 * 60 * 1000;
var PERMANENT_BLOCK_MS = 10 * 365 * DAY_MS;
@ -54,6 +57,12 @@
selectedVipLevel: 0,
submitting: false,
};
var bdLeaderState = {
action: '',
title: '',
searchedUser: null,
submitting: false,
};
function $(id) {
return document.getElementById(id);
@ -193,7 +202,10 @@
target_avatar: item.target_avatar || item.targetAvatar || '',
country: item.country || '',
blocked_until_ms:
item.blocked_until_ms || item.blockedUntilMs || item.unblockAtMs || 0,
item.blocked_until_ms ||
item.blockedUntilMs ||
item.unblockAtMs ||
0,
status: item.status || 'active',
};
}
@ -300,7 +312,11 @@
function ensureTrackSelector() {
var panel = $('levelPanel');
if (!panel || $('levelTrackGrid')) return;
var title = createNode('div', 'block-section-title', 'Select level type');
var title = createNode(
'div',
'block-section-title',
'Select level type'
);
var grid = createNode('div', 'track-grid');
grid.id = 'levelTrackGrid';
Object.keys(TRACK_LABELS).forEach(function (track) {
@ -359,7 +375,9 @@
}
function renderLevelButtons() {
var trackButtons = document.querySelectorAll('#levelTrackGrid [data-track]');
var trackButtons = document.querySelectorAll(
'#levelTrackGrid [data-track]'
);
trackButtons.forEach(function (button) {
button.classList.toggle(
'is-selected',
@ -388,7 +406,9 @@
}
function renderDurationButtons() {
var buttons = document.querySelectorAll('#durationGrid [data-duration-ms]');
var buttons = document.querySelectorAll(
'#durationGrid [data-duration-ms]'
);
buttons.forEach(function (button) {
var isSelected =
Number(button.getAttribute('data-duration-ms')) ===
@ -679,7 +699,8 @@
grantState.title = ACTION_LABELS[action] || 'Send';
grantState.searchedUser = null;
grantState.selectedResourceId = '';
grantState.selectedResourceDurationMs = DEFAULT_RESOURCE_GRANT_DURATION_MS;
grantState.selectedResourceDurationMs =
DEFAULT_RESOURCE_GRANT_DURATION_MS;
grantState.selectedVipLevel = 0;
$('grantDialogTitle').textContent = grantState.title;
$('grantSearchInput').value = '';
@ -688,7 +709,8 @@
$('grantModal').hidden = false;
$('grantModal').setAttribute('aria-hidden', 'false');
renderGrantOptions();
if (grantState.resourceType) loadGrantResources(grantState.resourceType);
if (grantState.resourceType)
loadGrantResources(grantState.resourceType);
setTimeout(function () {
$('grantSearchInput').focus();
}, 30);
@ -732,10 +754,14 @@
var container = $('grantSearchResult');
container.innerHTML = '';
container.appendChild(
renderUserRow(grantState.searchedUser, 'Select', function () {
$('grantPanel').hidden = false;
renderGrantOptions();
})
renderUserRow(
grantState.searchedUser,
'Select',
function () {
$('grantPanel').hidden = false;
renderGrantOptions();
}
)
);
$('grantPanel').hidden = false;
renderGrantOptions();
@ -777,9 +803,12 @@
});
} else {
$('grantOptionTitle').textContent = 'Select resource';
var resources = pageState.resourcesByType[grantState.resourceType] || [];
var resources =
pageState.resourcesByType[grantState.resourceType] || [];
if (!resources.length) {
grid.appendChild(createNode('div', 'blocked-empty', 'No resource available.'));
grid.appendChild(
createNode('div', 'blocked-empty', 'No resource available.')
);
}
resources.forEach(function (resource) {
var button = createNode('button', 'grant-resource-option');
@ -789,7 +818,8 @@
String(grantState.selectedResourceId) ===
String(resource.resource_id)
);
button.textContent = resource.name || 'Resource #' + resource.resource_id;
button.textContent =
resource.name || 'Resource #' + resource.resource_id;
button.addEventListener('click', function () {
grantState.selectedResourceId = resource.resource_id;
renderGrantOptions();
@ -854,6 +884,132 @@
});
}
function ensureBDLeaderModal() {
if ($('bdLeaderModal')) return;
var modal = createNode('div', 'block-modal bd-leader-modal');
modal.id = 'bdLeaderModal';
modal.hidden = true;
modal.setAttribute('aria-hidden', 'true');
modal.innerHTML =
'<button class="block-backdrop" id="bdLeaderBackdrop" type="button" aria-label="Close"></button>' +
'<section class="block-dialog" role="dialog" aria-modal="true" aria-labelledby="bdLeaderDialogTitle">' +
'<div class="block-dialog-header"><h2 id="bdLeaderDialogTitle">Add BD Leader</h2>' +
'<button class="block-close" id="bdLeaderCloseButton" type="button" aria-label="Close"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button></div>' +
'<div class="block-dialog-body"><div class="block-field"><label for="bdLeaderSearchInput">User ID</label>' +
'<div class="block-search-row"><input id="bdLeaderSearchInput" type="text" inputmode="numeric" autocomplete="off" placeholder="Enter user ID"/><button id="bdLeaderSearchButton" type="button">Search</button></div></div>' +
'<div class="block-search-result" id="bdLeaderSearchResult" aria-live="polite"></div>' +
'<div class="level-panel" id="bdLeaderPanel" hidden><div class="block-section-title">Add this user as BD Leader</div>' +
'<button class="confirm-level-button" id="confirmBDLeaderButton" type="button" disabled>Add BD Leader</button></div></div></section>';
document.querySelector('.manager-center').appendChild(modal);
$('bdLeaderBackdrop').addEventListener('click', closeBDLeaderModal);
$('bdLeaderCloseButton').addEventListener('click', closeBDLeaderModal);
$('bdLeaderSearchButton').addEventListener('click', searchBDLeaderUser);
$('bdLeaderSearchInput').addEventListener('keydown', function (event) {
if (event.key === 'Enter') searchBDLeaderUser();
});
$('confirmBDLeaderButton').addEventListener('click', confirmBDLeader);
}
function openBDLeaderModal(action) {
if (pageState.denied) {
toast('Permission denied.');
return;
}
ensureBDLeaderModal();
bdLeaderState.action = action;
bdLeaderState.title = ACTION_LABELS[action] || 'Add BD Leader';
bdLeaderState.searchedUser = null;
bdLeaderState.submitting = false;
$('bdLeaderDialogTitle').textContent = bdLeaderState.title;
$('confirmBDLeaderButton').textContent = bdLeaderState.title;
$('bdLeaderSearchInput').value = '';
$('bdLeaderSearchResult').innerHTML = '';
$('bdLeaderPanel').hidden = true;
renderBDLeaderActions();
$('bdLeaderModal').hidden = false;
$('bdLeaderModal').setAttribute('aria-hidden', 'false');
setTimeout(function () {
$('bdLeaderSearchInput').focus();
}, 30);
}
function closeBDLeaderModal() {
var modal = $('bdLeaderModal');
if (!modal) return;
modal.hidden = true;
modal.setAttribute('aria-hidden', 'true');
}
function searchBDLeaderUser() {
var value = $('bdLeaderSearchInput').value.trim();
if (!value) {
toast('Enter user ID.');
return;
}
apiSearchUser(value)
.then(function (items) {
if (!items.length) {
bdLeaderState.searchedUser = null;
$('bdLeaderSearchResult').innerHTML = '';
$('bdLeaderPanel').hidden = true;
renderBDLeaderActions();
toast('No user found.');
return;
}
bdLeaderState.searchedUser = items[0];
var container = $('bdLeaderSearchResult');
container.innerHTML = '';
container.appendChild(
renderUserRow(
bdLeaderState.searchedUser,
'Select',
function () {
$('bdLeaderPanel').hidden = false;
renderBDLeaderActions();
}
)
);
$('bdLeaderPanel').hidden = false;
renderBDLeaderActions();
})
.catch(showError);
}
function renderBDLeaderActions() {
var confirm = $('confirmBDLeaderButton');
if (!confirm) return;
confirm.disabled =
!bdLeaderState.searchedUser || bdLeaderState.submitting;
}
function confirmBDLeader() {
if (!bdLeaderState.searchedUser) {
toast('Search a user first.');
return;
}
if (!api || !api.createBDLeader) {
toast('API not available.');
return;
}
if (bdLeaderState.submitting) return;
bdLeaderState.submitting = true;
renderBDLeaderActions();
api.createBDLeader({
command_id: commandId('manager-bd-leader'),
target_user_id: userId(bdLeaderState.searchedUser),
reason: 'manager_center',
})
.then(function () {
toast('BD Leader added.');
closeBDLeaderModal();
})
.catch(showError)
.finally(function () {
bdLeaderState.submitting = false;
renderBDLeaderActions();
});
}
function bindEvents() {
var backButton = $('backButton');
if (backButton) backButton.addEventListener('click', goBack);
@ -868,6 +1024,14 @@
openLevelModal();
return;
}
if (
action === 'add-bd-leader' ||
action === 'add-admin' ||
action === 'add-superadmin'
) {
openBDLeaderModal(action);
return;
}
openGrantModal(action);
});
});
@ -885,21 +1049,26 @@
var confirmLevelButton = $('confirmLevelButton');
if (closeButton) closeButton.addEventListener('click', closeBlockModal);
if (backdrop) backdrop.addEventListener('click', closeBlockModal);
if (levelCloseButton) levelCloseButton.addEventListener('click', closeLevelModal);
if (levelBackdrop) levelBackdrop.addEventListener('click', closeLevelModal);
if (searchButton) searchButton.addEventListener('click', searchBlockUser);
if (levelCloseButton)
levelCloseButton.addEventListener('click', closeLevelModal);
if (levelBackdrop)
levelBackdrop.addEventListener('click', closeLevelModal);
if (searchButton)
searchButton.addEventListener('click', searchBlockUser);
if (searchInput) {
searchInput.addEventListener('keydown', function (event) {
if (event.key === 'Enter') searchBlockUser();
});
}
if (levelSearchButton) levelSearchButton.addEventListener('click', searchLevelUser);
if (levelSearchButton)
levelSearchButton.addEventListener('click', searchLevelUser);
if (levelSearchInput) {
levelSearchInput.addEventListener('keydown', function (event) {
if (event.key === 'Enter') searchLevelUser();
});
}
if (confirmButton) confirmButton.addEventListener('click', confirmBlockUser);
if (confirmButton)
confirmButton.addEventListener('click', confirmBlockUser);
if (confirmLevelButton) {
confirmLevelButton.addEventListener('click', confirmLevelUpdate);
}
@ -913,11 +1082,13 @@
);
});
});
document.querySelectorAll('#levelGrid [data-level]').forEach(function (button) {
button.addEventListener('click', function () {
selectLevel(button.getAttribute('data-level'));
document
.querySelectorAll('#levelGrid [data-level]')
.forEach(function (button) {
button.addEventListener('click', function () {
selectLevel(button.getAttribute('data-level'));
});
});
});
}
function init() {

View File

@ -15,10 +15,15 @@
.manager-center.is-loading .manager-panel::after {
position: absolute;
inset: 16px;
content: "";
content: '';
border-radius: 10px;
background:
linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.72), transparent)
linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.72),
transparent
)
0 0 / 180px 100% no-repeat,
linear-gradient(#f1e7ff, #f1e7ff) 0 0 / 100% 132px no-repeat,
linear-gradient(#f1e7ff, #f1e7ff) 0 144px / 100% 132px no-repeat,
@ -167,6 +172,21 @@
background: #fff1f4;
}
.bd-leader-icon {
color: #3c8a6b;
background: #eaf8f1;
}
.admin-icon {
color: #4e78d7;
background: #eef4ff;
}
.superadmin-icon {
color: #bd6c2f;
background: #fff2e6;
}
.block-modal[hidden] {
display: none;
}