[开发]SoSite — htmx + Alpine.js 重构计划(修正版)

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

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:20260806备份/htmx+Alpine.js 重构计划(修正版).md。

SoSite — htmx + Alpine.js 重构计划(修正版)

基于 2026-07-20 实际改造经验编写

核心教训:不要引入 Tailwind CSS,只做交互和 AJAX 的统一

目标:消除零散 JS、统一交互模式,保持原有 CSS 体系不变


目录

  1. 血的教训
  2. 改造范围(只改什么)
  3. 改造范围(不改什么)
  4. 第一阶段:引入基础环境
  5. 第二阶段:Alpine.js 替换交互
  6. 第三阶段:htmx 替换 AJAX
  7. 第四阶段:收尾清理
  8. 本次改造的实际文件变更清单
  9. 注意事项与坑
  10. 改造对照表

1. 血的教训

1.1 不要引入 Tailwind CSS ❌

原因:SoSite 是单人维护的轻量级 PHP 社区,已经有自洽的 mn-* 类名体系和合理的 CSS 文件拆分(layout.css/forum.css/blog.css 等)。Tailwind 的收益(团队协作统一规范)在这个场景下为零,但代价(两套体系混用、类名变长、构建工具链)是实实在在的。

结论:CSS 体系保持原样不动,所有 mn-、c-、fs-* 类名保留,不替换为 Tailwind。

1.2 不要做"机械替换" ❌

原因:把 mn-flex-center 改成 flex items-center 只是换了命名方式,没有解决任何实际问题。页面看起来一样,加载速度一样,但维护者需要同时记住两套体系。

结论:只改交互逻辑,不改样式类名。

1.3 改造前先想清楚收益

每次改动前问自己三个问题:

  1. 改了这个,用户能感知到吗? → 不能就不改
  2. 改了这个,维护成本降低了吗? → 没降低就不改
  3. 改了这个,后续扩展更方便吗? → 没变好就不改

2. 改造范围(只改什么)

2.1 值得改的 ✅

改造项原因收益
onclick="confirm(...)" → Alpine x-on:submit.prevent消除 30 处内联事件交互统一
onclick="toggleNav()" → Alpine x-on:click消除 nav.js 依赖少一个 JS 文件
onclick="showTab()" → Alpine x-on:click + x-show消除 Tab 切换的零散 JS交互统一
onclick="toggleVote()" → htmx hx-post消除 vote.js 依赖少一个 JS 文件
onclick="toggleAll()" → Alpine x-on:change消除全选/取消的零散 JS交互统一
骰子 XMLHttpRequest → htmx hx-post消除 AJAX 样板代码统一 AJAX 模式
猜题 XMLHttpRequest → htmx hx-post消除 AJAX 样板代码统一 AJAX 模式
签到表单提交 → htmx hx-post消除页面刷新局部刷新
模态框显隐 → Alpine x-show消除模态框的零散 JS交互统一

2.2 引入的库

库版本大小用途
alpine.min.jsv3.15.x~10KB gzip所有交互行为
htmx.min.jsv2.x~14KB gzip所有 AJAX 请求


3. 改造范围(不改什么)

3.1 不动 CSS 体系 ❌

  • 所有 mn-* 类名保留(mn-btn、mn-section、mn-flex-center 等)
  • 所有 c- / fs- 类名保留(c-999、fs-14 等)
  • 8 个旧 CSS 文件不动(style.css、layout.css、forum.css、pages.css、components.css、blog.css、home.css、modern.css、admin.css)
  • 不引入 Tailwind CSS,不生成 SoCSS,不构建任何 CSS 工具链

3.2 不动动态 PHP 颜色 ❌

  • style="color:<?php echo $thread['color']; ?>"
  • style="background:<?php echo $tcfg['color']; ?>"
  • 这些是 PHP 动态生成的,无法用类名替代,保持原样

3.3 不动编辑器 ❌

  • CKEditor 保持独立,不做任何改造
  • editor.js、editor.css 不修改

3.4 不动图标系统 ❌

  • FontAwesome 4.x 保持原样
  • $this->icon() 方法不修改

4. 第一阶段:引入基础环境

4.1 复制静态文件

assets/lib/
├── htmx.min.js          ← v2.x
└── alpine.min.js         ← v3.x

4.2 在 layouts 中引入

两个 layout 都要加:

  • assets/themes/modern/templates/layouts/main.php
  • app/Views/layouts/main.php

在 <head> 中添加:






4.3 CSP 安全策略(重要!)

Alpine.js 需要 unsafe-eval 才能运行,如果服务器有 CSP 头,需要在 app/Core/Controller.php 中添加:

header("Content-Security-Policy: script-src 'self' 'unsafe-eval' 'unsafe-inline'; ...");

如果不加,Alpine 的 x-data 和 x-on:click 会完全不工作,且控制台无报错。


5. 第二阶段:Alpine.js 替换交互

5.1 替换 confirm 确认弹窗(~30 处)

