729 lines
25 KiB
TypeScript
729 lines
25 KiB
TypeScript
import { _decorator, Node, Button, Sprite, Label, Toggle, ScrollView, EditBox, instantiate, UITransform, Vec2, EventTouch, EffectAsset, Prefab } from 'cc';
|
||
import { BaseView } from 'db://assets/scripts/core/BaseView';
|
||
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
||
import { CommonModal } from 'db://assets/prefabs/CommonModal';
|
||
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';
|
||
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, CompletedLevel } from 'db://assets/scripts/types/ApiTypes';
|
||
import { applyRoundedCorner } from 'db://assets/scripts/utils/roundedMaterial.utils';
|
||
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
|
||
const { ccclass, property } = _decorator;
|
||
|
||
/**
|
||
* 布局配置
|
||
* view (ScrollView 的可视窗口) 宽 900,高 1100
|
||
* 关卡 item 固定两列,纵向滚动
|
||
*
|
||
* item 的实际显示尺寸从 ListTpl 的 UITransform * scale 派生,
|
||
* 避免代码里再维护一套和 prefab 脱节的宽高。
|
||
*/
|
||
const LAYOUT_CONFIG = {
|
||
COLS: 2,
|
||
SPACING_X: 36,
|
||
SPACING_Y: 48,
|
||
EDGE_PADDING_Y: 32,
|
||
CENTER_ROWS: 2,
|
||
VIEW_WIDTH: 900,
|
||
VIEW_HEIGHT: 1300,
|
||
LIST_BOTTOM_GAP_TO_TITLE: 54,
|
||
};
|
||
|
||
/** 必须选择的关卡数量 */
|
||
const MAX_SELECTION = 6;
|
||
|
||
@ccclass('PageWriteLevels')
|
||
export class PageWriteLevels extends BaseView {
|
||
@property({ type: Node, tooltip: '返回按钮' })
|
||
backBtn: Node | null = null;
|
||
|
||
@property({ type: Node, tooltip: 'ScrollView可视区域节点' })
|
||
scrollView: Node | null = null;
|
||
|
||
@property({ type: Node, tooltip: '列表content节点' })
|
||
listContent: Node | null = null;
|
||
|
||
@property({ type: Node, tooltip: '列表项模板' })
|
||
listTemplate: Node | null = null;
|
||
|
||
@property({ type: Node, tooltip: '已选关卡提示Label节点' })
|
||
selectedLabel: Node | null = null;
|
||
|
||
@property({ type: Node, tooltip: '完成按钮节点' })
|
||
completeBtn: Node | null = null;
|
||
|
||
@property({ type: Node, tooltip: '预览按钮节点' })
|
||
previewBtn: Node | null = null;
|
||
|
||
@property({ type: Node, tooltip: '分享标题输入框节点' })
|
||
shareTitleEditBox: Node | null = null;
|
||
|
||
@property({ type: Node, tooltip: '挑战数据按钮节点' })
|
||
dataBtn: Node | null = null;
|
||
|
||
@property({ type: EffectAsset, tooltip: '关卡封面圆角材质 EffectAsset' })
|
||
roundedSpriteEffect: EffectAsset | null = null;
|
||
|
||
@property({ type: Prefab, tooltip: '通用弹窗预制体' })
|
||
commonModalPrefab: Prefab | null = null;
|
||
|
||
@property({ tooltip: '关卡封面圆角半径比例(相对于短边,0-0.5)' })
|
||
coverCornerRadius: number = 0.1;
|
||
|
||
private _selectedIndices: Set<number> = new Set();
|
||
private _levels: CompletedLevel[] = [];
|
||
private _levelCount: number = 0;
|
||
private _itemNodes: Node[] = [];
|
||
private _scrollViewComp: ScrollView | null = null;
|
||
private _levelListLoadToken: number = 0;
|
||
|
||
/** 缓存 view 节点的 UITransform,避免每次 _updateContentSize 重复查找 */
|
||
private _viewTransform: UITransform | null = null;
|
||
|
||
/** 防止重复提交 */
|
||
private _isSubmitting: boolean = false;
|
||
|
||
onViewLoad(): void {
|
||
console.log('[PageWriteLevels] onViewLoad');
|
||
this._initButtons();
|
||
this._initScrollView();
|
||
this._resizeScrollViewport();
|
||
this._updateSelectionUI();
|
||
}
|
||
|
||
private _initButtons(): void {
|
||
if (this.backBtn) {
|
||
this.backBtn.on(Button.EventType.CLICK, this._onBackClick, this);
|
||
}
|
||
if (this.previewBtn) {
|
||
this.previewBtn.on(Button.EventType.CLICK, this._onPreviewClick, this);
|
||
}
|
||
if (this.completeBtn) {
|
||
this.completeBtn.on(Button.EventType.CLICK, this._onCompleteClick, this);
|
||
}
|
||
if (this.dataBtn) {
|
||
this.dataBtn.on(Button.EventType.CLICK, this._onDataClick, this);
|
||
}
|
||
}
|
||
|
||
private _initScrollView(): void {
|
||
if (this.scrollView) {
|
||
this._scrollViewComp = this.scrollView.getComponent(ScrollView);
|
||
|
||
if (this._scrollViewComp) {
|
||
this._scrollViewComp.horizontal = false;
|
||
this._scrollViewComp.vertical = true;
|
||
this._scrollViewComp.inertia = true;
|
||
}
|
||
|
||
// 缓存 view 的 UITransform
|
||
const viewNode = this.scrollView.getChildByName('view');
|
||
if (viewNode) {
|
||
this._viewTransform = viewNode.getComponent(UITransform);
|
||
}
|
||
}
|
||
|
||
// content anchor 设为 (0, 1) 左上角,方便位置计算且符合 ScrollView 标准用法
|
||
if (this.listContent) {
|
||
const contentTransform = this.listContent.getComponent(UITransform);
|
||
if (contentTransform) {
|
||
contentTransform.setAnchorPoint(0, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
onViewShow(): void {
|
||
console.log('[PageWriteLevels] onViewShow');
|
||
this._resizeScrollViewport();
|
||
this._updateContentSize();
|
||
void this._initLevelList();
|
||
}
|
||
|
||
private _resizeScrollViewport(): void {
|
||
if (!this.scrollView || !this._viewTransform) {
|
||
return;
|
||
}
|
||
|
||
const scrollTransform = this.scrollView.getComponent(UITransform);
|
||
const scrollWidget = this.scrollView.getComponent('cc.Widget') as any;
|
||
const viewWidget = this._viewTransform.node.getComponent('cc.Widget') as any;
|
||
|
||
if (!scrollTransform) {
|
||
return;
|
||
}
|
||
|
||
scrollWidget?.updateAlignment?.();
|
||
viewWidget?.updateAlignment?.();
|
||
|
||
this._viewTransform.setContentSize(
|
||
scrollTransform.contentSize.width,
|
||
scrollTransform.contentSize.height,
|
||
);
|
||
}
|
||
|
||
private async _initLevelList(): Promise<void> {
|
||
const loadToken = ++this._levelListLoadToken;
|
||
const selectedLevelIds = this._getSelectedLevelIdSet();
|
||
|
||
// 拉取当前用户所有已通关关卡
|
||
const levels = await CompletedLevelsManager.instance.fetch(true);
|
||
if (loadToken !== this._levelListLoadToken) {
|
||
return;
|
||
}
|
||
|
||
if (levels === null) {
|
||
console.warn('[PageWriteLevels] 获取已通关关卡失败');
|
||
ToastManager.instance.show('获取关卡列表失败,请稍后重试');
|
||
return;
|
||
}
|
||
|
||
this._clearList();
|
||
this._levels = levels;
|
||
this._levelCount = this._levels.length;
|
||
this._restoreSelectedIndices(selectedLevelIds);
|
||
console.log('[PageWriteLevels] 已通关关卡总数:', this._levelCount);
|
||
|
||
if (this._levelCount === 0) {
|
||
console.warn('[PageWriteLevels] 用户尚未通关任何关卡');
|
||
ToastManager.instance.show('还没有已通关的关卡,快去玩几关吧');
|
||
this._updateContentSize();
|
||
this._updateSelectionUI();
|
||
return;
|
||
}
|
||
|
||
this._updateContentSize();
|
||
this._createItems();
|
||
|
||
if (this._scrollViewComp) {
|
||
this._scrollViewComp.scrollToTop(0);
|
||
}
|
||
}
|
||
|
||
private _getSelectedLevelIdSet(): Set<string> {
|
||
const selectedLevelIds = new Set<string>();
|
||
for (const index of this._selectedIndices) {
|
||
const level = this._levels[index] ?? null;
|
||
if (level) {
|
||
selectedLevelIds.add(level.id);
|
||
}
|
||
}
|
||
return selectedLevelIds;
|
||
}
|
||
|
||
private _restoreSelectedIndices(selectedLevelIds: Set<string>): void {
|
||
if (selectedLevelIds.size === 0) {
|
||
return;
|
||
}
|
||
|
||
for (let index = 0; index < this._levels.length; index++) {
|
||
if (selectedLevelIds.has(this._levels[index].id)) {
|
||
this._selectedIndices.add(index);
|
||
}
|
||
}
|
||
}
|
||
|
||
private _clearList(): void {
|
||
for (const node of this._itemNodes) {
|
||
if (node && node.isValid) {
|
||
node.destroy();
|
||
}
|
||
}
|
||
this._levels = [];
|
||
this._itemNodes = [];
|
||
this._selectedIndices.clear();
|
||
}
|
||
|
||
private _getViewSize(): { width: number, height: number } {
|
||
return {
|
||
width: this._viewTransform?.contentSize.width ?? LAYOUT_CONFIG.VIEW_WIDTH,
|
||
height: this._viewTransform?.contentSize.height ?? LAYOUT_CONFIG.VIEW_HEIGHT,
|
||
};
|
||
}
|
||
|
||
private _getItemDisplaySize(): { width: number, height: number } {
|
||
if (!this.listTemplate) {
|
||
return { width: 0, height: 0 };
|
||
}
|
||
|
||
const itemTransform = this.listTemplate.getComponent(UITransform);
|
||
if (!itemTransform) {
|
||
return { width: 0, height: 0 };
|
||
}
|
||
|
||
return {
|
||
width: itemTransform.contentSize.width * Math.abs(this.listTemplate.scale.x),
|
||
height: itemTransform.contentSize.height * Math.abs(this.listTemplate.scale.y),
|
||
};
|
||
}
|
||
|
||
private _getRowCount(): number {
|
||
return Math.ceil(this._levelCount / LAYOUT_CONFIG.COLS);
|
||
}
|
||
|
||
private _getHorizontalPadding(itemWidth: number): number {
|
||
const viewWidth = this._getViewSize().width;
|
||
const gridWidth = LAYOUT_CONFIG.COLS * itemWidth + (LAYOUT_CONFIG.COLS - 1) * LAYOUT_CONFIG.SPACING_X;
|
||
return Math.max(0, (viewWidth - gridWidth) / 2);
|
||
}
|
||
|
||
private _getVerticalPadding(rowCount: number, itemHeight: number): number {
|
||
const viewHeight = this._getViewSize().height;
|
||
const totalGridHeight = rowCount * itemHeight + Math.max(0, rowCount - 1) * LAYOUT_CONFIG.SPACING_Y;
|
||
|
||
if (rowCount <= LAYOUT_CONFIG.CENTER_ROWS) {
|
||
return Math.max(LAYOUT_CONFIG.EDGE_PADDING_Y, (viewHeight - totalGridHeight) / 2);
|
||
}
|
||
|
||
return LAYOUT_CONFIG.EDGE_PADDING_Y;
|
||
}
|
||
|
||
private _updateContentSize(): void {
|
||
if (!this.listContent) return;
|
||
|
||
const contentTransform = this.listContent.getComponent(UITransform);
|
||
if (!contentTransform) return;
|
||
|
||
const { width: viewWidth, height: viewHeight } = this._getViewSize();
|
||
const { width: itemWidth, height: itemHeight } = this._getItemDisplaySize();
|
||
const rowCount = this._getRowCount();
|
||
const paddingY = this._getVerticalPadding(rowCount, itemHeight);
|
||
const gridHeight = rowCount > 0
|
||
? rowCount * itemHeight + (rowCount - 1) * LAYOUT_CONFIG.SPACING_Y
|
||
: 0;
|
||
const contentHeight = Math.max(viewHeight, gridHeight + paddingY * 2);
|
||
|
||
contentTransform.setContentSize(viewWidth, contentHeight);
|
||
|
||
// content anchor=(0,1),需要贴到 view 左上角,供纵向列表按左上原点排布。
|
||
if (this._viewTransform) {
|
||
this.listContent.setPosition(-viewWidth / 2, 0, 0);
|
||
}
|
||
}
|
||
|
||
private _createItems(): void {
|
||
if (!this.listTemplate || !this.listContent) return;
|
||
|
||
for (let i = 0; i < this._levelCount; i++) {
|
||
const itemNode = this._createItem(i);
|
||
if (itemNode) {
|
||
this.listContent.addChild(itemNode);
|
||
this._itemNodes.push(itemNode);
|
||
this._loadAndRefreshCover(itemNode, i);
|
||
}
|
||
}
|
||
}
|
||
|
||
private _createItem(index: number): Node | null {
|
||
if (!this.listTemplate) return null;
|
||
|
||
const item = instantiate(this.listTemplate);
|
||
item.active = true;
|
||
item.name = `item_${index}`;
|
||
|
||
const pos = this._calculateItemPosition(index);
|
||
item.setPosition(pos.x, pos.y, 0);
|
||
|
||
// 设置默认名称和选中状态(封面由 _loadAndRefreshCover 异步填充)
|
||
this._initItemState(item, index);
|
||
|
||
// 禁用 Button 组件,防止它拦截触摸事件导致 ScrollView 无法滑动
|
||
const button = item.getComponent(Button);
|
||
if (button) {
|
||
button.enabled = false;
|
||
}
|
||
|
||
this._setupItemClick(item, index);
|
||
|
||
return item;
|
||
}
|
||
|
||
private _calculateItemPosition(index: number): { x: number, y: number } {
|
||
const { width: itemWidth, height: itemHeight } = this._getItemDisplaySize();
|
||
const rowCount = this._getRowCount();
|
||
const col = index % LAYOUT_CONFIG.COLS;
|
||
const row = Math.floor(index / LAYOUT_CONFIG.COLS);
|
||
|
||
const paddingLeft = this._getHorizontalPadding(itemWidth);
|
||
const paddingTop = this._getVerticalPadding(rowCount, itemHeight);
|
||
|
||
const x = paddingLeft
|
||
+ col * (itemWidth + LAYOUT_CONFIG.SPACING_X)
|
||
+ itemWidth / 2;
|
||
|
||
const y = -(paddingTop
|
||
+ row * (itemHeight + LAYOUT_CONFIG.SPACING_Y)
|
||
+ itemHeight / 2);
|
||
|
||
return { x, y };
|
||
}
|
||
|
||
/**
|
||
* 初始化 item 的默认名称和选中状态(不设置封面,由异步加载负责)
|
||
*/
|
||
private _initItemState(item: Node, index: number): void {
|
||
const level = this._levels[index] ?? null;
|
||
const levelName = item.getChildByName('LevelName');
|
||
if (levelName) {
|
||
const label = levelName.getComponent(Label);
|
||
if (label) {
|
||
const answerText = level?.answer?.trim();
|
||
label.string = answerText || (level ? `第${level.level}关` : `第${index + 1}关`);
|
||
}
|
||
}
|
||
|
||
const isSelected = item.getChildByName('IsSelected');
|
||
if (isSelected) {
|
||
const toggle = isSelected.getComponent(Toggle);
|
||
if (toggle) {
|
||
toggle.isChecked = this._selectedIndices.has(index);
|
||
// 禁用 Toggle 交互,仅作为视觉指示器,选中逻辑由 item Button 统一处理
|
||
toggle.interactable = false;
|
||
}
|
||
const checkmark = isSelected.getChildByName('Checkmark');
|
||
if (checkmark) {
|
||
checkmark.active = this._selectedIndices.has(index);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 异步加载关卡封面图并填充到 item
|
||
*/
|
||
private async _loadAndRefreshCover(item: Node, index: number): Promise<void> {
|
||
const level = this._levels[index] ?? null;
|
||
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) {
|
||
sprite.spriteFrame = spriteFrame;
|
||
this._applyCoverRoundedCorner(sprite);
|
||
}
|
||
}
|
||
}
|
||
|
||
private _applyCoverRoundedCorner(sprite: Sprite): void {
|
||
if (!this.roundedSpriteEffect) {
|
||
return;
|
||
}
|
||
|
||
const uiTransform = sprite.node.getComponent(UITransform);
|
||
if (!uiTransform) {
|
||
return;
|
||
}
|
||
|
||
applyRoundedCorner(
|
||
sprite,
|
||
this.roundedSpriteEffect,
|
||
uiTransform.width,
|
||
uiTransform.height,
|
||
this.coverCornerRadius
|
||
);
|
||
}
|
||
|
||
private _setupItemClick(item: Node, index: number): void {
|
||
// 用触摸事件代替 Button,区分点击和滑动:
|
||
// 短距离松手 = 点击(切换选中),长距离 = 滑动(交给 ScrollView)
|
||
let touchStartPos: Vec2 | null = null;
|
||
|
||
item.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
|
||
touchStartPos = event.getUILocation();
|
||
}, this);
|
||
|
||
item.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
|
||
if (!touchStartPos) return;
|
||
const endPos = event.getUILocation();
|
||
const dx = endPos.x - touchStartPos.x;
|
||
const dy = endPos.y - touchStartPos.y;
|
||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||
touchStartPos = null;
|
||
|
||
// 滑动距离小于阈值才算点击
|
||
if (distance < 20) {
|
||
const isCurrentlySelected = this._selectedIndices.has(index);
|
||
this._onItemToggle(index, !isCurrentlySelected);
|
||
}
|
||
}, this);
|
||
|
||
item.on(Node.EventType.TOUCH_CANCEL, () => {
|
||
touchStartPos = null;
|
||
}, this);
|
||
}
|
||
|
||
private _onItemToggle(index: number, selected: boolean): void {
|
||
// 如果要选中但已达上限,阻止选中
|
||
if (selected && this._selectedIndices.size >= MAX_SELECTION) {
|
||
// 恢复 toggle 的视觉状态为未选中
|
||
const item = this._itemNodes[index];
|
||
if (item) {
|
||
const isSelected = item.getChildByName('IsSelected');
|
||
if (isSelected) {
|
||
const toggle = isSelected.getComponent(Toggle);
|
||
if (toggle) {
|
||
toggle.isChecked = false;
|
||
}
|
||
const checkmark = isSelected.getChildByName('Checkmark');
|
||
if (checkmark) {
|
||
checkmark.active = false;
|
||
}
|
||
}
|
||
}
|
||
console.log(`[PageWriteLevels] 已达最大选择数量 ${MAX_SELECTION},无法继续选择`);
|
||
return;
|
||
}
|
||
|
||
if (selected) {
|
||
this._selectedIndices.add(index);
|
||
} else {
|
||
this._selectedIndices.delete(index);
|
||
}
|
||
|
||
AudioManager.instance.playButtonClick();
|
||
|
||
console.log('[PageWriteLevels] item切换选中:', index, selected, '当前已选:', this._selectedIndices.size);
|
||
|
||
const item = this._itemNodes[index];
|
||
if (item) {
|
||
const isSelected = item.getChildByName('IsSelected');
|
||
if (isSelected) {
|
||
const toggle = isSelected.getComponent(Toggle);
|
||
if (toggle) {
|
||
toggle.isChecked = selected;
|
||
}
|
||
const checkmark = isSelected.getChildByName('Checkmark');
|
||
if (checkmark) {
|
||
checkmark.active = selected;
|
||
}
|
||
}
|
||
}
|
||
|
||
this._updateSelectionUI();
|
||
}
|
||
|
||
/**
|
||
* 根据当前选中数量更新 SelectedLabel 文本和按钮可用状态。
|
||
* - 未选择任何关卡时:显示 "请选择6关"
|
||
* - 已选但不足6关时:显示 "已选 x 关,还差 y 关"
|
||
* - 恰好选满6关时:显示 "已选满6关",启用按钮
|
||
*/
|
||
private _updateSelectionUI(): void {
|
||
const count = this._selectedIndices.size;
|
||
const remaining = MAX_SELECTION - count;
|
||
const isFull = remaining <= 0;
|
||
|
||
// 更新 SelectedLabel 文本
|
||
if (this.selectedLabel) {
|
||
const label = this.selectedLabel.getComponent(Label);
|
||
if (label) {
|
||
if (count === 0) {
|
||
label.string = `请选择${MAX_SELECTION}关`;
|
||
} else if (isFull) {
|
||
label.string = `已选满${MAX_SELECTION}关`;
|
||
} else {
|
||
label.string = `已选${count}关,还差${remaining}关`;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 预览与分享数量不足时也要允许点击,统一弹出提示弹窗。
|
||
if (this.completeBtn) {
|
||
const btn = this.completeBtn.getComponent(Button);
|
||
if (btn) {
|
||
btn.interactable = true;
|
||
}
|
||
}
|
||
if (this.previewBtn) {
|
||
const btn = this.previewBtn.getComponent(Button);
|
||
if (btn) {
|
||
btn.interactable = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
private _onBackClick(): void {
|
||
AudioManager.instance.playButtonClick();
|
||
console.log('[PageWriteLevels] 返回按钮点击');
|
||
ViewManager.instance.back();
|
||
}
|
||
|
||
private _onDataClick(): void {
|
||
AudioManager.instance.playButtonClick();
|
||
ViewManager.instance.open('PagePKData');
|
||
}
|
||
|
||
/**
|
||
* 校验是否已选满关卡,未满则弹出统一提示弹窗
|
||
* @returns true 表示校验通过
|
||
*/
|
||
private _validateSelection(): boolean {
|
||
if (this._selectedIndices.size < MAX_SELECTION) {
|
||
this._showSelectionRequiredModal();
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private _showSelectionRequiredModal(): void {
|
||
if (!this.commonModalPrefab) {
|
||
console.warn('[PageWriteLevels] commonModalPrefab 未设置,回退为 Toast 提示');
|
||
ToastManager.instance.show(`请选择${MAX_SELECTION}个关卡后再预览或分享`);
|
||
return;
|
||
}
|
||
|
||
CommonModal.show(this.commonModalPrefab, {
|
||
title: '提示',
|
||
content: `要选择${MAX_SELECTION}个关卡才能分享和预览`,
|
||
buttonConfirm: '知道了',
|
||
});
|
||
}
|
||
|
||
private _onPreviewClick(): void {
|
||
AudioManager.instance.playButtonClick();
|
||
if (!this._validateSelection()) return;
|
||
const shareTitle = this.shareTitleEditBox?.getComponent(EditBox)?.string?.trim() || '';
|
||
ViewManager.instance.open('PagePreviewLevels', {
|
||
params: {
|
||
selectedIndices: Array.from(this._selectedIndices),
|
||
shareTitle: shareTitle
|
||
}
|
||
});
|
||
}
|
||
|
||
private async _onCompleteClick(): Promise<void> {
|
||
AudioManager.instance.playButtonClick();
|
||
if (!this._validateSelection()) return;
|
||
|
||
const shareTitle = this.shareTitleEditBox?.getComponent(EditBox)?.string?.trim() || '';
|
||
if (!shareTitle) {
|
||
ToastManager.instance.show('请输入分享标题');
|
||
return;
|
||
}
|
||
|
||
if (this._isSubmitting) return;
|
||
this._isSubmitting = true;
|
||
|
||
try {
|
||
const levelIds = this._getSelectedLevelIds();
|
||
if (levelIds.length !== MAX_SELECTION) {
|
||
ToastManager.instance.show('获取关卡数据失败,请重试');
|
||
return;
|
||
}
|
||
|
||
const shareCode = await ShareManager.instance.createShare(shareTitle, levelIds);
|
||
if (!shareCode) {
|
||
ToastManager.instance.show('创建分享失败,请重试');
|
||
return;
|
||
}
|
||
|
||
console.log('[PageWriteLevels] 创建分享成功, code:', shareCode);
|
||
|
||
// 获取用户头像昵称并上传
|
||
await this._uploadUserInfo();
|
||
|
||
ShareManager.instance.triggerWxShare(shareTitle, shareCode);
|
||
ToastManager.instance.show('分享创建成功!');
|
||
} catch (err) {
|
||
console.error('[PageWriteLevels] 完成按钮异常:', err);
|
||
ToastManager.instance.show('操作失败,请重试');
|
||
} finally {
|
||
this._isSubmitting = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 将选中的关卡索引转换为关卡 ID 数组
|
||
*/
|
||
private _getSelectedLevelIds(): string[] {
|
||
const ids: string[] = [];
|
||
const sortedIndices = Array.from(this._selectedIndices).sort((a, b) => a - b);
|
||
for (const index of sortedIndices) {
|
||
const level = this._levels[index] ?? null;
|
||
if (level) {
|
||
ids.push(level.id);
|
||
}
|
||
}
|
||
return ids;
|
||
}
|
||
|
||
/**
|
||
* 获取用户头像昵称并上传到服务端
|
||
*/
|
||
private async _uploadUserInfo(): Promise<void> {
|
||
// 先检查本地缓存
|
||
const cachedUserInfo = StorageManager.getUserInfo();
|
||
if (cachedUserInfo) {
|
||
console.log('[PageWriteLevels] 使用缓存的用户信息');
|
||
return;
|
||
}
|
||
|
||
if (!WxSDK.isWechat()) {
|
||
console.log('[PageWriteLevels] 非微信环境,跳过获取用户信息');
|
||
return;
|
||
}
|
||
|
||
// 获取当前登录用户的 ID
|
||
const userId = AuthManager.instance.userId;
|
||
if (!userId) {
|
||
console.warn('[PageWriteLevels] 用户未登录,跳过获取用户信息');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const userInfo = await getUserProfile();
|
||
|
||
// 本地缓存
|
||
StorageManager.setUserInfo(userInfo);
|
||
|
||
// 上传到服务端
|
||
const response = await HttpUtil.post<ApiEnvelope<unknown>>(
|
||
API_ENDPOINTS.USER_INFO,
|
||
{
|
||
userId: userId,
|
||
avatarUrl: userInfo.avatarUrl,
|
||
nickName: userInfo.nickName
|
||
},
|
||
API_TIMEOUT.DEFAULT
|
||
);
|
||
|
||
if (response.success) {
|
||
console.log('[PageWriteLevels] 用户信息上传成功');
|
||
} else {
|
||
console.warn('[PageWriteLevels] 用户信息上传失败:', response.message);
|
||
}
|
||
} catch (err) {
|
||
console.warn('[PageWriteLevels] 获取用户信息失败:', err);
|
||
// 不阻断主流程
|
||
}
|
||
}
|
||
|
||
onViewHide(): void {
|
||
console.log('[PageWriteLevels] onViewHide');
|
||
}
|
||
|
||
onViewDestroy(): void {
|
||
console.log('[PageWriteLevels] onViewDestroy');
|
||
if (this.backBtn) {
|
||
this.backBtn.off(Button.EventType.CLICK, this._onBackClick, this);
|
||
}
|
||
if (this.previewBtn) {
|
||
this.previewBtn.off(Button.EventType.CLICK, this._onPreviewClick, this);
|
||
}
|
||
if (this.completeBtn) {
|
||
this.completeBtn.off(Button.EventType.CLICK, this._onCompleteClick, this);
|
||
}
|
||
if (this.dataBtn) {
|
||
this.dataBtn.off(Button.EventType.CLICK, this._onDataClick, this);
|
||
}
|
||
this._clearList();
|
||
}
|
||
}
|