feat: 支持通用弹窗以及下一题二次确认
This commit is contained in:
280
assets/prefabs/CommonModal.ts
Normal file
280
assets/prefabs/CommonModal.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import { _decorator, error, instantiate, Label, Node, Prefab, Size, UITransform, Vec3, view } from 'cc';
|
||||
import { BaseModal } from 'db://assets/scripts/core/BaseModal';
|
||||
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
export type CommonModalAction = () => void | boolean;
|
||||
|
||||
export interface CommonModalCallbacks {
|
||||
/** 点击关闭按钮回调,返回 false 可阻止默认关闭 */
|
||||
onClose?: CommonModalAction;
|
||||
/** 点击主按钮回调,返回 false 可阻止默认关闭 */
|
||||
onConfirm?: CommonModalAction;
|
||||
}
|
||||
|
||||
export interface CommonModalParams extends CommonModalCallbacks {
|
||||
/** 弹窗标题 */
|
||||
title?: string;
|
||||
/** 弹窗内容 */
|
||||
content?: string;
|
||||
/** 主按钮文案 */
|
||||
buttonHint?: string;
|
||||
/** 主按钮文案别名,方便业务侧按 buttonText 调用 */
|
||||
buttonText?: string;
|
||||
/** 点击关闭按钮后是否自动关闭弹窗,默认 true */
|
||||
closeOnClose?: boolean;
|
||||
/** 点击主按钮后是否自动关闭弹窗,默认 true */
|
||||
closeOnConfirm?: boolean;
|
||||
/** 关闭时是否销毁节点,默认 true */
|
||||
destroyOnClose?: boolean;
|
||||
/** 弹窗层级,默认 CommonModal.MODAL_Z_INDEX */
|
||||
zIndex?: number;
|
||||
}
|
||||
|
||||
@ccclass('CommonModal')
|
||||
export class CommonModal extends BaseModal {
|
||||
public static readonly MODAL_Z_INDEX = 999;
|
||||
|
||||
private static readonly DEFAULT_TITLE = '温馨提示';
|
||||
private static readonly DEFAULT_CONTENT = '';
|
||||
private static readonly DEFAULT_BUTTON_HINT = '确定';
|
||||
|
||||
@property({ type: Label, tooltip: '标题文本' })
|
||||
titleLabel: Label | null = null;
|
||||
|
||||
@property({ type: Label, tooltip: '内容文本' })
|
||||
contentLabel: Label | null = null;
|
||||
|
||||
@property({ type: Label, tooltip: '主按钮文本' })
|
||||
buttonHintLabel: Label | null = null;
|
||||
|
||||
@property({ type: Node, tooltip: '关闭按钮节点' })
|
||||
closeBtn: Node | null = null;
|
||||
|
||||
@property({ type: Node, tooltip: '主按钮节点' })
|
||||
buttonHint: Node | null = null;
|
||||
|
||||
private _title: string = CommonModal.DEFAULT_TITLE;
|
||||
private _content: string = CommonModal.DEFAULT_CONTENT;
|
||||
private _buttonHintText: string = CommonModal.DEFAULT_BUTTON_HINT;
|
||||
private _callbacks: CommonModalCallbacks = {};
|
||||
private _closeOnClose: boolean = true;
|
||||
private _closeOnConfirm: boolean = true;
|
||||
private _destroyOnClose: boolean = true;
|
||||
private _screenSize: Size | null = null;
|
||||
private _loaded: boolean = false;
|
||||
|
||||
/**
|
||||
* 直接弹出通用弹窗。
|
||||
*
|
||||
* 使用示例:
|
||||
* CommonModal.show(this.commonModalPrefab, {
|
||||
* title: '提示',
|
||||
* content: '是否继续?',
|
||||
* buttonHint: '继续',
|
||||
* onConfirm: () => this.startGame()
|
||||
* });
|
||||
*/
|
||||
static show(prefab: Prefab, params: CommonModalParams = {}, parent?: Node): CommonModal | null {
|
||||
const container = parent ?? ViewManager.instance.getContainer();
|
||||
if (!container) {
|
||||
error('[CommonModal] 缺少弹窗挂载节点');
|
||||
return null;
|
||||
}
|
||||
|
||||
const modalNode = instantiate(prefab);
|
||||
const modal = modalNode.getComponent(CommonModal);
|
||||
if (!modal) {
|
||||
error('[CommonModal] 预制体缺少 CommonModal 组件');
|
||||
modalNode.destroy();
|
||||
return null;
|
||||
}
|
||||
|
||||
modal.setParams(params);
|
||||
modalNode.setPosition(Vec3.ZERO);
|
||||
modalNode.setSiblingIndex(params.zIndex ?? CommonModal.MODAL_Z_INDEX);
|
||||
container.addChild(modalNode);
|
||||
modal.onViewLoad();
|
||||
modal._doShow();
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
protected start(): void {
|
||||
this.onViewLoad();
|
||||
|
||||
if (!this.isShowing) {
|
||||
this._doShow();
|
||||
}
|
||||
}
|
||||
|
||||
setParams(params: CommonModalParams = {}): void {
|
||||
super.setParams(params);
|
||||
this.setConfig(params);
|
||||
}
|
||||
|
||||
setConfig(params: CommonModalParams = {}): void {
|
||||
this._title = params.title ?? CommonModal.DEFAULT_TITLE;
|
||||
this._content = params.content ?? CommonModal.DEFAULT_CONTENT;
|
||||
this._buttonHintText = params.buttonHint ?? params.buttonText ?? CommonModal.DEFAULT_BUTTON_HINT;
|
||||
this._callbacks = {
|
||||
onClose: params.onClose,
|
||||
onConfirm: params.onConfirm
|
||||
};
|
||||
this._closeOnClose = params.closeOnClose ?? true;
|
||||
this._closeOnConfirm = params.closeOnConfirm ?? true;
|
||||
this._destroyOnClose = params.destroyOnClose ?? true;
|
||||
this._applyContent();
|
||||
}
|
||||
|
||||
setTitle(title: string): void {
|
||||
this._title = title;
|
||||
this._applyContent();
|
||||
}
|
||||
|
||||
setContent(content: string): void {
|
||||
this._content = content;
|
||||
this._applyContent();
|
||||
}
|
||||
|
||||
setButtonHint(buttonHint: string): void {
|
||||
this._buttonHintText = buttonHint;
|
||||
this._applyContent();
|
||||
}
|
||||
|
||||
setCallbacks(callbacks: CommonModalCallbacks): void {
|
||||
this._callbacks = callbacks;
|
||||
}
|
||||
|
||||
close(destroy: boolean = this._destroyOnClose): void {
|
||||
if (!this.node?.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isShowing) {
|
||||
this._doHide();
|
||||
}
|
||||
|
||||
if (destroy) {
|
||||
this.node.destroy();
|
||||
} else {
|
||||
this.node.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
onViewLoad(): void {
|
||||
if (this._loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._loaded = true;
|
||||
this._resolveNodes();
|
||||
this._bindButtonEvents();
|
||||
this._applyContent();
|
||||
this._updateWidget();
|
||||
}
|
||||
|
||||
onViewShow(): void {
|
||||
this._resolveNodes();
|
||||
this._applyContent();
|
||||
this._updateWidget();
|
||||
super.onViewShow();
|
||||
}
|
||||
|
||||
onViewDestroy(): void {
|
||||
this._unbindButtonEvents();
|
||||
this._callbacks = {};
|
||||
}
|
||||
|
||||
private _resolveNodes(): void {
|
||||
const panelNode = this._findNode('dialogPanel');
|
||||
|
||||
this.backdropNode = this.backdropNode ?? this._findNode('BgMask');
|
||||
this.animationNodes = this.animationNodes.length > 0 ? this.animationNodes : (panelNode ? [panelNode] : []);
|
||||
|
||||
this.closeBtn = this.closeBtn ?? this._findNode('dialogPanel/closeBtn');
|
||||
this.buttonHint = this.buttonHint ?? this._findNode('dialogPanel/ButtonHint');
|
||||
this.titleLabel = this.titleLabel ?? this._findNode('dialogPanel/Title')?.getComponent(Label) ?? null;
|
||||
this.contentLabel = this.contentLabel ?? this._findNode('dialogPanel/Content')?.getComponent(Label) ?? null;
|
||||
this.buttonHintLabel = this.buttonHintLabel
|
||||
?? this._findNode('dialogPanel/ButtonHint/Label')?.getComponent(Label)
|
||||
?? null;
|
||||
}
|
||||
|
||||
private _applyContent(): void {
|
||||
this._resolveNodes();
|
||||
|
||||
if (this.titleLabel) {
|
||||
this.titleLabel.string = this._title;
|
||||
}
|
||||
|
||||
if (this.contentLabel) {
|
||||
this.contentLabel.string = this._content;
|
||||
}
|
||||
|
||||
if (this.buttonHintLabel) {
|
||||
this.buttonHintLabel.string = this._buttonHintText;
|
||||
}
|
||||
}
|
||||
|
||||
private _updateWidget(): void {
|
||||
if (!this._screenSize) {
|
||||
this._screenSize = view.getVisibleSize();
|
||||
}
|
||||
|
||||
const uiTransform = this.node.getComponent(UITransform);
|
||||
if (uiTransform) {
|
||||
uiTransform.setContentSize(this._screenSize.width, this._screenSize.height);
|
||||
}
|
||||
}
|
||||
|
||||
private _bindButtonEvents(): void {
|
||||
this._unbindButtonEvents();
|
||||
|
||||
if (this.closeBtn) {
|
||||
this.closeBtn.on(Node.EventType.TOUCH_END, this._onCloseClick, this);
|
||||
}
|
||||
|
||||
if (this.buttonHint) {
|
||||
this.buttonHint.on(Node.EventType.TOUCH_END, this._onConfirmClick, this);
|
||||
}
|
||||
}
|
||||
|
||||
private _unbindButtonEvents(): void {
|
||||
if (this.closeBtn?.isValid) {
|
||||
this.closeBtn.off(Node.EventType.TOUCH_END, this._onCloseClick, this);
|
||||
}
|
||||
|
||||
if (this.buttonHint?.isValid) {
|
||||
this.buttonHint.off(Node.EventType.TOUCH_END, this._onConfirmClick, this);
|
||||
}
|
||||
}
|
||||
|
||||
private _onCloseClick(): void {
|
||||
const shouldContinue = this._callbacks.onClose?.();
|
||||
if (shouldContinue !== false && this._closeOnClose) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
private _onConfirmClick(): void {
|
||||
const shouldContinue = this._callbacks.onConfirm?.();
|
||||
if (shouldContinue !== false && this._closeOnConfirm) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
private _findNode(path: string): Node | null {
|
||||
const names = path.split('/').filter(Boolean);
|
||||
let current: Node | null = this.node;
|
||||
|
||||
for (const name of names) {
|
||||
current = current?.getChildByName(name) ?? null;
|
||||
if (!current) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user