This commit is contained in:
zhx 2026-06-18 18:03:49 +08:00
parent cdd674f3d9
commit 266cacc036
77 changed files with 3265 additions and 26 deletions

View File

@ -110,6 +110,8 @@
data.qualification || data.qualifications || data.agency || {};
var record =
data.record || data.opening_record || data.openingRecord || {};
var application =
data.application || data.opening_application || data.openingApplication || {};
var joined = Boolean(
firstValue(data, ['joined', 'has_joined', 'hasJoined'], false)
);
@ -197,7 +199,20 @@
['reward', 'reward_coins', 'rewardCoins'],
'500K'
),
scoreEndMS:
Number(
firstValue(
record,
['score_end_ms', 'scoreEndMs'],
firstValue(
application,
['score_end_ms', 'scoreEndMs'],
0
)
)
) || 0,
},
application: application,
joined: joined,
};
}
@ -368,7 +383,10 @@
: Promise.resolve({
joined: true,
qualification: {
opening_status: t('agencyOpening.joined', 'Joined'),
opening_status: t(
'agencyOpening.pendingReview',
'Pending Review'
),
},
});
@ -388,13 +406,9 @@
'status_text',
'statusText',
],
t('agencyOpening.joined', 'Joined')
t('agencyOpening.pendingReview', 'Pending Review')
),
});
next.qualification.openingStatus = t(
'agencyOpening.joined',
'Joined'
);
next.qualification.eligible = false;
applyStatus(next);
showToast(

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

423
activity/wheel/index.html Normal file
View File

@ -0,0 +1,423 @@
<!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 data-i18n="wheel.pageTitle">Yumi Wheel</title>
<link
rel="stylesheet"
href="../../common/theme.css?v=20260618-wheel-batch2"
/>
<link rel="stylesheet" href="./style.css?v=20260618-wheel-batch2" />
</head>
<body>
<div id="app" v-cloak>
<main
class="wheel-page"
:class="pageClass"
data-i18n-aria="wheel.pageLabel"
aria-label="Yumi Wheel"
>
<div v-if="pageLoading" class="wheel-loading">
{{ common('loading') }}
</div>
<section v-else class="wheel-stage">
<nav class="lucky-tab" aria-label="Wheel type">
<button
v-for="tab in tabs"
v-show="enabled[tab.key]"
:key="tab.key"
type="button"
class="tab-item"
:class="[tab.key, { active: currentPage === tab.key }]"
@click="handleSelect(tab.key)"
>
{{ t(tab.labelKey) }}
</button>
</nav>
<div class="marquee-box" v-if="currentConfig.hasMarqueeBox">
<div class="marquee-content">
<div
class="reward"
:class="{ 'reward-active': currentLanguage === 'ar' }"
:style="{ animationDuration: durationSpeed }"
>
<div
v-for="(item, index) in marqueeRewards"
:key="index"
class="reward-list"
>
<img
:src="item.avatar"
class="img"
alt=""
/>
<span
>{{ replaceTwo('userAward',
item.nickname, item.value) }}</span
>
</div>
</div>
</div>
</div>
<div class="marquee-content" v-else>
<div
class="reward"
:class="{ 'reward-active': currentLanguage === 'ar' }"
:style="{ animationDuration: durationSpeed }"
>
<div
v-for="(item, index) in marqueeRewards"
:key="index"
class="reward-list"
>
<img :src="item.avatar" class="img" alt="" />
<span
>{{ replaceTwo('userAward', item.nickname,
item.value) }}</span
>
</div>
</div>
</div>
<div class="lucky-content">
<div class="content">
<img
v-if="currentConfig.lightPrefix"
:src="asset(currentConfig.lightPrefix + active.currentBackground + '.png')"
class="right"
alt=""
/>
<div
class="lottery-grid"
:class="currentConfig.gridClass"
>
<div
v-if="currentConfig.type === 'round'"
class="turntable-content"
>
<div
v-for="(item, index) in active.gridItems"
:key="item.id || index"
class="grid-item"
:style="getPrizePosition(index)"
role="button"
tabindex="0"
@click.stop="openPrizePreview(item)"
@keyup.enter.prevent="openPrizePreview(item)"
>
<div class="item">
<img
:src="item.merchandise.coverUrl"
class="img"
alt=""
/>
<div class="item-gold">
<img
:src="asset('gold_new.png')"
alt=""
/>{{ item.value }}
</div>
</div>
</div>
</div>
<template v-else>
<div
v-for="(item, index) in active.gridItems"
:key="item.id || index"
class="grid-item"
:class="{ active: active.activeIndex === index }"
role="button"
tabindex="0"
@click.stop="openPrizePreview(item)"
@keyup.enter.prevent="openPrizePreview(item)"
>
<img
v-if="currentPage === 'advanced' && index > 5"
:src="asset('advanced_gift.webp')"
class="gift-webp"
alt=""
/>
<img
:src="item.merchandise.coverUrl"
class="img"
alt=""
/>
<div class="item-gold">
<img
:src="asset('gold_new.png')"
alt=""
/>{{ item.value }}
</div>
</div>
</template>
<div
v-if="active.isInit && !active.gridItems.length"
class="wheel-empty"
>
{{ common('rewardUnavailable') }}
</div>
<button
class="start-button"
type="button"
:style="pointerStyle()"
:disabled="!active.gridItems.length || active.drawAmount <= 0 || active.isSpinning"
@click="startLottery"
>
<template v-if="currentPage === 'advanced'">
<span>{{ common('balance') }}:</span>
<strong
><i></i>{{ userBalance }}</strong
>
</template>
</button>
</div>
</div>
</div>
<div class="btn-start">
<button
v-for="(item, index) in active.goldBtn"
:key="item.value"
type="button"
class="btn"
:class="{ active: active.activeBtnIndex === index }"
:style="buttonStyle(index)"
:disabled="!active.gridItems.length || active.drawAmount <= 0"
@click="handleGoldBtn(index)"
>
<span>{{ item.name }}</span>
<strong
>{{ item.value * active.drawAmount }}</strong
>
</button>
</div>
<div class="desc-content">
<button
class="rule"
type="button"
:aria-label="common('rule')"
@click="active.showRecord = true"
></button>
<button
class="record"
type="button"
:aria-label="t('historyRecord')"
@click="active.showRule = true"
></button>
</div>
<div v-if="currentPage !== 'advanced'" class="user-gold">
<img :src="asset('gold_new.png')" alt="" />{{
userBalance }}
</div>
</section>
<div
v-if="active.showRule"
class="wheel-popup"
@click.self="active.showRule = false"
>
<section class="rule-content">
<button
class="popup-close rule-close"
type="button"
@click="active.showRule = false"
>
×
</button>
<div class="rule-title">{{ common('rule') }}</div>
<div
v-if="currentPage !== 'advanced'"
class="rule-top"
></div>
<div class="rule-center">
<p class="rule-desc">{{ t('rule1') }}</p>
<p class="rule-desc">{{ t('rule2') }}</p>
<p class="rule-desc">{{ t('rule3') }}</p>
</div>
<div
v-if="currentPage !== 'advanced'"
class="rule-bottom"
></div>
</section>
</div>
<div
v-if="active.showRecord"
class="wheel-popup"
@click.self="active.showRecord = false"
>
<section class="popup-content">
<button
class="popup-close icon rule-close"
type="button"
@click="active.showRecord = false"
>
×
</button>
<div class="bg-title">{{ t('historyRecord') }}</div>
<div
v-if="currentPage !== 'advanced'"
class="rule-top"
></div>
<div class="rule-center">
<div class="record-list">
<article
v-for="(item, index) in active.historyRecord"
:key="index"
class="record-item list"
>
<header class="item-title">
<span class="left"
>{{ formatDate(item.createdTime,
'yyyy-MM-dd') }}</span
>
<span
>{{ formatDate(item.createdTime,
'HH:mm:ss') }}</span
>
<strong class="right"
>X{{ item.drawTimes }}</strong
>
</header>
<div class="box-content">
<div
v-for="(reward, rewardIndex) in item.rewards"
:key="rewardIndex"
class="box-desc"
>
<div
class="box previewable-box"
role="button"
tabindex="0"
@click.stop="openPrizePreview(reward)"
@keyup.enter.prevent="openPrizePreview(reward)"
>
<img
:src="reward.coverUrl"
class="img"
alt=""
/>
</div>
<span>X{{ reward.count }}</span>
</div>
</div>
</article>
<p v-if="active.finished" class="finished-text">
{{ common('noDate') }}
</p>
</div>
</div>
<div
v-if="currentPage !== 'advanced'"
class="rule-bottom"
></div>
</section>
</div>
<div
v-if="active.showReward"
class="wheel-popup"
@click.self="active.showReward = false"
>
<section class="reward-content">
<div class="bg"></div>
<div class="title">{{ common('rewards') }}</div>
<button
class="popup-close reward-close"
type="button"
@click="active.showReward = false"
>
×
</button>
<div class="content">
<div class="box-content">
<div
v-for="(item, index) in active.currentReward.rewards"
:key="index"
class="box-desc"
>
<div
class="box previewable-box"
role="button"
tabindex="0"
@click.stop="openPrizePreview(item)"
@keyup.enter.prevent="openPrizePreview(item)"
>
<img
:src="item.coverUrl"
class="img"
alt=""
/>
</div>
<span>X{{ item.count }}</span>
</div>
</div>
<!-- <p class="tips">
{{ replaceOne('rewardTips',
active.currentReward.rewardValue) }}
</p> -->
<button
class="again-btn"
type="button"
@click="startLottery"
>
{{ t('drawAgain') }}
</button>
</div>
</section>
</div>
<div
v-if="preview.open"
class="material-preview-overlay"
role="button"
tabindex="0"
aria-label="Close preview"
@click="closePrizePreview"
@keyup.enter.prevent="closePrizePreview"
@keyup.space.prevent="closePrizePreview"
@keyup.esc.prevent="closePrizePreview"
>
<button
class="material-preview-close"
type="button"
aria-label="Close preview"
@click.stop="closePrizePreview"
>
×
</button>
<div class="material-preview-stage">
<img
v-if="preview.coverUrl"
ref="previewImage"
:src="preview.coverUrl"
class="material-preview-image"
:class="{ 'is-dynamic-hidden': preview.animationReady }"
alt=""
/>
<div
ref="previewEffect"
class="material-preview-effect"
></div>
</div>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.min.js"></script>
<script src="../../common/i18n.js?v=20260618-wheel-batch2"></script>
<script src="../../common/api.js?v=20260618-wheel-batch2"></script>
<script src="../../common/toast.js?v=20260618-wheel-batch2"></script>
<script src="../../common/effect-player.js?v=20260618-wheel-batch2"></script>
<script src="./script.js?v=20260618-wheel-batch2"></script>
</body>
</html>

1355
activity/wheel/script.js Normal file

File diff suppressed because it is too large Load Diff

1183
activity/wheel/style.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -756,6 +756,53 @@ var default_api = 'https://api.global-interaction.com/';
},
};
// 转盘 H5 只通过 gateway 活动接口读配置、抽奖、查记录和跑马灯提示;页面不再内置 mock 奖品,避免后台资源组未配置时前端仍展示假奖池。
// 抽奖 command_id 由页面点击时生成并随请求提交,服务端用它做幂等,网络重试不会重复扣费或重复发奖。
var activityWheelAPI = {
config: function (wheelID) {
return request(
'/api/v1/activity/wheel/' +
encodeURIComponent(wheelID || 'classic') +
'/config',
{ method: 'GET' }
);
},
draw: function (wheelID, payload) {
return request(
'/api/v1/activity/wheel/' +
encodeURIComponent(wheelID || 'classic') +
'/draw',
{
method: 'POST',
body: payload || {},
}
);
},
history: function (wheelID, params) {
return request(
'/api/v1/activity/wheel/' +
encodeURIComponent(wheelID || 'classic') +
'/history',
{
method: 'POST',
query: params || {},
body: {},
}
);
},
hints: function (wheelID) {
return request(
'/api/v1/activity/wheel/' +
encodeURIComponent(wheelID || 'classic') +
'/hints',
{
method: 'POST',
body: {},
}
);
},
};
// 邀请活动 H5 只关心当前用户本月状态和领取动作;有效人数、累计充值和最高档差额都以后端聚合事实为准。
// 领取时页面必须传入本次点击生成的 command_id服务端按用户、周期、档位和 command_id 做幂等发奖。
var inviteActivityRewardAPI = {
@ -938,6 +985,7 @@ var default_api = 'https://api.global-interaction.com/';
roleInvitations: roleInvitationAPI,
managerCenter: managerCenterAPI,
superadminCenter: superadminCenterAPI,
activityWheel: activityWheelAPI,
level: levelAPI,
task: taskAPI,
weeklyStar: weeklyStarAPI,

View File

@ -106,11 +106,7 @@
function resolveLang() {
var requestedLang = queryLang();
if (requestedLang) return normalizeLang(requestedLang) || DEFAULT_LANG;
return (
normalizeLang(storageGet(STORAGE_KEY)) ||
normalizeLang(window.navigator.language) ||
DEFAULT_LANG
);
return DEFAULT_LANG;
}
function load(lang) {

View File

@ -65,6 +65,27 @@
"rechargeReward.idPrefix": "المعرّف:",
"rechargeReward.accumulatedRecharge": "إجمالي الشحن:",
"rechargeReward.rechargeTitle": "اشحن {amount}",
"wheel.pageTitle": "عجلة يومي",
"wheel.pageLabel": "عجلة يومي",
"wheel.classic": "كلاسيكي",
"wheel.inLove": "رفاهية",
"wheel.advanced": "متقدم",
"wheel.historyRecord": "سجل التاريخ",
"wheel.rewardTips": "تهانينا على استلام: هدية ثمينة بقيمة {} قطعة ذهبية! وُضعت في حقيبة الظهر.",
"wheel.drawAgain": "اسحب مرة أخرى.",
"wheel.userAward": "تهانينا لـ {1} على حصوله على جائزة {2}",
"wheel.roomId": "لا يمكن أن يكون معرف الغرفة فارغًا",
"wheel.rule1": "1. اختر عجلة واسحب باستخدام العملات.",
"wheel.rule2": "2. يتم إرسال الجوائز إلى الحقيبة بعد نجاح السحب.",
"wheel.rule3": "3. لا علاقة لهذا النشاط بشركة Apple أو Google.",
"wheel.common.loading": "جارٍ التحميل",
"wheel.common.rule": "قاعدة",
"wheel.common.rewards": "المكافآت",
"wheel.common.balance": "الرصيد",
"wheel.common.noDate": "لا مزيد من البيانات",
"wheel.common.balanceNotEnough": "رصيد غير كاف",
"wheel.common.rewardUnavailable": "المكافآت غير متاحة بعد",
"wheel.common.requestFailed": "فشل الطلب",
"invite.pageAria": "دعوة الأصدقاء",
"invite.heroAria": "ادع الأصدقاء واكسب المال",
"invite.countdownAria": "العد التنازلي",
@ -557,6 +578,7 @@
"agencyOpening.joined": "تم الانضمام",
"agencyOpening.notJoined": "لم ينضم",
"agencyOpening.applied": "تم التقديم",
"agencyOpening.pendingReview": "بانتظار المراجعة",
"agencyOpening.applySuccess": "تم إرسال الطلب.",
"agencyOpening.applyFailed": "فشل الطلب. حاول لاحقا.",
"agencyOpening.rewardsTitle": "مكافآت الافتتاح",
@ -567,8 +589,8 @@
"agencyOpening.rule2": "يجب أن يكون عدد المضيفين أكثر من 10.",
"agencyOpening.rule3": "يجب أن يكون عمر الوكالة أقل من 7 أيام.",
"agencyOpening.rule4": "يمكن لكل وكالة المشاركة مرة واحدة فقط.",
"agencyOpening.rule5": "لا يمكن تشغيل فعالية الافتتاح مع برنامج دعم الوكالات في نفس الوقت.",
"agencyOpening.rule6": "ترسل المكافآت خلال 7 أيام بعد المراجعة.",
"agencyOpening.rule5": "تبدأ إيرادات الافتتاح بعد موافقة المسؤول وتحتسب لمدة 24 ساعة فقط.",
"agencyOpening.rule6": "ترسل المكافآت تلقائيا بعد انتهاء الفعالية.",
"agencyOpening.recordTitle": "سجل الافتتاح",
"agencyOpening.guildName": "اسم النقابة:",
"agencyOpening.created": "تاريخ الإنشاء:",

View File

@ -65,6 +65,27 @@
"rechargeReward.idPrefix": "ID:",
"rechargeReward.accumulatedRecharge": "Accumulated Recharge:",
"rechargeReward.rechargeTitle": "Recharge {amount}",
"wheel.pageTitle": "Yumi Wheel",
"wheel.pageLabel": "Yumi Wheel",
"wheel.classic": "Classic",
"wheel.inLove": "luxury",
"wheel.advanced": "Advanced",
"wheel.historyRecord": "History Record",
"wheel.rewardTips": "Congratulations on receiving: A precious gift worth {} gold coins! Already put in the backpack",
"wheel.drawAgain": "Draw again",
"wheel.userAward": "Congratulations to {1} for receiving the {2} award",
"wheel.roomId": "Room ID cannot be empty",
"wheel.rule1": "1. Select a wheel and draw with coins.",
"wheel.rule2": "2. Rewards are issued to the backpack after a successful draw.",
"wheel.rule3": "3. This activity has no relationship with Apple or Google.",
"wheel.common.loading": "Loading",
"wheel.common.rule": "rule",
"wheel.common.rewards": "Rewards",
"wheel.common.balance": "Balance",
"wheel.common.noDate": "No more data",
"wheel.common.balanceNotEnough": "Insufficient balance",
"wheel.common.rewardUnavailable": "Rewards are not available yet",
"wheel.common.requestFailed": "Request failed",
"invite.pageAria": "Invite friends",
"invite.heroAria": "Invite friends make money",
"invite.countdownAria": "Countdown",
@ -563,6 +584,7 @@
"agencyOpening.joined": "Joined",
"agencyOpening.notJoined": "Not Joined",
"agencyOpening.applied": "Applied",
"agencyOpening.pendingReview": "Pending Review",
"agencyOpening.applySuccess": "Application submitted.",
"agencyOpening.applyFailed": "Application failed. Try again later.",
"agencyOpening.rewardsTitle": "Opening Rewards",
@ -573,8 +595,8 @@
"agencyOpening.rule2": "Host count must be more than 10.",
"agencyOpening.rule3": "Agency age must be less than 7 days.",
"agencyOpening.rule4": "Each agency can join only once in lifetime.",
"agencyOpening.rule5": "Opening Event cannot run with Agency Support Program at the same time.",
"agencyOpening.rule6": "Rewards will be sent within 7 days after review.",
"agencyOpening.rule5": "Opening revenue starts after admin approval and counts for 24 hours only.",
"agencyOpening.rule6": "Rewards will be sent automatically after the activity ends.",
"agencyOpening.recordTitle": "Opening Record",
"agencyOpening.guildName": "Guild Name:",
"agencyOpening.created": "Created:",

View File

@ -65,6 +65,27 @@
"rechargeReward.idPrefix": "ID:",
"rechargeReward.accumulatedRecharge": "Recarga acumulada:",
"rechargeReward.rechargeTitle": "Recarga {amount}",
"wheel.pageTitle": "Rueda de Yumi",
"wheel.pageLabel": "Rueda de Yumi",
"wheel.classic": "Clásico",
"wheel.inLove": "lujo",
"wheel.advanced": "Avanzado",
"wheel.historyRecord": "Historial",
"wheel.rewardTips": "Felicidades por recibir: un regalo valioso de {} monedas de oro. Ya está en la mochila",
"wheel.drawAgain": "Girar otra vez",
"wheel.userAward": "Felicidades a {1} por recibir el premio {2}",
"wheel.roomId": "El ID de la sala no puede estar vacío",
"wheel.rule1": "1. Selecciona una rueda y gira con monedas.",
"wheel.rule2": "2. Las recompensas se envían a la mochila tras un sorteo exitoso.",
"wheel.rule3": "3. Esta actividad no tiene relación con Apple ni Google.",
"wheel.common.loading": "Cargando",
"wheel.common.rule": "regla",
"wheel.common.rewards": "Recompensas",
"wheel.common.balance": "Saldo",
"wheel.common.noDate": "No hay más datos",
"wheel.common.balanceNotEnough": "Saldo insuficiente",
"wheel.common.rewardUnavailable": "Las recompensas aún no están disponibles",
"wheel.common.requestFailed": "Solicitud fallida",
"invite.pageAria": "Invitar amigos",
"invite.heroAria": "Invita amigos y gana dinero",
"invite.countdownAria": "Cuenta regresiva",
@ -557,6 +578,7 @@
"agencyOpening.joined": "Unido",
"agencyOpening.notJoined": "No unido",
"agencyOpening.applied": "Solicitado",
"agencyOpening.pendingReview": "Pendiente de revisión",
"agencyOpening.applySuccess": "Solicitud enviada.",
"agencyOpening.applyFailed": "Solicitud fallida. Inténtalo más tarde.",
"agencyOpening.rewardsTitle": "Recompensas de apertura",
@ -567,8 +589,8 @@
"agencyOpening.rule2": "La cantidad de hosts debe ser mayor que 10.",
"agencyOpening.rule3": "La agencia debe tener menos de 7 días.",
"agencyOpening.rule4": "Cada agencia solo puede participar una vez.",
"agencyOpening.rule5": "El evento de apertura no puede ejecutarse junto con el Programa de soporte de agencias.",
"agencyOpening.rule6": "Las recompensas se enviarán dentro de 7 días después de la revisión.",
"agencyOpening.rule5": "Los ingresos de apertura comienzan tras la aprobación del admin y solo cuentan durante 24 horas.",
"agencyOpening.rule6": "Las recompensas se envían automáticamente cuando termina la actividad.",
"agencyOpening.recordTitle": "Registro de apertura",
"agencyOpening.guildName": "Nombre del gremio:",
"agencyOpening.created": "Creado:",

View File

@ -32,6 +32,27 @@
"rechargeReward.idPrefix": "ID:",
"rechargeReward.accumulatedRecharge": "Recharge akumulatif:",
"rechargeReward.rechargeTitle": "Recharge {amount}",
"wheel.pageTitle": "Yumi Wheel",
"wheel.pageLabel": "Yumi Wheel",
"wheel.classic": "Classic",
"wheel.inLove": "Luxury",
"wheel.advanced": "Advanced",
"wheel.historyRecord": "Riwayat Hadiah",
"wheel.rewardTips": "Selamat mendapatkan: hadiah bernilai {} koin emas! Sudah masuk ke backpack",
"wheel.drawAgain": "Undi lagi",
"wheel.userAward": "Selamat kepada {1} karena menerima hadiah {2}",
"wheel.roomId": "ID room tidak boleh kosong",
"wheel.rule1": "1. Pilih wheel dan undi dengan koin.",
"wheel.rule2": "2. Hadiah dikirim ke backpack setelah undian berhasil.",
"wheel.rule3": "3. Aktivitas ini tidak berhubungan dengan Apple atau Google.",
"wheel.common.loading": "Memuat",
"wheel.common.rule": "Aturan",
"wheel.common.rewards": "Hadiah",
"wheel.common.balance": "Saldo",
"wheel.common.noDate": "Tidak ada data lagi",
"wheel.common.balanceNotEnough": "Saldo tidak cukup",
"wheel.common.rewardUnavailable": "Hadiah belum tersedia",
"wheel.common.requestFailed": "Permintaan gagal",
"invite.pageAria": "Undang teman",
"invite.heroAria": "Undang teman dan hasilkan uang",
"invite.countdownAria": "Hitung mundur",
@ -524,6 +545,7 @@
"agencyOpening.joined": "Sudah bergabung",
"agencyOpening.notJoined": "Belum bergabung",
"agencyOpening.applied": "Sudah diajukan",
"agencyOpening.pendingReview": "Menunggu peninjauan",
"agencyOpening.applySuccess": "Pengajuan terkirim.",
"agencyOpening.applyFailed": "Pengajuan gagal. Coba lagi nanti.",
"agencyOpening.rewardsTitle": "Hadiah Pembukaan",
@ -534,8 +556,8 @@
"agencyOpening.rule2": "Jumlah host harus lebih dari 10.",
"agencyOpening.rule3": "Usia agensi harus kurang dari 7 hari.",
"agencyOpening.rule4": "Setiap agensi hanya dapat ikut sekali seumur hidup.",
"agencyOpening.rule5": "Event Pembukaan tidak dapat berjalan bersama Program Dukungan Agensi.",
"agencyOpening.rule6": "Hadiah dikirim dalam 7 hari setelah peninjauan.",
"agencyOpening.rule5": "Pendapatan pembukaan mulai dihitung setelah disetujui admin dan hanya berlaku 24 jam.",
"agencyOpening.rule6": "Hadiah dikirim otomatis setelah aktivitas berakhir.",
"agencyOpening.recordTitle": "Catatan Pembukaan",
"agencyOpening.guildName": "Nama guild:",
"agencyOpening.created": "Dibuat:",

View File

@ -32,6 +32,27 @@
"rechargeReward.idPrefix": "ID:",
"rechargeReward.accumulatedRecharge": "Toplam yükleme:",
"rechargeReward.rechargeTitle": "{amount} yükle",
"wheel.pageTitle": "Yumi Çarkı",
"wheel.pageLabel": "Yumi Çarkı",
"wheel.classic": "Klasik",
"wheel.inLove": "lüks",
"wheel.advanced": "Gelişmiş",
"wheel.historyRecord": "Tarih Kaydı",
"wheel.rewardTips": "Aldığınız için tebrikler: {} altın değerinde değerli bir hediye! Zaten sırt çantasına koydum",
"wheel.drawAgain": "Tekrar çekiliş",
"wheel.userAward": "{1}'i {2} ödülünü aldığı için tebrik ederiz",
"wheel.roomId": "Oda kimliği boş olamaz",
"wheel.rule1": "1. Bir çark seçin ve coin ile çekiliş yapın.",
"wheel.rule2": "2. Başarılı çekilişten sonra ödüller çantaya gönderilir.",
"wheel.rule3": "3. Bu etkinliğin Apple veya Google ile ilişkisi yoktur.",
"wheel.common.loading": "Yükleniyor",
"wheel.common.rule": "kural",
"wheel.common.rewards": "Ödüller",
"wheel.common.balance": "Bakiye",
"wheel.common.noDate": "Artık veri yok",
"wheel.common.balanceNotEnough": "Yetersiz bakiye",
"wheel.common.rewardUnavailable": "Ödüller henüz mevcut değil",
"wheel.common.requestFailed": "İstek başarısız",
"invite.pageAria": "Arkadaş davet et",
"invite.heroAria": "Arkadaş davet et, para kazan",
"invite.countdownAria": "Geri sayım",
@ -524,6 +545,7 @@
"agencyOpening.joined": "Katıldı",
"agencyOpening.notJoined": "Katılmadı",
"agencyOpening.applied": "Başvuruldu",
"agencyOpening.pendingReview": "İnceleme bekliyor",
"agencyOpening.applySuccess": "Başvuru gönderildi.",
"agencyOpening.applyFailed": "Başvuru başarısız. Daha sonra tekrar deneyin.",
"agencyOpening.rewardsTitle": "Açılış Ödülleri",
@ -534,8 +556,8 @@
"agencyOpening.rule2": "Host sayısı 10'dan fazla olmalıdır.",
"agencyOpening.rule3": "Ajans yaşı 7 günden az olmalıdır.",
"agencyOpening.rule4": "Her ajans ömür boyu yalnızca bir kez katılabilir.",
"agencyOpening.rule5": "Açılış Etkinliği Ajans Destek Programı ile aynı anda çalışamaz.",
"agencyOpening.rule6": "Ödüller incelemeden sonra 7 gün içinde gönderilir.",
"agencyOpening.rule5": "Açılış geliri admin onayından sonra başlar ve yalnızca 24 saat sayılır.",
"agencyOpening.rule6": "Ödüller etkinlik bittikten sonra otomatik gönderilir.",
"agencyOpening.recordTitle": "Açılış Kaydı",
"agencyOpening.guildName": "Lonca adı:",
"agencyOpening.created": "Oluşturuldu:",

View File

@ -32,6 +32,27 @@
"rechargeReward.idPrefix": "ID",
"rechargeReward.accumulatedRecharge": "累计充值:",
"rechargeReward.rechargeTitle": "充值 {amount}",
"wheel.pageTitle": "Yumi 转盘",
"wheel.pageLabel": "Yumi 转盘",
"wheel.classic": "Classic",
"wheel.inLove": "Luxury",
"wheel.advanced": "Advanced",
"wheel.historyRecord": "中奖历史",
"wheel.rewardTips": "恭喜获得:价值 {} 金币的珍贵礼物!已放入背包",
"wheel.drawAgain": "再抽一次",
"wheel.userAward": "恭喜 {1} 获得 {2} 奖励",
"wheel.roomId": "房间 ID 不能为空",
"wheel.rule1": "1. 选择转盘并消耗金币抽奖。",
"wheel.rule2": "2. 抽中奖品后会发放到背包。",
"wheel.rule3": "3. 本活动与 Apple 或 Google 无关。",
"wheel.common.loading": "加载中",
"wheel.common.rule": "规则",
"wheel.common.rewards": "奖励",
"wheel.common.balance": "余额",
"wheel.common.noDate": "没有更多数据",
"wheel.common.balanceNotEnough": "余额不足",
"wheel.common.rewardUnavailable": "奖池暂未开放",
"wheel.common.requestFailed": "请求失败",
"invite.pageAria": "邀请好友",
"invite.heroAria": "邀请好友赚钱",
"invite.countdownAria": "倒计时",
@ -530,6 +551,7 @@
"agencyOpening.joined": "已参加",
"agencyOpening.notJoined": "未参加",
"agencyOpening.applied": "已申请",
"agencyOpening.pendingReview": "待审核",
"agencyOpening.applySuccess": "申请已提交",
"agencyOpening.applyFailed": "申请失败,请稍后再试",
"agencyOpening.rewardsTitle": "开业奖励",
@ -540,8 +562,8 @@
"agencyOpening.rule2": "主播数量必须大于 10。",
"agencyOpening.rule3": "代理创建时间必须小于 7 天。",
"agencyOpening.rule4": "每个代理终身只能参加一次。",
"agencyOpening.rule5": "开业活动不能和代理扶持计划同时进行。",
"agencyOpening.rule6": "奖励会在审核通过后 7 天内发放。",
"agencyOpening.rule5": "开业流水从后台同意后开始计算,只统计 24 小时内的房间流水。",
"agencyOpening.rule6": "活动结束后按开业流水档位自动发放奖励。",
"agencyOpening.recordTitle": "开业记录",
"agencyOpening.guildName": "公会名称:",
"agencyOpening.created": "创建时间:",

View File

@ -11,7 +11,7 @@
<link rel="stylesheet" href="../common/center.css?v=20260608" />
<link
rel="stylesheet"
href="./style.css?v=20260612-manager-bd-leader"
href="./style.css?v=20260618-manager-permissions"
/>
</head>
@ -474,6 +474,6 @@
<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=20260612-manager-bd-leader"></script>
<script src="./script.js?v=20260618-manager-permissions"></script>
</body>
</html>

View File

@ -15,6 +15,15 @@
'send-frame': 'avatar_frame',
'send-car': 'vehicle',
};
var ACTION_PERMISSION_KEYS = {
'send-frame': 'canGrantAvatarFrame',
'send-car': 'canGrantVehicle',
'update-user-level': 'canUpdateUserLevel',
'add-bd-leader': 'canAddBdLeader',
'add-admin': 'canAddAdmin',
'add-superadmin': 'canAddSuperadmin',
'block-user': 'canBlockUser',
};
var RESOURCE_GRANT_DURATIONS = [
{ label: '7 days', ms: 7 * DAY_MS },
{ label: '30 days', ms: 30 * DAY_MS },
@ -32,6 +41,7 @@
loading: true,
denied: false,
overview: null,
permissions: {},
resourcesByType: {},
};
var blockState = {
@ -210,6 +220,73 @@
};
}
function normalizePermissions(raw) {
raw = raw || {};
return {
canGrantAvatarFrame:
raw.canGrantAvatarFrame !== undefined
? Boolean(raw.canGrantAvatarFrame)
: raw.can_grant_avatar_frame !== undefined
? Boolean(raw.can_grant_avatar_frame)
: true,
canGrantVehicle:
raw.canGrantVehicle !== undefined
? Boolean(raw.canGrantVehicle)
: raw.can_grant_vehicle !== undefined
? Boolean(raw.can_grant_vehicle)
: true,
canUpdateUserLevel:
raw.canUpdateUserLevel !== undefined
? Boolean(raw.canUpdateUserLevel)
: raw.can_update_user_level !== undefined
? Boolean(raw.can_update_user_level)
: true,
canAddBdLeader:
raw.canAddBdLeader !== undefined
? Boolean(raw.canAddBdLeader)
: raw.can_add_bd_leader !== undefined
? Boolean(raw.can_add_bd_leader)
: true,
canAddAdmin:
raw.canAddAdmin !== undefined
? Boolean(raw.canAddAdmin)
: raw.can_add_admin !== undefined
? Boolean(raw.can_add_admin)
: true,
canAddSuperadmin:
raw.canAddSuperadmin !== undefined
? Boolean(raw.canAddSuperadmin)
: raw.can_add_superadmin !== undefined
? Boolean(raw.can_add_superadmin)
: true,
canBlockUser:
raw.canBlockUser !== undefined
? Boolean(raw.canBlockUser)
: raw.can_block_user !== undefined
? Boolean(raw.can_block_user)
: true,
};
}
function canUseAction(action) {
var key = ACTION_PERMISSION_KEYS[action];
if (!key) return true;
return pageState.permissions[key] !== false;
}
function renderActionPermissions() {
document.querySelectorAll('[data-action]').forEach(function (button) {
var action = button.getAttribute('data-action');
button.hidden = !canUseAction(action);
});
}
function ensureActionAllowed(action) {
if (canUseAction(action)) return true;
toast('Permission denied.');
return false;
}
function apiSearchUser(keyword) {
if (!api || !api.searchUsers) {
return Promise.reject(new Error('API not available.'));
@ -231,8 +308,12 @@
.overview()
.then(function (data) {
pageState.overview = data || {};
pageState.permissions = normalizePermissions(
pageState.overview.permissions || pageState.overview
);
renderActionPermissions();
setDenied(false);
return refreshBlocks();
return canUseAction('block-user') ? refreshBlocks() : Promise.resolve();
})
.catch(function (error) {
setDenied(true);
@ -503,6 +584,7 @@
toast('Permission denied.');
return;
}
if (!ensureActionAllowed('block-user')) return;
var modal = $('blockModal');
if (!modal) return;
resetBlockSearch();
@ -539,6 +621,7 @@
toast('Permission denied.');
return;
}
if (!ensureActionAllowed('update-user-level')) return;
var modal = $('levelModal');
if (!modal) return;
resetLevelSearch();
@ -693,6 +776,7 @@
toast('Permission denied.');
return;
}
if (!ensureActionAllowed(action)) return;
ensureGrantModal();
grantState.action = action;
grantState.resourceType = RESOURCE_TYPES[action] || '';
@ -915,6 +999,7 @@
toast('Permission denied.');
return;
}
if (!ensureActionAllowed(action)) return;
ensureBDLeaderModal();
bdLeaderState.action = action;
bdLeaderState.title = ACTION_LABELS[action] || 'Add BD Leader';
@ -1016,6 +1101,9 @@
document.querySelectorAll('[data-action]').forEach(function (button) {
button.addEventListener('click', function () {
var action = button.getAttribute('data-action');
if (!ensureActionAllowed(action)) {
return;
}
if (action === 'block-user') {
openBlockModal();
return;