diff --git a/AGENTS.md b/AGENTS.md index bfd31c6..c407745 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,78 +40,62 @@ Git 历史采用 Conventional Commits,且摘要多为中文,例如 `feat: # Memory Context -# [mp-xieyingeng] recent context, 2026-05-12 7:49pm GMT+8 +# $CMEM mp-xieyingeng 2026-05-12 9:28pm GMT+8 -Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖️decision 🚨security_alert 🔐security_note +Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖️decision Format: ID TIME TYPE TITLE Fetch details: get_observations([IDs]) | Search: mem-search skill -Stats: 50 obs (11,836t read) | 1,674,484t work | 99% savings +Stats: 47 obs (10,663t read) | 538,681t work | 98% savings -### Apr 26, 2026 -S1309 移除 PageLevel.ts 进入关卡时弹出体力扣减 toast (Apr 26 at 5:16 PM) -1343 5:17p 🔴 进度条圆角畸形问题已修复 -1345 " ⚖️ 否决 Mask 方案,确认最小进度值方案最优 -### Apr 27, 2026 -1346 9:21a 🔴 移除进入游戏后的体力扣减 toast 提示 -1347 " 🔴 移除进入游戏后的体力扣减 toast 提示 -1348 " 🟣 首页添加体力值显示功能 -S1308 移除进入游戏关卡时弹出体力扣减的 toast 提示 (Apr 27 at 9:21 AM) -S1310 移除进入游戏体力扣减 toast 并在首页添加体力显示 (Apr 27 at 9:22 AM) -S1311 移除体力扣减 toast 并完善首页体验 (Apr 27 at 9:23 AM) -1349 9:25a ✅ PageHome.ts 添加 ToastManager 导入 -1350 " ✅ PK按钮点击改为提示"功能开发中" -S1313 Implement object-fit: cover image scaling in Cocos Creator to prevent downloaded images from being distorted (Apr 27 at 9:26 AM) -1351 9:32a 🟣 Stamina animation system for level entry -1352 " 🔵 Cocos Creator game project structure discovered -1354 " 🟣 StarGame 按钮点击动画需求 -1356 " 🔵 体力系统架构发现 -1353 9:33a 🔵 StaminaManager API confirmed for stamina operations -1355 9:37a 🔵 项目结构和现有代码发现 -1357 9:38a 🔵 EnterLevelData 结构分析 -1359 9:39a 🟣 Stamina animation system implemented for game entry flow -1358 9:41a ⚖️ 体力值飞行动画实现计划制定完成 -1362 9:45a 🟣 Image Cover Mode Scaling in Cocos Creator -S1312 Fix image distortion in PageLevel.ts by implementing object-fit: cover behavior for downloaded images (Apr 27 at 9:45 AM) -1360 " ✅ Cocos Creator animation imports added to PageHome.ts -1361 " ✅ Animation constants added to PageHome class -1363 " ✅ Start game button integrated with stamina check and animation flow -1364 " 🟣 Stamina consumption animation fully implemented in PageHome -S1314 Implement stamina consumption animation in Cocos Creator game - when clicking StarGame button, IconLive node flies to button with floating "-1" text, then navigates to level (Apr 27 at 9:45 AM) -1365 9:46a 🔴 Animation completes but navigation to PageLevel fails -S1315 为 PageLevel.ts 的 mainImage 和 mainImage2 节点实现 CSS cover 模式图片缩放 (Apr 27 at 9:46 AM) -1366 9:50a 🔴 Cocos Creator tween chain broken by premature node destroy -1367 9:55a 🔴 Navigation still failing after first tween chain fix - further debugging needed -1368 " 🔴 Decoupled fly animation from float text timing using setTimeout -1369 9:56a 🔴 Cocos Creator tween chain broken by any node state change mid-animation -1370 9:59a 🟣 CSS cover-style image scaling for PageLevel images -1371 10:01a ✅ PageLevel.ts imports extended for cover-style image scaling -1372 " 🟣 CSS cover-mode image scaling implemented for PageLevel images -S1316 Debug cover mode image overflow in PageLevel.ts - image exceeds container bounds despite Mask applied (Apr 27 at 10:02 AM) -1373 10:02a 🔴 Cover mode image overflows container bounds -1374 10:06a 🔵 GRAPHICS_RECT vs GRAPHICS_STENCIL for Mask in Cocos Creator -1375 10:08a 🔴 Mask.Type.RECT does not exist in Cocos Creator API -1376 " 🔵 MaskType enum found in Cocos Creator 3.8 engine declarations -1378 " 🔵 MaskType enum values confirmed - GRAPHICS_RECT exists -1377 10:09a 🔵 MaskType enum definition found at line 46015 -1379 10:10a 🔴 Reverted Mask.Type to GRAPHICS_RECT -S1317 Implement CSS cover-mode image scaling for PageLevel.ts mainImage nodes (Apr 27 at 10:10 AM) -### Apr 29, 2026 -1481 6:33p 🟣 Implementing rounded corner images in PageLevel.ts -1482 " 🔵 RoundedRectMask component already exists for image corner rounding -1485 6:34p 🟣 Implemented rounded corners for PageLevel main images -1484 " 🔄 RoundedRectMask component improved with quality standards -1487 6:37p 🔵 TypeScript verification confirms no errors in new rounded corners implementation -### May 12, 2026 -1542 4:39p 🟣 CommonModal prefab adds conditional dual-button support -1543 " 🔵 CommonModal prefab contains both button node variants -1544 4:40p 🟣 CommonModal prefab upgraded with dual-button support -1545 4:41p 🟣 CommonModal prefab restructured with dual-button ActionDouble container -1546 " 🔵 BaseModal provides modal animation infrastructure -1547 4:43p 🟣 CommonModal.ts upgraded with dual-button support and backward compatibility -1548 4:44p 🟣 CommonModal dual-button implementation completed -1549 " ✅ CommonModal dual-button feature ready for testing -1550 4:45p 🟣 CommonModal dual-button feature completed and validated +### Apr 24, 2026 +101 8:46a 🟣 Live label display format updated to X/Y format +114 6:40p 🟣 Dynamic Input Layout Initialization in PageLevel Prefab +115 6:41p 🟣 Dynamic Punch Block Layout for PageLevel.prefab +116 " 🔵 Layout Component Configuration for Input and Punch Blocks +119 6:42p 🟣 Dynamic Input Blocks and Punch Layout System Implemented +121 " 🟣 PageLevel Prefab Updated with punchLayout Property +122 " 🔄 Cleanup After Dynamic Block Refactoring +124 " ✅ TypeScript Compilation Check in Progress +126 6:43p ✅ TypeScript Compilation Check Extended +127 " 🔄 Complete Diff of PageLevel.ts Dynamic Block System +128 " 🔵 PageLevel.prefab Changes Not Persisted +129 6:44p 🟣 PageLevel Prefab Correctly Updated with punchLayout +130 " ✅ TypeScript Compilation Blocked - Permission Required +133 6:45p 🔄 Extracted getPunchBlockLabel Helper Method +134 " 🔄 Template Node Hiding Logic Improved +136 6:48p ⚖️ TypeScript diagnostics disabled, using IDE/linter instead +138 " 🔄 PageLevel 输入方式从单框改为逐字格子 +139 " 🔄 谐音梗展示从 Label 改为动态 Block 节点 +140 " ✅ PageLevel.prefab 布局位置微调 +165 8:08p 🟣 PageLevel input layout simplified to single-character boxes with auto-distribution +167 8:09p 🔵 PageLevel.ts input block structure and callback stubs discovered +168 " 🟣 Auto-distribute characters across input boxes and auto-submit on completion implemented +169 " 🔴 PageLevel.ts node cleanup now calls removeFromParent before destroy +170 8:10p 🔄 PageLevel.ts full diff: single EditBox replaced with per-character block system +171 " 🔴 distributeInputText() wrapped in try/finally to guarantee flag reset +179 8:23p ✅ Game011_3.ttf font relocated from resources/ to dedicated fonts/ bundle directory +180 " 🟣 PageLoading.ts now loads fonts as a dynamic bundle after level data, before UI +181 " 🔄 AssetManager import switched to type-only import in PageLoading.ts +182 8:31p 🟣 PageLevel input UX improvement: full-string editing support +183 " 🟣 PageLevel input UX: full-string editing implemented +186 8:34p 🔴 PageLevel: clear input text on wrong answer +187 8:35p 🟣 PageLevel: delay pass modal to show punchline +189 8:36p 🔄 PageLevel: level completion reporting made fire-and-forget +191 8:45p 🔴 PageLevel: punchline block cleanup improved +192 8:46p 🟣 PageLevel.ts TitleLevel Label variable reserved +193 8:47p 🔵 PageLevel.ts level number tracking mechanism discovered +195 " 🟣 PageLevel.ts TitleLevel dynamic label update implemented +196 8:53p 🔵 PageLevel punchline not updated from enterLevel API +198 " 🔴 LevelDataManager updateLevelDetails now includes punchline +199 " 🔴 PageLevel punchline flow and empty state layout fixed +200 8:54p 🔴 LevelDataManager preserves existing punchline when enter returns null +203 9:06p 🟣 PageLevel.ts 新增图片描述标签绑定字段 +205 9:07p 🟣 PageLevel.ts 图片描述标签字段对接完成 +206 9:09p ✅ PageLevel.ts 回退图片描述标签字段命名 +208 9:11p 🟣 LevelDataManager 增加图片描述字段存储 +214 9:40p 🟣 拼图游戏关卡内 Punch Layout 显隐控制 +216 " 🟣 PageLevel.ts punchline 显隐控制逻辑对接完成 -Access 1674k tokens of past work via get_observations([IDs]) or mem-search skill. +Access 539k tokens of past work via get_observations([IDs]) or mem-search skill. diff --git a/assets/prefabs/PagePKData.prefab b/assets/prefabs/PagePKData.prefab index ac0a427..bb5b28a 100644 --- a/assets/prefabs/PagePKData.prefab +++ b/assets/prefabs/PagePKData.prefab @@ -1612,7 +1612,7 @@ }, "_lpos": { "__type__": "cc.Vec3", - "x": -264.639, + "x": -336.301, "y": 152.257, "z": 0 }, @@ -1653,7 +1653,7 @@ }, "_contentSize": { "__type__": "cc.Size", - "width": 560, + "width": 400, "height": 88.2 }, "_anchorPoint": { @@ -1689,15 +1689,15 @@ "b": 29, "a": 255 }, - "_string": "地名谐音梗大挑战", - "_horizontalAlign": 1, + "_string": "地名谐音梗", + "_horizontalAlign": 0, "_verticalAlign": 1, "_actualFontSize": 70, "_fontSize": 70, "_fontFamily": "Arial", "_lineHeight": 70, - "_overflow": 0, - "_enableWrapText": true, + "_overflow": 1, + "_enableWrapText": false, "_font": { "__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80", "__expectedType__": "cc.TTFFont" @@ -1988,7 +1988,7 @@ "a": 255 }, "_string": "共66人参与", - "_horizontalAlign": 1, + "_horizontalAlign": 0, "_verticalAlign": 1, "_actualFontSize": 50, "_fontSize": 50, @@ -2848,7 +2848,7 @@ "a": 255 }, "_string": "第 1 名", - "_horizontalAlign": 1, + "_horizontalAlign": 0, "_verticalAlign": 1, "_actualFontSize": 60, "_fontSize": 60, @@ -6764,6 +6764,12 @@ "backBtn": { "__id__": 266 }, + "createdListContent": { + "__id__": 54 + }, + "createdListItemTemplate": { + "__id__": 63 + }, "_id": "" }, { diff --git a/assets/prefabs/PagePKData.ts b/assets/prefabs/PagePKData.ts index 2daeb68..5fef921 100644 --- a/assets/prefabs/PagePKData.ts +++ b/assets/prefabs/PagePKData.ts @@ -1,26 +1,42 @@ -import { _decorator, Node, Button } from 'cc'; +import { _decorator, Node, Button, instantiate, Label, ScrollView, UITransform, Sprite, SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc'; import { BaseView } from 'db://assets/scripts/core/BaseView'; import { ViewManager } from 'db://assets/scripts/core/ViewManager'; -import { CreatedShareItem } from 'db://assets/scripts/types/ApiTypes'; +import { CreatedShareItem, ShareParticipantRankSummary } from 'db://assets/scripts/types/ApiTypes'; import { ShareManager } from 'db://assets/scripts/utils/ShareManager'; import { ToastManager } from 'db://assets/scripts/utils/ToastManager'; const { ccclass, property } = _decorator; @ccclass('PagePKData') export class PagePKData extends BaseView { + private static readonly CREATED_ITEM_TOP_PADDING = 20; + private static readonly CREATED_ITEM_BOTTOM_PADDING = 20; + private static readonly CREATED_ITEM_SPACING = 20; + @property({ type: Node, tooltip: '返回按钮' }) backBtn: Node | null = null; + @property({ type: Node, tooltip: '我创建的挑战列表 content 节点' }) + createdListContent: Node | null = null; + + @property({ type: Node, tooltip: '我创建的挑战列表条目模板 FriendsPKRankListItem' }) + createdListItemTemplate: Node | null = null; + private _createdShares: CreatedShareItem[] = []; + private _createdItemNodes: Node[] = []; + private _createdButtonBindings: Array<{ node: Node; handler: () => void }> = []; private _isLoading: boolean = false; + private _renderVersion: number = 0; onViewLoad(): void { + this._resolveNodes(); if (this.backBtn) { this.backBtn.on(Button.EventType.CLICK, this._onBackClick, this); } + this._hideCreatedItemTemplate(); } onViewShow(): void { + this._resolveNodes(); void this._loadCreatedShares(); } @@ -46,6 +62,7 @@ export class PagePKData extends BaseView { this._createdShares = items; console.log('[PagePKData] 我创建的挑战列表:', this._createdShares); + this._renderCreatedShares(); } finally { this._isLoading = false; } @@ -55,5 +72,202 @@ export class PagePKData extends BaseView { if (this.backBtn) { this.backBtn.off(Button.EventType.CLICK, this._onBackClick, this); } + this._clearCreatedItems(); + } + + private _resolveNodes(): void { + if (!this.backBtn || !this.backBtn.isValid) { + this.backBtn = this.node.getChildByName('BtnBack'); + } + + const createdList = this.node.getChildByName('FriendsPKRankList'); + const view = createdList?.getChildByName('view'); + this.createdListContent = this.createdListContent ?? view?.getChildByName('content') ?? null; + this.createdListItemTemplate = this.createdListItemTemplate + ?? this.createdListContent?.getChildByName('FriendsPKRankListItem') + ?? null; + + if (!this.createdListContent) { + console.warn('[PagePKData] 未找到 FriendsPKRankList/content 节点'); + } + if (!this.createdListItemTemplate) { + console.warn('[PagePKData] 未找到 FriendsPKRankListItem 模板节点'); + } + } + + private _renderCreatedShares(): void { + this._renderVersion++; + this._clearCreatedItems(); + + if (!this.createdListContent || !this.createdListItemTemplate) { + return; + } + + this._layoutCreatedList(this._createdShares.length); + this._hideCreatedItemTemplate(); + + this._createdShares.forEach((share, index) => { + const item = instantiate(this.createdListItemTemplate!); + item.name = `FriendsPKRankListItem_${index + 1}`; + item.active = true; + this.createdListContent!.addChild(item); + this._createdItemNodes.push(item); + this._positionCreatedItem(item, index); + this._applyCreatedShare(item, share, this._renderVersion); + }); + + this._scrollCreatedListToTop(); + } + + private _applyCreatedShare(item: Node, share: CreatedShareItem, version: number): void { + this._setLabel(this._findLabelIn(item, 'Name'), share.title || '未命名挑战'); + this._setLabel(this._findLabelIn(item, 'ParticipateLabel'), `共${share.participantCount ?? 0}人参与`); + + const firstParticipant = this._getFirstParticipant(share); + const firstName = this._getParticipantName(firstParticipant); + const rankNumber = firstParticipant?.rank ?? 1; + + this._setLabel(this._findLabelIn(item, 'NameLabel'), firstName || (share.participantCount > 0 ? '微信用户' : '暂无参与')); + this._setLabel(this._findLabelIn(item, 'RankNumberLabel'), firstParticipant ? `第 ${rankNumber} 名` : '暂无排名'); + this._loadAvatar(firstParticipant?.avatarUrl ?? '', this._findAvatarSprite(item), version); + + const shareButton = this._findChild(item, 'ShareButton'); + if (shareButton) { + const handler = () => ShareManager.instance.triggerWxShare(share.title, share.shareCode); + shareButton.on(Button.EventType.CLICK, handler, this); + this._createdButtonBindings.push({ node: shareButton, handler }); + } + } + + private _getFirstParticipant(share: CreatedShareItem): ShareParticipantRankSummary | null { + return share.firstPlaceUser ?? share.topParticipant ?? share.firstParticipant ?? share.champion ?? null; + } + + private _getParticipantName(participant: ShareParticipantRankSummary | null): string { + return participant?.nickname || participant?.nickName || ''; + } + + private _layoutCreatedList(itemCount: number): void { + if (!this.createdListContent || !this.createdListItemTemplate) { + return; + } + + const contentTransform = this.createdListContent.getComponent(UITransform); + const viewTransform = this.createdListContent.parent?.getComponent(UITransform) ?? null; + const itemTransform = this.createdListItemTemplate.getComponent(UITransform); + if (!contentTransform || !viewTransform || !itemTransform) { + return; + } + + const contentHeight = Math.max( + viewTransform.height, + PagePKData.CREATED_ITEM_TOP_PADDING + + PagePKData.CREATED_ITEM_BOTTOM_PADDING + + itemCount * itemTransform.height + + Math.max(0, itemCount - 1) * PagePKData.CREATED_ITEM_SPACING, + ); + + contentTransform.setContentSize(contentTransform.width, contentHeight); + this.createdListContent.setPosition( + this.createdListContent.position.x, + viewTransform.height / 2, + this.createdListContent.position.z, + ); + } + + private _positionCreatedItem(item: Node, index: number): void { + const itemTransform = item.getComponent(UITransform); + if (!itemTransform) { + return; + } + + const y = -PagePKData.CREATED_ITEM_TOP_PADDING + - itemTransform.height / 2 + - index * (itemTransform.height + PagePKData.CREATED_ITEM_SPACING); + item.setPosition(0, y, item.position.z); + } + + private _scrollCreatedListToTop(): void { + const scrollView = this.node.getChildByName('FriendsPKRankList')?.getComponent(ScrollView); + scrollView?.scrollToTop(0); + } + + private _findAvatarSprite(item: Node): Sprite | null { + const headNode = this._findChild(item, 'Head'); + return headNode?.children[0]?.getComponent(Sprite) ?? headNode?.getComponent(Sprite) ?? null; + } + + private _loadAvatar(url: string, sprite: Sprite | null, version: number): void { + if (!url || !sprite) { + return; + } + + assetManager.loadRemote(url, (err, imageAsset) => { + if (err || !imageAsset || version !== this._renderVersion || !sprite.node.isValid) { + if (err) { + console.error('[PagePKData] 加载第一名头像失败:', url, err); + } + return; + } + + const texture = new Texture2D(); + texture.image = imageAsset; + const spriteFrame = new SpriteFrame(); + spriteFrame.texture = texture; + sprite.spriteFrame = spriteFrame; + }); + } + + private _clearCreatedItems(): void { + this._unbindCreatedButtons(); + + for (const item of this._createdItemNodes) { + if (item.isValid) { + item.removeFromParent(); + item.destroy(); + } + } + this._createdItemNodes = []; + this._hideCreatedItemTemplate(); + } + + private _unbindCreatedButtons(): void { + for (const binding of this._createdButtonBindings) { + if (binding.node.isValid) { + binding.node.off(Button.EventType.CLICK, binding.handler, this); + } + } + this._createdButtonBindings = []; + } + + private _hideCreatedItemTemplate(): void { + if (this.createdListItemTemplate?.isValid) { + this.createdListItemTemplate.active = false; + } + } + + private _setLabel(label: Label | null, text: string): void { + if (label) { + label.string = text; + } + } + + private _findLabelIn(root: Node, nodeName: string): Label | null { + return this._findChild(root, nodeName)?.getComponent(Label) ?? null; + } + + private _findChild(root: Node, nodeName: string): Node | null { + if (root.name === nodeName) { + return root; + } + + for (const child of root.children) { + const found = this._findChild(child, nodeName); + if (found) { + return found; + } + } + + return null; } } diff --git a/assets/scripts/types/ApiTypes.ts b/assets/scripts/types/ApiTypes.ts index cab4b27..6235ef4 100644 --- a/assets/scripts/types/ApiTypes.ts +++ b/assets/scripts/types/ApiTypes.ts @@ -162,6 +162,17 @@ export interface SubmitShareData { levels: SubmittedShareLevelData[]; } +/** 分享挑战参与者排行摘要 */ +export interface ShareParticipantRankSummary { + userId?: string | null; + nickname?: string | null; + nickName?: string | null; + avatarUrl?: string | null; + rank?: number | null; + correctCount?: number | null; + totalTimeSpent?: number | null; +} + /** 我创建的分享挑战条目 */ export interface CreatedShareItem { id: string; @@ -171,6 +182,10 @@ export interface CreatedShareItem { participantCount: number; userRank: number | null; createdAt: string; + firstPlaceUser?: ShareParticipantRankSummary | null; + topParticipant?: ShareParticipantRankSummary | null; + firstParticipant?: ShareParticipantRankSummary | null; + champion?: ShareParticipantRankSummary | null; } /** 我创建的分享挑战列表响应 */