特别注意 
本主题不推荐在生产环境中使用。
前言与简介 
经过对Xiuno BBS 4核心机制的深度研究,我成功实现了HTMX 的深度集成。以清爽蓝色主题 为基底,在不破坏原生逻辑的前提下,为纯原装Xiuno BBS带来现代化近乎SPA的体验。  
这意味着什么? 
意味着更流畅、更现代的用户体验,同时保持 Xiuno 一贯的简洁和高效。
技术实现亮点 
非侵入式改造 
零核心代码修改,完全通过Hook机制实现 
原生功能100%覆盖(目前仅限于此,插件兼容性保留) 
渐进式增强:未改动验证码/附件上传等复杂交互 
 
核心交互升级 
无刷新全局导航 
所有GET请求保持原URL结构,但实现内容动态替换 
增加 `updatePagination` 事件解决分页器兼容问题 
新增 `setActive` 函数替代jQuery依赖 
 
纯HTMX表单处理 
主题帖回帖:彻底脱离jQuery,通过 updatePostCount 事件同步计数,与HTMX本身的功能配合提供流畅体验 
密码交互:属性自动生成加密字段,告别jQuery-MD5   
管理功能:基于Pico CSS重构模态框(这部分不依赖Bootstrap功能,但暂时依赖Bootstrap样式),删除回帖时触发 removePost 等事件实现原子化更新(普通管理操作则全页刷新确保没有奇怪的副作用) 
 
改造后的 message.htm 成为事件中枢 
大多数操作的(文字)反馈都会通过模态框和吐司框呈现。
新增事件:
showModalSimple:显示模态框 
closeModal:关闭模态框 
showToast:右上角轻量吐司框通知 
 
智能路由识别 
通过全局变量精准控制渲染逻辑
/**
 * @var bool 是HTMX发起的请求吗?
 */
$IS_HTMX = isset($_SERVER['HTTP_HX_REQUEST']) || (isset($_SERVER['HTTP_HX_REQUEST']) && $_SERVER['HTTP_HX_REQUEST'] == 'true');
/**
 * @var bool 是通过翻页器访问的吗?
 */
$IS_IN_PAGINATION = isset($_REQUEST['IS_IN_PAGINATION']) && boolval($_REQUEST['IS_IN_PAGINATION']);.
技术选型思考 
HTMX的声明式特性与Xiuno的AOP架构完美契合 :  
后端仅需微调输出HTML片段(通过现有Hook点)   
前端交互复杂度降低60%+(移除大量jQuery回调)   
传输体积减少约40%(局部更新优势)   
 
已知注意事项 
插件页面(例如通知、排行榜、积分等页面)可能出现双重嵌套(HTMX正常工作的副作用)  
你看到的,是未来 这不是一个简单的“去 jQuery”项目,而是一次对传统论坛架构的重新思考:
我们不需要抛弃成熟的后端系统,也能拥有现代交互体验。  
HTMX 让服务端渲染应用焕发新生,而 Xiuno BBS 凭借其清晰的 Hook 机制和插件生态,完美适配这一理念。
更新记录 
1.7(2025年10月16日) 
新增:showModalSimple事件新增参数“type”,用于指定该弹窗的样式(可能会决定边框或标题颜色)和弹窗声音 
新增:在帖子页面中的代码高亮功能 
改进:现在几乎所有的图片都增加了alt loading=lazy decoding=async属性,增强图片可访问性和加载性能 
改进:现在几乎所有使用图标字体的地方(如icon-user这样的)都增加了aria-hidden属性,符合最佳实践 
改进:主页和帖子列表获取“从访问该页面到现在为止,有没有新的帖子”功能,增加了缓存以提升性能 
修复:跨页面点击帖子列表的管理按钮可能不会弹出对应操作弹窗,这个问题被修复了 
修复:登录、注册、更改密码等有关密码输入框的页面,里的“自动MD5化”现在应该正常工作了 
修复:登录、注册等页面的密码输入框会提示“加密后长度有问题”的问题已被修复 
 
插件兼容 
tt_ranklist 适配完成 
sg_invite_vip 适配完成 
zz_iqismart_invite 适配完成 
till_password_strength 适配完成 
z_kaomoji 直接可用 
fox_qrcode 适配完成(生成QR码的工序完全挪到前端) 
123c_top_nodel 直接可用 
xn_mod_enhance 适配完成
 
