baxi 1 неделя назад
Родитель
Сommit
9e78155ce0

+ 3 - 1
.gitignore

@@ -23,4 +23,6 @@ node_modules/
 #//////////////////////////
 #//////////////////////////
 .idea/
 .idea/
 
 
-.gradle
+.gradle
+
+.creator

+ 2 - 3
assets/lobbyRes/ui/home/HomeView.prefab

@@ -16887,9 +16887,8 @@
     "tabGamesScroll": {
     "tabGamesScroll": {
       "__id__": 269
       "__id__": 269
     },
     },
-    "gamePrefab": {
-      "__uuid__": "5afdeb54-c5fc-4e74-b2ee-8e4ce35f78d7",
-      "__expectedType__": "cc.Prefab"
+    "homeNotices": {
+      "__id__": 443
     },
     },
     "_id": ""
     "_id": ""
   },
   },

+ 6 - 1
assets/scripts/api/LobbyApi.ts

@@ -4,6 +4,7 @@ export const APP_ROUTES = get('routes');
 export const APP_PAGE_MODULE = API_BASE_PREFIX + 'pageModules/';
 export const APP_PAGE_MODULE = API_BASE_PREFIX + 'pageModules/';
 export const APP_GAME_LIST = get('sc/game/list');
 export const APP_GAME_LIST = get('sc/game/list');
 export const APP_GAME_LAUNCH = get('sc/game/launch');
 export const APP_GAME_LAUNCH = get('sc/game/launch');
+export const APP_GAME_HISTORY = get('sc/game/history');
 
 
 export const APP_GAME_CARD_INFO = get('gameinfo');
 export const APP_GAME_CARD_INFO = get('gameinfo');
 export const APP_GAME_ENTER_RECORD = get('game_enter/record');
 export const APP_GAME_ENTER_RECORD = get('game_enter/record');
@@ -13,7 +14,11 @@ export const APP_FAVO_REMOVE = get('favo/remove');
 export const APP_FAVO_GET = get('favo/get');
 export const APP_FAVO_GET = get('favo/get');
 
 
 export const APP_VIP_CONFIG = get("sc/vip/info");
 export const APP_VIP_CONFIG = get("sc/vip/info");
-export const APP_VIP_CLAIM =  get("sc/vip/claim");
+export const APP_VIP_CLAIM = get("sc/vip/claim");
+
+export const APP_BIG_WIN = get('lobby/big-win-broadcast');
+
+
 
 
 export const APP_SERVICE = get('service');
 export const APP_SERVICE = get('service');
 export const APP_USER_MESSAGE = get('user/message');
 export const APP_USER_MESSAGE = get('user/message');

+ 46 - 10
assets/scripts/modules/lobby/ui/HomeView/HomeNotices.ts

@@ -1,12 +1,13 @@
 import { AppKernel } from '@scripts/app/AppKernel';
 import { AppKernel } from '@scripts/app/AppKernel';
 import { CURRENCY_TYPE } from '@scripts/define/Define';
 import { CURRENCY_TYPE } from '@scripts/define/Define';
-import { INoticeData } from '@scripts/store/mainStore/data';
+import { IBigWinItem } from '@scripts/store/mainStore/data';
 import { formatMaskNickName, formatSCNum } from '@scripts/utils/Helper';
 import { formatMaskNickName, formatSCNum } from '@scripts/utils/Helper';
-import { _decorator, Component, Input, input, instantiate, KeyCode, Label, Node, NodePool, tween, Tween, UITransform, Vec3 } from 'cc';
+import { _decorator, Component, Input, input, instantiate, isValid, KeyCode, Label, Node, NodePool, tween, Tween, UITransform, Vec3 } from 'cc';
 const { ccclass, property } = _decorator;
 const { ccclass, property } = _decorator;
 
 
 const MIN_MESSAGE_GAP = 50;
 const MIN_MESSAGE_GAP = 50;
 const SCROLL_SPEED = 120;
 const SCROLL_SPEED = 120;
