Files
18xchat/assets/Scripts/chat18x/ui/panels/GirlDetailPanel.ts
T

568 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<GirlData>(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<GirlData>(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<GirlData>(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<GirlData>(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<GirlData>(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<GirlData>(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();
}
}