feat:支持二次确认弹窗确认、取消按钮

This commit is contained in:
richarjiang
2026-05-12 20:10:00 +08:00
parent 8b27dc7bc5
commit 078c8578fd
4 changed files with 1197 additions and 98 deletions

120
AGENTS.md
View File

@@ -40,62 +40,78 @@ Git 历史采用 Conventional Commits且摘要多为中文例如 `feat:
<claude-mem-context> <claude-mem-context>
# Memory Context # Memory Context
# $CMEM mp-xieyingeng 2026-05-11 11:01pm GMT+8 # [mp-xieyingeng] recent context, 2026-05-12 7:49pm GMT+8
Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖decision Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖decision 🚨security_alert 🔐security_note
Format: ID TIME TYPE TITLE Format: ID TIME TYPE TITLE
Fetch details: get_observations([IDs]) | Search: mem-search skill Fetch details: get_observations([IDs]) | Search: mem-search skill
Stats: 47 obs (10,663t read) | 538,681t work | 98% savings Stats: 50 obs (11,836t read) | 1,674,484t work | 99% savings
### Apr 24, 2026 ### Apr 26, 2026
101 8:46a 🟣 Live label display format updated to X/Y format S1309 移除 PageLevel.ts 进入关卡时弹出体力扣减 toast (Apr 26 at 5:16 PM)
114 6:40p 🟣 Dynamic Input Layout Initialization in PageLevel Prefab 1343 5:17p 🔴 进度条圆角畸形问题已修复
115 6:41p 🟣 Dynamic Punch Block Layout for PageLevel.prefab 1345 " ⚖️ 否决 Mask 方案,确认最小进度值方案最优
116 " 🔵 Layout Component Configuration for Input and Punch Blocks ### Apr 27, 2026
119 6:42p 🟣 Dynamic Input Blocks and Punch Layout System Implemented 1346 9:21a 🔴 移除进入游戏后的体力扣减 toast 提示
121 " 🟣 PageLevel Prefab Updated with punchLayout Property 1347 " 🔴 移除进入游戏后的体力扣减 toast 提示
122 " 🔄 Cleanup After Dynamic Block Refactoring 1348 " 🟣 首页添加体力值显示功能
124 " ✅ TypeScript Compilation Check in Progress S1308 移除进入游戏关卡时弹出体力扣减的 toast 提示 (Apr 27 at 9:21 AM)
126 6:43p ✅ TypeScript Compilation Check Extended S1310 移除进入游戏体力扣减 toast 并在首页添加体力显示 (Apr 27 at 9:22 AM)
127 " 🔄 Complete Diff of PageLevel.ts Dynamic Block System S1311 移除体力扣减 toast 并完善首页体验 (Apr 27 at 9:23 AM)
128 " 🔵 PageLevel.prefab Changes Not Persisted 1349 9:25a ✅ PageHome.ts 添加 ToastManager 导入
129 6:44p 🟣 PageLevel Prefab Correctly Updated with punchLayout 1350 " ✅ PK按钮点击改为提示"功能开发中"
130 " ✅ TypeScript Compilation Blocked - Permission Required S1313 Implement object-fit: cover image scaling in Cocos Creator to prevent downloaded images from being distorted (Apr 27 at 9:26 AM)
133 6:45p 🔄 Extracted getPunchBlockLabel Helper Method 1351 9:32a 🟣 Stamina animation system for level entry
134 " 🔄 Template Node Hiding Logic Improved 1352 " 🔵 Cocos Creator game project structure discovered
136 6:48p ⚖️ TypeScript diagnostics disabled, using IDE/linter instead 1354 " 🟣 StarGame 按钮点击动画需求
138 " 🔄 PageLevel 输入方式从单框改为逐字格子 1356 " 🔵 体力系统架构发现
139 " 🔄 谐音梗展示从 Label 改为动态 Block 节点 1353 9:33a 🔵 StaminaManager API confirmed for stamina operations
140 " ✅ PageLevel.prefab 布局位置微调 1355 9:37a 🔵 项目结构和现有代码发现
165 8:08p 🟣 PageLevel input layout simplified to single-character boxes with auto-distribution 1357 9:38a 🔵 EnterLevelData 结构分析
167 8:09p 🔵 PageLevel.ts input block structure and callback stubs discovered 1359 9:39a 🟣 Stamina animation system implemented for game entry flow
168 " 🟣 Auto-distribute characters across input boxes and auto-submit on completion implemented 1358 9:41a ⚖️ 体力值飞行动画实现计划制定完成
169 " 🔴 PageLevel.ts node cleanup now calls removeFromParent before destroy 1362 9:45a 🟣 Image Cover Mode Scaling in Cocos Creator
170 8:10p 🔄 PageLevel.ts full diff: single EditBox replaced with per-character block system S1312 Fix image distortion in PageLevel.ts by implementing object-fit: cover behavior for downloaded images (Apr 27 at 9:45 AM)
171 " 🔴 distributeInputText() wrapped in try/finally to guarantee flag reset 1360 " ✅ Cocos Creator animation imports added to PageHome.ts
179 8:23p ✅ Game011_3.ttf font relocated from resources/ to dedicated fonts/ bundle directory 1361 " ✅ Animation constants added to PageHome class
180 " 🟣 PageLoading.ts now loads fonts as a dynamic bundle after level data, before UI 1363 " ✅ Start game button integrated with stamina check and animation flow
181 " 🔄 AssetManager import switched to type-only import in PageLoading.ts 1364 " 🟣 Stamina consumption animation fully implemented in PageHome
182 8:31p 🟣 PageLevel input UX improvement: full-string editing support 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)
183 " 🟣 PageLevel input UX: full-string editing implemented 1365 9:46a 🔴 Animation completes but navigation to PageLevel fails
186 8:34p 🔴 PageLevel: clear input text on wrong answer S1315 为 PageLevel.ts 的 mainImage 和 mainImage2 节点实现 CSS cover 模式图片缩放 (Apr 27 at 9:46 AM)
187 8:35p 🟣 PageLevel: delay pass modal to show punchline 1366 9:50a 🔴 Cocos Creator tween chain broken by premature node destroy
189 8:36p 🔄 PageLevel: level completion reporting made fire-and-forget 1367 9:55a 🔴 Navigation still failing after first tween chain fix - further debugging needed
191 8:45p 🔴 PageLevel: punchline block cleanup improved 1368 " 🔴 Decoupled fly animation from float text timing using setTimeout
192 8:46p 🟣 PageLevel.ts TitleLevel Label variable reserved 1369 9:56a 🔴 Cocos Creator tween chain broken by any node state change mid-animation
193 8:47p 🔵 PageLevel.ts level number tracking mechanism discovered 1370 9:59a 🟣 CSS cover-style image scaling for PageLevel images
195 " 🟣 PageLevel.ts TitleLevel dynamic label update implemented 1371 10:01a ✅ PageLevel.ts imports extended for cover-style image scaling
196 8:53p 🔵 PageLevel punchline not updated from enterLevel API 1372 " 🟣 CSS cover-mode image scaling implemented for PageLevel images
198 " 🔴 LevelDataManager updateLevelDetails now includes punchline S1316 Debug cover mode image overflow in PageLevel.ts - image exceeds container bounds despite Mask applied (Apr 27 at 10:02 AM)
199 " 🔴 PageLevel punchline flow and empty state layout fixed 1373 10:02a 🔴 Cover mode image overflows container bounds
200 8:54p 🔴 LevelDataManager preserves existing punchline when enter returns null 1374 10:06a 🔵 GRAPHICS_RECT vs GRAPHICS_STENCIL for Mask in Cocos Creator
203 9:06p 🟣 PageLevel.ts 新增图片描述标签绑定字段 1375 10:08a 🔴 Mask.Type.RECT does not exist in Cocos Creator API
205 9:07p 🟣 PageLevel.ts 图片描述标签字段对接完成 1376 " 🔵 MaskType enum found in Cocos Creator 3.8 engine declarations
206 9:09p ✅ PageLevel.ts 回退图片描述标签字段命名 1378 " 🔵 MaskType enum values confirmed - GRAPHICS_RECT exists
208 9:11p 🟣 LevelDataManager 增加图片描述字段存储 1377 10:09a 🔵 MaskType enum definition found at line 46015
214 9:40p 🟣 拼图游戏关卡内 Punch Layout 显隐控制 1379 10:10a 🔴 Reverted Mask.Type to GRAPHICS_RECT
216 " 🟣 PageLevel.ts punchline 显隐控制逻辑对接完成 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
Access 539k tokens of past work via get_observations([IDs]) or mem-search skill. Access 1674k tokens of past work via get_observations([IDs]) or mem-search skill.
</claude-mem-context> </claude-mem-context>

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,10 @@ export type CommonModalAction = () => void | boolean;
export interface CommonModalCallbacks { export interface CommonModalCallbacks {
/** 点击关闭按钮回调,返回 false 可阻止默认关闭 */ /** 点击关闭按钮回调,返回 false 可阻止默认关闭 */
onClose?: CommonModalAction; onClose?: CommonModalAction;
/** 点击按钮回调,返回 false 可阻止默认关闭 */ /** 点击确认按钮回调,返回 false 可阻止默认关闭 */
onConfirm?: CommonModalAction; onConfirm?: CommonModalAction;
/** 点击取消按钮回调,返回 false 可阻止默认关闭 */
onCancel?: CommonModalAction;
} }
export interface CommonModalParams extends CommonModalCallbacks { export interface CommonModalParams extends CommonModalCallbacks {
@@ -17,14 +19,16 @@ export interface CommonModalParams extends CommonModalCallbacks {
title?: string; title?: string;
/** 弹窗内容 */ /** 弹窗内容 */
content?: string; content?: string;
/** 按钮文案 */ /** 确认按钮文案 */
buttonHint?: string; buttonConfirm?: string;
/** 按钮文案别名,方便业务侧按 buttonText 调用 */ /** 取消按钮文案,传入后展示双按钮区域 */
buttonText?: string; buttonCancel?: string;
/** 点击关闭按钮后是否自动关闭弹窗,默认 true */ /** 点击关闭按钮后是否自动关闭弹窗,默认 true */
closeOnClose?: boolean; closeOnClose?: boolean;
/** 点击按钮后是否自动关闭弹窗,默认 true */ /** 点击确认按钮后是否自动关闭弹窗,默认 true */
closeOnConfirm?: boolean; closeOnConfirm?: boolean;
/** 点击取消按钮后是否自动关闭弹窗,默认 true */
closeOnCancel?: boolean;
/** 关闭时是否销毁节点,默认 true */ /** 关闭时是否销毁节点,默认 true */
destroyOnClose?: boolean; destroyOnClose?: boolean;
/** 弹窗层级,默认 CommonModal.MODAL_Z_INDEX */ /** 弹窗层级,默认 CommonModal.MODAL_Z_INDEX */
@@ -37,7 +41,8 @@ export class CommonModal extends BaseModal {
private static readonly DEFAULT_TITLE = '温馨提示'; private static readonly DEFAULT_TITLE = '温馨提示';
private static readonly DEFAULT_CONTENT = ''; private static readonly DEFAULT_CONTENT = '';
private static readonly DEFAULT_BUTTON_HINT = '确定'; private static readonly DEFAULT_BUTTON_CONFIRM = '确定';
private static readonly DEFAULT_BUTTON_CANCEL = '取消';
@property({ type: Label, tooltip: '标题文本' }) @property({ type: Label, tooltip: '标题文本' })
titleLabel: Label | null = null; titleLabel: Label | null = null;
@@ -45,22 +50,40 @@ export class CommonModal extends BaseModal {
@property({ type: Label, tooltip: '内容文本' }) @property({ type: Label, tooltip: '内容文本' })
contentLabel: Label | null = null; contentLabel: Label | null = null;
@property({ type: Label, tooltip: '按钮文本' }) @property({ type: Label, tooltip: '确认按钮文本' })
buttonHintLabel: Label | null = null; buttonConfirmLabel: Label | null = null;
@property({ type: Label, tooltip: '双按钮确认文本' })
actionConfirmLabel: Label | null = null;
@property({ type: Label, tooltip: '取消按钮文本' })
buttonCancelLabel: Label | null = null;
@property({ type: Node, tooltip: '关闭按钮节点' }) @property({ type: Node, tooltip: '关闭按钮节点' })
closeBtn: Node | null = null; closeBtn: Node | null = null;
@property({ type: Node, tooltip: '按钮节点' }) @property({ type: Node, tooltip: '单确认按钮节点' })
buttonHint: Node | null = null; buttonConfirm: Node | null = null;
@property({ type: Node, tooltip: '双按钮容器节点' })
actionDouble: Node | null = null;
@property({ type: Node, tooltip: '双按钮确认节点' })
actionConfirm: Node | null = null;
@property({ type: Node, tooltip: '双按钮取消节点' })
actionCancel: Node | null = null;
private _title: string = CommonModal.DEFAULT_TITLE; private _title: string = CommonModal.DEFAULT_TITLE;
private _content: string = CommonModal.DEFAULT_CONTENT; private _content: string = CommonModal.DEFAULT_CONTENT;
private _buttonHintText: string = CommonModal.DEFAULT_BUTTON_HINT; private _buttonConfirmText: string = CommonModal.DEFAULT_BUTTON_CONFIRM;
private _buttonCancelText: string = CommonModal.DEFAULT_BUTTON_CANCEL;
private _callbacks: CommonModalCallbacks = {}; private _callbacks: CommonModalCallbacks = {};
private _closeOnClose: boolean = true; private _closeOnClose: boolean = true;
private _closeOnConfirm: boolean = true; private _closeOnConfirm: boolean = true;
private _closeOnCancel: boolean = true;
private _destroyOnClose: boolean = true; private _destroyOnClose: boolean = true;
private _useDoubleActions: boolean = false;
private _screenSize: Size | null = null; private _screenSize: Size | null = null;
private _loaded: boolean = false; private _loaded: boolean = false;
@@ -71,7 +94,7 @@ export class CommonModal extends BaseModal {
* CommonModal.show(this.commonModalPrefab, { * CommonModal.show(this.commonModalPrefab, {
* title: '提示', * title: '提示',
* content: '是否继续?', * content: '是否继续?',
* buttonHint: '继续', * buttonConfirm: '继续',
* onConfirm: () => this.startGame() * onConfirm: () => this.startGame()
* }); * });
*/ */
@@ -116,14 +139,18 @@ export class CommonModal extends BaseModal {
setConfig(params: CommonModalParams = {}): void { setConfig(params: CommonModalParams = {}): void {
this._title = params.title ?? CommonModal.DEFAULT_TITLE; this._title = params.title ?? CommonModal.DEFAULT_TITLE;
this._content = params.content ?? CommonModal.DEFAULT_CONTENT; this._content = params.content ?? CommonModal.DEFAULT_CONTENT;
this._buttonHintText = params.buttonHint ?? params.buttonText ?? CommonModal.DEFAULT_BUTTON_HINT; this._buttonConfirmText = params.buttonConfirm ?? CommonModal.DEFAULT_BUTTON_CONFIRM;
this._buttonCancelText = params.buttonCancel ?? CommonModal.DEFAULT_BUTTON_CANCEL;
this._callbacks = { this._callbacks = {
onClose: params.onClose, onClose: params.onClose,
onConfirm: params.onConfirm onConfirm: params.onConfirm,
onCancel: params.onCancel
}; };
this._closeOnClose = params.closeOnClose ?? true; this._closeOnClose = params.closeOnClose ?? true;
this._closeOnConfirm = params.closeOnConfirm ?? true; this._closeOnConfirm = params.closeOnConfirm ?? true;
this._closeOnCancel = params.closeOnCancel ?? true;
this._destroyOnClose = params.destroyOnClose ?? true; this._destroyOnClose = params.destroyOnClose ?? true;
this._useDoubleActions = this._shouldUseDoubleActions(params);
this._applyContent(); this._applyContent();
} }
@@ -137,8 +164,14 @@ export class CommonModal extends BaseModal {
this._applyContent(); this._applyContent();
} }
setButtonHint(buttonHint: string): void { setButtonConfirm(buttonConfirm: string): void {
this._buttonHintText = buttonHint; this._buttonConfirmText = buttonConfirm;
this._applyContent();
}
setButtonCancel(buttonCancel: string): void {
this._buttonCancelText = buttonCancel;
this._useDoubleActions = true;
this._applyContent(); this._applyContent();
} }
@@ -193,16 +226,26 @@ export class CommonModal extends BaseModal {
this.animationNodes = this.animationNodes.length > 0 ? this.animationNodes : (panelNode ? [panelNode] : []); this.animationNodes = this.animationNodes.length > 0 ? this.animationNodes : (panelNode ? [panelNode] : []);
this.closeBtn = this.closeBtn ?? this._findNode('dialogPanel/closeBtn'); this.closeBtn = this.closeBtn ?? this._findNode('dialogPanel/closeBtn');
this.buttonHint = this.buttonHint ?? this._findNode('dialogPanel/ButtonHint'); this.buttonConfirm = this.buttonConfirm ?? this._findNode('dialogPanel/ButtonConfirm');
this.actionDouble = this.actionDouble ?? this._findNode('dialogPanel/ActionDouble');
this.actionConfirm = this.actionConfirm ?? this._findNode('dialogPanel/ActionDouble/ButtonConfirm');
this.actionCancel = this.actionCancel ?? this._findNode('dialogPanel/ActionDouble/ButtonCancel');
this.titleLabel = this.titleLabel ?? this._findNode('dialogPanel/Title')?.getComponent(Label) ?? null; this.titleLabel = this.titleLabel ?? this._findNode('dialogPanel/Title')?.getComponent(Label) ?? null;
this.contentLabel = this.contentLabel ?? this._findNode('dialogPanel/Content')?.getComponent(Label) ?? null; this.contentLabel = this.contentLabel ?? this._findNode('dialogPanel/Content')?.getComponent(Label) ?? null;
this.buttonHintLabel = this.buttonHintLabel this.buttonConfirmLabel = this.buttonConfirmLabel
?? this._findNode('dialogPanel/ButtonHint/Label')?.getComponent(Label) ?? this._findNode('dialogPanel/ButtonConfirm/Label')?.getComponent(Label)
?? null;
this.actionConfirmLabel = this.actionConfirmLabel
?? this._findNode('dialogPanel/ActionDouble/ButtonConfirm/Label')?.getComponent(Label)
?? null;
this.buttonCancelLabel = this.buttonCancelLabel
?? this._findNode('dialogPanel/ActionDouble/ButtonCancel/Label')?.getComponent(Label)
?? null; ?? null;
} }
private _applyContent(): void { private _applyContent(): void {
this._resolveNodes(); this._resolveNodes();
this._applyActionMode();
if (this.titleLabel) { if (this.titleLabel) {
this.titleLabel.string = this._title; this.titleLabel.string = this._title;
@@ -212,8 +255,16 @@ export class CommonModal extends BaseModal {
this.contentLabel.string = this._content; this.contentLabel.string = this._content;
} }
if (this.buttonHintLabel) { if (this.buttonConfirmLabel) {
this.buttonHintLabel.string = this._buttonHintText; this.buttonConfirmLabel.string = this._buttonConfirmText;
}
if (this.actionConfirmLabel) {
this.actionConfirmLabel.string = this._buttonConfirmText;
}
if (this.buttonCancelLabel) {
this.buttonCancelLabel.string = this._buttonCancelText;
} }
} }
@@ -235,8 +286,16 @@ export class CommonModal extends BaseModal {
this.closeBtn.on(Node.EventType.TOUCH_END, this._onCloseClick, this); this.closeBtn.on(Node.EventType.TOUCH_END, this._onCloseClick, this);
} }
if (this.buttonHint) { if (this.buttonConfirm) {
this.buttonHint.on(Node.EventType.TOUCH_END, this._onConfirmClick, this); this.buttonConfirm.on(Node.EventType.TOUCH_END, this._onConfirmClick, this);
}
if (this.actionConfirm) {
this.actionConfirm.on(Node.EventType.TOUCH_END, this._onConfirmClick, this);
}
if (this.actionCancel) {
this.actionCancel.on(Node.EventType.TOUCH_END, this._onCancelClick, this);
} }
} }
@@ -245,8 +304,16 @@ export class CommonModal extends BaseModal {
this.closeBtn.off(Node.EventType.TOUCH_END, this._onCloseClick, this); this.closeBtn.off(Node.EventType.TOUCH_END, this._onCloseClick, this);
} }
if (this.buttonHint?.isValid) { if (this.buttonConfirm?.isValid) {
this.buttonHint.off(Node.EventType.TOUCH_END, this._onConfirmClick, this); this.buttonConfirm.off(Node.EventType.TOUCH_END, this._onConfirmClick, this);
}
if (this.actionConfirm?.isValid) {
this.actionConfirm.off(Node.EventType.TOUCH_END, this._onConfirmClick, this);
}
if (this.actionCancel?.isValid) {
this.actionCancel.off(Node.EventType.TOUCH_END, this._onCancelClick, this);
} }
} }
@@ -264,6 +331,27 @@ export class CommonModal extends BaseModal {
} }
} }
private _onCancelClick(): void {
const shouldContinue = this._callbacks.onCancel?.();
if (shouldContinue !== false && this._closeOnCancel) {
this.close();
}
}
private _applyActionMode(): void {
if (this.buttonConfirm?.isValid) {
this.buttonConfirm.active = !this._useDoubleActions;
}
if (this.actionDouble?.isValid) {
this.actionDouble.active = this._useDoubleActions;
}
}
private _shouldUseDoubleActions(params: CommonModalParams): boolean {
return params.buttonCancel !== undefined;
}
private _findNode(path: string): Node | null { private _findNode(path: string): Node | null {
const names = path.split('/').filter(Boolean); const names = path.split('/').filter(Boolean);
let current: Node | null = this.node; let current: Node | null = this.node;

View File

@@ -2132,13 +2132,17 @@ export class PageLevel extends BaseView {
} }
const modal = CommonModal.show(this.commonModalPrefab, { const modal = CommonModal.show(this.commonModalPrefab, {
title: '切换下一题', title: '提示',
content: '确认进入下一题吗?', content: '还有时间,确认进入下一题吗?',
buttonHint: '确认', buttonConfirm: '确认',
buttonCancel: '再想想',
zIndex: CommonModal.MODAL_Z_INDEX + 1, zIndex: CommonModal.MODAL_Z_INDEX + 1,
onClose: () => { onClose: () => {
this._commonModalNode = null; this._commonModalNode = null;
}, },
onCancel: () => {
this._commonModalNode = null;
},
onConfirm: () => { onConfirm: () => {
this._commonModalNode = null; this._commonModalNode = null;
onConfirm(); onConfirm();