import { _decorator, Label, Node, Sprite, VideoPlayer, UITransform, Size, Vec3, tween, view, screen, Button, } from "cc"; import li_BaseView from "db://assets/Scripts/Main/Common/li_BaseView"; import ResManager from "db://assets/Scripts/Main/Manager/ResManager"; import { DetailImageItem } from "../../uiitems/DetailImageItem"; import { NavigationManager, PageTransitionType, PanelType, } from "../../manager/NavigationManager"; import { DetailImageItemPool } from "../../manager/DetailImageItemPool"; import LanguageUtils from "../../../Main/Common/LanguageUtils"; import Utils from "../../../Main/Common/Utils"; import { InnerMsgCode } from "../../../Main/Config/InnerMsgCode"; import { SimpleToggle } from "../components/SimpleToggle"; import { DataManager, DataId } from "../../data/DataManager"; import { GirlData } from "../../data/GirlData"; import { GirlService } from "db://assets/Scripts/chat18x/network/services/GirlService"; import proto from "db://assets/Scripts/proto/proto.pb.js"; import { SceneBgVideoLayer } from "../../../Sub/UI/SceneBgVideoLayer"; import { logger } from "db://assets/Scripts/Main/Common/Logger"; import { GButton } from "../../../Main/Common/GButton"; const { ccclass, property } = _decorator; @ccclass("GirlDetailPanel") export class GirlDetailPanel extends li_BaseView { @property(UITransform) videoArea: UITransform; // 移除直接的 VideoPlayer 引用,改用 SceneBgVideoLayer 单例 // @property(VideoPlayer) // avatarVideo: VideoPlayer; @property(Label) girlName: Label; /** 获取 SceneBgVideoLayer 单例实例 */ private get videoLayer(): SceneBgVideoLayer | null { return SceneBgVideoLayer.handle; } @property(SimpleToggle) imgToggle: SimpleToggle = null; @property(SimpleToggle) videoToggle: SimpleToggle = null; @property(Node) starParent: Node; @property(Label) tags: Label; @property(Label) descAge: Label; @property(Label) desc: Label; @property(Node) chatBtn: Node; @property(Node) descContent: Node; id = 0; @property(DetailImageItem) imgItemInst: DetailImageItem; @property(Node) imgsLayout: Node; //videoAreaPos: Vec3; @property(Node) sendMsgText: Node; private _nodeTab: any = {}; ImgVidSelect: Node; ImgVidFakePos: Node; AddFavoriteBtn: Node; // 跟随控制相关属性 private isFollowing: boolean = true; // 当前是否处于跟随状态 private lastFakePosPosition: Vec3 = new Vec3(); // 记录上次的位置 // 收藏请求状态标记,防止重复点击 private isRequestingBookmark: boolean = false; protected onEnable(): void { this.refresh(); } onLoadCT() { super.onLoadCT(); Utils.parseNode(this.node, this._nodeTab); this.ImgVidSelect = this._nodeTab.ImgVidSelect; this.ImgVidFakePos = this._nodeTab.ImgVidFakePos; this.AddFavoriteBtn = this._nodeTab.AddFavoriteBtn; // 初始化位置跟随 if (this.ImgVidFakePos) { this.lastFakePosPosition = this.ImgVidFakePos.position.clone(); } DetailImageItemPool.Instance.setTemplate(this.imgItemInst.node); Utils.addInnerEL(InnerMsgCode.LanguageChange, this, () => { this.girlName.string = LanguageUtils.getText(this.nameKey); const tags = LanguageUtils.getText(this.tagKey); this.tags.string = tags; this.desc.string = LanguageUtils.getText(this.descKey); }); this.imgToggle.callback = this.bindImgToggle.bind(this); this.videoToggle.callback = this.bindVideoToggle.bind(this); GButton.BandClick(this.AddFavoriteBtn, this.OnClickAddFavoriteBtn, this); } setVideEnable(enable: boolean) { if (!this.videoLayer) { logger.error("GirlDetailPanel: SceneBgVideoLayer 不可用"); return; } if (enable) { // 恢复播放 this.videoLayer.resume(); logger.log("GirlDetailPanel: 开启视频播放"); } else { // 暂停播放 this.videoLayer.pause(); logger.log("GirlDetailPanel: 暂停视频播放"); } } /** * 获取目标视频尺寸 * 从 videoArea 获取实际需要填充的区域尺寸 */ private getTargetVideoSize(): Size | null { // 从 videoArea 获取目标尺寸 if (this.videoArea) { return new Size(this.videoArea.width, this.videoArea.height); } // 如果没有 videoArea,使用默认尺寸 logger.warn("GirlDetailPanel: videoArea 未设置,使用默认尺寸"); return new Size(600, 800); } nameKey: string; tagKey: string; descKey: string; category: number; async refresh() { this.id = NavigationManager.Instance.getSelectedGirlId(); this.category = NavigationManager.Instance.getSelectedCategoryId(); this.imgItemInst.node.active = false; //this.videoAreaPos = new Vec3(540, 1384 - 60); const pos = new Vec3( this.descContent.position.x, this.descContent.position.y, this.descContent.position.z ); this.descContent.position = new Vec3(pos.x, pos.y - 1000, pos.z); //this.node.scale = Vec3.ZERO; tween(this.descContent) .to(0.3, { position: pos }, { easing: "quadOut" }) .start(); // 检查 SceneBgVideoLayer 是否可用 if (!this.videoLayer) { logger.error("GirlDetailPanel: SceneBgVideoLayer.handle 未初始化"); } else { logger.log("GirlDetailPanel: 成功获取 SceneBgVideoLayer 实例"); // 初始化视频显示位置 //this.initVideoPosition(); } const girlData = DataManager.I.getDataById(DataId.Girl); this.sendMsgText.active = true; // 请求详细数据 const reqData = { id: this.id, }; let res = await GirlService.I.reqGetGirlDetail(reqData); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { // 保存数据 girlData.setGirlDetailData(this.category.toString(), res.data); // 根据数据,刷新界面 this.nameKey = girlData.getGrilName(this.category.toString(), this.id); this.girlName.string = LanguageUtils.getText(this.nameKey); this.descKey = girlData.getGrilDesc(this.category.toString(), this.id); this.desc.string = LanguageUtils.getText(this.descKey); this.tagKey = girlData.getGrilTagKey(this.category.toString(), this.id); this.tags.string = LanguageUtils.getText(this.tagKey); const star = girlData.getStar(this.category.toString(), this.id); const stars = this.starParent.getComponentsInChildren(Sprite); logger.log("好感度:" + star); for (let i = 0; i < stars.length; i++) { const element = stars[i]; if (star > i) { ResManager.I.changeResourceSpriteFrame( element, "ui/common/heart/heart" ); } else { ResManager.I.changeResourceSpriteFrame( element, "ui/common/heart/heart_empty" ); } } let age = girlData.getGrilAge(this.category.toString(), this.id); this.descAge.string = age.toString(); const firstPath = girlData.getFirstGrilVideoPath( this.category.toString(), this.id ); // 使用 SceneBgVideoLayer 加载并播放视频 if (this.videoLayer && firstPath) { logger.log(`GirlDetailPanel: 加载视频 ${firstPath}`); // 获取 videoArea 的尺寸和位置 const targetSize = this.videoArea ? new Size(this.videoArea.width, this.videoArea.height) : new Size(600, 800); // 获取 videoArea 的中心位置,确保使用正确的世界坐标 // 使用新的统一 playVideo 方法 this.videoLayer.playVideo(firstPath, { loop: true, size: targetSize, position: new Vec3(0, 67.71, 0), }); } let resIds = girlData.getAllDisplayGrilPhotoId( this.category.toString(), this.id ); this.refreshImgs( proto.cs.EnmResType.ERT_Image, this.category, this.id, resIds ); // 刷新收藏按钮状态 this.refreshAddFavoriteBtn(); } } /** * 刷新收藏按钮状态 */ refreshAddFavoriteBtn() { if (!this.AddFavoriteBtn) { return; } const girlData = DataManager.I.getDataById(DataId.Girl); const isBookmarked = girlData.isBookmarkGirl( this.category.toString(), this.id ); // 查找收藏按钮的状态节点(通常命名为 "selected" 或 "checked") // 如果有这样的子节点,根据收藏状态显示/隐藏 const selectedNode = this.AddFavoriteBtn.getChildByName("selected"); if (selectedNode) { selectedNode.active = isBookmarked; } // 如果有未选中状态的节点 const unselectedNode = this.AddFavoriteBtn.getChildByName("unselected"); if (unselectedNode) { unselectedNode.active = !isBookmarked; } logger.log( `[GirlDetailPanel] 收藏按钮状态已刷新: girlId=${this.id}, isBookmarked=${isBookmarked}` ); } bindImgToggle() { const girlData = DataManager.I.getDataById(DataId.Girl); let resIds = girlData.getAllDisplayGrilPhotoId( this.category.toString(), this.id ); this.refreshImgs( proto.cs.EnmResType.ERT_Image, this.category, this.id, resIds ); } bindVideoToggle() { const girlData = DataManager.I.getDataById(DataId.Girl); let resIds = girlData.getAllDisplayGrilVideoId( this.category.toString(), this.id ); this.refreshImgs( proto.cs.EnmResType.ERT_Video, this.category, this.id, resIds ); } refreshImgs( type: proto.cs.EnmResType, category: number, id: number, resIds: number[] ) { for (let i = this.imgsLayout.children.length - 1; i >= 0; i--) { DetailImageItemPool.Instance.put(this.imgsLayout.children[i]); } let count = resIds.length; for (let i = 0; i < count; i++) { let newNode = DetailImageItemPool.Instance.get(); if (newNode) { let item = newNode.getComponent(DetailImageItem); let resId = resIds[i]; item.refresh(this, type, category, id, resId); newNode.active = true; this.imgsLayout.addChild(newNode); } } } OnClickChatBtn() { const girlData = DataManager.I.getDataById(DataId.Girl); //if (isRelease || isFree) { // 直接在当前页面打开ChatPanel作为子页面 const currentPanel = NavigationManager.Instance.getCurrentActivePanelNode(); if (currentPanel) { const chatData = { girlId: this.id, base: currentPanel }; NavigationManager.Instance.openSubPanel( "ChatPanel", currentPanel, chatData, { openAnimation: PageTransitionType.NONE, closeAnimation: PageTransitionType.NONE, hideBasePanel: true, } ); } else { logger.warn("[GirlDetailPanel] 无法获取当前激活的主页面"); } // } else { // //未解锁 // NavigationManager.Instance.openPopupPanel("GirlListPopupPanel", { // base: this, // category: this.category, // id: this.id, // }); // } } /** * 点击收藏按钮 */ async OnClickAddFavoriteBtn() { // 防止重复点击 if (this.isRequestingBookmark) { logger.warn("[GirlDetailPanel] 收藏请求进行中,请稍候"); return; } const girlData = DataManager.I.getDataById(DataId.Girl); const isBookmarked = girlData.isBookmarkGirl( this.category.toString(), this.id ); logger.log( `[GirlDetailPanel] 点击收藏按钮: girlId=${this.id}, 当前状态=${ isBookmarked ? "已收藏" : "未收藏" }` ); try { this.isRequestingBookmark = true; // 调用收藏接口 const reqData = { girlId: this.id, cancel: isBookmarked, // true=取消收藏, false=添加收藏 }; const res = await GirlService.I.reqBookmarkGirl(reqData); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { // 更新本地收藏时间 if (isBookmarked) { // 取消收藏:设置为0 girlData.setBookmarkTime(this.category.toString(), this.id, 0); logger.log(`[GirlDetailPanel] 取消收藏成功: girlId=${this.id}`); } else { // 添加收藏:设置为当前时间戳(秒) const currentTimestamp = Math.floor(Date.now() / 1000); girlData.setBookmarkTime( this.category.toString(), this.id, currentTimestamp ); logger.log( `[GirlDetailPanel] 添加收藏成功: girlId=${this.id}, timestamp=${currentTimestamp}` ); } // 刷新按钮状态 this.refreshAddFavoriteBtn(); // TODO: 可以在这里添加UI提示,比如显示Toast // Utils.showToast(isBookmarked ? "已取消收藏" : "收藏成功"); } else { logger.warn( `[GirlDetailPanel] 收藏操作失败: girlId=${this.id}, code=${res?.code}` ); // TODO: 显示错误提示 // Utils.showToast("操作失败,请稍后重试"); } } catch (error) { logger.error(`[GirlDetailPanel] 收藏操作异常: girlId=${this.id}`, error); // TODO: 显示错误提示 // Utils.showToast("网络错误,请稍后重试"); } finally { this.isRequestingBookmark = false; } } returnBtn() { // GirlDetailPanel现在是subpanel,关闭自己即可 NavigationManager.Instance.closeSubPanel(this); } /** * 面板关闭时的清理工作 */ onClose() { // 停止视频播放,释放资源 if (this.videoLayer) { this.videoLayer.stop(); logger.log("GirlDetailPanel: 停止视频播放"); } super.onClose(); } /** * 检测节点是否在屏幕可见区域内 * @param node 要检测的节点 * @returns 是否在屏幕内 */ private isNodeInScreen(node: Node): boolean { if (!node) return false; const uiTransform = node.getComponent(UITransform); if (!uiTransform) return false; // 获取屏幕尺寸 const screenSize = screen.windowSize; const designResolution = view.getDesignResolutionSize(); // 获取节点的世界坐标 const worldPos = uiTransform.convertToWorldSpaceAR(Vec3.ZERO); // 获取节点的尺寸 const nodeSize = uiTransform.contentSize; // 计算节点的边界 const left = worldPos.x - nodeSize.width * uiTransform.anchorX; const right = worldPos.x + nodeSize.width * (1 - uiTransform.anchorX); const bottom = worldPos.y - nodeSize.height * uiTransform.anchorY; const top = worldPos.y + nodeSize.height * (1 - uiTransform.anchorY); // 检测是否完全在屏幕内 const isInScreen = left >= 0 && right <= designResolution.width && bottom >= 0 && top <= designResolution.height - 160; return isInScreen; } /** * 实时更新ImgVidSelect跟随ImgVidFakePos的逻辑 */ lateUpdate(dt: number) { if (!this.ImgVidSelect || !this.ImgVidFakePos) { return; } // 检测ImgVidFakePos是否在屏幕内 const isInScreen = this.isNodeInScreen(this.ImgVidFakePos); if (isInScreen) { // 在屏幕内,开始或继续跟随 if (!this.isFollowing) { this.isFollowing = true; logger.log("ImgVidSelect开始跟随ImgVidFakePos"); } // 更新ImgVidSelect的位置到ImgVidFakePos的位置 this.ImgVidSelect.worldPosition = this.ImgVidFakePos.worldPosition.clone(); this.lastFakePosPosition = this.ImgVidFakePos.worldPosition.clone(); } else { // 超出屏幕,停止跟随 if (this.isFollowing) { this.isFollowing = false; logger.log("ImgVidSelect停止跟随ImgVidFakePos(超出屏幕)"); // ImgVidSelect保持在最后的跟随位置 } } } /** * 面板销毁时的清理工作 */ onDestroy() { // 确保视频资源被释放 if (this.videoLayer) { this.videoLayer.stop(); logger.log("GirlDetailPanel: 销毁时停止视频播放"); } // 回收所有使用中的节点到对象池 // for (let i = this.imgsLayout.children.length - 1; i >= 0; i--) { // DetailImageItemPool.Instance.put(this.imgsLayout.children[i]); // } super.onDestroy(); } }