Merge branch 'main' of 47.107.44.202:xionglijia/18xchat

This commit is contained in:
chen wei bo
2025-09-21 14:08:40 +08:00
78 changed files with 2885 additions and 2953 deletions
+5 -1
View File
@@ -45,6 +45,7 @@ import { PlayerDataService } from "../../chat18x/network/services/PlayerDataServ
import proto from "db://assets/Scripts/proto/proto.pb.js";
import { ViewManager } from "../../Main/Manager/ViewManager";
import { MainScene } from "../MainScene";
import { prodConfig } from "../../chat18x/config/env/prod";
const { ccclass, property } = _decorator;
@@ -62,13 +63,16 @@ export class PreloadUI extends Component {
private jinduBar: Node = null;
private jinduFilled: Sprite = null;
private VideoPlayer: VideoPlayer;
private appVersion: Label;
start() {
//Utils.addInnerEL(InnerMsgCode.UI_ShowPrompt, this, this.resiveShowPrompt)
Utils.parseNode(this.node, this._nodeTab);
this.appVersion = this._nodeTab.appVersion.getComponent(Label);
this.loginBtn = this._nodeTab.btnLogin;
this.loginBtn.active = false;
this.appVersion.string = `app version: ${prodConfig.appVersion}`;
this.VideoPlayer = this._nodeTab.VideoPlayer.getComponent(VideoPlayer);
GButton.BandClick(this.loginBtn, this.onLogin, this);
+14 -14
View File
@@ -2,24 +2,24 @@
* 环境的类型,接口定义
*/
export enum Env {
Dev = "dev",
Test = "test",
Prod = "prod",
Dev = "dev",
Test = "test",
Prod = "prod",
}
export interface CDNConfig {
bgm: string;
bgm: string;
}
export interface Endpoints {
httpBase: string; // 业务 HTTP
socketBase: string; // WebSocket
cdn: CDNConfig; // 各类静态资源
httpBase: string; // 业务 HTTP
socketBase: string; // WebSocket
cdn: CDNConfig; // 各类静态资源
}
export interface AppConfigShape {
env: Env;
endpoints: Endpoints;
// 其他:开关、埋点、灰度、版本号……
env: Env;
endpoints: Endpoints;
// 其他:开关、埋点、灰度、版本号……
appVersion: string;
}
+2 -1
View File
@@ -11,4 +11,5 @@ export const prodConfig: AppConfigShape = {
bgm: "https://hxzgame.vip.hnhxzkj.com/lzx/XiangQin/BGM/",
},
},
};
appVersion: "0.8.1",
};
+5 -10
View File
@@ -194,13 +194,6 @@ export class ChatController {
TipsPanel.show(LanguageUtils.getText("chat_error_code_1004"));
}
// 添加用户消息到模型
this.chatModel.addDialog(true, message);
// 更新对话显示 - 用户消息
this.dialogManager?.updateDialog(true, message, true);
this.callback?.onDialogUpdated(true);
// 通知界面消息发送开始
this.callback?.onMessageSent(message);
@@ -215,12 +208,14 @@ export class ChatController {
);
if (response) {
// 移除加载中的对话
//this.dialogManager?.removeLoadingDialog();
// 添加用户消息到模型
this.chatModel.addDialog(true, message);
// 更新对话显示 - 用户消息
this.dialogManager?.updateDialog(true, message, true);
this.callback?.onDialogUpdated(true);
// 添加AI回复到模型 (保持完整消息)
this.chatModel.addDialog(false, response);
// 更新对话显示 - AI回复 (使用分段显示)
this.dialogManager?.updateDialogWithSegments(false, response);
this.callback?.onDialogUpdated(false);
@@ -41,10 +41,22 @@ export class ChatHistoryManager {
public saveHistory(roleId: number, messages: ChatMessage[]): void {
const key = `chat_history_${roleId}`;
try {
// 检查是否已存在历史记录以保留创建时间
let createdAt = Date.now();
const existingData = sys.localStorage.getItem(key);
if (existingData) {
try {
const existingHistory: ChatHistory = JSON.parse(existingData);
createdAt = existingHistory.createdAt;
} catch (parseError) {
console.warn(`Failed to parse existing history for role ${roleId}, using current time as createdAt`);
}
}
const history: ChatHistory = {
roleId: roleId,
messages: messages,
createdAt: Date.now(),
createdAt: createdAt,
updatedAt: Date.now(),
};
@@ -200,6 +212,31 @@ export class ChatHistoryManager {
}
}
/**
* 获取指定角色的最后聊天时间
* @param roleId 角色ID
* @returns 最后聊天时间戳,如果没有历史记录则返回null
*/
public getLastChatTime(roleId: number): number | null {
const key = `chat_history_${roleId}`;
try {
const data = sys.localStorage.getItem(key);
if (data) {
const history: ChatHistory = JSON.parse(data);
if (history.messages && history.messages.length > 0) {
// 获取最后一条消息的时间戳
const lastMessage = history.messages[history.messages.length - 1];
return lastMessage.timestamp || history.updatedAt;
}
// 如果有历史但没有消息,返回创建时间
return history.createdAt;
}
} catch (error) {
console.error(`Failed to get last chat time for role ${roleId}:`, error);
}
return null;
}
/**
* 从远程服务器下载历史(预留接口)
* @param roleId 角色ID
@@ -15,6 +15,10 @@ 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",
}
/**
@@ -23,7 +27,7 @@ export enum PageTransitionType {
export enum PanelType {
THEME = "ThemePanel",
GIRL_DETAIL = "GirlDetailPanel",
CHAT = "ChatPanel",
PAST_GIRL_LIST = "PastGirlListPanel",
PERSONAL = "PersonalPanel",
}
@@ -37,6 +41,15 @@ export interface PageTransitionConfig {
simultaneous?: boolean;
}
/**
* 子面板动画配置接口
*/
export interface SubPanelAnimationConfig {
openAnimation: PageTransitionType;
closeAnimation: PageTransitionType;
duration?: number;
}
/**
* 导航管理器
*
@@ -51,18 +64,14 @@ export class NavigationManager {
// NavigationPanel相关属性
private navigationPanel: any = null; // NavigationPanel实例引用
private currentActivePanelType: PanelType = PanelType.THEME;
private currentActivePanel: Node;
private currentActivePanelType: PanelType = PanelType.PAST_GIRL_LIST;
private currentActivePanel: Node = null; // 初始化为null
private panelCache: Map<PanelType, Node> = new Map(); // 面板缓存
private currentVisiblePanel: Node = null;
// 选中的角色相关信息
private selectedGirlId: number = 10002; // 当前选中的角色ID
private selectedCategoryId: number = -1; // 当前选中的主题ID
private themePanel: ThemePanel = null;
private girlListPanel: GirlListPanel = null;
private static LastSelectGirlID = "LastSelectGirlID";
/**
@@ -119,22 +128,27 @@ export class NavigationManager {
* @param panelType 目标面板类型
*/
public switchToPanel(panelType: PanelType, force: boolean = false): Node {
if (this.currentActivePanelType === panelType && !force) {
if (panelType === PanelType.THEME) {
this.girlListPanel?.hide();
}
if (!panelType) {
console.error("[NavigationManager] switchToPanel: panelType is invalid");
return null;
}
return;
if (this.currentActivePanelType === panelType && !force) {
console.log(`[NavigationManager] Already on ${panelType}, skipping switch`);
return this.currentActivePanel;
}
if (!this.navigationPanel) {
console.warn(
"[NavigationManager] NavigationPanel not registered, fallback to direct ViewManager call"
);
this.fallbackSwitchPanel(panelType);
return;
//this.fallbackSwitchPanel(panelType);
return null;
}
// 切换面板前先关闭所有弹窗
this.closeAllPopups();
// 通知NavigationPanel显示加载状态
this.navigationPanel.showLoading?.();
@@ -147,28 +161,17 @@ export class NavigationManager {
const showDirection = isMovingRight ? "right" : "left";
// 隐藏当前面板(包括子页面)
this.hideCurrentPanelWithChildren(hideDirection);
if (!this.currentActivePanel || !this.currentActivePanel.isValid) {
return;
}
this.hidePanelWithAnimation(this.currentActivePanel, hideDirection);
// 加载并显示目标面板
this.loadOrGetPanel(panelType, (panel: Node) => {
// 清理当前面板的subPanel(在hidePanelWithAnimation中已经清理,这里不需要重复)
this.currentActivePanelType = panelType;
this.currentVisiblePanel = panel;
if (this.subPanelDic.has(this.currentActivePanel)) {
//如果有子页面,关闭掉
let subs = this.subPanelDic.get(this.currentActivePanel);
if (subs) {
subs.forEach((n) => {
if (n.name != "") {
n.getComponent(li_BaseView)?.close();
} else {
//n.destroy();
}
});
}
this.subPanelDic.delete(this.currentActivePanel);
}
// 更新NavigationPanel按钮状态
this.navigationPanel.updateButtonStates?.(panelType);
@@ -181,17 +184,425 @@ export class NavigationManager {
}
private subPanelDic: Map<Node, Node[]> = new Map();
public openSubPanel(panelName: string, basePanel: Node, data: any = null) {
private subPanelMapping: Map<Node, PanelType> = new Map();
private subPanelAnimationConfigs: Map<Node, SubPanelAnimationConfig> = new Map();
// 弹窗管理相关属性
private popupPanels: Set<Node> = new Set(); // 管理所有打开的弹窗
// 动画锁,防止动画中重复操作
private isAnimating: boolean = false;
/**
* 统一的subPanel清理方法
* @param basePanel 主面板节点
* @param destroyPanels 是否销毁面板,默认为true
*/
private clearSubPanels(basePanel: Node, destroyPanels: boolean = true): void {
if (!basePanel || !basePanel.isValid) {
return;
}
const subPanels = this.subPanelDic.get(basePanel);
if (!subPanels || subPanels.length === 0) {
return;
}
// 清理所有子面板
for (let i = subPanels.length - 1; i >= 0; i--) {
const subPanel = subPanels[i];
if (subPanel && subPanel.isValid) {
// 清理subPanelMapping
this.subPanelMapping.delete(subPanel);
// 清理动画配置
this.subPanelAnimationConfigs.delete(subPanel);
if (destroyPanels) {
const baseView = subPanel.getComponent(li_BaseView);
if (baseView) {
baseView.close();
} else {
subPanel.destroy();
}
}
}
}
// 清理subPanelDic
this.subPanelDic.delete(basePanel);
}
public openSubPanel(
panelName: string,
basePanel: Node,
data: any = null,
animationConfig?: SubPanelAnimationConfig
) {
if (!panelName) {
console.error("[NavigationManager] openSubPanel: panelName is empty");
return;
}
if (!basePanel || !basePanel.isValid) {
console.error("[NavigationManager] openSubPanel: basePanel is invalid");
return;
}
// 使用默认动画配置
const defaultConfig: SubPanelAnimationConfig = {
openAnimation: PageTransitionType.SCALE,
closeAnimation: PageTransitionType.SCALE,
duration: 0.3
};
const config = animationConfig || defaultConfig;
const callback = (n: Node) => {
if (!n || !n.isValid) {
console.error("[NavigationManager] openSubPanel: created panel is invalid");
return;
}
if (!this.subPanelDic.has(basePanel)) {
this.subPanelDic.set(basePanel, []);
}
this.subPanelDic.get(basePanel).push(n);
const subPanels = this.subPanelDic.get(basePanel);
subPanels.push(n);
const basePanelType = this.getPanelType(basePanel);
if (basePanelType) {
this.subPanelMapping.set(n, basePanelType);
} else {
console.warn(`[NavigationManager] 无法确定basePanel类型: ${basePanel.name}`);
}
// 存储动画配置
this.subPanelAnimationConfigs.set(n, config);
// 执行打开动画
this.playSubPanelOpenAnimation(n, config);
};
ViewManager.I.openBundlesView(panelName, data, callback);
}
/**
* 支持自定义动画方向的面板切换方法
* @param panelType 目标面板类型
* @param transitionConfig 过渡动画配置
* @param force 是否强制切换
*/
public switchToPanelWithTransition(
panelType: PanelType,
transitionConfig: PageTransitionConfig,
force: boolean = false
): Node {
if (!panelType) {
console.error("[NavigationManager] switchToPanelWithTransition: panelType is invalid");
return null;
}
if (this.currentActivePanelType === panelType && !force) {
console.log(`[NavigationManager] Already on ${panelType}, skipping switch`);
return this.currentActivePanel;
}
if (!this.navigationPanel) {
console.warn(
"[NavigationManager] NavigationPanel not registered, fallback to direct ViewManager call"
);
return null;
}
// 切换面板前先关闭所有弹窗
this.closeAllPopups();
// 通知NavigationPanel显示加载状态
this.navigationPanel.showLoading?.();
// 获取动画方向
const hideDirection = this.getDirectionFromTransitionType(transitionConfig.outgoingTransition);
const showDirection = this.getDirectionFromTransitionType(transitionConfig.incomingTransition);
// 隐藏当前面板(包括子页面)
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?.();
});
});
return null;
}
/**
* 将过渡类型转换为方向字符串
*/
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) {
console.error("[NavigationManager] openPopupPanel: panelName is empty");
return;
}
const popupCallback = (panel: Node) => {
if (!panel || !panel.isValid) {
console.error("[NavigationManager] openPopupPanel: created panel is invalid");
return;
}
// 将弹窗添加到管理集合中
this.popupPanels.add(panel);
console.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) {
console.warn("[NavigationManager] closePopupPanel: panel is invalid");
return;
}
// 从弹窗管理集合中移除
if (this.popupPanels.has(panel)) {
this.popupPanels.delete(panel);
console.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) {
console.error("[NavigationManager] closePopupPanel error:", error);
}
}
/**
* 关闭所有弹窗
*/
private closeAllPopups(): void {
if (this.popupPanels.size === 0) {
return;
}
console.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();
}
public closeSubPanel(panel: Node | li_BaseView, animationConfig?: SubPanelAnimationConfig) {
if (!panel) {
console.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) {
console.warn("[NavigationManager] closeSubPanel: panelNode is invalid");
return;
}
// 获取动画配置(优先使用传入的配置,然后使用存储的配置,最后使用默认配置)
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
};
}
// 执行关闭动画
this.playSubPanelCloseAnimation(panelNode, config, () => {
// 从管理映射中移除
this.removeSubPanelFromMappings(panelNode);
// 销毁或关闭panel
try {
if (panel instanceof Node) {
panel.destroy();
} else {
panel.close();
}
} catch (error) {
console.error("[NavigationManager] closeSubPanel error:", error);
}
});
}
/**
* 从管理映射中移除子面板
*/
private removeSubPanelFromMappings(panelNode: Node): void {
// 从subPanelMapping和subPanelDic中移除
if (this.subPanelMapping.has(panelNode)) {
const baseType = this.subPanelMapping.get(panelNode);
const baseNode = this.panelCache.get(baseType);
if (baseNode && this.subPanelDic.has(baseNode)) {
const subPanels = this.subPanelDic.get(baseNode);
const index = subPanels.indexOf(panelNode);
if (index !== -1) {
subPanels.splice(index, 1);
// 如果subPanels为空,删除整个条目
if (subPanels.length === 0) {
this.subPanelDic.delete(baseNode);
}
} else {
console.warn(`[NavigationManager] ${panelNode.name} 不在子页面列表中`);
}
}
this.subPanelMapping.delete(panelNode);
}
// 移除动画配置
if (this.subPanelAnimationConfigs.has(panelNode)) {
this.subPanelAnimationConfigs.delete(panelNode);
}
}
/**
* 获取面板索引(用于动画方向计算)
*/
@@ -201,7 +612,7 @@ export class NavigationManager {
return 0;
case PanelType.GIRL_DETAIL:
return 1;
case PanelType.CHAT:
case PanelType.PAST_GIRL_LIST:
return 2;
case PanelType.PERSONAL:
return 3;
@@ -209,6 +620,26 @@ export class NavigationManager {
return 0;
}
}
private getPanelType(node: Node): PanelType {
if (!node || !node.isValid) {
console.warn("[NavigationManager] getPanelType: node is invalid");
return null;
}
switch (node.name) {
case "ThemePanel":
return PanelType.THEME;
case "GirlDetailPanel":
return PanelType.GIRL_DETAIL;
case "PastGirlListPanel":
return PanelType.PAST_GIRL_LIST;
case "PersonalPanel":
return PanelType.PERSONAL;
default:
console.warn(`[NavigationManager] Unknown panel type for node: ${node.name}`);
return null;
}
}
/**
* 获取面板名称
@@ -224,8 +655,6 @@ export class NavigationManager {
switch (panelType) {
case PanelType.GIRL_DETAIL:
return this.selectedGirlId; // 使用当前选中的角色ID
case PanelType.CHAT:
return { girlId: this.selectedGirlId }; // 使用当前选中的角色ID
default:
return null;
}
@@ -242,6 +671,9 @@ export class NavigationManager {
if (this.panelCache.has(panelType)) {
const cachedPanel = this.panelCache.get(panelType);
if (cachedPanel && cachedPanel.isValid) {
// 确保缓存panel的subPanel状态正确,清理可能残留的subPanel引用
this.clearSubPanels(cachedPanel, false); // 不销毁panel,只清理引用
// 特殊处理:主题面板刷新(避免重复调用Show)
if (panelType === PanelType.THEME) {
const themePanel = cachedPanel.getComponent(ThemePanel);
@@ -268,78 +700,17 @@ export class NavigationManager {
ViewManager.I.openBundlesView(panelName, openData, (panel: Node) => {
if (panel && panel.isValid) {
this.panelCache.set(panelType, panel);
if (panelName === "ThemePanel") {
this.themePanel = panel.getComponent(ThemePanel);
}
callback(panel);
}
});
}
/**
* 隐藏当前面板及其子页面
*/
private hideCurrentPanelWithChildren(direction: "left" | "right") {
if (!this.currentVisiblePanel) {
return;
}
// 如果当前是ThemePanel,需要同时处理可能的子页面(GirlListPanel
if (this.currentActivePanelType === PanelType.THEME) {
// 查找并隐藏GirlListPanel子页面
this.hideThemePanelChildren(direction);
// 隐藏ThemePanel主面板
this.hidePanelWithAnimation(this.currentVisiblePanel, direction);
} else {
// 其他面板直接隐藏
this.hidePanelWithAnimation(this.currentVisiblePanel, direction);
}
}
/**
* 隐藏ThemePanel的子页面
*/
private hideThemePanelChildren(direction: "left" | "right") {
const themePanel = this.findThemePanelNode();
if (themePanel) {
// 获取子页面节点
const childPanel = this.getThemePanelChildNode(themePanel);
if (childPanel && childPanel.active) {
console.log(
"[NavigationManager] Hiding ThemePanel child with animation"
);
this.hidePanelWithAnimation(childPanel, direction);
}
}
}
/**
* 获取ThemePanel的子页面节点
*/
private getThemePanelChildNode(themePanel: ThemePanel): Node | null {
try {
// ThemePanel存储子页面在currentChildPanel属性中
const childPanel = themePanel.getChildPanel();
if (childPanel) return childPanel.node;
return null;
} catch (error) {
console.error(
"[NavigationManager] Error getting child panel node:",
error
);
return null;
}
}
/**
* 隐藏面板动画
*/
private hidePanelWithAnimation(
panel: Node,
direction: "left" | "right",
direction: "left" | "right" | "up" | "down",
callback?: Function
) {
if (!panel || !panel.isValid || !panel.active) {
@@ -347,16 +718,30 @@ export class NavigationManager {
return;
}
if (direction === "left") {
UITransitionHelper.slideOutToLeft(panel, 0.3, () => {
panel.active = false;
callback && callback();
});
} else {
UITransitionHelper.slideOutToRight(panel, 0.3, () => {
panel.active = false;
callback && callback();
});
const animationCallback = () => {
panel.active = false;
// 使用统一的subPanel清理方法
this.clearSubPanels(panel, true);
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;
}
}
@@ -365,7 +750,7 @@ export class NavigationManager {
*/
private showPanelWithAnimation(
panel: Node,
direction: "left" | "right",
direction: "left" | "right" | "up" | "down",
callback?: Function
) {
if (!panel || !panel.isValid) {
@@ -375,28 +760,24 @@ export class NavigationManager {
panel.active = true;
if (panel.name === "ThemePanel" && this.girlListPanel) {
this.showPanelWithAnimation(this.girlListPanel.node, direction, callback);
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;
}
if (direction === "right") {
UITransitionHelper.slideInFromRight(panel, 0.3, () => {
callback && callback();
});
} else {
UITransitionHelper.slideInFromLeft(panel, 0.3, () => {
callback && callback();
});
}
}
/**
* 后备切换方法(当NavigationPanel未注册时)
*/
private fallbackSwitchPanel(panelType: PanelType): void {
const panelName = this.getPanelName(panelType);
const openData = this.getPanelData(panelType);
ViewManager.I.openBundlesView(panelName, openData);
}
/**
@@ -406,6 +787,13 @@ export class NavigationManager {
return this.currentActivePanelType;
}
/**
* 获取当前激活的面板节点
*/
public getCurrentActivePanelNode(): Node {
return this.currentActivePanel;
}
/**
* 设置选中的角色ID
* @param girlId 角色ID
@@ -440,152 +828,4 @@ export class NavigationManager {
public getSelectedCategoryId(): number {
return this.selectedCategoryId;
}
/**
* 进入角色列表页面(作为 ThemePanel 的子页面)
*
* @param {number} themeId - 主题ID,用于筛选角色
*
* @example
* ```typescript
* NavigationManager.Instance.navigateToGirlList(1);
* ```
*/
public navigateToGirlList(themeId: number): void {
if (themeId == null || themeId < 0) {
console.warn("Invalid theme ID for girl list navigation:", themeId);
return;
}
// 更新选中的主题ID
this.setSelectedCategoryId(themeId);
console.log(
`[NavigationManager] Navigating to girl list with theme ID: ${themeId}`
);
// 先检查是否已有GirlListPanel存在
const existingGirlListPanel = this.findExistingGirlListPanel();
if (existingGirlListPanel) {
// 如果面板已存在,重新显示并刷新数据
console.log("[NavigationManager] Reactivating existing GirlListPanel");
this.reactivateGirlListPanel(existingGirlListPanel);
} else {
// 如果面板不存在,创建新的
console.log("[NavigationManager] Creating new GirlListPanel");
ViewManager.I.openBundlesView(
"GirlListPanel",
{
themeId: themeId,
parentPanel: "ThemePanel", // 标记父页面
},
(girlListPanelNode) => {
this.girlListPanel = girlListPanelNode.getComponent(GirlListPanel);
// 获取 ThemePanel 实例并设置子页面关系
console.log(
"[NavigationManager] GirlListPanel opened, setting up parent-child relationship"
);
this.setupParentChildRelationship(girlListPanelNode);
}
);
}
}
/**
* 查找已存在的GirlListPanel
*/
private findExistingGirlListPanel(): GirlListPanel {
if (this.girlListPanel) return this.girlListPanel;
else return null;
}
/**
* 重新激活已存在的GirlListPanel
*/
private reactivateGirlListPanel(panel: GirlListPanel): void {
try {
// 重新激活面板
panel.node.active = true;
// 获取面板组件并刷新数据
//const girlListComponent = panelNode.getComponent("GirlListPanel");
if (panel) {
// 如果有刷新方法,调用它来更新主题数据
panel.refresh();
console.log(
"[NavigationManager] GirlListPanel reactivated with theme:",
this.selectedCategoryId
);
}
// 重新建立父子关系
this.setupParentChildRelationship(panel);
} catch (error) {
console.error(
"[NavigationManager] Error reactivating GirlListPanel:",
error
);
}
}
/**
* 建立父子页面关系的私有方法
* @private
* @param girlListPanelNode GirlListPanel 节点
*/
private setupParentChildRelationship(girlListPanelNode: any): void {
try {
// 查找 ThemePanel 实例
const themePanel = this.findThemePanelNode();
if (themePanel) {
const girlListPanelComponent =
girlListPanelNode.getComponent(GirlListPanel);
if (themePanel && girlListPanelComponent) {
console.log(
"[NavigationManager] Setting up parent-child relationship"
);
this.themePanel.setChildPanel(girlListPanelComponent);
} else {
console.warn("[NavigationManager] Failed to get panel components");
}
} else {
console.warn("[NavigationManager] ThemePanel not found");
}
} catch (error) {
console.error(
"[NavigationManager] Error setting up parent-child relationship:",
error
);
}
}
/**
* 查找 ThemePanel 节点的私有方法
* @private
* @returns ThemePanel 节点或 null
*/
private findThemePanelNode(): ThemePanel {
if (this.themePanel) return this.themePanel;
return null;
}
/**
* 处理导航时 ThemePanel 子页面的隐藏逻辑
* @private
*/
private handleThemePanelChildPanelsOnNavigation(): void {
console.log(
"[NavigationManager] Handling ThemePanel child panels on navigation"
);
const themePanelNode = this.findThemePanelNode();
if (themePanelNode) {
if (themePanelNode && themePanelNode.hideChildPanel) {
console.log("[NavigationManager] Closing ThemePanel child panels");
themePanelNode.hideChildPanel();
}
}
}
}
@@ -1,287 +1,307 @@
import { _decorator, Component, Node, log, Button, Label } from 'cc';
import { ChatAIService } from '../core/ChatAIService';
import { _decorator, Component, Node, log, Button, Label } from "cc";
import { ChatAIService } from "../core/ChatAIService";
const { ccclass, property } = _decorator;
interface TestResult {
roleId: number;
success: boolean;
response?: string;
error?: string;
duration: number;
roleId: number;
success: boolean;
response?: string;
error?: string;
duration: number;
}
interface TestReport {
totalTests: number;
successCount: number;
failureCount: number;
successIds: number[];
failureIds: number[];
results: TestResult[];
totalDuration: number;
totalTests: number;
successCount: number;
failureCount: number;
successIds: number[];
failureIds: number[];
results: TestResult[];
totalDuration: number;
}
@ccclass('ChatAIServiceBatchTest')
@ccclass("ChatAIServiceBatchTest")
export class ChatAIServiceBatchTest extends Component {
@property(Button)
startTestButton: Button = null;
@property(Button)
startTestButton: Button = null;
@property(Label)
statusLabel: Label = null;
@property(Label)
statusLabel: Label = null;
@property(Label)
resultLabel: Label = null;
@property(Label)
resultLabel: Label = null;
private isTestRunning: boolean = false;
private testReport: TestReport = null;
private isTestRunning: boolean = false;
private testReport: TestReport = null;
onLoad() {
if (this.startTestButton) {
this.startTestButton.node.on(Button.EventType.CLICK, this.startBatchTest, this);
}
this.updateStatus("点击开始按钮进行批量测试");
onLoad() {
if (this.startTestButton) {
this.startTestButton.node.on(
Button.EventType.CLICK,
this.startBatchTest,
this
);
}
/**
* 延时工具函数
* @param seconds 延时秒数
*/
private async delay(seconds: number): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, seconds * 1000);
});
this.updateStatus("点击开始按钮进行批量测试");
}
/**
* 延时工具函数
* @param seconds 延时秒数
*/
private async delay(seconds: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, seconds * 1000);
});
}
/**
* 更新状态显示
*/
private updateStatus(message: string): void {
if (this.statusLabel) {
this.statusLabel.string = message;
}
log(`[ChatAIBatchTest] ${message}`);
}
/**
* 更新状态显示
*/
private updateStatus(message: string): void {
if (this.statusLabel) {
this.statusLabel.string = message;
}
log(`[ChatAIBatchTest] ${message}`);
}
/**
* 更新结果显示
*/
private updateResult(report: TestReport): void {
if (!this.resultLabel) return;
/**
* 更新结果显示
*/
private updateResult(report: TestReport): void {
if (!this.resultLabel) return;
const resultText = `测试完成!
const resultText = `测试完成!
总测试数: ${report.totalTests}
成功数: ${report.successCount}
失败数: ${report.failureCount}
总耗时: ${(report.totalDuration / 1000).toFixed(2)}s
成功的ID: ${report.successIds.join(', ')}
成功的ID: ${report.successIds.join(", ")}
失败的ID: ${report.failureIds.join(', ')}`;
失败的ID: ${report.failureIds.join(", ")}`;
this.resultLabel.string = resultText;
}
this.resultLabel.string = resultText;
}
/**
* 测试单个角色ID
*/
private async testSingleRole(roleId: number): Promise<TestResult> {
const startTime = Date.now();
/**
* 测试单个角色ID
*/
private async testSingleRole(roleId: number): Promise<TestResult> {
const startTime = Date.now();
try {
this.updateStatus(`正在测试角色ID: ${roleId}`);
try {
this.updateStatus(`正在测试角色ID: ${roleId}`);
const response = await ChatAIService.Instance.sendMessage(roleId, "hello");
const duration = Date.now() - startTime;
const response = await ChatAIService.Instance.sendMessage(
roleId,
"hello"
);
const duration = Date.now() - startTime;
if (response && response.trim() !== "") {
log(`[ChatAIBatchTest] 角色 ${roleId} 测试成功: ${response.substring(0, 50)}...`);
return {
roleId,
success: true,
response: response.substring(0, 100), // 只记录前100字符
duration
};
} else {
log(`[ChatAIBatchTest] 角色 ${roleId} 返回空响应`);
return {
roleId,
success: false,
error: "返回空响应",
duration
};
}
} catch (error) {
const duration = Date.now() - startTime;
const errorMessage = error instanceof Error ? error.message : String(error);
log(`[ChatAIBatchTest] 角色 ${roleId} 测试失败: ${errorMessage}`);
return {
roleId,
success: false,
error: errorMessage,
duration
};
}
}
/**
* 开始批量测试
*/
public async startBatchTest(): Promise<void> {
if (this.isTestRunning) {
this.updateStatus("测试正在进行中,请等待...");
return;
}
this.isTestRunning = true;
if (this.startTestButton) {
this.startTestButton.interactable = false;
}
if (this.resultLabel) {
this.resultLabel.string = "";
}
const startTime = Date.now();
const results: TestResult[] = [];
const successIds: number[] = [];
const failureIds: number[] = [];
log("[ChatAIBatchTest] 开始批量测试角色ID 10001-10030");
this.updateStatus("开始批量测试...");
// 测试角色ID 10001-10030
for (let roleId = 10001; roleId <= 10030; roleId++) {
try {
// 测试单个角色
const result = await this.testSingleRole(roleId);
results.push(result);
if (result.success) {
successIds.push(roleId);
} else {
failureIds.push(roleId);
}
// 等待10秒(最后一个不需要等待)
if (roleId < 10030) {
this.updateStatus(`角色 ${roleId} 测试完成,等待10秒...`);
await this.delay(10);
}
} catch (error) {
log(`[ChatAIBatchTest] 测试角色 ${roleId} 时发生意外错误: ${error}`);
results.push({
roleId,
success: false,
error: `意外错误: ${error}`,
duration: 0
});
failureIds.push(roleId);
}
}
const totalDuration = Date.now() - startTime;
// 生成测试报告
this.testReport = {
totalTests: 30,
successCount: successIds.length,
failureCount: failureIds.length,
successIds,
failureIds,
results,
totalDuration
if (response && response.trim() !== "") {
log(
`[ChatAIBatchTest] 角色 ${roleId} 测试成功: ${response.substring(
0,
50
)}...`
);
return {
roleId,
success: true,
response: response.substring(0, 100), // 只记录前100字符
duration,
};
} else {
log(`[ChatAIBatchTest] 角色 ${roleId} 返回空响应`);
return {
roleId,
success: false,
error: "返回空响应",
duration,
};
}
} catch (error) {
const duration = Date.now() - startTime;
const errorMessage =
error instanceof Error ? error.message : String(error);
log(`[ChatAIBatchTest] 角色 ${roleId} 测试失败: ${errorMessage}`);
// 输出详细报告到控制台
this.logDetailedReport(this.testReport);
return {
roleId,
success: false,
error: errorMessage,
duration,
};
}
}
// 更新UI显示
this.updateResult(this.testReport);
this.updateStatus("批量测试完成!");
if (this.startTestButton) {
this.startTestButton.interactable = true;
}
this.isTestRunning = false;
/**
* 开始批量测试
*/
public async startBatchTest(): Promise<void> {
if (this.isTestRunning) {
this.updateStatus("测试正在进行中,请等待...");
return;
}
/**
* 输出详细测试报告到控制台
*/
private logDetailedReport(report: TestReport): void {
log("========== ChatAI 批量测试报告 ==========");
log(`测试时间: ${new Date().toLocaleString()}`);
log(`总测试数: ${report.totalTests}`);
log(`成功数: ${report.successCount}`);
log(`失败数: ${report.failureCount}`);
log(`成功率: ${((report.successCount / report.totalTests) * 100).toFixed(2)}%`);
log(`总耗时: ${(report.totalDuration / 1000).toFixed(2)}`);
log(`平均耗时: ${(report.totalDuration / report.totalTests / 1000).toFixed(2)}秒/测试`);
this.isTestRunning = true;
log("\n===== 成功的角色ID =====");
if (report.successIds.length > 0) {
log(report.successIds.join(', '));
if (this.startTestButton) {
this.startTestButton.interactable = false;
}
if (this.resultLabel) {
this.resultLabel.string = "";
}
const startTime = Date.now();
const results: TestResult[] = [];
const successIds: number[] = [];
const failureIds: number[] = [];
log("[ChatAIBatchTest] 开始批量测试角色ID 10001-10030");
this.updateStatus("开始批量测试...");
const list = [10010, 10018];
// 测试角色ID 10001-10030
for (let i = 0; i <= list.length; i++) {
const roleId = list[i];
try {
// 测试单个角色
const result = await this.testSingleRole(roleId);
results.push(result);
if (result.success) {
successIds.push(roleId);
} else {
log("无");
failureIds.push(roleId);
}
log("\n===== 失败的角色ID =====");
if (report.failureIds.length > 0) {
log(report.failureIds.join(', '));
log("\n===== 失败详情 =====");
report.results
.filter(r => !r.success)
.forEach(result => {
log(`角色 ${result.roleId}: ${result.error}`);
});
} else {
log("无");
// 等待10秒(最后一个不需要等待)
if (roleId < 10030) {
this.updateStatus(`角色 ${roleId} 测试完成,等待10秒...`);
await this.delay(10);
}
log("\n===== 详细测试结果 =====");
report.results.forEach(result => {
const status = result.success ? "成功" : "失败";
const duration = (result.duration / 1000).toFixed(2);
const extra = result.success
? `响应: ${result.response?.substring(0, 30)}...`
: `错误: ${result.error}`;
log(`角色 ${result.roleId}: ${status} (${duration}s) - ${extra}`);
} catch (error) {
log(`[ChatAIBatchTest] 测试角色 ${roleId} 时发生意外错误: ${error}`);
results.push({
roleId,
success: false,
error: `意外错误: ${error}`,
duration: 0,
});
log("========================================");
failureIds.push(roleId);
}
}
/**
* 获取测试报告(供外部调用)
*/
public getTestReport(): TestReport {
return this.testReport;
const totalDuration = Date.now() - startTime;
// 生成测试报告
this.testReport = {
totalTests: 30,
successCount: successIds.length,
failureCount: failureIds.length,
successIds,
failureIds,
results,
totalDuration,
};
// 输出详细报告到控制台
this.logDetailedReport(this.testReport);
// 更新UI显示
this.updateResult(this.testReport);
this.updateStatus("批量测试完成!");
if (this.startTestButton) {
this.startTestButton.interactable = true;
}
/**
* 重置测试状态
*/
public resetTest(): void {
this.isTestRunning = false;
this.testReport = null;
this.isTestRunning = false;
}
if (this.statusLabel) {
this.statusLabel.string = "点击开始按钮进行批量测试";
}
/**
* 输出详细测试报告到控制台
*/
private logDetailedReport(report: TestReport): void {
log("========== ChatAI 批量测试报告 ==========");
log(`测试时间: ${new Date().toLocaleString()}`);
log(`总测试数: ${report.totalTests}`);
log(`成功数: ${report.successCount}`);
log(`失败数: ${report.failureCount}`);
log(
`成功率: ${((report.successCount / report.totalTests) * 100).toFixed(2)}%`
);
log(`总耗时: ${(report.totalDuration / 1000).toFixed(2)}`);
log(
`平均耗时: ${(report.totalDuration / report.totalTests / 1000).toFixed(
2
)}秒/测试`
);
if (this.resultLabel) {
this.resultLabel.string = "";
}
if (this.startTestButton) {
this.startTestButton.interactable = true;
}
log("\n===== 成功的角色ID =====");
if (report.successIds.length > 0) {
log(report.successIds.join(", "));
} else {
log("无");
}
}
log("\n===== 失败的角色ID =====");
if (report.failureIds.length > 0) {
log(report.failureIds.join(", "));
log("\n===== 失败详情 =====");
report.results
.filter((r) => !r.success)
.forEach((result) => {
log(`角色 ${result.roleId}: ${result.error}`);
});
} else {
log("无");
}
log("\n===== 详细测试结果 =====");
report.results.forEach((result) => {
const status = result.success ? "成功" : "失败";
const duration = (result.duration / 1000).toFixed(2);
const extra = result.success
? `响应: ${result.response?.substring(0, 30)}...`
: `错误: ${result.error}`;
log(`角色 ${result.roleId}: ${status} (${duration}s) - ${extra}`);
});
log("========================================");
}
/**
* 获取测试报告(供外部调用)
*/
public getTestReport(): TestReport {
return this.testReport;
}
/**
* 重置测试状态
*/
public resetTest(): void {
this.isTestRunning = false;
this.testReport = null;
if (this.statusLabel) {
this.statusLabel.string = "点击开始按钮进行批量测试";
}
if (this.resultLabel) {
this.resultLabel.string = "";
}
if (this.startTestButton) {
this.startTestButton.interactable = true;
}
}
}
@@ -30,7 +30,7 @@ export class ChatContentsLayout extends Component {
// 缓存池
private cachedBubbles: DialogBubble[] = [];
// 等待回复气泡的引用
private waitingBubble: DialogBubble = null;
//private waitingBubble: DialogBubble = null;
// 延时任务ID数组
private delayedTasks: number[] = [];
// ScrollView组件引用
@@ -52,13 +52,22 @@ export class ChatContentsLayout extends Component {
this.fixMaxWidth = 730;
// 获取或添加UIOpacity组件到content节点
this.contentOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
this.contentOpacity =
this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
// 注册触摸事件
if (this.scrollView && this.scrollView.node) {
this.scrollView.node.on(NodeEventType.TOUCH_START, this.onTouchStart, this);
this.scrollView.node.on(
NodeEventType.TOUCH_START,
this.onTouchStart,
this
);
this.scrollView.node.on(NodeEventType.TOUCH_END, this.onTouchEnd, this);
this.scrollView.node.on(NodeEventType.TOUCH_CANCEL, this.onTouchEnd, this);
this.scrollView.node.on(
NodeEventType.TOUCH_CANCEL,
this.onTouchEnd,
this
);
}
}
@@ -85,11 +94,11 @@ export class ChatContentsLayout extends Component {
this.bubbles.push(playerBubble);
// 创建等待回复气泡(插入到首位,显示在玩家消息上面)
this.waitingBubble = this.createOrGetBubble(false);
this.waitingBubble.node.active = true;
this.waitingBubble.updateBubbleContent("...", false, undefined, true);
this.waitingBubble.node.setSiblingIndex(0);
this.bubbles.push(this.waitingBubble);
// this.waitingBubble = this.createOrGetBubble(false);
// this.waitingBubble.node.active = true;
// this.waitingBubble.updateBubbleContent("...", false, undefined, true);
// this.waitingBubble.node.setSiblingIndex(0);
// this.bubbles.push(this.waitingBubble);
this.node.position = Vec3.ZERO;
// 滚动到底部(等待下一帧Layout更新完成)
this.scheduleOnce(() => {
@@ -102,9 +111,9 @@ export class ChatContentsLayout extends Component {
*/
updateAIDialogs(dialogs: Dialog[]): void {
// 清除等待回复气泡
if (this.waitingBubble) {
this.removeWaitingBubble();
}
// if (this.waitingBubble) {
// this.removeWaitingBubble();
// }
// 清理之前的延时任务
this.clearDelayedTasks();
@@ -124,7 +133,7 @@ export class ChatContentsLayout extends Component {
}
}
this.bubbles = [];
this.waitingBubble = null;
//this.waitingBubble = null;
// 清理多余的缓存气泡
this.cleanupExcessCachedBubbles();
@@ -164,19 +173,19 @@ export class ChatContentsLayout extends Component {
/**
* 移除等待回复气泡
*/
private removeWaitingBubble(): void {
if (this.waitingBubble && this.waitingBubble.node) {
this.waitingBubble.node.active = false;
this.cachedBubbles.push(this.waitingBubble);
// private removeWaitingBubble(): void {
// if (this.waitingBubble && this.waitingBubble.node) {
// this.waitingBubble.node.active = false;
// this.cachedBubbles.push(this.waitingBubble);
// 从当前气泡列表中移除
const index = this.bubbles.indexOf(this.waitingBubble);
if (index > -1) {
this.bubbles.splice(index, 1);
}
}
this.waitingBubble = null;
}
// // 从当前气泡列表中移除
// const index = this.bubbles.indexOf(this.waitingBubble);
// if (index > -1) {
// this.bubbles.splice(index, 1);
// }
// }
// this.waitingBubble = null;
// }
/**
* 带延时地添加AI对话
@@ -316,9 +325,17 @@ export class ChatContentsLayout extends Component {
protected onDestroy(): void {
// 清理事件监听
if (this.scrollView && this.scrollView.node) {
this.scrollView.node.off(NodeEventType.TOUCH_START, this.onTouchStart, this);
this.scrollView.node.off(
NodeEventType.TOUCH_START,
this.onTouchStart,
this
);
this.scrollView.node.off(NodeEventType.TOUCH_END, this.onTouchEnd, this);
this.scrollView.node.off(NodeEventType.TOUCH_CANCEL, this.onTouchEnd, this);
this.scrollView.node.off(
NodeEventType.TOUCH_CANCEL,
this.onTouchEnd,
this
);
}
// 清理计时器
@@ -17,6 +17,7 @@ import { GirlData } from "../../data/GirlData";
import { EnvData } from "../../data/EnvData";
import proto from "db://assets/Scripts/proto/proto.pb.js";
import { GButton } from "../../../Main/Common/GButton";
import { NavigationManager } from "../../manager/NavigationManager";
const { ccclass, property } = _decorator;
@ccclass("ImagePopup")
@@ -105,9 +106,9 @@ export class ImagePopup extends Component {
url: girlData.getGrilPhotoPic(this.categoryId, this.girlId, this.resId),
isImg: true,
};
ViewManager.I.openBundlesView("ShowPanel", data);
NavigationManager.Instance.openPopupPanel("ShowPanel", data);
} else {
ViewManager.I.openBundlesView("PopupGirlDetailPanel", {
NavigationManager.Instance.openPopupPanel("PopupGirlDetailPanel", {
category: this.categoryId,
resId: this.resId,
girlId: this.girlId,
@@ -168,7 +168,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
);
if (!isFree && !isRelease) {
//未解锁
ViewManager.I.openBundlesPopupView("GirlListPopupPanel", {
NavigationManager.Instance.openPopupPanel("GirlListPopupPanel", {
base: this,
category: this.categoryId,
id: this.id,
@@ -267,8 +267,6 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
// this.videoLayer.playVideo(currentVideo, { size, position });
}
onChatCountChange() {
if (NavigationManager.Instance.getCurrentActivePanel() != PanelType.CHAT)
return;
console.log("Trigger On ChatCount Change");
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
let triggerId = girlData.getTriggerGrilPhotoId(this.categoryId, this.id);
@@ -507,7 +505,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
NavigationManager.Instance.switchToPanel(PanelType.GIRL_DETAIL);
}
onClickRecord() {
ViewManager.I.openBundlesView("RecordPanel", this.id);
NavigationManager.Instance.openPopupPanel("RecordPanel", this.id);
}
/**
@@ -132,7 +132,6 @@ export class GirlDetailPanel extends li_BaseView {
return new Size(600, 800);
}
nameKey: string;
tagKey: string;
descKey: string;
@@ -312,7 +311,17 @@ export class GirlDetailPanel extends li_BaseView {
OnClickChatBtn() {
// 使用带过渡动画的导航方法
NavigationManager.Instance.switchToPanel(PanelType.CHAT);
NavigationManager.Instance.switchToPanel(PanelType.PAST_GIRL_LIST);
// 延迟打开 ChatPanel,确保 PastGirlListPanel 已经完全加载
this.scheduleOnce(() => {
// 获取当前活动的 PastGirlListPanel 节点
const baseNode = NavigationManager.Instance.getCurrentActivePanelNode();
if (baseNode) {
const chatData = { girlId: this.id };
NavigationManager.Instance.openSubPanel("ChatPanel", baseNode, chatData);
}
}, 0.5); // 延迟 0.5 秒,确保动画完成
}
returnBtn() {
@@ -3,13 +3,10 @@ import li_BaseView from "db://assets/Scripts/Main/Common/li_BaseView";
import Utils from "db://assets/Scripts/Main/Common/Utils";
import { GButton } from "db://assets/Scripts/Main/Common/GButton";
import { GirlListItem } from "../../uiitems/GirlListItem";
import { ConfigManager } from "../../manager/ConfigManager";
import { GirlService } from "db://assets/Scripts/chat18x/network/services/GirlService";
import { DataManager, DataId } from "../../data/DataManager";
import { GirlData } from "../../data/GirlData";
import proto from "db://assets/Scripts/proto/proto.pb.js";
import GameRootUI from "../../../Main/Common/GameRootUI";
import { UITransitionHelper } from "../../utils/UITransitionHelper";
import { NavigationManager, PanelType } from "../../manager/NavigationManager";
const { ccclass, property } = _decorator;
@@ -28,7 +25,6 @@ export class GirlListPanel extends li_BaseView {
private maxPoolSize: number = 20;
category: number;
private parentPanelName: string = null;
onLoadCT() {
Utils.parseNode(this.node, this._nodeTab);
@@ -15,7 +15,7 @@ export class NavigationPanel extends li_BaseView {
private themeBtn: NavigationBtn;
private girlDetailBtn: NavigationBtn;
private chatBtn: NavigationBtn;
private pastGirlListBtn: NavigationBtn;
private settingBtn: NavigationBtn;
private loadingView: Node;
@@ -26,7 +26,7 @@ export class NavigationPanel extends li_BaseView {
this.themeBtn = this._nodeTab.themeBtn.getComponent(NavigationBtn);
this.girlDetailBtn =
this._nodeTab.girlDetailBtn.getComponent(NavigationBtn);
this.chatBtn = this._nodeTab.chatBtn.getComponent(NavigationBtn);
this.pastGirlListBtn = this._nodeTab.chatBtn.getComponent(NavigationBtn);
this.settingBtn = this._nodeTab.settingBtn.getComponent(NavigationBtn);
this.loadingView = this._nodeTab.loadingView;
@@ -36,7 +36,7 @@ export class NavigationPanel extends li_BaseView {
NavigationManager.Instance.registerNavigationPanel(this);
this.registerListener();
this.updateButtonStates(PanelType.THEME);
this.updateButtonStates(PanelType.PAST_GIRL_LIST);
// 初次打开时通过NavigationManager切换到主题面板
this.initializeFirstPanel();
@@ -57,8 +57,8 @@ export class NavigationPanel extends li_BaseView {
this
);
GButton.BandClick(
this.chatBtn.node,
() => NavigationManager.Instance.switchToPanel(PanelType.CHAT),
this.pastGirlListBtn.node,
() => NavigationManager.Instance.switchToPanel(PanelType.PAST_GIRL_LIST),
this
);
GButton.BandClick(
@@ -101,13 +101,13 @@ export class NavigationPanel extends li_BaseView {
this.girlDetailBtn.setFocus(
this.currentActivePanel === PanelType.GIRL_DETAIL
);
this.chatBtn.setFocus(this.currentActivePanel === PanelType.CHAT);
this.pastGirlListBtn.setFocus(this.currentActivePanel === PanelType.PAST_GIRL_LIST);
this.settingBtn.setFocus(this.currentActivePanel === PanelType.PERSONAL);
}
private initializeFirstPanel() {
// 初次打开时通过NavigationManager切换到主题面板
NavigationManager.Instance.switchToPanel(PanelType.THEME, true);
// 初次打开时通过NavigationManager切换到历史女孩列表面板
NavigationManager.Instance.switchToPanel(PanelType.PAST_GIRL_LIST, true);
}
public showPanel() {
@@ -294,6 +294,6 @@ export class RecordPanel extends li_BaseView {
*/
returnBtn() {
this.onClose();
ViewManager.I.closeView(this.node);
this.close(); // 使用li_BaseView的close方法
}
}
+3 -38
View File
@@ -53,7 +53,6 @@ export class ThemePanel extends li_BaseView {
cache: ThemeItem[] = [];
// 子页面管理
private listPanel: GirlListPanel = null;
// 加载状态保护
private isLoading: boolean = false;
@@ -209,7 +208,7 @@ export class ThemePanel extends li_BaseView {
for (let id of themeIds) {
let newNode = instantiate(this.itemInst.node);
let item = newNode.getComponent(ThemeItem);
item.refresh(id, this);
item.refresh(id, this.node);
newNode.active = true;
this.cache.push(item);
this.content.addChild(newNode);
@@ -319,7 +318,8 @@ export class ThemePanel extends li_BaseView {
NavigationManager.Instance.switchToPanel(PanelType.GIRL_DETAIL);
} else {
//未解锁
ViewManager.I.openBundlesPopupView("GirlListPopupPanel", {
NavigationManager.Instance.openPopupPanel("GirlListPopupPanel", {
base: this,
category: girlData.getGrilCategoryById(this.recId),
id: this.recId,
@@ -387,43 +387,11 @@ export class ThemePanel extends li_BaseView {
}
}
public getChildPanel(): GirlListPanel {
if (this.listPanel) return this.listPanel;
return null;
}
public setChildPanel(panel: GirlListPanel) {
this.listPanel = panel;
}
/**
* 隐藏子页面
*/
public hideChildPanel(): void {
if (this.listPanel) {
console.log("[ThemePanel] Closing child panel");
if (this.listPanel.hide) {
this.listPanel.hide();
}
}
}
/**
* 关闭子页面
*/
public clostChildPanel(): void {
if (this.listPanel) {
console.log("[ThemePanel] Closing child panel");
if (this.listPanel.close) {
this.listPanel.close();
}
this.listPanel = null;
}
}
/**
* 页面被隐藏时调用
*/
public onHide(): void {
console.log("[ThemePanel] onHide called");
this.hideChildPanel();
this.cancelCurrentOperation();
}
@@ -444,9 +412,6 @@ export class ThemePanel extends li_BaseView {
// 取消当前操作
this.cancelCurrentOperation();
// 关闭子页面
this.clostChildPanel();
// 清理缓存
this.clearCache();
@@ -19,6 +19,7 @@ import { DataManager, DataId } from "../data/DataManager";
import { GirlData } from "../data/GirlData";
import { EnvData } from "../data/EnvData";
import { TipsPanel } from "../ui/panels/TipsPanel";
import { NavigationManager } from "../manager/NavigationManager";
const { ccclass, property } = _decorator;
@@ -68,17 +69,25 @@ export class DetailImageItem extends Component {
},
};
if (this.url) {
ViewManager.I.openBundlesView("ShowPanel", data);
NavigationManager.Instance.openPopupPanel("ShowPanel", data);
this.base.setVideEnable(false);
}
} else {
ViewManager.I.openBundlesView("PopupGirlDetailPanel", {
NavigationManager.Instance.openPopupPanel("PopupGirlDetailPanel", {
category: this.category,
resId: this.resId,
girlId: this.girlId,
base: this,
type: this.type,
});
// ViewManager.I.openBundlesView("PopupGirlDetailPanel", {
// category: this.category,
// resId: this.resId,
// girlId: this.girlId,
// base: this,
// type: this.type,
// });
}
// if (!this.isImage) {
// //判断是否已经解锁此资源
@@ -238,13 +247,13 @@ export class DetailImageItem extends Component {
this.resId = 0;
this.type = null;
this.isVisible = false;
this.question.active = true;
this.video.enabled = false;
this.priceFrame.active = false;
this.loading.active = false;
this.videoPrice.string = "";
if (this.img && this.img.spriteFrame) {
this.img.spriteFrame = null;
}
@@ -216,7 +216,7 @@ export class GirlListItem extends Component {
NavigationManager.Instance.switchToPanel(PanelType.GIRL_DETAIL);
} else {
//未解锁
ViewManager.I.openBundlesPopupView("GirlListPopupPanel", {
NavigationManager.Instance.openPopupPanel("GirlListPopupPanel", {
base: this,
category: this.category,
id: this.id,
@@ -8,6 +8,7 @@ import { InnerMsgCode } from "../../Main/Config/InnerMsgCode";
import { DataManager, DataId } from "../data/DataManager";
import { GirlData } from "../data/GirlData";
import { EnvData } from "../data/EnvData";
import { ChatHistoryManager } from "../manager/ChatHistoryManager";
const { ccclass, property } = _decorator;
@@ -63,11 +64,11 @@ export class PastGirlListItem extends Component {
);
}
refreshData( girlId: number, baseNode: Node) {
refreshData(girlId: number, baseNode: Node) {
if (!this.stars) {
this.stars = this.starParent.getComponentsInChildren(Sprite);
}
this.baseNode = baseNode;
this.id = girlId;
@@ -120,21 +121,13 @@ export class PastGirlListItem extends Component {
this.updateLastChatTime();
}
// TODO: 实现获取最近聊天时间的逻辑
// 需要从聊天记录中获取最后一条消息的时间戳
private getLastChatTime(category: number, id: number): number | null {
// TODO: 实现获取最近聊天时间的逻辑
// 需要实现的逻辑:
// 1. 获取该女孩的聊天记录
// 2. 找到最后一条消息的时间戳
// 3. 返回时间戳或null(如果没有聊天记录)
console.log("TODO: 实现获取最近聊天时间逻辑");
return null;
private getLastChatTime(id: number): number | null {
return ChatHistoryManager.Instance.getLastChatTime(id);
}
// 更新最近聊天时间显示
private updateLastChatTime() {
const lastTime = this.getLastChatTime(this.category, this.id);
const lastTime = this.getLastChatTime(this.id);
this.lastChatTime.string = Utils.formatChatTime(lastTime);
}
@@ -170,12 +163,17 @@ export class PastGirlListItem extends Component {
}
}
onClickDetail() {
// 由于都是已解锁的女孩,直接跳转到角色详情
onClickGirl() {
// 由于都是已解锁的女孩,直接打开聊天面板作为子面板
// 先设置选中的角色ID
NavigationManager.Instance.setSelectedGirlId(this.id);
// 通过switchToPanel切换到角色详情面板,保持动画和状态同步
NavigationManager.Instance.switchToPanel(PanelType.GIRL_DETAIL);
// 通过openSubPanel打开ChatPanel作为子面板
const chatData = { girlId: this.id };
NavigationManager.Instance.openSubPanel(
"ChatPanel",
this.baseNode,
chatData
);
}
}
+5 -5
View File
@@ -34,7 +34,7 @@ export class ThemeItem extends Component {
private isLocked: boolean;
private parentPanel: any;
private baseNode: any;
key: string;
protected onLoad(): void {
@@ -62,9 +62,9 @@ export class ThemeItem extends Component {
}, 0);
}
refresh(themeId: number, parentPanel?: any) {
refresh(themeId: number, parentPanel?: Node) {
if (this.loading) this.loading.active = true;
this.parentPanel = parentPanel;
this.baseNode = parentPanel;
// 主题数据
const themeData = DataManager.I.getDataById<ThemeData>(DataId.Theme);
this.lockImg.node.active = this.lockCover.node.active =
@@ -95,7 +95,7 @@ export class ThemeItem extends Component {
"[ThemeItem] Opening GirlListPanel for category:",
this.category
);
// 不使用过渡动画,直接打开
NavigationManager.Instance.navigateToGirlList(this.category);
NavigationManager.Instance.setSelectedCategoryId(this.category);
NavigationManager.Instance.openSubPanel("GirlListPanel", this.baseNode);
}
}
@@ -1,4 +1,12 @@
import { _decorator, Node, tween, Vec3, UITransform, view } from "cc";
import {
_decorator,
Node,
tween,
Vec3,
UITransform,
view,
UIOpacity,
} from "cc";
const { ccclass, property } = _decorator;
@@ -176,6 +184,322 @@ export class UITransitionHelper {
.start();
}
/**
* 让节点从顶部滑入到屏幕中心
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static slideInFromTop(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for slideInFromTop");
return;
}
const screenHeight = view.getVisibleSize().height;
const targetPosition = new Vec3(0, 0, 0);
// 设置初始位置为屏幕顶部外
node.setPosition(0, screenHeight, 0);
// 执行滑入动画到屏幕中心
tween(node)
.to(
duration,
{ position: targetPosition },
{
easing: "cubicOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 让节点向顶部滑出到屏幕外
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static slideOutToTop(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for slideOutToTop");
return;
}
const screenHeight = view.getVisibleSize().height;
const currentPosition = node.getPosition();
const targetPosition = new Vec3(
currentPosition.x,
screenHeight,
currentPosition.z
);
// 执行滑出动画
tween(node)
.to(
duration,
{ position: targetPosition },
{
easing: "cubicOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 让节点从底部滑入到屏幕中心
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static slideInFromBottom(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for slideInFromBottom");
return;
}
const screenHeight = view.getVisibleSize().height;
const targetPosition = new Vec3(0, 0, 0);
// 设置初始位置为屏幕底部外
node.setPosition(0, -screenHeight, 0);
// 执行滑入动画到屏幕中心
tween(node)
.to(
duration,
{ position: targetPosition },
{
easing: "cubicOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 让节点向底部滑出到屏幕外
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static slideOutToBottom(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for slideOutToBottom");
return;
}
const screenHeight = view.getVisibleSize().height;
const currentPosition = node.getPosition();
const targetPosition = new Vec3(
currentPosition.x,
-screenHeight,
currentPosition.z
);
// 执行滑出动画
tween(node)
.to(
duration,
{ position: targetPosition },
{
easing: "cubicOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 淡入效果
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static fadeIn(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for fadeIn");
return;
}
let opacity = node.getComponent(UIOpacity);
if (!opacity) {
opacity = node.addComponent(UIOpacity);
}
// 设置初始透明度为0
opacity.opacity = 0;
// 执行淡入动画
tween(opacity)
.to(
duration,
{ opacity: 255 },
{
easing: "sineOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 淡出效果
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static fadeOut(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for fadeOut");
return;
}
let opacity = node.getComponent(UIOpacity);
if (!opacity) {
opacity = node.addComponent(UIOpacity);
}
// 执行淡出动画
tween(opacity)
.to(
duration,
{ opacity: 0 },
{
easing: "sineOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 缩放进入效果
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static scaleIn(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for scaleIn");
return;
}
// 设置初始缩放为0
node.setScale(0, 0, 1);
// 执行缩放进入动画
tween(node)
.parallel(
tween().to(
duration,
{ scale: new Vec3(1, 1, 1) },
{ easing: "backOut" }
),
tween().to(duration * 0.8, { opacity: 255 }, { easing: "sineOut" })
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 缩放退出效果
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static scaleOut(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for scaleOut");
return;
}
// 执行缩放退出动画
tween(node)
.parallel(
tween().to(
duration,
{ scale: new Vec3(0, 0, 1) },
{ easing: "backIn" }
),
tween().to(duration * 0.6, { opacity: 0 }, { easing: "sineOut" })
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 停止节点上的所有动画
*