tt_credits 90%
√ 用户中心页面 
√ 购买帖子付费内容 
√ 付费记录查看 
设置帖子付费内容:已知问题:需要刷新页面再设置 
 
 
xn_user_recent_thread 适配完成 
z_top 直接可用 
l4c_top 直接可用 
meyan_direct 直接可用 
xn_friendlink 适配完成 
lee_not_email 直接可用 
huux_os_set 直接可用 
huux_os_html 直接可用,但某些JS内容需要参考接下来讲到的方式初始化JS 
rob_anonymous 直接可用 
 
其他 
如何让只在特定页面生效的JS正确的加载 
在使用 htmx 的单页应用环境中,传统的页面特定 JavaScript 加载方式需要调整。
适用于这些hook点的JS加载初始化:
forum_js.htm 
index_js.htm 
message_js.htm 
my_common_js.htm 
post_js.htm 
thread_js.htm 
user_create_js.htm 
user_login_js.htm 
user_index_js.htm 
 
以代码高亮为例。
传统写法: 
<script  id ="prettyprint_js"  src ="prettify.js" > </script > 
<script  id ="prettyprint_js_init" > 
    
    document .addEventListener('DOMContentLoaded' , function (document .querySelectorAll('.message pre:not(.prettyprint)' ).forEach(elm  =>'prettyprint' );});
        prettyPrint();
    });
    
    $(document ).ready(function  (".message pre:not(.prettyprint)" ).addClass("prettyprint" );
        prettyPrint();
    });
 </script > 
这种写法在大多数情况是好使的,因为你是直接加载这个页面,$(document).ready和DOMContentLoaded都能正确触发。
但HTMX加载出来的页面不会触发以上两种初始化所需的事件。
但好在HTMX提供了新的事件: 
htmx:afterOnLoad :与一般的document.onLoad类似,但是与HTMX相性最好,是document.onLoad的平替;缺点是,必须要有HTMX 
htmx:afterSwap :内容替换后立即触发 
htmx:afterSettle :推荐使用 ,HTMX 请求成功并替换 HTML 后触发,避免与正常访问重复执行 
 