+const NEXT_NOTICE_DELAY = 2;
 
 
 interface INoticeNode {
 interface INoticeNode {
     label: Label,
     label: Label,
@@ -24,6 +25,13 @@ export class HomeNotices extends Component {
     private readonly pool = new NodePool('HomeNotices');
     private readonly pool = new NodePool('HomeNotices');
     private readonly activeItems: Node[] = [];
     private readonly activeItems: Node[] = [];
     private readonly noticeNodes = new WeakMap<Node, INoticeNode>();
     private readonly noticeNodes = new WeakMap<Node, INoticeNode>();
+    private timerID: number = 0;
+    private waitingForNext = false;
+
+    private readonly playNextNotice = () => {
+        this.waitingForNext = false;
+        this.startNotify();
+    };
 
 
     protected onLoad(): void {
     protected onLoad(): void {
         if (this.itemNode) this.itemNode.active = false;
         if (this.itemNode) this.itemNode.active = false;
@@ -42,23 +50,38 @@ export class HomeNotices extends Component {
     }
     }
 
 
     protected onDestroy(): void {
     protected onDestroy(): void {
+        this.unschedule(this.playNextNotice);
         for (let index = 0; index < this.activeItems.length; index += 1) {
         for (let index = 0; index < this.activeItems.length; index += 1) {
             Tween.stopAllByTarget(this.activeItems[index]);
             Tween.stopAllByTarget(this.activeItems[index]);
         }
         }
         this.activeItems.length = 0;
         this.activeItems.length = 0;
+        this.stopTimer();
+    }
+
+    private stopTimer() {
+        if (this.timerID === 0) return;
+        clearTimeout(this.timerID);
+        this.timerID = 0;
     }
     }
 
 
     startNotify(): void {
     startNotify(): void {
-        if (this.activeItems.length > 0) {
-            const last = this.activeItems[this.activeItems.length - 1];
-            if (last.x >= this.content.width) return;
-        }
+        // 外部刷新或旧消息播放结束不能跳过下一条消息的等待时间。
+        if (this.waitingForNext) return;
+        this.stopTimer();
 
 
         const mainStore = AppKernel.stores.mainStore;
         const mainStore = AppKernel.stores.mainStore;
         const data = mainStore.getNoticeData();
         const data = mainStore.getNoticeData();
         if (!data) {
         if (!data) {
             // 队列已空且没有正在移动的消息时,隐藏整个公告区域。
             // 队列已空且没有正在移动的消息时,隐藏整个公告区域。
-            if (this.activeItems.length === 0) this.node.active = false;
+            if (this.activeItems.length === 0) {
+                this.node.active = false;
+            }
+
+            this.timerID = setTimeout(async () => {
+                this.timerID = 0;
+                await AppKernel.stores.mainStore.reqBigWinNotices()
+                if (isValid(this.node)) this.startNotify();
+            }, 5000);
             return;
             return;
         }
         }
 
 
@@ -66,10 +89,10 @@ export class HomeNotices extends Component {
         this.onNotify(data);
         this.onNotify(data);
     }
     }
 
 
-    onNotify(data: INoticeData): void {
+    onNotify(data: IBigWinItem): void {
         if (!this.itemNode || !this.content) return;
         if (!this.itemNode || !this.content) return;
 
 
-        const msg = `${formatMaskNickName(data.nickName)} won ${CURRENCY_TYPE.SC} ${formatSCNum(data.value)}`;
+        const msg = `${formatMaskNickName(data.username)} won ${CURRENCY_TYPE.SC} ${formatSCNum(data.amount)}`;
 
 
         const item = this.pool.get() || instantiate(this.itemNode);
         const item = this.pool.get() || instantiate(this.itemNode);
         const noticeNode = this.getNoticeNode(item);
         const noticeNode = this.getNoticeNode(item);
@@ -93,9 +116,16 @@ export class HomeNotices extends Component {
         item.setPosition(startX, 0, item.position.z);
         item.setPosition(startX, 0, item.position.z);
         this.activeItems.push(item);
         this.activeItems.push(item);
 
 
+        // 消息右边缘进入 content 右边界,表示整条消息已经滑入。
+        // 超长消息也以尾部进入作为判定,不要求全文同时出现在可视区。
+        const fullyEnteredTime = Math.max(0, startX + itemWidth - this.content.width) / SCROLL_SPEED;
+        this.waitingForNext = true;
+        this.unschedule(this.playNextNotice);
+        this.scheduleOnce(this.playNextNotice, fullyEnteredTime + NEXT_NOTICE_DELAY);
+
         tween(item)
         tween(item)
             .to((startX - endX) / SCROLL_SPEED, {
             .to((startX - endX) / SCROLL_SPEED, {
-                position: new Vec3(endX, 0, item.position.z),
+                x: endX,
             }, { easing: 'linear' })
             }, { easing: 'linear' })
             .call(() => {
             .call(() => {
                 this.recycle(item);
                 this.recycle(item);
@@ -142,4 +172,10 @@ export class HomeNotices extends Component {
         item.removeFromParent();
         item.removeFromParent();
         this.pool.put(item);
         this.pool.put(item);
     }
     }
+
+    async updateNotices() {
+        await AppKernel.stores.mainStore.reqBigWinNotices();
+        if (!isValid(this.node)) return;
+        this.startNotify();
+    }
 }
 }

+ 4 - 2
assets/scripts/modules/lobby/ui/HomeView/HomeView.ts

@@ -11,6 +11,7 @@ import { IGameInfo } from '@scripts/store/gameStore/data';
 import { GameItem } from './GameItem';
 import { GameItem } from './GameItem';
 import { RED_POINTS } from '@scripts/app/AppRedpoint';
 import { RED_POINTS } from '@scripts/app/AppRedpoint';
 import { formatGCNum, formatSCNum } from '@scripts/utils/Helper';
 import { formatGCNum, formatSCNum } from '@scripts/utils/Helper';
+import { HomeNotices } from './HomeNotices';
 
 
 const { ccclass, property } = _decorator;
 const { ccclass, property } = _decorator;
 
 
@@ -41,8 +42,8 @@ export class HomeView extends UIView {
     @property(VirtualList)
     @property(VirtualList)
     tabGamesScroll: VirtualList;
     tabGamesScroll: VirtualList;
 
 
-    @property(Prefab)
-    gamePrefab: Prefab;
+    @property(HomeNotices)
+    homeNotices: HomeNotices;
 
 
     private userNode: UserNode;
     private userNode: UserNode;
     private gcModel: CoinModel;
     private gcModel: CoinModel;
@@ -132,6 +133,7 @@ export class HomeView extends UIView {
 
 
     protected onOpen(_params: unknown) {
     protected onOpen(_params: unknown) {
         this.updateView();
         this.updateView();
+        this.homeNotices?.updateNotices();
     }
     }
 
 
     private onUpdateFirstPack() {
     private onUpdateFirstPack() {

+ 5 - 1
assets/scripts/modules/lobby/ui/PlayHistory/PlayHistory.ts

@@ -22,6 +22,10 @@ export class PlayHistory extends UIPopup {
     @property(Node)
     @property(Node)
     btnClose: Node;
     btnClose: Node;
 
 
+    protected async onBeforeOpen(_params: unknown) {
+        await AppKernel.stores.gameStore.reqHistory(1);
+    }
+
     protected onCreate(): void | Promise<void> {
     protected onCreate(): void | Promise<void> {
         addBtnClick(this.btnClose, this.closeSelf, this);
         addBtnClick(this.btnClose, this.closeSelf, this);
     }
     }
@@ -31,7 +35,7 @@ export class PlayHistory extends UIPopup {
     }
     }
 
 
     private updateView() {
     private updateView() {
-        // LoopList.ins("PlayHistory").init(this.scrollView, null, 22);
+        
     }
     }
 }
 }
 
 

+ 0 - 9
assets/scripts/platform/FaceBook/FBAndroidImpl.ts.meta

@@ -1,9 +0,0 @@
-{
-  "ver": "4.0.24",
-  "importer": "typescript",
-  "imported": true,
-  "uuid": "9cbe5430-29fb-468a-96ae-7c57e998b75d",
-  "files": [],
-  "subMetas": {},
-  "userData": {}
-}

+ 0 - 49
assets/scripts/platform/FaceBook/FBIOSImpl.ts

@@ -1,49 +0,0 @@
-import {
-    FacebookImpl,
-    IFBEventParams,
-    IFBShareOptions,
-    IFBShareRes,
-} from "./FacebookImpl";
-
-export class FBIOSImpl extends FacebookImpl {
-    init() {
-
-
-    }
-
-    protected async onLogin() {
-        return { accessToken: "", isCancel: false };
-    }
-
-    async logOut() {
-        return true;
-    }
-
-    async share(_options: IFBShareOptions): Promise<IFBShareRes> {
-        return {
-            success: false,
-            isCancel: false,
-            reason: 'Facebook iOS 原生实现尚未接入。',
-        };
-    }
-
-    async logEvent(
-        _eventName: string,
-        _parameters: IFBEventParams = {},
-        _valueToSum?: number,
-    ): Promise<boolean> {
-        return false;
-    }
-
-    async logPurchase(
-        _amount: number,
-        _currency: string,
-        _parameters: IFBEventParams = {},
-    ): Promise<boolean> {
-        return false;
-    }
-
-    async flushEvents(): Promise<boolean> {
-        return false;
-    }
-}

+ 0 - 9
assets/scripts/platform/FaceBook/FBIOSImpl.ts.meta

@@ -1,9 +0,0 @@
-{
-  "ver": "4.0.24",
-  "importer": "typescript",
-  "imported": true,
-  "uuid": "e924a4b7-be34-4811-b30f-6d47ea6e8e11",
-  "files": [],
-  "subMetas": {},
-  "userData": {}
-}

+ 19 - 11
assets/scripts/platform/FaceBook/FBAndroidImpl.ts → assets/scripts/platform/FaceBook/FBNativeImpl.ts

@@ -29,6 +29,8 @@ interface NativeResponse<T = Record<string, never>> {
 interface LoginData {
 interface LoginData {
     accessToken?: string;
     accessToken?: string;
     userId?: string;
     userId?: string;
+    authenticationToken?: string;
+    nonce?: string;
 }
 }
 
 
 interface ShareData {
 interface ShareData {
@@ -46,7 +48,7 @@ interface PendingRequest {
     timer: ReturnType<typeof setTimeout>;
     timer: ReturnType<typeof setTimeout>;
 }
 }
 
 
-export class FBAndroidImpl extends FacebookImpl {
+export class FBNativeImpl extends FacebookImpl {
     private _requestSeed = 0;
     private _requestSeed = 0;
     private _pendingRequests = new Map<string, PendingRequest>();
     private _pendingRequests = new Map<string, PendingRequest>();
     private _initPromise: Promise<boolean> | null = null;
     private _initPromise: Promise<boolean> | null = null;
@@ -62,14 +64,14 @@ export class FBAndroidImpl extends FacebookImpl {
                 return true;
                 return true;
             })
             })
             .catch((error) => {
             .catch((error) => {
-                console.error("[Facebook][Android] 初始化失败:", error);
+                console.error("[Facebook][Native] 初始化失败:", error);
                 return false;
                 return false;
             });
             });
     }
     }
 
 
     protected async onLogin(): Promise<IFBLoginRes> {
     protected async onLogin(): Promise<IFBLoginRes> {
         if (!(await this.ensureInitialized())) {
         if (!(await this.ensureInitialized())) {
-            return this.loginFailure("Facebook Android SDK 尚未初始化。");
+            return this.loginFailure("Facebook 原生 SDK 尚未初始化。");
         }
         }
 
 
         try {
         try {
@@ -87,12 +89,18 @@ export class FBAndroidImpl extends FacebookImpl {
             }
             }
 
 
             const accessToken = response.data?.accessToken || "";
             const accessToken = response.data?.accessToken || "";
-            if (!accessToken) {
-                return this.loginFailure("Facebook 登录未返回 Access Token。");
+            const authenticationToken = response.data?.authenticationToken || "";
+            if (!accessToken && !authenticationToken) {
+                return this.loginFailure("Facebook 登录未返回有效令牌。");
             }
             }
 
 
             return {
             return {
                 accessToken,
                 accessToken,
+                ...(authenticationToken ? {
+                    authenticationToken,
+                    nonce: response.data?.nonce,
+                    tokenType: "authentication" as const,
+                } : {}),
                 isCancel: false,
                 isCancel: false,
                 status: "connected",
                 status: "connected",
             };
             };
@@ -107,7 +115,7 @@ export class FBAndroidImpl extends FacebookImpl {
             await this.request("logout");
             await this.request("logout");
             return true;
             return true;
         } catch (error) {
         } catch (error) {
-            console.error("[Facebook][Android] 登出失败:", error);
+            console.error("[Facebook][Native] 登出失败:", error);
             return false;
             return false;
         }
         }
     }
     }
@@ -117,7 +125,7 @@ export class FBAndroidImpl extends FacebookImpl {
             return {
             return {
                 success: false,
                 success: false,
                 isCancel: false,
                 isCancel: false,
-                reason: "Facebook Android SDK 尚未初始化。",
+                reason: "Facebook 原生 SDK 尚未初始化。",
             };
             };
         }
         }
 
 
@@ -157,7 +165,7 @@ export class FBAndroidImpl extends FacebookImpl {
             const response = await this.request<EventData>("logEvent", params);
             const response = await this.request<EventData>("logEvent", params);
             return response.data?.accepted === true;
             return response.data?.accepted === true;
         } catch (error) {
         } catch (error) {
-            console.error(`[Facebook][Android] 事件 ${eventName} 记录失败:`, error);
+            console.error(`[Facebook][Native] 事件 ${eventName} 记录失败:`, error);
             return false;
             return false;
         }
         }
     }
     }
@@ -176,7 +184,7 @@ export class FBAndroidImpl extends FacebookImpl {
             });
             });
             return response.data?.accepted === true;
             return response.data?.accepted === true;
         } catch (error) {
         } catch (error) {
-            console.error("[Facebook][Android] 购买事件记录失败:", error);
+            console.error("[Facebook][Native] 购买事件记录失败:", error);
             return false;
             return false;
         }
         }
     }
     }
