568 lines
16 KiB
TypeScript
568 lines
16 KiB
TypeScript
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();
|
||
}
|
||
}
|