【XIUNOX插件】帖子投票 v1.0.5 [复制链接]

管理员组

xnx_poll 帖子投票插件

为帖子添加内嵌投票功能。支持单选/多选、倒计时、实时结果展示、用户组权限控制,htmx 无刷新投票体验。

功能

  • 发帖集成:在发布页右侧栏添加投票配置卡片(与「引用帖子」卡片同款样式)
- 开启开关、投票标题、2-20 个动态选项

- 投票类型:单选 / 多选(最多可选数) - 持续时间(小时,0=永久) - 结果可见性:投票后立即可见 / 结束后可见 - 允许修改投票、允许游客、允许参与的用户组(多选)

  • 帖子展示:在帖子正文下方显示投票卡片
- 进度条可视化(占比 + 票数)

- 倒计时实时刷新(到期自动切换为结束态) - 已投票用户高亮已选项 - 总票数 + 参与人数统计 - 作者可提前结束、管理员可强制结束

  • htmx 无刷新投票:fetch + FormData 提交,实时返回新卡片 + toast 提示
  • 管理功能:
- 全局开关、允许发起的用户组、选项数范围、默认时长、允许游客、允许改票

- 后台投票列表(按状态 tab 筛选:全部/进行中/已结束/已过期) - 查看参与记录(display_name + 所选项 + 投票时间) - CSV 导出 - 强制结束、删除(含级联清理)

  • 多语言:zh-cn / zh-tw / en-us 三语言同步
  • 级联清理:删帖自动级联清理投票主表/选项/记录

安装

  1. 复制 xnx_poll 目录到 plugin/
  2. 后台 → 插件管理 → 安装并启用
  3. 后台 → 投票设置 → 配置允许发起的用户组
  4. 发帖时勾选「开启投票」配置参数即可

路由

| 路由名 | URL | 说明 | |---|---|---| | poll_vote | ?poll-vote.htm | 投票提交(POST) | | poll_end | ?poll-end-{poll_id}.htm | 提前结束(POST) | | poll_refresh | ?poll-refresh-{poll_id}.htm | 刷新卡片(GET) | | poll_admin | ?plugin-poll_admin.htm | 后台投票管理(仅管理员) |

文件结构

plugin/xnx_poll/
├── conf.json
├── install.php / uninstall.php / upgrade.php
├── setting.php + view/htm/setting.htm
├── model/PollService.php
├── route/
│   ├── poll.php            # 前台投票
│   └── admin_poll.php      # 后台管理
├── hook/
│   ├── model_inc_file.php
│   ├── model_route_table_end.php
│   ├── index_route_case_end.php
│   ├── admin_index_route_case_end.php
│   ├── thread_create_thread_end.php      # 发帖成功后创建投票
│   ├── model_thread_delete_end.php       # 删帖级联清理
│   ├── post_ref_thread_after.htm         # 桌面端发布页配置
│   ├── post_ref_thread_after_mobile.htm  # 手机端发布页配置
│   ├── post_js.htm                       # 加载 poll_post.js
│   ├── thread_message_after.htm          # 帖子正文下方投票卡
│   ├── footer_js_after.htm               # 加载 poll.js
│   ├── lang_zh_cn_bbs.php
│   ├── lang_zh_tw_bbs.php
│   └── lang_en_us_bbs.php
├── view/htm/
│   ├── poll_card.htm       # 投票卡模板(前台 + 路由共用)
│   ├── poll_admin.htm      # 后台管理页
│   └── setting.htm         # 后台设置页
├── static/js/
│   ├── poll_post.js        # 发布页交互
│   └── poll.js             # 投票卡交互(投票/结束/倒计时)
└── lang/
    ├── zh-cn.php
    ├── zh-tw.php
    └── en-us.php

数据库表

xnx_poll(主表)

| 字段 | 类型 | 说明 | |---|---|---| | id | int UNSIGNED PK | 投票 ID | | tid | int UNSIGNED | 帖子 ID | | uid | int UNSIGNED | 发起人 | | title | varchar(255) | 投票问题 | | type | tinyint | 0=单选 1=多选 | | max_choices | tinyint | 多选时最多可选数 | | duration_hours | int | 时长(0=永久) | | expire_at | int | 到期时间戳(0=永久) | | result_visibility | tinyint | 0=投票后 1=结束后 | | allow_change | tinyint | 是否允许修改 | | allow_guests | tinyint | 是否允许游客 | | allow_groups | varchar(255) | 允许的用户组(逗号分隔) | | status | tinyint | 0=进行中 1=手动结束 2=过期 3=已删 | | participants_count | int | 参与人数(原子计数) | | create_time | int | 创建时间 | | end_time | int | 结束时间 |

xnx_poll_option(选项表)

| 字段 | 类型 | 说明 | |---|---|---| | id | int UNSIGNED PK | 选项 ID | | poll_id | int | 投票 ID | | label | varchar(255) | 选项内容 | | sort | int | 排序 | | votes_count | int | 票数(原子计数) |

xnx_poll_vote(投票记录表)

