feat: 完善分享模式
This commit is contained in:
@@ -40,7 +40,7 @@ Git 历史采用 Conventional Commits,且摘要多为中文,例如 `feat:
|
||||
<claude-mem-context>
|
||||
# Memory Context
|
||||
|
||||
# $CMEM mp-xieyingeng 2026-05-03 10:36pm GMT+8
|
||||
# $CMEM mp-xieyingeng 2026-05-10 9:35pm GMT+8
|
||||
|
||||
Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖️decision
|
||||
Format: ID TIME TYPE TITLE
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
|
||||
import { PassModal } from 'db://assets/prefabs/PassModal';
|
||||
import { WrongModal } from 'db://assets/prefabs/WrongModal';
|
||||
import { TimeoutModal } from 'db://assets/prefabs/TimeoutModal';
|
||||
import { StaminaInfo, NextLevelData } from 'db://assets/scripts/types/ApiTypes';
|
||||
import { 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;
|
||||
@@ -286,6 +286,12 @@ export class PageLevel extends BaseView {
|
||||
/** 分享模式下的关卡索引(仅分享模式使用) */
|
||||
private _shareLevelIndex: number = 0;
|
||||
|
||||
/** 分享模式下每关最终提交内容,等整场结束后一次性提交 */
|
||||
private _shareSubmissions: Map<string, SubmitShareLevel> = new Map();
|
||||
|
||||
/** 是否正在提交分享挑战结果 */
|
||||
private _isSubmittingShareResult: boolean = false;
|
||||
|
||||
/**
|
||||
* 页面首次加载时调用
|
||||
*/
|
||||
@@ -300,6 +306,8 @@ export class PageLevel extends BaseView {
|
||||
|
||||
if (this._isShareMode) {
|
||||
this._shareLevelIndex = 0;
|
||||
this._shareSubmissions.clear();
|
||||
this._isSubmittingShareResult = false;
|
||||
console.log('[PageLevel] 进入分享挑战模式');
|
||||
} else {
|
||||
// 从 AuthManager 获取首关数据(由 PageLoading → game-data 提供)
|
||||
@@ -703,6 +711,7 @@ export class PageLevel extends BaseView {
|
||||
|
||||
private tryAutoSubmitAnswer(): void {
|
||||
if (!this._currentConfig || this._isTransitioning) return;
|
||||
if (this._isShareMode) return;
|
||||
|
||||
const values = this.getInputValues();
|
||||
const isFilled = values.length === Array.from(this._currentConfig.answer ?? '').length && values.every(value => value.length === 1);
|
||||
@@ -913,8 +922,15 @@ export class PageLevel extends BaseView {
|
||||
if (!this._isShareMode) {
|
||||
return;
|
||||
}
|
||||
if (this._isSubmittingShareResult) {
|
||||
return;
|
||||
}
|
||||
if (this._isTransitioning) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.playClickSound();
|
||||
this._recordCurrentShareSubmission();
|
||||
void this.goToNextLevel();
|
||||
}
|
||||
|
||||
@@ -1639,6 +1655,11 @@ export class PageLevel extends BaseView {
|
||||
const userAnswer = this.getAnswer();
|
||||
console.log(`[PageLevel] 提交答案: ${userAnswer}, 正确答案: ${this._currentConfig.answer}`);
|
||||
|
||||
if (this._isShareMode) {
|
||||
void this._submitShareAnswerAndContinue(userAnswer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userAnswer === this._currentConfig.answer) {
|
||||
// 答案正确,只播放成功音效(不播放点击音效,避免重合)
|
||||
this.showSuccess();
|
||||
@@ -1676,11 +1697,15 @@ export class PageLevel extends BaseView {
|
||||
|
||||
this.reportLevelCompleted(levelId, timeSpent);
|
||||
|
||||
// 不论是否有谐音梗,都停留固定时间再弹出通关弹窗,保证节奏一致
|
||||
// 不论是否有谐音梗,都停留固定时间,保证玩家能看到答案反馈
|
||||
await this.delay(PageLevel.PASS_MODAL_DELAY_MS);
|
||||
|
||||
if (this._isFinalShareLevel()) {
|
||||
this._showShareEndPage();
|
||||
if (this._isShareMode) {
|
||||
if (this._isFinalShareLevel()) {
|
||||
await this._showShareEndPage();
|
||||
} else {
|
||||
await this.goToNextLevel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1688,6 +1713,24 @@ export class PageLevel extends BaseView {
|
||||
this._showPassModal();
|
||||
}
|
||||
|
||||
private async _submitShareAnswerAndContinue(userAnswer: string): Promise<void> {
|
||||
if (!this._currentConfig || this._isTransitioning || this._isSubmittingShareResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isTransitioning = true;
|
||||
this.stopCountdown();
|
||||
this.hidePunchline();
|
||||
this._recordCurrentShareSubmission(userAnswer);
|
||||
|
||||
if (this._isFinalShareLevel()) {
|
||||
await this._showShareEndPage();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.goToNextLevel();
|
||||
}
|
||||
|
||||
private getValidPunchline(punchline: string | null): string | null {
|
||||
if (!punchline?.trim()) {
|
||||
return null;
|
||||
@@ -1730,8 +1773,43 @@ export class PageLevel extends BaseView {
|
||||
|
||||
this._passModalCompletedLevelCount = null;
|
||||
this._passModalPreviousCompletedLevelCount = null;
|
||||
// fire-and-forget: errors are logged inside reportLevelProgress
|
||||
void ShareManager.instance.reportLevelProgress(levelId, true, timeSpent);
|
||||
this._recordCurrentShareSubmission(undefined, timeSpent);
|
||||
}
|
||||
|
||||
private _recordCurrentShareSubmission(answer?: string, timeSpent?: number): void {
|
||||
if (!this._isShareMode || !this._currentConfig?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elapsedSeconds = Math.max(0, Math.round((Date.now() - this._levelStartTime) / 1000));
|
||||
const finalTimeSpent = Math.max(0, Math.round(timeSpent ?? elapsedSeconds));
|
||||
const finalAnswer = answer ?? this.getAnswer();
|
||||
|
||||
this._shareSubmissions.set(this._currentConfig.id, {
|
||||
levelId: this._currentConfig.id,
|
||||
answer: finalAnswer,
|
||||
timeSpent: finalTimeSpent,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[PageLevel] 记录分享挑战提交: ${this._currentConfig.id}, answer="${finalAnswer}", timeSpent=${finalTimeSpent}`,
|
||||
);
|
||||
}
|
||||
|
||||
private _buildShareSubmissionPayload(): SubmitShareLevel[] {
|
||||
const levelIds = ShareManager.instance.getShareLevelIds();
|
||||
const ids = levelIds.length > 0
|
||||
? levelIds
|
||||
: [...this._shareSubmissions.keys()];
|
||||
|
||||
return ids.map(levelId => {
|
||||
const submission = this._shareSubmissions.get(levelId);
|
||||
return submission ?? {
|
||||
levelId,
|
||||
answer: '',
|
||||
timeSpent: 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private delay(ms: number): Promise<void> {
|
||||
@@ -2021,7 +2099,7 @@ export class PageLevel extends BaseView {
|
||||
if (this._shareLevelIndex >= totalLevels) {
|
||||
console.log('[PageLevel] 分享关卡全部完成');
|
||||
this.stopCountdown();
|
||||
this._showShareEndPage();
|
||||
await this._showShareEndPage();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2056,9 +2134,41 @@ export class PageLevel extends BaseView {
|
||||
return totalLevels > 0 && this._shareLevelIndex >= totalLevels - 1;
|
||||
}
|
||||
|
||||
private _showShareEndPage(): void {
|
||||
private async _showShareEndPage(): Promise<void> {
|
||||
if (this._isSubmittingShareResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[PageLevel] 分享关卡全部完成,进入 PK 结算页');
|
||||
this.stopCountdown();
|
||||
ViewManager.instance.replace('PagePKEnd');
|
||||
|
||||
if (this._currentConfig?.id && !this._shareSubmissions.has(this._currentConfig.id)) {
|
||||
this._recordCurrentShareSubmission();
|
||||
}
|
||||
|
||||
const payload = this._buildShareSubmissionPayload();
|
||||
if (payload.length === 0) {
|
||||
ToastManager.show('挑战数据异常,请重新进入');
|
||||
this._isTransitioning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._isSubmittingShareResult = true;
|
||||
ToastManager.show('正在结算挑战...');
|
||||
|
||||
const result = await ShareManager.instance.submitShareChallenge(payload);
|
||||
this._isSubmittingShareResult = false;
|
||||
|
||||
if (!result) {
|
||||
ToastManager.show('提交挑战结果失败,请稍后重试');
|
||||
this._isTransitioning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ViewManager.instance.replace('PagePKEnd', {
|
||||
params: {
|
||||
result,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,14 +40,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 154
|
||||
"__id__": 160
|
||||
},
|
||||
{
|
||||
"__id__": 156
|
||||
"__id__": 162
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 158
|
||||
"__id__": 164
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -2258,20 +2258,20 @@
|
||||
"__id__": 93
|
||||
},
|
||||
{
|
||||
"__id__": 141
|
||||
"__id__": 147
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 149
|
||||
"__id__": 155
|
||||
},
|
||||
{
|
||||
"__id__": 151
|
||||
"__id__": 157
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 153
|
||||
"__id__": 159
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -2318,17 +2318,17 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 134
|
||||
"__id__": 140
|
||||
},
|
||||
{
|
||||
"__id__": 136
|
||||
"__id__": 142
|
||||
},
|
||||
{
|
||||
"__id__": 138
|
||||
"__id__": 144
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 140
|
||||
"__id__": 146
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -2375,11 +2375,11 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 131
|
||||
"__id__": 137
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 133
|
||||
"__id__": 139
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -2424,16 +2424,19 @@
|
||||
},
|
||||
{
|
||||
"__id__": 108
|
||||
},
|
||||
{
|
||||
"__id__": 128
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 128
|
||||
"__id__": 134
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 130
|
||||
"__id__": 136
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -3240,6 +3243,168 @@
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "AnswerLabel",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 95
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 129
|
||||
},
|
||||
{
|
||||
"__id__": 131
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 133
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 163.106,
|
||||
"y": -13.134,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.712,
|
||||
"y": 0.712,
|
||||
"z": 0.712
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 128
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 130
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 170,
|
||||
"height": 136
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "33SRp0SWVHl4kQ1pZ7BQhx"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 128
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 132
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_string": "答案",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 80,
|
||||
"_fontSize": 80,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 100,
|
||||
"_overflow": 0,
|
||||
"_enableWrapText": true,
|
||||
"_font": {
|
||||
"__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80",
|
||||
"__expectedType__": "cc.TTFFont"
|
||||
},
|
||||
"_isSystemFontUsed": false,
|
||||
"_spacingX": 0,
|
||||
"_isItalic": false,
|
||||
"_isBold": true,
|
||||
"_isUnderline": false,
|
||||
"_underlineHeight": 2,
|
||||
"_cacheMode": 0,
|
||||
"_enableOutline": true,
|
||||
"_outlineColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 191,
|
||||
"g": 127,
|
||||
"b": 2,
|
||||
"a": 255
|
||||
},
|
||||
"_outlineWidth": 5,
|
||||
"_enableShadow": false,
|
||||
"_shadowColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 255
|
||||
},
|
||||
"_shadowOffset": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 2,
|
||||
"y": 2
|
||||
},
|
||||
"_shadowBlur": 2,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "940bZjtkVLIYX8EolxrbQ5"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "3aNOwLJv5EJa3QIvuuIt/d",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
@@ -3250,7 +3415,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 129
|
||||
"__id__": 135
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -3291,7 +3456,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 132
|
||||
"__id__": 138
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -3332,7 +3497,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 135
|
||||
"__id__": 141
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -3360,7 +3525,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 137
|
||||
"__id__": 143
|
||||
},
|
||||
"_type": 0,
|
||||
"_inverted": false,
|
||||
@@ -3382,7 +3547,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 139
|
||||
"__id__": 145
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -3443,17 +3608,17 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 142
|
||||
"__id__": 148
|
||||
},
|
||||
{
|
||||
"__id__": 144
|
||||
"__id__": 150
|
||||
},
|
||||
{
|
||||
"__id__": 146
|
||||
"__id__": 152
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 148
|
||||
"__id__": 154
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -3490,11 +3655,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 141
|
||||
"__id__": 147
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 143
|
||||
"__id__": 149
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -3518,11 +3683,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 141
|
||||
"__id__": 147
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 145
|
||||
"__id__": 151
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -3563,11 +3728,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 141
|
||||
"__id__": 147
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 147
|
||||
"__id__": 153
|
||||
},
|
||||
"_alignFlags": 40,
|
||||
"_target": null,
|
||||
@@ -3616,7 +3781,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 150
|
||||
"__id__": 156
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -3644,7 +3809,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 152
|
||||
"__id__": 158
|
||||
},
|
||||
"bounceDuration": 0.23,
|
||||
"brake": 0.75,
|
||||
@@ -3688,7 +3853,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 155
|
||||
"__id__": 161
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -3716,9 +3881,32 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 157
|
||||
"__id__": 163
|
||||
},
|
||||
"settingButton": {
|
||||
"__id__": 76
|
||||
},
|
||||
"rankLabel": {
|
||||
"__id__": 47
|
||||
},
|
||||
"rightNumberLabel": {
|
||||
"__id__": 53
|
||||
},
|
||||
"rankNumberLabel": {
|
||||
"__id__": 59
|
||||
},
|
||||
"participateNumberLabel": {
|
||||
"__id__": 65
|
||||
},
|
||||
"answerTitleLabel": {
|
||||
"__id__": 89
|
||||
},
|
||||
"answerListContent": {
|
||||
"__id__": 94
|
||||
},
|
||||
"answerItemTemplate": {
|
||||
"__id__": 95
|
||||
},
|
||||
"settingButton": null,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,25 +1,61 @@
|
||||
import { _decorator, Button, Node } from 'cc';
|
||||
import { _decorator, assetManager, Button, ImageAsset, instantiate, Label, Node, ScrollView, Sprite, SpriteFrame, Texture2D, UITransform } from 'cc';
|
||||
import { BaseView } from 'db://assets/scripts/core/BaseView';
|
||||
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
||||
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
|
||||
import { SubmitShareData, SubmittedShareLevelData } from 'db://assets/scripts/types/ApiTypes';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('PagePKEnd')
|
||||
export class PagePKEnd extends BaseView {
|
||||
private static readonly ANSWER_ITEM_TOP_PADDING = 16;
|
||||
private static readonly ANSWER_ITEM_BOTTOM_PADDING = 16;
|
||||
private static readonly ANSWER_ITEM_SPACING = 16;
|
||||
private static readonly COVER_IMAGE_WIDTH = 1299;
|
||||
private static readonly COVER_IMAGE_HEIGHT = 1004;
|
||||
|
||||
@property({ type: Node, tooltip: '返回首页按钮' })
|
||||
settingButton: Node | null = null;
|
||||
|
||||
@property({ type: Label, tooltip: '顶部排名文案,例如:获得了第1名' })
|
||||
rankLabel: Label | null = null;
|
||||
|
||||
@property({ type: Label, tooltip: '答对题数文案,例如:答对了4题' })
|
||||
rightNumberLabel: Label | null = null;
|
||||
|
||||
@property({ type: Label, tooltip: '本人排名文案,例如:您获得了第1名' })
|
||||
rankNumberLabel: Label | null = null;
|
||||
|
||||
@property({ type: Label, tooltip: '参与人数文案,例如:一共66人参与了挑战' })
|
||||
participateNumberLabel: Label | null = null;
|
||||
|
||||
@property({ type: Label, tooltip: '答案列表标题' })
|
||||
answerTitleLabel: Label | null = null;
|
||||
|
||||
@property({ type: Node, tooltip: '答案列表 content 节点' })
|
||||
answerListContent: Node | null = null;
|
||||
|
||||
@property({ type: Node, tooltip: '答案列表条目模板 AnswerItem' })
|
||||
answerItemTemplate: Node | null = null;
|
||||
|
||||
private _answerItemNodes: Node[] = [];
|
||||
private _answerButtonBindings: Array<{ node: Node; handler: () => void }> = [];
|
||||
private _renderVersion: number = 0;
|
||||
|
||||
onViewLoad(): void {
|
||||
this._resolveNodes();
|
||||
this._bindEvents();
|
||||
this._hideAnswerTemplate();
|
||||
}
|
||||
|
||||
onViewShow(): void {
|
||||
console.log('[PagePKEnd] onViewShow');
|
||||
this._resolveNodes();
|
||||
this._renderResult(this.getParams()?.result ?? null);
|
||||
}
|
||||
|
||||
onViewDestroy(): void {
|
||||
this._unbindEvents();
|
||||
this._clearAnswerItems();
|
||||
}
|
||||
|
||||
private _resolveNodes(): void {
|
||||
@@ -27,9 +63,28 @@ export class PagePKEnd extends BaseView {
|
||||
this.settingButton = this.node.getChildByName('SettingButton');
|
||||
}
|
||||
|
||||
this.rankLabel = this.rankLabel ?? this._findLabel('RankLabel');
|
||||
this.rightNumberLabel = this.rightNumberLabel ?? this._findLabel('RightNumberLabel');
|
||||
this.rankNumberLabel = this.rankNumberLabel ?? this._findLabel('RankNumberLabel');
|
||||
this.participateNumberLabel = this.participateNumberLabel ?? this._findLabel('PartipateNumberLabel');
|
||||
this.answerTitleLabel = this.answerTitleLabel ?? this._findLabel('AnswerTitle');
|
||||
|
||||
const answerList = this.node.getChildByName('AnswerList');
|
||||
const view = answerList?.getChildByName('view');
|
||||
this.answerListContent = this.answerListContent ?? view?.getChildByName('content') ?? null;
|
||||
this.answerItemTemplate = this.answerItemTemplate
|
||||
?? this.answerListContent?.getChildByName('AnswerItem')
|
||||
?? null;
|
||||
|
||||
if (!this.settingButton) {
|
||||
console.warn('[PagePKEnd] 未找到 SettingButton 节点');
|
||||
}
|
||||
if (!this.answerListContent) {
|
||||
console.warn('[PagePKEnd] 未找到 AnswerList/content 节点');
|
||||
}
|
||||
if (!this.answerItemTemplate) {
|
||||
console.warn('[PagePKEnd] 未找到 AnswerItem 模板节点');
|
||||
}
|
||||
}
|
||||
|
||||
private _bindEvents(): void {
|
||||
@@ -46,10 +101,236 @@ export class PagePKEnd extends BaseView {
|
||||
if (this.settingButton && this.settingButton.isValid) {
|
||||
this.settingButton.off(Button.EventType.CLICK, this._onHomeClick, this);
|
||||
}
|
||||
this._unbindAnswerButtons();
|
||||
}
|
||||
|
||||
private _onHomeClick(): void {
|
||||
ShareManager.instance.clearShareMode();
|
||||
ViewManager.instance.replace('PageHome');
|
||||
}
|
||||
|
||||
private _renderResult(result: SubmitShareData | null): void {
|
||||
this._renderVersion++;
|
||||
this._clearAnswerItems();
|
||||
|
||||
if (!result) {
|
||||
this._setLabel(this.rankLabel, '暂无排名');
|
||||
this._setLabel(this.rightNumberLabel, '答对了0题');
|
||||
this._setLabel(this.rankNumberLabel, '您暂未上榜');
|
||||
this._setLabel(this.participateNumberLabel, '暂无参与数据');
|
||||
this._setLabel(this.answerTitleLabel, '暂无挑战结果');
|
||||
this._hideAnswerTemplate();
|
||||
return;
|
||||
}
|
||||
|
||||
this._setLabel(this.rankLabel, `获得了第${result.rank}名`);
|
||||
this._setLabel(this.rightNumberLabel, `答对了${result.correctCount}题`);
|
||||
this._setLabel(this.rankNumberLabel, `您获得了第${result.rank}名`);
|
||||
this._setLabel(this.participateNumberLabel, `一共${result.participantCount}人参与了挑战`);
|
||||
this._setLabel(this.answerTitleLabel, `共用时${result.totalTimeSpent}s,揭晓答案吧`);
|
||||
this._renderAnswerList(result.levels ?? []);
|
||||
}
|
||||
|
||||
private _renderAnswerList(levels: SubmittedShareLevelData[]): void {
|
||||
if (!this.answerListContent || !this.answerItemTemplate) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._hideAnswerTemplate();
|
||||
const version = this._renderVersion;
|
||||
this._layoutAnswerContent(levels.length);
|
||||
|
||||
levels.forEach((level, index) => {
|
||||
const item = instantiate(this.answerItemTemplate!);
|
||||
item.name = `AnswerItem_${index + 1}`;
|
||||
item.active = true;
|
||||
this.answerListContent!.addChild(item);
|
||||
this._answerItemNodes.push(item);
|
||||
this._positionAnswerItem(item, index);
|
||||
|
||||
this._applyAnswerState(item, level);
|
||||
|
||||
const coverSprite = this._findChild(item, 'CoverImage')?.getComponent(Sprite) ?? null;
|
||||
this._prepareCoverSprite(coverSprite);
|
||||
this._loadCoverImage(level.image1Url, coverSprite, version);
|
||||
});
|
||||
|
||||
this._scrollAnswerListToTop();
|
||||
}
|
||||
|
||||
private _layoutAnswerContent(itemCount: number): void {
|
||||
if (!this.answerListContent || !this.answerItemTemplate) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentTransform = this.answerListContent.getComponent(UITransform);
|
||||
const viewTransform = this.answerListContent.parent?.getComponent(UITransform) ?? null;
|
||||
const itemTransform = this.answerItemTemplate.getComponent(UITransform);
|
||||
if (!contentTransform || !viewTransform || !itemTransform) {
|
||||
return;
|
||||
}
|
||||
|
||||
const itemHeight = itemTransform.height;
|
||||
const contentHeight = Math.max(
|
||||
viewTransform.height,
|
||||
PagePKEnd.ANSWER_ITEM_TOP_PADDING
|
||||
+ PagePKEnd.ANSWER_ITEM_BOTTOM_PADDING
|
||||
+ itemCount * itemHeight
|
||||
+ Math.max(0, itemCount - 1) * PagePKEnd.ANSWER_ITEM_SPACING,
|
||||
);
|
||||
|
||||
contentTransform.setContentSize(contentTransform.width, contentHeight);
|
||||
this.answerListContent.setPosition(
|
||||
this.answerListContent.position.x,
|
||||
viewTransform.height / 2,
|
||||
this.answerListContent.position.z,
|
||||
);
|
||||
}
|
||||
|
||||
private _positionAnswerItem(item: Node, index: number): void {
|
||||
const itemTransform = item.getComponent(UITransform);
|
||||
if (!itemTransform) {
|
||||
return;
|
||||
}
|
||||
|
||||
const y = -PagePKEnd.ANSWER_ITEM_TOP_PADDING
|
||||
- itemTransform.height / 2
|
||||
- index * (itemTransform.height + PagePKEnd.ANSWER_ITEM_SPACING);
|
||||
item.setPosition(0, y, item.position.z);
|
||||
}
|
||||
|
||||
private _applyAnswerState(item: Node, level: SubmittedShareLevelData): void {
|
||||
const answerButton = this._findChild(item, 'ButtonViewAnswer');
|
||||
const buttonLabel = answerButton?.getChildByName('Label')?.getComponent(Label) ?? null;
|
||||
const answerLabelNode = this._findChild(item, 'AnswerLabel');
|
||||
const answerLabel = answerLabelNode?.getComponent(Label) ?? null;
|
||||
|
||||
this._setLabel(buttonLabel, '查看答案');
|
||||
this._setLabel(answerLabel, level.answer || '-');
|
||||
|
||||
if (level.isCorrect) {
|
||||
if (answerButton) {
|
||||
answerButton.active = false;
|
||||
}
|
||||
if (answerLabelNode) {
|
||||
answerLabelNode.active = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (answerButton) {
|
||||
answerButton.active = true;
|
||||
}
|
||||
if (answerLabelNode) {
|
||||
answerLabelNode.active = false;
|
||||
}
|
||||
|
||||
const handler = () => {
|
||||
if (answerButton?.isValid) {
|
||||
answerButton.active = false;
|
||||
}
|
||||
if (answerLabelNode?.isValid) {
|
||||
answerLabelNode.active = true;
|
||||
}
|
||||
};
|
||||
if (answerButton) {
|
||||
answerButton.on(Button.EventType.CLICK, handler, this);
|
||||
this._answerButtonBindings.push({ node: answerButton, handler });
|
||||
}
|
||||
}
|
||||
|
||||
private _scrollAnswerListToTop(): void {
|
||||
const scrollView = this.node.getChildByName('AnswerList')?.getComponent(ScrollView);
|
||||
scrollView?.scrollToTop(0);
|
||||
}
|
||||
|
||||
private _loadCoverImage(url: string, sprite: Sprite | null, version: number): void {
|
||||
if (!url || !sprite) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._prepareCoverSprite(sprite);
|
||||
assetManager.loadRemote<ImageAsset>(url, (err, imageAsset) => {
|
||||
if (err || !imageAsset || version !== this._renderVersion || !sprite.node.isValid) {
|
||||
if (err) {
|
||||
console.error('[PagePKEnd] 加载答案封面失败:', url, err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const texture = new Texture2D();
|
||||
texture.image = imageAsset;
|
||||
const spriteFrame = new SpriteFrame();
|
||||
spriteFrame.texture = texture;
|
||||
this._prepareCoverSprite(sprite);
|
||||
sprite.spriteFrame = spriteFrame;
|
||||
this._prepareCoverSprite(sprite);
|
||||
});
|
||||
}
|
||||
|
||||
private _prepareCoverSprite(sprite: Sprite | null): void {
|
||||
if (!sprite?.node.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
sprite.sizeMode = Sprite.SizeMode.CUSTOM;
|
||||
const transform = sprite.node.getComponent(UITransform);
|
||||
if (transform) {
|
||||
transform.setContentSize(PagePKEnd.COVER_IMAGE_WIDTH, PagePKEnd.COVER_IMAGE_HEIGHT);
|
||||
}
|
||||
sprite.node.setScale(0.242, 0.242, 0.242);
|
||||
}
|
||||
|
||||
private _clearAnswerItems(): void {
|
||||
this._unbindAnswerButtons();
|
||||
|
||||
for (const item of this._answerItemNodes) {
|
||||
if (item.isValid) {
|
||||
item.removeFromParent();
|
||||
item.destroy();
|
||||
}
|
||||
}
|
||||
this._answerItemNodes = [];
|
||||
this._hideAnswerTemplate();
|
||||
}
|
||||
|
||||
private _unbindAnswerButtons(): void {
|
||||
for (const binding of this._answerButtonBindings) {
|
||||
if (binding.node.isValid) {
|
||||
binding.node.off(Button.EventType.CLICK, binding.handler, this);
|
||||
}
|
||||
}
|
||||
this._answerButtonBindings = [];
|
||||
}
|
||||
|
||||
private _hideAnswerTemplate(): void {
|
||||
if (this.answerItemTemplate?.isValid) {
|
||||
this.answerItemTemplate.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _setLabel(label: Label | null, text: string): void {
|
||||
if (label) {
|
||||
label.string = text;
|
||||
}
|
||||
}
|
||||
|
||||
private _findLabel(nodeName: string): Label | null {
|
||||
return this._findChild(this.node, nodeName)?.getComponent(Label) ?? null;
|
||||
}
|
||||
|
||||
private _findChild(root: Node, nodeName: string): Node | null {
|
||||
if (root.name === nodeName) {
|
||||
return root;
|
||||
}
|
||||
|
||||
for (const child of root.children) {
|
||||
const found = this._findChild(child, nodeName);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ export const API_ENDPOINTS = {
|
||||
/** 分享相关 */
|
||||
SHARE_CREATE: `${API_BASE}/share`,
|
||||
SHARE_CREATED: `${API_BASE}/share/created`,
|
||||
SHARE_PROGRESS: `${API_BASE}/share/progress`,
|
||||
/** 用户信息 */
|
||||
USER_INFO: `${API_BASE}/user/info`,
|
||||
/** 用户所有已通关的关卡(成就墙 / 关卡回看) */
|
||||
@@ -38,6 +37,10 @@ export function getShareJoinUrl(code: string): string {
|
||||
return `${API_BASE}/share/${code}/join`;
|
||||
}
|
||||
|
||||
export function getShareSubmitUrl(code: string): string {
|
||||
return `${API_BASE}/share/${code}/submit`;
|
||||
}
|
||||
|
||||
export function getGameConfigUrl(key: string): string {
|
||||
return `${API_BASE}/game-configs/${key}`;
|
||||
}
|
||||
|
||||
@@ -134,13 +134,34 @@ export interface JoinShareData {
|
||||
levels: ShareLevelData[];
|
||||
}
|
||||
|
||||
/** 上报关卡进度响应 */
|
||||
export interface ReportProgressData {
|
||||
passed: boolean;
|
||||
/** 分享挑战单关提交 */
|
||||
export interface SubmitShareLevel {
|
||||
levelId: string;
|
||||
answer: string;
|
||||
timeSpent: number;
|
||||
}
|
||||
|
||||
/** 分享挑战提交后的单关结果 */
|
||||
export interface SubmittedShareLevelData extends ShareLevelData {
|
||||
submittedAnswer: string;
|
||||
timeSpent: number;
|
||||
isCorrect: boolean;
|
||||
timeLimit: number | null;
|
||||
withinTimeLimit: boolean;
|
||||
}
|
||||
|
||||
/** 分享挑战整场提交响应 */
|
||||
export interface SubmitShareData {
|
||||
shareCode: string;
|
||||
title: string;
|
||||
rank: number;
|
||||
correctCount: number;
|
||||
levelCount: number;
|
||||
participantCount: number;
|
||||
totalTimeSpent: number;
|
||||
levels: SubmittedShareLevelData[];
|
||||
}
|
||||
|
||||
/** 我创建的分享挑战条目 */
|
||||
export interface CreatedShareItem {
|
||||
id: string;
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc';
|
||||
import { HttpUtil } from './HttpUtil';
|
||||
import { WxSDK } from './WxSDK';
|
||||
import { API_ENDPOINTS, getShareJoinUrl, API_TIMEOUT } from '../config/ApiConfig';
|
||||
import { API_ENDPOINTS, getShareJoinUrl, getShareSubmitUrl, API_TIMEOUT } from '../config/ApiConfig';
|
||||
import {
|
||||
ApiEnvelope,
|
||||
CreateShareData,
|
||||
JoinShareData,
|
||||
ReportProgressData,
|
||||
ShareLevelData,
|
||||
CreatedShareItem,
|
||||
CreatedShareListData,
|
||||
SubmitShareData,
|
||||
SubmitShareLevel,
|
||||
} from '../types/ApiTypes';
|
||||
import { RuntimeLevelConfig } from '../types/LevelTypes';
|
||||
|
||||
@@ -50,6 +51,14 @@ export class ShareManager {
|
||||
return [...this._createdShares];
|
||||
}
|
||||
|
||||
get shareCode(): string | null {
|
||||
return this._shareCode;
|
||||
}
|
||||
|
||||
get shareTitle(): string {
|
||||
return this._shareTitle;
|
||||
}
|
||||
|
||||
async createShare(title: string, levelIds: string[]): Promise<string | null> {
|
||||
try {
|
||||
const response = await HttpUtil.post<ApiEnvelope<CreateShareData>>(
|
||||
@@ -101,6 +110,7 @@ export class ShareManager {
|
||||
clue3: level.hint3,
|
||||
answer: level.answer,
|
||||
completed: false,
|
||||
timeLimit: null,
|
||||
}));
|
||||
|
||||
// 预加载首关图片(两张并行加载)
|
||||
@@ -178,40 +188,34 @@ export class ShareManager {
|
||||
return this._shareLevels?.length ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报单关通关进度
|
||||
* @param levelId 关卡 ID
|
||||
* @param passed 是否通过
|
||||
* @param timeSpent 用时(秒)
|
||||
*/
|
||||
async reportLevelProgress(
|
||||
levelId: string,
|
||||
passed: boolean,
|
||||
timeSpent: number,
|
||||
): Promise<ReportProgressData | null> {
|
||||
getShareLevelIds(): string[] {
|
||||
return this._shareApiLevels.map(level => level.id);
|
||||
}
|
||||
|
||||
async submitShareChallenge(levels: SubmitShareLevel[]): Promise<SubmitShareData | null> {
|
||||
if (!this._shareCode) {
|
||||
console.warn('[ShareManager] reportLevelProgress: 无分享码,跳过上报');
|
||||
console.warn('[ShareManager] submitShareChallenge: 无分享码,跳过提交');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await HttpUtil.post<ApiEnvelope<ReportProgressData>>(
|
||||
API_ENDPOINTS.SHARE_PROGRESS,
|
||||
{ shareCode: this._shareCode, levelId, passed, timeSpent },
|
||||
const response = await HttpUtil.post<ApiEnvelope<SubmitShareData>>(
|
||||
getShareSubmitUrl(this._shareCode),
|
||||
{ levels },
|
||||
API_TIMEOUT.DEFAULT,
|
||||
);
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
console.error('[ShareManager] 上报进度失败:', response.message);
|
||||
console.error('[ShareManager] 提交挑战结果失败:', response.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[ShareManager] 上报成功: passed=${response.data.passed}, withinTimeLimit=${response.data.withinTimeLimit}`,
|
||||
`[ShareManager] 提交挑战结果成功: rank=${response.data.rank}, correct=${response.data.correctCount}/${response.data.levelCount}`,
|
||||
);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error('[ShareManager] 上报进度异常:', err);
|
||||
console.error('[ShareManager] 提交挑战结果异常:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user