所以我们只需这么做: 
<script  id ="prettyprint_js"  src ="prettify.js" > </script > 
<script  id ="prettyprint_js_init" > 
    
    function  applyPrettyPrint (document .querySelectorAll('.message pre:not(.prettyprint)' ).forEach(elm  =>'prettyprint' );});
        prettyPrint();
    }
    
    document .addEventListener('DOMContentLoaded' , function (document .addEventListener('htmx:afterSettle' , function (10 );
    });
 </script > 
这样你就做好了正常访问和HTMX访问的两手准备。
一些最佳实践分享 
1.6(2025年9月20日) 
新增:主页和论坛板块页会每60秒尝试获取“从访问该页面到现在为止,有没有新的帖子”,如果有的话,则会显示提示,点击后更新(相当于将第一页更新过来),类似IT之家那种
意思是,如果你在15:00访问,则下一次检查时间是15:01,如果在15:00到15:01之间有新发布的帖子,会进行通知 
如果不点击刷新按钮的话,会继续检查(下一次检查时间是15:02)在15:00到15:02之间有新发布的帖子,然后加起来通知…… 
 
 
新增:现在会每60秒获取一次是否有最新通知,并用Toast显示在页面中
意思是,如果你在15:00访问,则下一次检查时间是15:01,如果在15:00到15:01之间有新的未读消息,会进行通知 
用户看到 Toast 后,不会自动让其已读,但每次轮询都会尝试加载新的通知,旧的就不会再提醒了
如果全部标记为已读的话,就不会继续查数据库了,所以  高度建议用户在看到消息后按一下“全部设为已读”来降低服务端开支  
未来也许会有更好的方法 
 
 
 
 
改进:点击链接、按钮等时候会增加转圈动画,表示正在加载 
改进:搜索弹窗,输入框增加搜索按钮,点击后会进入常规搜索页面 
改进:(暂时)增加了粉彩色页面背景(更换\view\img\page-bg.png),因为你们还是觉得它“不好看” 
改进:填充了“网站规则”页面 
改进:吐司框增加音效(音量33%,不吵人) 
修复:帖子列表在无限滚动的时候可能会发生“在第一页就告知‘没有更多帖子’ ”的情况,现在会正确计算了 
 
同时再次提醒:这个“主题”只建议在测试环境中使用,它还没准备好。
因为增加了这些定期检查的功能,可能会让服务器开始变忙,因为这些功能需要查数据库,继而消耗大约0.0001到0.0004秒的时间。 这不是 bug,是 feature。轮询的代价,就是用“确定性的查询”换取“不确定的新内容”。
假设场景:
1000个在线用户 
每分钟:1000次查询 
每次查询:0.0003秒 
总耗时:0.3秒/分钟 
 
换算:
每小时:18秒 CPU时间 
每天:7.2分钟 CPU时间 
 
对于现代服务器来说,这个负载是完全可以接受的。
插件兼容 
till_news_ticker 直接可用 
till_login_banner 不兼容 
till_anticopy 直接可用 
till_user_deprecated 适配完成 
till_verified_member 适配完成 
 
准备新路由 
以下路由除非特别标注,只有HTMX
 
index,forum  完成 
请求方式:GET
参数:update_since,int类型,作为flag,通常为1
轮询增量更新帖子列表(IT之家风格)
用户首次访问页面时,服务器将当前时间的Unix时间戳记录在Session中,键名为htmx_last_threadlist_check_{fid},作为上次检查时间。
"用户主动刷新页面"   应该被视为"重置基准点"的行为,应当更新htmx_last_threadlist_check_{fid}为当前时间。只在访问index、forum的时候 更新htmx_last_threadlist_check_{fid},其他页面不要更新。 
 
前端通过定时轮询(如每30秒)向服务端发送update_since=1参数。
在轮询的时候不要更新 htmx_last_threadlist_check_{fid}服务端结合Session中保存的htmx_last_threadlist_check_{fid}与当前服务器时间$_SERVER['REQUEST_TIME'],构成一个时间区间,查询在此期间内新发布的帖子。 
例如:count from threads where create_time < {当前时间} and create_time > {session时间} 
 
 
根据查询结果,返回以下响应之一:
若有新帖:返回新增帖子的数量,前端在帖子列表顶部显示提示(如"发现2个新帖子,点击刷新"),引导用户手动刷新; 
若无新内容:返回空响应。http_response_code(204); 
 
 
当用户点击刷新按钮后,不仅要返回最新的帖子列表,也要把点击按钮的时间写入session的htmx_last_threadlist_check_{fid}中。 
在以上步骤结束后,前台还是会定期发送请求,继续比较新的时间区间。 
 
周而复始。
0 s   - 用户进入页面,htmx_last_threadlist_check_{fid} = 0 s
30 s  - 轮询:查找 0 s-30 s 的新帖子,找到2 个(共2 个),htmx_last_threadlist_check_{fid} = 0 
60 s  - 轮询:查找 0 s-60 s 的新帖子,找到1 个(共3 个),htmx_last_threadlist_check_{fid} = 0 
90 s  - 用户点击刷新按钮,htmx_last_threadlist_check_{fid} = 90 s(重置!)
120 s - 轮询:查找 90 s-120 s 的新帖子,找到0 个
my-notice-getnew  完成 
请求方式:GET
获取最新未读通知,轮询
在初次访问任何页面的时候在session里记录当前时间  htmx_last_noticelist_check 
update_since参数会由前端以一定间隔发送 
这样就获得了两个时间点,旧的和新的 
然后根据这两个时间点作为寻找通知的参数,获得在这段时间内的通知 
返回这短时间里的新的未读的  通知的信息本身   ,类别不限
如果没有新的通知,就返回空白信息http_response_code(204); 
 
 
发送  hx-trigger-after-settle: showToast
 
将新的时间点再设置成旧的时间点  htmx_last_noticelist_check,以备下次请求获取 
 
新的会记录到Session里的内容 
htmx_last_threadlist_check_{fid}htmx_last_noticelist_check 
新的事件 
showToastMulti用法和showToast高度相似,只是从单个Toast变成了多个Toast。
header('HX-Trigger-After-Swap: '  . json_encode([
    'showToastMulti'  => [
        [ 
            'type'     => 'success' ,
            'title'    => '通知1' ,
            'subtitle' => '' ,
            'content'  => '通知1内容' ,
            'delay'    => 5000 
        ],
        [ 
            'type'     => 'info' ,
            'title'    => '通知2' ,
            'subtitle' => '' ,
            'content'  => '通知2内容' ,
            'delay'    => 5000 
        ],
    ]
]));
杂项 
消息分类的定义 
0:所有通知 
1:公告(1.6版本已取消) 
2:评论 
3:系统(主题通知,关注通知等) 
7:收到私信 
39:任务 
66:举报/反馈 
99:其他 
150:点赞 
155:收藏 
156:at提及 
233:勋章 
 
1.5(2025年9月13日) 
插件兼容 
huux_notice 适配完成
 
haya_post_like 适配完成
主题贴点赞 √
 
主题贴取消点赞 √ 
回贴点赞 √
 
回贴取消点赞 √ 
TODO 潜在问题:翻页后需要重新绑定事件 
 
 
xn_tag 适配完成
板块页面的筛选
 
发帖页面的选择 与 编辑帖子页面的选择
经过重新设计,使用原生HTML 
为此增加了新的路由:forum-gettagcatelist-{fid}-{tid}.htm 
 
 
 
 
haya_favorite 50%
帖子页面收藏按钮 √
 
个人中心收藏帖子列表 √ 
个人中心收藏帖子列表删除按钮 √ 
 
 
 
准备新路由 
以下路由除非特别标注,只有HTMX
 
quickserch   完成
请求方式:GET
参数:kw,string类型,搜索关键词
forum-gettagcatelist-{fid}-{tid}   完成
请求方式:GET 参数:fid,int类型,要获取的板块ID
参数:tid,int类型,要获取的帖子ID,如果提供了,则会尝试获取该帖子选中的tag,并预先选中
获取对应论坛版块可以选择的tag列表 复选框
杂项 
点赞插件:API 在成功时只返回数字(最新的点赞次数),错误时返回 HX-Trigger(主题定义行为)
这个规律有意思。
 
1.4(2025年9月10日) 
新增:关于本站、网站规则、隐私声明、联系我们、所有论坛板块页,其中:
“网站规则、隐私声明、联系我们”可在“关于本站”进入 
“关于本站”在页脚进入 
“所有论坛板块页”在页眉导航栏进入 
 
 
 
新路由 
以下路由除非特别标注,只有HTMX
 
about_us  【完成】 请求方式:GET 参数:无
关于我们页面,HTMX只要页面内容本身
有意模仿移动 App 的“关于”页面风格,增强产品感与品牌调性。 
 
terms  【完成,占位】 请求方式:GET 参数:无
网站规则页面,HTMX只要页面内容本身
privacy  【完成,占位】 请求方式:GET 参数:无
隐私声明页面,HTMX只要页面内容本身
contact_us  【完成,占位】 请求方式:GET 参数:无
联系我们页面,HTMX只要页面内容本身
bbs  请求方式:GET 参数:无
展示所有论坛板块的页面,HTMX只要页面内容本身
插件兼容 
kan_rsdjs 直接可用 
sg_highlight 适配完成 
nciaer_copyright 直接可用 
mx_site_time 直接可用 
zls_sitemap 直接可用 
sg_group 直接可用 
wr_direct 不兼容 
xn_bgee_radom_thread 适配完成(注意,这个插件对性能有影响) 
wish_gowild 直接可用(外观可能不太好) 
xn_onlinetime 直接可用 
cf_life_countdown 直接可用 
hp_only_host 不考虑兼容,请用haya_post_info 
qt_check_token 直接可用(未测试,经过查看源代码应该是兼容的) 
cf_bugfix_emptycontent 直接可用 
cf_bugfix_tmpfiles 直接可用 
tianapi_dictum 直接可用 
ccreed_poison_word 直接可用 
xiuno_online_userlist 适配完成 
git_online_userlist 适配完成 
a8c5_fontlist 直接可用 
a8c5_fontstyle 直接可用 
a8c5_icolist 直接可用 
cf_bottomline 直接可用 
di_email_notice 直接可用 
tianapi_mingyan 直接可用 
haya_post_attach_lite 直接可用 
xn_ipaccess 直接可用 
 
1.3(2025年9月9日) 
修复:解决了当a标签同时具备data-modal-url和href的时候,两者都会触发(显示弹窗的同时会跳转到href所指的网址) 
修复:登录页面没有错误提醒,以及登录成功后不会跳转 
 
插件兼容 
huux_notice 适配完成(这个下次再说)
 
haya_post_like 25%(这个下次再说)
主题贴点赞 √ 
主题贴取消点赞 √ 
回贴点赞 
回贴取消点赞 
 
 
haya_post_info 适配完成
回复排序 √ 
回复默认排序 √ 
只看楼主 √ 
只看Ta √ 
回复显示楼主 √ 
@用户提醒 √ 
帖子列表显示浏览量 √ 
首页帖子列表显示板块 √ 
帖子列表分页 √ 
首页板块自定义 √ 
首页显示板块 √ 
今日发帖时间高亮 √ 
 
 
nt_rename 适配完成 
xn_screen_reader 直接可用 
zz_iqismart_newpost 直接可用 
zaesky_todaypost 直接可用 
xn_recount 直接可用 
xn_read_unread 不兼容 
xn_nav_more 不兼容 
xn_manual 无法兼容,作者都没写完 
xn_antispawn 可能不兼容,未测试 
xiuno_top_search 下一版再做,功能可能会集成 
till_thread_passcode 适配完成 
till_digitalclock 不兼容 
till_editviews 适配完成 
till_widget_monthlyProgress 直接可用 
till_xnlog_viewer 直接可用 
till_spoiler 直接可用 
till_post_author_badge 直接可用 
till_hot_thread 适配完成 
abs_shortcode 直接可用 
zz_iqismart_newpost 适配完成 
zz_top 直接可用 
xn_accesscount 直接可用 
xu_autosurl 直接可用 
haya_attach 直接可用 
haya_post_sort 【建议使用haya_post_info】 
qt_sensitive_word 直接可用 
 
1.2(2025年9月3日) 
【新增】:解决了覆盖其他hook的最大难点。本插件会覆盖其他插件的更多内容,目的主要是添加HTMX支持 修复:解决了process_pagination_to_htmx_trigger在特定情况下出错 
新增:用原生JS+HTMX实现了$('[data-modal-title]').each(function () {...}的弹窗显示属性
必须拥有data-modal-url属性,这会让HTMX往这里发送GET请求 
可选data-modal-title属性,替代原来的弹窗标题 
可选data-modal-arg属性,约等于hx-include属性值,但如果你没有在这个属性里写input或textarea或select的话,会自动加上来保证可用性和兼容性 
推荐使用data-modal-url直接作用于按钮等控件上来打开弹窗  
 
修改:用HTMX实现了$.ajax_modal的弹窗部分功能 
移除:大约是xiuno bbs 3.0时期的旧函数:xn_position、xn_menu、xn_dropdown、xn_toggle,因为完全没有插件使用 
移除:“点击响应整行”.tap功能 
新增:应当使用“fullpage”参数来获取全页(不含页眉页脚) 
 
插件兼容 
art_signature 适配完成 
ax_comment 直接可用 
ax_notice_sx 适配完成 
fox_prison 适配完成 
fox_search 适配完成 
xn_digest 适配完成 
haya_post_like 25% 
huux_notice 适配完成
独立消息页面 √
但是在当前的个人中心里是加载到右侧,因为其他的都确定了 
在导航栏里的是加载到页面内容 
消息分类切换 √ 
翻页 √ 
 
 
标记已读 √ 
点击a标签设置已读 √ 
删除单条 √ 
全部已读 √ 
删除本页信息 √ 
无限滚动 × 
 
 
 
其他 
$.fn.button的第一个参数的取值有
loading:将按钮变为不可点击,然后将按钮原来的文字设置到按钮的default-text属性里,然后将按钮的文字设置为按钮的data-loading-text属性值 
disabled:将按钮变为不可点击 
enable:将按钮变为可点击(去掉disabled状态) 
reset:将按钮变为可点击,然后检查是否有default-text属性,如果有,则将按钮的文字设置为default-text属性值 
其他字符串:直接将按钮的文字设置为该值 
 
message函数的第一个参数: 
-101 ,目前具体表现为显示toast 类型为info 
发掘到的未使用部分 
$.ajax_modal函数中提到了if (code == -101),这会让该函数解析JSON里的HTML\CSS\JS然后隐藏 modal-footer
class=xn-dropdown的元素会变成xiuno的dropdown,所有插件都没使用
用法:
<div  class ="xn-dropdown"  data-pos="5"  data-hidearrow="0" >
    <div  class ="dropdown-toggle"  >显示dropdown</div >
    <div  class ="dropdown-menu" >dropdown内容</div >
</div >
点击“显示dropdown”后,会根据data-pos的值定位“dropdown内容”的位置并显示出来,并且“dropdown内容”会附带一个小三角,这个小三角则是根据data-hidearrow的值决定是否显示,如果data-hidearrow是1或其他表示true的值则不会显示
data-pos的值:
         11         12      1 
        --------------------
     10  |                  | 2 
        |                  |
      9  |        0          | 3 
        |                  |
      8  |                  | 4 
        --------------------
         7         6        5 
class=xn-toggle的元素会变成xiuno的toggle开关,所有插件都没使用
用法:
<div  class ="xn-toggle"  data-target="#toggle_demo" >toggle按钮</div >
<div  id ="toggle_demo" >toggle内容</div >
点击“toggle按钮”后,data-target属性指定的“toggle内容”会滑动显示出来,然后再次点击屏幕任何位置,“toggle内容”会滑动隐藏
如何使用fullpage参数 
当链接是HTMX发起的时候,$IS_HTMX会优先响应,然后根据携带的额外参数来提供HTML片段,但有时候我们确实需要返回页面本体,但如果对该链接不使用HTMX的话就失去了用HTMX的意义。那么后端插件在写链接地址的时候这样写:
<a  href ="<?= url('my-notice');?>" > 消息</a > 
<a  href ="<?= url('my-notice',['fullpage'=>1]);?>"  hx-boost ="true"  hx-trigger ="click"  hx-target ="#body"  hx-swap ="innerHTML"  hx-push-url ="true" > 消息</a > 
然后在插件本身提供的页面里写:
<?php 
if ($IS_HTMX && boolval(param('fullpage' ,0 )) === false ){
    
    include  _include(APP_PATH.'plugin/huux_notice/view/htm/my_notice_list.inc.htm' );
} elseif (!$IS_HTMX || ($IS_HTMX && boolval(param('fullpage' ,0 )))) { ?> <?php   if (!$IS_HTMX) {include  _include(APP_PATH . 'view/htm/header.inc.htm' );} ?> <?php   include  _include(APP_PATH.'plugin/huux_notice/view/htm/my_notice_list.inc.htm' ); ?> <?php   if (!$IS_HTMX) {include  _include(APP_PATH . 'view/htm/footer.inc.htm' );} ?> <?php  } ?> 这样就可以兼顾HTMX的片段输出和全页输出。
1.1.0(2025年7月27日) 
新增:主页、论坛版块页面、个人中心、用户页面 里的“帖子列表”的无限滚动加载 
新增:帖子页面、个人中心、用户页面 里的“回帖列表”的无限滚动加载 
修改:process_pagination_to_htmx_trigger函数增肌了第二个参数,用来指示给前台的翻页器添加什么参数,为后续精细化控制打下基础。目前是写死的thread(代表threadlist,在前台会增加IS_IN_THREADLIST)和post(代表postlist,在前台会增加IS_IN_POSTLIST) 
新增:兼容了xiuno第一方插件“我的回帖” 
 
外观没有变化。有人说“ui不好看”,那确实。这个的外观和原装xiuno相差无几。但从一个熟悉的起点出发可以更容易理解。
1.0.0(2025年7月24日) 
初次发布。实现核心 HTMX 集成: 无刷新导航、HTMX 表单提交、事件中枢、新版Pico CSS 弹窗、替代JQuery的自动 MD5 等。
截图 
源码获取 
见附件。请尽可能下载版本号更高的版本,旧版无法获得更新记录中提到的新功能。
这只是一个开始。
当 HTMX 遇见 Xiuno BBS,
老树开新花,原味生未来。
 					
		
		最后于   14天前		
				被Tillreetree编辑
				
		,原因: 		
	
					
					
上传的附件: