feat: 接入激励视频
This commit is contained in:
704
软著源代码/09-分享与预览.md
Normal file
704
软著源代码/09-分享与预览.md
Normal file
@@ -0,0 +1,704 @@
|
||||
# 梗中作乐 V1.0 - 源代码文档(第 9 部分)
|
||||
|
||||
软件名称:梗中作乐
|
||||
版本号:V1.0
|
||||
|
||||
---
|
||||
|
||||
## 第 9 部分:好友分享与预览
|
||||
|
||||
本部分包含 PK 关卡预览页、分享逻辑管理器、分享启动参数处理器。
|
||||
|
||||
### 9.1 分享预览页 (assets/prefabs/PagePreviewLevels.ts)
|
||||
|
||||
```typescript
|
||||
import { _decorator, Node, Button, Label, ScrollView, instantiate, UITransform } from 'cc';
|
||||
import { BaseView } from 'db://assets/scripts/core/BaseView';
|
||||
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
||||
import { CompletedLevelsManager } from 'db://assets/scripts/utils/CompletedLevelsManager';
|
||||
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
|
||||
import { PreviewLevelItem } from './PreviewLevelItem';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* 预览试卷页面
|
||||
* 垂直滚动展示用户在 PageWriteLevels 中选中的 6 个关卡
|
||||
* 每个关卡展示:封面图、线索1、线索2、线索3、答案
|
||||
*
|
||||
* 节点结构(仅 ScrollView 侧需要固定):
|
||||
* PagePreviewLevels
|
||||
* ├── ScrollView / view / content ← listContent 容器
|
||||
* └── ListTpl ← listTemplate 模板根节点
|
||||
* (挂 PreviewLevelItem 组件,字段由编辑器拖拽绑定)
|
||||
*
|
||||
* item 内部节点层级/命名对本文件透明:所有引用都来自 PreviewLevelItem 的 @property。
|
||||
*/
|
||||
|
||||
/** 布局配置 — 垂直列表 */
|
||||
const LAYOUT = {
|
||||
/** 关卡项高度(与 ListTpl UITransform 高度一致) */
|
||||
ITEM_HEIGHT: 300,
|
||||
/** 关卡项之间的垂直间距 */
|
||||
SPACING_Y: 30,
|
||||
/** 列表顶部内边距 */
|
||||
PADDING_TOP: 20,
|
||||
};
|
||||
|
||||
@ccclass('PagePreviewLevels')
|
||||
export class PagePreviewLevels 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: '底部返回按钮' })
|
||||
backButton: Node | null = null;
|
||||
|
||||
@property({ type: Node, tooltip: '标题Label节点' })
|
||||
pkTitle: Node | null = null;
|
||||
|
||||
/** 已创建的 item 节点列表 */
|
||||
private _itemNodes: Node[] = [];
|
||||
|
||||
// ─── 生命周期 ───────────────────────────────────────
|
||||
|
||||
onViewLoad(): void {
|
||||
console.log('[PagePreviewLevels] onViewLoad');
|
||||
this._initButtons();
|
||||
this._initScrollView();
|
||||
}
|
||||
|
||||
onViewShow(): void {
|
||||
console.log('[PagePreviewLevels] onViewShow');
|
||||
this._buildList();
|
||||
}
|
||||
|
||||
onViewHide(): void {
|
||||
console.log('[PagePreviewLevels] onViewHide');
|
||||
}
|
||||
|
||||
onViewDestroy(): void {
|
||||
console.log('[PagePreviewLevels] onViewDestroy');
|
||||
this._offButtons();
|
||||
this._clearList();
|
||||
}
|
||||
|
||||
// ─── 初始化 ─────────────────────────────────────────
|
||||
|
||||
private _initButtons(): void {
|
||||
if (this.backBtn) {
|
||||
this.backBtn.on(Button.EventType.CLICK, this._onBackClick, this);
|
||||
}
|
||||
if (this.backButton) {
|
||||
this.backButton.on(Button.EventType.CLICK, this._onBackClick, this);
|
||||
}
|
||||
}
|
||||
|
||||
private _offButtons(): void {
|
||||
if (this.backBtn) {
|
||||
this.backBtn.off(Button.EventType.CLICK, this._onBackClick, this);
|
||||
}
|
||||
if (this.backButton) {
|
||||
this.backButton.off(Button.EventType.CLICK, this._onBackClick, this);
|
||||
}
|
||||
}
|
||||
|
||||
private _initScrollView(): void {
|
||||
if (!this.listContent) return;
|
||||
const contentTransform = this.listContent.getComponent(UITransform);
|
||||
if (contentTransform) {
|
||||
contentTransform.setAnchorPoint(0.5, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 列表构建 ───────────────────────────────────────
|
||||
|
||||
private _buildList(): void {
|
||||
this._clearList();
|
||||
|
||||
const params = this.getParams();
|
||||
if (!params || !params.selectedIndices || params.selectedIndices.length === 0) {
|
||||
console.warn('[PagePreviewLevels] 未传入选中关卡数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示用户输入的标题
|
||||
if (this.pkTitle) {
|
||||
const label = this.pkTitle.getComponent(Label);
|
||||
if (label) {
|
||||
label.string = params.shareTitle || '挑战';
|
||||
}
|
||||
}
|
||||
|
||||
const indices: number[] = params.selectedIndices;
|
||||
console.log('[PagePreviewLevels] 选中关卡索引:', indices);
|
||||
|
||||
// 更新 content 高度
|
||||
this._updateContentSize(indices.length);
|
||||
|
||||
// 创建每个关卡 item
|
||||
for (let i = 0; i < indices.length; i++) {
|
||||
const levelIndex = indices[i];
|
||||
const itemNode = this._createItem(i);
|
||||
if (itemNode) {
|
||||
this.listContent!.addChild(itemNode);
|
||||
this._itemNodes.push(itemNode);
|
||||
this._loadLevelData(itemNode, levelIndex, i);
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动到顶部
|
||||
const scrollComp = this.scrollView?.getComponent(ScrollView);
|
||||
if (scrollComp) {
|
||||
scrollComp.scrollToTop(0);
|
||||
}
|
||||
}
|
||||
|
||||
private _clearList(): void {
|
||||
for (const node of this._itemNodes) {
|
||||
if (node && node.isValid) {
|
||||
node.destroy();
|
||||
}
|
||||
}
|
||||
this._itemNodes = [];
|
||||
}
|
||||
|
||||
private _updateContentSize(count: number): void {
|
||||
if (!this.listContent) return;
|
||||
const contentTransform = this.listContent.getComponent(UITransform);
|
||||
if (!contentTransform) return;
|
||||
|
||||
const totalHeight = LAYOUT.PADDING_TOP
|
||||
+ count * LAYOUT.ITEM_HEIGHT
|
||||
+ (count > 0 ? (count - 1) * LAYOUT.SPACING_Y : 0)
|
||||
+ LAYOUT.PADDING_TOP;
|
||||
|
||||
contentTransform.setContentSize(contentTransform.contentSize.width, totalHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个关卡展示项
|
||||
* content anchor=(0.5, 1),y 轴负向下
|
||||
*/
|
||||
private _createItem(displayIndex: number): Node | null {
|
||||
if (!this.listTemplate) return null;
|
||||
|
||||
const item = instantiate(this.listTemplate);
|
||||
item.active = true;
|
||||
item.name = `preview_item_${displayIndex}`;
|
||||
|
||||
// 垂直居中排列:x=0(水平居中于 content),y 负向下
|
||||
const y = -(LAYOUT.PADDING_TOP + displayIndex * (LAYOUT.ITEM_HEIGHT + LAYOUT.SPACING_Y) + LAYOUT.ITEM_HEIGHT / 2);
|
||||
item.setPosition(0, y, 0);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步加载关卡数据并填充到 item 节点
|
||||
*/
|
||||
private async _loadLevelData(item: Node, levelIndex: number, displayIndex: number): Promise<void> {
|
||||
const level = CompletedLevelsManager.instance.getByIndex(levelIndex);
|
||||
if (!level || !item.isValid) return;
|
||||
|
||||
const view = item.getComponent(PreviewLevelItem);
|
||||
if (!view) {
|
||||
console.warn('[PagePreviewLevels] listTemplate 缺少 PreviewLevelItem 组件');
|
||||
return;
|
||||
}
|
||||
|
||||
view.setTexts({
|
||||
answer: level.answer || '',
|
||||
hint1: level.hint1 || '',
|
||||
hint2: level.hint2 || '',
|
||||
hint3: level.hint3 || '',
|
||||
});
|
||||
|
||||
// 异步加载封面图(通常已由 WriteLevels 预热到缓存)
|
||||
const spriteFrame = await CompletedLevelsManager.instance.loadImage(level.image1Url);
|
||||
if (!spriteFrame || !item.isValid) return;
|
||||
view.setCover(spriteFrame);
|
||||
}
|
||||
|
||||
// ─── 事件处理 ───────────────────────────────────────
|
||||
|
||||
private _onBackClick(): void {
|
||||
AudioManager.instance.playButtonClick();
|
||||
console.log('[PagePreviewLevels] 返回');
|
||||
ViewManager.instance.back();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 分享管理器 (assets/scripts/utils/ShareManager.ts)
|
||||
|
||||
```typescript
|
||||
import { SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc';
|
||||
import { HttpUtil } from './HttpUtil';
|
||||
import { WxSDK } from './WxSDK';
|
||||
import { API_ENDPOINTS, getShareDetailUrl, getShareJoinUrl, getShareSubmitUrl, API_TIMEOUT } from '../config/ApiConfig';
|
||||
import {
|
||||
ApiEnvelope,
|
||||
CreateShareData,
|
||||
JoinShareData,
|
||||
ShareLevelData,
|
||||
CreatedShareItem,
|
||||
CreatedShareListData,
|
||||
ParticipatedShareItem,
|
||||
ParticipatedShareListData,
|
||||
ShareDetailData,
|
||||
SubmitShareData,
|
||||
SubmitShareLevel,
|
||||
} from '../types/ApiTypes';
|
||||
import { RuntimeLevelConfig } from '../types/LevelTypes';
|
||||
|
||||
/**
|
||||
* 分享管理器
|
||||
* 负责创建分享、加入分享、缓存分享关卡数据
|
||||
*/
|
||||
export class ShareManager {
|
||||
private static _instance: ShareManager | null = null;
|
||||
|
||||
/** 分享模式的关卡数据(null 表示正常模式) */
|
||||
private _shareLevels: RuntimeLevelConfig[] | null = null;
|
||||
|
||||
/** API 返回的原始关卡数据(保留 image1Url/image2Url 用于懒加载) */
|
||||
private _shareApiLevels: ShareLevelData[] = [];
|
||||
|
||||
private _shareTitle: string = '';
|
||||
private _shareCode: string | null = null;
|
||||
private _createdShares: CreatedShareItem[] = [];
|
||||
private _participatedShares: ParticipatedShareItem[] = [];
|
||||
|
||||
/** 图片缓存:URL -> SpriteFrame */
|
||||
private _imageCache: Map<string, SpriteFrame> = new Map();
|
||||
|
||||
static get instance(): ShareManager {
|
||||
if (!this._instance) {
|
||||
this._instance = new ShareManager();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
get isShareMode(): boolean {
|
||||
return this._shareLevels !== null && this._shareLevels.length > 0;
|
||||
}
|
||||
|
||||
get createdShares(): CreatedShareItem[] {
|
||||
return [...this._createdShares];
|
||||
}
|
||||
|
||||
get participatedShares(): ParticipatedShareItem[] {
|
||||
return [...this._participatedShares];
|
||||
}
|
||||
|
||||
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>>(
|
||||
API_ENDPOINTS.SHARE_CREATE,
|
||||
{ title, levelIds },
|
||||
API_TIMEOUT.DEFAULT,
|
||||
);
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
console.error('[ShareManager] 创建分享失败:', response.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.data.shareCode;
|
||||
} catch (err) {
|
||||
console.error('[ShareManager] 创建分享异常:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async joinShare(code: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await HttpUtil.post<ApiEnvelope<JoinShareData>>(
|
||||
getShareJoinUrl(code),
|
||||
{},
|
||||
API_TIMEOUT.DEFAULT,
|
||||
);
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
console.error('[ShareManager] 加入分享失败:', response.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
const { shareCode, title, levels } = response.data;
|
||||
this._shareCode = shareCode;
|
||||
this._shareTitle = title;
|
||||
this._shareApiLevels = levels;
|
||||
|
||||
const runtimeLevels: RuntimeLevelConfig[] = levels.map((level) => ({
|
||||
id: level.id,
|
||||
name: `第${level.level}关`,
|
||||
spriteFrame1: null,
|
||||
spriteFrame2: null,
|
||||
image1Description: level.image1Description,
|
||||
image2Description: level.image2Description,
|
||||
punchline: level.punchline,
|
||||
clue1: level.hint1,
|
||||
clue2: level.hint2,
|
||||
clue3: level.hint3,
|
||||
answer: level.answer,
|
||||
completed: false,
|
||||
timeLimit: null,
|
||||
}));
|
||||
|
||||
// 预加载首关图片(两张并行加载)
|
||||
if (levels.length > 0) {
|
||||
const [sf1, sf2] = await Promise.all([
|
||||
this._loadImage(levels[0].image1Url),
|
||||
this._loadImage(levels[0].image2Url),
|
||||
]);
|
||||
if (sf1) {
|
||||
runtimeLevels[0].spriteFrame1 = sf1;
|
||||
}
|
||||
if (sf2) {
|
||||
runtimeLevels[0].spriteFrame2 = sf2;
|
||||
}
|
||||
}
|
||||
|
||||
this._shareLevels = runtimeLevels;
|
||||
console.log(`[ShareManager] 加入分享成功: ${title}, ${levels.length} 关`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('[ShareManager] 加入分享异常:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchCreatedShares(): Promise<CreatedShareItem[] | null> {
|
||||
try {
|
||||
const response = await HttpUtil.get<ApiEnvelope<CreatedShareListData>>(
|
||||
API_ENDPOINTS.SHARE_CREATED,
|
||||
API_TIMEOUT.DEFAULT,
|
||||
);
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
console.error('[ShareManager] 获取我创建的挑战列表失败:', response.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
this._createdShares = response.data.items ?? [];
|
||||
console.log(`[ShareManager] 获取我创建的挑战列表成功: ${this._createdShares.length} 条`);
|
||||
return this.createdShares;
|
||||
} catch (err) {
|
||||
console.error('[ShareManager] 获取我创建的挑战列表异常:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchParticipatedShares(): Promise<ParticipatedShareItem[] | null> {
|
||||
try {
|
||||
const response = await HttpUtil.get<ApiEnvelope<ParticipatedShareListData>>(
|
||||
API_ENDPOINTS.SHARE_PARTICIPATED,
|
||||
API_TIMEOUT.DEFAULT,
|
||||
);
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
console.error('[ShareManager] 获取我参与的挑战列表失败:', response.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
this._participatedShares = response.data.items ?? [];
|
||||
console.log(`[ShareManager] 获取我参与的挑战列表成功: ${this._participatedShares.length} 条`);
|
||||
return this.participatedShares;
|
||||
} catch (err) {
|
||||
console.error('[ShareManager] 获取我参与的挑战列表异常:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchShareDetail(code: string): Promise<ShareDetailData | null> {
|
||||
try {
|
||||
const response = await HttpUtil.get<ApiEnvelope<ShareDetailData>>(
|
||||
getShareDetailUrl(code),
|
||||
API_TIMEOUT.DEFAULT,
|
||||
);
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
console.error('[ShareManager] 获取分享挑战详情失败:', response.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`[ShareManager] 获取分享挑战详情成功: ${response.data.title}, ${response.data.rankings?.length ?? 0} 条排行`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error('[ShareManager] 获取分享挑战详情异常:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async ensureShareLevelReady(index: number): Promise<RuntimeLevelConfig | null> {
|
||||
if (!this._shareLevels || index < 0 || index >= this._shareLevels.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const config = this._shareLevels[index];
|
||||
if (config.spriteFrame1) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const apiLevel = this._shareApiLevels[index];
|
||||
if (apiLevel?.image1Url) {
|
||||
const [sf1, sf2] = await Promise.all([
|
||||
this._loadImage(apiLevel.image1Url),
|
||||
this._loadImage(apiLevel.image2Url),
|
||||
]);
|
||||
if (sf1) {
|
||||
config.spriteFrame1 = sf1;
|
||||
}
|
||||
if (sf2) {
|
||||
config.spriteFrame2 = sf2;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
getShareLevelCount(): number {
|
||||
return this._shareLevels?.length ?? 0;
|
||||
}
|
||||
|
||||
getShareLevelIds(): string[] {
|
||||
return this._shareApiLevels.map(level => level.id);
|
||||
}
|
||||
|
||||
async submitShareChallenge(levels: SubmitShareLevel[]): Promise<SubmitShareData | null> {
|
||||
if (!this._shareCode) {
|
||||
console.warn('[ShareManager] submitShareChallenge: 无分享码,跳过提交');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await HttpUtil.post<ApiEnvelope<SubmitShareData>>(
|
||||
getShareSubmitUrl(this._shareCode),
|
||||
{ levels },
|
||||
API_TIMEOUT.DEFAULT,
|
||||
);
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
console.error('[ShareManager] 提交挑战结果失败:', response.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[ShareManager] 提交挑战结果成功: rank=${response.data.rank}, correct=${response.data.correctCount}/${response.data.levelCount}`,
|
||||
);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error('[ShareManager] 提交挑战结果异常:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
triggerWxShare(title: string, shareCode: string): void {
|
||||
WxSDK.shareAppMessage({
|
||||
title: title || '来挑战我出的谐音梗吧!',
|
||||
query: `shareCode=${shareCode}`,
|
||||
});
|
||||
}
|
||||
|
||||
clearShareMode(): void {
|
||||
this._shareLevels = null;
|
||||
this._shareApiLevels = [];
|
||||
this._shareTitle = '';
|
||||
this._shareCode = null;
|
||||
this._imageCache.clear();
|
||||
}
|
||||
|
||||
private _loadImage(url: string): Promise<SpriteFrame | null> {
|
||||
const cached = this._imageCache.get(url);
|
||||
if (cached) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
assetManager.loadRemote<ImageAsset>(url, (err, imageAsset) => {
|
||||
if (err) {
|
||||
console.error('[ShareManager] 加载图片失败:', url, err);
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
const texture = new Texture2D();
|
||||
texture.image = imageAsset;
|
||||
const spriteFrame = new SpriteFrame();
|
||||
spriteFrame.texture = texture;
|
||||
this._imageCache.set(url, spriteFrame);
|
||||
resolve(spriteFrame);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 启动分享处理器 (assets/scripts/utils/ShareLaunchHandler.ts)
|
||||
|
||||
```typescript
|
||||
import { WxSDK } from './WxSDK';
|
||||
import { ShareManager } from './ShareManager';
|
||||
import { AuthManager } from './AuthManager';
|
||||
import { ViewManager } from '../core/ViewManager';
|
||||
|
||||
/**
|
||||
* 分享启动监听器
|
||||
*
|
||||
* 微信小游戏未被杀掉、只是退到后台时,再次通过好友分享卡片打开小游戏,
|
||||
* 不会重新走启动链路(PageLoading 不会再跑),因此 `wx.getLaunchOptionsSync()`
|
||||
* 取到的 query 可能是上一次启动的旧值。这个 handler 通过 `wx.onShow`
|
||||
* 拿到最新的 query,检测 shareCode 变化后:
|
||||
* 1. 清掉旧的分享态
|
||||
* 2. 调用 `ShareManager.joinShare(code)` 拉取新的题单
|
||||
* 3. 直接打开 `PageLevel` 进入分享挑战
|
||||
*
|
||||
* 对应在 PageLevel 那侧通过 `onViewShow` 检测到 ShareManager.shareCode
|
||||
* 变化,重新走 `_reinitLevelSession`。
|
||||
*/
|
||||
export class ShareLaunchHandler {
|
||||
private static _instance: ShareLaunchHandler | null = null;
|
||||
|
||||
static get instance(): ShareLaunchHandler {
|
||||
if (!this._instance) {
|
||||
this._instance = new ShareLaunchHandler();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/** 已经处理过的 shareCode,相同则不再重复 join */
|
||||
private _activeShareCode: string | null = null;
|
||||
|
||||
/** 是否正在处理一次 onShow 触发的 join 流程,避免并发 */
|
||||
private _isHandlingShow: boolean = false;
|
||||
|
||||
/** 是否已经初始化 */
|
||||
private _initialized: boolean = false;
|
||||
|
||||
private _showHandler = (res: { query?: Record<string, any> } | undefined) => {
|
||||
const code = WxSDK.extractShareCodeFromQuery(res?.query);
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
// 同一个 shareCode 且当前已经处于该分享态,无需重复处理
|
||||
if (code === this._activeShareCode && ShareManager.instance.isShareMode) {
|
||||
return;
|
||||
}
|
||||
// 即使不是新的 code,但如果 ShareManager 已经丢失了分享态(例如挑战完成被 clear),
|
||||
// 用户重新点同一个分享卡片仍然应当重新加入。
|
||||
void this._handleShareCode(code);
|
||||
};
|
||||
|
||||
private _hideHandler = () => {
|
||||
// 目前不在 onHide 时做任何破坏性操作;保留监听只为方便后续扩展
|
||||
// (比如:暂停倒计时、上报埋点)。
|
||||
console.log('[ShareLaunchHandler] 小游戏切到后台');
|
||||
};
|
||||
|
||||
/**
|
||||
* 在 main.onLoad 中调用,注册 wx.onShow / wx.onHide。
|
||||
* 同时把当前启动参数中的 shareCode 作为种子,避免初次冷启动时
|
||||
* 因 wx.onShow 也会被调用一次而重复触发分享流程。
|
||||
*/
|
||||
init(): void {
|
||||
if (this._initialized) {
|
||||
return;
|
||||
}
|
||||
this._initialized = true;
|
||||
|
||||
if (!WxSDK.isWechat()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 冷启动时先把当前 launch 中的 shareCode 标记成已处理,
|
||||
// 避免 wx.onShow 在初始展示时拿到同一个 code 又走一遍 join。
|
||||
this._activeShareCode = WxSDK.getShareCodeFromLaunch();
|
||||
|
||||
WxSDK.onAppShow(this._showHandler);
|
||||
WxSDK.onAppHide(this._hideHandler);
|
||||
|
||||
console.log('[ShareLaunchHandler] 已注册 onShow/onHide 监听');
|
||||
}
|
||||
|
||||
/**
|
||||
* 由 PageLoading 在初始 join 之后调用,把已处理的 shareCode 显式同步过来,
|
||||
* 让 onShow 收到相同 code 时不会重复 join。
|
||||
*/
|
||||
markActiveShareCode(code: string | null): void {
|
||||
this._activeShareCode = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主动取消监听(一般无需调用,留作扩展)
|
||||
*/
|
||||
dispose(): void {
|
||||
if (!this._initialized) return;
|
||||
this._initialized = false;
|
||||
WxSDK.offAppShow(this._showHandler);
|
||||
WxSDK.offAppHide(this._hideHandler);
|
||||
}
|
||||
|
||||
private async _handleShareCode(code: string): Promise<void> {
|
||||
if (this._isHandlingShow) {
|
||||
console.log('[ShareLaunchHandler] 已有 onShow 分享流程在执行,跳过', code);
|
||||
return;
|
||||
}
|
||||
this._isHandlingShow = true;
|
||||
|
||||
try {
|
||||
console.log('[ShareLaunchHandler] 检测到新的 shareCode,准备切换:', code);
|
||||
|
||||
// 确保已登录(initialize 内部对已有 token 做了校验,幂等可重复调用)
|
||||
const loginOk = await AuthManager.instance.initialize();
|
||||
if (!loginOk) {
|
||||
console.warn('[ShareLaunchHandler] 登录失败,放弃 onShow 分享切换');
|
||||
return;
|
||||
}
|
||||
|
||||
// 切到新的分享前清掉旧分享态,避免 ShareManager 内残留旧题单导致 PageLevel 错位
|
||||
if (ShareManager.instance.isShareMode) {
|
||||
ShareManager.instance.clearShareMode();
|
||||
}
|
||||
|
||||
const joinOk = await ShareManager.instance.joinShare(code);
|
||||
if (!joinOk) {
|
||||
console.warn('[ShareLaunchHandler] 加入分享失败:', code);
|
||||
return;
|
||||
}
|
||||
|
||||
// 标记当前激活的分享码
|
||||
this._activeShareCode = code;
|
||||
|
||||
// 跳过中间页,直接打开 PageLevel 进入分享挑战。
|
||||
// PageLevel 会在 onViewShow 中根据 ShareManager.shareCode 与本地缓存比对,
|
||||
// 决定是否需要 `_reinitLevelSession`。
|
||||
ViewManager.instance.open('PageLevel', {
|
||||
params: { shareMode: true },
|
||||
});
|
||||
|
||||
console.log('[ShareLaunchHandler] 已切换到分享挑战:', code);
|
||||
} catch (err) {
|
||||
console.error('[ShareLaunchHandler] 处理 shareCode 异常:', err);
|
||||
} finally {
|
||||
this._isHandlingShow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user