@@ -187,7 +195,7 @@ export class FBAndroidImpl extends FacebookImpl {
             const response = await this.request<EventData>("flushEvents");
             const response = await this.request<EventData>("flushEvents");
             return response.data?.accepted === true;
             return response.data?.accepted === true;
         } catch (error) {
         } catch (error) {
-            console.error("[Facebook][Android] App Events 刷新失败:", error);
+            console.error("[Facebook][Native] App Events 刷新失败:", error);
             return false;
             return false;
         }
         }
     }
     }
@@ -234,7 +242,7 @@ export class FBAndroidImpl extends FacebookImpl {
         try {
         try {
             response = JSON.parse(payload) as NativeResponse<any>;
             response = JSON.parse(payload) as NativeResponse<any>;
         } catch (error) {
         } catch (error) {
-            console.error("[Facebook][Android] 无法解析原生响应:", error);
+            console.error("[Facebook][Native] 无法解析原生响应:", error);
             return;
             return;
         }
         }
 
 

+ 1 - 0
assets/scripts/platform/FaceBook/FBNativeImpl.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"811c578f-7d73-43cf-9e08-1af3fcfc16cb","files":[],"subMetas":{},"userData":{}}

+ 3 - 8
assets/scripts/platform/FaceBook/Facebook.ts

