feat: 进入关卡时 toast 提示体力消耗,修复 StorageManager 接口位置和 WxSDK 访问级别

- 进入关卡成功后显示 toast 提示消耗体力及剩余体力
- 将 StorageManager 中 UserInfo 接口移至模块顶层,修复嵌套接口语法问题
- WxSDK.getWx() 改为 static 公开方法,便于外部调用
This commit is contained in:
richarjiang
2026-04-10 10:10:19 +08:00
parent 447e7a944a
commit 69c0986996
16 changed files with 3523 additions and 503 deletions

View File

@@ -2,13 +2,14 @@ import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, Sp
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { StorageManager } from 'db://assets/scripts/utils/StorageManager';
import { UserAssetsManager } from 'db://assets/scripts/utils/UserAssetsManager';
import { StaminaManager } from 'db://assets/scripts/utils/StaminaManager';
import { WxSDK } from 'db://assets/scripts/utils/WxSDK';
import { LevelDataManager } from 'db://assets/scripts/utils/LevelDataManager';
import { RuntimeLevelConfig } from 'db://assets/scripts/types/LevelTypes';
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { PassModal } from 'db://assets/prefabs/PassModal';
import { StaminaInfo } from 'db://assets/scripts/types/ApiTypes';
const { ccclass, property } = _decorator;
/**
@@ -60,7 +61,7 @@ export class PageLevel extends BaseView {
@property(Label)
clockLabel: Label | null = null;
/** 积分显示标签prefab 中序列化名为 liveLabel保持兼容 */
/** 体力值显示标签prefab 中序列化名为 liveLabel保持兼容 */
@property(Label)
liveLabel: Label | null = null;
@@ -99,7 +100,7 @@ export class PageLevel extends BaseView {
/** 是否正在切换关卡(防止重复提交) */
private _isTransitioning: boolean = false;
/** 是否正在解锁提示(防止双击重复消耗积分 */
/** 是否正在解锁提示(防止双击重复触发 */
private _isUnlocking: boolean = false;
/** 通关弹窗实例 */
@@ -108,6 +109,9 @@ export class PageLevel extends BaseView {
/** 是否处于分享挑战模式 */
private _isShareMode: boolean = false;
/** 体力恢复倒计时定时器 */
private _staminaTimerId: ReturnType<typeof setInterval> | null = null;
/**
* 页面首次加载时调用
*/
@@ -125,16 +129,14 @@ export class PageLevel extends BaseView {
this.currentLevelIndex = StorageManager.getCurrentLevelIndex();
console.log(`[PageLevel] 恢复关卡进度: 第 ${this.currentLevelIndex + 1}`);
}
this.updatePointsLabel();
this.updateStaminaLabel();
this.initIconSetting();
this.initUnlockButtons();
this.initSubmitButton();
// 异步加载关卡资源,完成后启动倒计时
this.initLevel().then(() => {
this.startCountdown();
}).catch(err => {
console.error('[PageLevel] 加载关卡失败:', err);
// 异步加载关卡资源并调用进入关卡接口,完成后启动倒计时
this._enterAndInitLevel().catch(err => {
console.error('[PageLevel] 进入关卡失败:', err);
});
}
@@ -143,7 +145,8 @@ export class PageLevel extends BaseView {
*/
onViewShow(): void {
console.log('[PageLevel] onViewShow');
this.updatePointsLabel();
this.updateStaminaLabel();
this._startStaminaRecoverTimer();
}
/**
@@ -151,6 +154,7 @@ export class PageLevel extends BaseView {
*/
onViewHide(): void {
console.log('[PageLevel] onViewHide');
this._stopStaminaRecoverTimer();
}
/**
@@ -161,6 +165,7 @@ export class PageLevel extends BaseView {
this.clearInputNodes();
this.stopCountdown();
this._closePassModal();
this._stopStaminaRecoverTimer();
// 清理事件监听
this.iconSetting?.off(Node.EventType.TOUCH_END, this.onIconSettingClick, this);
@@ -170,16 +175,18 @@ export class PageLevel extends BaseView {
}
/**
* 初始化关卡(从 API 数据加载,异步确保资源就绪)
* 进入关卡并初始化
* 1. 加载关卡图片资源
* 2. 调用进入关卡接口(消耗体力,获取答案和线索)
* 3. 启动倒计时
*/
private async initLevel(): Promise<void> {
private async _enterAndInitLevel(): Promise<void> {
// 先加载关卡图片资源
let config: RuntimeLevelConfig | null = null;
if (this._isShareMode) {
// 分享模式:从 ShareManager 获取关卡
config = await ShareManager.instance.ensureShareLevelReady(this.currentLevelIndex);
} else {
// 正常模式:先尝试缓存,再异步加载
config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
if (!config) {
console.log(`[PageLevel] 关卡 ${this.currentLevelIndex + 1} 资源未缓存,开始加载...`);
@@ -192,8 +199,53 @@ export class PageLevel extends BaseView {
return;
}
// 非分享模式下,调用进入关卡接口获取答案和线索
if (!this._isShareMode) {
const levelId = LevelDataManager.instance.getLevelId(this.currentLevelIndex);
if (levelId) {
const enterData = await StaminaManager.instance.enterLevel(levelId);
if (!enterData) {
// 进入关卡失败(可能是体力不足)
const stamina = StaminaManager.instance.getStamina();
if (stamina.current <= 0) {
ToastManager.show('体力不足,请等待恢复');
this._startStaminaRecoverTimer();
} else {
ToastManager.show('进入关卡失败,请重试');
}
this.updateStaminaLabel();
return;
}
// 提示用户消耗体力
ToastManager.show(`消耗1点体力剩余 ${enterData.stamina.current}/${enterData.stamina.max}`);
// 用 enter 接口返回的数据更新关卡配置(填充答案和线索)
LevelDataManager.instance.updateLevelDetails(
this.currentLevelIndex,
{
answer: enterData.answer,
hint1: enterData.hint1,
hint2: enterData.hint2,
hint3: enterData.hint3,
}
);
// 重新获取更新后的配置
config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
if (!config) {
console.error('[PageLevel] 更新关卡详情后获取配置失败');
return;
}
// 更新体力显示
this.updateStaminaLabel();
}
}
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}: ${config.name}`);
this._applyLevelConfig(config);
this.startCountdown();
}
/**
@@ -212,8 +264,10 @@ export class PageLevel extends BaseView {
// 设置主图
this.setMainImage(config.spriteFrame);
// 设置线索1默认解锁
this.setClue(1, config.clue1);
// 设置线索1默认解锁,如果有的话
if (config.clue1) {
this.setClue(1, config.clue1);
}
// 隐藏线索2、3
this.hideClue(2);
@@ -224,7 +278,9 @@ export class PageLevel extends BaseView {
this.showUnlockButton(3);
// 根据答案长度创建单个输入框
this.createSingleInput(config.answer.length);
if (config.answer) {
this.createSingleInput(config.answer.length);
}
// 更新倒计时显示
this.updateClockLabel();
@@ -239,7 +295,7 @@ export class PageLevel extends BaseView {
LevelDataManager.instance.preloadNextLevel(this.currentLevelIndex);
}
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${config.answer.length}`);
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${config.answer?.length ?? 0}`);
}
/**
@@ -313,6 +369,11 @@ export class PageLevel extends BaseView {
private clearInputNodes(): void {
for (const node of this._inputNodes) {
if (node.isValid) {
const editBox = node.getComponent(EditBox);
if (editBox) {
editBox.node.off(EditBox.EventType.TEXT_CHANGED, this.onInputTextChanged, this);
editBox.node.off(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this);
}
node.destroy();
}
}
@@ -501,38 +562,38 @@ export class PageLevel extends BaseView {
}
/**
* 点击解锁线索
* 点击解锁线索(观看激励视频广告后解锁)
*/
private async onUnlockClue(index: number): Promise<void> {
// 防止双击重复消耗
// 防止双击重复触发
if (this._isUnlocking) return;
if (!this.hasPoints()) {
ToastManager.show('积分不足,无法解锁提示!');
return;
}
this._isUnlocking = true;
try {
const levelId = this._currentConfig?.id;
const success = await UserAssetsManager.instance.consumePoint(levelId, index);
if (!success) {
ToastManager.show('积分不足,无法解锁提示!');
// 检查线索是否存在
if (!this._currentConfig) return;
const clueContent = index === 2 ? this._currentConfig.clue2 : this._currentConfig.clue3;
if (!clueContent) {
ToastManager.show('该提示暂未配置');
return;
}
// 调用微信激励视频广告
ToastManager.show('观看视频即可解锁提示');
const adWatched = await WxSDK.showRewardedVideoAd();
if (!adWatched) {
ToastManager.show('需要看完视频才能解锁提示哦');
return;
}
this.updatePointsLabel();
this.playClickSound();
this.hideUnlockButton(index);
this.showClue(index);
this.setClue(index, clueContent);
if (this._currentConfig) {
const clueContent = index === 2 ? this._currentConfig.clue2 : this._currentConfig.clue3;
this.setClue(index, clueContent);
}
console.log(`[PageLevel] 解锁线索${index}`);
console.log(`[PageLevel] 通过观看广告解锁线索${index}`);
} finally {
this._isUnlocking = false;
}
@@ -639,17 +700,70 @@ export class PageLevel extends BaseView {
// 可以在这里添加游戏结束逻辑
}
// ========== 积分相关方法 ==========
// ========== 体力值相关方法 ==========
private updatePointsLabel(): void {
/** 上次显示的体力值,用于变更检测 */
private _lastDisplayedStamina: number = -1;
/**
* 更新体力值显示(仅值变化时更新 UI
*/
private updateStaminaLabel(): void {
if (this.liveLabel) {
const points = StorageManager.getPoints();
this.liveLabel.string = `x ${points}`;
const stamina = StaminaManager.instance.getStamina();
if (stamina.current !== this._lastDisplayedStamina) {
this.liveLabel.string = `x ${stamina.current}`;
this._lastDisplayedStamina = stamina.current;
}
}
}
private hasPoints(): boolean {
return StorageManager.hasPoints();
/**
* 启动体力恢复倒计时 UI
*/
private _startStaminaRecoverTimer(): void {
this._stopStaminaRecoverTimer();
const stamina = StaminaManager.instance.getStamina();
if (!stamina.nextRecoverAt || stamina.current >= stamina.max) {
return;
}
const targetTime = new Date(stamina.nextRecoverAt).getTime();
if (isNaN(targetTime)) return;
this._staminaTimerId = setInterval(() => {
if (targetTime - Date.now() > 0) return;
// 恢复一点体力
const currentStamina = StaminaManager.instance.getStamina();
const newCurrent = Math.min(currentStamina.current + 1, currentStamina.max);
const newStamina: StaminaInfo = {
...currentStamina,
current: newCurrent,
nextRecoverAt: newCurrent < currentStamina.max
? new Date(Date.now() + 10 * 60 * 1000).toISOString()
: null,
};
StaminaManager.instance.updateStamina(newStamina);
this.updateStaminaLabel();
this._stopStaminaRecoverTimer();
if (newCurrent < currentStamina.max) {
this._startStaminaRecoverTimer();
}
}, 1000);
}
/**
* 停止体力恢复倒计时
*/
private _stopStaminaRecoverTimer(): void {
if (this._staminaTimerId !== null) {
clearInterval(this._staminaTimerId);
this._staminaTimerId = null;
}
}
// ========== 答案提交与关卡切换 ==========
@@ -674,7 +788,7 @@ export class PageLevel extends BaseView {
}
/**
* 显示成功提示
* 显示成功提示并上报通关
*/
private async showSuccess(): Promise<void> {
console.log('[PageLevel] 答案正确!');
@@ -692,8 +806,13 @@ export class PageLevel extends BaseView {
const timeSpent = 60 - this._countdown;
if (!this._isShareMode) {
await UserAssetsManager.instance.earnPoint(levelId, timeSpent);
this.updatePointsLabel();
// 上报通关耗时
const result = await StaminaManager.instance.completeLevel(levelId, timeSpent);
if (result) {
console.log(`[PageLevel] 通关上报成功,首次通关: ${result.firstClear}`);
}
// 标记关卡为已通关(本地缓存)
LevelDataManager.instance.markLevelCompleted(this.currentLevelIndex);
} else {
// fire-and-forget: errors are logged inside reportLevelProgress
void ShareManager.instance.reportLevelProgress(levelId, true, timeSpent);
@@ -812,11 +931,8 @@ export class PageLevel extends BaseView {
return;
}
// 重置并加载下一关,重新开始倒计时
await this.initLevel();
this.startCountdown();
// 重置并加载下一关(包含进入关卡接口调用)
await this._enterAndInitLevel();
console.log(`[PageLevel] 进入关卡 ${this.currentLevelIndex + 1}`);
}
}

View File

@@ -30,7 +30,7 @@ export class PassModal extends BaseView {
@property(Node)
shareButton: Node | null = null;
/** 提示Label+1 生命 */
/** 提示Label恭喜通关 */
@property(Label)
tipLabel: Label | null = null;