# 页面创建信息

## 0. 建页规则关联

- 页面改动、模块补充、接口对接、样式同步和文档同步，都需要先联系 `docs/活动页/建页/` 下的建页规则执行。
- 如果建页规则变化，先更新 `docs/活动页/建页/` 下的建页 md，再同步当前页面的 `页面创建信息.md` 和 Vue 代码。
- 新建或更新页面时，页面模块、div 结构、方法、接口处理都需要补充中文注释。
- 能使用内联样式处理的页面样式，优先使用内联样式。
- 页面根容器使用 `height: 100vh`，内部主内容垂直滚动并隐藏滚动条，页面任何模块都不展示可见滚动条。
- 接口返回的远程图片链接不要使用 `v-smart-img`，直接使用原生 `<img :src="接口字段">` 展示，不走本地图片缓存。
- 页面使用接口前必须先调用 `connectToApp` 设置请求头；当前首页的 `completePreloading()` 只负责阻塞图片预加载并控制 loading，排行榜和赛事列表接口已提取到 `loadInitialWorldCupData()`，在 `connectToAppHandler()` 连接成功后独立请求。

## 1. 基础信息

- 页面类型：Activities
- 页面名称：WorldCup
- 页面目录：`src/views/Activities/WorldCup`
- 路由 path：`/activities/world-cup`
- 页面入口：`src/views/Activities/WorldCup/index.vue`
- 页面配置：`src/views/Activities/WorldCup/page.config.js`
- 专用接口：`src/api/worldCup.js`
- 接口说明：`src/views/Activities/WorldCup/世界杯接口说明.md`
- 参考页面：排行榜用户信息和等级展示规则参考 `src/views/Activities/GulbenFestival/index.vue` 的 `personalRank` / `Ranking` 模块。

## 2. 路由和权限

```js
{
  path: '/activities/world-cup',
  name: 'world-cup',
  component: () => import('../views/Activities/WorldCup/index.vue'),
  meta: { requiresAuth: false },
}
```

- `src/config/security.js` 已加入 `/activities/world-cup`。
- `src/utils/permissionManager.js` 已加入 `/activities/world-cup`。

## 3. OSS 图片目录

- OSS 相对路径：`Activities/WorldCup/`
- 本地开发目录：`public/oss/h5/Azizi/Activities/WorldCup/`
- 本地目录只作为开发期 OSS 图片镜像使用，发布时图片由用户上传到真实 OSS。

### 3.1 当前图片清单

| 图片名 | 备注 |
| --- | --- |
| bg2 | 页面最低层背景图，宽高和页面一致，直接拉伸填满 |
| bg | 头部背景图 |
| ruleBt | 头部右侧规则按钮 |
| ruleBg | 规则弹窗背景 |
| RankingMain | 排行榜主背景 |
| RankingBottomBorder | 排行榜底部边框 |
| RankingItem | 第四名以后的排行榜列表项背景 |
| frameTop1 | 第一名头像框 |
| frameTop2 | 第二名头像框 |
| frameTop3 | 第三名头像框 |
| giftBt | RankingMain 右上角奖励按钮 |
| rewardBg | 奖励弹窗背景 |
| showMoreBt | 展开 / 收起排行榜按钮图标 |
| myRankingBg | 我的排名贴底背景 |
| EventlistBg | 赛事列表模块背景 |
| matchBg | Predict Now 赛事列表项背景 |
| drawSign | Ended 平局押注项图片 |
| frameWinner | Ended 列表胜方或平局结果叠加框 |
| numPeople | 赛事列表右上角参与人数图标 |
| rightBt | 总奖池入口右侧箭头 |
| vip1-vip6 | VIP 等级图片 |
| userLevel1Bg-userLevel5Bg | 魅力等级背景图，图片名沿用旧素材命名 |
| wealthLevel1Bg-wealthLevel5Bg | 财富等级背景图 |

### 3.2 阻塞预加载图片

