Files
mp-xieyingeng/软著源代码/02-基类与类型定义.md
2026-06-29 19:55:40 +08:00

610 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 梗中作乐 V1.0 - 源代码文档(第 2 部分)
软件名称:梗中作乐
版本号V1.0
---
## 第 2 部分:基类与类型定义
本部分包含 5 个源文件:页面 / 弹窗的基类,以及全项目共用的 API 类型与配置常量。
### 2.1 视图基类 (assets/scripts/core/BaseView.ts)
```typescript
import { _decorator, Component, Prefab } from 'cc';
const { ccclass } = _decorator;
/**
* 页面配置接口
*/
export interface ViewConfig {
prefab: Prefab; // 预制体引用(主包资源)
cache?: boolean; // 是否缓存页面,默认 true
zIndex?: number; // 层级,默认 0
}
/**
* 页面打开选项
*/
export interface ViewOptions {
params?: any; // 传递给页面的参数
onComplete?: (view: BaseView) => void;
onError?: (err: Error) => void;
}
/**
* 页面基类
* 所有页面组件应继承此类,实现生命周期方法
*/
@ccclass('BaseView')
export class BaseView extends Component {
/** 页面唯一标识 */
viewId: string = '';
/** 页面配置 */
config: ViewConfig | null = null;
/** 是否正在显示 */
isShowing: boolean = false;
/** 传递给页面的参数 */
protected _params: any = null;
/**
* 设置页面参数
*/
setParams(params: any): void {
this._params = params;
}
/**
* 获取页面参数
*/
getParams(): any {
return this._params;
}
/**
* 页面加载时调用(首次创建时)
* 子类应重写此方法
*/
onViewLoad(): void {
// 子类实现
}
/**
* 页面显示时调用(每次打开时)
* 子类应重写此方法
*/
onViewShow(): void {
// 子类实现
}
/**
* 页面隐藏时调用(关闭或被其他页面覆盖时)
* 子类应重写此方法
*/
onViewHide(): void {
// 子类实现
}
/**
* 页面销毁时调用
* 子类应重写此方法
*/
onViewDestroy(): void {
// 子类实现
}
// ========== 内部方法,由 ViewManager 调用 ==========
/**
* 内部方法:执行显示逻辑
*/
_doShow(): void {
if (this.isShowing) return;
this.isShowing = true;
this.node.active = true;
this.onViewShow();
}
/**
* 内部方法:执行隐藏逻辑
*/
_doHide(): void {
if (!this.isShowing) return;
this.isShowing = false;
this.onViewHide();
this.node.active = false;
}
/**
* 内部方法:执行销毁逻辑
*/
_doDestroy(): void {
// 标记已销毁,防止 onDestroy 中重复调用
this._destroyed = true;
this.node.destroy();
}
/** 是否已标记销毁 */
private _destroyed: boolean = false;
// ========== Cocos 生命周期 ==========
protected onDestroy(): void {
// 仅在未被 _doDestroy 调用时执行生命周期
if (!this._destroyed) {
if (this.isShowing) {
this.onViewHide();
}
}
this.onViewDestroy();
}
}
```
### 2.2 弹窗基类 (assets/scripts/core/BaseModal.ts)
```typescript
import { _decorator, Node, Tween, UIOpacity, Vec3, tween } from 'cc';
import { BaseView } from './BaseView';
const { ccclass, property } = _decorator;
@ccclass('BaseModal')
export class BaseModal extends BaseView {
@property([Node])
protected animationNodes: Node[] = [];
@property(Node)
protected backdropNode: Node | null = null;
@property
protected openAnimationEnabled: boolean = true;
@property
protected openAnimationDuration: number = 0.36;
private readonly _originalScales: Map<Node, Vec3> = new Map();
onViewShow(): void {
this.playOpenAnimation();
}
onViewHide(): void {
this.stopOpenAnimation();
}
protected playOpenAnimation(): void {
if (!this.openAnimationEnabled) {
return;
}
this.playBackdropFadeIn();
const targets = this.getAnimationTargets();
targets.forEach((target, index) => {
this.playBounceIn(target, index * 0.035);
});
}
protected stopOpenAnimation(): void {
this.getAnimationTargets().forEach((target) => {
Tween.stopAllByTarget(target);
});
const opacity = this.backdropNode?.getComponent(UIOpacity);
if (opacity) {
Tween.stopAllByTarget(opacity);
}
}
private getAnimationTargets(): Node[] {
const configuredTargets = this.animationNodes.filter((node) => node?.isValid);
return configuredTargets.length > 0 ? configuredTargets : [this.node];
}
private getOriginalScale(target: Node): Vec3 {
const cachedScale = this._originalScales.get(target);
if (cachedScale) {
return cachedScale;
}
const originalScale = target.scale.clone();
this._originalScales.set(target, originalScale);
return originalScale;
}
private playBounceIn(target: Node, delay: number): void {
const originalScale = this.getOriginalScale(target);
const startScale = this.multiplyScale(originalScale, 0.82);
const peakScale = this.multiplyScale(originalScale, 1.045);
Tween.stopAllByTarget(target);
target.setScale(startScale);
tween(target)
.delay(delay)
.to(this.openAnimationDuration * 0.58, { scale: peakScale }, { easing: 'backOut' })
.to(this.openAnimationDuration * 0.24, { scale: this.multiplyScale(originalScale, 0.985) }, { easing: 'sineOut' })
.to(this.openAnimationDuration * 0.18, { scale: originalScale }, { easing: 'sineOut' })
.start();
}
private playBackdropFadeIn(): void {
if (!this.backdropNode?.isValid) {
return;
}
const opacity = this.backdropNode.getComponent(UIOpacity) ?? this.backdropNode.addComponent(UIOpacity);
Tween.stopAllByTarget(opacity);
opacity.opacity = 0;
tween(opacity)
.to(0.18, { opacity: 255 }, { easing: 'sineOut' })
.start();
}
private multiplyScale(scale: Vec3, factor: number): Vec3 {
return new Vec3(scale.x * factor, scale.y * factor, scale.z);
}
}
```
### 2.3 接口类型定义 (assets/scripts/types/ApiTypes.ts)
```typescript
/**
* 服务端 API 通用响应类型
*/
/** 服务端标准响应封装 */
export interface ApiEnvelope<T> {
success: boolean;
data: T | null;
message: string | null;
timestamp: string;
}
/** 体力值信息 */
export interface StaminaInfo {
/** 当前体力值(已计算恢复) */
current: number;
/** 体力上限,固定为 50 */
max: number;
/** 下一点体力恢复的时间ISO 8601满体力时为 null */
nextRecoverAt: string | null;
}
/** 下一关完整数据(多个接口共用) */
export interface NextLevelData {
/** 关卡 ID */
id: string;
/** 关卡编号sortOrder */
level: number;
/** 图片1 URL */
image1Url: string;
/** 图片1 文本说明 */
image1Description: string | null;
/** 图片2 URL */
image2Url: string;
/** 图片2 文本说明 */
image2Description: string | null;
/** 答案 */
answer: string;
/** 谐音梗说明 */
punchline: string | null;
/** 线索1 */
hint1: string | null;
/** 线索2 */
hint2: string | null;
/** 线索3 */
hint3: string | null;
/** 限时null 表示不限时 */
timeLimit: number | null;
}
/** 登录响应数据 */
export interface WxLoginData {
token: string;
user: {
id: string;
nickname: string | null;
stamina: number;
};
}
/** 用户资料响应数据 */
export interface UserProfileData {
id: string;
nickname: string | null;
stamina: StaminaInfo;
}
/** 游戏数据响应Loading 页面) */
export interface GameData {
user: {
id: string;
stamina: StaminaInfo;
};
/** 已通关的关卡数量 */
completedLevelCount: number;
/** 下一个待通关的关卡(全部通关时为 null */
nextLevel: NextLevelData | null;
}
/** 进入关卡响应 */
export interface EnterLevelData {
id: string;
level: number;
image1Url: string;
image1Description: string | null;
image2Url: string;
image2Description: string | null;
answer: string;
punchline: string | null;
hint1: string | null;
hint2: string | null;
hint3: string | null;
stamina: StaminaInfo;
/** 预加载的下一关数据(无下一关时为 null */
preloadNextLevel: NextLevelData | null;
}
/** 通关上报响应 */
export interface CompleteLevelData {
firstClear: boolean;
levelId: string;
timeSpent: number;
/** 下一个待通关的关卡(全部通关时为 null */
nextLevel: NextLevelData | null;
}
/** 创建分享响应 */
export interface CreateShareData {
shareCode: string;
title: string;
levelCount: number;
}
/** 分享关卡数据 */
export interface ShareLevelData {
id: string;
level: number;
image1Url: string;
image1Description: string | null;
image2Url: string;
image2Description: string | null;
answer: string;
punchline: string | null;
hint1: string | null;
hint2: string | null;
hint3: string | null;
sortOrder: number;
}
/** 加入分享响应 */
export interface JoinShareData {
shareCode: string;
title: string;
levels: ShareLevelData[];
}
/** 分享挑战单关提交 */
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 ShareParticipantRankSummary {
userId?: string | null;
participantId?: string | null;
nickname?: string | null;
nickName?: string | null;
avatarUrl?: string | null;
rank?: number | null;
correctCount?: number | null;
totalTimeSpent?: number | null;
}
/** 我创建的分享挑战条目 */
export interface CreatedShareItem {
id: string;
shareCode: string;
title: string;
levelCount: number;
participantCount: number;
userRank: number | null;
createdAt: string;
firstPlaceUser?: ShareParticipantRankSummary | null;
topParticipant?: ShareParticipantRankSummary | null;
firstParticipant?: ShareParticipantRankSummary | null;
champion?: ShareParticipantRankSummary | null;
}
/** 我创建的分享挑战列表响应 */
export interface CreatedShareListData {
items: CreatedShareItem[];
}
/** 我参与的分享挑战条目 */
export interface ParticipatedShareItem {
title: string;
participantCount: number;
userRank: number | null;
}
/** 我参与的分享挑战列表响应 */
export interface ParticipatedShareListData {
items: ParticipatedShareItem[];
}
/** 分享挑战详情响应 */
export interface ShareDetailData {
id: string;
shareCode: string;
title: string;
levelCount: number;
participantCount: number;
userRank: number | null;
createdAt: string;
rankings: ShareParticipantRankSummary[];
}
/** 已通关关卡数据(成就墙 / 关卡回看场景) */
export interface CompletedLevel {
/** 关卡 ID */
id: string;
/** 关卡编号sortOrder */
level: number;
/** 图片1 URL */
image1Url: string;
/** 图片1 文本说明 */
image1Description: string | null;
/** 图片2 URL */
image2Url: string;
/** 图片2 文本说明 */
image2Description: string | null;
/** 答案 */
answer: string;
/** 谐音梗说明 */
punchline: string | null;
/** 线索1 */
hint1: string | null;
/** 线索2 */
hint2: string | null;
/** 线索3 */
hint3: string | null;
/** 限时null 表示不限时 */
timeLimit: number | null;
/** 首次通关时长(秒) */
timeSpent: number;
/** 通关时间ISO 8601 */
completedAt: string;
}
```
### 2.4 关卡类型定义 (assets/scripts/types/LevelTypes.ts)
```typescript
import { SpriteFrame } from 'cc';
/**
* 运行时关卡配置(包含已加载的图片)
*/
export interface RuntimeLevelConfig {
/** 关卡 ID */
id: string;
/** 关卡名称 */
name: string;
/** 图片1 SpriteFrame可能为 null 如果加载失败) */
spriteFrame1: SpriteFrame | null;
/** 图片2 SpriteFrame可能为 null 如果加载失败) */
spriteFrame2: SpriteFrame | null;
/** 图片1 文本说明 */
image1Description: string | null;
/** 图片2 文本说明 */
image2Description: string | null;
/** 谐音梗说明 */
punchline: string | null;
/** 线索1 */
clue1: string | null;
/** 线索2 */
clue2: string | null;
/** 线索3 */
clue3: string | null;
/** 答案 */
answer: string | null;
/** 是否已通关 */
completed: boolean;
/** 限时null 表示不限时 */
timeLimit: number | null;
}
```
### 2.5 API 配置常量 (assets/scripts/config/ApiConfig.ts)
```typescript
/**
* API 配置常量
* 统一管理所有服务端 API 地址
*/
/** 服务端 API 基础地址 */
export const API_BASE = 'https://ilookai.cn/api/v1';
/** API 端点 */
export const API_ENDPOINTS = {
/** 微信登录 */
WX_LOGIN: `${API_BASE}/auth/wx-login`,
/** 用户资料(含实时体力) */
USER_PROFILE: `${API_BASE}/user/profile`,
/** 游戏数据(体力 + 通关进度 + 下一关) */
USER_GAME_DATA: `${API_BASE}/user/game-data`,
/** 游戏配置 */
GAME_CONFIGS: `${API_BASE}/game-configs`,
/** 分享相关 */
SHARE_CREATE: `${API_BASE}/share`,
SHARE_CREATED: `${API_BASE}/share/created`,
SHARE_PARTICIPATED: `${API_BASE}/share/participated`,
/** 用户信息 */
USER_INFO: `${API_BASE}/user/info`,
/** 用户所有已通关的关卡(成就墙 / 关卡回看) */
COMPLETED_LEVELS: `${API_BASE}/levels/completed`,
} as const;
export function getLevelEnterUrl(levelId: string): string {
return `${API_BASE}/levels/${levelId}/enter`;
}
export function getLevelCompleteUrl(levelId: string): string {
return `${API_BASE}/levels/${levelId}/complete`;
}
export function getShareJoinUrl(code: string): string {
return `${API_BASE}/share/${code}/join`;
}
export function getShareDetailUrl(code: string): string {
return `${API_BASE}/share/${code}`;
}
export function getShareSubmitUrl(code: string): string {
return `${API_BASE}/share/${code}/submit`;
}
export function getGameConfigUrl(key: string): string {
return `${API_BASE}/game-configs/${key}`;
}
/** 请求超时时间(毫秒) */
export const API_TIMEOUT = {
DEFAULT: 8000,
SHORT: 5000,
} as const;
```