rp_10012/assets/Game/SlotRanking/scripts/RankList.ts
TJH 354766ccb9
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 47s
龙虎榜相关
2025-12-24 16:55:31 +08:00

492 lines
20 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, Button, Color, Component, Label, Node, Sprite, Tween, tween, UITransform, v3 } from 'cc';
import { VirtualScrollView } from './VScrollView';
import { Palette } from './Palette';
import { SlotRankingDataManager } from './SlotRankingDataManager';
import { callGameApiForRank, getGameId, truncateString } from 'db://assets/Loading/scripts/comm';
import { I18nManager } from 'db://assets/Loading/scripts/manager/I18nManager';
import { AudioManager } from 'db://assets/Loading/scripts/manager/AudioManager';
const { ccclass, property } = _decorator;
@ccclass('RankList')
export class RankList extends Component {
// ==================== 当前排行榜 ====================
msg_1: Node = null // 跑马灯信息
currentMarqueeIndex: number = 0; // 当前跑马灯文本索引
marqueeTexts: string[] = []; // 跑马灯文本数组
rankList: Node = null; // 当前排行榜节点
rankingEndTime: Node = null; // 排行榜结束时间节点
rankLoadingNode: Node = null; // 加载动画节点
rankingListVScroll: VirtualScrollView = null; // 排行榜虚拟列表
selfInfo: Node = null; // 自己的信息节点
// 当前排行榜 - 日周月切换
rankRadioDWM: Node = null; // 日周月单选按钮父节点
rankRadioDayBtn: Node = null; // 日榜单选按钮
rankRadioWeekBtn: Node = null; // 周榜单选按钮
rankRadioMonthBtn: Node = null; // 月榜单选按钮
rankingHistoryBtn: Node = null; // 历史记录按钮
rankingRewardBtn: Node = null; // 奖励按钮
// 活动未开启提示
activityNotOpenTip: Node = null; // 活动未开启提示 label
currentRankType: string = '';
countdownTimer: any = null; // 存储定时器
availableTypes: Set<string> = new Set(); // 可用的类型
// 回调函数
onShowHistory: (type: string) => void = null; // 跳转到历史记录
onShowReward: (type: string) => void = null; // 跳转到奖励列表
onClose: () => void = null; // 关闭主弹窗
init(rankListNode: Node) {
this.rankList = rankListNode;
this.msg_1 = this.rankList.getChildByName('msg').getChildByName('Mask').getChildByName('msg_1');
this.rankLoadingNode = this.rankList.getChildByName('list').getChildByName('loading');
this.rankingEndTime = this.rankList.getChildByName('endTime');
this.rankingListVScroll = this.rankList.getChildByName('list').getChildByName('vScroll').getComponent(VirtualScrollView);
this.selfInfo = this.rankList.getChildByName('list').getChildByName('self');
this.rankRadioDWM = this.rankList.getChildByName('rankRadioDWM');
this.rankRadioDayBtn = this.rankRadioDWM.getChildByName('dayBtn');
this.rankRadioWeekBtn = this.rankRadioDWM.getChildByName('weekBtn');
this.rankRadioMonthBtn = this.rankRadioDWM.getChildByName('monthBtn');
this.rankingHistoryBtn = this.rankList.getChildByName('history');
let radioTitle = this.rankList.getChildByName('radioTitle');
this.rankingRewardBtn = radioTitle.getChildByName('rewardBtn');
this.activityNotOpenTip = this.rankList.getChildByName('list').getChildByName('activityNotOpenTip');
this.rankList.active = false;
this.rankLoadingNode.active = false;
this.activityNotOpenTip.active = false;
this.rankingHistoryBtn.on(Node.EventType.TOUCH_END, this.onClickRankHistoryBtn, this);
this.rankingRewardBtn.on(Node.EventType.TOUCH_END, this.onClickRewardBtn, this);
this.rankRadioDayBtn.on(Node.EventType.TOUCH_END, this.onClickRankRadioDayBtn, this);
this.rankRadioWeekBtn.on(Node.EventType.TOUCH_END, this.onClickRankRadioWeekBtn, this);
this.rankRadioMonthBtn.on(Node.EventType.TOUCH_END, this.onClickRankRadioMonthBtn, this);
}
// ==================== 对外接口 ====================
async show(defaultType: string = 'day') {
this.rankList.active = true;
this.currentRankType = '';
this.updateRankButtonsAvailability();
await this.switchRankTab(defaultType);
}
hide() {
this.rankList.active = false;
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
this.countdownTimer = null;
}
// 停止跑马灯
if (this.msg_1 && this.msg_1.isValid) {
Tween.stopAllByTarget(this.msg_1);
this.msg_1.active = false;
}
this.marqueeTexts = [];
this.currentMarqueeIndex = 0;
}
getCurrentType(): string {
return this.currentRankType;
}
// ==================== 排行榜逻辑 ====================
isTypeAvailable(type: string): boolean {
return this.availableTypes.has(type);
}
showActivityNotOpenTip() {
this.activityNotOpenTip.active = true;
this.rankingListVScroll.setTotalCount(0);
this.rankingEndTime.getComponent(Label).string = "--:--:--";
}
hideActivityNotOpenTip() {
this.activityNotOpenTip.active = false;
}
async switchRankTab(type: string) {
if (this.currentRankType === type) {
return;
}
this.currentRankType = type;
this.setRankRadioBtn(type);
if (!this.isTypeAvailable(type)) {
console.log(`${type} 榜单活动未开启`);
this.rankLoadingNode.active = false;
if (this.msg_1 && this.msg_1.isValid) {
this.msg_1.active = false;
}
this.marqueeTexts = [];
this.currentMarqueeIndex = 0;
this.refreshSelfRankingInfo(null);
this.showActivityNotOpenTip();
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
this.countdownTimer = null;
}
return;
}
this.hideActivityNotOpenTip();
this.rankingListVScroll.setTotalCount(0);
Tween.stopAllByTarget(this.rankLoadingNode);
this.rankLoadingNode.active = true;
tween(this.rankLoadingNode)
.by(0.3, { angle: 360 })
.repeatForever()
.start();
let currentRankInfo = SlotRankingDataManager.instance.getRankInfoByType(type);
if (this.msg_1 && this.msg_1.isValid) {
Tween.stopAllByTarget(this.msg_1);
}
this.setupMarquee(currentRankInfo);
this.setRankingEndTime(type);
let data = {
Type: type,
GameId: getGameId(),
Date: this.getCurrentDateString(),
}
try {
let rankInfos = await callGameApiForRank('getRankInfos', data);
Tween.stopAllByTarget(this.rankLoadingNode);
this.rankLoadingNode.active = false;
this.openRankingPopup(rankInfos);
} catch (error) {
console.error('获取排行榜数据失败:', error);
this.showActivityNotOpenTip();
Tween.stopAllByTarget(this.rankLoadingNode);
this.rankLoadingNode.active = false;
}
}
// ==================== 跑马灯逻辑 ====================
setupMarquee(currentRankInfo: any) {
if (!this.msg_1 || !this.msg_1.isValid) return;
// 获取活动数据
let rewardsCount = currentRankInfo.Rewards?.length || 0;
let spinLimit = currentRankInfo.SpinLimit || 0;
let betLimit = currentRankInfo.BetLimit || 0;
// 获取翻译文本并替换XX
let text1 = I18nManager.instance.t('During the event, participate in designated games and rank in the top XX by winnings to receive generous rewards!');
let text2 = I18nManager.instance.t('Accumulate XX bets or a total wager of XX to participate in the event');
// 替换第一段文本中的XX为Rewards的length
text1 = text1.replace('XX', rewardsCount.toString());
// 替换第二段文本中的两个XXSpinLimit和BetLimit
text2 = text2.replace(/XX/, spinLimit.toString()).replace(/XX/, (betLimit / 10000).toString());
// 保存两段文本
this.marqueeTexts = [text1, text2];
this.currentMarqueeIndex = 0;
// 显示跑马灯并播放第一段文本
this.msg_1.active = true;
this.playMarquee();
}
playMarquee() {
if (!this.msg_1 || !this.msg_1.isValid || this.marqueeTexts.length === 0) return;
let labelComp = this.msg_1.getComponent(Label);
if (!labelComp) return;
// 获取当前要显示的文本
let currentText = this.marqueeTexts[this.currentMarqueeIndex];
// 确保Label的overflow设置为NONE以便完整显示文本
labelComp.overflow = Label.Overflow.NONE;
labelComp.string = currentText;
// 强制更新Label的渲染数据确保宽度正确计算
labelComp.updateRenderData(true);
// 等待下一帧更新UITransform
this.scheduleOnce(() => {
if (!this.msg_1 || !this.msg_1.isValid) return;
// 再次强制更新以确保宽度准确
labelComp.updateRenderData(true);
let labelLength = labelComp.node.getComponent(UITransform).width;
// 设置初始位置
this.msg_1.setPosition(-450, this.msg_1.position.y, this.msg_1.position.z);
let endX = -labelLength;
let duration = Math.max(3, (-450 + labelLength) / 80);
// 启动跑马灯动画
Tween.stopAllByTarget(this.msg_1);
tween(this.msg_1)
.delay(1)
.to(duration, { position: v3(endX, this.msg_1.position.y, this.msg_1.position.z) })
.call(() => {
// 切换到下一段文本
this.currentMarqueeIndex = (this.currentMarqueeIndex + 1) % this.marqueeTexts.length;
// 播放下一段文本
this.playMarquee();
})
.start();
}, 0);
}
// ==================== 排行榜列表逻辑 ====================
openRankingPopup(rankInfos: any) {
this.rankingListVScroll.setTotalCount(rankInfos.List.length);
this.rankingListVScroll.renderItemFn = (node: Node, idx: number) => {
let itemData = rankInfos.List[idx];
node.getChildByName('bg').active = idx % 2 === 0;
node.getChildByName('bg2').active = idx % 2 !== 0;
node.getChildByName('Rank').getChildByName('sp_1').active = idx === 0;
node.getChildByName('Rank').getChildByName('sp_2').active = idx === 1;
node.getChildByName('Rank').getChildByName('sp_3').active = idx === 2;
node.getChildByName('Rank').getChildByName('rankLab').active = idx >= 3;
node.getChildByName('Rank').getChildByName('rankLab').getComponent(Label).string = itemData.Rank.toString();
node.getChildByName('Uid').getComponent(Label).string = truncateString(itemData.Uid);
node.getChildByName('Shop').getComponent(Label).string = SlotRankingDataManager.instance.getRankListServerId() || '-';
node.getChildByName('Win').getComponent(Label).string = itemData.Win.toString();
}
this.rankingListVScroll.refreshList(rankInfos.List);
this.refreshSelfRankingInfo(rankInfos.Self);
}
refreshSelfRankingInfo(selfInfo: any) {
if (!selfInfo) {
this.selfInfo.getChildByName('Rank').getChildByName('sp_1').active = false;
this.selfInfo.getChildByName('Rank').getChildByName('sp_2').active = false;
this.selfInfo.getChildByName('Rank').getChildByName('sp_3').active = false;
this.selfInfo.getChildByName('Rank').getChildByName('rankLab').active = false;
this.selfInfo.getChildByName('Rank').getChildByName('rankLab').getComponent(Label).string = '-';
this.selfInfo.getChildByName('Uid').getComponent(Label).string = '-';
this.selfInfo.getChildByName('Shop').getComponent(Label).string = '-';
this.selfInfo.getChildByName('Win').getComponent(Label).string = '-';
return;
}
this.selfInfo.getChildByName('Rank').getChildByName('sp_1').active = selfInfo.Rank === 1;
this.selfInfo.getChildByName('Rank').getChildByName('sp_2').active = selfInfo.Rank === 2;
this.selfInfo.getChildByName('Rank').getChildByName('sp_3').active = selfInfo.Rank === 3;
this.selfInfo.getChildByName('Rank').getChildByName('rankLab').active = selfInfo.Rank >= 4;
this.selfInfo.getChildByName('Rank').getChildByName('rankLab').getComponent(Label).string = selfInfo.Rank.toString();
if (selfInfo.Rank === -1) {
this.selfInfo.getChildByName('Rank').getChildByName('rankLab').active = true;
this.selfInfo.getChildByName('Rank').getChildByName('rankLab').getComponent(Label).string = '-';
}
this.selfInfo.getChildByName('Uid').getComponent(Label).string = truncateString(selfInfo.Uid);
this.selfInfo.getChildByName('Shop').getComponent(Label).string = SlotRankingDataManager.instance.getRankListServerId() || '-';
this.selfInfo.getChildByName('Win').getComponent(Label).string = selfInfo.Win.toString();
}
// ==================== 按钮状态管理 ====================
updateRankButtonsAvailability() {
let rankList = SlotRankingDataManager.instance.rankList;
this.availableTypes.clear();
if (rankList && rankList.List) {
for (let item of rankList.List) {
this.availableTypes.add(item.Type);
}
}
this.updateButtonAvailability(this.rankRadioDayBtn);
this.updateButtonAvailability(this.rankRadioWeekBtn);
this.updateButtonAvailability(this.rankRadioMonthBtn);
}
updateButtonAvailability(btn: Node) {
btn.active = true;
let button = btn.getComponent(Button);
if (button) {
button.interactable = true;
}
let check = btn.getChildByName('check');
let unCheck = btn.getChildByName('unCheck');
let whiteColor = Color.WHITE;
if (check) {
let checkSprite = check.getComponent(Sprite);
if (checkSprite) {
checkSprite.color = whiteColor;
}
}
if (unCheck) {
let unCheckSprite = unCheck.getComponent(Sprite);
if (unCheckSprite) {
unCheckSprite.color = whiteColor;
}
}
}
setRankRadioBtn(type: string) {
let selectedColor = new Color(219, 180, 180, 255);
let unselectedColor = new Color(197, 255, 175, 255);
this.setRadioBtnState(this.rankRadioDayBtn, type === 'day', selectedColor, unselectedColor);
this.setRadioBtnState(this.rankRadioWeekBtn, type === 'week', selectedColor, unselectedColor);
this.setRadioBtnState(this.rankRadioMonthBtn, type === 'month', selectedColor, unselectedColor);
}
setRadioBtnState(btn: Node, isSelected: boolean, selectedColor: Color, unselectedColor: Color) {
let check = btn.getChildByName('check');
let unCheck = btn.getChildByName('unCheck');
let label = btn.getChildByName('label');
let palette = label?.getComponent(Palette);
if (isSelected) {
check.active = true;
unCheck.active = false;
if (palette) {
palette.colorLB = selectedColor;
palette.colorRB = selectedColor;
}
} else {
check.active = false;
unCheck.active = true;
if (palette) {
palette.colorLB = unselectedColor;
palette.colorRB = unselectedColor;
}
}
}
// ==================== 倒计时功能 ====================
setRankingEndTime(type: string) {
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
this.countdownTimer = null;
}
let endTime = SlotRankingDataManager.instance.getRankEndTimeByType(type);
if (!endTime) {
this.rankingEndTime.getComponent(Label).string = "--:--:--";
return;
}
this.updateCountdown(endTime);
this.countdownTimer = setInterval(() => {
this.updateCountdown(endTime);
}, 1000);
}
// 1. 客户端可定时通过HTTP请求轮询服务器定期拉取活动状态或结束时间如每分钟或每次切换排行榜时如果发现状态变为停止则立即处理。
// 2. 在每次需要用到活动信息前(如展示排行榜、下注等),都重新请求活动状态信息,避免缓存已过期的信息。
// 3. 可以在服务器接口响应中增加“活动状态”字段每次请求都返回当前的活动状态如active、stopped客户端据此判定。
// 4. 如有WebSocket等实时通道可让服务器主动推送“活动已结束”通知客户端收到后立刻更新活动状态并做相应提示。
// 5. 若客户端检测到接口数据异常(如活动相关接口返回错误代码、空列表或特定标记等),也可判定活动可能已停止并刷新状态。
updateCountdown(endTime: number) {
let now = Math.floor(Date.now() / 1000);
let remainingSeconds = endTime - now;
if (remainingSeconds <= 0) {
this.rankingEndTime.getComponent(Label).string = "--:--:--";
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
this.countdownTimer = null;
}
return;
}
let timeStr = this.formatCountdown(remainingSeconds);
this.rankingEndTime.getComponent(Label).string = I18nManager.instance.t('Countdown to end') + ' : ' + timeStr;
}
formatCountdown(seconds: number): string {
let days = Math.floor(seconds / 86400);
let hours = Math.floor((seconds % 86400) / 3600);
let minutes = Math.floor((seconds % 3600) / 60);
let secs = seconds % 60;
if (days >= 1) {
return `${days}d ${hours}h ${minutes}min ${secs}s`;
} else {
return `${hours}h ${minutes}min ${secs}s`;
}
}
// ==================== 按钮事件 ====================
onClickRankRadioDayBtn() {
AudioManager.instance.playSFX("Common_Button_Click");
this.switchRankTab('day');
}
onClickRankRadioWeekBtn() {
AudioManager.instance.playSFX("Common_Button_Click");
this.switchRankTab('week');
}
onClickRankRadioMonthBtn() {
AudioManager.instance.playSFX("Common_Button_Click");
this.switchRankTab('month');
}
onClickRankHistoryBtn() {
AudioManager.instance.playSFX("Common_Button_Click");
this.onShowHistory?.call(null, this.currentRankType || 'day');
}
onClickRewardBtn() {
AudioManager.instance.playSFX("Common_Button_Click");
this.onShowReward?.call(null, this.currentRankType || 'day');
}
closeRankingPopup() {
this.onClose?.call(null);
}
// ==================== 工具方法 ====================
getCurrentDateString(): string {
let now = new Date();
let year = now.getFullYear();
let month = (now.getMonth() + 1).toString();
let day = now.getDate().toString();
if (month.length < 2) month = '0' + month;
if (day.length < 2) day = '0' + day;
return `${year}${month}${day}`;
}
onDestroy() {
clearInterval(this.countdownTimer);
this.countdownTimer = null;
this.rankingHistoryBtn.off(Node.EventType.TOUCH_END, this.onClickRankHistoryBtn, this);
this.rankingRewardBtn.off(Node.EventType.TOUCH_END, this.onClickRewardBtn, this);
this.rankRadioDayBtn.off(Node.EventType.TOUCH_END, this.onClickRankRadioDayBtn, this);
this.rankRadioWeekBtn.off(Node.EventType.TOUCH_END, this.onClickRankRadioWeekBtn, this);
this.rankRadioMonthBtn.off(Node.EventType.TOUCH_END, this.onClickRankRadioMonthBtn, this);
}
}