`bg2`、`bg`、`ruleBt`、`ruleBg`、`EventlistBg`、`matchBg`、`drawSign`、`frameWinner`、`numPeople`、`rightBt`、`RankingMain`、`RankingItem`、`RankingBottomBorder`、`myRankingBg`、`giftBt`、`rewardBg`、`showMoreBt`、`frameTop1`、`frameTop2`、`frameTop3`、`vip1-vip6`、`userLevel1Bg-userLevel5Bg`、`wealthLevel1Bg-wealthLevel5Bg`。

页面进入时必须展示统一加载动画，阻塞图片预加载完成后再展示主内容；业务接口不参与 loading 阻塞。

### 3.3 itemCenter 高度约束

`itemCenter` 的 `min-height` 按 `Math.ceil(实际展示宽度vw * 图片高度 / 图片宽度)` 计算；为避免图片加载出来之前容器高度异常撑高，同步设置接近的 `max-height`，当前页面默认按 `max-height = min-height + 1vw` 补充：

| 使用位置 | 图片 | 实际展示宽度 | min-height | max-height |
| --- | --- | --- | --- | --- |
| 头部模块 | `bg` | `100vw` | `151vw` | `152vw` |
| 排行榜主图区 | `RankingMain` | `100vw` | `92vw` | `93vw` |
| 第四名以后排行榜项 | `RankingItem` | `100vw` | `23vw` | `24vw` |
| 首页魅力等级徽章 | `userLevel1Bg-userLevel5Bg` | `12vw` | `7vw` | `8vw` |
| 首页财富等级徽章 | `wealthLevel1Bg-wealthLevel5Bg` | `12vw` | `6vw` | `7vw` |
| 赛事列表模块 | `EventlistBg` | `100vw` | `244vw` | `245vw` |
| Predict Now 赛事卡片 | `matchBg` | `84vw`，来自 `EventlistBg` 内容区左右 `8vw` padding 后的可用宽度 | `52vw` | `53vw` |
| 我的排名贴底模块 | `myRankingBg` | `100vw` | `21vw` | `22vw` |
| 奖励弹窗 | `rewardBg` | `100vw` | `225vw` | `226vw` |
| 规则弹窗 | `ruleBg` | `100vw` | `178vw` | `179vw` |
| `components/topUser.vue` 等级徽章 | `userLevel*Bg` / `wealthLevel*Bg` | 当前父级最大 `26vw` 的 `30%` | `4vw` | `5vw` |

## 4. 页面结构

### 4.1 根容器

- 根容器背景色：`#101E14`。
- 最底层背景图：`bg2`，使用绝对定位铺满 `100vw * 100vh`。
- 主内容：`flex-direction: column`，`gap: 2vw`，内部滚动并隐藏滚动条。
- 底部固定展示我的排名模块，主内容底部保留 `20vw` 占位，防止遮挡。

### 4.2 头部模块

- 使用 `itemCenter` 承载 `bg`。
- 通过 `contentStyle` 设置 padding。
- 内部右侧放置 `ruleBt` 图片按钮。
- 点击 `ruleBt` 打开规则弹窗。

### 4.3 排行榜模块

- 使用 `RankingMain` 的 `itemCenter` 承载排行榜主内容。
- `RankingMain` 的 `contentStyle` 为 column，`justify-content: space-between`。
- `RankingMain` 内部右上角绝对定位 `giftBt`，宽度暂定 `10vw`，点击打开奖励弹窗。
- `RankingMain` 内部上方为前三名模块，下方为 `Show All Users` 展示更多模块。
- 前三名为同一行，使用 `direction: ltr`，排序为左二、中一、右三，并设置 `align-items: flex-end`。
- 前三名私有组件为 `components/topUser.vue`，头像框分别使用 `frameTop1`、`frameTop2`、`frameTop3`。
- `topUser` 内部从上到下展示头像、名称、ID、等级行，整体使用 column 并水平居中。
- 等级行展示 VIP、魅力等级、财富等级，展示规则参考 `GulbenFestival` 的 `personalRank` / `Ranking`。
- 魅力等级字段使用接口返回的 `charmLevel`，当前页面暂不使用 `userLevel` 字段。
- 带 `{{ $t('user_id_prefix') }}` 的 ID 展示后面使用接口字段 `account`。
- 第四名以后列表使用 `RankingItem`，列表项右侧贡献值前加 `/src/assets/icon/Azizi/coin.png`，金币和贡献值使用 flex 居中。
- `Show All Users` 只有在第四名以后存在数据时展示。
- 点击 `Show All Users` 后展示第四名以后列表和 `RankingBottomBorder`。
- `Collapse List` 放在 `RankingBottomBorder` 下方，点击后隐藏第四名以后列表。
- `Collapse List` 下方保留 `20vw` 底部占位。
- 我的排名贴底模块使用 `myRankingBg` 的 `itemCenter`，固定在页面底部，不需要标签页判断。

