[开发]猜题对决(quiz_duel)插件说明

👑Lv.11 元老 🌞 骨干会员
2026-10-26 00:19:19

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/quiz_duel/说明.md。

猜题对决(quiz_duel)插件说明

版本:1.2.0 | 作者:SoSite | 状态:✅ 已启用

功能:一对一猜题对赌——出题人设题并押注,应战人猜出题人的答案,猜中者赢家通吃(拿走双倍注额)。


一、功能简介

  • 出题(挑战):填写问题、选项 A / B、自己的答案,并押注一笔积分 → 生成一条 open 状态的挑战
  • 应战:他人选择 A 或 B 并押上同等注额 → 立即结算
  • 应战人选的答案 == 出题人设的答案 → 应战人赢
  • 否则 → 出题人赢
  • 赢家通吃:赢家获得 stake × 2(双方的注额之和)
  • 三个页面:等待应战列表(/duel)、创建挑战(/duel/create)、我的对决(/duel/my)、对决详情(/duel/detail/{id})
  • 侧栏:/duel 页面右栏输出「导航 + 赢家榜 + 最近对决」三块面板(桌面走钩子,移动端走视图内兜底)
  • 前端交互:htmx 局部刷新(成功响应携带 data-redirect 时延迟跳转),零内联 JS

二、目录结构

plugins/quiz_duel/
├── plugin.json                      # 插件清单(4 个钩子;无 admin_url / permissions)
├── Plugin.php                       # 主类:建表、赢家榜、最近历史、今日应战数、总盈亏、生命周期
├── QuizDuelController.php           # 前台:列表 / 创建 / 我的 / 详情 / 应战结算
├── hook/
│   ├── init_after.php               # 幂等建表兜底(缓存标记 protected/.quiz_duel_table_cache)
│   ├── route_register.php           # 前台路由注册
│   ├── nav_plugin_links.php         # 应用下拉菜单入口
│   └── right_sidebar_after.php      # 右栏「导航 / 赢家榜 / 最近对决」(仅 /duel 前缀页面输出)
├── views/
│   ├── index.php                    # 等待应战列表
│   ├── create.php                   # 创建挑战
│   ├── detail.php                   # 详情 / 应战
│   └── my.php                       # 我的对决
├── assets/                          # style.css / script.js(零内联)
├── data/                            # quiz_duel.sqlite(独立库)+ .htaccess / web.config
└── lang/                            # zh.php / en.php / zh_tw.php(各 64 键,三语零差异)

三、数据表

插件独立库:plugins/quiz_duel/data/quiz_duel.sqlite

表用途
quiz_duels对决记录(creator_id / opponent_id / question / option_a / option_b / creator_choice / opponent_choice / stake / status / winner_id / created_at / accepted_at / completed_at)

  • 索引:idx_quiz_duels_status、idx_quiz_duels_creator、idx_quiz_duels_opponent
  • status 状态机:open(等待应战)→ completed(已结算)
  • 建表:activate() + init_after 双保险;卸载:uninstall() 删表

四、路由

方法路径说明
GET/duel等待应战的挑战列表(需登录)
GET/duel/create创建挑战页(需登录)
POST/duel/create提交挑战(需登录 + CSRF)
GET/duel/my我的对决(需登录)
GET/duel/detail/{id}对决详情(需登录)
POST/duel/accept应战并结算(需登录 + CSRF)

本插件无后台页,故 plugin.json 不声明 admin_url 与 permissions(二者均为可选字段)。

五、核心规则

创建挑战的字段校验(store())

字段规则
question长度 4 ~ 200 字
option_a / option_b长度 1 ~ 50 字
creator_choice必须是 A 或 B
stakeduel_bet_min ~ duel_bet_max

校验通过 → Points::deduct() 扣押注 → 写 quiz_duels(status = 'open')。

写库失败自动退款(Points::award,理由「猜题对决退款」)。

应战结算(accept())

① 校验 choice ∈ {A, B}
② 取对决记录,必须存在且 status = 'open'(否则「已关闭」)
③ 不能应战自己的挑战
④ 扣应战人积分(Points::deduct,理由「猜题对决押注」)
⑤ 判定:opponent_choice == creator_choice → 应战人赢,否则出题人赢
⑥ 更新记录(opponent_id / opponent_choice / status='completed' / winner_id / accepted_at / completed_at)
⑦ 发奖:Points::award(winnerId, stake * 2)

  • 结算与发奖在同一个 try 块内:任一步抛异常 → 退还应战人积分(理由「猜题对决退款」)并返回错误
  • Points::award() 外层再套一层 try/catch:封装内异常降级为日志,不阻断「已结算、即将返回 JSON」的响应

