feat: 完善关卡创作页面

This commit is contained in:
richarjiang
2026-04-30 16:35:08 +08:00
parent f8198e0463
commit 3d246de24c
18 changed files with 2135 additions and 841 deletions

View File

@@ -1,7 +1,7 @@
import { _decorator, Node, Button, Sprite, Label, Toggle, ScrollView, EditBox, instantiate, UITransform, Vec2, EventTouch } from 'cc';
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { LevelDataManager } from 'db://assets/scripts/utils/LevelDataManager';
import { CompletedLevelsManager } from 'db://assets/scripts/utils/CompletedLevelsManager';
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { StorageManager } from 'db://assets/scripts/utils/StorageManager';
@@ -9,28 +9,27 @@ import { WxSDK, getUserProfile } from 'db://assets/scripts/utils/WxSDK';
import { AuthManager } from 'db://assets/scripts/utils/AuthManager';
import { API_ENDPOINTS, API_TIMEOUT } from 'db://assets/scripts/config/ApiConfig';
import { HttpUtil } from 'db://assets/scripts/utils/HttpUtil';
import { ApiEnvelope } from 'db://assets/scripts/types/ApiTypes';
const { ccclass, property } = _decorator;
/**
* 布局配置
* 基于实际 prefab 尺寸计算:
* ScrollView / view 宽 900高 1300
* view (ScrollView 的可视窗口) 宽 900高 1000anchor (0.5, 1) 顶部中点
* ListTpl (item) 宽 300高 400
*
* 水平居中2 × 300 + 1 × 40 = 640, padding_left = (900 - 640) / 2 = 130
* 垂直居中3 × 400 + 2 × 25 = 1250, padding_top = (1300 - 1250) / 2 = 25
* 每页 2 行 × 2 列 = 4 个关卡
* PADDING 不再手写,由 VIEW / ITEM / SPACING 自动派生(见 _getHorizontalPadding / _getVerticalPadding
* 保证 item 网格在 view 内始终居中。改 SPACING 时不用再算 PADDING。
*/
const LAYOUT_CONFIG = {
COLS: 2,
ROWS: 3,
ROWS: 2,
ITEM_WIDTH: 300,
ITEM_HEIGHT: 400,
SPACING_X: 40,
SPACING_Y: 25,
PADDING_LEFT: 130,
PADDING_TOP: 25,
SPACING_X: 160,
SPACING_Y: 180,
VIEW_WIDTH: 900,
VIEW_HEIGHT: 1300,
VIEW_HEIGHT: 1000,
};
/** 必须选择的关卡数量 */
@@ -140,7 +139,7 @@ export class PageWriteLevels extends BaseView {
}
}
private _onTouchStart(event: EventTouch): void {
private _onTouchStart(_event: EventTouch): void {
if (!this._scrollViewComp) return;
this._touchStartOffsetX = this._scrollViewComp.getScrollOffset().x;
this._touchStartTime = Date.now();
@@ -149,7 +148,7 @@ export class PageWriteLevels extends BaseView {
/**
* 触摸结束:根据滑动距离和速度决定翻页方向
*/
private _onTouchEnd(event: EventTouch): void {
private _onTouchEnd(_event: EventTouch): void {
if (!this._scrollViewComp || this._totalPages <= 1) return;
const currentOffsetX = this._scrollViewComp.getScrollOffset().x;
@@ -176,20 +175,27 @@ export class PageWriteLevels extends BaseView {
console.log('[PageWriteLevels] onViewShow');
// 仅首次初始化列表,从预览页返回时保留选中状态
if (this._itemNodes.length === 0) {
this._initLevelList();
void this._initLevelList();
}
}
private _initLevelList(): void {
private async _initLevelList(): Promise<void> {
this._clearList();
// TODO: LevelDataManager API 已重构为 NextLevel 驱动,此页面需要重新设计数据来源
// this._levelCount = LevelDataManager.instance.getLevelCount();
this._levelCount = 0;
console.log('[PageWriteLevels] 关卡总数:', this._levelCount);
// 拉取当前用户所有已通关关卡
const levels = await CompletedLevelsManager.instance.fetch();
if (levels === null) {
console.warn('[PageWriteLevels] 获取已通关关卡失败');
ToastManager.instance.show('获取关卡列表失败,请稍后重试');
return;
}
this._levelCount = levels.length;
console.log('[PageWriteLevels] 已通关关卡总数:', this._levelCount);
if (this._levelCount === 0) {
console.warn('[PageWriteLevels] 没有关卡数据');
console.warn('[PageWriteLevels] 用户尚未通关任何关卡');
ToastManager.instance.show('还没有已通关的关卡,快去玩几关吧');
return;
}
@@ -214,6 +220,25 @@ export class PageWriteLevels extends BaseView {
this._selectedIndices.clear();
}
/**
* 水平 padding让整行 item 在 view 宽度内居中
* padding_left = (VIEW_WIDTH - cols*ITEM_WIDTH - (cols-1)*SPACING_X) / 2
*/
private _getHorizontalPadding(): number {
const { VIEW_WIDTH, COLS, ITEM_WIDTH, SPACING_X } = LAYOUT_CONFIG;
const gridWidth = COLS * ITEM_WIDTH + (COLS - 1) * SPACING_X;
return (VIEW_WIDTH - gridWidth) / 2;
}
/**
* 垂直 padding让整列 item 在 view 高度内居中
*/
private _getVerticalPadding(): number {
const { VIEW_HEIGHT, ROWS, ITEM_HEIGHT, SPACING_Y } = LAYOUT_CONFIG;
const gridHeight = ROWS * ITEM_HEIGHT + (ROWS - 1) * SPACING_Y;
return (VIEW_HEIGHT - gridHeight) / 2;
}
private _updateContentSize(): void {
if (!this.listContent) return;
@@ -223,11 +248,12 @@ export class PageWriteLevels extends BaseView {
const totalWidth = this._totalPages * LAYOUT_CONFIG.VIEW_WIDTH;
contentTransform.setContentSize(totalWidth, LAYOUT_CONFIG.VIEW_HEIGHT);
// anchor=(0,1)将 content 左上角对齐到 view 左上角
// content anchor=(0,1)view anchor=(0.5,1)。
// view 本地坐标系下view 的左上角 = (-viewWidth/2, 0)。
// content 的 anchor 点(左上角)需要贴到 view 的左上角。
if (this._viewTransform) {
const viewWidth = this._viewTransform.contentSize.width;
const viewHeight = this._viewTransform.contentSize.height;
this.listContent.setPosition(-viewWidth / 2, viewHeight / 2, 0);
this.listContent.setPosition(-viewWidth / 2, 0, 0);
}
}
@@ -277,12 +303,15 @@ export class PageWriteLevels extends BaseView {
const col = localIndex % LAYOUT_CONFIG.COLS;
const row = Math.floor(localIndex / LAYOUT_CONFIG.COLS);
const paddingLeft = this._getHorizontalPadding();
const paddingTop = this._getVerticalPadding();
const x = pageIndex * LAYOUT_CONFIG.VIEW_WIDTH
+ LAYOUT_CONFIG.PADDING_LEFT
+ paddingLeft
+ col * (LAYOUT_CONFIG.ITEM_WIDTH + LAYOUT_CONFIG.SPACING_X)
+ LAYOUT_CONFIG.ITEM_WIDTH / 2;
const y = -(LAYOUT_CONFIG.PADDING_TOP
const y = -(paddingTop
+ row * (LAYOUT_CONFIG.ITEM_HEIGHT + LAYOUT_CONFIG.SPACING_Y)
+ LAYOUT_CONFIG.ITEM_HEIGHT / 2);
@@ -293,11 +322,12 @@ export class PageWriteLevels extends BaseView {
* 初始化 item 的默认名称和选中状态(不设置封面,由异步加载负责)
*/
private _initItemState(item: Node, index: number): void {
const level = CompletedLevelsManager.instance.getByIndex(index);
const levelName = item.getChildByName('LevelName');
if (levelName) {
const label = levelName.getComponent(Label);
if (label) {
label.string = `${index + 1}`;
label.string = level ? `${level.level}` : `${index + 1}`;
}
}
@@ -317,27 +347,20 @@ export class PageWriteLevels extends BaseView {
}
/**
* 异步加载关卡资源并刷新封面图和名称。
* TODO: LevelDataManager API 已重构为 NextLevel 驱动,此方法需要重新设计
* 异步加载关卡封面图并填充到 item
*/
private async _loadAndRefreshCover(item: Node, index: number): Promise<void> {
// const config = await LevelDataManager.instance.ensureLevelReady(index);
const config = null as any; // TODO: 需要适配新 API
if (!config || !item.isValid) return;
const level = CompletedLevelsManager.instance.getByIndex(index);
if (!level || !item.isValid) return;
const spriteFrame = await CompletedLevelsManager.instance.loadImage(level.image1Url);
if (!spriteFrame || !item.isValid) return;
const levelCover = item.getChildByName('LevelCover');
if (levelCover) {
const sprite = levelCover.getComponent(Sprite);
if (sprite && config.spriteFrame1) {
sprite.spriteFrame = config.spriteFrame1;
}
}
const levelName = item.getChildByName('LevelName');
if (levelName) {
const label = levelName.getComponent(Label);
if (label) {
label.string = config.name;
if (sprite) {
sprite.spriteFrame = spriteFrame;
}
}
}
@@ -550,15 +573,14 @@ export class PageWriteLevels extends BaseView {
* 将选中的关卡索引转换为关卡 ID 数组
*/
private _getSelectedLevelIds(): string[] {
// TODO: LevelDataManager API 已重构为 NextLevel 驱动,此方法需要重新设计
const ids: string[] = [];
// const sortedIndices = Array.from(this._selectedIndices).sort((a, b) => a - b);
// for (const index of sortedIndices) {
// const config = LevelDataManager.instance.getLevelConfig(index);
// if (config) {
// ids.push(config.id);
// }
// }
const sortedIndices = Array.from(this._selectedIndices).sort((a, b) => a - b);
for (const index of sortedIndices) {
const level = CompletedLevelsManager.instance.getByIndex(index);
if (level) {
ids.push(level.id);
}
}
return ids;
}
@@ -592,7 +614,7 @@ export class PageWriteLevels extends BaseView {
StorageManager.setUserInfo(userInfo);
// 上传到服务端
const response = await HttpUtil.post(
const response = await HttpUtil.post<ApiEnvelope<unknown>>(
API_ENDPOINTS.USER_INFO,
{
userId: userId,