@@ -2,17 +2,20 @@ import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, Sp
import { BaseView } from 'db://assets/scripts/core/BaseView' ;
import { ViewManager } from 'db://assets/scripts/core/ViewManager' ;
import { StaminaManager } from 'db://assets/scripts/utils/StaminaManager' ;
import { WxSDK } from 'db://assets/scripts/utils/WxSDK' ;
import { WxSDK , getUserProfile , type WxUserInfo } from 'db://assets/scripts/utils/WxSDK' ;
import { LevelDataManager } from 'db://assets/scripts/utils/LevelDataManager' ;
import { AuthManager } from 'db://assets/scripts/utils/AuthManager' ;
import { RuntimeLevelConfig } from 'db://assets/scripts/types/LevelTypes' ;
import { ToastManager } from 'db://assets/scripts/utils/ToastManager' ;
import { ShareManager } from 'db://assets/scripts/utils/ShareManager' ;
import { StorageManager } from 'db://assets/scripts/utils/StorageManager' ;
import { HttpUtil } from 'db://assets/scripts/utils/HttpUtil' ;
import { API_ENDPOINTS , API_TIMEOUT } from 'db://assets/scripts/config/ApiConfig' ;
import { PassModal } from 'db://assets/prefabs/PassModal' ;
import { WrongModal } from 'db://assets/prefabs/WrongModal' ;
import { TimeoutModal } from 'db://assets/prefabs/TimeoutModal' ;
import { CommonModal } from 'db://assets/prefabs/CommonModal' ;
import { StaminaInfo , NextLevelData , SubmitShareLevel } from 'db://assets/scripts/types/ApiTypes' ;
import { ApiEnvelope , StaminaInfo, NextLevelData , SubmitShareLevel } from 'db://assets/scripts/types/ApiTypes' ;
import { AchievementTitleManager } from 'db://assets/scripts/utils/AchievementTitleManager' ;
import { applyRoundedCorner } from 'db://assets/scripts/utils/roundedMaterial.utils' ;
const { ccclass , property } = _decorator ;
@@ -72,6 +75,12 @@ export class PageLevel extends BaseView {
/** 分享模式只展示提示 1 时,固定放回 TipsLayout 顶部,避免 Layout 把它排到底部被下一题按钮遮挡 */
private static readonly SHARE_MODE_TIP1_Y = 120 ;
/** 分享模式底部按钮默认文案 */
private static readonly SHARE_NEXT_BUTTON_TEXT = '下一题' ;
/** 分享模式最后一题提交文案 */
private static readonly SHARE_SUBMIT_BUTTON_TEXT = '提交答案' ;
// ========== 节点引用 ==========
@property ( Node )
inputLayout : Node | null = null ;
@@ -225,6 +234,9 @@ export class PageLevel extends BaseView {
/** 最近一次自动提交的答案,避免填满后重复提交同一内容 */
private _lastAutoSubmittedAnswer : string = '' ;
/** 当前正在编辑的输入格索引 */
private _editingInputIndex : number = - 1 ;
/** 倒计时剩余秒数 */
private _countdown : number = 60 ;
@@ -302,6 +314,9 @@ export class PageLevel extends BaseView {
/** 是否正在提交分享挑战结果 */
private _isSubmittingShareResult : boolean = false ;
/** 本场分享挑战是否已经尝试拉取过用户头像昵称,避免结算流程重复弹窗 */
private _hasRequestedShareUserInfo : boolean = false ;
/**
* 页面首次加载时调用
*/
@@ -318,6 +333,7 @@ export class PageLevel extends BaseView {
this . _shareLevelIndex = 0 ;
this . _shareSubmissions . clear ( ) ;
this . _isSubmittingShareResult = false ;
this . _hasRequestedShareUserInfo = false ;
console . log ( '[PageLevel] 进入分享挑战模式' ) ;
} else {
// 从 AuthManager 获取首关数据(由 PageLoading → game-data 提供)
@@ -494,6 +510,7 @@ export class PageLevel extends BaseView {
// 设置关卡标题
this . updateTitleLevelLabel ( ) ;
this . updatePkLevelProgressLabel ( ) ;
this . updatePkNextLevelButtonText ( ) ;
// 隐藏包袱答案,通关后再按 punchline 展示
this . hidePunchline ( ) ;
@@ -645,28 +662,13 @@ export class PageLevel extends BaseView {
// ========== EditBox 事件回调 ==========
/**
* 输入框开始编辑时,把当前所有格子的内容合并到当前输入框里
*/
private onInputEditingBegan ( editBox : EditBox ) : void {
if ( this . _isSyncingInputText ) return ;
const inputIndex = this . _inputNodes . findIndex ( node = > node === editBox . node ) ;
if ( inputIndex < 0 ) return ;
const answer = this . getAnswer ( ) ;
this . _isSyncingInputText = true ;
try {
for ( let i = 0 ; i < this . _inputNodes . length ; i ++ ) {
const itemEditBox = this . _inputNodes [ i ] . getComponent ( EditBox ) ;
if ( itemEditBox ) {
itemEditBox . string = i === inputIndex ? answer : '' ;
}
}
} finally {
this . _isSyncingInputText = false ;
}
this . _editingInputIndex = inputIndex ;
}
/**
@@ -682,22 +684,48 @@ export class PageLevel extends BaseView {
private onInputEditingEnded ( editBox : EditBox ) : void {
if ( this . _isSyncingInputText ) return ;
const inputIndex = this . _inputNodes . findIndex ( node = > node === editBox . node ) ;
const inputIndex = this . _editingInputIndex >= 0
? this . _editingInputIndex
: this._inputNodes.findIndex ( node = > node === editBox . node ) ;
if ( inputIndex < 0 ) return ;
this . distribute InputText( editBox . string ) ;
this . apply InputTextToBlocks ( editBox . string , inputIndex );
this . _editingInputIndex = - 1 ;
this . tryAutoSubmitAnswer ( ) ;
}
private distribute InputText( text : string ) : void {
const chars = Array . from ( text ) ;
private apply InputTextToBlocks ( text : string , inputIndex : number ): void {
const chars = Array . from ( text . trim ( ) );
if ( chars . length <= 1 ) {
this . setInputBlockText ( inputIndex , chars [ 0 ] ? ? '' ) ;
return ;
}
this . distributeInputText ( text , inputIndex ) ;
}
private setInputBlockText ( index : number , text : string ) : void {
const editBox = this . _inputNodes [ index ] ? . getComponent ( EditBox ) ;
if ( ! editBox ) return ;
this . _isSyncingInputText = true ;
try {
editBox . string = Array . from ( text . trim ( ) ) [ 0 ] ? ? '' ;
} finally {
this . _isSyncingInputText = false ;
}
}
private distributeInputText ( text : string , startIndex : number = 0 ) : void {
const chars = Array . from ( text . trim ( ) ) ;
const safeStartIndex = Math . max ( 0 , Math . min ( startIndex , this . _inputNodes . length - 1 ) ) ;
this . _isSyncingInputText = true ;
try {
for ( let i = 0 ; i < this . _inputNodes . length ; i ++ ) {
for ( let i = safeStartIndex ; i < this . _inputNodes . length ; i ++ ) {
const editBox = this . _inputNodes [ i ] . getComponent ( EditBox ) ;
if ( editBox ) {
editBox . string = chars [ i ] ? ? '' ;
editBox . string = chars [ i - safeStartIndex ] ? ? '' ;
}
}
} finally {
@@ -738,6 +766,10 @@ export class PageLevel extends BaseView {
this . onSubmitAnswer ( ) ;
}
private normalizeAnswerForCompare ( answer : string ) : string {
return answer . trim ( ) . toLocaleLowerCase ( ) ;
}
// ========== IconSetting 按钮相关 ==========
/**
@@ -1115,6 +1147,21 @@ export class PageLevel extends BaseView {
: ` ${ currentIndex } ` ;
}
private updatePkNextLevelButtonText ( ) : void {
if ( ! this . pkNextLevelButton ) {
return ;
}
const label = this . pkNextLevelButton . getChildByName ( 'Label' ) ? . getComponent ( Label ) ;
if ( ! label ) {
return ;
}
label . string = this . _isFinalShareLevel ( )
? PageLevel.SHARE_SUBMIT_BUTTON_TEXT
: PageLevel.SHARE_NEXT_BUTTON_TEXT ;
}
private _refreshModeUI ( ) : void {
const isPkMode = this . _isShareMode ;
@@ -1146,6 +1193,7 @@ export class PageLevel extends BaseView {
this . updateTitleLevelLabel ( ) ;
this . updatePkLevelProgressLabel ( ) ;
this . updatePkNextLevelButtonText ( ) ;
}
private _refreshTipsModeUI ( ) : void {
@@ -1711,7 +1759,7 @@ export class PageLevel extends BaseView {
const userAnswer = this . getAnswer ( ) ;
console . log ( ` [PageLevel] 提交答案: ${ userAnswer } , 正确答案: ${ this . _currentConfig . answer } ` ) ;
if ( userAnswer === this . _currentConfig . answer ) {
if ( this . normalizeAnswerForCompare ( userAnswer ) === this . normalizeAnswerForCompare ( this . _currentConfig. answer ) ) {
if ( this . _isShareMode ) {
this . _recordCurrentShareSubmission ( userAnswer ) ;
}
@@ -1885,11 +1933,20 @@ export class PageLevel extends BaseView {
passModal . setParams ( {
levelIndex : this.getDisplayLevelNumber ( ) ,
nextButtonText : this._isShareMode && this . _isFinalShareLevel ( )
? PageLevel.SHARE_SUBMIT_BUTTON_TEXT
: undefined ,
titleInfo ,
previousTitleInfo
} ) ;
passModal . setCallbacks ( {
onNextLevel : ( ) = > {
if ( this . _isShareMode ) {
this . _closePassModal ( ) ;
void this . goToNextLevel ( ) ;
return ;
}
this . _showShareNextConfirmModal ( ( ) = > {
this . _closePassModal ( ) ;
void this . goToNextLevel ( ) ;
@@ -2131,9 +2188,12 @@ export class PageLevel extends BaseView {
return ;
}
const isFinalShareLevel = this . _isFinalShareLevel ( ) ;
const modal = CommonModal . show ( this . commonModalPrefab , {
title : '提示' ,
content : '还有时间\n确认进入下一题吗? ' ,
content : isFinalShareLevel
? '确认提交挑战答案吗?'
: '还有时间\n确认进入下一题吗? ' ,
buttonConfirm : '确认' ,
buttonCancel : '再想想' ,
zIndex : CommonModal.MODAL_Z_INDEX + 1 ,
@@ -2216,6 +2276,67 @@ export class PageLevel extends BaseView {
return totalLevels > 0 && this . _shareLevelIndex >= totalLevels - 1 ;
}
private async _ensureShareParticipantUserInfo ( ) : Promise < void > {
if ( ! this . _isShareMode || this . _hasRequestedShareUserInfo ) {
return ;
}
this . _hasRequestedShareUserInfo = true ;
const cachedUserInfo = StorageManager . getUserInfo ( ) ;
if ( cachedUserInfo && this . _hasUsableUserInfo ( cachedUserInfo ) ) {
await this . _uploadShareParticipantUserInfo ( cachedUserInfo ) ;
return ;
}
if ( ! WxSDK . isWechat ( ) ) {
console . log ( '[PageLevel] 非微信环境,跳过获取用户头像昵称' ) ;
return ;
}
try {
const userInfo = await getUserProfile ( ) ;
StorageManager . setUserInfo ( userInfo ) ;
await this . _uploadShareParticipantUserInfo ( userInfo ) ;
} catch ( err ) {
console . warn ( '[PageLevel] 获取用户头像昵称失败,继续提交挑战:' , err ) ;
ToastManager . show ( '未授权头像昵称,将使用默认资料提交' ) ;
}
}
private _hasUsableUserInfo ( userInfo : WxUserInfo ) : boolean {
return ! ! userInfo . avatarUrl ? . trim ( )
&& ! ! userInfo . nickName ? . trim ( )
&& userInfo . nickName !== '微信用户' ;
}
private async _uploadShareParticipantUserInfo ( userInfo : WxUserInfo ) : Promise < void > {
const userId = AuthManager . instance . userId ;
if ( ! userId ) {
console . warn ( '[PageLevel] 用户未登录,跳过上传头像昵称' ) ;
return ;
}
try {
const response = await HttpUtil . post < ApiEnvelope < unknown > > (
API_ENDPOINTS . USER_INFO ,
{
userId ,
avatarUrl : userInfo.avatarUrl ,
nickName : userInfo.nickName ,
} ,
API_TIMEOUT . DEFAULT ,
) ;
if ( response . success ) {
console . log ( '[PageLevel] 分享挑战用户头像昵称上传成功' ) ;
} else {
console . warn ( '[PageLevel] 分享挑战用户头像昵称上传失败:' , response . message ) ;
}
} catch ( err ) {
console . warn ( '[PageLevel] 分享挑战用户头像昵称上传异常:' , err ) ;
}
}
private async _showShareEndPage ( ) : Promise < void > {
if ( this . _isSubmittingShareResult ) {
return ;
@@ -2238,6 +2359,7 @@ export class PageLevel extends BaseView {
this . _isSubmittingShareResult = true ;
ToastManager . show ( '正在结算挑战...' ) ;
await this . _ensureShareParticipantUserInfo ( ) ;
const result = await ShareManager . instance . submitShareChallenge ( payload ) ;
this . _isSubmittingShareResult = false ;