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; } }