852 lines
22 KiB
Markdown
852 lines
22 KiB
Markdown
# MemeMind 分享挑战功能 API 文档
|
||
|
||
> 本文档面向微信小游戏客户端(Cocos Creator)开发人员
|
||
|
||
## 目录
|
||
|
||
- [概述](#概述)
|
||
- [认证方式](#认证方式)
|
||
- [通用响应格式](#通用响应格式)
|
||
- [接口列表](#接口列表)
|
||
- [错误码说明](#错误码说明)
|
||
- [接入流程](#接入流程)
|
||
- [Cocos Creator 调用示例](#cocos-creator-调用示例)
|
||
|
||
---
|
||
|
||
## 概述
|
||
|
||
分享挑战功能允许用户创建包含 6 个关卡的挑战链接,分享给好友。好友通过分享码加入挑战,独立完成关卡并上报进度。
|
||
|
||
### 新增功能点(当前分支)
|
||
|
||
1. **关卡时间限制**:`levels` 表新增 `time_limit` 字段,支持关卡通关时间限制
|
||
2. **单关进度上报**:`POST /api/v1/share/progress` 接口,用于上报用户单关通关状态和时间
|
||
3. **进度查询**:`reportLevelProgress` 返回是否在时间限制内通过
|
||
4. **我创建的挑战列表**:`GET /api/v1/share/created` 接口,用于查询当前用户创建过的分享挑战、参与人数和本人排名
|
||
|
||
---
|
||
|
||
## 认证方式
|
||
|
||
除微信登录接口外,所有接口均需通过 JWT Token 进行身份认证。
|
||
|
||
### 请求头格式
|
||
|
||
```
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
`token` 为微信登录接口返回的 JWT 令牌。
|
||
|
||
---
|
||
|
||
## 通用响应格式
|
||
|
||
所有接口均返回以下 JSON 结构:
|
||
|
||
```typescript
|
||
{
|
||
"success": boolean, // 请求是否成功
|
||
"data": T | null, // 成功时返回的数据,失败时为 null
|
||
"message": string | null,// 错误信息,成功时为 null
|
||
"timestamp": string // 服务器响应时间(ISO 8601 格式)
|
||
}
|
||
```
|
||
|
||
### 成功响应示例
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"shareCode": "abc12345",
|
||
"title": "我的挑战",
|
||
"levelCount": 6
|
||
},
|
||
"message": null,
|
||
"timestamp": "2026-04-08T12:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
### 失败响应示例
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"data": null,
|
||
"message": "分享不存在或已过期",
|
||
"timestamp": "2026-04-08T12:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 接口列表
|
||
|
||
### 1. 微信登录
|
||
|
||
获取用户身份令牌。
|
||
|
||
**接口地址**:`POST /api/v1/auth/wx-login`
|
||
|
||
**是否需要认证**:否
|
||
|
||
**请求体**:
|
||
|
||
```json
|
||
{
|
||
"code": "微信 wx.login 返回的 code"
|
||
}
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```typescript
|
||
{
|
||
token: string; // JWT 访问令牌,有效期 7 天
|
||
user: {
|
||
id: string; // 用户 ID
|
||
nickname: string | null; // 用户昵称(微信昵称)
|
||
stamina: number; // 当前体力值
|
||
}
|
||
}
|
||
```
|
||
|
||
**成功响应示例**:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||
"user": {
|
||
"id": "user_abc123",
|
||
"nickname": "游戏玩家",
|
||
"stamina": 5
|
||
}
|
||
},
|
||
"message": null,
|
||
"timestamp": "2026-04-08T12:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
**客户端调用时机**:
|
||
|
||
- 用户首次进入游戏时调用
|
||
- 小游戏冷启动时调用(建议缓存 token)
|
||
|
||
---
|
||
|
||
### 2. 创建分享
|
||
|
||
创建一个新的分享挑战。
|
||
|
||
**接口地址**:`POST /api/v1/share`
|
||
|
||
**是否需要认证**:是(JWT Bearer Token)
|
||
|
||
**请求头**:
|
||
|
||
```
|
||
Authorization: Bearer <token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
**请求体**:
|
||
|
||
```json
|
||
{
|
||
"title": "我的挑战", // 分享标题,不超过 100 字符
|
||
"levelIds": [
|
||
// 恰好 6 个关卡 ID
|
||
"level_id_1",
|
||
"level_id_2",
|
||
"level_id_3",
|
||
"level_id_4",
|
||
"level_id_5",
|
||
"level_id_6"
|
||
]
|
||
}
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```typescript
|
||
{
|
||
shareCode: string; // 8 位分享码,用于分享和加入
|
||
title: string; // 分享标题
|
||
levelCount: number; // 关卡数量(固定为 6)
|
||
}
|
||
```
|
||
|
||
**成功响应示例**:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"shareCode": "abc12345",
|
||
"title": "我的挑战",
|
||
"levelCount": 6
|
||
},
|
||
"message": null,
|
||
"timestamp": "2026-04-08T12:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
**分享码生成规则**:
|
||
|
||
- 使用 nanoid 生成 8 位字符
|
||
- 字符集为 a-z, A-Z, 0-9
|
||
- 发生碰撞时最多重试 3 次
|
||
|
||
**客户端调用场景**:
|
||
|
||
- 用户点击「分享挑战」按钮时调用
|
||
- 用户选择 6 个关卡后,生成分享码
|
||
- 将分享码拼接为分享链接或二维码
|
||
|
||
---
|
||
|
||
### 3. 加入分享
|
||
|
||
通过分享码加入一个分享挑战,获取关卡数据。
|
||
|
||
**接口地址**:`POST /api/v1/share/{code}/join`
|
||
|
||
**是否需要认证**:是(JWT Bearer Token)
|
||
|
||
**路径参数**:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
| ---- | ------ | ---- | -------------- |
|
||
| code | string | 是 | 分享码(8 位) |
|
||
|
||
**响应数据**:
|
||
|
||
```typescript
|
||
{
|
||
shareCode: string; // 分享码
|
||
title: string; // 分享标题
|
||
levels: [
|
||
{
|
||
id: string; // 关卡 ID
|
||
level: number; // 关卡序号(1-6)
|
||
imageUrl: string; // 关卡图片 URL
|
||
answer: string; // 正确答案
|
||
hint1: string | null; // 提示 1
|
||
hint2: string | null; // 提示 2
|
||
hint3: string | null; // 提示 3
|
||
sortOrder: number; // 排序顺序
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**成功响应示例**:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"shareCode": "abc12345",
|
||
"title": "我的挑战",
|
||
"levels": [
|
||
{
|
||
"id": "level_001",
|
||
"level": 1,
|
||
"imageUrl": "https://example.com/levels/1.png",
|
||
"answer": "答案1",
|
||
"hint1": "提示1",
|
||
"hint2": null,
|
||
"hint3": null,
|
||
"sortOrder": 1
|
||
}
|
||
]
|
||
},
|
||
"message": null,
|
||
"timestamp": "2026-04-08T12:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
**特殊逻辑**:
|
||
|
||
- 如果 `userId` 与分享创建者相同,不会创建 `ShareParticipant` 记录
|
||
- 返回的关卡列表按 `levelIds` 创建时的顺序排列
|
||
|
||
**客户端调用场景**:
|
||
|
||
- 用户通过分享码/链接进入游戏时调用
|
||
- 解析 URL 参数中的分享码,调用此接口获取关卡数据
|
||
|
||
---
|
||
|
||
### 4. 获取我创建的分享挑战
|
||
|
||
获取当前登录用户创建过的分享挑战列表。
|
||
|
||
**接口地址**:`GET /api/v1/share/created`
|
||
|
||
**是否需要认证**:是(JWT Bearer Token)
|
||
|
||
**请求头**:
|
||
|
||
```
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
**响应数据**:
|
||
|
||
```typescript
|
||
{
|
||
items: [
|
||
{
|
||
id: string; // 分享挑战 ID
|
||
shareCode: string; // 分享码
|
||
title: string; // 分享标题
|
||
levelCount: number; // 关卡数量
|
||
participantCount: number; // 参与挑战人数
|
||
userRank: number | null; // 当前用户在该挑战中的排名;未完成全部关卡时为 null
|
||
createdAt: string; // 创建时间,ISO 8601 字符串
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**成功响应示例**:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": "share_001",
|
||
"shareCode": "abc12345",
|
||
"title": "我的挑战",
|
||
"levelCount": 6,
|
||
"participantCount": 8,
|
||
"userRank": 2,
|
||
"createdAt": "2026-04-13T10:00:00.000Z"
|
||
},
|
||
{
|
||
"id": "share_002",
|
||
"shareCode": "xyz67890",
|
||
"title": "速度挑战",
|
||
"levelCount": 6,
|
||
"participantCount": 1,
|
||
"userRank": null,
|
||
"createdAt": "2026-04-12T09:00:00.000Z"
|
||
}
|
||
]
|
||
},
|
||
"message": null,
|
||
"timestamp": "2026-04-13T12:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
**排名规则**:
|
||
|
||
1. 只有完成该分享挑战全部关卡的用户才会进入排名。
|
||
2. 排名按通关总耗时升序计算,总耗时越短排名越高。
|
||
3. 当总耗时相同时,服务端会按 `participantId` 做稳定排序,保证返回顺序可重复。
|
||
4. `userRank` 表示当前登录用户在自己创建的该挑战中的排名。如果自己尚未完成全部关卡,则返回 `null`。
|
||
|
||
**参与人数统计规则**:
|
||
|
||
- 统计 `share_participants` 中该挑战的参与者数量。
|
||
- 创建者本人在调用创建接口时不会自动写入参与记录;只有真正以参与者身份产生挑战进度后,才可能出现在排名内。
|
||
|
||
**客户端调用场景**:
|
||
|
||
- 用户进入「我发起的挑战」页面时调用。
|
||
- 用于展示每个分享挑战的传播效果和本人当前成绩。
|
||
|
||
---
|
||
|
||
### 5. 上报单关进度
|
||
|
||
用户在分享挑战中完成单关后,上报进度。
|
||
|
||
**接口地址**:`POST /api/v1/share/progress`
|
||
|
||
**是否需要认证**:是(JWT Bearer Token)
|
||
|
||
**请求体**:
|
||
|
||
```json
|
||
{
|
||
"shareCode": "abc12345", // 分享码
|
||
"levelId": "level_001", // 关卡 ID
|
||
"passed": true, // 是否通过(true/false)
|
||
"timeSpent": 30 // 通关时间(秒)
|
||
}
|
||
```
|
||
|
||
**字段说明**:
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --------- | ------- | ---- | -------------------------- |
|
||
| shareCode | string | 是 | 分享码 |
|
||
| levelId | string | 是 | 关卡 ID |
|
||
| passed | boolean | 是 | 是否通过 |
|
||
| timeSpent | number | 是 | 通关时间(秒),最小值为 0 |
|
||
|
||
**响应数据**:
|
||
|
||
```typescript
|
||
{
|
||
passed: boolean; // 是否通过
|
||
timeLimit: number | null; // 该关卡时间限制(秒),null 表示无限制
|
||
withinTimeLimit: boolean; // 是否在时间限制内通过
|
||
}
|
||
```
|
||
|
||
**成功响应示例**:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"passed": true,
|
||
"timeLimit": 60,
|
||
"withinTimeLimit": true
|
||
},
|
||
"message": null,
|
||
"timestamp": "2026-04-08T12:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
**业务逻辑说明**:
|
||
|
||
1. **首次通关记录**:只有首次 `passed=true` 才会记录通关时间
|
||
2. **重复通关**:如果用户再次通关同一关卡(且之前已通过),返回之前记录的时间判断结果,不会覆盖
|
||
3. **未通过**:可以多次上报 `passed=false`,更新通关时间记录
|
||
4. **时间限制判断**:
|
||
- 如果关卡 `timeLimit` 为 `null`,`withinTimeLimit` 始终为 `true`
|
||
- 如果 `timeLimit` 不为 `null`,只有 `timeSpent <= timeLimit` 时 `withinTimeLimit` 才为 `true`
|
||
|
||
**客户端调用场景**:
|
||
|
||
- 用户完成一个关卡后调用
|
||
- 无论通关还是失败都需要调用
|
||
- 失败时 `passed=false`,`timeSpent` 可以传入实际用时或关卡时间上限
|
||
|
||
---
|
||
|
||
## 错误码说明
|
||
|
||
| HTTP Status | message | 说明 |
|
||
| ----------- | ------------------------------------- | ---------------------------- |
|
||
| 400 | 关卡ID不能重复,需要恰好6个不同的关卡 | 创建分享时 levelIds 格式错误 |
|
||
| 400 | 以下关卡不存在: xxx | 创建分享时关卡 ID 不存在 |
|
||
| 400 | 生成分享码失败,请重试 | 服务器生成分享码失败 |
|
||
| 401 | 未提供访问令牌 | 请求头缺少 Authorization |
|
||
| 401 | 访问令牌无效或已过期 | JWT Token 无效或过期 |
|
||
| 404 | 分享不存在或已过期 | 分享码不存在或已被删除 |
|
||
| 404 | 关卡不存在 | levelId 不存在于 levels 表 |
|
||
| 500 | Internal server error | 服务器内部错误 |
|
||
|
||
---
|
||
|
||
## 接入流程
|
||
|
||
### 整体流程
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 分享发起方 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ 1. 用户选择 6 个关卡 │
|
||
│ 2. 调用 POST /api/v1/share 创建分享 │
|
||
│ 3. 获取 8 位分享码 │
|
||
│ 4. 生成分享链接/二维码,分享给好友 │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 分享接收方 │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ 1. 通过分享链接/二维码进入游戏 │
|
||
│ 2. 解析分享码 │
|
||
│ 3. 调用 POST /api/v1/auth/wx-login 获取/确认身份 │
|
||
│ 4. 调用 POST /api/v1/share/{code}/join 加入挑战 │
|
||
│ 5. 获取 6 个关卡数据,开始挑战 │
|
||
│ 6. 每完成一关,调用 POST /api/v1/share/progress 上报进度 │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 客户端状态管理建议
|
||
|
||
```typescript
|
||
// 建议在客户端维护以下状态
|
||
|
||
interface ShareChallengeState {
|
||
isInChallenge: boolean; // 是否正在参与分享挑战
|
||
shareCode: string | null; // 当前分享码
|
||
currentLevelIndex: number; // 当前关卡索引(0-5)
|
||
levels: LevelData[]; // 关卡数据
|
||
progress: {
|
||
[levelId: string]: {
|
||
passed: boolean;
|
||
timeSpent: number;
|
||
withinTimeLimit: boolean;
|
||
};
|
||
};
|
||
}
|
||
```
|
||
|
||
### 分享链接格式建议
|
||
|
||
```
|
||
wechatminiprogram://pages/challenge?code=abc12345
|
||
```
|
||
|
||
或在微信中使用 `wx.openUrl` 打开 H5 页面,H5 页面再跳转到小程序。
|
||
|
||
---
|
||
|
||
## Cocos Creator 调用示例
|
||
|
||
### 1. HTTP 请求工具类
|
||
|
||
```typescript
|
||
// HttpManager.ts
|
||
import { Color } from 'cc';
|
||
|
||
export interface ApiResponse<T> {
|
||
success: boolean;
|
||
data: T | null;
|
||
message: string | null;
|
||
timestamp: string;
|
||
}
|
||
|
||
export class HttpManager {
|
||
private baseUrl = 'https://your-api-domain.com/api';
|
||
private token: string | null = null;
|
||
|
||
setToken(token: string) {
|
||
this.token = token;
|
||
}
|
||
|
||
getToken(): string | null {
|
||
return this.token;
|
||
}
|
||
|
||
async request<T>(
|
||
method: 'GET' | 'POST',
|
||
url: string,
|
||
body?: object,
|
||
): Promise<ApiResponse<T>> {
|
||
return new Promise((resolve, reject) => {
|
||
const xhr = new XMLHttpRequest();
|
||
xhr.open(method, this.baseUrl + url, true);
|
||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||
|
||
if (this.token) {
|
||
xhr.setRequestHeader('Authorization', `Bearer ${this.token}`);
|
||
}
|
||
|
||
xhr.onload = () => {
|
||
if (xhr.status >= 200 && xhr.status < 300) {
|
||
resolve(JSON.parse(xhr.responseText));
|
||
} else {
|
||
try {
|
||
const errorResp = JSON.parse(xhr.responseText);
|
||
reject(new Error(errorResp.message || '请求失败'));
|
||
} catch {
|
||
reject(new Error(`请求失败: ${xhr.status}`));
|
||
}
|
||
}
|
||
};
|
||
|
||
xhr.onerror = () => {
|
||
reject(new Error('网络错误'));
|
||
};
|
||
|
||
if (body) {
|
||
xhr.send(JSON.stringify(body));
|
||
} else {
|
||
xhr.send();
|
||
}
|
||
});
|
||
}
|
||
|
||
// GET 请求
|
||
async get<T>(url: string): Promise<ApiResponse<T>> {
|
||
return this.request<T>('GET', url);
|
||
}
|
||
|
||
// POST 请求
|
||
async post<T>(url: string, body: object): Promise<ApiResponse<T>> {
|
||
return this.request<T>('POST', url, body);
|
||
}
|
||
}
|
||
|
||
export const httpManager = new HttpManager();
|
||
```
|
||
|
||
### 2. 微信登录
|
||
|
||
```typescript
|
||
// 调用时机:小游戏启动时,或缓存的 token 过期时
|
||
async function wxLogin() {
|
||
// 1. 调用微信 wx.login 获取 code
|
||
const wxLoginRes = await new Promise<{ code: string }>((resolve, reject) => {
|
||
wx.login({
|
||
success: (res) => resolve({ code: res.code }),
|
||
fail: reject,
|
||
});
|
||
});
|
||
|
||
// 2. 将 code 发送到服务器换取 token
|
||
try {
|
||
const response = await httpManager.post<{
|
||
token: string;
|
||
user: { id: string; nickname: string | null; stamina: number };
|
||
}>('/v1/auth/wx-login', {
|
||
code: wxLoginRes.code,
|
||
});
|
||
|
||
if (response.success && response.data) {
|
||
// 3. 缓存 token
|
||
httpManager.setToken(response.data.token);
|
||
wx.setStorageSync('jwt_token', response.data.token);
|
||
|
||
console.log('登录成功,用户信息:', response.data.user);
|
||
return response.data;
|
||
} else {
|
||
throw new Error(response.message || '登录失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('登录失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 创建分享
|
||
|
||
```typescript
|
||
interface CreateShareResponse {
|
||
shareCode: string;
|
||
title: string;
|
||
levelCount: number;
|
||
}
|
||
|
||
async function createShare(
|
||
title: string,
|
||
levelIds: string[],
|
||
): Promise<CreateShareResponse> {
|
||
// 确保已登录
|
||
if (!httpManager.getToken()) {
|
||
await wxLogin();
|
||
}
|
||
|
||
const response = await httpManager.post<CreateShareResponse>('/v1/share', {
|
||
title,
|
||
levelIds,
|
||
});
|
||
|
||
if (response.success && response.data) {
|
||
console.log('分享创建成功:', response.data);
|
||
return response.data;
|
||
} else {
|
||
throw new Error(response.message || '创建分享失败');
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
const levelIds = [
|
||
'level_1',
|
||
'level_2',
|
||
'level_3',
|
||
'level_4',
|
||
'level_5',
|
||
'level_6',
|
||
];
|
||
const share = await createShare('一起来挑战!', levelIds);
|
||
console.log('分享码:', share.shareCode);
|
||
// 生成分享链接: `https://your-game.com/invite?code=${share.shareCode}`
|
||
```
|
||
|
||
### 4. 加入分享
|
||
|
||
```typescript
|
||
interface LevelData {
|
||
id: string;
|
||
level: number;
|
||
imageUrl: string;
|
||
answer: string;
|
||
hint1: string | null;
|
||
hint2: string | null;
|
||
hint3: string | null;
|
||
sortOrder: number;
|
||
}
|
||
|
||
interface JoinShareResponse {
|
||
shareCode: string;
|
||
title: string;
|
||
levels: LevelData[];
|
||
}
|
||
|
||
async function joinShare(shareCode: string): Promise<JoinShareResponse> {
|
||
// 确保已登录
|
||
if (!httpManager.getToken()) {
|
||
await wxLogin();
|
||
}
|
||
|
||
const response = await httpManager.post<JoinShareResponse>(
|
||
`/v1/share/${shareCode}/join`,
|
||
);
|
||
|
||
if (response.success && response.data) {
|
||
console.log('加入分享成功:', response.data);
|
||
return response.data;
|
||
} else {
|
||
throw new Error(response.message || '加入分享失败');
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
// 从分享链接中获取分享码
|
||
const shareCode = 'abc12345'; // 从 URL 参数解析
|
||
const shareData = await joinShare(shareCode);
|
||
// 保存关卡数据,开始游戏
|
||
```
|
||
|
||
### 5. 上报关卡进度
|
||
|
||
```typescript
|
||
interface ReportProgressResponse {
|
||
passed: boolean;
|
||
timeLimit: number | null;
|
||
withinTimeLimit: boolean;
|
||
}
|
||
|
||
async function reportLevelProgress(
|
||
shareCode: string,
|
||
levelId: string,
|
||
passed: boolean,
|
||
timeSpent: number,
|
||
): Promise<ReportProgressResponse> {
|
||
// 确保已登录
|
||
if (!httpManager.getToken()) {
|
||
await wxLogin();
|
||
}
|
||
|
||
const response = await httpManager.post<ReportProgressResponse>(
|
||
'/v1/share/progress',
|
||
{
|
||
shareCode,
|
||
levelId,
|
||
passed,
|
||
timeSpent,
|
||
},
|
||
);
|
||
|
||
if (response.success && response.data) {
|
||
console.log('进度上报成功:', response.data);
|
||
return response.data;
|
||
} else {
|
||
throw new Error(response.message || '进度上报失败');
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
async function onLevelComplete(levelId: string, timeSpent: number) {
|
||
const passed = true; // 根据游戏逻辑判断是否通过
|
||
const result = await reportLevelProgress(
|
||
this.shareCode,
|
||
levelId,
|
||
passed,
|
||
timeSpent,
|
||
);
|
||
|
||
if (result.passed) {
|
||
console.log(
|
||
`通关成功!${result.withinTimeLimit ? '在' : '超出'}时间限制内完成`,
|
||
);
|
||
if (result.timeLimit) {
|
||
console.log(`本关时间限制: ${result.timeLimit}秒`);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6. 启动流程示例
|
||
|
||
```typescript
|
||
// GameEntry.ts - 游戏入口脚本
|
||
import { _decorator, Component } from 'cc';
|
||
const { ccclass } = _decorator;
|
||
|
||
@ccclass('GameEntry')
|
||
export class GameEntry extends Component {
|
||
async start() {
|
||
// 1. 检查本地缓存的 token
|
||
const cachedToken = wx.getStorageSync('jwt_token');
|
||
if (cachedToken) {
|
||
httpManager.setToken(cachedToken);
|
||
}
|
||
|
||
// 2. 尝试登录(无论是否有缓存 token)
|
||
try {
|
||
await wxLogin();
|
||
console.log('登录成功');
|
||
} catch (error) {
|
||
console.error('登录失败:', error);
|
||
}
|
||
|
||
// 3. 检查是否有分享码(从启动参数或 URL 获取)
|
||
const launchOptions = wx.getLaunchOptionsSync();
|
||
console.log('启动参数:', launchOptions);
|
||
|
||
// 解析分享码(具体解析方式根据你的分享链接格式)
|
||
// if (launchOptions.query && launchOptions.query.code) {
|
||
// await joinShare(launchOptions.query.code);
|
||
// }
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 关卡数据结构
|
||
|
||
### LevelData 完整字段
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --------- | -------------- | --------------- |
|
||
| id | string | 关卡唯一标识 |
|
||
| level | number | 关卡序号(1-6) |
|
||
| imageUrl | string | 关卡图片 URL |
|
||
| answer | string | 正确答案 |
|
||
| hint1 | string \| null | 第 1 个提示 |
|
||
| hint2 | string \| null | 第 2 个提示 |
|
||
| hint3 | string \| null | 第 3 个提示 |
|
||
| sortOrder | number | 排序顺序 |
|
||
|
||
### 关卡时间限制说明
|
||
|
||
`timeLimit` 字段在关卡数据结构中**不直接返回**,而是通过上报进度接口返回。
|
||
|
||
如果需要在前端判断时间限制:
|
||
|
||
1. 用户完成关卡后调用 `reportLevelProgress`
|
||
2. 从返回的 `timeLimit` 字段获取当前关卡的时间限制
|
||
3. 从返回的 `withinTimeLimit` 字段判断是否在时间内完成
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
|
||
1. **Token 有效期**:JWT Token 有效期为 7 天,客户端应缓存并在启动时使用
|
||
2. **重复通关**:首次通关记录会被保留,重复通关不会覆盖之前的记录
|
||
3. **分享码格式**:8 位字母数字组合,大小写敏感
|
||
4. **关卡数量**:每次分享挑战固定包含 6 个关卡
|
||
5. **网络异常**:建议在调用接口时显示 loading 状态,并处理网络异常情况
|
||
6. **hint 字段**:`hint1/hint2/hint3` 可能为 `null`,表示该提示未配置
|
||
7. **时间限制**:`timeLimit` 为 `null` 表示该关卡没有时间限制
|