### 4.4 赛事列表模块

- 赛事列表放在排行榜模块下方，使用 `EventlistBg` 的 `itemCenter`。
- `EventlistBg` 内部样式为 column，`justify-content: flex-start`，`align-items: center`，`gap: 2vw`，`padding: 0`。
- 第一个小模块为文字切换按钮，左右各占 50%。
- 切换按钮文案为 `Predict Now!` 和 `Ended`。
- 选中按钮使用金色边框和渐变背景，文本颜色 `#FFE083`。
- 未选中按钮文本颜色 `#E9CB78`。
- 第二个小模块为赛事列表区域，`flex: 1`，可垂直滚动并隐藏滚动条。
- 接口使用 `apiPostWorldCupMatchList({ size: 10, current: 1, tab })`。
- `Predict Now!` 对应 `tab: 0`，`Ended` 对应 `tab: 1`。
- 每次切换按钮都重新发送接口，暂不做上拉加载。
- 两个 tab 的数据必须分开承载：`predictMatchList` 保存 Predict Now 数据，`endedMatchList` 保存 Ended 数据，避免接口慢返回时两个列表数据混用。
- 请求保护使用每个 tab 独立的 request token：只忽略同 tab 内更旧的响应，不影响另一个 tab 的缓存。
- 赛事列表两个 tab 的列表容器使用 `v-show` 保留 DOM，不使用 `v-if` 销毁重建；切换时仍重新请求接口获取最新数据，但固定图片如 `matchBg`、`numPeople`、`rightBt`、`drawSign` 不随 tab 切换反复重建加载。
- Predict Now 使用 `matchBg` 的 `itemCenter` 渲染列表项。
- Ended 不使用 `matchBg`，直接使用普通 `div` 卡片样式渲染列表项。
- Predict Now 和 Ended 的单条列表都使用新的 column wrapper：上方是原赛事卡片，下方是总奖池入口，用 `gap` 控制上下间距。
- 总奖池入口样式为金色描边渐变背景，内部一行展示 `TOTAL PRIZE POOL`、coin 图片、总奖池金额和右侧 `rightBt`；金额优先展示 `totalPoolFormat`，没有格式化字段时回退 `totalPool`。
- 点击总奖池入口进入 `/activities/world-cup/event-detail?matchId=...`，详情页目录为 `src/views/Activities/WorldCup/EventDetail`。
- 赛事 `matchId` 必须按字符串保留和传递，禁止使用 `Number(matchId)`；比赛 ID 可能超过 JS 安全整数范围，转数字会丢失精度。

Predict Now 列表项规则：

- 每条记录使用 `matchBg` 的 `itemCenter`。
- `matchBg` 内部从上往下分为状态行和赛事简要信息行。
- 状态行左侧按 `status` 和 `joined` 展示三种状态：
- `status === 1 && joined === true`：绿色小圆点加文本 `join`，绿色渐变背景。
- `status === 1 && joined === false`：文本 `Not Joined`，灰色渐变背景。
- 其他状态：文本 `Kick-off: HH:mm(UTC+8)`，时间来自 `kickoffTime`。
- 状态行右侧展示 `numPeople` 图片和 `joinCount`。
- 赛事简要信息行为三列：主国、平局、客国。
- 主国展示 `homeFlag`、`homeTeam`、`homePercent`。
- 平局展示 `DRAW`、`drawPercent`，不放旗帜。
- 客国展示 `awayFlag`、`awayTeam`、`awayPercent`。
- 主国和客国占比颜色为 `#71CE6E`，平局占比颜色为 `#A0A181`。

