# 超级嘉年华前端对接文档

面向 App/H5 和管理后台。协议约定与 [Splay 2.0 前端对接文档](./splay_2_0_frontend_integration.md) 第 2 节相同：`POST`、登录态、`snake_case`、金额为 decimal 字符串、时间为 Unix 秒（`int64` 在 JSON 里可能是字符串或数字）。

## 1. 活动规则（给前端展示用）

| 项 | 说明 |
| --- | --- |
| 活动名 | 超级嘉年华 |
| 开始时间 | **2026-08-18 00:00:00（东七区）** |
| 结束时间 | **2026-08-18 19:00:00（东七区）** |
| 计奖窗口 | `[开始, 结束)`：结束时刻起**不再计新邀请**，只做统一入账 |
| 谁能拿奖励 | 仅 **1.0 迁移用户**，且已选 **加速方案**、有正数金库、金库未完成解锁 |
| 谁能贡献 | **2.0 新用户**（普通 / 泡沫），在活动期内 **生涯首次**升至 Lv2（降级再升不计） |
| 不计贡献 | **1.0 老用户**升级；活动开始前已达过 Lv2 的下级 |
| 奖励规则 | 每有效 1 人 **+10%** 解锁 1.0 金库总额；最多 **10 人 / 100%** |
| 上限 | 实际待解锁 ≤ 当前金库剩余锁定 |
| 入账时机 | 活动期间只记 **待解锁**；结束后由服务端统一写入金库已解锁（无需用户点领取） |

前端**不要自己硬编码判断是否结束**，一律以后端 `activity_started` / `activity_ended` / `ended_at` 为准。开始时间如需倒计时，可按上表东七区写死，或用 `ended_at` 反推（本期开始 = 结束日 00:00）。

## 2. 活动状态

主接口：`POST /v1/super_carnival/get`

| `activity_started` | `activity_ended` | 页面状态 | 建议 UI |
| --- | --- | --- | --- |
| `false` | `false` | 未开始 | 倒计时 / 敬请期待 |
| `true` | `false` | 进行中 | 正常展示邀请进度、待解锁、滚动播报 |
| `true` | `true` | 已结束 | 展示结束态；`pending_unlock` 会在结算后变为 `"0"`，金额已并入 `unlocked_base` |

```ts
type SuperCarnivalState = "not_started" | "running" | "ended";

function superCarnivalState(started: boolean, ended: boolean): SuperCarnivalState {
  if (!started) return "not_started";
  if (ended) return "ended";
  return "running";
}
```

倒计时到结束：用 `ended_at`（Unix 秒）相对本地时钟即可。

## 3. C 端接口

需登录。金额字段均为字符串。

### 3.1 活动页主数据

`POST /v1/super_carnival/get`

请求：空 body `{}`

响应：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `activity_started` | bool | 是否已到开始时间 |
| `activity_ended` | bool | 是否已到结束时间（结束后不再计新邀请） |
| `ended_at` | int64 | 结束时间 Unix 秒 |
| `eligible` | bool | 当前用户是否具备**计奖资格**（见下） |
| `scheme` | enum | 金库方案：`NONE` / `REGULAR` / `ACCELERATED`（也可能是数字 0/1/2） |
| `original_total` | string | 1.0 金库总额；非迁移用户为 `"0"` |
| `pending_unlock` | string | 本活动**待解锁**；已按剩余锁定封顶；已结算为 `"0"` |
| `unlocked_base` | string | **已解锁展示额**（见下表） |
| `valid_invite_count` | int32 | 有效邀请人数，**0～10** |
| `pending_lv2_count` | int64 | 直系 2.0（普通+泡沫）中**尚未生涯达 Lv2** 的人数；查询失败时可能为 `0` |
| `invite_code` | string | 当前用户邀请码 `fans_code`；无则 `""` |
| `ticks` | array | 全站滚动播报（约 20 条，服务端短缓存） |

#### `eligible` 为 true 的条件

同时满足：

1. 1.0 迁移用户且有金库  
2. 方案为 **加速** `ACCELERATED`  
3. `original_total > 0`  
4. 金库未完成（未标记完成 / 未全部解锁完成）

`eligible = false` 时仍可进活动页：可看规则、播报、自己的邀请码；但**不应**展示「正在累计解锁」主进度（或置灰说明需加速方案）。

#### `unlocked_base` 怎么展示