@@ -1,17 +1,12 @@
 import { ANDROID, IOS } from "cc/env";
 import { ANDROID, IOS } from "cc/env";
 import { FacebookImpl } from "./FacebookImpl";
 import { FacebookImpl } from "./FacebookImpl";
-import { FBAndroidImpl } from "./FBAndroidImpl";
 import { FBH5Impl } from "./FBH5Impl";
 import { FBH5Impl } from "./FBH5Impl";
-import { FBIOSImpl } from "./FBIOSImpl";
+import { FBNativeImpl } from "./FBNativeImpl";
 
 
 
 
 function getFaceBookImpl(): FacebookImpl {
 function getFaceBookImpl(): FacebookImpl {
-    if (ANDROID) {
-        return new FBAndroidImpl();
-    }
-
-    if (IOS) {
-        return new FBIOSImpl();
+    if (ANDROID || IOS) {
+        return new FBNativeImpl();
     }
     }
 
 
     return new FBH5Impl();
     return new FBH5Impl();

+ 4 - 0
assets/scripts/platform/FaceBook/FacebookImpl.ts

@@ -1,5 +1,9 @@
 export interface IFBLoginRes {
 export interface IFBLoginRes {
     accessToken: string;
     accessToken: string;
+    /** iOS Limited Login JWT; never use as a Graph API access token. */
+    authenticationToken?: string;
+    tokenType?: "access" | "authentication";
+    nonce?: string;
     isCancel: boolean;
     isCancel: boolean;
     status?: string;
     status?: string;
     reason?: string;
     reason?: string;

+ 8 - 3
assets/scripts/store/gameStore/GameStore.ts

@@ -1,8 +1,8 @@
-import { APP_FAVO_ADD, APP_FAVO_REMOVE, APP_GAME_LAUNCH, APP_GAME_LIST } from "@scripts/api";
+import { APP_FAVO_ADD, APP_FAVO_REMOVE, APP_GAME_HISTORY, APP_GAME_LAUNCH, APP_GAME_LIST } from "@scripts/api";
 import { Http } from "@scripts/request";
 import { Http } from "@scripts/request";
 import { checkResponse, success } from '@scripts/api/ApiHelper';
 import { checkResponse, success } from '@scripts/api/ApiHelper';
-import { IGameInfo, IGameLaunch, IGameListParams } from "./data";
-import { CURRENCY_TYPE, CurrencyType, GAME_TABS, GameTabType } from "@scripts/define/Define";
+import { IGameHistoryRes, IGameInfo, IGameLaunch, IGameListParams } from "./data";
+import { CurrencyType, GAME_TABS, GameTabType } from "@scripts/define/Define";
 
 
 const UPDATE_LIST_CD = 10 * 60 * 1000;
 const UPDATE_LIST_CD = 10 * 60 * 1000;
 
 
@@ -138,6 +138,11 @@ class GameStore {
             this._recentMap[currency] = recents;
             this._recentMap[currency] = recents;
         }
         }
     }
     }
+
+    async reqHistory(page: number, pageSize = 20) {
+        let res = await Http.call<IGameHistoryRes>(APP_GAME_HISTORY, { page, pageSize })
+
+    }
 }
 }
 
 
 export const gameStore = new GameStore();
 export const gameStore = new GameStore();

+ 24 - 0
assets/scripts/store/gameStore/data.ts