配置项(核心 settings 表,惰性默认)

键默认说明
duel_bet_min10最低押注额
duel_bet_max500最高押注额

统计接口(Plugin.php)

方法说明
getLeaderboard($limit)赢家榜(按 SUM(stake) 降序,仅 completed)
getRecentHistory($limit)最近对决(按 completed_at 降序)
getTodayJoinCount($userId)今日应战次数(status IN ('accepted','completed'))
getUserProfit($userId)用户总盈亏 = 赢得的 SUM(stake*2) − 输掉的 SUM(stake)

跨库无法 JOIN:用户名 / 头像经 User::batchGetUsers() / User::batchGetNames() 从核心库批量补全。

六、注意事项

  • ⚠️ 需要登录才能查看与参与,游客访问会被 requireLogin() 重定向
  • ⚠️ 依赖核心积分体系:Points::deduct() / Points::award()
  • ⚠️ 只有 status = 'open' 的挑战可被应战,已结算的挑战再次应战返回「已关闭」;不能应战自己的挑战
  • ⚠️ 一次应战即终局:没有「出题人反悔」「超时作废」机制——open 状态的挑战会一直挂着,直到有人应战
  • ⚠️ 出题人与应战人各扣一次注额,赢家拿 stake × 2,平台不抽成(零和)
  • ⚠️ 本插件目录名含下划线,核心 Plugin::activate/deactivate/uninstall() 的类名推导

("\\Plugin\\{$name}\\Plugin")会拼出 \Plugin\quiz_duel\Plugin 而无法命中 \Plugin\QuizDuel\Plugin

→ 已在 Plugin.php 末尾用 class_alias 补别名(否则卸载时数据表不会被删除)

样式依赖(2026-10-08 修复)

  • ⚠️⚠️ **视图只能用 duel- 或核心 mn- 类**,禁用旧原子类(.flex-1 / .gap- / .mt- / .mb-* /

.fs- / .fw- / .c-999 / .text-center,以及 Bootstrap 式的 .row / .col-6 / .g-10)——

核心已把旧原子类**统一迁移到 mn-* 并删除旧定义**(见 assets/css/modern.css 的 [迁移] 注释),

继续用旧类名会静默失效(不报错、不写日志,只是样式不生效)。

  • **本插件样式统一走 duel-* 命名空间**(与核心解耦),工具类集中在 assets/style.css 第 28 节。
  • 典型症状:主列 .duel-flex-1 若漏掉 flex: 1,在 .duel-flex-layout(flex + wrap)里

宽度会退化成「内容宽度」而非撑满容器 → 整个内容区看起来变窄(本次即为此问题)。

  • 历史坑:视图曾 <link> 核心的 /assets/css/components.css,该文件已不存在(404),本次已移除。
  • ⚠️ 两轮扫描的教训:第一轮只比对了 duel-* 类,漏掉了 4 个非 duel- 前缀的 Bootstrap 式旧类

(align-items-center / gap-2 / ms-2 / mb-0)—— 它们在前台实际加载的 CSS 里同样没有定义

(.mb-0 只在后台 admin.css 且作用域是 .admin-container)。第二轮已补齐为

duel-items-center / duel-gap-8 / duel-ms-8 / duel-mb-0(-2 按 Bootstrap = 0.5rem = 8px)。

→ 改完必须全量比对「视图里所有静态类 ↔ 前台实际加载的 CSS」,不能只看自己改过的那批前缀。

(判定「某类有没有定义」时,admin.css / editor.css 前台不加载,算进去会漏判。)

  • 当前状态:quiz_duels 1 行;插件已启用(activated: true)
  • 版本历史:
  • 1.2.0(2026-10-08)修复「内容页变窄」——核心原子类迁移后插件仍在用旧类名导致静默失效,

视图 21 个失效类全部改 duel-*、移除 404 的 components.css 引用、style.css 第 28 节补齐定义

  • 1.1.0(2026-10-03)侧栏/卡片布局重构
  • 1.0.0 首个版本
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 2 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录