From b8da554530dcb1a01a20ea767d6c8378c7a2415a Mon Sep 17 00:00:00 2001 From: richarjiang Date: Wed, 13 May 2026 08:28:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=88=86=E4=BA=AB?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E6=8C=89=E9=92=AE=E6=96=87=E6=A1=88=E6=94=AF?= =?UTF-8?q?=E6=8C=81=EF=BC=8C=E4=BC=98=E5=8C=96=E9=80=9A=E5=85=B3=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- assets/prefabs/PageLevel.ts | 110 +++++++++++++++++++++++++++++++++++- assets/prefabs/PassModal.ts | 22 ++++++++ 3 files changed, 130 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 358e604..915aa3f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,7 @@ Git 历史采用 Conventional Commits,且摘要多为中文,例如 `feat: # Memory Context -# $CMEM mp-xieyingeng 2026-05-12 9:37pm GMT+8 +# $CMEM mp-xieyingeng 2026-05-13 8:06am GMT+8 Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖️decision Format: ID TIME TYPE TITLE diff --git a/assets/prefabs/PageLevel.ts b/assets/prefabs/PageLevel.ts index 7fd9c73..195f35f 100644 --- a/assets/prefabs/PageLevel.ts +++ b/assets/prefabs/PageLevel.ts @@ -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; @@ -302,6 +311,9 @@ export class PageLevel extends BaseView { /** 是否正在提交分享挑战结果 */ private _isSubmittingShareResult: boolean = false; + /** 本场分享挑战是否已经尝试拉取过用户头像昵称,避免结算流程重复弹窗 */ + private _hasRequestedShareUserInfo: boolean = false; + /** * 页面首次加载时调用 */ @@ -318,6 +330,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 +507,7 @@ export class PageLevel extends BaseView { // 设置关卡标题 this.updateTitleLevelLabel(); this.updatePkLevelProgressLabel(); + this.updatePkNextLevelButtonText(); // 隐藏包袱答案,通关后再按 punchline 展示 this.hidePunchline(); @@ -1115,6 +1129,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 +1175,7 @@ export class PageLevel extends BaseView { this.updateTitleLevelLabel(); this.updatePkLevelProgressLabel(); + this.updatePkNextLevelButtonText(); } private _refreshTipsModeUI(): void { @@ -1885,11 +1915,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 +2170,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 +2258,67 @@ export class PageLevel extends BaseView { return totalLevels > 0 && this._shareLevelIndex >= totalLevels - 1; } + private async _ensureShareParticipantUserInfo(): Promise { + 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 { + const userId = AuthManager.instance.userId; + if (!userId) { + console.warn('[PageLevel] 用户未登录,跳过上传头像昵称'); + return; + } + + try { + const response = await HttpUtil.post>( + 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 { if (this._isSubmittingShareResult) { return; @@ -2238,6 +2341,7 @@ export class PageLevel extends BaseView { this._isSubmittingShareResult = true; ToastManager.show('正在结算挑战...'); + await this._ensureShareParticipantUserInfo(); const result = await ShareManager.instance.submitShareChallenge(payload); this._isSubmittingShareResult = false; diff --git a/assets/prefabs/PassModal.ts b/assets/prefabs/PassModal.ts index 63cd106..e6e3e06 100644 --- a/assets/prefabs/PassModal.ts +++ b/assets/prefabs/PassModal.ts @@ -21,6 +21,8 @@ export interface PassModalTitleInfo { interface PassModalParams { levelIndex?: number; + /** 下一步按钮文案,不传时使用 prefab 默认文案 */ + nextButtonText?: string; titleInfo?: PassModalTitleInfo; /** * 通关前的称号信息。传入后,本次显示会把进度条从该起点动画到 titleInfo 的终点; @@ -93,6 +95,9 @@ export class PassModal extends BaseModal { /** 进度动画所绑定的对象,用于 Tween.stopAllByTarget */ private readonly _progressTweenTarget: { progress: number } = { progress: 0 }; + /** 下一步按钮文案,为 null 时保留 prefab 默认值 */ + private _nextButtonText: string | null = null; + /** 进度游标 0% 时的本地 X 坐标,使用 prefab 当前摆放位置作为起点 */ private _progressAnchorStartX: number | null = null; @@ -107,6 +112,11 @@ export class PassModal extends BaseModal { if (params?.titleInfo) { this.setTitleInfo(params.titleInfo); } + + if (params && 'nextButtonText' in params) { + this._nextButtonText = params.nextButtonText ?? null; + this._applyNextButtonText(); + } } /** @@ -144,6 +154,7 @@ export class PassModal extends BaseModal { super.onViewShow(); this._updateWidget(); this._refreshTitleView(); + this._applyNextButtonText(); this._playSuccessSound(); this._playProgressAnimation(); } @@ -234,6 +245,17 @@ export class PassModal extends BaseModal { } } + private _applyNextButtonText(): void { + if (!this.nextLevelButton || this._nextButtonText === null) { + return; + } + + const label = this.nextLevelButton.getChildByName('Label')?.getComponent(Label); + if (label) { + label.string = this._nextButtonText; + } + } + private _applyProgressText(text: string | undefined): void { if (this.progressLabel && text !== undefined) { this.progressLabel.string = text;