涉及文件:

  • app/Views/admin/blog_categories.php
  • app/Views/admin/categories.php
  • app/Views/admin/database.php
  • app/Views/admin/tags.php
  • app/Views/admin/threads.php
  • app/Views/admin/trashed.php
  • app/Views/admin/users.php
  • app/Views/message/inbox.php、sent.php、view.php
  • app/Views/plugins/content_review/notifications.php
  • app/Views/plugins/content_review/admin.php
  • app/Views/plugins/friend_links/admin.php
  • assets/themes/modern/templates/thread/show.php
  • app/Views/thread/show.php

改造前:


改造后:


5.2 替换模态框(~10 处)

涉及文件:

  • app/Views/plugins/announcements/admin.php + Modern 版
  • app/Views/plugins/friend_links/admin.php + Modern 版
  • app/Views/plugins/content_review/admin.php + Modern 版

改造前:


改造后:


5.3 替换导航切换(4 处)

涉及文件:

  • app/Views/layouts/main.php + Modern 版

改造前:

菜单

改造后:

菜单

5.4 替换 Tab 切换(3 处)

涉及文件:

  • assets/themes/modern/templates/profile/index.php
  • app/Views/profile/index.php

改造前:

基本资料
...

改造后:


5.5 替换全选/取消(4 处)

涉及文件:

  • app/Views/admin/blog_manage.php
  • app/Views/admin/threads.php
  • app/Views/plugins/content_review/admin.php + Modern 版

改造前:


改造后:


5.6 关于 x-show 与 CSS 冲突的注意事项

重要:如果现有 CSS 用 .父类.子类 { display: block; } 模式控制显隐(如下拉菜单),不能直接用 x-show,因为 Alpine 移除内联 display:none 后,CSS 样式表中的 display:none 权重更高,导致菜单永远不显示。

正确做法:用 :class 在父容器上绑定类,由现有 CSS 机制控制显隐。


切换
切换


6. 第三阶段:htmx 替换 AJAX

6.1 骰子游戏(1 处)

文件:plugins/dice/index.php(两个模板)

改造前:plugins/dice/assets/script.js 中有 XMLHttpRequest。

改造后:


    

注意:骰子动画的 CSS 类(rolling)保留,不可删除。

6.2 猜题对决(2 处)

文件:plugins/quiz_duel/create.php、detail.php

改造前:plugins/quiz_duel/assets/script.js 中有 XMLHttpRequest。

改造后:


    发布挑战

6.3 每日签到(1 处)

文件:plugins/daily_checkin/index.php(两个模板)

改造前:表单 POST 提交,页面完全刷新。

改造后:


    立即签到

6.4 投票(1 处,在 thread/show.php 中)

文件:assets/themes/modern/templates/thread/show.php、app/Views/thread/show.php

改造前:vote.js 中的 toggleVote() 函数。

改造后:


    <?php echo $this->icon('like', 14); ?>
    <?php echo (int)($tv['likes'] ?? 0); ?>

注意:后端接口需要返回 HTML 片段而非 JSON。对应的 Vote.php 或 ApiController.php 需要调整。


7. 第四阶段:收尾清理

7.1 删除废弃文件

# 已废弃的 JS 文件(功能已被 Alpine/htmx 替代)
assets/js/nav.js                    ← 导航切换已用 Alpine 替代
assets/js/vote.js                   ← 投票已用 htmx 替代

# 插件 JS 文件(功能已被 htmx 替代)
plugins/dice/assets/script.js       ← 骰子 AJAX 已用 htmx 替代
plugins/quiz_duel/assets/script.js  ← 猜题 AJAX 已用 htmx 替代

7.2 删除旧 layouts 中的 JS 引用

从两个 layout 的 main.php 中移除:



7.3 删除 ziyuan 目录(已用完的资源包)

确认 ziyuan/ 目录中的资源已复制到 assets/lib/ 后,可以删除。


8. 本次改造的实际文件变更清单

8.1 新增文件

文件说明
assets/lib/alpine.min.jsAlpine.js 运行时
assets/lib/htmx.min.jshtmx 运行时
assets/lib/so-state.jsAlpine.js 别名(可选)
assets/lib/so-fetch.jshtmx 别名(可选)

8.2 修改文件

Alpine.js 改造(~35 个文件):

文件改造内容
app/Views/layouts/main.php引入 Alpine + htmx,导航切换 Alpine 化
assets/themes/modern/templates/layouts/main.php同上
app/Views/admin/*.php(约 15 个)confirm 弹窗 Alpine 化
app/Views/plugins/*/admin.php(约 6 个)confirm 弹窗 + 模态框 Alpine 化
app/Views/message/inbox.php、sent.phpconfirm 删除 Alpine 化
app/Views/profile/index.phpTab 切换 Alpine 化
assets/themes/modern/templates/profile/index.phpTab 切换 Alpine 化

htmx 改造(~5 个文件):

文件改造内容
plugins/dice/index.php(两个模板)骰子 AJAX htmx 化
plugins/quiz_duel/create.php、detail.php猜题 AJAX htmx 化
plugins/daily_checkin/index.php(两个模板)签到 AJAX htmx 化
assets/themes/modern/templates/thread/show.php投票 htmx 化
app/Views/thread/show.php投票 htmx 化