| 阶段 | 含义 |
| --- | --- |
| 进行中 | 金库当前已解锁（不含本活动待解锁） |
| 已结束且未结算完 | `金库已解锁 + pending_unlock`（预览入账后） |
| 已结算 | 金库已解锁（已含本活动入账）；此时 `pending_unlock` 为 `"0"` |

建议主数字：

- 待解锁：`pending_unlock`
- 已解锁 / 累计解锁展示：`unlocked_base`
- 进度条比例：`valid_invite_count / 10`，或文案 `{valid_invite_count * 10}%`（封顶 100%）

理论解锁额也可本地估算：`original_total * min(valid_invite_count, 10) / 10`，但**最终以 `pending_unlock` 为准**（会再被剩余锁定截断）。

#### `ticks[]`

| 字段 | 说明 |
| --- | --- |
| `nickname` | 邀请人昵称 |
| `ratio` | 展示文案，如 `"+10%"` / `"+30%"` / `"100%"`（该邀请人**当前累计**进度，不是单次增量） |

播报失败时 `ticks` 可能为空数组，页面不要报错。

### 3.2 示例响应

进行中、有资格：

```json
{
  "code": 0,
  "data": {
    "activity_started": true,
    "activity_ended": false,
    "ended_at": 1755514800,
    "eligible": true,
    "scheme": "ACCELERATED",
    "original_total": "4470",
    "pending_unlock": "1341",
    "unlocked_base": "223.5",
    "valid_invite_count": 3,
    "pending_lv2_count": 12,
    "invite_code": "F123456",
    "ticks": [
      { "nickname": "Ada", "ratio": "+30%" },
      { "nickname": "Bob", "ratio": "+10%" }
    ]
  }
}
```

2.0 用户（无计奖资格，仍可分享）：

```json
{
  "code": 0,
  "data": {
    "activity_started": true,
    "activity_ended": false,
    "ended_at": 1755514800,
    "eligible": false,
    "scheme": "NONE",
    "original_total": "0",
    "pending_unlock": "0",
    "unlocked_base": "0",
    "valid_invite_count": 0,
    "pending_lv2_count": 5,
    "invite_code": "F998877",
    "ticks": []
  }
}
```

> `ended_at` 示例值请以实际接口为准，不要写死文档里的数字。

## 4. 页面状态机建议

```
进入活动页
  → POST /v1/super_carnival/get
  → 未开始：倒计时到 2026-08-18 00:00（东七区）
  → 进行中：
       eligible=true  → 展示 original_total / pending_unlock / unlocked_base / 邀请进度
       eligible=false → 展示规则 + 邀请码（若有）+ 引导去选加速方案
  → 已结束：结束文案；用 unlocked_base 展示最终已解锁；pending 结算中可能短暂非 0
滚动播报
  → 使用同接口 ticks，可定时轮询 get（30s 内 ticks 可能不变）
邀请
  → 分享 invite_code；引导下级升到 Lv2
```

文案建议：

> 活动期间，你的 2.0 好友（新用户）生涯首次升到 Lv2，你可获得 1.0 金库 10% 待解锁，最多 10 人共 100%。活动结束后统一解锁到金库。

无资格引导：

> 需为 1.0 迁移用户并选择加速解锁方案后参与计奖。

## 5. 管理后台接口（简述）

需管理员登录。

| 接口 | 说明 |
| --- | --- |
| `POST /v1/man/super_carnival/overview/get` | 总览：时间窗、参与人数、有效邀请合计、待解锁/已入账合计、已结算人数 |
| `POST /v1/man/super_carnival/user/list` | 参与用户分页；可筛 `user_id` / `scheme` / `settled`（0全部 1已结算 2未结算） |
| `POST /v1/man/super_carnival/invite/list` | 某邀请人的有效邀请明细；请求必填 `user_id` |

金额、待解锁口径与 C 端一致：待解锁按金库剩余锁定**现场计算**；已结算用户待解锁为 0。

## 6. 联调注意

- 金额用字符串展示，不要用 JS `number` 做累加/比较金额。
- 空金额按 `"0"` 处理。
- **没有领取按钮**：结束后服务端自动入账，前端只需刷新 `get`。
- 结束后短时间内 `activity_ended=true` 且 `pending_unlock` 仍可能 > 0（结算 cron 尚未跑完），属正常；可提示「结算中」或继续轮询。
- `valid_invite_count` 最大 10；进度 UI 不要超过 100%。
- `pending_lv2_count` 失败降级为 0，不要因此整页报错。
- 本期测试窗：**8.18 00:00～19:00（东七区）**；上线改窗后只改后端配置，前端继续信接口布尔与 `ended_at`。
