diff --git a/.hermes/plans/chatroom-frontend-optimization.md b/.hermes/plans/chatroom-frontend-optimization.md
new file mode 100644
index 0000000..7638674
--- /dev/null
+++ b/.hermes/plans/chatroom-frontend-optimization.md
@@ -0,0 +1,311 @@
+# 🚀 聊天室项目 — 前端加载优化详细方案
+
+> **项目路径:** `/Users/pllx/Web/Herd/chatroom`
+> **当前构建输出:** `public/build/` 共 664KB
+> **核心问题:** `chat.js` 单文件 **308KB**,所有功能模块一次性加载
+
+---
+
+## 一、现状分析
+
+### 当前构建产物
+
+| 文件 | 大小 | 说明 |
+|------|------|------|
+| `chat-*.js` | **308 KB** | 🚨 **主聊天室脚本** — 全部模块一次性加载 |
+| `app-*.js` (后台) | 24 KB | ✅ 合理 |
+| `effects-*.js` | 5.6 KB | ✅ 已代码分割(特效按需分包) |
+| 其他入口 | 1.5~5.6 KB | ✅ 正常 |
+| **图片资源** | 87 KB | ✅ 12张背景图已很小 |
+| **CSS** | 532行 | ✅ 轻量 |
+
+### 根因分析
+
+`resources/js/chat.js` → 导入 `bootstrap.js` + `chat-room.js` + `context.js`
+
+而 `chat-room.js`(**982行**)**同步导入**了所有 50+ 个子模块:
+
+| 类别 | 模块 | 行数 |
+|------|------|------|
+| 🎮 **游戏(5,483行)** | gomoku-panel(1001), horse-race-panel(552), baccarat-panel(448), baccarat-loss-cover(408), slot-machine(347), lottery-panel(343), fishing(625), fortune-panel(173), game-hall(586), baccarat-events(129)等 | **5,483行** |
+| 🛒 **商店/银行** | shop-controls(903), compact-shop-panel(586), bank-modal(477) | **1,966行** |
+| 💒 **婚姻系统** | marriage-modals(920), marriage-status(662) | **1,582行** |
+| 👤 **用户资料** | user-card(855), profile-controls(715), friend-panel(447) | **2,017行** |
+| 👑 **VIP/红包** | vip-controls(477), red-packet-panel(679) | **1,156行** |
+| 🛠️ **核心功能** | message-renderer(474), chat-state(223), composer(231), preferences-status(855)等 | **~3,000行** |
+
+**核心问题:** 用户进入聊天室,等看到 30KB 核心界面之前,要先下载并解析 **308KB 的所有游戏代码**——哪怕他从没点开过百家乐、五子棋或赛马。
+
+---
+
+## 二、优化方案
+
+### 🔥 方案 A:游戏模块动态导入(收益最大)
+
+**目标:** 将 5 个主要游戏的 UI 面板改为按需加载(用户点击时才下载对应代码)。
+
+#### 涉及的模块
+
+| 游戏 | 文件 | 行数 | 建议策略 |
+|------|------|------|---------|
+| 🎰 **五子棋** | gomoku-panel.js | 1,001行 | 点击「五子棋」按钮时动态 import |
+| 🏇 **赛马** | horse-race-panel.js | 552行 | 点击「赛马」按钮时动态 import |
+| 🃏 **百家乐** | baccarat-panel.js | 448行 | 点击「百家乐」按钮时动态 import |
+| 🎣 **钓鱼** | fishing.js | 625行 | 点击「钓鱼」按钮时动态 import |
+| 🔫 **老虎机** | slot-machine.js | 347行 | 点击「老虎机」按钮时动态 import |
+| 🎲 **彩票** | lottery-panel.js | 343行 | 点击「双色球」按钮时动态 import |
+| 🔮 **占卜** | fortune-panel.js | 173行 | 点击「神秘占卜」时动态 import |
+
+#### 具体实现方式
+
+**修改前(chat-room.js 的 game-hall.js):**
+```js
+// 当前:同步导入
+import { bindBaccaratPanelControls, baccaratPanel } from "./baccarat-panel.js";
+import { bindGomokuPanelControls, gomokuPanel } from "./gomoku-panel.js";
+// ...更多同步导入
+```
+
+**修改后:**
+```js
+// game-hall.js 中延迟加载
+export async function openGameHall() {
+ // 先渲染游戏大厅界面(无游戏逻辑)
+ renderGameHallUI();
+
+ // 只有当用户点击具体游戏时才加载对应模块
+ document.getElementById('btn-baccarat').addEventListener('click', async () => {
+ const { bindBaccaratPanelControls, baccaratPanel } = await import('./baccarat-panel.js');
+ bindBaccaratPanelControls();
+ baccaratPanel.open();
+ });
+
+ document.getElementById('btn-gomoku').addEventListener('click', async () => {
+ const { bindGomokuPanelControls, gomokuPanel } = await import('./gomoku-panel.js');
+ bindGomokuPanelControls();
+ gomokuPanel.open();
+ });
+
+ // ...其他游戏同理
+}
+```
+
+**预期收益:**
+- `chat.js` 从 **308KB → ~150KB**(减半)
+- 各游戏模块按需加载(20~50KB 每次)
+- 首次交互速度提升 **50%+**
+
+---
+
+### 🔥 方案 B:游戏事件监听延迟注册
+
+**问题:** 游戏的 WebSocket 事件监听(赛马进度、百家乐开局等)在 `chat.js` 启动时全部注册。
+
+**方案:** 同样采用延迟注册——只在对应游戏被首次打开后才激活事件监听。
+
+```js
+// 当前:chat-events.js 中 .listen(".horse.progress", ...) 等
+// 改为:游戏面板打开时再注册
+export function initHorseRaceEventsOnce() {
+ if (window._horseRaceEventsInitialized) return;
+ window._horseRaceEventsInitialized = true;
+
+ window.Echo.join(`room.${roomId}`)
+ .listen(".horse.progress", (e) => { /* ... */ });
+}
+```
+
+---
+
+### 🔥 方案 C:重型功能模块懒加载
+
+**目标:** 以下模块仅在用户点击对应按钮时加载。
+
+| 模块 | 文件 | 行数 | 触发时机 |
+|------|------|------|---------|
+| 🛒 **商店** | shop-controls.js | 903行 | 点击「商店」按钮 |
+| 💒 **婚姻** | marriage-modals.js | 920行 | 点击「婚姻」入口 |
+| 👤 **用户名片** | user-card.js | 855行 | 点击任意用户名 |
+| 🏦 **银行** | bank-modal.js | 477行 | 点击「银行」按钮 |
+| 👑 **VIP** | vip-controls.js | 477行 | 点击「VIP中心」 |
+| 🧧 **红包** | red-packet-panel.js | 679行 | 点击「发红包」或抢包 |
+| ⚙️ **个人设置** | profile-controls.js | 715行 | 点击「个人资料」 |
+
+#### 具体实现方式
+
+通过 Vite 的「魔法注释」给动态 chunk 命名:
+
+```js
+// 在工具栏按钮点击处理器中
+document.getElementById('btn-shop')?.addEventListener('click', async () => {
+ const { bindShopControls, openShopModal } = await import(
+ /* webpackChunkName: "shop" */ './shop-controls.js'
+ );
+ bindShopControls();
+ openShopModal();
+});
+
+document.getElementById('btn-marriage')?.addEventListener('click', async () => {
+ const { bindMarriageModalControls, openProposeModal } = await import(
+ /* webpackChunkName: "marriage" */ './marriage-modals.js'
+ );
+ bindMarriageModalControls();
+});
+```
+
+**预期收益:** chat.js 再减少 **~4,000行**(从 150KB → **~100KB**)
+
+---
+
+### ⚡ 方案 D:Vite 构建优化
+
+当前 `vite.config.js` 只做了基础配置,可以增加以下优化:
+
+```js
+// vite.config.js
+export default defineConfig({
+ plugins: [
+ laravel({ input: [...], refresh: true }),
+ tailwindcss(),
+ ],
+ build: {
+ rollupOptions: {
+ output: {
+ // 自动分包策略
+ manualChunks(id) {
+ // 将 vendor 依赖(axios, Echo, Pusher)单独打包
+ if (id.includes('node_modules')) {
+ return 'vendor';
+ }
+ },
+ },
+ },
+ // 压缩级别
+ minify: 'esbuild', // 默认已启用
+ // CSS 代码分割
+ cssCodeSplit: true, // 默认已启用
+ // 启用 sourcemap 仅开发环境
+ sourcemap: false, // 生产环境关闭
+ },
+});
+```
+
+---
+
+### ⚡ 方案 E:图片优化
+
+虽然背景图已经很小(87KB 共12张),但仍有优化空间:
+
+| 优化 | 说明 | 收益 |
+|------|------|------|
+| WebP 格式 | 将 PNG 背景图转为 WebP | 减少 30-50% 体积 |
+| 聊天图片懒加载 | 消息中的图片使用 `loading="lazy"` | 非可视区域图片不加载 |
+| 图片预压缩 | 上传头像时生成多尺寸缩略图 | 减少列表页加载时间 |
+
+```js
+// 在 message-renderer.js 中的图片渲染部分
+// 当前:
+
+// 改为:
+
+```
+
+---
+
+### ⚡ 方案 F:CSS 优化
+
+当前 CSS 很小(532行),但可以进一步优化:
+
+```js
+// chat-decorations.css(384行)中检查是否有未使用的样式
+// 建议:将 chat.css 也通过 Vite 导入,而非放在 public/css/ 中
+```
+
+---
+
+### 💡 方案 G:WebSocket 连接延迟
+
+**当前:** `chat.js` 在页面加载时立即建立 WebSocket Presence Channel 连接。
+**优化:** 核心消息通道保持立即连接,但游戏专属事件监听(如赛马进度)延迟注册。
+
+```js
+// chat.js → initChat() 中
+// 保持核心监听(消息、在线、退出、禁言等)
+// 将游戏事件监听移动到各游戏模块各自的 init 中
+```
+
+---
+
+## 三、实施步骤
+
+### 第一阶段(核心 · 代码分割)
+
+| # | 任务 | 文件修改范围 | 预估工时 |
+|---|------|-------------|---------|
+| 1 | **游戏模块动态导入** | `chat-room/game-hall.js`, `chat-room/toolbar.js`, `chat-room.js` | 4h |
+| 2 | **游戏事件延迟注册** | `chat-room/baccarat-events.js`, `horse-race-events.js`, `chat-events.js` | 2h |
+| 3 | **商店/银行/婚姻懒加载** | `chat-room/toolbar.js`, `chat-room.js` | 3h |
+| 4 | **用户名片动态导入** | `chat-room/user-card.js`, `chat-room/user-target-actions.js` | 1h |
+| 5 | **Vite vendor 分包** | `vite.config.js` | 30min |
+
+**预期成果:** chat.js 从 **308KB → ~100KB**,首屏加载速度提升 **60%**
+
+### 第二阶段(优化 · 增强)
+
+| # | 任务 | 说明 | 预估工时 |
+|---|------|------|---------|
+| 6 | 聊天图片 `loading="lazy"` | `message-renderer.js` 中的 img 标签 | 30min |
+| 7 | 背景图转 WebP | 用 `cwebp` 或在线工具转换 | 30min |
+| 8 | 关闭 sourcemap | `vite.config.js` 配置 | 5min |
+| 9 | 测试每个动态导入路径 | 确保所有游戏/功能正常可用 | 2h |
+
+---
+
+## 四、验证方法
+
+优化后验证以下指标:
+
+```bash
+# 1. 查看构建产物大小
+ls -lh public/build/assets/chat-*.js
+
+# 2. 检查分包情况
+cat public/build/manifest.json | python3 -m json.tool
+
+# 3. 浏览器 DevTools 验证
+# - Network 面板:确认游戏模块只在点击后加载
+# - Coverage 面板:首屏 JS 使用率应 > 70%
+```
+
+**预期最终构建产物:**
+
+| 文件 | 预期大小 | 加载时机 |
+|------|---------|---------|
+| `chat-*.js` | **~100 KB** | 📌 页面加载 |
+| `vendor-*.js` | **~50 KB** | 📌 页面加载(Echo/Pusher/Axios) |
+| `game-baccarat-*.js` | **~25 KB** | 🔘 点击百家乐 |
+| `game-gomoku-*.js` | **~35 KB** | 🔘 点击五子棋 |
+| `shop-*.js` | **~20 KB** | 🔘 点击商店 |
+| `marriage-*.js` | **~18 KB** | 🔘 点击婚姻 |
+| `effects-*.js` | **~6 KB** | ✅ 已有分包 |
+| **总计首次加载** | **~150 KB** | 🚀 减少 **51%** |
+
+---
+
+## 五、注意事项
+
+1. **渐进式实施:** 不要一次性改所有模块。先改游戏模块,验证无问题后再改其他。
+2. **加载状态反馈:** 动态导入期间显示轻量加载指示(spinner),避免用户等待焦虑。
+3. **WebSocket 事件时序:** 游戏模块如果需要在页面加载时接收事件(如赛马正在进行的进度广播),不能完全延迟,需要保留核心事件监听器。
+4. **错误边界:** 动态导入失败时应有 fallback(如提示"模块加载失败,请刷新重试")。
+5. **缓存策略:** 动态分块后的文件名带 hash,Vite 自动处理缓存失效。
+
+---
+
+> **总结:** 最核心的优化就是**把游戏和重型功能模块从 chat.js 中拆出来**,
+> 利用 Vite 原生支持的 `import()` 动态导入实现按需加载。
+> 这项改动不需要修改后端代码、不需要修改路由、不需要重构现有逻辑,
+> 只需在现有模块的入口处添加 2-3 行 `await import()` 代码即可。
+>
+> 建议先从 **游戏模块**(方案 A)开始实施,收益最大、风险最低。
+> 完成后再逐步推进 **商店/婚姻/银行**(方案 C)的懒加载。
diff --git a/.hermes/plans/chatroom-security-performance-plan.md b/.hermes/plans/chatroom-security-performance-plan.md
new file mode 100644
index 0000000..681f477
--- /dev/null
+++ b/.hermes/plans/chatroom-security-performance-plan.md
@@ -0,0 +1,335 @@
+# 🛡️ 聊天室项目 — 安全与访问速度优化规划方案
+
+> **项目路径:** `/Users/pllx/Web/Herd/chatroom`
+> **技术栈:** Laravel 12 + PHP 8.4 + Redis + MySQL + Reverb (WebSocket) + TailwindCSS 4 + Vite
+> **检查日期:** 2026-04-27
+
+---
+
+## 一、安全优化(🔴高危 / 🟡中危 / 🟢低危)
+
+### 🔴 1. 关闭 APP_DEBUG(生产环境)
+
+**当前:** `.env` 中 `APP_DEBUG=true`
+**风险:** 生产环境开启 DEBUG 会在报错时泄露数据库密码、Redis 密码、Reverb 密钥等敏感信息。
+
+**方案:**
+```
+# .env 生产环境改为
+APP_DEBUG=false
+```
+
+---
+
+### 🔴 2. 启用 Session 加密
+
+**当前:** `SESSION_ENCRYPT=false`
+**风险:** Session 数据以明文存储在 Redis 中,若 Redis 被入侵或存在 SSRF,用户身份数据全部泄露。
+
+**方案:**
+```
+# .env 添加
+SESSION_ENCRYPT=true
+```
+
+---
+
+### 🔴 3. 限制 Reverb WebSocket 允许源(Allowed Origins)
+
+**当前:** `config/reverb.php` 中 `'allowed_origins' => ['*']`
+**风险:** 任何第三方网站均可连接你的 WebSocket 服务,可被用于 CSWSH(Cross-Site WebSocket Hijacking)攻击,窃取聊天消息。
+
+**方案:**
+```php
+// config/reverb.php
+'allowed_origins' => [
+ env('APP_URL', 'http://chatroom.test'),
+ // 如果有多个域名,手动列出
+],
+```
+
+---
+
+### 🔴 4. Reverb WebSocket 启用 TLS(WSS)
+
+**当前:** `REVERB_SCHEME=http`,WebSocket 走明文 HTTP
+**风险:** 所有聊天消息、用户在线状态等实时数据明文传输,可被中间人攻击窃听。
+
+**方案:**
+```
+# .env 生产环境
+REVERB_SCHEME=https
+REVERB_PORT=443 # 或 8443
+# 并在 reverb.php 中配置 TLS 证书路径
+```
+
+---
+
+### 🔴 5. 设置 Session Cookie Secure 标志
+
+**当前:** `SESSION_SECURE_COOKIE` 未设置(null)
+**风险:** 在 HTTPS 下,未标记 Secure 的 Cookie 仍可能被非 HTTPS 连接泄露。
+
+**方案:**
+```
+# .env 生产环境
+SESSION_SECURE_COOKIE=true
+SESSION_SAME_SITE=strict
+```
+
+---
+
+### 🟡 6. 加强登录安全策略
+
+**当前状况:**
+- 存在验证码(mews/captcha)✅
+- 登录有 `throttle:chat-login` 限流 ✅
+- 自动注册(用户名+密码即可注册)⚡ 双刃剑
+- MD5 老密码兼容 ✅ 会自动升级为 Bcrypt
+
+**优化方案:**
+
+| 项目 | 建议 |
+|------|------|
+| 登录失败锁定 | 同一 IP 5 次失败后临时锁定 15 分钟,后端实现 |
+| 密码强度 | 最低 6 位,建议增加 min:6 验证或至少含数字/字母 |
+| 管理员登录 2FA | id=1 站长登录时增加二次验证(如邮箱验证码) |
+| 验证码频率 | 同一 IP 每天最多注册 3 个账号,防恶意注册 |
+
+---
+
+### 🟡 7. 敏感字段防止 Mass Assignment
+
+**当前:** User 模型的 `$fillable` 中包含 `user_level`、`jjb`、`meili`、`bank_jjb`、`exp_num` 等金钱/权限字段。
+
+**风险:** 如果其他地方调用了 `User::create($request->all())` 或 `User::update($request->all())` 且未使用 FormRequest 过滤,可能导致权限提升或刷币。
+
+**方案:**
+- 将 `user_level`、`jjb`、`meili`、`bank_jjb`、`exp_num` 等敏感字段移出 `$fillable`
+- 仅在特定 Service 中使用 `forceFill()` 并加日志审计
+
+---
+
+### 🟡 8. XSS 输出转义检查
+
+**当前:** 消息内容 `content` 限制 500 字符 ✅,但需确认前端渲染时是否正确转义 HTML。
+
+**需要检查的点:**
+- [ ] 聊天消息在前端如何渲染?(`innerHTML` 还是 `textContent`?)
+- [ ] 用户签名(sign)字段是否转义?
+- [ ] 房间公告是否转义?
+- [ ] 用户头像路径是否校验?(当前有基本校验)
+
+**建议加固:**
+- 前端渲染消息一律使用 `textContent` 或 Vue/React 自动转义
+- 如果必须支持 HTML 表情/颜色,使用白名单 sanitizer(如 DOMPurify)
+
+---
+
+### 🟡 9. 管理员操作审计加强
+
+**当前:** 已有 `PositionAuthorityLog` 和 `AdminLog` 记录 ✅
+**建议:**
+- 所有金币/积分操作必须有完整的前后对比日志
+- 敏感操作(封号、解封、改权限)推送微信通知给站长
+
+---
+
+### 🟡 10. 隐藏管理员入口路径
+
+**当前:** `/lkddi` 作为管理员登录入口(隐藏路径),但路径硬编码在 `routes/web.php` 中。
+**风险:** 任何能阅读源码或通过路径扫描的人都能发现。
+
+**方案(可选):**
+- 改为通过环境变量配置:`ADMIN_LOGIN_PATH=lkddi`
+- 增加 IP 白名单限制:仅站长 IP 可访问 `/admin/*`
+
+---
+
+### 🟢 11. 其他安全改进
+
+| 项目 | 说明 |
+|------|------|
+| CSP Header | 添加 Content-Security-Policy HTTP 头,限制脚本执行来源 |
+| X-Frame-Options | 添加 DENY/SAMEORIGIN 防止点击劫持 |
+| Reverb 消息大小上限 | 当前 10KB,建议根据业务适当降低 |
+| 依赖安全扫描 | 定期运行 `composer audit` 检查 Laravel 及第三方包漏洞 |
+| 文件上传安全 | 自定义头像上传已限制图片类型 ✅,但建议增加文件内容校验 |
+
+---
+
+## 二、访问速度优化(🔥高优 / ⚡中优 / 💡低优)
+
+### 🔥 1. 启用 Laravel OPcache
+
+**当前环境:** 通过 Laravel Herd 运行(PHP-FPM),未启用 OPcache。
+**影响:** 每个 PHP 请求都要重新编译框架文件,浪费大量 CPU。
+
+**方案(Mac/Linux 生产环境):**
+```ini
+; php.ini
+opcache.enable=1
+opcache.memory_consumption=128
+opcache.max_accelerated_files=10000
+opcache.revalidate_freq=0
+opcache.validate_timestamps=0 ; 生产环境关闭文件修改检查
+```
+
+---
+
+### 🔥 2. 数据库查询优化
+
+**当前状况:**
+- 使用 Redis 缓存,但部分页面可能直接查询 MySQL
+- 存在多个游戏(百家乐、赛马、彩票、五子棋等),每次查询都走数据库
+
+**优化方案:**
+
+| 措施 | 说明 | 优先级 |
+|------|------|--------|
+| 排行榜缓存 | `->remember(60)` 缓存排行榜结果 60 秒 | 🔥 |
+| 在线人数缓存 | 当前使用 Redis 实时维护 ✅,保持现状 | - |
+| 房间列表缓存 | `Room::all()` 结果缓存到 Redis | 🔥 |
+| 游戏配置缓存 | `GameConfig::isEnabled()` 结果缓存 10 秒 | ⚡ |
+| 慢查询日志 | 启用 MySQL slow_query_log,定位慢 SQL | ⚡ |
+
+---
+
+### 🔥 3. 静态资源 CDN 加速
+
+**当前:** 所有静态资源(CSS、JS、图片)直接从源服务器加载。
+
+**方案:**
+
+| 资源类型 | 方案 |
+|----------|------|
+| Vite 构建产物(CSS/JS) | 上传到 CDN(阿里云 OSS+CDN / CloudFlare R2) |
+| 头像图片 | 启用单独域名或 CDN,添加长期缓存头 |
+| 聊天背景图 | 使用 CDN 分发 12 张背景图 |
+| Reverb WS 连接 | 通过 CDN/反向代理(如 Nginx)代理 WSS 连接 |
+
+**当前已做:** `.htaccess` 中已对 Vite 构建产物设置 31536000 秒缓存 ✅
+**改进:** 将 `public/build/` 下的资源部署到 CDN。
+
+---
+
+### 🔥 4. Reverb WebSocket 优化
+
+**当前:** Reverb 单节点运行,HTTP 协议,端口 8080。
+
+**优化方案:**
+
+| 措施 | 说明 |
+|------|------|
+| 升级为 WSS | 启用 TLS,避免被运营商劫持/限速 |
+| 水平扩展 | 启用 Reverb Scaling(通过 Redis 发布订阅,见 `reverb.php` 配置) |
+| Nginx 反向代理 | 用 Nginx 代理 WSS,可同时处理 HTTP 静态资源 |
+| 心跳优化 | 当前 `ping_interval=60s`,可考虑适当延长 |
+
+---
+
+### ⚡ 5. Laravel 应用层优化
+
+| 措施 | 说明 | 优先级 |
+|------|------|--------|
+| 路由缓存 | `php artisan route:cache` — 减少路由注册开销 | ⚡ |
+| 配置缓存 | `php artisan config:cache` — 减少 config 加载 | ⚡ |
+| 事件缓存 | `php artisan event:cache` — L12 原生支持 | ⚡ |
+| 视图缓存 | `php artisan view:cache` — Blade 编译缓存 | ⚡ |
+| 模型预加载 | 检查 N+1 查询,使用 `->with()` | ⚡ |
+
+**⚠️ 注意:** `php artisan optimize` 已在 Laravel 12 中被移除,应单独执行以上四个命令。
+
+---
+
+### ⚡ 6. Redis 优化
+
+**当前:** 单机单实例 Redis,承载 Session、Cache、Queue、Reverb Scaling 全部功能。
+
+**建议:**
+- 生产环境建议至少 2 个 Redis 实例:一个用于 Session/Cache(可随时清),一个用于 Queue(需持久化)
+- Reverb Scaling 发布订阅建议单独连接
+- 为 Redis 设置 `maxmemory` 和 `maxmemory-policy allkeys-lru` 防止内存溢出
+
+---
+
+### ⚡ 7. 前端加载优化
+
+| 措施 | 说明 |
+|------|------|
+| JS 代码分割 | Vite 动态 import 拆分大 JS 文件 |
+| 懒加载 | 游戏模块(百家乐、赛马等)按需加载 |
+| 图片懒加载 | 头像、礼物图片使用 `loading="lazy"` |
+| Alpine.js 轻量化 | 当前已使用 Alpine.js ✅ 但避免过多 watcher |
+
+---
+
+### 💡 8. 考虑 Laravel Octane(长期规划)
+
+**说明:** Laravel Octane(Swoole / RoadRunner)将应用常驻内存,消除框架启动开销,可带来 10-30 倍并发性能提升。
+
+**条件:** 需要确保代码无静态变量状态污染,适合用户量增长后的升级。
+
+---
+
+## 三、实施优先级建议
+
+### 第一阶段(紧急 · 1-2 天)🔴🔥
+| # | 任务 | 预估工时 |
+|---|------|---------|
+| 1 | 关闭 `APP_DEBUG` | 5 分钟 |
+| 2 | 启用 `SESSION_ENCRYPT` | 5 分钟 |
+| 3 | 限制 Reverb `allowed_origins` | 10 分钟 |
+| 4 | 配置 Route/Config/Event/View 缓存 | 30 分钟 |
+| 5 | 排行榜、房间列表等 Redis 缓存 | 1 小时 |
+
+### 第二阶段(重要 · 3-5 天)🟡⚡
+| # | 任务 | 预估工时 |
+|---|------|---------|
+| 6 | 敏感字段移出 `$fillable` | 1 小时 |
+| 7 | 登录失败锁定 + 注册频率限制 | 2 小时 |
+| 8 | 数据库慢查询分析与索引优化 | 2 小时 |
+| 9 | 前端 JS 懒加载与代码分割 | 3 小时 |
+| 10 | OPcache 配置 | 30 分钟 |
+
+### 第三阶段(完善 · 1-2 周)🟢💡
+| # | 任务 | 预估工时 |
+|---|------|---------|
+| 11 | Reverb WSS + Nginx 反向代理 | 2 小时 |
+| 12 | 管理员 2FA 验证 | 4 小时 |
+| 13 | CDN 部署静态资源 | 1 天 |
+| 14 | Content-Security-Policy 等安全头 | 1 小时 |
+| 15 | 生产环境 Redis 分实例部署 | 2 小时 |
+| 16 | 评估 Laravel Octane 迁移 | 2-3 天 |
+
+---
+
+## 四、检查清单工具
+
+部署到生产环境前可使用以下命令快速检查:
+
+```bash
+# Laravel 安全检查
+php artisan about # 查看环境配置
+php artisan route:list # 查看所有路由(确认无暴露的管理路径)
+
+# Composer 安全审计
+composer audit
+
+# 缓存优化
+php artisan config:cache
+php artisan route:cache
+php artisan event:cache
+php artisan view:cache
+
+# 依赖更新
+composer update --no-dev -o
+```
+
+---
+
+> **总结:** 该项目整体架构设计良好(Redis + Reverb 实时通信 + Alpine.js 轻量前端),
+> 主要安全短板集中在**生产环境配置**(DEBUG 未关、Session 未加密、WebSocket 无 TLS)和**部分敏感字段保护**。
+> 速度优化则聚焦于**缓存策略**和**CDN 静态资源分发**。
+>
+> 建议从第一阶段紧急问题入手,逐步推进到第二阶段。需要我帮你实施其中任何一部分,随时说!
diff --git a/resources/js/chat-room.js b/resources/js/chat-room.js
index 4d5be5c..7b87627 100644
--- a/resources/js/chat-room.js
+++ b/resources/js/chat-room.js
@@ -57,158 +57,18 @@
* - message-utils.js:提供图片消息过期等消息渲染辅助判断。
*/
-// 统一转发各子模块导出,方便测试或后续模块继续复用同一组工具。
+// 统一转发各子模块导出(仅保留轻量核心模块的静态导出)
export { escapeHtml, escapeHtmlWithLineBreaks, normalizeSafeChatUrl } from "./chat-room/html.js";
-export { bindAppointmentAnnouncementControls, showAppointmentBanner } from "./chat-room/appointment-announcement.js";
-export { bindChatBanner } from "./chat-room/banner.js";
-export { bindChatBotControls, clearChatBotContext, sendToChatBot } from "./chat-room/chat-bot.js";
export { bindGlobalDialogControls } from "./chat-room/dialog.js";
-export { bindDailySignInControls } from "./chat-room/daily-sign-in.js";
-export { bindEarnPanelControls, createEarnPanelData } from "./chat-room/earn-panel.js";
-export { applyFontSize, bindChatFontSizeControl, CHAT_FONT_SIZE_STORAGE_KEY, restoreChatFontSize } from "./chat-room/font-size.js";
-export { bindChatImageUploadControl } from "./chat-room/image-upload.js";
-export { bindChatComposerControls, setChatComposerAction } from "./chat-room/composer.js";
export { bindChatToast } from "./chat-room/toast.js";
-export { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel, quickFriendAction } from "./chat-room/friend-panel.js";
-export { bindFriendNotificationControls, setupBannerNotification, setupFriendNotification, showFriendBanner } from "./chat-room/friend-notifications.js";
-export { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js";
-export { bindLotteryPanelControls, closeLotteryPanel, lotteryPanel, openLotteryPanel, showLotteryMsg } from "./chat-room/lottery-panel.js";
+export { bindChatComposerControls, setChatComposerAction } from "./chat-room/composer.js";
export {
- bindMobileDrawerControls,
- closeMobileDrawer,
- loadMobileRoomList,
- openMobileDrawer,
- renderMobileRoomList,
- renderMobileUserList,
- scheduleRenderMobileUserList,
- switchMobileTab,
-} from "./chat-room/mobile-drawer.js";
-export {
- bindMarriageStatusControls,
- closeMarriageStatusModal,
- fetchMarriedList,
- fetchMyMarriageStatus,
- marriageAction,
- openMarriageStatusModal,
- renderMarriedList,
- renderMarriageStatus,
- switchMarriageTab,
- tryDivorce,
-} from "./chat-room/marriage-status.js";
-export {
- appendSystemMessage,
- bindMarriageModalControls,
- divorceConfirmModal,
- divorceRequestModal,
- marriageAcceptedModal,
- marriageDivorcedModal,
- marriageIncomingModal,
- marriageProposeModal,
- openProposeModal,
- openWeddingSetupModal,
- weddingEnvelopeModal,
- weddingSetupModal,
-} from "./chat-room/marriage-modals.js";
-export { bindToolbarControls, runFeatureShortcut, runToolbarAction } from "./chat-room/toolbar.js";
-export { bindUserCardControls, userCardComponent } from "./chat-room/user-card.js";
-export { bindUserTargetActions, openUserCard, switchTarget } from "./chat-room/user-target-actions.js";
-export { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js";
-export { bindAdminMenuControls } from "./chat-room/admin-menu.js";
-export { baccaratPanel, bindBaccaratPanelControls } from "./chat-room/baccarat-panel.js";
-export { baccaratFab, bindBaccaratFabControls } from "./chat-room/baccarat-fab.js";
-export { bindBaccaratEvents } from "./chat-room/baccarat-events.js";
-export {
- bindBaccaratLossCoverAdminControls,
- closeAdminBaccaratLossCoverModal,
- closeCurrentBaccaratLossCoverEvent,
- loadAdminCurrentLossCoverEvent,
- openAdminBaccaratLossCoverModal,
- submitBaccaratLossCoverEvent,
-} from "./chat-room/baccarat-loss-cover-admin.js";
-export {
- bindBaccaratLossCoverControls,
- claimBaccaratLossCover,
- closeBaccaratLossCoverModal,
- openBaccaratLossCoverModal,
- switchBaccaratLossCoverTab,
-} from "./chat-room/baccarat-loss-cover.js";
-export { bindGameHallControls, closeGameHall, openGameHall } from "./chat-room/game-hall.js";
-export { bindGameBootstrapControls, deferChatGameBootstrap } from "./chat-room/game-bootstrap.js";
-export { bindGamePanelControls } from "./chat-room/game-panels.js";
-export { bindGomokuPanelControls, gomokuPanel } from "./chat-room/gomoku-panel.js";
-export { acceptGomokuInvite, bindGomokuControls, openGomokuPanel } from "./chat-room/gomoku-controls.js";
-export { bindHorseRacePanelControls, horseRacePanel, requestHorseRaceJson } from "./chat-room/horse-race-panel.js";
-export { bindHorseRaceFabControls, horseRaceFab } from "./chat-room/horse-race-fab.js";
-export { bindHorseRaceEvents } from "./chat-room/horse-race-events.js";
-export {
- bindHolidayModalControls,
- buildHolidayClaimActionButton,
- buildHolidaySystemMessage,
- holidayEventModal,
- openHolidayRunFromSystemMessage,
-} from "./chat-room/holiday-modal.js";
-export { bindChatInitialStateControls } from "./chat-room/initial-state.js";
-export {
- bankAction,
- bankLoadInfo,
- bankShowMsg,
- bindBankControls,
- closeBankModal,
- fetchBankRanking,
- openBankModal,
- switchBankTab,
- toggleBankRankSort,
-} from "./chat-room/bank-modal.js";
-export { bindFishingControls, checkAndAutoStartFishing, createBobber, reelFish, removeBobber, resetFishingBtn, startFishing, stopAutoFishing } from "./chat-room/fishing.js";
-export { bindFortunePanelControls, fortunePanel } from "./chat-room/fortune-panel.js";
-export {
- bindProfileControls,
- closeAvatarPicker,
- closeSettingsModal,
- copyWechatBindCode,
- generateWechatBindCode,
- handleAvatarUpload,
- loadHeadfaces,
- openAvatarPicker,
- openSettingsModal,
- saveAvatar,
- savePassword,
- saveSettings,
- selectAvatar,
- sendEmailCode,
- showInlineMsg,
- unbindWechat,
-} from "./chat-room/profile-controls.js";
-export {
- bindShopControls,
- buyItem,
- closeGiftDialog,
- closeRenameModal,
- closeShopModal,
- confirmGift,
- fetchShopData,
- loadShop,
- openGiftDialog,
- openRenameModal,
- openShopModal,
- renderShop,
- showShopToast,
- submitRename,
-} from "./chat-room/shop-controls.js";
-export {
- bindCompactShopPanelControls,
- buyCompactShopItem,
- closeCompactRenameModal,
- fetchCompactShopData,
- loadCompactShop,
- openCompactRenameModal,
- renderCompactShop,
- showCompactShopToast,
- submitCompactRename,
-} from "./chat-room/compact-shop-panel.js";
-export { bindSlotMachineControls, slotFab, slotPanel } from "./chat-room/slot-machine.js";
-export { bindVipControls, buyVip, closeVipModal, openVipModal, saveVipPresenceSettings, switchVipTab } from "./chat-room/vip-controls.js";
-export { showVipPresenceBanner } from "./chat-room/vip-presence.js";
+ isExpiredChatImageMessage,
+ localClearScreen,
+ scrollChatToBottom,
+ syncAutoScrollControls,
+ toggleAutoScroll,
+} from "./chat-room/message-utils.js";
export {
BLOCKABLE_SYSTEM_SENDERS,
BLOCKED_SYSTEM_SENDERS_STORAGE_KEY,
@@ -242,35 +102,10 @@ export {
toggleFeatureMenu,
toggleSoundMute,
} from "./chat-room/preferences-status.js";
-export { bindChatRightPanelControls } from "./chat-room/right-panel.js";
-export {
- bindRoomStatusControls,
- normalizeRoomStatus,
- renderRoomStatusRow,
- renderRoomsOnlineStatus,
- renderRoomsOnlineStatusToContainer,
- resolveRoomUrl,
-} from "./chat-room/rooms.js";
-export { bindRewardModalControls, openRewardModal, rewardModal } from "./chat-room/reward-modal.js";
-export {
- bindRedPacketPanelControls,
- claimRedPacket,
- closeRedPacketModal,
- sendRedPacket,
- showRedPacketModal,
- updateRedPacketClaimsUI,
-} from "./chat-room/red-packet-panel.js";
-export { createMessageQueue } from "./chat-room/message-queue.js";
export {
bindInstantHoverTooltip,
} from "./chat-room/hover-tooltip.js";
-export {
- isExpiredChatImageMessage,
- localClearScreen,
- scrollChatToBottom,
- syncAutoScrollControls,
- toggleAutoScroll,
-} from "./chat-room/message-utils.js";
+export { createMessageQueue } from "./chat-room/message-queue.js";
// 新增:聊天室核心引擎模块导出
export {
@@ -296,156 +131,200 @@ export {
export { bindChatEvents } from "./chat-room/chat-events.js";
export { leaveRoom, notifyExpiredLeave, saveExp, startHeartbeat, stopHeartbeat, HEARTBEAT_INTERVAL, MAX_HEARTBEAT_FAILS } from "./chat-room/heartbeat.js";
+// ─── 懒加载工具 ──────────────────────────────────────
+import { createLazyModule, createLazyAlpineComponent } from "./chat-room/lazy-loader.js";
+
+// ─── 游戏模块(按需懒加载)──────────────────────────
+const _baccaratPanel = createLazyModule(
+ () => import("./chat-room/baccarat-panel.js"),
+ (mod) => mod.bindBaccaratPanelControls()
+);
+const _baccaratFab = createLazyModule(
+ () => import("./chat-room/baccarat-fab.js"),
+ (mod) => mod.bindBaccaratFabControls()
+);
+const _baccaratEvents = createLazyModule(
+ () => import("./chat-room/baccarat-events.js"),
+ (mod) => mod.bindBaccaratEvents()
+);
+const _baccaratLossCoverAdmin = createLazyModule(
+ () => import("./chat-room/baccarat-loss-cover-admin.js"),
+ (mod) => mod.bindBaccaratLossCoverAdminControls()
+);
+const _baccaratLossCover = createLazyModule(
+ () => import("./chat-room/baccarat-loss-cover.js"),
+ (mod) => mod.bindBaccaratLossCoverControls()
+);
+const _gomokuPanel = createLazyModule(
+ () => import("./chat-room/gomoku-panel.js"),
+ (mod) => mod.bindGomokuPanelControls()
+);
+const _gomokuControls = createLazyModule(
+ () => import("./chat-room/gomoku-controls.js"),
+ (mod) => mod.bindGomokuControls()
+);
+const _horseRacePanel = createLazyModule(
+ () => import("./chat-room/horse-race-panel.js"),
+ (mod) => mod.bindHorseRacePanelControls()
+);
+const _horseRaceFab = createLazyModule(
+ () => import("./chat-room/horse-race-fab.js"),
+ (mod) => mod.bindHorseRaceFabControls()
+);
+const _horseRaceEvents = createLazyModule(
+ () => import("./chat-room/horse-race-events.js"),
+ (mod) => mod.bindHorseRaceEvents()
+);
+const _fishing = createLazyModule(
+ () => import("./chat-room/fishing.js"),
+ (mod) => mod.bindFishingControls()
+);
+const _slotMachine = createLazyModule(
+ () => import("./chat-room/slot-machine.js"),
+ (mod) => mod.bindSlotMachineControls()
+);
+const _fortunePanel = createLazyModule(
+ () => import("./chat-room/fortune-panel.js"),
+ (mod) => mod.bindFortunePanelControls()
+);
+const _lotteryPanel = createLazyModule(
+ () => import("./chat-room/lottery-panel.js"),
+ (mod) => mod.bindLotteryPanelControls()
+);
+const _gameHall = createLazyModule(
+ () => import("./chat-room/game-hall.js"),
+ (mod) => mod.bindGameHallControls()
+);
+const _gameBootstrap = createLazyModule(
+ () => import("./chat-room/game-bootstrap.js"),
+ (mod) => mod.bindGameBootstrapControls()
+);
+const _gamePanels = createLazyModule(
+ () => import("./chat-room/game-panels.js"),
+ (mod) => mod.bindGamePanelControls()
+);
+
+// ─── 功能模块(按需懒加载)────────────────────────
+const _shop = createLazyModule(
+ () => import("./chat-room/shop-controls.js"),
+ (mod) => mod.bindShopControls()
+);
+const _compactShop = createLazyModule(
+ () => import("./chat-room/compact-shop-panel.js"),
+ (mod) => mod.bindCompactShopPanelControls()
+);
+const _bank = createLazyModule(
+ () => import("./chat-room/bank-modal.js"),
+ (mod) => mod.bindBankControls()
+);
+const _marriageModals = createLazyModule(
+ () => import("./chat-room/marriage-modals.js"),
+ (mod) => mod.bindMarriageModalControls()
+);
+const _marriageStatus = createLazyModule(
+ () => import("./chat-room/marriage-status.js"),
+ (mod) => mod.bindMarriageStatusControls()
+);
+const _profile = createLazyModule(
+ () => import("./chat-room/profile-controls.js"),
+ (mod) => mod.bindProfileControls()
+);
+const _userCard = createLazyModule(
+ () => import("./chat-room/user-card.js"),
+ (mod) => mod.bindUserCardControls()
+);
+const _userTargetActions = createLazyModule(
+ () => import("./chat-room/user-target-actions.js"),
+ (mod) => mod.bindUserTargetActions()
+);
+const _vip = createLazyModule(
+ () => import("./chat-room/vip-controls.js"),
+ (mod) => mod.bindVipControls()
+);
+const _redPacket = createLazyModule(
+ () => import("./chat-room/red-packet-panel.js"),
+ (mod) => mod.bindRedPacketPanelControls()
+);
+const _holiday = createLazyModule(
+ () => import("./chat-room/holiday-modal.js"),
+ (mod) => mod.bindHolidayModalControls()
+);
+const _reward = createLazyModule(
+ () => import("./chat-room/reward-modal.js"),
+ (mod) => mod.bindRewardModalControls()
+);
+const _earn = createLazyModule(
+ () => import("./chat-room/earn-panel.js"),
+ (mod) => mod.bindEarnPanelControls()
+);
+const _dailySignIn = createLazyModule(
+ () => import("./chat-room/daily-sign-in.js"),
+ (mod) => mod.bindDailySignInControls()
+);
+const _mobileDrawer = createLazyModule(
+ () => import("./chat-room/mobile-drawer.js"),
+ (mod) => mod.bindMobileDrawerControls()
+);
+const _welcomeMenu = createLazyModule(
+ () => import("./chat-room/welcome-menu.js"),
+ (mod) => mod.bindWelcomeMenuControls()
+);
+const _adminMenu = createLazyModule(
+ () => import("./chat-room/admin-menu.js"),
+ (mod) => mod.bindAdminMenuControls()
+);
+const _friendPanel = createLazyModule(
+ () => import("./chat-room/friend-panel.js"),
+ (mod) => mod.bindFriendPanelControls()
+);
+const _friendNotifications = createLazyModule(
+ () => import("./chat-room/friend-notifications.js"),
+ (mod) => mod.bindFriendNotificationControls()
+);
+const _lightbox = createLazyModule(
+ () => import("./chat-room/lightbox.js")
+);
+const _rooms = createLazyModule(
+ () => import("./chat-room/rooms.js"),
+ (mod) => mod.bindRoomStatusControls()
+);
+const _rightPanel = createLazyModule(
+ () => import("./chat-room/right-panel.js"),
+ (mod) => mod.bindChatRightPanelControls()
+);
+const _imageUpload = createLazyModule(
+ () => import("./chat-room/image-upload.js"),
+ (mod) => mod.bindChatImageUploadControl()
+);
+const _fontSize = createLazyModule(
+ () => import("./chat-room/font-size.js"),
+ (mod) => mod.bindChatFontSizeControl()
+);
+const _appointment = createLazyModule(
+ () => import("./chat-room/appointment-announcement.js"),
+ (mod) => mod.bindAppointmentAnnouncementControls()
+);
+const _banner = createLazyModule(
+ () => import("./chat-room/banner.js"),
+ (mod) => mod.bindChatBanner()
+);
+const _chatBot = createLazyModule(
+ () => import("./chat-room/chat-bot.js"),
+ (mod) => mod.bindChatBotControls()
+);
+
+// ─── 轻量核心模块(保持静态导入)────────────────────
import { escapeHtml, escapeHtmlWithLineBreaks, normalizeSafeChatUrl } from "./chat-room/html.js";
-import { bindAppointmentAnnouncementControls, showAppointmentBanner } from "./chat-room/appointment-announcement.js";
-import { bindChatBanner } from "./chat-room/banner.js";
-import { bindChatBotControls, clearChatBotContext, sendToChatBot } from "./chat-room/chat-bot.js";
import { bindGlobalDialogControls } from "./chat-room/dialog.js";
-import { bindDailySignInControls } from "./chat-room/daily-sign-in.js";
-import { bindEarnPanelControls, createEarnPanelData } from "./chat-room/earn-panel.js";
-import { applyFontSize, bindChatFontSizeControl, CHAT_FONT_SIZE_STORAGE_KEY, restoreChatFontSize } from "./chat-room/font-size.js";
-import { bindChatImageUploadControl } from "./chat-room/image-upload.js";
-import { bindChatComposerControls, setChatComposerAction } from "./chat-room/composer.js";
import { bindChatToast } from "./chat-room/toast.js";
-import { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel, quickFriendAction } from "./chat-room/friend-panel.js";
-import { bindFriendNotificationControls, setupBannerNotification, setupFriendNotification, showFriendBanner } from "./chat-room/friend-notifications.js";
-import { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js";
-import { bindLotteryPanelControls, closeLotteryPanel, lotteryPanel, openLotteryPanel, showLotteryMsg } from "./chat-room/lottery-panel.js";
+import { bindChatComposerControls, setChatComposerAction } from "./chat-room/composer.js";
import {
- bindMobileDrawerControls,
- closeMobileDrawer,
- loadMobileRoomList,
- openMobileDrawer,
- renderMobileRoomList,
- renderMobileUserList,
- scheduleRenderMobileUserList,
- switchMobileTab,
-} from "./chat-room/mobile-drawer.js";
-import {
- bindMarriageStatusControls,
- closeMarriageStatusModal,
- fetchMarriedList,
- fetchMyMarriageStatus,
- marriageAction,
- openMarriageStatusModal,
- renderMarriedList,
- renderMarriageStatus,
- switchMarriageTab,
- tryDivorce,
-} from "./chat-room/marriage-status.js";
-import {
- appendSystemMessage,
- bindMarriageModalControls,
- divorceConfirmModal,
- divorceRequestModal,
- marriageAcceptedModal,
- marriageDivorcedModal,
- marriageIncomingModal,
- marriageProposeModal,
- openProposeModal,
- openWeddingSetupModal,
- weddingEnvelopeModal,
- weddingSetupModal,
-} from "./chat-room/marriage-modals.js";
-import { bindToolbarControls, runFeatureShortcut, runToolbarAction } from "./chat-room/toolbar.js";
-import { bindUserCardControls, userCardComponent } from "./chat-room/user-card.js";
-import { bindUserTargetActions, openUserCard, switchTarget } from "./chat-room/user-target-actions.js";
-import { bindWelcomeMenuControls } from "./chat-room/welcome-menu.js";
-import { bindAdminMenuControls } from "./chat-room/admin-menu.js";
-import { baccaratPanel, bindBaccaratPanelControls } from "./chat-room/baccarat-panel.js";
-import { baccaratFab, bindBaccaratFabControls } from "./chat-room/baccarat-fab.js";
-import { bindBaccaratEvents } from "./chat-room/baccarat-events.js";
-import {
- bindBaccaratLossCoverAdminControls,
- closeAdminBaccaratLossCoverModal,
- closeCurrentBaccaratLossCoverEvent,
- loadAdminCurrentLossCoverEvent,
- openAdminBaccaratLossCoverModal,
- submitBaccaratLossCoverEvent,
-} from "./chat-room/baccarat-loss-cover-admin.js";
-import {
- bindBaccaratLossCoverControls,
- claimBaccaratLossCover,
- closeBaccaratLossCoverModal,
- openBaccaratLossCoverModal,
- switchBaccaratLossCoverTab,
-} from "./chat-room/baccarat-loss-cover.js";
-import { bindGameHallControls, closeGameHall, openGameHall } from "./chat-room/game-hall.js";
-import { bindGameBootstrapControls, deferChatGameBootstrap } from "./chat-room/game-bootstrap.js";
-import { bindGamePanelControls } from "./chat-room/game-panels.js";
-import { bindGomokuPanelControls, gomokuPanel } from "./chat-room/gomoku-panel.js";
-import { acceptGomokuInvite, bindGomokuControls, openGomokuPanel } from "./chat-room/gomoku-controls.js";
-import { bindHorseRacePanelControls, horseRacePanel, requestHorseRaceJson } from "./chat-room/horse-race-panel.js";
-import { bindHorseRaceFabControls, horseRaceFab } from "./chat-room/horse-race-fab.js";
-import { bindHorseRaceEvents } from "./chat-room/horse-race-events.js";
-import {
- bindHolidayModalControls,
- buildHolidayClaimActionButton,
- buildHolidaySystemMessage,
- holidayEventModal,
- openHolidayRunFromSystemMessage,
-} from "./chat-room/holiday-modal.js";
-import { bindChatInitialStateControls } from "./chat-room/initial-state.js";
-import {
- bankAction,
- bankLoadInfo,
- bankShowMsg,
- bindBankControls,
- closeBankModal,
- fetchBankRanking,
- openBankModal,
- switchBankTab,
- toggleBankRankSort,
-} from "./chat-room/bank-modal.js";
-import { bindFishingControls, checkAndAutoStartFishing, createBobber, reelFish, removeBobber, resetFishingBtn, startFishing, stopAutoFishing } from "./chat-room/fishing.js";
-import { bindFortunePanelControls, fortunePanel } from "./chat-room/fortune-panel.js";
-import {
- bindProfileControls,
- closeAvatarPicker,
- closeSettingsModal,
- copyWechatBindCode,
- generateWechatBindCode,
- handleAvatarUpload,
- loadHeadfaces,
- openAvatarPicker,
- openSettingsModal,
- saveAvatar,
- savePassword,
- saveSettings,
- selectAvatar,
- sendEmailCode,
- showInlineMsg,
- unbindWechat,
-} from "./chat-room/profile-controls.js";
-import {
- bindShopControls,
- buyItem,
- closeGiftDialog,
- closeRenameModal,
- closeShopModal,
- confirmGift,
- fetchShopData,
- loadShop,
- openGiftDialog,
- openRenameModal,
- openShopModal,
- renderShop,
- showShopToast,
- submitRename,
-} from "./chat-room/shop-controls.js";
-import {
- bindCompactShopPanelControls,
- buyCompactShopItem,
- closeCompactRenameModal,
- fetchCompactShopData,
- loadCompactShop,
- openCompactRenameModal,
- renderCompactShop,
- showCompactShopToast,
- submitCompactRename,
-} from "./chat-room/compact-shop-panel.js";
-import { bindSlotMachineControls, slotFab, slotPanel } from "./chat-room/slot-machine.js";
-import { bindVipControls, buyVip, closeVipModal, openVipModal, saveVipPresenceSettings, switchVipTab } from "./chat-room/vip-controls.js";
+ isExpiredChatImageMessage,
+ localClearScreen,
+ scrollChatToBottom,
+ syncAutoScrollControls,
+ toggleAutoScroll,
+} from "./chat-room/message-utils.js";
import {
BLOCKABLE_SYSTEM_SENDERS,
BLOCKED_SYSTEM_SENDERS_STORAGE_KEY,
@@ -479,35 +358,10 @@ import {
toggleFeatureMenu,
toggleSoundMute,
} from "./chat-room/preferences-status.js";
-import { bindChatRightPanelControls } from "./chat-room/right-panel.js";
-import {
- bindRoomStatusControls,
- normalizeRoomStatus,
- renderRoomStatusRow,
- renderRoomsOnlineStatus,
- renderRoomsOnlineStatusToContainer,
- resolveRoomUrl,
-} from "./chat-room/rooms.js";
-import { bindRewardModalControls, openRewardModal, rewardModal } from "./chat-room/reward-modal.js";
-import {
- bindRedPacketPanelControls,
- claimRedPacket,
- closeRedPacketModal,
- sendRedPacket,
- showRedPacketModal,
- updateRedPacketClaimsUI,
-} from "./chat-room/red-packet-panel.js";
-import { createMessageQueue } from "./chat-room/message-queue.js";
import {
bindInstantHoverTooltip,
} from "./chat-room/hover-tooltip.js";
-import {
- isExpiredChatImageMessage,
- localClearScreen,
- scrollChatToBottom,
- syncAutoScrollControls,
- toggleAutoScroll,
-} from "./chat-room/message-utils.js";
+import { createMessageQueue } from "./chat-room/message-queue.js";
// 新增:聊天室核心引擎模块(共享状态、消息渲染、用户名单、事件监听、心跳)
import "./chat-room/chat-state.js";
@@ -516,203 +370,39 @@ import { buildUserBadgeHtml, filterUserList, refreshRenderedUserBadges, renderUs
import { bindChatEvents } from "./chat-room/chat-events.js";
import { leaveRoom, notifyExpiredLeave, saveExp, startHeartbeat, stopHeartbeat } from "./chat-room/heartbeat.js";
+// ─── 工具 & 初始化模块(静态保留)────────────────────
+import { bindToolbarControls, runFeatureShortcut, runToolbarAction } from "./chat-room/toolbar.js";
+import { bindChatInitialStateControls } from "./chat-room/initial-state.js";
+
if (typeof window !== "undefined") {
bindInstantHoverTooltip();
- // 保留聚合入口,给新迁移模块、测试和仍在 Blade 内的存量脚本统一读取工具。
+ // 保留聚合入口,懒加载模块通过按需动态导入自动初始化。
window.ChatRoomTools = {
+ // ── 静态核心模块(直接引用) ────────────────
escapeHtml,
escapeHtmlWithLineBreaks,
normalizeSafeChatUrl,
- bindAppointmentAnnouncementControls,
- showAppointmentBanner,
- bindChatBanner,
- bindChatBotControls,
- clearChatBotContext,
- sendToChatBot,
bindGlobalDialogControls,
- bindDailySignInControls,
- bindEarnPanelControls,
- createEarnPanelData,
- bindLotteryPanelControls,
- closeLotteryPanel,
- lotteryPanel,
- openLotteryPanel,
- showLotteryMsg,
- applyFontSize,
- bindChatFontSizeControl,
- bindChatImageUploadControl,
+ bindChatToast,
bindChatComposerControls,
setChatComposerAction,
- bindChatToast,
- bindFriendPanelControls,
- bindFriendNotificationControls,
- closeFriendPanel,
- friendSearch,
- loadFriends,
- openFriendPanel,
- quickFriendAction,
- setupBannerNotification,
- setupFriendNotification,
- showFriendBanner,
- bindMobileDrawerControls,
- closeMobileDrawer,
- loadMobileRoomList,
- openMobileDrawer,
- renderMobileRoomList,
- renderMobileUserList,
- scheduleRenderMobileUserList,
- switchMobileTab,
- bindToolbarControls,
- runFeatureShortcut,
- runToolbarAction,
- bindUserCardControls,
- userCardComponent,
- bindUserTargetActions,
- openUserCard,
- switchTarget,
- bindWelcomeMenuControls,
- bindAdminMenuControls,
- baccaratPanel,
- bindBaccaratPanelControls,
- baccaratFab,
- bindBaccaratFabControls,
- bindBaccaratEvents,
- bindBaccaratLossCoverAdminControls,
- closeAdminBaccaratLossCoverModal,
- closeCurrentBaccaratLossCoverEvent,
- bindBaccaratLossCoverControls,
- claimBaccaratLossCover,
- closeBaccaratLossCoverModal,
- openBaccaratLossCoverModal,
- switchBaccaratLossCoverTab,
- bindGameHallControls,
- closeGameHall,
- openGameHall,
- bindGameBootstrapControls,
- deferChatGameBootstrap,
- bindGamePanelControls,
- bindGomokuPanelControls,
- gomokuPanel,
- acceptGomokuInvite,
- bindGomokuControls,
- openGomokuPanel,
- bindHorseRacePanelControls,
- horseRacePanel,
- requestHorseRaceJson,
- bindHorseRaceFabControls,
- horseRaceFab,
- bindHorseRaceEvents,
- bindHolidayModalControls,
- buildHolidayClaimActionButton,
- buildHolidaySystemMessage,
- holidayEventModal,
- openHolidayRunFromSystemMessage,
- bindChatInitialStateControls,
- loadAdminCurrentLossCoverEvent,
- openAdminBaccaratLossCoverModal,
- submitBaccaratLossCoverEvent,
- bankAction,
- bankLoadInfo,
- bankShowMsg,
- bindBankControls,
- closeBankModal,
- fetchBankRanking,
- openBankModal,
- switchBankTab,
- toggleBankRankSort,
- bindFishingControls,
- checkAndAutoStartFishing,
- createBobber,
- reelFish,
- removeBobber,
- resetFishingBtn,
- startFishing,
- stopAutoFishing,
- bindFortunePanelControls,
- fortunePanel,
- bindProfileControls,
- closeAvatarPicker,
- closeSettingsModal,
- copyWechatBindCode,
- generateWechatBindCode,
- handleAvatarUpload,
- loadHeadfaces,
- openAvatarPicker,
- openSettingsModal,
- saveAvatar,
- savePassword,
- saveSettings,
- selectAvatar,
- sendEmailCode,
- showInlineMsg,
- unbindWechat,
- bindMarriageStatusControls,
- appendSystemMessage,
- bindMarriageModalControls,
- divorceConfirmModal,
- divorceRequestModal,
- marriageAcceptedModal,
- marriageDivorcedModal,
- marriageIncomingModal,
- marriageProposeModal,
- closeMarriageStatusModal,
- fetchMarriedList,
- fetchMyMarriageStatus,
- marriageAction,
- openMarriageStatusModal,
- openProposeModal,
- openWeddingSetupModal,
- weddingEnvelopeModal,
- weddingSetupModal,
- renderMarriedList,
- renderMarriageStatus,
- switchMarriageTab,
- tryDivorce,
- bindShopControls,
- buyItem,
- closeGiftDialog,
- closeRenameModal,
- closeShopModal,
- confirmGift,
- fetchShopData,
- loadShop,
- openGiftDialog,
- openRenameModal,
- openShopModal,
- renderShop,
- showShopToast,
- submitRename,
- bindCompactShopPanelControls,
- buyCompactShopItem,
- closeCompactRenameModal,
- fetchCompactShopData,
- loadCompactShop,
- openCompactRenameModal,
- renderCompactShop,
- showCompactShopToast,
- submitCompactRename,
- bindSlotMachineControls,
- slotFab,
- slotPanel,
- bindVipControls,
- buyVip,
- closeVipModal,
- openVipModal,
- saveVipPresenceSettings,
- switchVipTab,
- CHAT_FONT_SIZE_STORAGE_KEY,
- restoreChatFontSize,
- closeChatImageLightbox,
- initChatImageLightboxEvents,
- openChatImageLightbox,
+ isExpiredChatImageMessage,
+ localClearScreen,
+ scrollChatToBottom,
+ syncAutoScrollControls,
+ toggleAutoScroll,
+ bindInstantHoverTooltip,
+ createMessageQueue,
BLOCKABLE_SYSTEM_SENDERS,
BLOCKED_SYSTEM_SENDERS_STORAGE_KEY,
CHAT_SOUND_MUTED_STORAGE_KEY,
bindBlockMenuControls,
bindSoundMuteControl,
+ buildChatPreferencesPayload,
closeDailyStatusEditor,
closeFeatureMenu,
+ getCurrentUserDailyStatus,
handleFeatureLocalClear,
isSoundMuted,
loadBlockedSystemSenders,
@@ -721,45 +411,24 @@ if (typeof window !== "undefined") {
openDailyStatusEditor,
parseDailyStatusExpiry,
persistBlockedSystemSenders,
- setSoundMuted,
- shouldMigrateLocalChatPreferences,
- toggleBlockMenu,
- toggleFeatureMenu,
- toggleSoundMute,
- buildChatPreferencesPayload,
- getCurrentUserDailyStatus,
persistChatPreferencesToLocal,
removeDailyStatusFields,
resolveBlockedSystemSenderKey,
saveChatPreferences,
setOnlineUserDailyStatus,
setRenderedMessagesVisibilityBySender,
+ setSoundMuted,
+ shouldMigrateLocalChatPreferences,
syncBlockedSystemSenderCheckboxes,
syncDailyStatusUi,
+ toggleBlockMenu,
toggleBlockedSystemSender,
- bindChatRightPanelControls,
- bindRoomStatusControls,
- normalizeRoomStatus,
- renderRoomStatusRow,
- renderRoomsOnlineStatus,
- renderRoomsOnlineStatusToContainer,
- resolveRoomUrl,
- bindRewardModalControls,
- openRewardModal,
- rewardModal,
- bindRedPacketPanelControls,
- claimRedPacket,
- closeRedPacketModal,
- sendRedPacket,
- showRedPacketModal,
- updateRedPacketClaimsUI,
- createMessageQueue,
- bindInstantHoverTooltip,
- isExpiredChatImageMessage,
- localClearScreen,
- scrollChatToBottom,
- syncAutoScrollControls,
- toggleAutoScroll,
+ toggleFeatureMenu,
+ toggleSoundMute,
+ bindToolbarControls,
+ runFeatureShortcut,
+ runToolbarAction,
+ bindChatInitialStateControls,
// 聊天室核心引擎
bindChatEvents,
appendMessage,
@@ -780,144 +449,451 @@ if (typeof window !== "undefined") {
notifyExpiredLeave,
startHeartbeat,
stopHeartbeat,
+
+ // ── 功能模块(懒加载包装)────────────────
+ bindAppointmentAnnouncementControls: (...args) => _appointment.wrap('bindAppointmentAnnouncementControls')(...args),
+ showAppointmentBanner: (...args) => _appointment.wrap('showAppointmentBanner')(...args),
+ bindChatBanner: (...args) => _banner.wrap('bindChatBanner')(...args),
+ bindChatBotControls: (...args) => _chatBot.wrap('bindChatBotControls')(...args),
+ clearChatBotContext: (...args) => _chatBot.wrap('clearChatBotContext')(...args),
+ sendToChatBot: (...args) => _chatBot.wrap('sendToChatBot')(...args),
+ bindDailySignInControls: (...args) => _dailySignIn.wrap('bindDailySignInControls')(...args),
+ bindEarnPanelControls: (...args) => _earn.wrap('bindEarnPanelControls')(...args),
+ createEarnPanelData: (...args) => _earn.wrap('createEarnPanelData')(...args),
+ applyFontSize: (...args) => _fontSize.wrap('applyFontSize')(...args),
+ bindChatFontSizeControl: (...args) => _fontSize.wrap('bindChatFontSizeControl')(...args),
+ CHAT_FONT_SIZE_STORAGE_KEY: undefined, // 静态常量,通过模块直接导入或 window 上取
+ restoreChatFontSize: (...args) => _fontSize.wrap('restoreChatFontSize')(...args),
+ bindChatImageUploadControl: (...args) => _imageUpload.wrap('bindChatImageUploadControl')(...args),
+ bindLotteryPanelControls: (...args) => _lotteryPanel.wrap('bindLotteryPanelControls')(...args),
+ closeLotteryPanel: (...args) => _lotteryPanel.wrap('closeLotteryPanel')(...args),
+ openLotteryPanel: (...args) => _lotteryPanel.wrap('openLotteryPanel')(...args),
+ showLotteryMsg: (...args) => _lotteryPanel.wrap('showLotteryMsg')(...args),
+ bindFriendPanelControls: (...args) => _friendPanel.wrap('bindFriendPanelControls')(...args),
+ bindFriendNotificationControls: (...args) => _friendNotifications.wrap('bindFriendNotificationControls')(...args),
+ closeFriendPanel: (...args) => _friendPanel.wrap('closeFriendPanel')(...args),
+ friendSearch: (...args) => _friendPanel.wrap('friendSearch')(...args),
+ loadFriends: (...args) => _friendPanel.wrap('loadFriends')(...args),
+ openFriendPanel: (...args) => _friendPanel.wrap('openFriendPanel')(...args),
+ quickFriendAction: (...args) => _friendPanel.wrap('quickFriendAction')(...args),
+ setupBannerNotification: (...args) => _friendNotifications.wrap('setupBannerNotification')(...args),
+ setupFriendNotification: (...args) => _friendNotifications.wrap('setupFriendNotification')(...args),
+ showFriendBanner: (...args) => _friendNotifications.wrap('showFriendBanner')(...args),
+ bindMobileDrawerControls: (...args) => _mobileDrawer.wrap('bindMobileDrawerControls')(...args),
+ closeMobileDrawer: (...args) => _mobileDrawer.wrap('closeMobileDrawer')(...args),
+ loadMobileRoomList: (...args) => _mobileDrawer.wrap('loadMobileRoomList')(...args),
+ openMobileDrawer: (...args) => _mobileDrawer.wrap('openMobileDrawer')(...args),
+ renderMobileRoomList: (...args) => _mobileDrawer.wrap('renderMobileRoomList')(...args),
+ renderMobileUserList: (...args) => _mobileDrawer.wrap('renderMobileUserList')(...args),
+ scheduleRenderMobileUserList: (...args) => _mobileDrawer.wrap('scheduleRenderMobileUserList')(...args),
+ switchMobileTab: (...args) => _mobileDrawer.wrap('switchMobileTab')(...args),
+ bindUserCardControls: (...args) => _userCard.wrap('bindUserCardControls')(...args),
+ bindUserTargetActions: (...args) => _userTargetActions.wrap('bindUserTargetActions')(...args),
+ openUserCard: (...args) => _userTargetActions.wrap('openUserCard')(...args),
+ switchTarget: (...args) => _userTargetActions.wrap('switchTarget')(...args),
+ bindWelcomeMenuControls: (...args) => _welcomeMenu.wrap('bindWelcomeMenuControls')(...args),
+ bindAdminMenuControls: (...args) => _adminMenu.wrap('bindAdminMenuControls')(...args),
+ bindBaccaratPanelControls: (...args) => _baccaratPanel.wrap('bindBaccaratPanelControls')(...args),
+ bindBaccaratFabControls: (...args) => _baccaratFab.wrap('bindBaccaratFabControls')(...args),
+ bindBaccaratEvents: (...args) => _baccaratEvents.wrap('bindBaccaratEvents')(...args),
+ bindBaccaratLossCoverAdminControls: (...args) => _baccaratLossCoverAdmin.wrap('bindBaccaratLossCoverAdminControls')(...args),
+ closeAdminBaccaratLossCoverModal: (...args) => _baccaratLossCoverAdmin.wrap('closeAdminBaccaratLossCoverModal')(...args),
+ closeCurrentBaccaratLossCoverEvent: (...args) => _baccaratLossCoverAdmin.wrap('closeCurrentBaccaratLossCoverEvent')(...args),
+ loadAdminCurrentLossCoverEvent: (...args) => _baccaratLossCoverAdmin.wrap('loadAdminCurrentLossCoverEvent')(...args),
+ openAdminBaccaratLossCoverModal: (...args) => _baccaratLossCoverAdmin.wrap('openAdminBaccaratLossCoverModal')(...args),
+ submitBaccaratLossCoverEvent: (...args) => _baccaratLossCoverAdmin.wrap('submitBaccaratLossCoverEvent')(...args),
+ bindBaccaratLossCoverControls: (...args) => _baccaratLossCover.wrap('bindBaccaratLossCoverControls')(...args),
+ claimBaccaratLossCover: (...args) => _baccaratLossCover.wrap('claimBaccaratLossCover')(...args),
+ closeBaccaratLossCoverModal: (...args) => _baccaratLossCover.wrap('closeBaccaratLossCoverModal')(...args),
+ openBaccaratLossCoverModal: (...args) => _baccaratLossCover.wrap('openBaccaratLossCoverModal')(...args),
+ switchBaccaratLossCoverTab: (...args) => _baccaratLossCover.wrap('switchBaccaratLossCoverTab')(...args),
+ bindGameHallControls: (...args) => _gameHall.wrap('bindGameHallControls')(...args),
+ closeGameHall: (...args) => _gameHall.wrap('closeGameHall')(...args),
+ openGameHall: (...args) => _gameHall.wrap('openGameHall')(...args),
+ bindGameBootstrapControls: (...args) => _gameBootstrap.wrap('bindGameBootstrapControls')(...args),
+ deferChatGameBootstrap: (...args) => _gameBootstrap.wrap('deferChatGameBootstrap')(...args),
+ bindGamePanelControls: (...args) => _gamePanels.wrap('bindGamePanelControls')(...args),
+ bindGomokuPanelControls: (...args) => _gomokuPanel.wrap('bindGomokuPanelControls')(...args),
+ acceptGomokuInvite: (...args) => _gomokuControls.wrap('acceptGomokuInvite')(...args),
+ bindGomokuControls: (...args) => _gomokuControls.wrap('bindGomokuControls')(...args),
+ openGomokuPanel: (...args) => _gomokuControls.wrap('openGomokuPanel')(...args),
+ bindHorseRacePanelControls: (...args) => _horseRacePanel.wrap('bindHorseRacePanelControls')(...args),
+ requestHorseRaceJson: (...args) => _horseRacePanel.wrap('requestHorseRaceJson')(...args),
+ bindHorseRaceFabControls: (...args) => _horseRaceFab.wrap('bindHorseRaceFabControls')(...args),
+ bindHorseRaceEvents: (...args) => _horseRaceEvents.wrap('bindHorseRaceEvents')(...args),
+ bindHolidayModalControls: (...args) => _holiday.wrap('bindHolidayModalControls')(...args),
+ buildHolidayClaimActionButton: (...args) => _holiday.wrap('buildHolidayClaimActionButton')(...args),
+ buildHolidaySystemMessage: (...args) => _holiday.wrap('buildHolidaySystemMessage')(...args),
+ openHolidayRunFromSystemMessage: (...args) => _holiday.wrap('openHolidayRunFromSystemMessage')(...args),
+ bankAction: (...args) => _bank.wrap('bankAction')(...args),
+ bankLoadInfo: (...args) => _bank.wrap('bankLoadInfo')(...args),
+ bankShowMsg: (...args) => _bank.wrap('bankShowMsg')(...args),
+ bindBankControls: (...args) => _bank.wrap('bindBankControls')(...args),
+ closeBankModal: (...args) => _bank.wrap('closeBankModal')(...args),
+ fetchBankRanking: (...args) => _bank.wrap('fetchBankRanking')(...args),
+ openBankModal: (...args) => _bank.wrap('openBankModal')(...args),
+ switchBankTab: (...args) => _bank.wrap('switchBankTab')(...args),
+ toggleBankRankSort: (...args) => _bank.wrap('toggleBankRankSort')(...args),
+ bindFishingControls: (...args) => _fishing.wrap('bindFishingControls')(...args),
+ checkAndAutoStartFishing: (...args) => _fishing.wrap('checkAndAutoStartFishing')(...args),
+ createBobber: (...args) => _fishing.wrap('createBobber')(...args),
+ reelFish: (...args) => _fishing.wrap('reelFish')(...args),
+ removeBobber: (...args) => _fishing.wrap('removeBobber')(...args),
+ resetFishingBtn: (...args) => _fishing.wrap('resetFishingBtn')(...args),
+ startFishing: (...args) => _fishing.wrap('startFishing')(...args),
+ stopAutoFishing: (...args) => _fishing.wrap('stopAutoFishing')(...args),
+ bindFortunePanelControls: (...args) => _fortunePanel.wrap('bindFortunePanelControls')(...args),
+ bindProfileControls: (...args) => _profile.wrap('bindProfileControls')(...args),
+ closeAvatarPicker: (...args) => _profile.wrap('closeAvatarPicker')(...args),
+ closeSettingsModal: (...args) => _profile.wrap('closeSettingsModal')(...args),
+ copyWechatBindCode: (...args) => _profile.wrap('copyWechatBindCode')(...args),
+ generateWechatBindCode: (...args) => _profile.wrap('generateWechatBindCode')(...args),
+ handleAvatarUpload: (...args) => _profile.wrap('handleAvatarUpload')(...args),
+ loadHeadfaces: (...args) => _profile.wrap('loadHeadfaces')(...args),
+ openAvatarPicker: (...args) => _profile.wrap('openAvatarPicker')(...args),
+ openSettingsModal: (...args) => _profile.wrap('openSettingsModal')(...args),
+ saveAvatar: (...args) => _profile.wrap('saveAvatar')(...args),
+ savePassword: (...args) => _profile.wrap('savePassword')(...args),
+ saveSettings: (...args) => _profile.wrap('saveSettings')(...args),
+ selectAvatar: (...args) => _profile.wrap('selectAvatar')(...args),
+ sendEmailCode: (...args) => _profile.wrap('sendEmailCode')(...args),
+ showInlineMsg: (...args) => _profile.wrap('showInlineMsg')(...args),
+ unbindWechat: (...args) => _profile.wrap('unbindWechat')(...args),
+ bindMarriageStatusControls: (...args) => _marriageStatus.wrap('bindMarriageStatusControls')(...args),
+ appendSystemMessage: (...args) => _marriageModals.wrap('appendSystemMessage')(...args),
+ bindMarriageModalControls: (...args) => _marriageModals.wrap('bindMarriageModalControls')(...args),
+ divorceConfirmModal: (...args) => _marriageModals.wrap('divorceConfirmModal')(...args),
+ divorceRequestModal: (...args) => _marriageModals.wrap('divorceRequestModal')(...args),
+ marriageAcceptedModal: (...args) => _marriageModals.wrap('marriageAcceptedModal')(...args),
+ marriageDivorcedModal: (...args) => _marriageModals.wrap('marriageDivorcedModal')(...args),
+ marriageIncomingModal: (...args) => _marriageModals.wrap('marriageIncomingModal')(...args),
+ marriageProposeModal: (...args) => _marriageModals.wrap('marriageProposeModal')(...args),
+ closeMarriageStatusModal: (...args) => _marriageStatus.wrap('closeMarriageStatusModal')(...args),
+ fetchMarriedList: (...args) => _marriageStatus.wrap('fetchMarriedList')(...args),
+ fetchMyMarriageStatus: (...args) => _marriageStatus.wrap('fetchMyMarriageStatus')(...args),
+ marriageAction: (...args) => _marriageStatus.wrap('marriageAction')(...args),
+ openMarriageStatusModal: (...args) => _marriageStatus.wrap('openMarriageStatusModal')(...args),
+ openProposeModal: (...args) => _marriageModals.wrap('openProposeModal')(...args),
+ openWeddingSetupModal: (...args) => _marriageModals.wrap('openWeddingSetupModal')(...args),
+ weddingEnvelopeModal: (...args) => _marriageModals.wrap('weddingEnvelopeModal')(...args),
+ weddingSetupModal: (...args) => _marriageModals.wrap('weddingSetupModal')(...args),
+ renderMarriedList: (...args) => _marriageStatus.wrap('renderMarriedList')(...args),
+ renderMarriageStatus: (...args) => _marriageStatus.wrap('renderMarriageStatus')(...args),
+ switchMarriageTab: (...args) => _marriageStatus.wrap('switchMarriageTab')(...args),
+ tryDivorce: (...args) => _marriageStatus.wrap('tryDivorce')(...args),
+ bindShopControls: (...args) => _shop.wrap('bindShopControls')(...args),
+ buyItem: (...args) => _shop.wrap('buyItem')(...args),
+ closeGiftDialog: (...args) => _shop.wrap('closeGiftDialog')(...args),
+ closeRenameModal: (...args) => _shop.wrap('closeRenameModal')(...args),
+ closeShopModal: (...args) => _shop.wrap('closeShopModal')(...args),
+ confirmGift: (...args) => _shop.wrap('confirmGift')(...args),
+ fetchShopData: (...args) => _shop.wrap('fetchShopData')(...args),
+ loadShop: (...args) => _shop.wrap('loadShop')(...args),
+ openGiftDialog: (...args) => _shop.wrap('openGiftDialog')(...args),
+ openRenameModal: (...args) => _shop.wrap('openRenameModal')(...args),
+ openShopModal: (...args) => _shop.wrap('openShopModal')(...args),
+ renderShop: (...args) => _shop.wrap('renderShop')(...args),
+ showShopToast: (...args) => _shop.wrap('showShopToast')(...args),
+ submitRename: (...args) => _shop.wrap('submitRename')(...args),
+ bindCompactShopPanelControls: (...args) => _compactShop.wrap('bindCompactShopPanelControls')(...args),
+ buyCompactShopItem: (...args) => _compactShop.wrap('buyCompactShopItem')(...args),
+ closeCompactRenameModal: (...args) => _compactShop.wrap('closeCompactRenameModal')(...args),
+ fetchCompactShopData: (...args) => _compactShop.wrap('fetchCompactShopData')(...args),
+ loadCompactShop: (...args) => _compactShop.wrap('loadCompactShop')(...args),
+ openCompactRenameModal: (...args) => _compactShop.wrap('openCompactRenameModal')(...args),
+ renderCompactShop: (...args) => _compactShop.wrap('renderCompactShop')(...args),
+ showCompactShopToast: (...args) => _compactShop.wrap('showCompactShopToast')(...args),
+ submitCompactRename: (...args) => _compactShop.wrap('submitCompactRename')(...args),
+ bindSlotMachineControls: (...args) => _slotMachine.wrap('bindSlotMachineControls')(...args),
+ bindVipControls: (...args) => _vip.wrap('bindVipControls')(...args),
+ buyVip: (...args) => _vip.wrap('buyVip')(...args),
+ closeVipModal: (...args) => _vip.wrap('closeVipModal')(...args),
+ openVipModal: (...args) => _vip.wrap('openVipModal')(...args),
+ saveVipPresenceSettings: (...args) => _vip.wrap('saveVipPresenceSettings')(...args),
+ switchVipTab: (...args) => _vip.wrap('switchVipTab')(...args),
+ closeChatImageLightbox: (...args) => _lightbox.wrap('closeChatImageLightbox')(...args),
+ initChatImageLightboxEvents: (...args) => _lightbox.wrap('initChatImageLightboxEvents')(...args),
+ openChatImageLightbox: (...args) => _lightbox.wrap('openChatImageLightbox')(...args),
+ bindChatRightPanelControls: (...args) => _rightPanel.wrap('bindChatRightPanelControls')(...args),
+ bindRoomStatusControls: (...args) => _rooms.wrap('bindRoomStatusControls')(...args),
+ normalizeRoomStatus: (...args) => _rooms.wrap('normalizeRoomStatus')(...args),
+ renderRoomStatusRow: (...args) => _rooms.wrap('renderRoomStatusRow')(...args),
+ renderRoomsOnlineStatus: (...args) => _rooms.wrap('renderRoomsOnlineStatus')(...args),
+ renderRoomsOnlineStatusToContainer: (...args) => _rooms.wrap('renderRoomsOnlineStatusToContainer')(...args),
+ resolveRoomUrl: (...args) => _rooms.wrap('resolveRoomUrl')(...args),
+ bindRewardModalControls: (...args) => _reward.wrap('bindRewardModalControls')(...args),
+ openRewardModal: (...args) => _reward.wrap('openRewardModal')(...args),
+ bindRedPacketPanelControls: (...args) => _redPacket.wrap('bindRedPacketPanelControls')(...args),
+ claimRedPacket: (...args) => _redPacket.wrap('claimRedPacket')(...args),
+ closeRedPacketModal: (...args) => _redPacket.wrap('closeRedPacketModal')(...args),
+ sendRedPacket: (...args) => _redPacket.wrap('sendRedPacket')(...args),
+ showRedPacketModal: (...args) => _redPacket.wrap('showRedPacketModal')(...args),
+ updateRedPacketClaimsUI: (...args) => _redPacket.wrap('updateRedPacketClaimsUI')(...args),
+
+ // ── 游戏模块 Alpine 对象(懒加载 Proxy) ──
+ lotteryPanel: new Proxy({}, {
+ get(_, prop) { return (...args) => _lotteryPanel.load().then(m => { const v = m.lotteryPanel; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ baccaratPanel: new Proxy({}, {
+ get(_, prop) { return (...args) => _baccaratPanel.load().then(m => { const v = m.baccaratPanel; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ baccaratFab: new Proxy({}, {
+ get(_, prop) { return (...args) => _baccaratFab.load().then(m => { const v = m.baccaratFab; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ gomokuPanel: new Proxy({}, {
+ get(_, prop) { return (...args) => _gomokuPanel.load().then(m => { const v = m.gomokuPanel; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ horseRacePanel: new Proxy({}, {
+ get(_, prop) { return (...args) => _horseRacePanel.load().then(m => { const v = m.horseRacePanel; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ horseRaceFab: new Proxy({}, {
+ get(_, prop) { return (...args) => _horseRaceFab.load().then(m => { const v = m.horseRaceFab; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ slotFab: new Proxy({}, {
+ get(_, prop) { return (...args) => _slotMachine.load().then(m => { const v = m.slotFab; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ slotPanel: new Proxy({}, {
+ get(_, prop) { return (...args) => _slotMachine.load().then(m => { const v = m.slotPanel; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ fortunePanel: new Proxy({}, {
+ get(_, prop) { return (...args) => _fortunePanel.load().then(m => { const v = m.fortunePanel; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ rewardModal: new Proxy({}, {
+ get(_, prop) { return (...args) => _reward.load().then(m => { const v = m.rewardModal; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ userCardComponent: new Proxy({}, {
+ get(_, prop) { return (...args) => _userCard.load().then(m => { const v = m.userCardComponent; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
+ holidayEventModal: new Proxy({}, {
+ get(_, prop) { return (...args) => _holiday.load().then(m => { const v = m.holidayEventModal; return typeof v[prop] === 'function' ? v[prop](...args) : v[prop]; }); }
+ }),
};
// 直接挂载只服务暂未迁移的 Blade 调用点;新代码优先通过模块导入或 ChatRoomTools 复用。
- window.closeChatImageLightbox = closeChatImageLightbox;
+ // ── 静态核心模块 window 挂载 ──
window.escapeHtml = escapeHtml;
window.isExpiredChatImageMessage = isExpiredChatImageMessage;
window.localClearScreen = localClearScreen;
window.normalizeSafeChatUrl = normalizeSafeChatUrl;
window.setAction = setChatComposerAction;
window.syncAutoScrollControls = syncAutoScrollControls;
- window.openChatImageLightbox = openChatImageLightbox;
- window.closeFriendPanel = closeFriendPanel;
- window.friendSearch = friendSearch;
- window.openFriendPanel = openFriendPanel;
- window.quickFriendAction = quickFriendAction;
- window.setupBannerNotification = setupBannerNotification;
- window.setupFriendNotification = setupFriendNotification;
- window.showFriendBanner = showFriendBanner;
- window.closeMobileDrawer = closeMobileDrawer;
- window.loadMobileRoomList = loadMobileRoomList;
- window.openMobileDrawer = openMobileDrawer;
- window.openUserCard = openUserCard;
- window.openRewardModal = openRewardModal;
- window.rewardModal = rewardModal;
- window.renderMobileRoomList = renderMobileRoomList;
- window.renderMobileUserList = renderMobileUserList;
- window.scheduleRenderMobileUserList = scheduleRenderMobileUserList;
- window.switchMobileTab = switchMobileTab;
- window.switchTarget = switchTarget;
- window.baccaratPanel = baccaratPanel;
- window.baccaratFab = baccaratFab;
- window.clearChatBotContext = clearChatBotContext;
- window.sendToChatBot = sendToChatBot;
- window.slotFab = slotFab;
- window.slotPanel = slotPanel;
+
+ // ── 懒加载功能模块 window 挂载(按需加载) ──
+ window.closeChatImageLightbox = (...args) => _lightbox.wrap('closeChatImageLightbox')(...args);
+ window.openChatImageLightbox = (...args) => _lightbox.wrap('openChatImageLightbox')(...args);
+ window.closeFriendPanel = (...args) => _friendPanel.wrap('closeFriendPanel')(...args);
+ window.friendSearch = (...args) => _friendPanel.wrap('friendSearch')(...args);
+ window.openFriendPanel = (...args) => _friendPanel.wrap('openFriendPanel')(...args);
+ window.quickFriendAction = (...args) => _friendPanel.wrap('quickFriendAction')(...args);
+ window.setupBannerNotification = (...args) => _friendNotifications.wrap('setupBannerNotification')(...args);
+ window.setupFriendNotification = (...args) => _friendNotifications.wrap('setupFriendNotification')(...args);
+ window.showFriendBanner = (...args) => _friendNotifications.wrap('showFriendBanner')(...args);
+ window.closeMobileDrawer = (...args) => _mobileDrawer.wrap('closeMobileDrawer')(...args);
+ window.loadMobileRoomList = (...args) => _mobileDrawer.wrap('loadMobileRoomList')(...args);
+ window.openMobileDrawer = (...args) => _mobileDrawer.wrap('openMobileDrawer')(...args);
+ window.openUserCard = (...args) => _userTargetActions.wrap('openUserCard')(...args);
+ window.openRewardModal = (...args) => _reward.wrap('openRewardModal')(...args);
+ window.renderMobileRoomList = (...args) => _mobileDrawer.wrap('renderMobileRoomList')(...args);
+ window.renderMobileUserList = (...args) => _mobileDrawer.wrap('renderMobileUserList')(...args);
+ window.scheduleRenderMobileUserList = (...args) => _mobileDrawer.wrap('scheduleRenderMobileUserList')(...args);
+ window.switchMobileTab = (...args) => _mobileDrawer.wrap('switchMobileTab')(...args);
+ window.switchTarget = (...args) => _userTargetActions.wrap('switchTarget')(...args);
+ window.clearChatBotContext = (...args) => _chatBot.wrap('clearChatBotContext')(...args);
+ window.sendToChatBot = (...args) => _chatBot.wrap('sendToChatBot')(...args);
window.runFeatureShortcut = runFeatureShortcut;
window.runToolbarAction = runToolbarAction;
- window.userCardComponent = userCardComponent;
- window.buildHolidayClaimActionButton = buildHolidayClaimActionButton;
- window.buildHolidaySystemMessage = buildHolidaySystemMessage;
- window.holidayEventModal = holidayEventModal;
- window.openHolidayRunFromSystemMessage = openHolidayRunFromSystemMessage;
- window.closeAdminBaccaratLossCoverModal = closeAdminBaccaratLossCoverModal;
- window.closeCurrentBaccaratLossCoverEvent = closeCurrentBaccaratLossCoverEvent;
- window.claimBaccaratLossCover = claimBaccaratLossCover;
- window.closeBaccaratLossCoverModal = closeBaccaratLossCoverModal;
- window.openBaccaratLossCoverModal = openBaccaratLossCoverModal;
- window.openAdminBaccaratLossCoverModal = openAdminBaccaratLossCoverModal;
- window.submitBaccaratLossCoverEvent = submitBaccaratLossCoverEvent;
- window.switchBaccaratLossCoverTab = switchBaccaratLossCoverTab;
- window.bankAction = bankAction;
- window.bankLoadInfo = bankLoadInfo;
- window.bankShowMsg = bankShowMsg;
- window.closeBankModal = closeBankModal;
- window.closeGameHall = closeGameHall;
- window.fetchBankRanking = fetchBankRanking;
- window.fortunePanel = fortunePanel;
- window.closeLotteryPanel = closeLotteryPanel;
- window.createEarnPanelData = createEarnPanelData;
- window.deferChatGameBootstrap = deferChatGameBootstrap;
- window.lotteryPanel = lotteryPanel;
- window.openGameHall = openGameHall;
- window.gomokuPanel = gomokuPanel;
- window.acceptGomokuInvite = acceptGomokuInvite;
- window.openGomokuPanel = openGomokuPanel;
- window.horseRacePanel = horseRacePanel;
- window.horseRaceFab = horseRaceFab;
- window.openLotteryPanel = openLotteryPanel;
- window.openBankModal = openBankModal;
- window.showLotteryMsg = showLotteryMsg;
- window.checkAndAutoStartFishing = checkAndAutoStartFishing;
- window.createBobber = createBobber;
- window.reelFish = reelFish;
- window.removeBobber = removeBobber;
- window.resetFishingBtn = resetFishingBtn;
- window.startFishing = startFishing;
- window.stopAutoFishing = stopAutoFishing;
- window.buyVip = buyVip;
- window.closeVipModal = closeVipModal;
- window.openVipModal = openVipModal;
- window.saveVipPresenceSettings = saveVipPresenceSettings;
- window.switchVipTab = switchVipTab;
- window.switchBankTab = switchBankTab;
- window.toggleBankRankSort = toggleBankRankSort;
- window.claimRedPacket = claimRedPacket;
- window.closeRedPacketModal = closeRedPacketModal;
- window.sendRedPacket = sendRedPacket;
- window.showRedPacketModal = showRedPacketModal;
- window.updateRedPacketClaimsUI = updateRedPacketClaimsUI;
- window.applyFontSize = applyFontSize;
- window.closeAvatarPicker = closeAvatarPicker;
- window.closeSettingsModal = closeSettingsModal;
- window.copyWechatBindCode = copyWechatBindCode;
- window.generateWechatBindCode = generateWechatBindCode;
- window.handleAvatarUpload = handleAvatarUpload;
- window.loadHeadfaces = loadHeadfaces;
- window.openAvatarPicker = openAvatarPicker;
- window.openSettingsModal = openSettingsModal;
- window.saveAvatar = saveAvatar;
- window.savePassword = savePassword;
- window.saveSettings = saveSettings;
- window.selectAvatar = selectAvatar;
- window.sendEmailCode = sendEmailCode;
- window.showInlineMsg = showInlineMsg;
- window.unbindWechat = unbindWechat;
- window.closeMarriageStatusModal = closeMarriageStatusModal;
- window.fetchMarriedList = fetchMarriedList;
- window.fetchMyMarriageStatus = fetchMyMarriageStatus;
- window.marriageAction = marriageAction;
- window.openMarriageStatusModal = openMarriageStatusModal;
- window.appendSystemMessage = appendSystemMessage;
- window.divorceConfirmModal = divorceConfirmModal;
- window.divorceRequestModal = divorceRequestModal;
- window.marriageAcceptedModal = marriageAcceptedModal;
- window.marriageDivorcedModal = marriageDivorcedModal;
- window.marriageIncomingModal = marriageIncomingModal;
- window.marriageProposeModal = marriageProposeModal;
- window.openProposeModal = openProposeModal;
- window.openWeddingSetupModal = openWeddingSetupModal;
- window.weddingEnvelopeModal = weddingEnvelopeModal;
- window.weddingSetupModal = weddingSetupModal;
- window.renderMarriedList = renderMarriedList;
- window.renderMarriageStatus = renderMarriageStatus;
- window.switchMarriageTab = switchMarriageTab;
- window.tryDivorce = tryDivorce;
- window.buyItem = buyItem;
- window.closeGiftDialog = closeGiftDialog;
- window.closeRenameModal = closeRenameModal;
- window.closeShopModal = closeShopModal;
- window.confirmGift = confirmGift;
- window.fetchShopData = fetchShopData;
- window.loadShop = loadShop;
- window.openGiftDialog = openGiftDialog;
- window.openRenameModal = openRenameModal;
- window.openShopModal = openShopModal;
- window.renderShop = renderShop;
- window.showShopToast = showShopToast;
- window.submitRename = submitRename;
+ window.buildHolidayClaimActionButton = (...args) => _holiday.wrap('buildHolidayClaimActionButton')(...args);
+ window.buildHolidaySystemMessage = (...args) => _holiday.wrap('buildHolidaySystemMessage')(...args);
+ window.openHolidayRunFromSystemMessage = (...args) => _holiday.wrap('openHolidayRunFromSystemMessage')(...args);
+ window.closeAdminBaccaratLossCoverModal = (...args) => _baccaratLossCoverAdmin.wrap('closeAdminBaccaratLossCoverModal')(...args);
+ window.closeCurrentBaccaratLossCoverEvent = (...args) => _baccaratLossCoverAdmin.wrap('closeCurrentBaccaratLossCoverEvent')(...args);
+ window.claimBaccaratLossCover = (...args) => _baccaratLossCover.wrap('claimBaccaratLossCover')(...args);
+ window.closeBaccaratLossCoverModal = (...args) => _baccaratLossCover.wrap('closeBaccaratLossCoverModal')(...args);
+ window.openBaccaratLossCoverModal = (...args) => _baccaratLossCover.wrap('openBaccaratLossCoverModal')(...args);
+ window.openAdminBaccaratLossCoverModal = (...args) => _baccaratLossCoverAdmin.wrap('openAdminBaccaratLossCoverModal')(...args);
+ window.submitBaccaratLossCoverEvent = (...args) => _baccaratLossCoverAdmin.wrap('submitBaccaratLossCoverEvent')(...args);
+ window.switchBaccaratLossCoverTab = (...args) => _baccaratLossCover.wrap('switchBaccaratLossCoverTab')(...args);
+ window.bankAction = (...args) => _bank.wrap('bankAction')(...args);
+ window.bankLoadInfo = (...args) => _bank.wrap('bankLoadInfo')(...args);
+ window.bankShowMsg = (...args) => _bank.wrap('bankShowMsg')(...args);
+ window.closeBankModal = (...args) => _bank.wrap('closeBankModal')(...args);
+ window.closeGameHall = (...args) => _gameHall.wrap('closeGameHall')(...args);
+ window.fetchBankRanking = (...args) => _bank.wrap('fetchBankRanking')(...args);
+ window.closeLotteryPanel = (...args) => _lotteryPanel.wrap('closeLotteryPanel')(...args);
+ window.createEarnPanelData = (...args) => _earn.wrap('createEarnPanelData')(...args);
+ window.deferChatGameBootstrap = (...args) => _gameBootstrap.wrap('deferChatGameBootstrap')(...args);
+ window.openGameHall = (...args) => _gameHall.wrap('openGameHall')(...args);
+ window.acceptGomokuInvite = (...args) => _gomokuControls.wrap('acceptGomokuInvite')(...args);
+ window.openGomokuPanel = (...args) => _gomokuControls.wrap('openGomokuPanel')(...args);
+ window.openLotteryPanel = (...args) => _lotteryPanel.wrap('openLotteryPanel')(...args);
+ window.openBankModal = (...args) => _bank.wrap('openBankModal')(...args);
+ window.showLotteryMsg = (...args) => _lotteryPanel.wrap('showLotteryMsg')(...args);
+ window.checkAndAutoStartFishing = (...args) => _fishing.wrap('checkAndAutoStartFishing')(...args);
+ window.createBobber = (...args) => _fishing.wrap('createBobber')(...args);
+ window.reelFish = (...args) => _fishing.wrap('reelFish')(...args);
+ window.removeBobber = (...args) => _fishing.wrap('removeBobber')(...args);
+ window.resetFishingBtn = (...args) => _fishing.wrap('resetFishingBtn')(...args);
+ window.startFishing = (...args) => _fishing.wrap('startFishing')(...args);
+ window.stopAutoFishing = (...args) => _fishing.wrap('stopAutoFishing')(...args);
+ window.buyVip = (...args) => _vip.wrap('buyVip')(...args);
+ window.closeVipModal = (...args) => _vip.wrap('closeVipModal')(...args);
+ window.openVipModal = (...args) => _vip.wrap('openVipModal')(...args);
+ window.saveVipPresenceSettings = (...args) => _vip.wrap('saveVipPresenceSettings')(...args);
+ window.switchVipTab = (...args) => _vip.wrap('switchVipTab')(...args);
+ window.switchBankTab = (...args) => _bank.wrap('switchBankTab')(...args);
+ window.toggleBankRankSort = (...args) => _bank.wrap('toggleBankRankSort')(...args);
+ window.claimRedPacket = (...args) => _redPacket.wrap('claimRedPacket')(...args);
+ window.closeRedPacketModal = (...args) => _redPacket.wrap('closeRedPacketModal')(...args);
+ window.sendRedPacket = (...args) => _redPacket.wrap('sendRedPacket')(...args);
+ window.showRedPacketModal = (...args) => _redPacket.wrap('showRedPacketModal')(...args);
+ window.updateRedPacketClaimsUI = (...args) => _redPacket.wrap('updateRedPacketClaimsUI')(...args);
+ window.applyFontSize = (...args) => _fontSize.wrap('applyFontSize')(...args);
+ window.closeAvatarPicker = (...args) => _profile.wrap('closeAvatarPicker')(...args);
+ window.closeSettingsModal = (...args) => _profile.wrap('closeSettingsModal')(...args);
+ window.copyWechatBindCode = (...args) => _profile.wrap('copyWechatBindCode')(...args);
+ window.generateWechatBindCode = (...args) => _profile.wrap('generateWechatBindCode')(...args);
+ window.handleAvatarUpload = (...args) => _profile.wrap('handleAvatarUpload')(...args);
+ window.loadHeadfaces = (...args) => _profile.wrap('loadHeadfaces')(...args);
+ window.openAvatarPicker = (...args) => _profile.wrap('openAvatarPicker')(...args);
+ window.openSettingsModal = (...args) => _profile.wrap('openSettingsModal')(...args);
+ window.saveAvatar = (...args) => _profile.wrap('saveAvatar')(...args);
+ window.savePassword = (...args) => _profile.wrap('savePassword')(...args);
+ window.saveSettings = (...args) => _profile.wrap('saveSettings')(...args);
+ window.selectAvatar = (...args) => _profile.wrap('selectAvatar')(...args);
+ window.sendEmailCode = (...args) => _profile.wrap('sendEmailCode')(...args);
+ window.showInlineMsg = (...args) => _profile.wrap('showInlineMsg')(...args);
+ window.unbindWechat = (...args) => _profile.wrap('unbindWechat')(...args);
+ window.closeMarriageStatusModal = (...args) => _marriageStatus.wrap('closeMarriageStatusModal')(...args);
+ window.fetchMarriedList = (...args) => _marriageStatus.wrap('fetchMarriedList')(...args);
+ window.fetchMyMarriageStatus = (...args) => _marriageStatus.wrap('fetchMyMarriageStatus')(...args);
+ window.marriageAction = (...args) => _marriageStatus.wrap('marriageAction')(...args);
+ window.openMarriageStatusModal = (...args) => _marriageStatus.wrap('openMarriageStatusModal')(...args);
+ window.appendSystemMessage = (...args) => _marriageModals.wrap('appendSystemMessage')(...args);
+ window.divorceConfirmModal = (...args) => _marriageModals.wrap('divorceConfirmModal')(...args);
+ window.divorceRequestModal = (...args) => _marriageModals.wrap('divorceRequestModal')(...args);
+ window.marriageAcceptedModal = (...args) => _marriageModals.wrap('marriageAcceptedModal')(...args);
+ window.marriageDivorcedModal = (...args) => _marriageModals.wrap('marriageDivorcedModal')(...args);
+ window.marriageIncomingModal = (...args) => _marriageModals.wrap('marriageIncomingModal')(...args);
+ window.marriageProposeModal = (...args) => _marriageModals.wrap('marriageProposeModal')(...args);
+ window.openProposeModal = (...args) => _marriageModals.wrap('openProposeModal')(...args);
+ window.openWeddingSetupModal = (...args) => _marriageModals.wrap('openWeddingSetupModal')(...args);
+ window.weddingEnvelopeModal = (...args) => _marriageModals.wrap('weddingEnvelopeModal')(...args);
+ window.weddingSetupModal = (...args) => _marriageModals.wrap('weddingSetupModal')(...args);
+ window.renderMarriedList = (...args) => _marriageStatus.wrap('renderMarriedList')(...args);
+ window.renderMarriageStatus = (...args) => _marriageStatus.wrap('renderMarriageStatus')(...args);
+ window.switchMarriageTab = (...args) => _marriageStatus.wrap('switchMarriageTab')(...args);
+ window.tryDivorce = (...args) => _marriageStatus.wrap('tryDivorce')(...args);
+ window.buyItem = (...args) => _shop.wrap('buyItem')(...args);
+ window.closeGiftDialog = (...args) => _shop.wrap('closeGiftDialog')(...args);
+ window.closeRenameModal = (...args) => _shop.wrap('closeRenameModal')(...args);
+ window.closeShopModal = (...args) => _shop.wrap('closeShopModal')(...args);
+ window.confirmGift = (...args) => _shop.wrap('confirmGift')(...args);
+ window.fetchShopData = (...args) => _shop.wrap('fetchShopData')(...args);
+ window.loadShop = (...args) => _shop.wrap('loadShop')(...args);
+ window.openGiftDialog = (...args) => _shop.wrap('openGiftDialog')(...args);
+ window.openRenameModal = (...args) => _shop.wrap('openRenameModal')(...args);
+ window.openShopModal = (...args) => _shop.wrap('openShopModal')(...args);
+ window.renderShop = (...args) => _shop.wrap('renderShop')(...args);
+ window.showShopToast = (...args) => _shop.wrap('showShopToast')(...args);
+ window.submitRename = (...args) => _shop.wrap('submitRename')(...args);
+
+ // ── Alpine 组件懒加载(createLazyAlpineComponent:$watch 触发时才加载真实模块) ──
+ window.baccaratPanel = createLazyAlpineComponent(
+ () => import("./chat-room/baccarat-panel.js"),
+ "baccaratPanel",
+ { show: false }
+ );
+ window.baccaratFab = createLazyAlpineComponent(
+ () => import("./chat-room/baccarat-fab.js"),
+ "baccaratFab",
+ { show: false }
+ );
+ window.slotFab = createLazyAlpineComponent(
+ () => import("./chat-room/slot-machine.js"),
+ "slotFab",
+ { show: false }
+ );
+ window.slotPanel = createLazyAlpineComponent(
+ () => import("./chat-room/slot-machine.js"),
+ "slotPanel",
+ { show: false }
+ );
+ window.userCardComponent = createLazyAlpineComponent(
+ () => import("./chat-room/user-card.js"),
+ "userCardComponent",
+ {
+ showUserModal: false,
+ showOriginalLightbox: false,
+ userInfo: {},
+ isMuting: false,
+ muteDuration: 5,
+ showWhispers: false,
+ whisperList: [],
+ showAnnounce: false,
+ announceText: "",
+ is_friend: false,
+ friendLoading: false,
+ gifts: [],
+ selectedGiftId: 0,
+ giftCount: 1,
+ sendingGift: false,
+ showGiftPanel: false,
+ showGiftGoldPanel: false,
+ giftGoldAmount: "",
+ giftGoldSending: false,
+ rewardAmount: 0,
+ sendingReward: false,
+ showRewardPanel: false,
+ showAppointPanel: false,
+ appointPositions: [],
+ selectedPositionId: null,
+ appointRemark: "",
+ appointLoading: false,
+ showAdminView: false,
+ showPositionHistory: false,
+ showAdminPanel: false,
+ targetMarriage: null,
+ marriageLoading: false,
+ mySex: "",
+ assetCache: [],
+ },
+ "showUserModal"
+ );
+ window.holidayEventModal = createLazyAlpineComponent(
+ () => import("./chat-room/holiday-modal.js"),
+ "holidayEventModal",
+ { show: false }
+ );
+ window.fortunePanel = createLazyAlpineComponent(
+ () => import("./chat-room/fortune-panel.js"),
+ "fortunePanel",
+ { show: false }
+ );
+ window.lotteryPanel = createLazyAlpineComponent(
+ () => import("./chat-room/lottery-panel.js"),
+ "lotteryPanel",
+ { show: false }
+ );
+ window.gomokuPanel = createLazyAlpineComponent(
+ () => import("./chat-room/gomoku-panel.js"),
+ "gomokuPanel",
+ { show: false }
+ );
+ window.horseRacePanel = createLazyAlpineComponent(
+ () => import("./chat-room/horse-race-panel.js"),
+ "horseRacePanel",
+ { show: false }
+ );
+ window.horseRaceFab = createLazyAlpineComponent(
+ () => import("./chat-room/horse-race-fab.js"),
+ "horseRaceFab",
+ { show: false }
+ );
+ window.rewardModal = createLazyAlpineComponent(
+ () => import("./chat-room/reward-modal.js"),
+ "rewardModal",
+ { show: false }
+ );
// 聊天室核心引擎 window 挂载
window.bindChatEvents = bindChatEvents;
@@ -926,56 +902,15 @@ if (typeof window !== "undefined") {
window.startHeartbeat = startHeartbeat;
window.stopHeartbeat = stopHeartbeat;
- // 页面加载后立即注册事件委托,具体业务逻辑仍由各子模块负责。
- bindChatBanner();
- bindChatBotControls();
- bindAppointmentAnnouncementControls();
+ // 页面加载后立即注册事件委托(仅静态核心模块,懒加载模块通过 createLazyModule initFn 自动初始化)
bindGlobalDialogControls();
- bindDailySignInControls();
- bindEarnPanelControls();
- bindLotteryPanelControls();
- bindChatFontSizeControl();
- bindChatImageUploadControl();
bindChatComposerControls();
bindChatToast();
- bindFriendPanelControls();
- bindFriendNotificationControls();
- bindToolbarControls();
- bindUserCardControls();
- bindUserTargetActions();
- bindAdminMenuControls();
- bindBaccaratPanelControls();
- bindBaccaratFabControls();
- bindBaccaratEvents();
- bindBaccaratLossCoverAdminControls();
- bindBaccaratLossCoverControls();
- bindGameHallControls();
- bindGameBootstrapControls();
- bindGamePanelControls();
- bindGomokuPanelControls();
- bindGomokuControls();
- bindHorseRacePanelControls();
- bindHorseRaceFabControls();
- bindHorseRaceEvents();
- bindHolidayModalControls();
- bindMarriageModalControls();
- bindChatInitialStateControls();
- bindBankControls();
- bindFishingControls();
- bindFortunePanelControls();
- bindMarriageStatusControls();
- bindProfileControls();
- bindShopControls();
- bindCompactShopPanelControls();
- bindSlotMachineControls();
- bindVipControls();
- bindChatRightPanelControls();
- bindRoomStatusControls();
- bindRewardModalControls();
- bindRedPacketPanelControls();
- bindMobileDrawerControls();
- bindWelcomeMenuControls();
bindBlockMenuControls();
+ bindSoundMuteControl();
+ bindInstantHoverTooltip();
+ bindToolbarControls();
+ bindChatInitialStateControls();
bindChatEvents();
startBadgeRotation();
startHeartbeat();
diff --git a/resources/js/chat-room/lazy-loader.js b/resources/js/chat-room/lazy-loader.js
new file mode 100644
index 0000000..248840a
--- /dev/null
+++ b/resources/js/chat-room/lazy-loader.js
@@ -0,0 +1,185 @@
+/**
+ * 懒加载工具模块
+ *
+ * 提供按需动态导入辅助函数,支持:
+ * 1. 首次加载时自动调用初始化函数(如 bind*Controls)
+ * 2. 包裹目标函数,实现 window.xxx = (...args) => 自动加载并调用
+ * 3. 模块缓存机制,避免重复加载
+ * 4. Alpine 组件懒加载:返回同步 stub,在 $watch 触发时异步加载真实组件
+ */
+
+/**
+ * 创建一个可延迟加载的模块引用。
+ *
+ * @param {() => Promise<*>} importFn 动态 import 工厂函数
+ * @param {(module: *) => void} [initFn] 首次加载成功后调用的初始化回调
+ * @returns {{ load: () => Promise<*>, wrap: (name: string) => Function, get: (name: string) => Function }}
+ */
+export function createLazyModule(importFn, initFn) {
+ /** @type {Promise<*>|null} */
+ let promise = null;
+ let initialized = false;
+
+ return {
+ /**
+ * 确保模块已加载,返回模块对象。
+ * @returns {Promise<*>}
+ */
+ async load() {
+ if (!promise) {
+ promise = importFn().then((mod) => {
+ if (!initialized && initFn) {
+ initialized = true;
+ initFn(mod);
+ }
+ return mod;
+ });
+ }
+ return promise;
+ },
+
+ /**
+ * 创建一个延迟执行的目标函数包装器。
+ * 首次调用时自动加载模块,之后直接调用缓存。
+ *
+ * @param {string} fnName 模块中导出的函数名
+ * @returns {Function}
+ */
+ wrap(fnName) {
+ return async (...args) => {
+ const mod = await this.load();
+ const fn = mod[fnName];
+ if (typeof fn !== "function") {
+ throw new Error(
+ `懒加载模块中找不到函数 "${fnName}"`,
+ );
+ }
+ return fn(...args);
+ };
+ },
+
+ /**
+ * 返回一个返回模块导出的 getter 函数。
+ * 适用于返回非函数值(如 Alpine 组件对象)。
+ *
+ * @param {string} name 模块中导出的名称
+ * @returns {Function}
+ */
+ get(name) {
+ return async () => {
+ const mod = await this.load();
+ return mod[name];
+ };
+ },
+ };
+}
+
+/**
+ * 创建 Alpine 组件延迟加载包装器。
+ *
+ * Alpine 的 x-data="ComponentName()" 要求工厂函数返回一个同步对象。
+ * 本函数返回一个函数,该函数:
+ * 1. 立即返回一个包含安全默认值的 stub 对象
+ * 2. 通过 Alpine 的 $watch 监听显示状态变化
+ * 3. 仅当组件变为可见(show/showUserModal 变为 true)时,才异步加载真实模块
+ * 4. 通过 Alpine 的响应式代理(this)写入真实数据,触发模板重新渲染
+ *
+ * 这实现了"真懒加载"——用户若不打开面板,对应代码块永远不会下载。
+ *
+ * @param {() => Promise<*>} importFn 动态 import 工厂函数
+ * @param {string} exportName 模块导出的组件工厂函数名
+ * @param {Record} [defaults={}] 安全默认值
+ * @param {string} [watchKey='show'] 用于触发懒加载的 $watch 属性名
+ * @returns {Function} Alpine 组件工厂函数
+ */
+export function createLazyAlpineComponent(importFn, exportName, defaults = {}, watchKey = "show") {
+ return function (...args) {
+ const stub = {
+ [watchKey]: false,
+ ...defaults,
+ init() {
+ const proxy = this;
+ let loaded = false;
+
+ // 使用 Alpine 的 $watch 监听显示状态变化
+ // 仅在组件变为可见时才加载真实模块
+ if (
+ watchKey in proxy &&
+ typeof proxy.$watch === "function"
+ ) {
+ proxy.$watch(watchKey, (value, oldValue) => {
+ if (value && !loaded) {
+ loaded = true;
+ importFn()
+ .then((mod) => {
+ const componentFn = mod[exportName];
+ const realData =
+ typeof componentFn === "function"
+ ? componentFn(...args)
+ : componentFn;
+
+ // 通过 Alpine 响应式代理写入所有属性
+ Object.keys(realData).forEach((key) => {
+ if (key !== "init") {
+ proxy[key] = realData[key];
+ }
+ });
+
+ // 处理继承属性
+ for (const key in realData) {
+ if (!(key in proxy)) {
+ proxy[key] = realData[key];
+ }
+ }
+
+ // 调用真实组件的 init(如果存在)
+ if (
+ typeof proxy.init === "function" &&
+ proxy.init !== stub.init
+ ) {
+ proxy.init.call(proxy);
+ }
+ })
+ .catch((err) => {
+ console.error(
+ `[懒加载] Alpine 组件 "${exportName}" 加载失败:`,
+ err,
+ );
+ loaded = false; // 允许重试
+ });
+ }
+ });
+ }
+ },
+ };
+
+ // 使用 Proxy 包裹 stub,对任何未在 defaults 中定义的属性/方法提供安全兜底
+ // 这样组件模板中的所有表达式(方法调用、属性访问)都不会抛出 "not defined" 错误
+ return new Proxy(stub, {
+ get(target, prop, receiver) {
+ // 已存在的属性直接返回
+ if (prop in target) {
+ return Reflect.get(target, prop, receiver);
+ }
+ // 对于未定义的属性/方法,返回一个安全的、支持调用的兜底值
+ // - 作为值访问(如 targetMarriage?.status): 返回 '',保证 .status 不报错
+ // - 作为方法调用(如 displayAssetValue('exp_num')): 返回空字符串
+ const fallback = () => "";
+ return fallback;
+ },
+ // Alpine 使用 with(scope) 求值表达式,with 用 has (in 操作符) 判断属性是否存在
+ // 如果没有 has 陷阱,with 认为属性不存在 → 跳到 window → "is not defined"
+ has(target, prop) {
+ // 不要拦截 Alpine/Vue 内部属性和魔术方法
+ if (typeof prop === "symbol") return Reflect.has(target, prop);
+ if (String(prop).startsWith("__v_")) return Reflect.has(target, prop);
+ if (String(prop).startsWith("$")) return Reflect.has(target, prop);
+ // 所有其他属性都报告存在,让 with 继续用 get 获取兜底值
+ return true;
+ },
+ set(target, prop, value, receiver) {
+ return Reflect.set(target, prop, value, receiver);
+ },
+ });
+ };
+}
diff --git a/vite.config.js b/vite.config.js
index 045cd60..cacdcda 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -19,6 +19,18 @@ export default defineConfig({
}),
tailwindcss(),
],
+ build: {
+ rollupOptions: {
+ output: {
+ manualChunks(id) {
+ if (id.includes("node_modules")) {
+ return "vendor";
+ }
+ },
+ },
+ },
+ sourcemap: false,
+ },
server: {
watch: {
ignored: ["**/storage/framework/views/**"],