8.3 删除文件

文件原因
assets/js/nav.js导航切换已用 Alpine 替代
assets/js/vote.js投票已用 htmx 替代
plugins/dice/assets/script.js骰子 AJAX 已用 htmx 替代
plugins/quiz_duel/assets/script.js猜题 AJAX 已用 htmx 替代


9. 注意事项与坑

9.1 CSP 安全策略(踩坑记录)

症状:Alpine 的下拉菜单、弹窗完全不工作,浏览器控制台无报错。

原因:服务器 CSP 头缺少 'unsafe-eval',Alpine 的 x-data 解析需要 eval。

修复:在 app/Core/Controller.php 中修改 CSP 头。

9.2 x-show 与 CSS display:none 冲突(踩坑记录)

症状:用 x-show 控制显隐的元素一直不显示。

原因:现有 CSS 中有 .dropdown-menu { display: none; },权重高于 Alpine 的内联 display。

修复:改用 :class 在容器上绑定类名,由 CSS 控制显隐。

9.3 投票按钮图标不一致

症状:Vote::renderButtons() 用 SVG 图标,视图用 FontAwesome 图标,样式不一致。

修复:统一为 FontAwesome 图标。

9.4 投票二次点击无效

症状:第一次点击投票正常,第二次点击无效。

原因:htmx 的 hx-vals 中缺少 CSRF token。

修复:在 hx-vals 中加入 csrf 参数。

9.5 猜题按钮文字乱码

症状:猜题按钮显示乱码。

原因:x-text 属性中包含 PHP 图标 HTML 代码,破坏了属性解析。

修复:移除 x-text,保留 :disabled。

9.6 骰子无效押注类型

症状:骰子投注时提示"无效押注类型"。

原因:bet_type 和 amount 控件在 <form> 外部。

修复:将控件移入 <form> 内。

9.7 默认模板变宽

症状:改造后默认模板的页面宽度变宽了。

原因:Tailwind 的 .container 类覆盖了原有布局的 max-width。

修复:在 tailwind.config.js 中设置 container: false(如果引入了 Tailwind)。


10. 改造对照表

10.1 内联事件替换对照表

当前模式替换为数量
onsubmit="return confirm('...')"x-on:submit.prevent="if(!confirm('...')) return; $el.submit()"30 处
onclick="closeModal()"x-on:click="open = false"10 处
onclick="toggleNav(event)"x-on:click="$refs.nav.classList.toggle('open')"4 处
onclick="showTab('info')"x-on:click="tab = 'info'"3 处
onclick="toggleVote(this, 1)"hx-post="/api/vote" hx-vals='{"vote":"1"}'8 处
onchange="toggleAll(this)"x-on:change="document.querySelectorAll(...).forEach(...)"4 处

10.2 AJAX 替换对照表

功能当前方式替换为
骰子投注XMLHttpRequest + JSONhx-post="/dice/play" + HTML 片段
创建挑战XMLHttpRequest + JSONhx-post="/duel/create" + HTML 片段
应战XMLHttpRequest + JSONhx-post="/duel/accept" + HTML 片段
签到表单 POST + 页面刷新hx-post="/checkin" + 局部刷新
投票XMLHttpRequest + JSONhx-post="/api/vote" + HTML 片段


附录 A:Alpine.js 常用指令速查

指令用途示例
x-data定义组件数据x-data="{ open: false }"
x-show显隐控制x-show="open"
x-text设置文本x-text="count"
x-on:click点击事件x-on:click="count++"
x-on:submit.prevent表单提交x-on:submit.prevent="handle()"
:class动态 class:class="{ 'active': tab === 'info' }"
x-ref引用元素$refs.myEl
x-on:click.outside点击外部x-on:click.outside="open = false"
x-on:keydown.escape键盘事件x-on:keydown.escape.window="open = false"

附录 B:htmx 常用属性速查

属性用途示例
hx-getGET 请求hx-get="/api/data"
hx-postPOST 请求hx-post="/api/submit"
hx-target目标元素hx-target="#result"
hx-swap替换方式hx-swap="innerHTML"
hx-trigger触发方式hx-trigger="click"
hx-vals附加数据hx-vals='{"key":"value"}'
hx-confirm确认弹窗hx-confirm="确定?"
hx-indicator加载指示器hx-indicator="#spinner"

附录 C:核心原则(下次改造必读)

  1. 只解决实际问题,不为技术而技术
  2. 不改 CSS 类名——你的 mn-* 体系是自洽的,不需要换
  3. 不改内联样式——动态 PHP 颜色保留
  4. 不改编辑器——CKEditor 保持独立
  5. 不改图标系统——FontAwesome 保持原样
  6. 每次改完跑一遍全站——确认功能正常再继续
  7. 不要引入任何构建工具链——SoSite 是 PHP 项目,不需要 npm/Webpack/Vite
  8. 如果某个改动只是"换了个写法"但没有实际收益,那就不要做
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 5 次 | 回覆 0 次

全部回覆 (0)

暫無回覆

請 登入