import { ViewManager } from "db://assets/Scripts/Main/Manager/ViewManager"; import { UITransitionHelper } from "../utils/UITransitionHelper"; import GameRootUI from "../../Main/Common/GameRootUI"; import { Node, sys } from "cc"; import li_EventManager from "../../Main/Common/li_EventManager"; import { InnerMsgCode } from "../../Main/Config/InnerMsgCode"; import { ThemePanel } from "../ui/panels/ThemePanel"; import { GirlListPanel } from "../ui/panels/GirlListPanel"; import li_BaseView from "../../Main/Common/li_BaseView"; import { logger } from "db://assets/Scripts/Main/Common/Logger"; /** * 页面过渡动画类型枚举 */ export enum PageTransitionType { NONE = "none", SLIDE_LEFT = "slide_left", SLIDE_RIGHT = "slide_right", SLIDE_UP = "slide_up", SLIDE_DOWN = "slide_down", FADE = "fade", SCALE = "scale", } /** * NavigationPanel面板类型枚举 */ export enum PanelType { THEME = "ThemePanel", RANK = "RankPanel", PAST_GIRL_LIST = "PastGirlListPanel", PERSONAL = "PersonalPanel", } /** * 页面过渡动画配置接口 */ export interface PageTransitionConfig { incomingTransition: PageTransitionType; outgoingTransition: PageTransitionType; duration?: number; simultaneous?: boolean; } /** * 子面板动画配置接口 */ export interface SubPanelAnimationConfig { openAnimation: PageTransitionType; closeAnimation: PageTransitionType; duration?: number; hideBasePanel?: boolean; // 是否隐藏basePanel,用于全屏子面板 } /** * 导航管理器 * * 负责管理游戏中的页面导航和路由,将导航逻辑从业务逻辑中分离出来 * 统一管理NavigationPanel和其他面板的切换 * * @author AI Chat System * @version 1.0.0 */ export class NavigationManager { private static _instance: NavigationManager; // ==================== 主面板管理相关 ==================== private navigationPanel: any = null; // NavigationPanel实例引用 private currentActivePanelType: PanelType = PanelType.PAST_GIRL_LIST; private currentActivePanel: Node = null; private panelCache: Map = new Map(); // ==================== 子面板栈管理相关 ==================== private subPanelStacks: Map = new Map(); private subPanelMapping: Map = new Map(); private subPanelAnimationConfigs: Map = new Map(); private hiddenBasePanels: Map = new Map(); private subPanelHiddenStates: Map> = new Map(); private tempHiddenStates: Map = null; // ==================== 弹窗管理相关 ==================== private popupPanels: Set = new Set(); // ==================== 角色选择相关 ==================== private selectedGirlId: number = 10002; private selectedCategoryId: number = -1; private static LastSelectGirlID = "LastSelectGirlID"; // ==================== 动画状态 ==================== private isAnimating: boolean = false; /** * 获取NavigationManager的单例实例 * @returns 导航管理器实例 * @static */ public static get Instance(): NavigationManager { if (!this._instance) { this._instance = new NavigationManager(); //初始化默认选的girl let cacheGirlId = sys.localStorage.getItem(this.LastSelectGirlID); if (!cacheGirlId) { cacheGirlId = 10016; sys.localStorage.setItem(this.LastSelectGirlID, cacheGirlId); } this._instance.selectedGirlId = Number(cacheGirlId); let cacheCategoryId = sys.localStorage.getItem("LastSelectCategoryId"); if (!cacheCategoryId) { cacheCategoryId = 0; sys.localStorage.setItem("LastSelectCategoryId", cacheCategoryId); } this._instance.selectedCategoryId = cacheCategoryId; } return this._instance; } private constructor() { // 私有构造函数,确保单例模式 } // ==================== 公共方法 ==================== /** * 注册NavigationPanel实例 * @param panel NavigationPanel实例 */ public registerNavigationPanel(panel: any): void { this.navigationPanel = panel; logger.log("[NavigationManager] NavigationPanel registered"); } /** * 统一的面板切换方法 * @param panelType 目标面板类型 * @param force 是否强制切换(即使已经在当前面板) * @param callback 切换完成后的回调函数 * @returns 返回切换后的面板节点,失败返回null */ public switchToPanel( panelType: PanelType, force: boolean = false, callback?: (panel: Node) => void ): Node | null { if (!panelType) { logger.error("[NavigationManager] switchToPanel: panelType is invalid"); return null; } if (this.currentActivePanelType === panelType && !force) { logger.log( `[NavigationManager] Already on ${panelType}, skipping switch` ); callback && callback(this.getCurrentActivePanelNode()); return this.currentActivePanel; } if (!this.navigationPanel) { logger.warn( "[NavigationManager] NavigationPanel not registered" ); return null; } // 切换面板前先关闭所有弹窗 this.closeAllPopups(); // 通知NavigationPanel显示加载状态 this.navigationPanel.showLoading?.(); // 计算动画方向 const currentIndex = this.getPanelIndex(this.currentActivePanelType); const targetIndex = this.getPanelIndex(panelType); const isMovingRight = currentIndex < targetIndex; const hideDirection = isMovingRight ? "left" : "right"; const showDirection = isMovingRight ? "right" : "left"; // 清空当前主面板的子页面栈 if (this.currentActivePanelType) { logger.log( `[NavigationManager] 切换主面板,清空当前面板的子页面栈: ${this.currentActivePanelType}` ); this.clearSubPanelStack(this.currentActivePanelType, true); } // 隐藏当前面板 if (this.currentActivePanel && this.currentActivePanel.isValid) { this.hidePanelWithAnimation(this.currentActivePanel, hideDirection); } // 加载并显示目标面板 this.loadOrGetPanel(panelType, (panel: Node) => { this.currentActivePanelType = panelType; // 更新NavigationPanel按钮状态 this.navigationPanel.updateButtonStates?.(panelType); // 显示面板 this.showPanelWithAnimation(panel, showDirection, () => { this.currentActivePanel = panel; this.navigationPanel.hideLoading?.(); if (callback instanceof Function) callback(panel); }); }); } // ==================== 子面板管理方法 ==================== /** * 打开子面板 * @param panelName 子面板名称 * @param basePanel 基础面板节点 * @param data 传递给子面板的数据 * @param animationConfig 动画配置 */ public openSubPanel( panelName: string, basePanel: Node, data: any = null, animationConfig?: SubPanelAnimationConfig ): void { if (!panelName) { logger.error("[NavigationManager] openSubPanel: panelName is empty"); return; } if (!basePanel || !basePanel.isValid) { logger.error("[NavigationManager] openSubPanel: basePanel is invalid"); return; } // 确定主面板类型 const basePanelType = this.getPanelType(basePanel); if (!basePanelType) { logger.error( `[NavigationManager] 无法确定basePanel类型: ${basePanel.name}` ); return; } // 使用默认动画配置 const defaultConfig: SubPanelAnimationConfig = { openAnimation: PageTransitionType.SCALE, closeAnimation: PageTransitionType.SCALE, duration: 0.3, hideBasePanel: false, }; const config = animationConfig || defaultConfig; // 获取当前栈顶的子面板 const currentTopSubPanel = this.getTopSubPanel(basePanelType); // 如果需要隐藏basePanel,同时隐藏栈中所有子面板,并记录它们的原始显隐状态 if (config.hideBasePanel) { logger.log( `[NavigationManager] hideBasePanel=true,隐藏basePanel并记录栈中所有子面板的显隐状态` ); // 创建临时存储Map来记录所有被隐藏子面板的原始active状态 this.tempHiddenStates = new Map(); // 先隐藏当前显示的栈顶子面板(如果有) if (currentTopSubPanel && currentTopSubPanel.isValid) { // 记录当前栈顶的原始状态 this.tempHiddenStates.set( currentTopSubPanel, currentTopSubPanel.active ); logger.log( `[NavigationManager] 记录当前栈顶子面板状态: ${currentTopSubPanel.name}, 原始active=${currentTopSubPanel.active}` ); // 隐藏当前栈顶 if (currentTopSubPanel.active) { currentTopSubPanel.active = false; logger.log( `[NavigationManager] 隐藏当前栈顶子面板: ${currentTopSubPanel.name}` ); } } // 获取当前栈中的所有子面板 const stack = this.subPanelStacks.get(basePanelType); if (stack && stack.length > 0) { // 遍历栈中所有子面板,记录并隐藏它们 for (const subPanel of stack) { // 跳过已经处理的当前栈顶 if (subPanel === currentTopSubPanel) { continue; } if (subPanel && subPanel.isValid) { // 记录原始active状态 this.tempHiddenStates.set(subPanel, subPanel.active); logger.log( `[NavigationManager] 记录子面板状态: ${subPanel.name}, 原始active=${subPanel.active}` ); // 隐藏子面板 if (subPanel.active) { subPanel.active = false; logger.log( `[NavigationManager] 隐藏栈中子面板: ${subPanel.name}` ); } } } } // 同时记录basePanel的原始active状态 this.tempHiddenStates.set(basePanel, basePanel.active); logger.log( `[NavigationManager] 记录basePanel状态: ${basePanel.name}, 原始active=${basePanel.active}` ); // 隐藏basePanel if (basePanel.active) { logger.log(`[NavigationManager] 隐藏basePanel: ${basePanel.name}`); basePanel.active = false; } } else { // 不需要隐藏basePanel,只隐藏当前栈顶子面板(原有逻辑) if (currentTopSubPanel && currentTopSubPanel.isValid) { logger.log( `[NavigationManager] 隐藏当前栈顶子面板: ${currentTopSubPanel.name}` ); this.hideSubPanel(currentTopSubPanel); } } const callback = (n: Node) => { if (!n || !n.isValid) { logger.error( "[NavigationManager] openSubPanel: created panel is invalid" ); return; } // 初始化栈(如果不存在) if (!this.subPanelStacks.has(basePanelType)) { this.subPanelStacks.set(basePanelType, []); } // 将新子面板推入栈顶 const stack = this.subPanelStacks.get(basePanelType); stack.push(n); // 记录子面板到主面板的映射 this.subPanelMapping.set(n, basePanelType); // 存储动画配置 this.subPanelAnimationConfigs.set(n, config); // 如果配置了隐藏basePanel,记录映射关系 if (config.hideBasePanel) { this.hiddenBasePanels.set(n, basePanel); logger.log( `[NavigationManager] 记录隐藏映射: ${n.name} -> ${basePanel.name}` ); // 如果有临时存储的隐藏状态,转移到新面板的映射中 if (this.tempHiddenStates && this.tempHiddenStates.size > 0) { this.subPanelHiddenStates.set(n, this.tempHiddenStates); logger.log( `[NavigationManager] 保存${this.tempHiddenStates.size}个子面板的隐藏状态到${n.name}` ); // 清空临时存储 this.tempHiddenStates = null; } } logger.log( `[NavigationManager] 子面板已推入栈顶: ${n.name}, 当前栈深度: ${stack.length}` ); // 执行打开动画 this.playSubPanelOpenAnimation(n, config); // 动画完成后,根据栈顶子面板更新 basePanel 显隐状态 this.updateBasePanelVisibility(basePanelType); }; ViewManager.I.openBundlesView(panelName, data, callback); } /** * 将过渡类型转换为方向字符串 */ private getDirectionFromTransitionType( transitionType: PageTransitionType ): "left" | "right" | "up" | "down" { switch (transitionType) { case PageTransitionType.SLIDE_LEFT: return "left"; case PageTransitionType.SLIDE_RIGHT: return "right"; case PageTransitionType.SLIDE_UP: return "up"; case PageTransitionType.SLIDE_DOWN: return "down"; default: return "left"; // 默认向左 } } /** * 执行子面板打开动画 */ private playSubPanelOpenAnimation( panel: Node, config: SubPanelAnimationConfig ): void { if (!panel || !panel.isValid) { return; } const duration = config.duration || 0.3; switch (config.openAnimation) { case PageTransitionType.SCALE: UITransitionHelper.scaleIn(panel, duration); break; case PageTransitionType.FADE: UITransitionHelper.fadeIn(panel, duration); break; case PageTransitionType.SLIDE_UP: UITransitionHelper.slideInFromBottom(panel, duration); break; case PageTransitionType.SLIDE_DOWN: UITransitionHelper.slideInFromTop(panel, duration); break; case PageTransitionType.SLIDE_LEFT: UITransitionHelper.slideInFromRight(panel, duration); break; case PageTransitionType.SLIDE_RIGHT: UITransitionHelper.slideInFromLeft(panel, duration); break; default: // 无动画,直接显示 break; } } /** * 执行子面板关闭动画 */ private playSubPanelCloseAnimation( panel: Node, config: SubPanelAnimationConfig, callback?: Function ): void { if (!panel || !panel.isValid) { callback && callback(); return; } const duration = config.duration || 0.3; switch (config.closeAnimation) { case PageTransitionType.SCALE: UITransitionHelper.scaleOut(panel, duration, callback); break; case PageTransitionType.FADE: UITransitionHelper.fadeOut(panel, duration, callback); break; case PageTransitionType.SLIDE_UP: UITransitionHelper.slideOutToTop(panel, duration, callback); break; case PageTransitionType.SLIDE_DOWN: UITransitionHelper.slideOutToBottom(panel, duration, callback); break; case PageTransitionType.SLIDE_LEFT: UITransitionHelper.slideOutToLeft(panel, duration, callback); break; case PageTransitionType.SLIDE_RIGHT: UITransitionHelper.slideOutToRight(panel, duration, callback); break; default: // 无动画,直接执行回调 callback && callback(); break; } } // ==================== 弹窗管理方法 ==================== /** * 打开弹窗面板 * @param panelName 弹窗面板名称 * @param data 传递的数据 * @param callback 可选的回调函数 */ public openPopupPanel( panelName: string, data: any = null, callback?: Function ): void { if (!panelName) { logger.error("[NavigationManager] openPopupPanel: panelName is empty"); return; } const popupCallback = (panel: Node) => { if (!panel || !panel.isValid) { logger.error( "[NavigationManager] openPopupPanel: created panel is invalid" ); return; } // 将弹窗添加到管理集合中 this.popupPanels.add(panel); logger.log( `[NavigationManager] Popup panel opened: ${panelName}, total popups: ${this.popupPanels.size}` ); if (callback) { callback(panel); } }; ViewManager.I.openBundlesPopupView(panelName, data, popupCallback); } /** * 关闭弹窗面板 * @param panel 要关闭的弹窗节点 */ public closePopupPanel(panel: Node): void { if (!panel || !panel.isValid) { logger.warn("[NavigationManager] closePopupPanel: panel is invalid"); return; } // 从弹窗管理集合中移除 if (this.popupPanels.has(panel)) { this.popupPanels.delete(panel); logger.log( `[NavigationManager] Popup panel closed: ${panel.name}, total popups: ${this.popupPanels.size}` ); } // 关闭面板 try { const baseView = panel.getComponent(li_BaseView); if (baseView) { baseView.close(); } else { panel.destroy(); } } catch (error) { logger.error("[NavigationManager] closePopupPanel error:", error); } } /** * 关闭所有弹窗 */ private closeAllPopups(): void { if (this.popupPanels.size === 0) { return; } logger.log( `[NavigationManager] Closing all popups, count: ${this.popupPanels.size}` ); // 复制Set以避免在迭代过程中修改 const popupPanelsToClose = Array.from(this.popupPanels); for (const panel of popupPanelsToClose) { if (panel && panel.isValid) { this.closePopupPanel(panel); } } // 确保清空集合 this.popupPanels.clear(); } /** * 获取指定主面板栈顶的子面板 * @param panelType 主面板类型 * @returns 栈顶子面板,如果栈为空则返回null */ private getTopSubPanel(panelType: PanelType): Node | null { const stack = this.subPanelStacks.get(panelType); if (!stack || stack.length === 0) { return null; } return stack[stack.length - 1]; } /** * 清空指定主面板的子页面栈 * @param panelType 主面板类型 * @param withAnimation 是否播放动画 * @param callback 清空完成后的回调 */ private clearSubPanelStack( panelType: PanelType, withAnimation: boolean = true, callback?: Function ): void { const stack = this.subPanelStacks.get(panelType); if (!stack || stack.length === 0) { callback && callback(); return; } if (!withAnimation) { // 不播放动画,直接销毁所有子面板 for (let i = stack.length - 1; i >= 0; i--) { const subPanel = stack[i]; if (subPanel && subPanel.isValid) { // 清理映射 this.subPanelMapping.delete(subPanel); this.subPanelAnimationConfigs.delete(subPanel); this.hiddenBasePanels.delete(subPanel); // 销毁节点 const baseView = subPanel.getComponent(li_BaseView); if (baseView) { baseView.close(); } else { subPanel.destroy(); } } } this.subPanelStacks.delete(panelType); callback && callback(); return; } // 播放动画 let completedCount = 0; const totalCount = stack.length; const onAnimationComplete = () => { completedCount++; if (completedCount >= totalCount) { this.subPanelStacks.delete(panelType); callback && callback(); } }; // 同时播放所有子面板的关闭动画 for (let i = stack.length - 1; i >= 0; i--) { const subPanel = stack[i]; if (subPanel && subPanel.isValid) { // 获取动画配置 let config = this.subPanelAnimationConfigs.get(subPanel); if (!config) { config = { openAnimation: PageTransitionType.SCALE, closeAnimation: PageTransitionType.SCALE, duration: 0.2, }; } // 播放关闭动画 this.playSubPanelCloseAnimation(subPanel, config, () => { // 清理映射 this.subPanelMapping.delete(subPanel); this.subPanelAnimationConfigs.delete(subPanel); this.hiddenBasePanels.delete(subPanel); // 销毁节点 const baseView = subPanel.getComponent(li_BaseView); if (baseView) { baseView.close(); } else { subPanel.destroy(); } onAnimationComplete(); }); } else { onAnimationComplete(); } } } /** * 显示子面板 * @param panel 子面板节点 * @param config 动画配置(可选) * @param withAnimation 是否播放打开动画,默认为 true */ private showSubPanel( panel: Node, config?: SubPanelAnimationConfig, withAnimation: boolean = true ): void { if (!panel || !panel.isValid) { return; } panel.active = true; if (config && withAnimation) { this.playSubPanelOpenAnimation(panel, config); } } /** * 隐藏子面板(不销毁) * @param panel 子面板节点 * @param callback 隐藏完成后的回调 */ private hideSubPanel(panel: Node, callback?: Function): void { if (!panel || !panel.isValid) { callback && callback(); return; } // 直接隐藏,不播放动画(因为会被新面板覆盖) panel.active = false; callback && callback(); } /** * 恢复被隐藏的子面板的原始显隐状态 * @param panelNode 当前要关闭的子面板 * @param newTopSubPanel 新的栈顶子面板(可选,保留参数以保持接口兼容性) * @private */ private restoreHiddenSubPanels( panelNode: Node, newTopSubPanel: Node | null ): void { if (!this.subPanelHiddenStates.has(panelNode)) { return; } const hiddenStates = this.subPanelHiddenStates.get(panelNode); logger.log( `[NavigationManager] 恢复${hiddenStates.size}个子面板的原始显隐状态` ); // 遍历恢复每个子面板的原始active状态 // 注意:basePanel 的显隐状态现在统一由 updateBasePanelVisibility() 管理 // 这里只恢复栈中其他子面板的状态 hiddenStates.forEach((originalActive, subPanel) => { if (subPanel && subPanel.isValid) { subPanel.active = originalActive; logger.log( `[NavigationManager] 恢复子面板: ${subPanel.name}, active=${originalActive}` ); } }); // 清理隐藏状态映射 this.subPanelHiddenStates.delete(panelNode); } /** * 执行子面板关闭的降级处理(当面板不在栈中或栈已空时) * @private */ private handleSubPanelCloseFallback( panelNode: Node, panel: Node | li_BaseView, basePanelType: PanelType, animationConfig?: SubPanelAnimationConfig ): void { // 获取动画配置 let config = animationConfig || this.subPanelAnimationConfigs.get(panelNode); if (!config) { config = { openAnimation: PageTransitionType.SCALE, closeAnimation: PageTransitionType.SCALE, duration: 0.3, hideBasePanel: false, }; } // 播放关闭动画并销毁 this.playSubPanelCloseAnimation(panelNode, config, () => { // 恢复被隐藏的子面板状态(如果有) this.restoreHiddenSubPanels(panelNode, null); // 清理映射 this.subPanelMapping.delete(panelNode); this.subPanelAnimationConfigs.delete(panelNode); this.hiddenBasePanels.delete(panelNode); try { if (panel instanceof Node) { panel.destroy(); } else { panel.close(); } } catch (error) { logger.error("[NavigationManager] closeSubPanel error:", error); } // 统一使用 updateBasePanelVisibility 更新 basePanel 显隐状态 this.updateBasePanelVisibility(basePanelType); }); } /** * 关闭子面板 * @param panel 要关闭的子面板节点或BaseView组件 * @param animationConfig 动画配置(可选) */ public closeSubPanel( panel: Node | li_BaseView, animationConfig?: SubPanelAnimationConfig ): void { if (!panel) { logger.warn( "[NavigationManager] closeSubPanel: panel is null or undefined" ); return; } let panelNode: Node; if (panel instanceof Node) { panelNode = panel; } else { panelNode = panel.node; } if (!panelNode || !panelNode.isValid) { logger.warn("[NavigationManager] closeSubPanel: panelNode is invalid"); return; } // 确定子面板所属的主面板类型 const basePanelType = this.subPanelMapping.get(panelNode); if (!basePanelType) { logger.warn( `[NavigationManager] closeSubPanel: 无法确定子面板所属的主面板类型: ${panelNode.name}` ); return; } // 获取栈 const stack = this.subPanelStacks.get(basePanelType); if (!stack || stack.length === 0) { logger.warn( `[NavigationManager] closeSubPanel: 栈为空或不存在,执行降级处理: ${panelNode.name}` ); this.handleSubPanelCloseFallback(panelNode, panel, basePanelType, animationConfig); return; } // 查找子面板在栈中的索引 const panelIndex = stack.indexOf(panelNode); if (panelIndex === -1) { logger.warn( `[NavigationManager] closeSubPanel: 子面板不在栈中,执行降级处理: ${panelNode.name}` ); this.handleSubPanelCloseFallback(panelNode, panel, basePanelType, animationConfig); return; } // 从栈中移除 stack.splice(panelIndex, 1); logger.log( `[NavigationManager] 从栈中移除子面板: ${panelNode.name}, 剩余栈深度: ${stack.length}` ); // 获取动画配置(优先使用传入的配置,然后使用存储的配置,最后使用默认配置) let config = animationConfig; if (!config && this.subPanelAnimationConfigs.has(panelNode)) { config = this.subPanelAnimationConfigs.get(panelNode); } if (!config) { config = { openAnimation: PageTransitionType.SCALE, closeAnimation: PageTransitionType.SCALE, duration: 0.3, hideBasePanel: false, }; } // 检查是否需要恢复显示basePanel或显示下一层子面板 const shouldRestoreBasePanel = config.hideBasePanel && this.hiddenBasePanels.has(panelNode); const basePanel = shouldRestoreBasePanel ? this.hiddenBasePanels.get(panelNode) : null; // 获取新的栈顶子面板(如果有) const newTopSubPanel = this.getTopSubPanel(basePanelType); // 执行关闭动画 this.playSubPanelCloseAnimation(panelNode, config, () => { // 恢复被隐藏的子面板状态(如果有) this.restoreHiddenSubPanels(panelNode, newTopSubPanel); // 清理映射 this.subPanelMapping.delete(panelNode); this.subPanelAnimationConfigs.delete(panelNode); if (this.hiddenBasePanels.has(panelNode)) { this.hiddenBasePanels.delete(panelNode); } // 销毁或关闭panel try { if (panel instanceof Node) { panel.destroy(); } else { panel.close(); } } catch (error) { logger.error("[NavigationManager] closeSubPanel error:", error); } // 关闭动画完成后,决定显示什么 if (newTopSubPanel && newTopSubPanel.isValid) { // 栈中还有子面板,显示新的栈顶(不播放动画,直接显示) logger.log( `[NavigationManager] 显示新的栈顶子面板(无动画): ${newTopSubPanel.name}` ); const newTopConfig = this.subPanelAnimationConfigs.get(newTopSubPanel); this.showSubPanel(newTopSubPanel, newTopConfig, false); // 传入 false,不播放动画 } // 统一使用 updateBasePanelVisibility 更新 basePanel 显隐状态 // 这个方法会根据当前栈顶是否是特殊面板自动决定显示或隐藏 this.updateBasePanelVisibility(basePanelType); // 如果栈完全清空,删除映射 if (stack.length === 0) { this.subPanelStacks.delete(basePanelType); logger.log( `[NavigationManager] 子页面栈已清空,删除映射: ${basePanelType}` ); } }); } // ==================== 私有辅助方法 ==================== /** * 检测是否是特殊面板(GirlDetailPanel 或 ChatPanel) * 这些特殊面板在显示时需要隐藏对应的主页面 * @param panel 要检测的面板节点 * @returns 是否是特殊面板 * @private */ private isSpecialPanel(panel: Node): boolean { if (!panel || !panel.isValid) { return false; } return panel.name === "GirlDetailPanel" || panel.name === "ChatPanel"; } /** * 根据栈顶子面板更新 basePanel 的显隐状态 * 如果栈顶是特殊面板(GirlDetailPanel 或 ChatPanel),则隐藏 basePanel * 否则显示 basePanel * @param basePanelType 主面板类型 * @private */ private updateBasePanelVisibility(basePanelType: PanelType): void { const basePanel = this.panelCache.get(basePanelType); if (!basePanel || !basePanel.isValid) { return; } const topSubPanel = this.getTopSubPanel(basePanelType); if (topSubPanel && topSubPanel.isValid && this.isSpecialPanel(topSubPanel)) { // 栈顶是特殊面板,隐藏 basePanel if (basePanel.active) { logger.log( `[NavigationManager] 隐藏 basePanel(栈顶是特殊面板 ${topSubPanel.name}): ${basePanel.name}` ); basePanel.active = false; } } else { // 栈顶不是特殊面板或栈为空,显示 basePanel if (!basePanel.active) { logger.log( `[NavigationManager] 显示 basePanel(栈顶不是特殊面板): ${basePanel.name}` ); basePanel.active = true; } } } /** * 获取面板索引(用于动画方向计算) * @param panelType 面板类型 * @returns 面板索引 */ private getPanelIndex(panelType: PanelType): number { switch (panelType) { case PanelType.THEME: return 0; case PanelType.RANK: return 1; case PanelType.PAST_GIRL_LIST: return 2; case PanelType.PERSONAL: return 3; default: return 0; } } /** * 根据节点名称获取面板类型 * @param node 节点 * @returns 面板类型,如果无法识别则返回null */ private getPanelType(node: Node): PanelType | null { if (!node || !node.isValid) { logger.warn("[NavigationManager] getPanelType: node is invalid"); return null; } switch (node.name) { case "ThemePanel": return PanelType.THEME; case "RankPanel": return PanelType.RANK; case "PastGirlListPanel": return PanelType.PAST_GIRL_LIST; case "PersonalPanel": return PanelType.PERSONAL; default: logger.warn( `[NavigationManager] Unknown panel type for node: ${node.name}` ); return null; } } /** * 获取面板名称 */ private getPanelName(panelType: PanelType): string { return panelType as string; } /** * 获取面板数据(预留用于扩展) * @param panelType 面板类型 * @returns 面板数据,当前返回null */ private getPanelData(panelType: PanelType): any { // 所有主页面都不需要特殊数据,此方法预留用于未来扩展 return null; } /** * 加载或获取面板(从缓存或新建) * @param panelType 面板类型 * @param callback 面板加载完成的回调函数 */ private loadOrGetPanel( panelType: PanelType, callback: (panel: Node) => void ): void { // 检查缓存 if (this.panelCache.has(panelType)) { const cachedPanel = this.panelCache.get(panelType); if (cachedPanel && cachedPanel.isValid) { // 注意:子面板的清理已经在 switchToPanel 中通过 clearSubPanelStack 完成了 // 这里不需要再清理 // 特殊处理:主题面板刷新(避免重复调用Show) if (panelType === PanelType.THEME) { const themePanel = cachedPanel.getComponent(ThemePanel); if (themePanel) { // 不在这里调用Show(),因为ThemePanel已经有加载状态保护 // 只在必要时调用onHide()确保子页面状态正确 if (typeof themePanel.onHide === "function") { logger.log("[NavigationManager] 确保主题面板子页面状态正确"); themePanel.onHide(); } } } callback(cachedPanel); return; } else { this.panelCache.delete(panelType); } } // 加载新面板 const panelName = this.getPanelName(panelType); const openData = this.getPanelData(panelType); ViewManager.I.openBundlesView(panelName, openData, (panel: Node) => { if (panel && panel.isValid) { this.panelCache.set(panelType, panel); callback(panel); } }); } /** * 隐藏面板并播放动画 * @param panel 要隐藏的面板节点 * @param direction 动画方向 * @param callback 动画完成的回调函数 */ private hidePanelWithAnimation( panel: Node, direction: "left" | "right" | "up" | "down", callback?: () => void ): void { if (!panel || !panel.isValid) { callback && callback(); return; } // 注意:子面板的清理已经在 switchToPanel 中通过 clearSubPanelStack 完成了 // 这里只需要播放主面板的隐藏动画 if (!panel.active) panel.active = true; const animationCallback = () => { panel.active = false; callback && callback(); }; // 主面板动画 switch (direction) { case "left": UITransitionHelper.slideOutToLeft(panel, 0.3, animationCallback); break; case "right": UITransitionHelper.slideOutToRight(panel, 0.3, animationCallback); break; case "up": UITransitionHelper.slideOutToTop(panel, 0.3, animationCallback); break; case "down": UITransitionHelper.slideOutToBottom(panel, 0.3, animationCallback); break; default: // 默认左侧滑出 UITransitionHelper.slideOutToLeft(panel, 0.3, animationCallback); break; } } /** * 显示面板并播放动画 * @param panel 要显示的面板节点 * @param direction 动画方向 * @param callback 动画完成的回调函数 */ private showPanelWithAnimation( panel: Node, direction: "left" | "right" | "up" | "down", callback?: () => void ): void { if (!panel || !panel.isValid) { callback && callback(); return; } panel.active = true; switch (direction) { case "left": UITransitionHelper.slideInFromLeft(panel, 0.3, callback); break; case "right": UITransitionHelper.slideInFromRight(panel, 0.3, callback); break; case "up": UITransitionHelper.slideInFromTop(panel, 0.3, callback); break; case "down": UITransitionHelper.slideInFromBottom(panel, 0.3, callback); break; default: // 默认从右侧滑入 UITransitionHelper.slideInFromRight(panel, 0.3, callback); break; } } // ==================== 公共访问器方法 ==================== /** * 获取当前激活的面板类型 * @returns 当前激活的面板类型 */ public getCurrentActivePanel(): PanelType { return this.currentActivePanelType; } /** * 获取当前激活的面板节点 */ public getCurrentActivePanelNode(): Node { return this.currentActivePanel; } /** * 设置选中的角色ID * @param girlId 角色ID */ public setSelectedGirlId(girlId: number): void { this.selectedGirlId = girlId; sys.localStorage.setItem(NavigationManager.LastSelectGirlID, girlId); logger.log("[NavigationManager] Selected girl ID set to:", girlId); } /** * 获取当前选中的角色ID * @returns 当前选中的角色ID */ public getSelectedGirlId(): number { return this.selectedGirlId; } /** * 设置选中的主题ID * @param themeId 主题ID */ public setSelectedCategoryId(themeId: number): void { this.selectedCategoryId = themeId; logger.log("[NavigationManager] Selected theme ID set to:", themeId); } /** * 获取当前选中的主题ID * @returns 当前选中的主题ID */ public getSelectedCategoryId(): number { return this.selectedCategoryId; } }