From ac77b82c51234e779b99fc8a51cdf721d15f44d6 Mon Sep 17 00:00:00 2001
From: hzj <1304805162@qq.com>
Date: Thu, 14 May 2026 16:07:43 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B4=BB=E5=8A=A8=E9=A1=B5=E9=9D=A2?=
=?UTF-8?q?=E5=BB=BA=E7=AB=8B=E6=B5=81=E7=A8=8B=E6=A8=A1=E5=9D=97=E5=8C=96?=
=?UTF-8?q?=E5=B0=9D=E8=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/活动页/幸运美金第6季创建记录.md | 302 ++++++++++
docs/活动页/建页执行流程.md | 223 ++++++++
docs/活动页/建页操作模板.md | 447 +++++++++++++++
docs/活动页/建页资料库.md | 532 ++++++++++++++++++
docs/活动页/快速建立活动页方案.md | 65 +++
.../Season6/页面创建信息-完整参考.md | 429 ++++++++++++++
.../Season6/页面创建信息-待填写.md | 433 ++++++++++++++
vite.config.js | 21 +
8 files changed, 2452 insertions(+)
create mode 100644 docs/活动页/幸运美金第6季创建记录.md
create mode 100644 docs/活动页/建页执行流程.md
create mode 100644 docs/活动页/建页操作模板.md
create mode 100644 docs/活动页/建页资料库.md
create mode 100644 docs/活动页/快速建立活动页方案.md
create mode 100644 src/views/Activities/LuckyDollars/Season6/页面创建信息-完整参考.md
create mode 100644 src/views/Activities/LuckyDollars/Season6/页面创建信息-待填写.md
diff --git a/docs/活动页/幸运美金第6季创建记录.md b/docs/活动页/幸运美金第6季创建记录.md
new file mode 100644
index 0000000..b09b67e
--- /dev/null
+++ b/docs/活动页/幸运美金第6季创建记录.md
@@ -0,0 +1,302 @@
+# LuckyDollars Season6 创建记录
+
+本文记录按《快速建立活动页方案》创建 `LuckyDollars Season6` 新活动页的实际步骤。Season6 被当作一个新页面处理:有独立页面目录、独立图片目录、独立页面配置、独立路由和权限配置。为了先验证页面效果,接口参数暂时沿用 Season5。
+
+## 1. 确认页面类型和模块
+
+Season6 属于复杂活动页,包含以下模块:
+
+```text
+reward
+ranking
+lottery
+task
+withdraw
+history
+rule
+```
+
+页面功能目标:先做出与 Season5 行为一致的活动页,后续再替换 Season6 的真实图片和活动接口参数。
+
+## 2. 创建页面目录
+
+创建新页面目录:
+
+```text
+src/views/Activities/LuckyDollars/Season6/
+src/views/Activities/LuckyDollars/Season6/components/
+```
+
+创建后包含:
+
+```text
+src/views/Activities/LuckyDollars/Season6/index.vue
+src/views/Activities/LuckyDollars/Season6/page.config.js
+src/views/Activities/LuckyDollars/Season6/components/Barrage.vue
+src/views/Activities/LuckyDollars/Season6/components/topUser.vue
+```
+
+说明:当前 UI 结构参考 Season5 的实现,但 Season6 已抽出自己的页面配置,不直接依赖 Season5 的图片路径和路由。
+
+## 3. 创建页面配置
+
+新增:
+
+```text
+src/views/Activities/LuckyDollars/Season6/page.config.js
+```
+
+配置内容包括:
+
+```js
+export const pageConfig = {
+ name: 'LuckyDollars Season6',
+ routePath: '/activities/lucky-dollars-season6',
+ assetPath: 'Activities/LuckyDollars/Season6/',
+
+ activity: {
+ activityId: '2007771533999124198',
+ activityCode: '24198',
+ rewardActivityType: 'CONSUMPTION_ACTIVITY',
+ rankingActivityType: 'USER_RECHARGE_DRAW',
+ },
+
+ modules: ['reward', 'ranking', 'lottery', 'task', 'withdraw', 'history', 'rule'],
+}
+```
+
+当前 `activityId` 和 `activityCode` 沿用 Season5,仅用于验证 Season6 页面是否完整可用。后续接真实 Season6 活动时,只改这里。
+
+## 4. 配置图片路径
+
+页面中不写死路径,统一从 `pageConfig.assetPath` 获取:
+
+```js
+const imageUrl = (filename) => getPngUrl(pageConfig.assetPath, filename)
+const webpUrl = (filename) => getWebpUrl(pageConfig.assetPath, filename)
+```
+
+Season6 的图片路径为:
+
+```text
+Activities/LuckyDollars/Season6/
+```
+
+开发环境会解析为:
+
+```text
+/oss/h5/Azizi/Activities/LuckyDollars/Season6/xxx.png
+```
+
+## 5. 创建本地 OSS 图片目录
+
+创建:
+
+```text
+public/oss/h5/Azizi/Activities/LuckyDollars/Season6/
+```
+
+这个目录用于开发阶段模拟真实 OSS。你已把 Season6 图片补充到该目录。
+
+注意:`vite.config.js` 中 `/oss` 开发代理已调整为“本地 public 文件优先,本地不存在再代理 CDN”。这样本地 Season6 图片可以直接被 dev server 读取。
+
+## 6. 按图片清单配置多语言
+
+Season6 使用图片清单中的支持语言生成多语言图片回退。
+
+在 `page.config.js` 中声明哪些图片支持语言后缀:
+
+```js
+images: {
+ localized: {
+ helpInfo: ['ar'],
+ historyBg: ['ar'],
+ historyBt: ['ar'],
+ lotteryBt: ['ar'],
+ lotteryBtActive: ['ar'],
+ rankBt: ['ar'],
+ rankBtActive: ['ar'],
+ rewardReceive: ['ar'],
+ rewardReceived: ['ar'],
+ rewardReceiveNot: ['ar'],
+ rulesBt: ['ar'],
+ ruleInfo: ['ar'],
+ },
+}
+```
+
+页面中使用:
+
+```js
+const getImgName = (filename) => {
+ return getLocalizedImageName(filename, currentLangType.value)
+}
+```
+
+如果当前语言没有对应图片,自动回退原图,避免请求不存在的图片。
+
+## 7. 配置排行榜模式
+
+Season6 的排行榜沿用 Season5 接口,属于服务端分页模式:
+
+```js
+ranking: {
+ loadMode: 'remote',
+ pageSize: 20,
+ topCount: 3,
+}
+```
+
+数据流:
+
+```text
+首次请求 pageNo = 1
+上拉触底后请求 pageNo + 1
+把新数据追加到 rankingAdd
+rankingTotal = ranking + rankingAdd
+```
+
+## 8. 配置抽奖模式
+
+Season6 当前奖品已经画在抽奖背景图上,因此抽奖格子属于 `track-only` 模式:
+
+```js
+lottery: {
+ prizeRenderMode: 'track-only',
+ slotCount: 9,
+}
+```
+
+含义:
+
+```text
+接口奖品用于计算中奖位置
+页面只渲染高亮框转圈
+不在格子内额外渲染奖品图和文字
+```
+
+## 9. 增加路由
+
+在 `src/router/index.js` 中增加:
+
+```js
+{
+ path: '/activities/lucky-dollars-season6',
+ name: 'lucky-dollars-season6',
+ component: () => import('../views/Activities/LuckyDollars/Season6/index.vue'),
+ meta: { requiresAuth: true },
+}
+```
+
+访问地址:
+
+```text
+/activities/lucky-dollars-season6
+```
+
+## 10. 补充安全白名单
+
+在 `src/config/security.js` 的 `PUBLIC_PATHS` 中加入:
+
+```js
+'/activities/lucky-dollars-season6'
+```
+
+否则页面可能被安全路径检查拦截。
+
+## 11. 补充权限配置
+
+在 `src/utils/permissionManager.js` 的 `ACTIVITIES` 中加入:
+
+```js
+'/activities/lucky-dollars-season6'
+```
+
+否则页面可能被权限管理拦截。
+
+## 12. 修改页面内跳转来源
+
+页面内跳转到提现页时,`from` 参数改为 Season6 自己的路由:
+
+```js
+query: { from: '/activities/lucky-dollars-season6', activeAction: 'incomeShow' }
+```
+
+## 13. 当前仍沿用的参考实现
+
+为了快速验证视觉和功能,当前 Season6 的主体模板结构、样式细节、弹幕组件、顶部用户组件等仍参考 Season5 的实现。
+
+本次已先接入以下通用能力,避免新页面只能整页复制:
+
+```text
+useLotteryDraw
+ActivityLotteryGrid
+useActivityAssets
+```
+
+已落地内容:
+
+- `useActivityAssets`:Season6 的图片 URL、多语言图片名从 `pageConfig` 读取。
+- `ActivityLotteryGrid`:Season6 的抽奖格子、高亮框、中心按钮区域从页面模板中抽离。
+- `useLotteryDraw`:Season6 的抽奖转圈动画从页面脚本中抽离。
+- `pageConfig.lottery`:抽奖背景内容布局、格子布局、历史按钮位置、奖品展示模式改为配置。
+
+暂未强行改造:
+
+```text
+useActivityTabs
+useActivityRanking
+```
+
+原因:Season6 当前标签页和排行榜与大量现有展示状态耦合,继续硬改收益不高。后续真正新建页面时,初始骨架应直接使用这两个 composable,避免一开始就写多个 `reward/rankShow/lotteryShow/incomeShow` 布尔值。
+
+## 14. 后续给 Codex 建类似页面时的输入
+
+以后建立同类页面时,不需要说“复制某页面”。建议先按 [建页操作模板.md](./建页操作模板.md) 让 Codex 创建专属页面信息 md,再把必要信息填进去,例如:
+
+```text
+页面类型:Activities
+新页面目录:src/views/Activities/LuckyDollars/Season7
+参考页面:src/views/Activities/LuckyDollars/Season6
+路由 path:/activities/lucky-dollars-season7
+OSS 图片路径:Activities/LuckyDollars/Season7/
+模块:reward / ranking / lottery / task / withdraw / history / rule
+排行榜分页方式:remote
+抽奖奖品展示:track-only
+多语言图片:按页面创建信息 md 的 3.1 图片清单处理
+接口参数:先沿用 Season6 或填写真实 activityId/activityCode
+```
+
+Codex 应产出可运行初始版本,你再调整具体样式和图片对位。
+
+## 15. 本地图片 404 处理记录
+
+开发时曾出现:
+
+```text
+GET http://localhost:5173/oss/h5/Azizi/Activities/LuckyDollars/Season6/lotteryBtActive.png 404
+```
+
+原因:开发环境的 `/oss` 代理会转发到 CDN。如果本地 dev server 没有优先读取 `public/oss/...`,Season6 这种还没上传到服务端 OSS 的图片就会请求失败。
+
+处理方式:调整 `vite.config.js` 的 `/oss` 代理,先判断请求文件是否存在于项目 `public` 目录,存在则直接返回本地 public 路径;不存在再代理到 CDN。
+
+修改配置后需要重启 `npm run dev`,旧 dev server 不会自动吃到 `vite.config.js` 的变更。
+
+## 16. 验证记录
+
+已运行:
+
+```bash
+npm run build
+```
+
+结果:构建成功。
+
+构建期间仍有既有警告:
+
+```text
+../assets/images/Azizi/secondBg.png referenced in ../assets/images/Azizi/secondBg.png didn't resolve at build time
+```
+
+该警告不是 Season6 新增代码引起的,构建仍正常完成。
diff --git a/docs/活动页/建页执行流程.md b/docs/活动页/建页执行流程.md
new file mode 100644
index 0000000..f26b5cd
--- /dev/null
+++ b/docs/活动页/建页执行流程.md
@@ -0,0 +1,223 @@
+# 建页执行流程
+
+这份文档给 Codex 执行用。用户不需要阅读这里,用户只需要按 [建页操作模板.md](./建页操作模板.md) 操作。
+
+## 1. 用户只给页面名称和创建位置时
+
+当用户只提供类似信息:
+
+```text
+页面名称:LuckyDollars Season7
+创建位置:src/views/Activities/LuckyDollars/Season7
+```
+
+只执行以下步骤:
+
+```text
+1. 根据路径判断页面类型:
+ - src/views/Activities 下为 Activities
+ - src/views/Ranking 下为 Ranking
+2. 在 docs/活动页/ 下创建专属页面信息 md。
+3. md 文件名使用中文或页面名,例如 LuckyDollars-Season7-创建信息.md。
+4. 预填页面类型、页面名称、页面目录、route path、route name、OSS 相对路径、本地开发目录。
+5. route path 和 route name 根据页面类型、页面名称、页面目录自动生成;用户后续可以自行修改。
+6. 安全白名单片段和权限页面片段也使用 route path 自动生成,不留空。
+7. OSS 相对路径和本地开发目录根据页面类型和页面目录自动生成;用户后续可以自行修改。
+8. 如果本地开发目录已存在且里面有图片,扫描图片文件并预填 3.1 多语言图片清单。
+9. 根据页面类型、3.1 图片清单和模块预设,自动预填 3.2、3.3、4.x 模块配置。
+10. 只把无法推断或风险较高的字段留空,并在备注中说明需要用户确认。
+11. 本轮不创建页面代码,除非用户明确要求继续创建页面。
+```
+
+路由自动生成规则:
+
+```text
+Activities 页面:
+src/views/Activities/LuckyDollars/Season6
+-> route path: /activities/lucky-dollars-season6
+-> route name: lucky-dollars-season6
+
+Ranking 页面:
+src/views/Ranking/OverallSeason2
+-> route path: /ranking/overall-season2
+-> route name: overall-season2
+```
+
+安全白名单和权限页面片段生成规则:
+
+```text
+src/config/security.js:使用 route path。
+src/utils/permissionManager.js:使用 route path,后面注释使用页面名称。
+```
+
+OSS 路径生成规则:
+
+```text
+Activities 页面:
+src/views/Activities/LuckyDollars/Season6
+-> OSS 相对路径: Activities/LuckyDollars/Season6/
+-> 本地开发目录: public/oss/h5/Azizi/Activities/LuckyDollars/Season6/
+
+Ranking 页面:
+src/views/Ranking/OverallSeason2
+-> OSS 相对路径: Ranking/OverallSeason2/
+-> 本地开发目录: public/oss/h5/Azizi/Ranking/OverallSeason2/
+```
+
+多语言图片自动扫描规则:
+
+```text
+scanDir = 专属 md 中的本地开发目录
+只扫描常见图片扩展名:png、jpg、jpeg、webp、gif、svg。
+忽略 .gitkeep 等非图片文件。
+文件名不带语言后缀时,记为 en 原图,例如 bg0.png -> bg0: en。
+文件名带 _ar、_bn、_tr 等语言后缀时,按基础名合并,例如 rankBt.png + rankBt_ar.png -> rankBt: en, ar。
+如果只有 rankBt_ar.png 且没有 rankBt.png,也生成 rankBt: ar,并在备注里标记缺少原图。
+默认支持 ar、bn、tr;遇到其它形如 _xx 的语言后缀时也保留,但生成页面时需要确认是否接入。
+生成 3.1 图片清单时,按图片名首字母排序,排序忽略大小写。
+专属 md 的 3.1 展示扫描结果;备注优先匹配 docs/活动页/建页资料库.md 中的常用图片中文用途。
+能匹配到常用名时不要写“扫描生成”,例如 bg0 写“页面背景第 0 段”,rankBt 写“排行榜按钮”。
+匹配不到常用名时,根据图片名语义推断中文用途;用户的图片名通常按功能命名,例如 taskTalk 可写“聊天任务图标”,RewardRecharge500 可写“充值 500 奖励图”。
+rankingTopBg 统一解释为“排行榜前几名展示区背景”;历史 topBg 也按该含义解释,不要写成“顶部区域背景”。
+只有图片名完全看不出功能时,才备注“待确认用途”,方便用户回头改。
+不再要求用户填写“多语言图片规则”;生成页面时直接读取 3.1 中每张图片的支持语言。
+3.2 和 3.3 默认按图片用途自动生成:首屏背景、默认标签页、顶部信息、默认模块放 blocking;弹窗、非默认标签页、抽奖结果、下方模块放 background。
+```
+
+## 2. 用户补完专属 md 并要求继续建页时
+
+执行顺序:
+
+```text
+1. 读取专属页面信息 md。
+2. 读取 docs/活动页/建页操作模板.md。
+3. 读取 docs/活动页/建页资料库.md。
+4. 如果有参考页面,读取参考页面结构和接口用法。
+5. 只处理专属 md 中已填写的信息,未填写项跳过。
+6. 创建页面目录。
+7. 创建 page.config.js。
+8. 创建或生成 index.vue 初版。
+9. 如果专属 md 中“是否需要 Codex 自动创建本地开发目录”为“是”,创建 public/oss/h5/Azizi/... 本地图片目录。
+10. 添加路由。
+11. 补充 src/config/security.js。
+12. 补充 src/utils/permissionManager.js。
+13. 按需接入组件和 composable。
+14. 运行 npm run build。
+15. 把完成项、跳过项、待补项、验证结果写回专属 md。
+```
+
+## 3. 执行原则
+
+```text
+专属 md 没填的项,不猜。
+专属 md 中需要用户填写的模块配置、接口参数、组件需求,优先使用 JSON 代码块,不使用 Markdown 表格。
+扫描生成的图片清单和预加载清单可以保留 Markdown 表格,因为它们主要用于检查和排序。
+模块图片优先从 3.1 图片清单中按图片名语义推断;推断不到时再参考建页资料库给默认名,并标记待确认。
+模块预设里的图片名只有在 3.1 图片清单存在时才直接写入;不存在时留空,并在 remark 中写“本地图片清单未扫描到 xxx,待确认”。
+常见模块接口优先走模块预设,不要求用户重复填写。
+如果同一个模块在不同页面存在接口或字段差异,且没有提供参考页面,要先问用户参考哪个页面的模块。
+参考页面只作为结构和接口参考,不把新页面当成复制旧页面。
+新增 js/vue 文件尽量加中文注释。
+docs 下文件和文件夹使用中文命名。
+页面初版以能运行、结构清楚、方便微调为目标。
+```
+
+## 4. page.config.js 建议结构
+
+新页面优先使用分组后的模块配置,不再使用单纯数组:
+
+```js
+export const pageConfig = {
+ name: '',
+ routePath: '',
+ assetPath: '',
+
+ activity: {
+ activityId: '',
+ activityCode: '',
+ activityType: '',
+ rankingActivityType: '',
+ rewardActivityType: '',
+ },
+
+ modules: {
+ tabs: [],
+ sections: [],
+ popups: [],
+ features: [],
+ },
+
+ images: {
+ localized: {},
+ },
+}
+```
+
+含义:
+
+```text
+tabs:标签页模块,如 reward、ranking、lottery、withdraw
+sections:页面区块,如 task、badgeReward、myInfo
+popups:弹窗,如 rule、help、history、result
+features:功能能力,如 countdown、barrage、transfer、exchange
+```
+
+## 5. 路由和权限
+
+每个新页面都要确认:
+
+```text
+src/router/index.js
+src/config/security.js
+src/utils/permissionManager.js
+```
+
+如果专属 md 中明确写“不需要”,才跳过。
+
+## 6. 图片和缓存
+
+开发期图片目录:
+
+```text
+public/oss/h5/Azizi/Activities/PageName/
+public/oss/h5/Azizi/Ranking/PageName/
+```
+
+页面代码中仍使用 OSS 相对路径:
+
+```js
+assetPath: 'Activities/PageName/'
+assetPath: 'Ranking/PageName/'
+```
+
+替换同名 OSS 图片时,需要提醒用户更新:
+
+```text
+src/config/imageCacheVersion.js
+```
+
+分阶段预加载实现规则:
+
+```text
+1. page.config.js 中保留两个列表:
+ - preload.blocking:进入页面前同步预加载,参与加载动画。
+ - preload.background:进入页面后后台预加载,不参与加载动画。
+2. blocking 列表为空时,不强制等待图片,页面可直接进入。
+3. background 列表为空时,不生成额外逻辑或生成 no-op 逻辑。
+4. 两个列表都要走同一套 imageUrl/getImgName 规则,保证本地 OSS、线上 OSS、图片缓存和多语言后缀一致。
+5. 后台预加载在 blocking 完成、页面可见后执行,优先使用 requestIdleCallback,兼容时用 setTimeout。
+6. 后台预加载失败只记录日志,不影响页面展示和交互。
+```
+
+## 7. 完成后写回专属 md
+
+建页完成后,在专属 md 的“建页后记录”中补充:
+
+```text
+已完成:
+未填写所以跳过:
+待用户微调:
+构建结果:
+本地图片目录:
+路由 path:
+```
diff --git a/docs/活动页/建页操作模板.md b/docs/活动页/建页操作模板.md
new file mode 100644
index 0000000..6059fb2
--- /dev/null
+++ b/docs/活动页/建页操作模板.md
@@ -0,0 +1,447 @@
+# 建页操作模板
+
+这份文档只回答一个问题:你要怎么操作,才能让我快速生成 `src/views/Activities` 或 `src/views/Ranking` 的初版页面。
+
+真正建页时不需要一次说完整需求。推荐先让我创建一份“页面创建信息 md”,你在里面按坑位填写;你没填的项,我生成初版时先不处理。
+
+相关资料:
+
+- [建页资料库.md](./建页资料库.md):组件用法、常用图片名、配置选项。
+- [建页执行流程.md](./建页执行流程.md):我真正执行建页时遵守的流程。
+- [快速建立活动页方案.md](./快速建立活动页方案.md):文档入口和总览。
+
+## 1. 推荐操作流程
+
+第一次只告诉我页面名称和创建位置:
+
+```text
+新建活动页:LuckyDollars Season7
+创建位置:src/views/Activities/LuckyDollars/Season7
+```
+
+或者:
+
+```text
+新建排行榜页:Overall Season2
+创建位置:src/views/Ranking/OverallSeason2
+```
+
+我收到后只做一件事:在 `docs/活动页/` 下创建专属页面信息 md,例如:
+
+```text
+docs/活动页/LuckyDollars-Season7-创建信息.md
+```
+
+这份 md 会先填好:
+
+```text
+页面类型
+页面名称
+页面目录
+route path
+route name
+OSS 相对路径
+本地开发目录
+```
+
+其中 `route path` 和 `route name` 会根据页面类型、页面名称、页面目录自动生成。你有特殊需求时,直接在 md 里改掉即可。
+
+`OSS 相对路径` 和 `本地开发目录` 也会根据页面类型和页面目录自动生成。你只需要选择是否让我自动创建本地开发目录。
+
+其它信息会尽量由图片扫描和模块预设先填好;只有无法推断的地方才留给你补。
+
+你填完后,再告诉我:
+
+```text
+按 docs/活动页/LuckyDollars-Season7-创建信息.md 继续生成初版页面
+```
+
+我再根据你填写的信息创建页面、配置、路由、权限、本地 OSS 目录,并把执行结果写回这份专属 md。
+
+## 2. 填写规则
+
+- 模块配置:优先由 Codex 根据页面类型、扫描图片和参考页面自动预设;你只改不符合预期的地方。
+- 图片名和模块图片:优先从 `3.1 多语言图片清单` 和模块预设中自动推断,通常不用额外填写。
+- 模块接口:常见模块会自动预设,例如抽奖、奖池、任务、排行榜、中奖历史这类接口不用每次手写。
+- 没填且无法推断的模块:我先不实现,或标记为“待确认”。
+- 参考页面只作为结构和接口参考,不代表新页面就是复制旧页面。
+- 初版页面只保证结构、路径、接口位置、基础交互可用,图片对位和局部样式后续你再微调。
+- 需要你填写的模块配置、接口参数、组件需求,尽量使用 JSON 代码块,避免用 Markdown 表格对齐。
+
+## 3. 专属页面信息 md 模板
+
+我创建专属页面信息 md 时使用下面模板。基础信息会先填好,模块、预加载和图片用途会尽量根据扫描结果自动预设。
+
+````md
+# 页面创建信息
+
+## 1. 基础信息
+
+- 页面类型:Activities / Ranking
+- 页面名称:
+- 页面目录:
+- 参考页面:
+- 备注:
+
+## 2. 路由和权限
+
+- route path:
+- route name:
+- 是否需要登录:是 / 否
+- 是否加入 `src/config/security.js`:是 / 否
+- 是否加入 `src/utils/permissionManager.js`:是 / 否
+
+说明:`route path` 和 `route name` 由 Codex 先自动生成,你可以按需要修改。
+
+生成规则:
+
+| 页面类型 | 示例目录 | route path | route name |
+| --- | --- | --- | --- |
+| Activities | `src/views/Activities/LuckyDollars/Season6` | `/activities/lucky-dollars-season6` | `lucky-dollars-season6` |
+| Ranking | `src/views/Ranking/OverallSeason2` | `/ranking/overall-season2` | `overall-season2` |
+
+### 2.1 路由、白名单、权限片段说明
+
+专属页面信息 md 中可以展示这三个片段,但不需要你重复填写:
+
+```text
+路由代码片段:使用 route path、route name 和页面目录生成。
+安全白名单片段:值等于 route path,补充到 src/config/security.js。
+权限页面片段:值等于 route path,后面注释使用页面名称,补充到 src/utils/permissionManager.js。
+```
+
+示例:
+
+```js
+'/activities/lucky-dollars-season6'
+'/activities/lucky-dollars-season6' // LuckyDollars Season6
+```
+
+## 3. OSS 图片目录
+
+- OSS 相对路径:
+- 本地开发目录:
+- 是否需要 Codex 自动创建本地开发目录:是 / 否
+- 图片是否已放入本地目录:是 / 否
+
+说明:OSS 相对路径和本地开发目录由 Codex 自动预填。
+
+生成规则:
+
+| 页面类型 | 示例页面目录 | OSS 相对路径 | 本地开发目录 |
+| --- | --- | --- | --- |
+| Activities | `src/views/Activities/LuckyDollars/Season6` | `Activities/LuckyDollars/Season6/` | `public/oss/h5/Azizi/Activities/LuckyDollars/Season6/` |
+| Ranking | `src/views/Ranking/OverallSeason2` | `Ranking/OverallSeason2/` | `public/oss/h5/Azizi/Ranking/OverallSeason2/` |
+
+如果“是否需要 Codex 自动创建本地开发目录”填“是”,Codex 建页时会创建该目录,方便你直接把图片放进去。
+
+### 3.1 多语言图片清单
+
+这部分可由 Codex 扫描本地开发目录自动生成。规则:
+
+- 无后缀图片视为 `en` 原图,例如 `rankBt.png`。
+- `_ar`、`_bn`、`_tr` 这类后缀视为对应语言,例如 `rankBt_ar.png`。
+- 扫描时会按基础图片名合并语言,例如 `rankBt.png` + `rankBt_ar.png` 生成 `rankBt | en, ar`。
+- 图片清单按图片名首字母排序,排序时忽略大小写,方便你快速找图。
+- 备注优先按 [建页资料库.md](./建页资料库.md) 的常用图片名整理成中文用途,例如 `bg0` 写“页面背景第 0 段”,`rankBt` 写“排行榜按钮”。
+- 如果资料库里没有对应图片名,Codex 可以按图片名语义自己起中文用途,例如 `taskTalk` 推断为“聊天任务图标”,`RewardRecharge500` 推断为“充值 500 奖励图”。
+- 新页面推荐用 `rankingTopBg` 表示“排行榜前几名展示区背景”;历史图片名 `topBg` 也按这个含义识别,不再按“页面顶部背景”处理。
+- 其它语言后缀可按页面实际情况扩展;没识别到对应语言图片时,页面代码回退使用原图。
+- 页面生成时不再单独填写“多语言图片规则”,直接以这张清单为准。
+
+| 图片名 | 支持语言 | 备注 |
+| --- | --- | --- |
+| | | Codex 扫描并匹配或推断中文用途,必要时你再改 |
+
+### 3.2 进入页面前同步预加载图片
+
+这里填写需要和进入页面预加载动画同步的关键图片。Codex 生成页面时会先等待这些图片加载完成,再结束加载动画。
+
+| 图片名 | 是否包含多语言 | 备注 |
+| --- | --- | --- |
+| | | |
+
+### 3.3 进入页面后后台预加载图片
+
+这里填写不参与进入页面动画、但希望进入页面后继续自动预加载的图片。适合下方长背景、弹窗图、非默认标签页图片、抽奖结果图等。
+
+| 图片名 | 是否包含多语言 | 备注 |
+| --- | --- | --- |
+| | | |
+
+## 4. 模块配置
+
+这一部分会优先由 Codex 根据页面类型、扫描到的图片名和参考页面自动预设。你只需要改不符合预期的部分;删除或设为 `false` 的模块,初版页面会跳过。
+
+### 4.1 标签页模块
+
+填写方式:
+
+```json
+{
+ "enabled": "",
+ "defaultValue": "",
+ "items": [
+ {
+ "value": "reward",
+ "labelKey": "recharge_reward",
+ "buttonImage": "rewardBt",
+ "activeButtonImage": "rewardBtActive",
+ "remark": "奖励"
+ },
+ {
+ "value": "ranking",
+ "labelKey": "ranking",
+ "buttonImage": "rankBt",
+ "activeButtonImage": "rankBtActive",
+ "remark": "排行榜"
+ }
+ ]
+}
+```
+
+### 4.2 页面区块模块
+
+常见区块:
+
+```json
+[
+ {
+ "name": "reward",
+ "enabled": "",
+ "apis": [],
+ "images": [],
+ "remark": "奖励区块"
+ },
+ {
+ "name": "ranking",
+ "enabled": "",
+ "apis": [],
+ "images": [],
+ "remark": "排行榜区块"
+ },
+ {
+ "name": "lottery",
+ "enabled": "",
+ "apis": [],
+ "images": [],
+ "remark": "抽奖区块"
+ },
+ {
+ "name": "task",
+ "enabled": "",
+ "apis": [],
+ "images": [],
+ "remark": "任务区块"
+ },
+ {
+ "name": "withdraw",
+ "enabled": "",
+ "apis": [],
+ "images": [],
+ "remark": "提现/兑换区块"
+ },
+ {
+ "name": "countdown",
+ "enabled": "",
+ "apis": [],
+ "images": [],
+ "remark": "倒计时区块"
+ }
+]
+```
+
+### 4.3 弹窗模块
+
+常见弹窗:
+
+```json
+[
+ {
+ "name": "rule",
+ "triggerImage": "",
+ "backgroundImage": "",
+ "contentImage": "",
+ "apis": [],
+ "remark": "规则弹窗"
+ },
+ {
+ "name": "help",
+ "triggerImage": "",
+ "backgroundImage": "",
+ "contentImage": "",
+ "apis": [],
+ "remark": "帮助弹窗"
+ },
+ {
+ "name": "history",
+ "triggerImage": "",
+ "backgroundImage": "",
+ "contentImage": "",
+ "apis": [],
+ "remark": "历史记录弹窗"
+ },
+ {
+ "name": "result",
+ "triggerImage": "",
+ "backgroundImage": "",
+ "contentImage": "",
+ "apis": [],
+ "remark": "中奖结果弹窗"
+ },
+ {
+ "name": "rewardDetail",
+ "triggerImage": "",
+ "backgroundImage": "",
+ "contentImage": "",
+ "apis": [],
+ "remark": "奖励详情弹窗"
+ }
+]
+```
+
+### 4.4 排行榜模块
+
+```json
+{
+ "enabled": "",
+ "api": "",
+ "loadMode": "remote / local",
+ "pageSize": "",
+ "topCount": "",
+ "myRankingPath": "",
+ "fieldMap": {
+ "avatar": "",
+ "name": "",
+ "value": "",
+ "userId": "",
+ "rank": ""
+ }
+}
+```
+
+### 4.5 抽奖模块
+
+```json
+{
+ "enabled": "",
+ "drawApis": [],
+ "prizePoolApi": "",
+ "drawCountApi": "",
+ "extraApis": [],
+ "displayMode": "track-only / prize-card / custom",
+ "slotCount": "",
+ "centerButtonPosition": "",
+ "historyButtonPosition": "inside / below / absolute / external / hidden",
+ "images": {
+ "background": "",
+ "activeFrame": "",
+ "singleDrawButton": "",
+ "multiDrawButton": "",
+ "historyButton": "",
+ "resultBackground": ""
+ },
+ "remark": ""
+}
+```
+
+## 5. 接口参数
+
+```json
+{
+ "activityId": "",
+ "activityCode": "",
+ "activityType": "",
+ "rankingActivityType": "",
+ "rewardActivityType": "",
+ "otherParams": {}
+}
+```
+
+## 6. 组件需求
+
+需要哪个就填“是”,不需要或没填的我先不接。
+
+```json
+[
+ {
+ "name": "BackgroundLayer",
+ "enabled": "",
+ "remark": "长背景分段"
+ },
+ {
+ "name": "itemCenter",
+ "enabled": "",
+ "remark": "图片容器"
+ },
+ {
+ "name": "topUser / TopUser",
+ "enabled": "",
+ "remark": "前三名用户展示"
+ },
+ {
+ "name": "Barrage",
+ "enabled": "",
+ "remark": "弹幕"
+ },
+ {
+ "name": "ActivityTabs",
+ "enabled": "",
+ "remark": "标签页"
+ },
+ {
+ "name": "ActivityLotteryGrid",
+ "enabled": "",
+ "remark": "抽奖格子"
+ }
+]
+```
+
+## 7. 建页后记录
+
+这部分由 Codex 建页后填写。
+
+### 7.1 已完成
+
+-
+
+### 7.2 未填写所以跳过
+
+-
+
+### 7.3 待你微调
+
+-
+
+### 7.4 验证结果
+
+- `npm run build`:
+- 备注:
+````
+
+## 4. 预设选项说明
+
+这些选项填到专属页面信息 md 时,按下面含义理解。
+
+| 选项 | 可选值 | 说明 |
+| --- | --- | --- |
+| 页面类型 | `Activities` / `Ranking` | 活动页或排行榜页 |
+| 预加载方式 | `同步预加载` / `后台预加载` | 同步预加载参与进入动画,后台预加载不阻塞进入页面 |
+| 排行榜加载方式 | `remote` / `local` | `remote` 为接口分页,`local` 为前端切片 |
+| 抽奖展示方式 | `track-only` / `prize-card` / `custom` | 只转框 / 显示奖品卡 / 自定义格子 |
+| 历史按钮位置 | `inside` / `below` / `absolute` / `external` / `hidden` | 抽奖历史按钮放置方式 |
+
+## 5. 初版页面完成后你检查什么
+
+```text
+页面能打开
+图片路径不 404
+切语言时图片后缀符合预期
+路由能进入
+security 和 permissionManager 已补
+排行榜能首屏展示
+上拉加载策略正确:remote 请求下一页 / local slice 下一段
+抽奖点击后有高亮转圈,并能停到中奖位置
+弹窗能打开关闭
+npm run build 通过
+```
diff --git a/docs/活动页/建页资料库.md b/docs/活动页/建页资料库.md
new file mode 100644
index 0000000..6803161
--- /dev/null
+++ b/docs/活动页/建页资料库.md
@@ -0,0 +1,532 @@
+# 建页资料库
+
+这份文档存放建页时常用但不需要每次都手写的信息:组件用法、常用图片名、配置片段。
+
+## 1. 常用组件用法
+
+### 1.1 BackgroundLayer
+
+用途:长页面背景分段渲染,适合 `bg0/bg1/bg2/bg3` 这类长活动页。
+
+```vue
+
+```
+
+### 1.2 itemCenter
+
+用途:背景图 + 内部绝对定位内容,是活动页最常用的图片容器。
+
+```vue
+
+
+
+```
+
+### 1.3 topUser / TopUser
+
+用途:前三名用户展示。不同页面可能有自己的 `components/topUser.vue`,新页面可先从参考页面复制一份再微调。
+
+```vue
+
+```
+
+```js
+import TopUser from './components/topUser.vue'
+```
+
+### 1.4 Barrage
+
+用途:中奖用户、活动动态等弹幕。LuckyDollars 页面常用。
+
+```vue
+
+```
+
+```js
+import Barrage from './components/Barrage.vue'
+```
+
+### 1.5 ActivityTabs
+
+用途:图片背景标签页。适合初始页面快速搭标签切换。
+
+```vue
+
+```
+
+### 1.6 ActivityLotteryGrid
+
+用途:抽奖格子、高亮框和中心按钮区域。不同抽奖背景只改 `page.config.js` 里的布局。
+
+```vue
+
+```
+
+## 2. 常用图片名清单
+
+下面的图片名来自 `src/views/Activities` 和 `src/views/Ranking` 中常见的 `imageUrl('xxx')`、`webpUrl('xxx')`、`getImgName('xxx')` 用法。新页面不需要全部准备,按模块挑选即可。
+
+注意:图片名大小写必须和文件名一致。项目里已有一些历史命名,例如 `RankingItem`、`RankingBottomBorder`、`rechargehidden`,这里按现有代码保留。
+
+### 2.1 页面背景和顶部
+
+```js
+imageUrl('bg') // 页面主背景
+imageUrl('bg0') // 页面背景第 0 段
+imageUrl('bg1') // 页面背景第 1 段
+imageUrl('bg2') // 页面背景第 2 段
+imageUrl('bg3') // 页面背景第 3 段
+webpUrl('pageTop') // 页面顶部 WebP 背景
+imageUrl('timeBg') // 活动时间背景
+imageUrl('dayBg') // 日期/天数背景
+imageUrl('TimeBg') // 活动时间背景,历史大写命名
+```
+
+### 2.1.1 排行榜前几名展示区
+
+推荐新页面使用 `rankingTopBg`,含义更明确:排行榜前几名展示区背景,不限定一定是前三名。
+
+```js
+imageUrl('rankingTopBg') // 排行榜前几名展示区背景,推荐新命名
+imageUrl('topBg') // 排行榜前几名展示区背景,历史命名,不再作为页面顶部背景理解
+imageUrl('top1') // 第一名头像/排名装饰
+imageUrl('top2') // 第二名头像/排名装饰
+imageUrl('top3') // 第三名头像/排名装饰
+imageUrl('top1Bg') // 第一名独立背景
+imageUrl('top2Bg') // 第二名独立背景
+imageUrl('top3Bg') // 第三名独立背景
+```
+
+### 2.2 标签页和按钮
+
+```js
+imageUrl('tagsBg') // 标签页整体背景
+imageUrl('tagBtBg') // 标签页选中底图
+imageUrl(getImgName('rankBt')) // 排行榜未选中按钮
+imageUrl(getImgName('rankBtActive')) // 排行榜选中按钮
+imageUrl(getImgName('rankingBt')) // 排行按钮
+imageUrl(getImgName('rankingBtActive')) // 排行选中按钮
+imageUrl(getImgName('lotteryBt')) // 抽奖未选中按钮
+imageUrl(getImgName('lotteryBtActive')) // 抽奖选中按钮
+imageUrl(getImgName('rewardBt')) // 奖励未选中按钮
+imageUrl(getImgName('rewardBtActive')) // 奖励选中按钮
+imageUrl(getImgName('rechargeBt')) // 充值未选中按钮
+imageUrl(getImgName('rechargeBtActive')) // 充值选中按钮
+imageUrl(getImgName('redeemBt')) // 兑换按钮
+imageUrl(getImgName('withdrawalBt')) // 提现未选中按钮
+imageUrl(getImgName('withdrawalBtActive')) // 提现选中按钮
+```
+
+### 2.3 排行榜
+
+```js
+imageUrl('RankingMain') // 排行榜主体背景
+imageUrl('RankingItem') // 排行榜列表项背景
+imageUrl('rankingItemBg') // 排行榜列表项背景,另一套命名
+imageUrl('RankingTopBorder') // 排行榜顶部边框
+imageUrl('RankingBottomBorder') // 排行榜底部边框
+imageUrl('RankingBorder') // 排行榜通用边框
+imageUrl('myRankingBg') // 我的排名固定栏背景
+imageUrl('rankingNumBg') // 排名数字背景
+imageUrl('rankingTopBg') // 排行榜前几名展示区背景,推荐新命名
+imageUrl('topBg') // 排行榜前几名展示区背景,历史命名
+imageUrl('top1') // 第一名头像/排名装饰
+imageUrl('top2') // 第二名头像/排名装饰
+imageUrl('top3') // 第三名头像/排名装饰
+imageUrl('top1Bg') // 第一名独立背景
+imageUrl('top2Bg') // 第二名独立背景
+imageUrl('top3Bg') // 第三名独立背景
+imageUrl('top1-charm') // 魅力榜第一名装饰
+imageUrl('top2-charm') // 魅力榜第二名装饰
+imageUrl('top3-charm') // 魅力榜第三名装饰
+imageUrl('top1-wealth') // 财富榜第一名装饰
+imageUrl('top2-wealth') // 财富榜第二名装饰
+imageUrl('top3-wealth') // 财富榜第三名装饰
+imageUrl('top1-room') // 房间榜第一名装饰
+imageUrl('top2-room') // 房间榜第二名装饰
+imageUrl('top3-room') // 房间榜第三名装饰
+imageUrl('myRankingBg-charm') // 魅力榜我的排名背景
+imageUrl('myRankingBg-wealth') // 财富榜我的排名背景
+imageUrl('myRankingBg-room') // 房间榜我的排名背景
+imageUrl('RankingItem-charm') // 魅力榜列表项背景
+imageUrl('RankingItem-wealth') // 财富榜列表项背景
+imageUrl('RankingItem-room') // 房间榜列表项背景
+imageUrl('tabSelectedBg') // 榜单分类选中底图
+imageUrl('rankingSeasonBt') // 赛季榜按钮
+imageUrl('rankingSeasonBtActive') // 赛季榜选中按钮
+imageUrl('rankingWeeklyBt') // 周榜按钮
+imageUrl('rankingWeeklyBtActive') // 周榜选中按钮
+```
+
+### 2.4 抽奖
+
+```js
+imageUrl('lottery') // 抽奖机/抽奖盘背景
+imageUrl('lotteryBg') // 抽奖区域背景
+imageUrl('prizeActiveFrame') // 抽奖高亮框
+imageUrl('draw1BtBg') // 单抽按钮背景
+imageUrl('draw10BtBg') // 十连抽按钮背景
+imageUrl('resultBg') // 中奖结果弹窗背景
+imageUrl('resultBack') // 中奖结果上一页按钮
+imageUrl('resultNext') // 中奖结果下一页按钮
+imageUrl('resultBt') // 中奖结果确认按钮
+imageUrl('resultGoOnBt') // 继续抽奖按钮
+```
+
+### 2.5 奖励和奖品
+
+```js
+imageUrl('rewardBg') // 奖励模块背景
+imageUrl('reward_banner') // 奖励横幅
+imageUrl('rewards') // 奖励集合图
+imageUrl('rewardBack') // 奖励上一页按钮
+imageUrl('rewardNext') // 奖励下一页按钮
+imageUrl(getImgName('rewardReceive')) // 奖励领取按钮
+imageUrl(getImgName('rewardReceived')) // 奖励已领取按钮
+imageUrl(getImgName('rewardReceiveNot')) // 奖励不可领取按钮
+imageUrl(getImgName('receiveBt')) // 领取按钮
+imageUrl(getImgName('receivedBt')) // 已领取按钮
+imageUrl('reward_gift') // 礼物奖励图标
+imageUrl('reward_frame') // 头像框奖励图标
+imageUrl('reward_ride') // 座驾奖励图标
+imageUrl('ticket') // 抽奖券图标
+imageUrl('crystal') // 水晶/活动货币图标
+imageUrl('debris') // 碎片/道具图标
+imageUrl('giftBg') // 礼物背景
+imageUrl('giftItemBg') // 礼物列表项背景
+imageUrl('RewardRecharge100') // 充值 100 奖励图
+imageUrl('RewardRecharge500') // 充值 500 奖励图
+imageUrl('RewardRecharge1000') // 充值 1000 奖励图
+imageUrl('RewardRecharge3000') // 充值 3000 奖励图
+imageUrl('RewardRecharge5000') // 充值 5000 奖励图
+imageUrl('RewardRecharge10000') // 充值 10000 奖励图
+imageUrl('rechargeReward100') // 充值 100 奖励图,另一套命名
+imageUrl('rechargeReward500') // 充值 500 奖励图,另一套命名
+imageUrl('rechargeReward1000') // 充值 1000 奖励图,另一套命名
+imageUrl('rechargeReward3000') // 充值 3000 奖励图,另一套命名
+imageUrl('rechargeReward5000') // 充值 5000 奖励图,另一套命名
+imageUrl('rechargeReward10000') // 充值 10000 奖励图,另一套命名
+```
+
+### 2.6 任务
+
+```js
+imageUrl('taskBtGo') // 任务去完成按钮
+imageUrl('taskBtReceive') // 任务领取按钮
+imageUrl('taskBtReceived') // 任务已领取按钮
+imageUrl('taskGame') // 游戏任务图标
+imageUrl('taskGift') // 送礼任务图标
+imageUrl('taskRecharge') // 充值任务图标
+imageUrl('taskTalk') // 聊天/上麦任务图标
+imageUrl('taskItemBg') // 任务列表项背景
+imageUrl('taskListBg') // 任务列表背景
+imageUrl('taskTypeGame') // 游戏任务类型图标
+imageUrl('taskTypeGift') // 礼物任务类型图标
+```
+
+### 2.7 弹窗、规则、帮助和历史
+
+```js
+imageUrl(getImgName('helpInfo')) // 帮助说明图片
+imageUrl('helpBg') // 帮助弹窗背景
+imageUrl('helpBt') // 帮助按钮
+imageUrl(getImgName('ruleInfo')) // 规则说明图片
+imageUrl('ruleInfoBg') // 规则弹窗背景
+imageUrl(getImgName('ruleTitle')) // 规则标题图
+imageUrl(getImgName('rulesBt')) // 规则按钮
+imageUrl(getImgName('historyBt')) // 历史按钮
+imageUrl(getImgName('historyBg')) // 历史弹窗背景
+imageUrl('historyTopBg') // 历史弹窗顶部背景
+imageUrl('recordBg') // 记录列表背景
+imageUrl('closeBt') // 关闭按钮
+```
+
+### 2.8 用户信息、充值和收益
+
+```js
+imageUrl('myInfoBg') // 我的信息背景
+imageUrl('myAvatarFrame') // 我的头像框
+imageUrl('userNameBorder') // 用户名边框
+imageUrl('userFrame') // 用户头像框
+imageUrl('earningInfoBg') // 收益信息背景
+imageUrl('earningsBg') // 收益背景
+imageUrl('distribution') // 分发/派奖图标
+imageUrl('rechargehidden') // 充值金额隐藏图标
+imageUrl('rechargeShow') // 充值金额显示图标
+imageUrl('rechargeNumHidden') // 充值数字隐藏图标
+imageUrl('rechargeNumShow') // 充值数字显示图标
+imageUrl('amountHiddenBt') // 金额隐藏按钮
+imageUrl('amountShowBt') // 金额显示按钮
+```
+
+### 2.9 游戏玩法和特殊模块
+
+```js
+imageUrl('gameLotteryBt') // 游戏玩法-抽奖按钮
+imageUrl('gameLotteryBtActive') // 游戏玩法-抽奖选中按钮
+imageUrl('gameSignInBt') // 游戏玩法-签到按钮
+imageUrl('gameSignInBtActive') // 游戏玩法-签到选中按钮
+imageUrl('gameExchangeBt') // 游戏玩法-兑换按钮
+imageUrl('gameExchangeBtActive') // 游戏玩法-兑换选中按钮
+imageUrl('goodsItem') // 兑换商品项背景
+imageUrl('makeUpBtBg') // 补签按钮背景
+imageUrl('rewardGoodsBg') // 奖励物品背景
+imageUrl('progressBar') // 进度条图片
+imageUrl('timeBack') // 时间/日期上一页按钮
+imageUrl('timeNext') // 时间/日期下一页按钮
+```
+
+## 3. 模块预设
+
+这些预设用于创建“页面创建信息 md”时自动填充,目标是少问、少填、先生成能跑的初版。图片字段优先从 `3.1 多语言图片清单` 中匹配;没有匹配到的图片名可以留空或标记待确认。
+
+### 3.1 Activities 通用活动页
+
+适用:`src/views/Activities` 下类似 LuckyDollars、LesserBairam、SpringFestival 的活动页。
+
+```json
+{
+ "reward": {
+ "enabledWhenAnyImageExists": ["rewardBg", "rewardReceive", "rewardReceived", "rewardReceiveNot"],
+ "apis": ["activityRewardConfigs", "myRecharge", "receiveRechargeReward"],
+ "defaultImages": ["rewardBg", "rewardReceive", "rewardReceived", "rewardReceiveNot", "ticket", "reward_gift", "reward_frame", "reward_ride"],
+ "remark": "充值/消费奖励模块"
+ },
+ "ranking": {
+ "enabledWhenAnyImageExists": ["RankingItem", "myRankingBg", "rankingTopBg", "topBg", "top1", "top2", "top3", "rankBt", "rankBtActive"],
+ "apis": ["ranklist"],
+ "loadMode": "remote",
+ "pageSize": 20,
+ "topCount": 3,
+ "myRankingPath": "ranklist.body.currentUser",
+ "fieldMap": {
+ "avatar": "userAvatar",
+ "name": "userName",
+ "value": "amount",
+ "userId": "userId",
+ "rank": "rank"
+ },
+ "defaultImages": ["RankingItem", "myRankingBg", "rankingTopBg", "top1", "top2", "top3"],
+ "remark": "活动内排行榜,常见为接口分页"
+ },
+ "lottery": {
+ "enabledWhenAnyImageExists": ["lottery", "lotteryBg", "prizeActiveFrame", "lotteryBt", "lotteryBtActive"],
+ "drawApis": ["onceDraw", "multiDraw"],
+ "prizePoolApi": "activityDetail",
+ "drawCountApi": "myTickets",
+ "extraApis": ["myTotalDrawCount", "rewardConfigsBadge", "winnerHistory"],
+ "historyApi": "drawRecords",
+ "displayMode": "track-only",
+ "slotCount": 8,
+ "historyButtonPosition": "inside",
+ "defaultImages": {
+ "background": "lottery",
+ "activeFrame": "prizeActiveFrame",
+ "historyButton": "historyBt",
+ "resultBackground": "resultBg"
+ },
+ "remark": "抽奖接口基本固定;奖品展示方式按页面图片调整"
+ },
+ "task": {
+ "enabledWhenAnyImageExists": ["taskBtGo", "taskBtReceive", "taskBtReceived", "taskGame", "taskGift", "taskRecharge", "taskTalk"],
+ "apis": ["ActTaskList", "receiveTickets"],
+ "defaultImages": ["taskBtGo", "taskBtReceive", "taskBtReceived", "taskGame", "taskGift", "taskRecharge", "taskTalk"],
+ "remark": "任务列表和任务领奖"
+ },
+ "withdraw": {
+ "enabledWhenAnyImageExists": ["earningInfoBg", "myInfoBg"],
+ "apis": ["withdrawableAmount", "transferActivityDollar", "exchangeCoin"],
+ "defaultImages": ["earningInfoBg", "myInfoBg"],
+ "remark": "提现、转账或兑换模块"
+ },
+ "popups": {
+ "rule": {
+ "enabledWhenAnyImageExists": ["rulesBt", "ruleInfo"],
+ "triggerImage": "rulesBt",
+ "contentImage": "ruleInfo"
+ },
+ "help": {
+ "enabledWhenAnyImageExists": ["helpBt", "helpInfo"],
+ "triggerImage": "helpBt",
+ "contentImage": "helpInfo"
+ },
+ "history": {
+ "enabledWhenAnyImageExists": ["historyBt", "historyBg"],
+ "triggerImage": "historyBt",
+ "backgroundImage": "historyBg",
+ "apis": ["drawRecords"]
+ }
+ }
+}
+```
+
+### 3.2 Ranking 独立排行榜页
+
+适用:`src/views/Ranking` 下独立排行榜页。这里要特别区分加载方式:
+
+- `remote`:接口支持 `pageNo/pageSize`,上拉时继续请求下一页。
+- `local`:接口一次返回完整榜单,前端先展示一段,上拉时 `slice` 下一段,例如 `src/views/Ranking/Overall/Ranking.vue`。
+
+```json
+{
+ "ranking": {
+ "loadMode": "local / remote",
+ "topCount": 3,
+ "pageSize": 10,
+ "fieldMap": {
+ "avatar": "avatar",
+ "name": "nickname",
+ "value": "quantityFormat",
+ "userId": "id",
+ "rank": "rank"
+ },
+ "commonImages": [
+ "RankingItem",
+ "myRankingBg",
+ "top1",
+ "top2",
+ "top3",
+ "tabSelectedBg"
+ ],
+ "remark": "如果接口差异不明确,先问用户参考哪个 Ranking 页面"
+ }
+}
+```
+
+### 3.3 什么时候需要问用户
+
+```text
+同一模块存在多个接口版本时,问参考页面。
+抽奖模块不是 8 格或不是 track-only 时,问抽奖展示方式。
+排行榜接口不知道是 remote 还是 local 时,问参考页面或看接口是否传 pageNo/pageSize。
+图片名看不出用途且无法从参考页面推断时,标记待确认,不阻塞初版生成。
+```
+
+## 4. 页面配置片段
+
+### 4.1 图片工具
+
+```js
+import { useActivityAssets } from '@/composables/activity/useActivityAssets.js'
+import { pageConfig } from './page.config.js'
+
+const currentLangType = computed(() => {
+ return langStore.selectedLang?.type || 'en'
+})
+
+const { imageUrl, webpUrl, getImageName: getImgName } = useActivityAssets(
+ pageConfig,
+ currentLangType,
+)
+```
+
+### 4.2 标签页
+
+```js
+import { useActivityTabs } from '@/composables/activity/useActivityTabs.js'
+
+const { currentTab, visibleTabs, isTab, changeTab } = useActivityTabs(pageConfig.tabs, {
+ translate: t,
+})
+```
+
+### 4.3 排行榜
+
+```js
+import { useActivityRanking } from '@/composables/activity/useActivityRanking.js'
+
+const rankingState = useActivityRanking({
+ loadMode: pageConfig.ranking.loadMode,
+ pageSize: pageConfig.ranking.pageSize,
+ topCount: pageConfig.ranking.topCount,
+ fetchPage: ({ pageNo, pageSize }) => {
+ return ranklist({
+ activityId: pageConfig.activity.activityId,
+ pageNo,
+ pageSize,
+ })
+ },
+})
+```
+
+### 4.4 抽奖
+
+```js
+import { useLotteryDraw } from '@/composables/activity/useLotteryDraw.js'
+
+const { activeIndex, isRolling, startLottery } = useLotteryDraw({
+ getSlotCount: () => prizeList.value.length || pageConfig.lottery.slotCount,
+})
+```
+
+### 4.5 分阶段图片预加载
+
+用途:把首屏关键图片和后续图片拆开。首屏关键图片参与进入页面加载动画;弹窗、非默认标签页、下方长背景等图片在页面进入后后台继续预加载。
+
+```js
+// 进入页面前必须完成的图片,加载完成后再结束 loading 动画。
+const blockingPreloadImageNames = pageConfig.preload?.blocking || []
+
+// 进入页面后继续后台加载的图片,不阻塞页面展示。
+const backgroundPreloadImageNames = pageConfig.preload?.background || []
+
+// 统一走 imageUrl 和 getImgName,保证本地 OSS、线上 OSS、缓存版本、多语言后缀规则一致。
+const resolvePreloadUrls = (names = []) => {
+ return [...new Set(names)].map((name) => imageUrl(getImgName(name)))
+}
+
+const preloadImageList = (urls = []) => {
+ return Promise.all(
+ urls.map((url) => new Promise((resolve) => {
+ const img = new Image()
+ img.onload = resolve
+ img.onerror = resolve
+ img.src = url
+ })),
+ )
+}
+
+const preloadBlockingImages = async () => {
+ await preloadImageList(resolvePreloadUrls(blockingPreloadImageNames))
+}
+
+const preloadBackgroundImages = () => {
+ const urls = resolvePreloadUrls(backgroundPreloadImageNames)
+ if (!urls.length) return
+
+ // 后台预加载不影响当前页面展示,所以失败也只吞掉。
+ const run = () => preloadImageList(urls).catch(() => {})
+ if (window.requestIdleCallback) {
+ window.requestIdleCallback(run)
+ return
+ }
+ setTimeout(run, 0)
+}
+
+onMounted(async () => {
+ await preloadBlockingImages()
+ isLoading.value = false
+ preloadBackgroundImages()
+})
+```
diff --git a/docs/活动页/快速建立活动页方案.md b/docs/活动页/快速建立活动页方案.md
new file mode 100644
index 0000000..0f1ec1c
--- /dev/null
+++ b/docs/活动页/快速建立活动页方案.md
@@ -0,0 +1,65 @@
+# 活动页与排行榜页建页文档总览
+
+当前 `src/views/Activities` 和 `src/views/Ranking` 的快速建页流程已经收敛为三份文档。
+
+## 1. 你主要看哪份
+
+优先看:
+
+[建页操作模板.md](./建页操作模板.md)
+
+用途:
+
+```text
+告诉你怎么操作
+告诉你第一次只需要提供什么
+提供专属页面信息 md 模板
+保留需要填写的模块、接口、图片名、组件需求
+```
+
+以后新建页面时,推荐先告诉 Codex:
+
+```text
+页面名称:
+创建位置:
+```
+
+Codex 会先创建专属页面信息 md 给你填写。
+
+## 2. 有用资料放在哪
+
+[建页资料库.md](./建页资料库.md)
+
+用途:
+
+```text
+常用组件用法
+常用 imageUrl 图片名和中文用途
+图片工具、标签页、排行榜、抽奖配置片段
+```
+
+## 3. Codex 执行流程放在哪
+
+[建页执行流程.md](./建页执行流程.md)
+
+用途:
+
+```text
+规定 Codex 先建专属页面信息 md
+规定 Codex 只处理你填写过的信息
+规定 Codex 如何生成初版页面
+规定完成后要把结果写回专属 md
+```
+
+你平时不需要看这份,除非想确认 Codex 会怎么执行。
+
+## 4. 历史创建记录
+
+[幸运美金第6季创建记录.md](./幸运美金第6季创建记录.md)
+
+用途:
+
+```text
+记录 LuckyDollars Season6 当时的创建过程
+作为历史参考,不作为新的通用模板
+```
diff --git a/src/views/Activities/LuckyDollars/Season6/页面创建信息-完整参考.md b/src/views/Activities/LuckyDollars/Season6/页面创建信息-完整参考.md
new file mode 100644
index 0000000..19f8937
--- /dev/null
+++ b/src/views/Activities/LuckyDollars/Season6/页面创建信息-完整参考.md
@@ -0,0 +1,429 @@
+# 页面创建信息
+
+## 1. 基础信息
+
+- 页面类型:Activities
+- 页面名称:LuckyDollars Season6
+- 页面目录:src/views/Activities/LuckyDollars/Season6
+- 参考页面:src/views/Activities/LuckyDollars/Season5
+- 备注:作为新页面处理,初版效果参考 Season5,接口参数和图片可先沿用 Season5/Season6 测试数据。
+
+## 2. 路由和权限
+
+- route path:/activities/lucky-dollars-season6
+- route name:lucky-dollars-season6
+- 是否需要登录:是
+- 是否加入 `src/config/security.js`:是
+- 是否加入 `src/utils/permissionManager.js`:是
+
+### 2.1 路由代码片段
+
+```js
+{
+ path: '/activities/lucky-dollars-season6',
+ name: 'lucky-dollars-season6',
+ component: () => import('../views/Activities/LuckyDollars/Season6/index.vue'),
+ meta: { requiresAuth: true },
+}
+```
+
+### 2.2 安全白名单片段
+
+建页时由 Codex 使用上面的 route path 补充到 `src/config/security.js`。
+
+```js
+'/activities/lucky-dollars-season6'
+```
+
+### 2.3 权限页面片段
+
+建页时由 Codex 使用上面的 route path 补充到 `src/utils/permissionManager.js`,后面的注释使用页面名称。
+
+```js
+'/activities/lucky-dollars-season6' // 幸运美金第 6 季
+```
+
+## 3. OSS 图片目录
+
+- OSS 相对路径:Activities/LuckyDollars/Season6/
+- 本地开发目录:public/oss/h5/Azizi/Activities/LuckyDollars/Season6/
+- 是否需要 Codex 自动创建本地开发目录:是
+- 图片是否已放入本地目录:是
+
+说明:OSS 相对路径和本地开发目录由 Codex 根据页面类型和页面目录自动生成。如果“是否需要 Codex 自动创建本地开发目录”填“是”,Codex 建页时会先创建这个目录。
+
+### 3.1 多语言图片清单
+
+说明:下面清单由 Codex 扫描本地开发目录自动生成。`en` 表示无后缀原图,`ar` 表示存在 `_ar` 图片。页面生成时以这张清单为准处理多语言图片。
+
+| 图片名 | 支持语言 | 备注 |
+| --- | --- | --- |
+| bg0 | en | 页面背景第 0 段 |
+| bg1 | en | 页面背景第 1 段 |
+| bg2 | en | 页面背景第 2 段 |
+| bg3 | en | 页面背景第 3 段 |
+| helpInfo | en, ar | 帮助说明图 |
+| historyBg | en, ar | 历史弹窗背景 |
+| historyBt | en, ar | 历史按钮 |
+| lottery | en | 抽奖机背景 |
+| lotteryBt | en, ar | 抽奖按钮 |
+| lotteryBtActive | en, ar | 抽奖选中按钮 |
+| myAvatarFrame | en | 我的头像框 |
+| myInfoBg | en | 我的信息背景 |
+| myRankingBg | en | 我的排名背景 |
+| prizeActiveFrame | en | 抽奖高亮框 |
+| rankBt | en, ar | 排行榜按钮 |
+| rankBtActive | en, ar | 排行榜选中按钮 |
+| RankingItem | en | 排行榜列表项背景 |
+| rechargehidden | en | 充值隐藏图标 |
+| rechargeShow | en | 充值显示图标 |
+| reward_frame | en | 头像框奖励图标 |
+| reward_gift | en | 礼物奖励图标 |
+| reward_ride | en | 座驾奖励图标 |
+| rewardBack | en | 奖励上一页 |
+| rewardBg | en | 奖励背景 |
+| rewardNext | en | 奖励下一页 |
+| rewardReceive | en, ar | 奖励领取按钮 |
+| rewardReceived | en, ar | 奖励已领取按钮 |
+| rewardReceiveNot | en, ar | 奖励不可领取按钮 |
+| RewardRecharge100 | en | 充值奖励 100 |
+| RewardRecharge1000 | en | 充值奖励 1000 |
+| RewardRecharge10000 | en | 充值奖励 10000 |
+| RewardRecharge3000 | en | 充值奖励 3000 |
+| RewardRecharge500 | en | 充值奖励 500 |
+| RewardRecharge5000 | en | 充值奖励 5000 |
+| ruleInfo | en, ar | 规则说明图 |
+| rulesBt | en, ar | 规则按钮 |
+| tagBtBg | en | 标签选中底图 |
+| tagsBg | en | 标签背景 |
+| taskBtGo | en | 任务去完成按钮 |
+| taskBtReceive | en | 任务领取按钮 |
+| taskBtReceived | en | 任务已领取按钮 |
+| taskGame | en | 游戏任务图标 |
+| taskGift | en | 礼物任务图标 |
+| taskRecharge | en | 充值任务图标 |
+| taskTalk | en | 聊天任务图标 |
+| ticket | en | 抽奖券图标 |
+| timeBg | en | 时间背景 |
+| top1 | en | 第一名装饰 |
+| top2 | en | 第二名装饰 |
+| top3 | en | 第三名装饰 |
+
+### 3.2 进入页面前同步预加载图片
+
+说明:这里放进入页面后首屏和默认标签页马上会用到的图片。Season6 参考默认把长背景、顶部时间、标签页、用户信息、奖励模块图片放在同步预加载里。
+
+| 图片名 | 是否包含多语言 | 备注 |
+| --- | --- | --- |
+| bg0 | 否 | 首屏长背景 |
+| bg1 | 否 | 长背景第 1 段 |
+| bg2 | 否 | 长背景第 2 段 |
+| bg3 | 否 | 长背景第 3 段 |
+| timeBg | 否 | 顶部时间模块 |
+| tagsBg | 否 | 标签背景 |
+| tagBtBg | 否 | 标签选中底图 |
+| rankBt | 是 | 首屏标签按钮 |
+| rankBtActive | 是 | 首屏标签按钮 |
+| lotteryBt | 是 | 首屏标签按钮 |
+| lotteryBtActive | 是 | 首屏标签按钮 |
+| myInfoBg | 否 | 我的信息背景 |
+| myAvatarFrame | 否 | 我的头像框 |
+| rechargehidden | 否 | 充值金额隐藏图标 |
+| rechargeShow | 否 | 充值金额显示图标 |
+| rewardBg | 否 | 默认奖励模块背景 |
+| rewardBack | 否 | 奖励上一页按钮 |
+| rewardNext | 否 | 奖励下一页按钮 |
+| rewardReceive | 是 | 奖励领取按钮 |
+| rewardReceived | 是 | 奖励已领取按钮 |
+| rewardReceiveNot | 是 | 奖励不可领取按钮 |
+| RewardRecharge100 | 否 | 充值 100 奖励图 |
+| RewardRecharge500 | 否 | 充值 500 奖励图 |
+| RewardRecharge1000 | 否 | 充值 1000 奖励图 |
+| RewardRecharge3000 | 否 | 充值 3000 奖励图 |
+| RewardRecharge5000 | 否 | 充值 5000 奖励图 |
+| RewardRecharge10000 | 否 | 充值 10000 奖励图 |
+
+### 3.3 进入页面后后台预加载图片
+
+说明:这里放非默认标签页、下方列表、弹窗、抽奖和任务相关图片。它们不阻塞进入页面动画,页面进入后后台继续加载。
+
+| 图片名 | 是否包含多语言 | 备注 |
+| --- | --- | --- |
+| lottery | 否 | 切到抽奖页时使用 |
+| prizeActiveFrame | 否 | 抽奖高亮框 |
+| ticket | 否 | 抽奖券图标 |
+| historyBt | 是 | 抽奖历史按钮 |
+| historyBg | 是 | 历史弹窗背景 |
+| helpInfo | 是 | 帮助弹窗说明图 |
+| ruleInfo | 是 | 规则弹窗说明图 |
+| rulesBt | 是 | 规则按钮 |
+| RankingItem | 否 | 排行榜列表项背景 |
+| myRankingBg | 否 | 我的排名背景 |
+| top1 | 否 | 第一名装饰 |
+| top2 | 否 | 第二名装饰 |
+| top3 | 否 | 第三名装饰 |
+| reward_frame | 否 | 头像框奖励图标 |
+| reward_gift | 否 | 礼物奖励图标 |
+| reward_ride | 否 | 座驾奖励图标 |
+| taskBtGo | 否 | 任务去完成按钮 |
+| taskBtReceive | 否 | 任务领取按钮 |
+| taskBtReceived | 否 | 任务已领取按钮 |
+| taskGame | 否 | 游戏任务图标 |
+| taskGift | 否 | 礼物任务图标 |
+| taskRecharge | 否 | 充值任务图标 |
+| taskTalk | 否 | 聊天任务图标 |
+
+## 4. 模块配置
+
+### 4.1 标签页模块
+
+```json
+{
+ "enabled": true,
+ "defaultValue": "reward",
+ "items": [
+ {
+ "value": "reward",
+ "labelKey": "recharge_reward",
+ "buttonImage": "rankBt",
+ "activeButtonImage": "rankBtActive",
+ "remark": "奖励组内的充值奖励页"
+ },
+ {
+ "value": "ranking",
+ "labelKey": "ranking",
+ "buttonImage": "rankBt",
+ "activeButtonImage": "rankBtActive",
+ "remark": "奖励组内的排行榜页"
+ },
+ {
+ "value": "lottery",
+ "labelKey": "lottery",
+ "buttonImage": "lotteryBt",
+ "activeButtonImage": "lotteryBtActive",
+ "remark": "抽奖组内的抽奖页"
+ },
+ {
+ "value": "withdraw",
+ "labelKey": "withdraw",
+ "buttonImage": "lotteryBt",
+ "activeButtonImage": "lotteryBtActive",
+ "remark": "抽奖组内的提现页"
+ }
+ ]
+}
+```
+
+### 4.2 页面区块模块
+
+```json
+[
+ {
+ "name": "reward",
+ "enabled": true,
+ "apis": ["activityRewardConfigs", "myRecharge", "receiveRechargeReward"],
+ "images": ["rewardBg", "rewardReceive", "rewardReceived", "rewardReceiveNot"],
+ "remark": "充值奖励"
+ },
+ {
+ "name": "ranking",
+ "enabled": true,
+ "apis": ["ranklist"],
+ "images": ["RankingItem", "myRankingBg", "top1", "top2", "top3"],
+ "remark": "服务端分页排行榜"
+ },
+ {
+ "name": "lottery",
+ "enabled": true,
+ "apis": ["activityDetail", "onceDraw", "multiDraw", "myTickets", "myTotalDrawCount"],
+ "images": ["lottery", "prizeActiveFrame", "ticket"],
+ "remark": "抽奖机"
+ },
+ {
+ "name": "task",
+ "enabled": true,
+ "apis": ["ActTaskList", "receiveTickets"],
+ "images": ["taskBtGo", "taskBtReceive", "taskBtReceived"],
+ "remark": "任务列表"
+ },
+ {
+ "name": "withdraw",
+ "enabled": true,
+ "apis": ["withdrawableAmount", "transferActivityDollar", "exchangeCoin"],
+ "images": [],
+ "remark": "提现、转账、兑换;本地图片清单未扫描到 earningInfoBg,图片待确认"
+ },
+ {
+ "name": "countdown",
+ "enabled": true,
+ "apis": [],
+ "images": ["timeBg"],
+ "remark": "倒计时"
+ }
+]
+```
+
+### 4.3 弹窗模块
+
+```json
+[
+ {
+ "name": "rule",
+ "triggerImage": "rulesBt",
+ "backgroundImage": "",
+ "contentImage": "ruleInfo",
+ "apis": [],
+ "remark": "规则说明"
+ },
+ {
+ "name": "help",
+ "triggerImage": "",
+ "backgroundImage": "",
+ "contentImage": "helpInfo",
+ "apis": [],
+ "remark": "帮助说明;本地图片清单未扫描到 helpBt,触发按钮待确认"
+ },
+ {
+ "name": "history",
+ "triggerImage": "historyBt",
+ "backgroundImage": "historyBg",
+ "contentImage": "",
+ "apis": ["drawRecords"],
+ "remark": "中奖历史"
+ },
+ {
+ "name": "result",
+ "triggerImage": "",
+ "backgroundImage": "",
+ "contentImage": "",
+ "apis": ["onceDraw", "multiDraw"],
+ "remark": "中奖结果;本地图片清单未扫描到 resultBg,背景图待确认"
+ },
+ {
+ "name": "rewardDetail",
+ "triggerImage": "",
+ "backgroundImage": "rewardBg",
+ "contentImage": "",
+ "apis": ["activityRewardConfigs"],
+ "remark": "奖励详情"
+ }
+]
+```
+
+### 4.4 排行榜模块
+
+```json
+{
+ "enabled": true,
+ "api": "ranklist",
+ "loadMode": "remote",
+ "pageSize": 20,
+ "topCount": 3,
+ "myRankingPath": "ranklist.body.currentUser",
+ "fieldMap": {
+ "avatar": "userAvatar",
+ "name": "userName",
+ "value": "amount",
+ "userId": "userId",
+ "rank": "rank"
+ }
+}
+```
+
+### 4.5 抽奖模块
+
+```json
+{
+ "enabled": true,
+ "drawApis": ["onceDraw", "multiDraw"],
+ "prizePoolApi": "activityDetail",
+ "drawCountApi": "myTickets",
+ "extraApis": ["myTotalDrawCount", "rewardConfigsBadge", "winnerHistory"],
+ "displayMode": "track-only",
+ "slotCount": 8,
+ "centerButtonPosition": "grid-area: 2 / 2",
+ "historyButtonPosition": "inside",
+ "images": {
+ "background": "lottery",
+ "activeFrame": "prizeActiveFrame",
+ "singleDrawButton": "无,按钮区域在背景图上",
+ "multiDrawButton": "无,按钮区域在背景图上",
+ "historyButton": "historyBt",
+ "resultBackground": ""
+ },
+ "remark": "奖品内容已画在抽奖背景图上,接口奖品只用于计算高亮框停靠位置;本地图片清单未扫描到 resultBg。"
+}
+```
+
+## 5. 接口参数
+
+```json
+{
+ "activityId": "2007771533999124198",
+ "activityCode": "24198",
+ "activityType": "CONSUMPTION_ACTIVITY",
+ "rankingActivityType": "USER_RECHARGE_DRAW",
+ "rewardActivityType": "CONSUMPTION_ACTIVITY",
+ "otherParams": {
+ "drawCount": [1, 10]
+ }
+}
+```
+
+## 6. 组件需求
+
+```json
+[
+ {
+ "name": "BackgroundLayer",
+ "enabled": true,
+ "remark": "使用 bg0/bg1/bg2/bg3 长背景"
+ },
+ {
+ "name": "itemCenter",
+ "enabled": true,
+ "remark": "图片容器"
+ },
+ {
+ "name": "topUser / TopUser",
+ "enabled": true,
+ "remark": "从参考页面组件复制后微调"
+ },
+ {
+ "name": "Barrage",
+ "enabled": true,
+ "remark": "从参考页面组件复制后微调"
+ },
+ {
+ "name": "ActivityTabs",
+ "enabled": "可选",
+ "remark": "如果重做标签页,优先用通用组件"
+ },
+ {
+ "name": "ActivityLotteryGrid",
+ "enabled": true,
+ "remark": "抽奖格子和高亮框"
+ }
+]
+```
+
+## 7. 建页后记录
+
+这部分由 Codex 建页后填写。
+
+### 7.1 已完成
+
+-
+
+### 7.2 未填写所以跳过
+
+-
+
+### 7.3 待你微调
+
+-
+
+### 7.4 验证结果
+
+- `npm run build`:
+- 备注:
diff --git a/src/views/Activities/LuckyDollars/Season6/页面创建信息-待填写.md b/src/views/Activities/LuckyDollars/Season6/页面创建信息-待填写.md
new file mode 100644
index 0000000..06a33d0
--- /dev/null
+++ b/src/views/Activities/LuckyDollars/Season6/页面创建信息-待填写.md
@@ -0,0 +1,433 @@
+# 页面创建信息
+
+## 1. 基础信息
+
+- 页面类型:Activities
+- 页面名称:LuckyDollars Season6
+- 页面目录:src/views/Activities/LuckyDollars/Season6
+- 参考页面:src/views/Activities/LuckyDollars/Season5
+- 备注:作为新页面处理,初版效果参考 Season5,接口参数和图片可先沿用 Season5/Season6 测试数据。
+
+## 2. 路由和权限
+
+- route path:/activities/lucky-dollars-season6
+- route name:lucky-dollars-season6
+- 是否需要登录:是
+- 是否加入 `src/config/security.js`:是
+- 是否加入 `src/utils/permissionManager.js`:是
+
+### 2.1 路由代码片段
+
+建页时由 Codex 根据上面的 route path、route name 和页面目录生成。
+
+```js
+{
+ path: '/activities/lucky-dollars-season6',
+ name: 'lucky-dollars-season6',
+ component: () => import('../views/Activities/LuckyDollars/Season6/index.vue'),
+ meta: { requiresAuth: true },
+}
+```
+
+### 2.2 安全白名单片段
+
+建页时由 Codex 使用上面的 route path 补充到 `src/config/security.js`。
+
+```js
+'/activities/lucky-dollars-season6'
+```
+
+### 2.3 权限页面片段
+
+建页时由 Codex 使用上面的 route path 补充到 `src/utils/permissionManager.js`,后面的注释使用页面名称。
+
+```js
+'/activities/lucky-dollars-season6' // 幸运美金第 6 季
+```
+
+## 3. OSS 图片目录
+
+- OSS 相对路径:Activities/LuckyDollars/Season6/
+- 本地开发目录:public/oss/h5/Azizi/Activities/LuckyDollars/Season6/
+- 是否需要 Codex 自动创建本地开发目录:是
+- 图片是否已放入本地目录:是
+
+说明:OSS 相对路径和本地开发目录由 Codex 根据页面类型和页面目录自动生成,你可以按需要修改。如果“是否需要 Codex 自动创建本地开发目录”填“是”,Codex 建页时会先创建这个目录,方便你直接放图片。
+
+### 3.1 多语言图片清单
+
+说明:下面清单由 Codex 扫描本地开发目录自动生成。无后缀图片视为 `en` 原图;如存在 `_ar`、`_bn`、`_tr` 等后缀,则合并到同一个图片名的支持语言中。页面生成时以这张清单为准处理多语言图片,你可以按实际需求删除、改名或补备注。
+
+| 图片名 | 支持语言 | 备注 |
+| --- | --- | --- |
+| bg0 | en | 页面背景第 0 段 |
+| bg1 | en | 页面背景第 1 段 |
+| bg2 | en | 页面背景第 2 段 |
+| bg3 | en | 页面背景第 3 段 |
+| helpInfo | en, ar | 帮助说明图 |
+| historyBg | en, ar | 历史弹窗背景 |
+| historyBt | en, ar | 历史按钮 |
+| lottery | en | 抽奖机背景 |
+| lotteryBt | en, ar | 抽奖按钮 |
+| lotteryBtActive | en, ar | 抽奖选中按钮 |
+| myAvatarFrame | en | 我的头像框 |
+| myInfoBg | en | 我的信息背景 |
+| myRankingBg | en | 我的排名背景 |
+| prizeActiveFrame | en | 抽奖高亮框 |
+| rankBt | en, ar | 排行榜按钮 |
+| rankBtActive | en, ar | 排行榜选中按钮 |
+| RankingItem | en | 排行榜列表项背景 |
+| rechargehidden | en | 充值金额隐藏图标 |
+| rechargeShow | en | 充值金额显示图标 |
+| reward_frame | en | 头像框奖励图标 |
+| reward_gift | en | 礼物奖励图标 |
+| reward_ride | en | 座驾奖励图标 |
+| rewardBack | en | 奖励上一页按钮 |
+| rewardBg | en | 奖励模块背景 |
+| rewardNext | en | 奖励下一页按钮 |
+| rewardReceive | en, ar | 奖励领取按钮 |
+| rewardReceived | en, ar | 奖励已领取按钮 |
+| rewardReceiveNot | en, ar | 奖励不可领取按钮 |
+| RewardRecharge100 | en | 充值 100 奖励图 |
+| RewardRecharge1000 | en | 充值 1000 奖励图 |
+| RewardRecharge10000 | en | 充值 10000 奖励图 |
+| RewardRecharge3000 | en | 充值 3000 奖励图 |
+| RewardRecharge500 | en | 充值 500 奖励图 |
+| RewardRecharge5000 | en | 充值 5000 奖励图 |
+| ruleInfo | en, ar | 规则说明图 |
+| rulesBt | en, ar | 规则按钮 |
+| tagBtBg | en | 标签页选中底图 |
+| tagsBg | en | 标签页背景 |
+| taskBtGo | en | 任务去完成按钮 |
+| taskBtReceive | en | 任务领取按钮 |
+| taskBtReceived | en | 任务已领取按钮 |
+| taskGame | en | 游戏任务图标 |
+| taskGift | en | 礼物任务图标 |
+| taskRecharge | en | 充值任务图标 |
+| taskTalk | en | 聊天任务图标 |
+| ticket | en | 抽奖券图标 |
+| timeBg | en | 活动时间背景 |
+| top1 | en | 第一名装饰 |
+| top2 | en | 第二名装饰 |
+| top3 | en | 第三名装饰 |
+
+### 3.2 进入页面前同步预加载图片
+
+说明:这里填写会参与进入页面加载动画的关键图片。Codex 生成页面时会先等待这些图片加载完成,再结束预加载动画。
+
+| 图片名 | 是否包含多语言 | 备注 |
+| --- | --- | --- |
+| bg0 | 否 | 首屏长背景 |
+| bg1 | 否 | 长背景第 1 段 |
+| bg2 | 否 | 长背景第 2 段 |
+| bg3 | 否 | 长背景第 3 段 |
+| timeBg | 否 | 顶部时间模块 |
+| tagsBg | 否 | 标签背景 |
+| tagBtBg | 否 | 标签选中底图 |
+| rankBt | 是 | 首屏标签按钮 |
+| rankBtActive | 是 | 首屏标签按钮 |
+| lotteryBt | 是 | 首屏标签按钮 |
+| lotteryBtActive | 是 | 首屏标签按钮 |
+| myInfoBg | 否 | 我的信息背景 |
+| myAvatarFrame | 否 | 我的头像框 |
+| rechargehidden | 否 | 充值金额隐藏图标 |
+| rechargeShow | 否 | 充值金额显示图标 |
+| rewardBg | 否 | 默认奖励模块背景 |
+| rewardBack | 否 | 奖励上一页按钮 |
+| rewardNext | 否 | 奖励下一页按钮 |
+| rewardReceive | 是 | 奖励领取按钮 |
+| rewardReceived | 是 | 奖励已领取按钮 |
+| rewardReceiveNot | 是 | 奖励不可领取按钮 |
+| RewardRecharge100 | 否 | 充值 100 奖励图 |
+| RewardRecharge500 | 否 | 充值 500 奖励图 |
+| RewardRecharge1000 | 否 | 充值 1000 奖励图 |
+| RewardRecharge3000 | 否 | 充值 3000 奖励图 |
+| RewardRecharge5000 | 否 | 充值 5000 奖励图 |
+| RewardRecharge10000 | 否 | 充值 10000 奖励图 |
+
+### 3.3 进入页面后后台预加载图片
+
+说明:这里填写不影响进入页面动画的图片。Codex 可以在首屏图片加载完成后继续后台预加载它们,避免后续打开弹窗、切标签页或滚动到下方时才开始请求图片。
+
+| 图片名 | 是否包含多语言 | 备注 |
+| --- | --- | --- |
+| lottery | 否 | 切到抽奖页时使用 |
+| prizeActiveFrame | 否 | 抽奖高亮框 |
+| ticket | 否 | 抽奖券图标 |
+| historyBt | 是 | 抽奖历史按钮 |
+| historyBg | 是 | 历史弹窗背景 |
+| helpInfo | 是 | 帮助弹窗说明图 |
+| ruleInfo | 是 | 规则弹窗说明图 |
+| rulesBt | 是 | 规则按钮 |
+| RankingItem | 否 | 排行榜列表项背景 |
+| myRankingBg | 否 | 我的排名背景 |
+| top1 | 否 | 第一名装饰 |
+| top2 | 否 | 第二名装饰 |
+| top3 | 否 | 第三名装饰 |
+| reward_frame | 否 | 头像框奖励图标 |
+| reward_gift | 否 | 礼物奖励图标 |
+| reward_ride | 否 | 座驾奖励图标 |
+| taskBtGo | 否 | 任务去完成按钮 |
+| taskBtReceive | 否 | 任务领取按钮 |
+| taskBtReceived | 否 | 任务已领取按钮 |
+| taskGame | 否 | 游戏任务图标 |
+| taskGift | 否 | 礼物任务图标 |
+| taskRecharge | 否 | 充值任务图标 |
+| taskTalk | 否 | 聊天任务图标 |
+
+## 4. 模块配置
+
+没有填写的模块,初版页面先不处理。
+
+### 4.1 标签页模块
+
+```json
+{
+ "enabled": true,
+ "defaultValue": "reward",
+ "items": [
+ {
+ "value": "reward",
+ "labelKey": "recharge_reward",
+ "buttonImage": "rankBt",
+ "activeButtonImage": "rankBtActive",
+ "remark": "奖励组内的充值奖励页"
+ },
+ {
+ "value": "ranking",
+ "labelKey": "ranking",
+ "buttonImage": "rankBt",
+ "activeButtonImage": "rankBtActive",
+ "remark": "奖励组内的排行榜页"
+ },
+ {
+ "value": "lottery",
+ "labelKey": "lottery",
+ "buttonImage": "lotteryBt",
+ "activeButtonImage": "lotteryBtActive",
+ "remark": "抽奖组内的抽奖页"
+ },
+ {
+ "value": "withdraw",
+ "labelKey": "withdraw",
+ "buttonImage": "lotteryBt",
+ "activeButtonImage": "lotteryBtActive",
+ "remark": "抽奖组内的提现页"
+ }
+ ]
+}
+```
+
+### 4.2 页面区块模块
+
+```json
+[
+ {
+ "name": "reward",
+ "enabled": true,
+ "apis": ["activityRewardConfigs", "myRecharge", "receiveRechargeReward"],
+ "images": ["rewardBg", "rewardReceive", "rewardReceived", "rewardReceiveNot"],
+ "remark": "奖励区块"
+ },
+ {
+ "name": "ranking",
+ "enabled": true,
+ "apis": ["ranklist"],
+ "images": ["RankingItem", "myRankingBg", "top1", "top2", "top3"],
+ "remark": "排行榜区块"
+ },
+ {
+ "name": "lottery",
+ "enabled": true,
+ "apis": ["activityDetail", "onceDraw", "multiDraw", "myTickets", "myTotalDrawCount"],
+ "images": ["lottery", "prizeActiveFrame", "ticket"],
+ "remark": "抽奖区块"
+ },
+ {
+ "name": "task",
+ "enabled": true,
+ "apis": ["ActTaskList", "receiveTickets"],
+ "images": ["taskBtGo", "taskBtReceive", "taskBtReceived"],
+ "remark": "任务区块"
+ },
+ {
+ "name": "withdraw",
+ "enabled": true,
+ "apis": ["withdrawableAmount", "transferActivityDollar", "exchangeCoin"],
+ "images": [],
+ "remark": "提现/兑换区块;本地图片清单未扫描到 earningInfoBg,图片待确认"
+ },
+ {
+ "name": "countdown",
+ "enabled": true,
+ "apis": [],
+ "images": ["timeBg"],
+ "remark": "倒计时区块,截止日期为 2026-06-01"
+ }
+]
+```
+
+### 4.3 弹窗模块
+
+```json
+[
+ {
+ "name": "rule",
+ "triggerImage": "rulesBt",
+ "backgroundImage": "",
+ "contentImage": "ruleInfo",
+ "apis": [],
+ "remark": "规则弹窗"
+ },
+ {
+ "name": "help",
+ "triggerImage": "",
+ "backgroundImage": "",
+ "contentImage": "helpInfo",
+ "apis": [],
+ "remark": "帮助弹窗;本地图片清单未扫描到 helpBt,触发按钮待确认"
+ },
+ {
+ "name": "history",
+ "triggerImage": "historyBt",
+ "backgroundImage": "historyBg",
+ "contentImage": "",
+ "apis": ["drawRecords"],
+ "remark": "历史记录弹窗"
+ },
+ {
+ "name": "result",
+ "triggerImage": "",
+ "backgroundImage": "",
+ "contentImage": "",
+ "apis": ["onceDraw", "multiDraw"],
+ "remark": "中奖结果弹窗;本地图片清单未扫描到 resultBg,背景图待确认"
+ },
+ {
+ "name": "rewardDetail",
+ "triggerImage": "",
+ "backgroundImage": "rewardBg",
+ "contentImage": "",
+ "apis": ["activityRewardConfigs"],
+ "remark": "奖励详情弹窗"
+ }
+]
+```
+
+### 4.4 排行榜模块
+
+```json
+{
+ "enabled": true,
+ "api": "ranklist",
+ "loadMode": "remote",
+ "pageSize": 20,
+ "topCount": 3,
+ "myRankingPath": "ranklist.body.currentUser",
+ "fieldMap": {
+ "avatar": "userAvatar",
+ "name": "userName",
+ "value": "amount",
+ "userId": "userId",
+ "rank": "rank"
+ }
+}
+```
+
+### 4.5 抽奖模块
+
+```json
+{
+ "enabled": true,
+ "drawApis": ["onceDraw", "multiDraw"],
+ "prizePoolApi": "activityDetail",
+ "drawCountApi": "myTickets",
+ "extraApis": ["myTotalDrawCount", "rewardConfigsBadge", "winnerHistory"],
+ "displayMode": "track-only",
+ "slotCount": 8,
+ "centerButtonPosition": "grid-area: 2 / 2",
+ "historyButtonPosition": "inside",
+ "images": {
+ "background": "lottery",
+ "activeFrame": "prizeActiveFrame",
+ "singleDrawButton": "无,按钮区域在背景图上",
+ "multiDrawButton": "无,按钮区域在背景图上",
+ "historyButton": "historyBt",
+ "resultBackground": ""
+ },
+ "remark": "奖品内容已画在抽奖背景图上,接口奖品只用于计算高亮框停靠位置;本地图片清单未扫描到 resultBg。"
+}
+```
+
+## 5. 接口参数
+
+```json
+{
+ "activityId": "",
+ "activityCode": "",
+ "activityType": "",
+ "rankingActivityType": "",
+ "rewardActivityType": "",
+ "otherParams": {}
+}
+```
+
+## 6. 组件需求
+
+需要哪个就填“是”,不需要或没填的初版页面先不接。
+
+```json
+[
+ {
+ "name": "BackgroundLayer",
+ "enabled": "",
+ "remark": "长背景分段"
+ },
+ {
+ "name": "itemCenter",
+ "enabled": "",
+ "remark": "图片容器"
+ },
+ {
+ "name": "topUser / TopUser",
+ "enabled": "",
+ "remark": "前三名用户展示"
+ },
+ {
+ "name": "Barrage",
+ "enabled": "",
+ "remark": "弹幕"
+ },
+ {
+ "name": "ActivityTabs",
+ "enabled": "",
+ "remark": "标签页"
+ },
+ {
+ "name": "ActivityLotteryGrid",
+ "enabled": "",
+ "remark": "抽奖格子"
+ }
+]
+```
+
+## 7. 建页后记录
+
+这部分由 Codex 建页后填写。
+
+### 7.1 已完成
+
+-
+
+### 7.2 未填写所以跳过
+
+-
+
+### 7.3 待你微调
+
+-
+
+### 7.4 验证结果
+
+- `npm run build`:
+- 备注:
diff --git a/vite.config.js b/vite.config.js
index 98ae941..0c055dc 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -3,11 +3,15 @@ import Components from 'unplugin-vue-components/vite'
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'
+import { existsSync } from 'node:fs'
+import { isAbsolute, relative, resolve } from 'node:path'
import { VantResolver } from 'unplugin-vue-components/resolvers'
const buildTimestamp = Date.now()
const PRODUCTION_LIKE_MODES = new Set(['production', 'production-atu'])
const STRIPPABLE_CONSOLE_METHODS = ['log', 'debug', 'info']
+const PUBLIC_DIR = fileURLToPath(new URL('./public', import.meta.url))
+const PUBLIC_ROOT = resolve(PUBLIC_DIR)
function isEscaped(code, index) {
let backslashCount = 0
@@ -315,6 +319,23 @@ export default defineConfig(({ mode }) => {
target: 'https://cdn.azizichat.com',
changeOrigin: true,
secure: true,
+ bypass: (req) => {
+ const requestUrl = req.url || ''
+
+ try {
+ const pathname = decodeURIComponent(new URL(requestUrl, 'http://localhost').pathname)
+ const localPublicFile = resolve(PUBLIC_ROOT, `.${pathname}`)
+ const localPathInPublic = relative(PUBLIC_ROOT, localPublicFile)
+
+ if (!localPathInPublic.startsWith('..') && !isAbsolute(localPathInPublic) && existsSync(localPublicFile)) {
+ return pathname
+ }
+ } catch (error) {
+ console.warn('Failed to check local OSS fallback:', error)
+ }
+
+ return null
+ },
rewrite: (path) => path.replace(/^\/oss/, ''),
configure: (proxy) => {
proxy.on('proxyRes', (proxyRes) => {