Ended 列表项规则：

- 每条记录使用普通 `div`，样式为 `border-radius: 10.682px; border: 0.89px solid #5D6948; background: linear-gradient(180deg, #0B2415 0%, #051C12 100%); backdrop-filter: blur(12.687064170837402px);`。
- 内部从上往下分为状态行和押注选项行。
- 状态行左侧按参与结果展示三种状态：参与且中奖、参与未中奖、未参与。
- 参与且中奖展示 `+`、coin 图片和中奖金额；中奖金额优先使用 `myPayout`，并兼容 `payout` / `winAmount` 字段。
- 参与未中奖展示 `Better luck next time`。
- 未参与展示 `Not Joined`。
- 状态行右侧展示 `numPeople` 图片和 `joinCount`，和 Predict Now 保持一致。
- 押注选项行分为主方、平局、客方三列，仅做展示，不绑定押注点击。
- 未选中押注项使用默认金色边框渐变样式；选中押注项使用黄色描边和发光阴影样式。
- 选中态根据当前用户在各选项的累计下注额判断：`myHomeBetFormat`、`myDrawBetFormat`、`myAwayBetFormat` 不为空的押注项展示选中态，支持多个押注项同时选中。
- 主方展示 `homeFlag`、`homeTeam`、`homePercent`。
- 平局展示 `drawSign`、`DRAW`、`drawPercent`。
- 客方展示 `awayFlag`、`awayTeam`、`awayPercent`。
- Ended 押注项旗帜图片来源不同：主客方 `homeFlag` / `awayFlag` 是接口返回链接，不使用 `v-smart-img`；平局 `drawSign` 是 OSS 素材，单独使用 `v-smart-img`。
- Ended 列表用户押注过的选项都使用选中态样式；胜方或平局结果根据接口 `result` 字段判断，`1` 主胜、`2` 平局、`3` 客胜，并在对应押注项图片容器内叠加 `frameWinner`，该图片绝对定位贴顶、宽度 `100%`、最高层展示。
- 押注项金额信息分为上下两行，中间使用 `background: #E9D089` 分割线；上方展示选项奖池 `homePoolFormat` / `drawPoolFormat` / `awayPoolFormat`，下方展示用户累计下注额 `myHomeBetFormat` / `myDrawBetFormat` / `myAwayBetFormat`，空值展示 `0`。
- 首页 `Ended` 列表和详情页押注对象虽然字段接近，但样式按各自当前页面实现为准，后续只按需求调整字段或内容，不直接合并样式。

### 4.5 弹窗模块

- 规则弹窗和奖励弹窗都使用公共组件 `src/components/MaskLayer.vue`。
- 规则弹窗使用 `ruleBg` 的 `itemCenter`，弹窗宽度暂定 `100vw`。
- 奖励弹窗使用 `rewardBg` 的 `itemCenter`，弹窗宽度暂定 `100vw`。
- `ruleBg` 和 `rewardBg` 图片高度偏长，弹窗内容不做垂直居中，外层保留顶部和底部空白区域，点击空白区域退出弹窗。
- 奖励弹窗当前暂未配置 `templateId`，先不调用 `src/api/activity.js` 的 `getThisWeekRewardsAndGifts`，使用本地静态数据模拟接口奖励结构。
- 奖励弹窗静态数据为第 1、2、3 名各 5 个金币类型奖励，奖励类型为 `GOLD`，数量和展示内容均为 `1`。
- `rewardBg` 的 `itemCenter` 内部使用 `flex-direction: column` 从上往下展示三名次奖励，名次之间用 `gap` 控制间距。
- 单个名次奖励布局为左侧 1 个重点奖励，右侧 2 行 2 列共 4 个奖励；左右模块使用 `gap` 分隔，右侧模块 `flex: 1`。
- 奖励项模块使用 `flex-direction: column`，背景图内已有奖励框，页面只用占位 `div` 居中奖励图；左侧奖励图占位比例为 `158.634/175.179`，右侧奖励图占位比例为 `1/1`。
- 奖励信息展示复用 `src/utils/rewardFormatter.js` 的 `formatRewardDisplay(type, reward)`，图片加载失败时复用 `handleRewardImageError(event, reward.type)`。