@@ -39,4 +39,28 @@ export interface IGameLaunch {
     providerGameId: string;
     providerGameId: string;
     currency: string;
     currency: string;
     launchUrl: string;
     launchUrl: string;
+}
+
+export interface IGameHistory {
+    roundId: number;
+    gameName: string;
+    gameTime: string;
+    entry: number;
+    win: number;
+    currency: string;
+}
+
+export interface IGameHistoryRes {
+    current_page: number;
+    data: IGameHistory[];
+    first_page_url?: string;
+    from: number;
+    last_page: number;
+    last_page_url?: string;
+    next_page_url?: string;
+    path?: string;
+    per_page: number;
+    prev_page_url?: string;
+    to: number;
+    total: number;
 }
 }

+ 23 - 3
assets/scripts/store/mainStore/MainStore.ts

@@ -15,6 +15,7 @@ import {
     APP_BIND_3RD,
     APP_BIND_3RD,
     APP_UNBIND_3RD,
     APP_UNBIND_3RD,
     APP_UNBIND_3RD_CONFIRM,
     APP_UNBIND_3RD_CONFIRM,
+    APP_BIG_WIN,
 } from "../../api";
 } from "../../api";
 import { success } from '@scripts/api/ApiHelper';
 import { success } from '@scripts/api/ApiHelper';
 import { AppKernel } from "../../app/AppKernel";
 import { AppKernel } from "../../app/AppKernel";