| 字段 | 类型 | 说明 | |---|---|---| | id | int UNSIGNED PK | 记录 ID | | poll_id | int | 投票 ID | | option_id | int | 所选项 ID | | uid | int UNSIGNED | 投票人 | | vote_time | int | 投票时间 | | UNIQUE KEY | (poll_id, option_id, uid) | 同一用户对同一选项只能投一次 |

关键技术点

  • 版本号缓存:xnx_poll_poll_{id}_v{v} + xnx_poll_poll_v_{id} 双键机制避免长键哈希
  • 原子计数:UPDATE ... SET votes_count = votes_count + 1 无并发问题
  • CAS 防并发结束:UPDATE xnx_poll SET status=1 WHERE id=? AND status=0 防止重复结束
  • HTMX 兼容:所有 hook 用 if 包裹(不用 return;,hook 内 return 会从宿主函数退出)
  • htmx 4 事件名:冒号分隔格式(htmx:config:request 等)
  • 显示名:用 user_find_by_uids / user_read_cache 取 display_name,不用 db_find('user')
  • CSRF:所有 POST 表单走 CsrfService::input() + CsrfService::check()
  • 多语言:双写 lang/.php + hook/lang__bbs.php(前台实际读 hook)
  • 卡片样式:统一 x-card(无 border + 阴影 + dark mode border)

已知限制

  • 倒计时依赖 JS 在前台运行(无 cron 后台扫描过期)
  • 删除投票仅清理数据库与缓存,不通过消息中心通知参与者
  • 游客投票需开启 allow_guests 且未登录态可触发(按 IP 记录,故一行仅一票)

测试要点

  • 后台启用插件、配置用户组权限
  • 发帖创建单选/多选投票
  • 已登录用户参与投票(已投提示拦截)
  • 游客在开启游客权限时参与(按 IP 记录)
  • 倒计时到期自动刷新卡片
  • 作者提前结束、管理员后台强制结束
  • 后台列表筛选(tab 切换)、查看记录、CSV 导出、删除
  • 删主帖后级联清理(数据库 + 缓存)

版本 1.0.5 | 兼容 Xiuno 1.1+



版本:v1.0.5
适用版本:1.1
开发者:贰先生

请登录后查看此内容
最新回复
  • 㫘非人 站长连续签到7天2级用户 二级用户组

    自己在安装使用过程中,发现两个问题,并新增了一个功能,详见如下:

    修复两个已知设置问题

    • 一、修复后台设置页「允许发起的用户组」无法设置:setting.php 赋值变量 $grouplist 与模板 setting.htm 读取的 $poll_grouplist 不一致,导致用户组复选框永不渲染;同时 xiunox 核心不存在 group_list() 函数(实际为 group_list_cache()),一并修正。

    • 二、修复后台关闭「允许游客投票」「允许用户修改投票」后前台发帖表单仍显示对应选项:发帖表单现按全局设置条件渲染,全局关闭时不显示且强制为 0;服务端入库(新建 createPoll / 编辑 post_update_post_end)统一经 PollService::sanitizePollData() 强制过滤,防止绕过前端伪造提交;游客投票判定叠加全局开关校验(全局关闭时游客一律不可投)。

    新增[允许参与的用户组]

    • 一、后台设置新增「允许参与的用户组」多选(不选=全部用户组可参与),存储键 participate_groups。

    • 二、前台发帖表单「允许参与的用户组」列表与后台白名单联动:后台配置白名单后,发起时仅可从白名单内用户组中选择。

    • 三、服务端投票校验叠加全局参与组白名单(canVote),发起时选择的参与组与全局白名单自动求交集。

    • 四、新装默认设置同步 participate_groups;语言包 zh-cn / zh-tw / en-us 同步新增设置文案。

    若已安装之前版本,后台「插件 → 上传插件」上传该 zip 走升级流程即可,已有投票数据和设置会保留(不要卸载重装,重装会重置设置为默认值);升级后建议清一下浏览器缓存看前台效果。

    设置已经没有1.0.6的问题,还没有测试发帖,有兴趣的可以试试。另外,声明:这是我个人修复版,不是原作者的修复版。

    1.webp
    xnx_poll_fixed_v1.0.7.zip 53.5KB
    13天前
  • xiunox 一级用户组

    感谢分享修复版,两条补充建议:

    1. 过期态惰性兜底:读卡片或提交投票时判断 expire_at > 0 && expire_at <= time() && status == 0,直接 UPDATE xnx_poll SET status = 2 WHERE id = ? AND status = 0(CAS)。这样即使 JS 未执行、无 cron 扫描,状态也能正确落库,不只停留在前端切换。

    2. 游客按 IP 记票的隐患:正文提到一行一票,但公司/校园 NAT 出口会让多人互顶。若要细化,需给 xnx_poll_vote 加 ip 字段(uid=0 时使用),唯一键调整为 (poll_id, option_id, uid, ip) —— 属于表结构变更,得在 upgrade.php 里写迁移。

    另外建议把改动整理成 diff 或 PR 交回上游,双版本分叉后续升级容易冲突。【 AI 自动回复,仅供参考】

    12天前

请先登录后再回复 登录