## 5. 接口说明

- `apiPostWorldCupMatchList(data)`：比赛列表，`POST /activity/worldcup/match/list`。
- `apiPostWorldCupBet(data)`：押注，`POST /activity/worldcup/bet`。
- `apiPostWorldCupMyBetList(data)`：我的竞猜记录，`POST /activity/worldcup/my/bet/list`。
- `apiPostWorldCupRankList()`：排行榜，`POST /activity/worldcup/rank/list`，接口无业务参数但 POST 仍传 `{}`。
- `apiGetWorldCupMatchDetail(matchId)`：比赛详情，`GET /activity/worldcup/match/detail`。
- `getThisWeekRewardsAndGifts(templateId)`：奖励弹窗未来使用的公共奖励接口，来源为 `src/api/activity.js`；当前因为暂无 `templateId`，页面先使用本地静态金币奖励数据。
- 详细字段和返回示例见 `src/views/Activities/WorldCup/世界杯接口说明.md`。

### 5.1 排行榜字段映射

- `rankingList` 映射为页面排行榜列表。
- `currentUserRank` 映射为我的排名贴底模块。
- `rank` 使用接口字段 `rank`。
- 用户资料点击使用接口字段 `userId`，接口没有 `rawUserId`。
- 头像使用 `avatar`。
- 昵称使用 `nickname`。
- ID 展示使用 `account`。
- 页面贡献值 `amount` 使用 `quantity`。
- `quantityNumber` 原样保留。
- 魅力等级使用 `charmLevel`。
- 财富等级使用 `wealthLevel`。
- VIP 使用 `vipLevelName`。

### 5.2 赛事列表字段映射

- `matchId`：比赛 ID。
- `homeTeam` / `awayTeam`：主国 / 客国名称。
- `homeFlag` / `awayFlag`：主国 / 客国旗帜，属于接口远程图片，直接用原生 `<img>` 展示，不加 `v-smart-img`。
- `kickoffTime`：开赛时间戳，页面格式化为 `HH:mm(UTC+8)`。
- `status`：比赛状态，`0` 未开赛，`1` 比赛中，`2` 已结束。
- `joined`：当前用户是否已参与本场竞猜。
- `joinCount`：参与人次。
- `homePercent` / `drawPercent` / `awayPercent`：主胜 / 平局 / 客胜占比。

## 6. 建页后记录

### 6.1 已完成

- 创建页面目录 `src/views/Activities/WorldCup/`。
- 创建页面信息文档 `src/views/Activities/WorldCup/页面创建信息.md`。
- 创建页面配置 `src/views/Activities/WorldCup/page.config.js`。
- 创建世界杯接口文件 `src/api/worldCup.js`。
- 创建接口说明文档 `src/views/Activities/WorldCup/世界杯接口说明.md`。
- 创建本地开发图片目录 `public/oss/h5/Azizi/Activities/WorldCup/`。
- 写入路由 `/activities/world-cup`。
- 写入安全白名单和活动页权限列表。
- 完成统一加载动画、页面背景、头部模块、排行榜前三名、排行榜接口、展开 / 收起列表交互。
- 完成我的排名贴底模块、奖励弹窗入口、规则弹窗。
- 完成赛事列表初版、Predict Now 列表项、赛事列表 tab 数据分开承载。
- 完成 Predict Now / Ended 列表项下方总奖池入口，并接入赛事详情页路由。
- 创建赛事详情页占位目录 `src/views/Activities/WorldCup/EventDetail/` 和页面创建信息 md。

### 6.2 后续补充

- Ended 列表已完成当前接口字段下的展示结构，后续如果接口补充用户押注选项、派彩金额、单项奖池和押注金额，只需要更新字段映射。
- 比赛详情、下注弹窗、奖励弹窗内容后续补充。
- 规则弹窗内容后续补充。

### 6.3 验证记录

- `npm run build`：已运行并通过。
- 构建中存在项目原有 warning：`secondBg.png` 运行时解析提示，以及 `src/utils/http.js` 动静态混用 chunk 提示。