@@ -23,8 +24,9 @@ import { LOCAL_CURRENCY_MODE, LOCAL_SIGN } from "../../constants/Localkey";
 import { ApiEnvelope, Http } from "../../request";
 import { ApiEnvelope, Http } from "../../request";
 import { getEnterPlatform, localStorageGet, localStorageSet } from "../../utils/Helper";
 import { getEnterPlatform, localStorageGet, localStorageSet } from "../../utils/Helper";
 import {
 import {
+    IBigWinItem,
+    IBigWinRes,
     IForgetPassReq,
     IForgetPassReq,
-    INoticeData,
     IProtectInfo,
     IProtectInfo,
     IRoutesResponse,
     IRoutesResponse,
     IUserData,
     IUserData,
@@ -53,8 +55,9 @@ class MainStore {
     private _withdrawFeeRate: number;
     private _withdrawFeeRate: number;
     private _serverKick: boolean;
     private _serverKick: boolean;
     private _protectInfo: IProtectInfo = null;
     private _protectInfo: IProtectInfo = null;
-    private _noticesList: INoticeData[] = [];
+    private _noticesList: IBigWinItem[] = [];
     private _currencyKind: CurrencyType;
     private _currencyKind: CurrencyType;
+    private _lastNoticeIdx = 0;
 
 
     private _otpTsMap: Record<OtpType, number> = {
     private _otpTsMap: Record<OtpType, number> = {
         [OtpType.Main]: 0,
         [OtpType.Main]: 0,
@@ -392,7 +395,9 @@ class MainStore {
     }
     }
 
 
     getNoticeData() {
     getNoticeData() {
-        return this._noticesList.shift();
+        const data = this._noticesList.shift();
+        if (!!data) this._lastNoticeIdx = data.id;
+        return data;
     }
     }
 
 
     async reqBindPhoneConfirm({ account, code, password }) {
     async reqBindPhoneConfirm({ account, code, password }) {
@@ -497,7 +502,22 @@ class MainStore {
         return false;
         return false;
     }
     }
 
 
+    async reqBigWinNotices() {
+        const params = {
+            currency: CURRENCY_TYPE.SC,
+            lastId: this._lastNoticeIdx,
+        };
+        const res = await Http.call<IBigWinRes>(APP_BIG_WIN, params);
+        if (success(res)) {
+            let list = res.data.items || [];
+            list = list.sort((a, b) => a.id - b.id)
+            this._noticesList = [...this._noticesList, ...list];
+            console.log(this._noticesList)
+            return this._noticesList.length > 0;
+        }
 
 
+        return false;
+    }
 
 
 }
 }
 
 

+ 17 - 3
assets/scripts/store/mainStore/data.ts

@@ -70,7 +70,21 @@ export interface IForgetPassReq {
     country?: string | number;
     country?: string | number;
 }
 }
 
 
-export interface INoticeData {
-    nickName: string,
-    value: number,
+export interface IBigWinItem {
+    id: number;
+    username: string;
+    faceId: number;
+    gameId: number;
+    gameName: string;
+    amount: number;
+    currency: string;
+    level: string;
+    createdAt: number;
+}
+
+export interface IBigWinRes {
+    serverTime: number;
+    latestId: number;
+    reset: boolean;
+    items: IBigWinItem[];
 }
 }

+ 6 - 0
native/engine/ios/AppDelegate.mm

@@ -32,6 +32,7 @@
 #include "platform/ios/IOSPlatform.h"
 #include "platform/ios/IOSPlatform.h"
 #import "platform/ios/AppDelegateBridge.h"
 #import "platform/ios/AppDelegateBridge.h"
 #import "service/SDKWrapper.h"
 #import "service/SDKWrapper.h"
+#import "service/FBWrapper.h"
 
 
 @implementation AppDelegate
 @implementation AppDelegate
 @synthesize window;
 @synthesize window;
@@ -41,6 +42,7 @@
 #pragma mark Application lifecycle
 #pragma mark Application lifecycle
 
 
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+    [[FBWrapper shared] application:application didFinishLaunchingWithOptions:launchOptions];
     [[SDKWrapper shared] application:application didFinishLaunchingWithOptions:launchOptions];
     [[SDKWrapper shared] application:application didFinishLaunchingWithOptions:launchOptions];
     appDelegateBridge = [[AppDelegateBridge alloc] init];
     appDelegateBridge = [[AppDelegateBridge alloc] init];
     
     
@@ -69,6 +71,10 @@
     [appDelegateBridge applicationWillResignActive:application];
     [appDelegateBridge applicationWillResignActive:application];
 }
 }
 
 
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
+    return [[FBWrapper shared] application:application openURL:url options:options];
+}
+
 - (void)applicationDidBecomeActive:(UIApplication *)application {
 - (void)applicationDidBecomeActive:(UIApplication *)application {
     /*
     /*
      Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
      Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

+ 6 - 0
native/engine/ios/CMakeLists.txt

@@ -18,3 +18,9 @@ set(EXECUTABLE_NAME ${APP_NAME}-mobile)
 cc_ios_before_target(${EXECUTABLE_NAME})
 cc_ios_before_target(${EXECUTABLE_NAME})
 add_executable(${EXECUTABLE_NAME} ${CC_ALL_SOURCES})
 add_executable(${EXECUTABLE_NAME} ${CC_ALL_SOURCES})
 cc_ios_after_target(${EXECUTABLE_NAME})
 cc_ios_after_target(${EXECUTABLE_NAME})
+
+target_sources(${EXECUTABLE_NAME} PRIVATE ${CC_PROJECT_DIR}/service/FBWrapper.mm)
+set_source_files_properties(${CC_PROJECT_DIR}/service/FBWrapper.mm PROPERTIES COMPILE_FLAGS "-fobjc-arc")
+set_target_properties(${EXECUTABLE_NAME} PROPERTIES
+    XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "12.0"
+    XCODE_ATTRIBUTE_ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES "YES")

+ 42 - 0
native/engine/ios/FACEBOOK.md

@@ -0,0 +1,42 @@
+# Facebook iOS 接入(Creator 3.8.x)
+
+实现初始化、Limited Login、登出、链接分享、自定义事件、购买事件和事件刷新。
+TypeScript 的 FBIOSImpl 与 Android 共用 FBNativeImpl 请求桥;原生由 service/FBWrapper.mm 实现。
+AppDelegate 直接持有 FBWrapper 单例,无需在 service.json 再次注册。
+
+## 在 Mac 上构建
+
+1. 在 Creator iOS 构建面板的「SDK 管理 → Facebook」勾选启用,填写 App ID、Client Token 和自动事件选项,构建并生成 Xcode 工程。
+   插件更新源 Info.plist 的凭据、fb<AppID> URL Scheme 及查询白名单。Xcode 工程生成必须在这些配置写入之后。
+2. 安装 CocoaPods 后,在本目录执行(替换为生成工程的实际绝对路径):
+
+   ```sh
+   COCOS_IOS_PROJECT="/absolute/path/game_2c.xcodeproj" pod install
+   ```
+
+   若 target 改名,额外设置 `COCOS_IOS_TARGET="实际 target 名"`。
+   SDK 固定为 18.1.1(CoreKit/LoginKit/ShareKit),最低 iOS 12。
+3. 打开生成工程旁的 `FacebookGame.xcworkspace`,设置签名,在 iPhone 上运行。
+   Creator 重新生成 Xcode 工程后,重新执行 pod install;不要只打开 xcodeproj。
+4. 在 Meta 后台增加 iOS 平台,Bundle ID 必须与实际包一致;开发模式账号须有应用角色/测试权限。
+
+当前 Windows 环境未进行 Xcode 编译、CocoaPods 安装或真机验证。
+
+## 登录结果与后端
+
+默认使用 Limited Login,不主动请求 ATT,不开启广告追踪。
+成功时 `status === 'connected'`,返回 `authenticationToken`(JWT)、`nonce`、`tokenType: 'authentication'`;`accessToken` 为空。
+服务端需要验证 Meta JWT 的签名、issuer、audience(App ID)、有效期和 nonce,并做防重放处理。
+不能把 JWT 直接交给现有只接受 Graph API Access Token 的接口;业务登录接口需支持此类令牌后才能完成服务器登录。
+当前没有修改业务登录接口或 TrackEvents.ts。
+
+## 验证清单
+
+- 初始化失败能返回配置错误;正常初始化后 isInitialized 为 true。
+- 真机登录成功/取消/失败;浏览器或 Facebook App 返回游戏;登出后可再次登录。
+- 链接分享成功/取消/失败;重复操作被拒绝。
+- Meta 测试事件里检查 logEvent、logPurchase 和 flushEvents;返回 true 仅代表本地 SDK 接受调用。
+- 自动应用事件由 FacebookAutoLogAppEventsEnabled 控制;手动事件仍由业务显式调用。
+- 重复构建及修改 App ID 后检查 Info.plist URL Scheme,其他 SDK Scheme 应保留。
+
+依赖来源:https://github.com/facebook/facebook-ios-sdk/tree/v18.1.1

+ 15 - 0
native/engine/ios/Podfile

@@ -0,0 +1,15 @@
+platform :ios, '12.0'
+use_frameworks!
+
+# Creator generates the Xcode project outside this source directory.
+# COCOS_IOS_PROJECT=/absolute/path/game_2c.xcodeproj pod install
+project_path = ENV['COCOS_IOS_PROJECT']
+abort 'Set COCOS_IOS_PROJECT to the generated .xcodeproj path.' unless project_path && File.directory?(project_path)
+project project_path
+workspace File.join(File.dirname(project_path), 'FacebookGame')
+
+target ENV.fetch('COCOS_IOS_TARGET', 'game_2c-mobile') do
+  pod 'FBSDKCoreKit', '18.1.1'
+  pod 'FBSDKLoginKit', '18.1.1'
+  pod 'FBSDKShareKit', '18.1.1'
+end

+ 7 - 0
native/engine/ios/service/FBWrapper.h

@@ -0,0 +1,7 @@
+#import <UIKit/UIKit.h>
+
+@interface FBWrapper : NSObject
++ (instancetype)shared;
+- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options;
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options;
+@end

+ 209 - 0
native/engine/ios/service/FBWrapper.mm

@@ -0,0 +1,209 @@
+#import "FBWrapper.h"
+#import <FBSDKCoreKit/FBSDKCoreKit.h>
+#import <FBSDKLoginKit/FBSDKLoginKit.h>
+#import <FBSDKShareKit/FBSDKShareKit.h>
+#import "platform/apple/JsbBridgeWrapper.h"
+#include "application/ApplicationManager.h"
+#include "application/BaseApplication.h"
+#include "base/Scheduler.h"
+#include "engine/BaseEngine.h"
+#include <cmath>
+
+@interface FBWrapper () <FBSDKSharingDelegate>
+@property(nonatomic, strong) FBSDKLoginManager *loginManager;
+@property(nonatomic, strong) FBSDKShareDialog *shareDialog;
+@property(nonatomic, strong) NSDictionary *shareRequest;
+@property(nonatomic, copy) NSString *startupError;
+@property(nonatomic) BOOL loginPending;
+@end
+
+@implementation FBWrapper
++ (instancetype)shared {
+    static FBWrapper *instance;
+    static dispatch_once_t once;
+    dispatch_once(&once, ^{ instance = [FBWrapper new]; });
+    return instance;
+}
+
+- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options {
+    // Register even on configuration failure so JS receives a useful error instead of timing out.
+    [[JsbBridgeWrapper sharedInstance] addScriptEventListener:@"facebook.request" listener:^(NSString *payload) {
+        dispatch_async(dispatch_get_main_queue(), ^{ [self handlePayload:payload]; });
+    }];
+    NSDictionary *info = NSBundle.mainBundle.infoDictionary;
+    if (![info[@"FacebookAppID"] isKindOfClass:NSString.class] || ![info[@"FacebookAppID"] length] ||
+        ![info[@"FacebookClientToken"] isKindOfClass:NSString.class] || ![info[@"FacebookClientToken"] length]) {
+        self.startupError = @"请在 SDK 管理构建面板启用 Facebook 并填写 App ID 和 Client Token。";
+        return;
+    }
+    @try {
+        [[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:options];
+        self.loginManager = [FBSDKLoginManager new];
+    } @catch (NSException *exception) {
+        self.startupError = exception.reason ?: @"Facebook 初始化失败。";
+    }
+}
+
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options {
+    if (!self.loginManager) return NO;
+    return [[FBSDKApplicationDelegate sharedInstance] application:application openURL:url options:options];
+}
+
+- (void)reply:(NSDictionary *)request data:(NSDictionary *)data cancelled:(BOOL)cancelled error:(NSString *)error {
+    NSMutableDictionary *response = [@{@"requestId": request[@"requestId"] ?: @"",
+        @"method": request[@"method"] ?: @"", @"success": @(!error), @"cancelled": @(cancelled)} mutableCopy];
+    if (data) response[@"data"] = data;
+    if (error) response[@"error"] = @{@"code": @"FACEBOOK_IOS_ERROR", @"message": error};
+    NSData *json = [NSJSONSerialization dataWithJSONObject:response options:0 error:nil];
+    NSString *payload = [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];
+    auto app = CC_CURRENT_APPLICATION();
+    if (!app || !app->getEngine() || !payload) return;
+    app->getEngine()->getScheduler()->performFunctionInCocosThread([payload]() {
+        [[JsbBridgeWrapper sharedInstance] dispatchEventToScript:@"facebook.response" arg:payload];
+    });
+}
+
+- (UIViewController *)presenter {
+    UIViewController *controller = UIApplication.sharedApplication.delegate.window.rootViewController;
+    while (controller.presentedViewController) controller = controller.presentedViewController;
+    return controller;
+}
+
+- (void)handlePayload:(NSString *)payload {
+    id decoded = [NSJSONSerialization JSONObjectWithData:[payload dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
+    if (![decoded isKindOfClass:NSDictionary.class]) return;
+    NSDictionary *request = decoded;
+    if (![request[@"requestId"] isKindOfClass:NSString.class] || ![request[@"method"] isKindOfClass:NSString.class]) return;
+    NSString *method = request[@"method"];
+    NSDictionary *params = [request[@"params"] isKindOfClass:NSDictionary.class] ? request[@"params"] : @{};
+    @try {
+        if (!self.loginManager) {
+            [self reply:request data:nil cancelled:NO error:self.startupError ?: @"Facebook SDK 未初始化。"];
+        } else if ([method isEqualToString:@"init"]) {
+            [self reply:request data:@{} cancelled:NO error:nil];
+        } else if ([method isEqualToString:@"login"]) {
+            [self login:request];
+        } else if ([method isEqualToString:@"logout"]) {
+            if (self.loginPending) {
+                [self reply:request data:nil cancelled:NO error:@"Facebook 登录仍在进行中。"];
+                return;
+            }
+            [self.loginManager logOut];
+            [self reply:request data:@{} cancelled:NO error:nil];
+        } else if ([method isEqualToString:@"share"]) {
+            [self share:request params:params];
+        } else if ([method isEqualToString:@"logEvent"] || [method isEqualToString:@"logPurchase"] || [method isEqualToString:@"flushEvents"]) {
+            [self event:request params:params];
+        } else {
+            [self reply:request data:nil cancelled:NO error:@"不支持的 Facebook 方法。"];
+        }
+    } @catch (NSException *exception) {
+        if ([method isEqualToString:@"login"]) self.loginPending = NO;
+        if ([method isEqualToString:@"share"]) { self.shareRequest = nil; self.shareDialog = nil; }
+        [self reply:request data:nil cancelled:NO error:exception.reason ?: @"Facebook SDK 调用失败。"];
+    }
+}
+
+- (void)login:(NSDictionary *)request {
+    if (self.loginPending || self.shareRequest || !self.presenter) {
+        [self reply:request data:nil cancelled:NO error:@"已有 Facebook 操作进行中,或没有可用窗口。"];
+        return;
+    }
+    // Limited Login works without an ATT prompt. Each attempt gets its own nonce.
+    NSString *nonce = NSUUID.UUID.UUIDString;
+    FBSDKLoginConfiguration *config = [[FBSDKLoginConfiguration alloc]
+        initWithPermissions:@[@"public_profile", @"email"] tracking:FBSDKLoginTrackingLimited nonce:nonce];
+    self.loginPending = YES;
+    [self.loginManager logInFromViewController:self.presenter configuration:config
+        completion:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
+        self.loginPending = NO;
+        if (error) { [self reply:request data:nil cancelled:NO error:error.localizedDescription]; return; }
+        if (result.isCancelled) { [self reply:request data:nil cancelled:YES error:nil]; return; }
+        NSString *token = result.authenticationToken.tokenString;
+        if (!token.length) { [self reply:request data:nil cancelled:NO error:@"Facebook 未返回身份令牌。"] ; return; }
+        [self reply:request data:@{@"authenticationToken": token, @"nonce": nonce} cancelled:NO error:nil];
+    }];
+}
+
+- (void)share:(NSDictionary *)request params:(NSDictionary *)params {
+    if (self.shareRequest || self.loginPending || !self.presenter) {
+        [self reply:request data:nil cancelled:NO error:@"已有 Facebook 操作进行中,或没有可用窗口。"];
+        return;
+    }
+    NSString *value = [params[@"url"] isKindOfClass:NSString.class] ? params[@"url"] : @"";
+    NSURL *url = [NSURL URLWithString:value];
+    if (!url.host.length || ![@[@"https", @"http"] containsObject:url.scheme.lowercaseString]) {
+        [self reply:request data:nil cancelled:NO error:@"分享链接必须为有效的 HTTP(S) URL。"];
+        return;
+    }
+    FBSDKShareLinkContent *content = [FBSDKShareLinkContent new];
+    content.contentURL = url;
+    if ([params[@"quote"] isKindOfClass:NSString.class]) content.quote = params[@"quote"];
+    if ([params[@"hashtag"] isKindOfClass:NSString.class] && [params[@"hashtag"] length]) {
+        content.hashtag = [[FBSDKHashtag alloc] initWithString:params[@"hashtag"]];
+    }
+    self.shareRequest = request;
+    self.shareDialog = [[FBSDKShareDialog alloc] initWithViewController:self.presenter content:content delegate:self];
+    self.shareDialog.mode = FBSDKShareDialogModeAutomatic;
+    if (![self.shareDialog canShow] || ![self.shareDialog show]) {
+        if (self.shareRequest) [self reply:request data:nil cancelled:NO error:@"无法打开 Facebook 分享窗口。"];
+        self.shareRequest = nil;
+        self.shareDialog = nil;
+    }
+}
+
+- (void)sharer:(id<FBSDKSharing>)sharer didCompleteWithResults:(NSDictionary *)results {
+    if (self.shareRequest) [self reply:self.shareRequest data:@{@"postId": results[@"postId"] ?: @""} cancelled:NO error:nil];
+    self.shareRequest = nil; self.shareDialog = nil;
+}
+- (void)sharer:(id<FBSDKSharing>)sharer didFailWithError:(NSError *)error {
+    if (self.shareRequest) [self reply:self.shareRequest data:nil cancelled:NO error:error.localizedDescription];
+    self.shareRequest = nil; self.shareDialog = nil;
+}
+- (void)sharerDidCancel:(id<FBSDKSharing>)sharer {
+    if (self.shareRequest) [self reply:self.shareRequest data:nil cancelled:YES error:nil];
+    self.shareRequest = nil; self.shareDialog = nil;
+}
+
+- (void)event:(NSDictionary *)request params:(NSDictionary *)params {
+    NSString *method = request[@"method"];
+    NSDictionary *values = params[@"parameters"] ?: @{};
+    if (![values isKindOfClass:NSDictionary.class]) {
+        [self reply:request data:nil cancelled:NO error:@"事件参数必须为字典。"];
+        return;
+    }
+    for (id key in values) {
+        id value = values[key];
+        if (![key isKindOfClass:NSString.class] ||
+            (![value isKindOfClass:NSString.class] && ![value isKindOfClass:NSNumber.class])) {
+            [self reply:request data:nil cancelled:NO error:@"事件参数只支持字符串或数字。"];
+            return;
+        }
+    }
+    if ([method isEqualToString:@"flushEvents"]) {
+        [FBSDKAppEvents.shared flush];
+    } else if ([method isEqualToString:@"logPurchase"]) {
+        NSNumber *amount = params[@"amount"];
+        NSString *currency = params[@"currency"];
+        if (![amount isKindOfClass:NSNumber.class] || !std::isfinite(amount.doubleValue) || amount.doubleValue < 0 ||
+            ![currency isKindOfClass:NSString.class] ||
+            [currency rangeOfString:@"^[A-Za-z]{3}$" options:NSRegularExpressionSearch].location == NSNotFound) {
+            [self reply:request data:nil cancelled:NO error:@"购买金额或 ISO 4217 货币代码无效。"];
+            return;
+        }
+        [FBSDKAppEvents.shared logPurchase:amount.doubleValue currency:currency.uppercaseString parameters:values];
+    } else {
+        NSString *name = params[@"eventName"];
+        NSNumber *sum = params[@"valueToSum"];
+        if (![name isKindOfClass:NSString.class] || !name.length ||
+            (sum && (![sum isKindOfClass:NSNumber.class] || !std::isfinite(sum.doubleValue)))) {
+            [self reply:request data:nil cancelled:NO error:@"事件名称或累计数值无效。"];
+            return;
+        }
+        if (sum) [FBSDKAppEvents.shared logEvent:name valueToSum:sum.doubleValue parameters:values];
+        else [FBSDKAppEvents.shared logEvent:name parameters:values];
+    }
+    // Accepted by the local SDK, not a delivery receipt from Meta.
+    [self reply:request data:@{@"accepted": @YES} cancelled:NO error:nil];
+}
+@end