[开发]SoSite — Alpine.js + htmx 重构完整总结(四合一)

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

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:20260806备份/最新的/重构总结_20260806.md。

SoSite — Alpine.js + htmx 重构完整总结(四合一)

整理日期:2026-08-06

执行:AtomCode(deepseek-v4-flash)

本文档整合自以下三份文档,覆盖 SoSite 前端交互重构的计划 → 第一轮实施(07-20)→ 第二轮补全(08-06)→ 第三轮单模板体系(08-06)全过程:

1. 《htmx+Alpine.js 重构计划(修正版).md》——改造方案与核心原则

2. 《会话总结-Alpine+htmx改造.md》——2026-07-20 第一轮实施记录

3. 《重构总结_20260806.md》——2026-08-06 剩余工作补全记录(本三合一文档在 08-06 版基础上扩写)

4. 《modern模板单体系改造方案.md》——2026-08-06 第三轮单模板体系 + CSS 清理(第 12 章)


目录

  1. 总体背景与目标
  2. 第一部分:重构计划(修正版)要点
  3. 第二部分:第一轮改造总结(2026-07-20)
  4. 第三部分:第二轮补全总结(2026-08-06)
  5. 综合踩坑记录(三轮合并去重)
  6. 综合改造对照表
  7. 全站功能验证(两轮汇总)
  8. 备份与回滚
  9. 核心原则(下次改造必读)
  10. 附录:Alpine.js / htmx 常用指令速查
  11. 后续建议
  12. 第四部分:单模板体系改造与 CSS 清理(2026-08-06 第三轮)
  13. 第五部分:安全复查与遗留修复(2026-08-06 第四轮)

1. 总体背景与目标

SoSite 是单人维护的轻量级 PHP 社区,存在大量零散 JS:内联 onclick/onsubmit/onchange

事件、多个独立 JS 文件(nav.js/vote.js/插件 script.js)、手写 XMLHttpRequest/fetch 样板。

改造总目标:引入 Alpine.js + htmx,消除零散 JS、统一交互模式与 AJAX 模式,

保持原有 CSS 体系不变,不引入任何构建工具链。

引入的库:

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


2. 第一部分:重构计划(修正版)要点

2.1 血的教训(计划阶段定下的红线)

  1. 不要引入 Tailwind CSS:SoSite 已有自洽的 mn-* 类名体系和 CSS 文件拆分,

Tailwind 收益为零、代价实实在在(两套体系混用、构建工具链)

  1. 不要做"机械替换":把 mn-flex-center 改成 flex items-center 没有解决任何问题
  2. 改造前先想清楚收益,每次改动问三个问题:
  • 用户能感知到吗?→ 不能就不改
  • 维护成本降低了吗?→ 没降低就不改
  • 后续扩展更方便吗?→ 没变好就不改

2.2 改造范围(只改什么 / 不改什么)

值得改的:

  • onclick="confirm(...)" → Alpine x-on:submit.prevent(~30 处)
  • onclick="toggleNav()" → Alpine x-on:click(4 处,消除 nav.js)
  • onclick="showTab()" → Alpine x-on:click + x-show(3 处)
  • onclick="toggleVote()" → htmx hx-post(8 处,消除 vote.js)
  • onchange="toggleAll()" → Alpine x-on:change(4 处)
  • 骰子/猜题 XMLHttpRequest → htmx hx-post(消除 AJAX 样板)
  • 签到表单提交 → htmx hx-post(消除页面刷新)
  • 模态框显隐 → Alpine x-show

不改的:

  • ❌ CSS 体系(mn-/c-/fs-* 类名、8 个旧 CSS 文件)
  • ❌ 动态 PHP 颜色(style="color:<?php echo $thread['color']; ?>" 等)
  • ❌ CKEditor(editor.js/editor.css 不修改)
  • ❌ FontAwesome 图标系统($this->icon() 不修改)
  • ❌ 不引入 Tailwind / 不构建 CSS 工具链

2.3 四阶段计划

阶段内容
第一阶段引入基础环境:复制静态文件到 assets/lib/,两个 layout 引入 Alpine + htmx,CSP 加 'unsafe-eval'
第二阶段Alpine.js 替换交互:confirm 弹窗、模态框、导航切换、Tab 切换、全选/取消
第三阶段htmx 替换 AJAX:骰子、猜题对决、每日签到、投票
第四阶段收尾清理:删除废弃 JS 文件(nav.js/vote.js/插件 script.js)、删除 ziyuan 目录


3. 第二部分:第一轮改造总结(2026-07-20)

3.1 改造目标与已遵守原则

引入 Alpine.js 和 htmx,替换原有零散 JS 交互和 AJAX 请求。不引入 Tailwind CSS,不动现有 CSS 体系。

已遵守的核心原则:

  1. ✅ 不改 CSS 类名(mn-、c-、fs-* 保留)
  2. ✅ 不改内联样式(动态 PHP 颜色保留)
  3. ✅ 不改编辑器(CKEditor 独立)
  4. ✅ 不改图标系统(FontAwesome 原样)
  5. ✅ 未引入 Tailwind CSS
  6. ✅ 未引入任何构建工具(npm/Webpack/Vite)
  7. ✅ 每次改完做 PHP 语法检查

3.2 第一轮文件变更清单(~30 个文件)

新增文件:

文件说明
assets/lib/alpine.min.jsAlpine.js v3.x 运行时(约 46KB)
assets/lib/htmx.min.jshtmx v2.x 运行时(约 51KB)

修改文件:

类别文件改造内容
布局(2)app/Views/layouts/main.php、assets/themes/modern/templates/layouts/main.php引入 Alpine + htmx,导航切换 Alpine 化,修复下拉菜单
核心(1)app/Core/Controller.phpCSP 添加 'unsafe-eval'(Alpine 必需)
Admin(~15)app/Views/admin/blog_categories.php、categories.php、database.php、groups.php、levels.php、maintenance.php、plugins.php、search_settings.php、tags.php、themes.php、threads.php、users.php、blog_manage.phpconfirm 弹窗 Alpine 化 + 全选/取消 Alpine 化
Adminapp/Controllers/Admin/ThreadController.php添加 needsEditor => true
Message(3)app/Views/message/inbox.php、sent.php、view.phpconfirm 删除 Alpine 化
Profile(1)assets/themes/modern/templates/profile/index.phpTab 切换 Alpine 化
Thread(2)app/Views/thread/show.php、assets/themes/modern/templates/thread/show.phpconfirm 删除 + 投票 htmx 化
API(1)app/Controllers/ApiController.php添加 htmx HTML 片段响应
Vote(1)app/Helpers/Vote.phprenderButtons 改用 htmx 属性
骰子(2)app/Views/plugins/dice/index.php、plugins/dice/DiceController.php骰子 AJAX htmx 化
猜题(3)app/Views/plugins/quiz_duel/create.php、detail.php、plugins/quiz_duel/QuizDuelController.php创建挑战/应战 htmx 化
签到(3)app/Views/plugins/daily_checkin/index.php + Modern 版、plugins/daily_checkin/CheckinController.php签到 htmx 化
插件后台模态框(6)announcements/content_review/friend_links 的 admin.php + Modern 版模态框 Alpine 化
插件后台 confirm(6)content_review/admin.php、notifications.php、friend_links/admin.php、qa/admin/index.php、tags.phpconfirm Alpine 化

删除文件:

文件原因
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 替代
assets/lib/s-state.js、assets/lib/s-fetch.js包装文件,已废弃

3.3 第一轮踩坑记录(6 条)

  1. CSP 安全策略:Alpine 完全不工作、控制台无报错 → CSP 头缺 'unsafe-eval' → Controller.php 的 script-src 中加入
  2. x-show 与 CSS display:none 冲突:元素一直不显示 → CSS 权重高于 Alpine 内联样式 → 改用 :class 在父容器绑定类名
  3. $refs 作用域问题:$refs.navLinks 为 undefined → $refs 只能在 x-data 作用域内访问 → 把 x-data 移到共同父元素,用 :class 替代
  4. 投票图标不显示:ApiController 返回 HTML 用 CSS 类方式(fa fa-thumbs-o-up),项目用 Unicode 字符实体(&#xf087;)→ 统一为 Unicode 字符实体
  5. 个人中心 Tab 全部显示:x-data 在 Tab 导航上、x-show 内容在同级不在作用域 → 把 x-data 移到共同父容器
  6. 批量替换导致 $el. 丢失:confirm 弹窗中 $el.submit() 变成 .submit() → 正则批量替换时 $el 被当作捕获组引用 → 逐个修复受影响文件(⚠️ 本轮发现 QA admin 与 friend_links admin 漏网,见第三部分)

3.4 第一轮遗留的剩余工作

  1. 测试全站功能
  2. Modern 主题论坛 category.php 检查
  3. 默认模板 profile/index.php 确认
  4. 其他插件前台(QA 等)未动
  5. 删除 ziyuan 目录

4. 第三部分:第二轮补全总结(2026-08-06)

4.1 本轮完成内容(7 项任务全部完成)

#任务结果
1备份要改动的文件✅ _backup_20260806/
2检查 Modern 主题论坛 category.php✅ 无需改造(纯展示页)
3检查并改造默认模板 profile/index.php✅ Tab 切换 Alpine 化
4检查并改造 QA 问答插件前台页面✅ 前台 htmx 化 + 后台遗留修复
5检查其他插件前台零散 JS✅ content_review / post_favorite / friend_links / announcements / red_packet
6全站功能测试(语法 + 页面访问)✅ 全部通过
7确认资源复制后删除 ziyuan 目录✅ 已删除(有备份)

4.2 默认模板 profile/index.php — Tab 切换 Alpine 化

改造前:页面底部 DOMContentLoaded 内联 <script>,通过 classList / style.display 手动切换 4 个 Tab。

改造后:


  • 菜单项:x-on:click.prevent + :class 动态高亮;内容区:x-show
  • 内联 <script> 整段删除;x-init 保留锚点直达(外部链接 #info 直达对应 Tab)

4.3 QA 问答插件 — 前台 htmx 化(重点)

改造前:3 类零散 JS —— qaVote()(XHR 投票)、qaAccept()(XHR 采纳 + confirm)、

qaAnswerForm/qaAskForm(fetch 提交回答/提问)。

改造后:

功能改造前改造后
问题/回答投票onclick="qaVote('q', id, 1)" + XHRhx-post="/qa/vote" + hx-vals='{"type":"q","id":ID,"vote":1,"csrf":"..."}' + hx-target="#vote_q_ID" + hx-swap="outerHTML"
采纳回答onclick="qaAccept(id)" + XHR + confirmhx-post="/qa/accept/ID" + hx-vals='{"csrf":"..."}' + hx-confirm
提交回答fetch + JSON表单 hx-post="/qa/question/ID/answer" + HX-Redirect 头
提问/编辑fetch + JSON表单 hx-post="/qa/ask" / /qa/edit/ID + HX-Redirect 头

QaController 新增 htmx 分支(vote/accept/answer/save/edit 五个方法):

  • vote():htmx 请求返回「新计数 span + script」,同时更新按钮 voted 高亮与内联颜色
  • accept():成功后返回 HX-Redirect 头,整页刷新(采纳改变排序与标记)
  • answer() / save() / edit():成功返回 HX-Redirect 头;校验失败返回

<div class="qa-form-error">错误信息</div> 到表单错误容器

  • 所有错误分支均补 HTTP_HX_REQUEST 判断,防止 htmx 场景返回 JSON 文本

默认模板 + Modern 主题共 4 个前台视图同步改造,内联 <script> 整段删除。

4.4 其他插件前台与后台遗留修复

content_review 通知页(前台):

  • 默认模板:删除残留死代码 toggleNoti()(已被 Alpine x-on:change 全选取代)
  • Modern 模板:onsubmit="return confirm(...)" → Alpine x-on:submit.prevent;onchange="toggleNoti(this)" → Alpine 全选;删除死代码

post_favorite 收藏页(前台):

  • 「取消收藏」表单(默认 + Modern):改为 hx-post="/favorite/toggle" + hx-target="closest .fav-item / .mn-row" + hx-swap="outerHTML"(取消后整行消失)
  • FavoriteController::toggle() 增加 htmx 分支:成功返回空片段
  • assets/script.js 精简:删除已被 htmx 取代的 fav-del-form fetch;保留帖子页收藏按钮注入(依赖动态 DOM 注入,暂不 htmx 化)

后台 admin 遗留修复(上一轮声称已改、实际有遗漏):

  • friend_links admin(默认 + Modern):修复 .submit() 缺 $el bug;onchange → x-on:change="$el.form.submit()";onsubmit → Alpine
  • announcements admin(默认 + Modern):删除按钮 confirm → 表单 x-on:submit.prevent
  • content_review admin(默认 + Modern):批量通过/批量删除 confirm → 表单级 x-on:submit.prevent(保留动态数量提示)

red_packet Modern 详情页:

  • 删除按钮上冗余的 onmouseover/onmouseout 内联事件(与 CSS :hover 规则重复)

4.5 本轮改动文件清单

类别文件改动
个人中心app/Views/profile/index.phpTab 切换 Alpine 化,删内联 script
QA 控制器plugins/qa/QaController.php5 方法加 htmx 分支 + 全部错误分支 htmx 响应
QA 前台app/Views/plugins/qa/question/show.php + Modern 版投票/采纳/回答表单 htmx 化,删 script
QA 前台app/Views/plugins/qa/question/ask.php + Modern 版提问/编辑表单 htmx 化,删 script
QA 后台app/Views/plugins/qa/admin/index.php、tags.php + Modern 版修 $el bug + onsubmit → Alpine
审核通知app/Views/plugins/content_review/notifications.php + Modern 版删死代码 / Alpine 化
收藏app/Views/plugins/post_favorite/index.php + Modern 版取消收藏 htmx 化
收藏plugins/post_favorite/FavoriteController.phptoggle 加 htmx 分支
收藏plugins/post_favorite/assets/script.js删除已 htmx 化的 fetch 部分
友链后台app/Views/plugins/friend_links/admin.php + Modern 版修 $el bug + Alpine 化
公告后台app/Views/plugins/announcements/admin.php + Modern 版删除 confirm Alpine 化
审核后台app/Views/plugins/content_review/admin.php + Modern 版批量 confirm Alpine 化
红包assets/themes/modern/templates/plugins/red_packet/detail.php删除冗余内联 hover 事件
资源目录ziyuan/确认资源已复制后整目录删除


5. 综合踩坑记录(三轮合并去重)

以下坑点来自:重构计划(9.1-9.7)、第一轮会话总结(6 条)、第二轮补全总结(11 条),已合并去重并按重要性排序。

5.1 $el. 丢失 bug(两轮反复出现)⚠️ 最高优先级

症状:confirm 表单写成

x-data x-on:submit.prevent="if(!confirm('确定删除?')) return; .submit()"

.submit() 前丢了 $el,删除按钮直接失效。

原因:用正则批量替换 onsubmit="return confirm(...)" 时,$el 被当作正则捕获组 $1 引用吞掉。

第一轮声称"逐个修复受影响文件",但 QA admin 和 friend_links admin 漏网,第二轮才发现。

解决:逐文件补齐 $el.submit()。

教训:正则批量替换 + 含 $ 的内容 = 灾难,替换后必须 grep '\.submit()' 全库复核 $el 完整性。

5.2 CSP 安全策略缺 'unsafe-eval'

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

原因:服务器 CSP 头缺少 'unsafe-eval',Alpine 的表达式解析依赖 eval。

解决:app/Core/Controller.php 的 script-src 中加入 'unsafe-eval'。

5.3 x-show 与 CSS display:none 冲突

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

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

解决:改用 :class 在父容器上绑定类名,由现有 CSS 机制控制显隐;不要直接 x-show 被 CSS 控制的元素。

5.4 htmx 错误提示容器不能预置 display:none

症状:回答/提问表单的错误容器 <div id="qaAnswerError" style="display:none;"></div>,

htmx 把错误片段 swap 进容器后依然不可见。

原因:htmx 替换的是容器内容(innerHTML),容器自身的 display:none 内联样式还在。

解决:去掉容器上的 style="display:none"(空容器本身不占高度,无需隐藏)。

教训:htmx 目标容器不要预置 display:none 内联样式。

5.5 $refs 作用域问题

症状:$refs.navLinks 为 undefined。

原因:$refs 只能在 x-data 作用域内访问子元素;按钮的 x-data 与 x-ref 在兄弟元素上。

解决:把 x-data 移到共同父元素上,用 :class 替代 x-ref。

5.6 个人中心 Tab 全部显示(作用域问题变体)

症状:三个 Tab 内容同时显示。

原因:x-data 放在 Tab 导航上,x-show 的 Tab 内容在同级位置,不在作用域内。

解决:把 x-data 移到共同父容器上。

5.7 投票图标不显示 / 不一致

症状:htmx 替换后投票图标消失(第一轮);Vote::renderButtons() 用 SVG 而视图用 FontAwesome,样式不一致(计划阶段)。

原因:ApiController 返回的 HTML 用了 CSS 类方式(fa fa-thumbs-o-up),项目实际用 Unicode 字符实体(&#xf087;)。

解决:统一使用 &#xf087; / &#xf088; Unicode 字符实体。

5.8 投票二次点击无效(htmx 缺 csrf)

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

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

解决:在 hx-vals 中加入 csrf 参数。

教训:htmx 按钮类请求必须带 csrf(表单类可用隐藏域自动带上)。

5.9 投票按钮高亮:默认模板用 class,Modern 模板用内联颜色

症状:默认模板按钮用 class="... voted" 表达高亮;Modern 模板用

style="color:var(--mn-primary)" 内联颜色。htmx 返回的 script 若只 toggle class,Modern 高亮不生效。

解决:vote() 返回的 script 里两者都处理:

b.classList.toggle("voted", on);            // 默认模板
if (b.style.color) {                        // Modern 模板(有内联颜色才走)
    b.style.color = on ? (dir===1 ? "var(--mn-primary)" : "var(--mn-error)") : "var(--mn-text-muted)";
}

5.10 Modern 主题存在"引用了未定义函数"的隐患

症状:Modern 主题 QA 详情页 script 调用 qaVote() / qaAccept(),但函数只在默认模板内联 script 中定义,点击投票 ReferenceError。

解决:本轮将投票/采纳全部改为 htmx 属性,函数彻底消失,根治。

教训:跨模板复制 JS 逻辑时函数定义和调用必须一起迁移;htmx 化可从根上消灭此类问题。

5.11 Modern 主题部分页面上一轮根本没改

症状:grep 发现 Modern 版 content_review 通知页还是原始 onsubmit/onchange,而默认模板早已 Alpine 化。

原因:两套模板都要改,上一轮只改了默认模板,Modern 漏了。

解决:第二轮将 Modern 版按默认模板同等模式改造。

教训:两套主题(默认 + Modern)必须同步检查,用 grep 把 Modern 目录也扫一遍。

5.12 猜题按钮文字乱码

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

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

解决:移除 x-text,保留 :disabled。

5.13 骰子无效押注类型

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

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

解决:将控件移入 <form> 内。

5.14 默认模板变宽

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

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

解决:在 tailwind.config.js 中设置 container: false(如引入了 Tailwind)。

说明:SoSite 未引入 Tailwind,此坑为规避性记录。

5.15 同一段代码出现两次导致编辑工具无法定位

症状:edit_file 报 "old_string appears 2 times" —— QaController 里 answer() 和 edit()

都以 $this->json(['success' => true, 'url' => '/qa/question/' . $id]); 结尾。

解决:用缩进区分(answer 在方法体顶层 8 空格,edit 在 if 块内 12 空格),携带方法尾部上下文精确定位。

5.16 Windows / phpstudy 环境坑

坑症状解决
php 不在 PATHphp -l 报"不是内部或外部命令"用完整路径 D:\phpstudy_pro\Extensions\php\php8.2.9nts\php.exe -l <file>
cmd 变量展开set VAR=... & %VAR% 报"不是内部或外部命令"cmd 一行内 %VAR% 在赋值前展开;直接写完整路径,不用变量
内置服务器启动超时start /b php -S 让 bash 工具等到超时;PowerShell Start-Process 报"不支持输入重定向"放弃自建服务器,直接用 phpstudy 已在 80 端口运行的 nginx 做 curl 测试

5.17 ziyuan 与 assets/lib 文件"不一致"的误判

症状:fc /b 显示 ziyuan/alpine/alpine.min.js 与 assets/lib/alpine.min.js 不同,差异字节达 44452 个。

原因:assets/lib 版文件头多了一段 SoSite 项目注释(约 150 字节),加上行尾符(CRLF/LF)差异,

按字节逐位比较几乎处处不同。

解决:用 PowerShell 规范化换行后验证 ziyuan 内容完整包含于 assets/lib 版($lib.Contains($ziyuan) = True),

确认 lib 版只是"加注释版"、功能一致,且全库无代码引用 ziyuan 路径,才安全删除。

教训:文件"看起来不一样"不等于内容不一样,比较前先考虑注释头、行尾符,用"包含关系"验证而非逐字节比较。

5.18 错误日志中疑似 bug 的排查

症状:全站测试时 protected/error.log 出现 QuizDuel accept error: SQLSTATE[HY093]。

排查:该日志是 7-19(改造前一天)的历史记录;当时 SQL 用 :now 占位符而绑定参数名不匹配,

当前代码已改为 :accepted_at / :completed_at 且绑定正确 → 已修复,非本轮引入。


6. 综合改造对照表

6.1 内联事件替换对照表

当前模式替换为计划数量实际完成
onsubmit="return confirm('...')"x-data 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 处✅ 两轮(含默认 profile)
onclick="toggleVote(this, 1)"hx-post="/api/vote" hx-vals='{"vote":"1"}'8 处✅ 第一轮
onchange="toggleAll(this)"x-on:change="document.querySelectorAll(...).forEach(...)"4 处✅ 两轮
onchange="this.form.submit()"x-on:change="$el.form.submit()"—✅ 第二轮 friend_links
onmouseover/onmouseout(冗余)删除(CSS :hover 已有)—✅ 第二轮 red_packet

6.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 片段✅ 第一轮
QA 投票onclick=qaVote() + XHRhx-post="/qa/vote" + HTML 片段✅ 第二轮
QA 采纳onclick=qaAccept() + XHRhx-post="/qa/accept/ID" + hx-confirm✅ 第二轮
QA 回答/提问fetch + JSON表单 hx-post + HX-Redirect✅ 第二轮
收藏取消fetch + JSON(script.js)hx-post="/favorite/toggle" + outerHTML✅ 第二轮


7. 全站功能验证(两轮汇总)

7.1 PHP 语法检查

  • 第一轮:改动文件全部通过 php -l
  • 第二轮:28 个改动文件全部 No syntax errors detected

(含 profile、QA 前台/后台、content_review、post_favorite、friend_links、announcements、red_packet)

7.2 页面访问测试(第二轮,通过 phpstudy nginx + curl)

结果路由
✅ HTTP 200/ /forum /login /qa /blog /tags /qa/tags /theme-settings /register /forgot-password /blog/category/1
✅ HTTP 302(需登录,正常跳转)/redpacket /favorites /profile /search /qa/ask /checkin /dice /admin /duel /duel/create /message /links/apply
说明/quiz_duel 404 是路由路径问题——猜题实际路由为 /duel(非 bug)

7.3 错误日志

  • protected/error.log 无 08-04 ~ 08-06 的新错误
  • 仅存的 QuizDuel accept error 为 7-19 历史记录,对应 SQL 参数问题当前代码已修复

7.4 资源完整性确认(删除 ziyuan 前)

  • alpine.min.js / htmx.min.js 的 ziyuan 版完整包含于 assets/lib/ 版(仅多头部注释 + 行尾符差异)
  • app/、assets/ 全库无任何 ziyuan 路径引用

8. 备份与回滚

  • 第一轮与第二轮改动前均做备份,最新完整备份位于 _backup_20260806/

(含被删除的 ziyuan/ 目录、profile、全部插件视图/控制器、thread 页、ThreadController 等)

  • 回滚方式:将备份目录中的文件按原路径覆盖回去即可

9. 核心原则(下次改造必读)

  1. 只解决实际问题,不为技术而技术
  2. 不改 CSS 类名——mn-* 体系是自洽的,不需要换
  3. 不改内联样式——动态 PHP 颜色保留
  4. 不改编辑器——CKEditor 保持独立
  5. 不改图标系统——FontAwesome 保持原样
  6. 每次改完跑一遍全站——确认功能正常再继续
  7. 不要引入任何构建工具链——SoSite 是 PHP 项目,不需要 npm/Webpack/Vite
  8. 如果某个改动只是"换了个写法"但没有实际收益,那就不要做
  9. 两套主题(默认 + Modern)必须同步检查——上一轮遗漏几乎全部集中在 Modern
  10. 含 $el 的代码绝不能用正则批量替换——替换后必须 grep 复核
  11. htmx 按钮请求带 csrf(hx-vals),表单用隐藏域;目标容器不要预置 display:none
  12. 验证三层都要过:php -l + curl 页面 + 查 error.log

10. 附录:Alpine.js / htmx 常用指令速查

10.1 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="if(!confirm('确定?')) return; $el.submit()"
:class动态 class:class="{ 'active': tab === 'info' }"
x-ref引用元素$refs.myEl
x-on:click.outside点击外部x-on:click.outside="open = false"
x-on:keydown.escape.window键盘事件x-on:keydown.escape.window="open = false"
x-init初始化逻辑x-init="if (location.hash) tab = location.hash.substring(1)"
$el当前元素引用$el.submit()

10.2 htmx 常用属性

属性用途示例
hx-getGET 请求hx-get="/api/data"
hx-postPOST 请求hx-post="/api/submit"
hx-target目标元素hx-target="#result" / hx-target="closest .fav-item"
hx-swap替换方式hx-swap="innerHTML" / hx-swap="outerHTML"
hx-trigger触发方式hx-trigger="click"
hx-vals附加数据hx-vals='{"key":"value","csrf":"..."}'
hx-confirm确认弹窗hx-confirm="确定?"
hx-indicator加载指示器hx-indicator="#spinner"
响应头 HX-Redirect服务端重定向header('HX-Redirect: /qa/question/5')
判断条件服务端识别 htmx 请求!empty($_SERVER['HTTP_HX_REQUEST'])


11. 后续建议

  1. post_favorite 帖子页收藏按钮:目前仍依赖 script.js 动态注入 + fetch。

若要彻底 htmx 化,需要 ThreadController 传入 isFavorited、thread 页服务端渲染按钮、

toggle 返回按钮 HTML —— 属于较大改动,建议单独排期

  1. QA 前台问答列表/标签页:已检查为纯展示,无需改;若后续加"热门/未回答" Tab 交互,直接用 Alpine
  2. 增加轻量 JS 规范 lint:扫描 onclick= / onsubmit= / XMLHttpRequest / fetch( / <script> 残留,

纳入日常自查脚本,防止回潮

  1. editAnnouncement() / editLink() 等模态框填充函数:已与 Alpine 模态框配合

(Alpine.$data(...).open=true),属于模态框配套逻辑,保留合理;若追求极致统一可后续改为 x-data 直接承载

  1. 删除旧 layout 中废弃 JS 引用:确认 vote.js / nav.js 引用已从两个 layout 移除

12. 第四部分:单模板体系改造与 CSS 清理(2026-08-06 第三轮)

在第二轮补全之后,进一步将 SoSite 收敛为 Modern 单模板体系:

① 删除默认模板体系 ② 修复主题切换失效 ③ 整合 assets/themes/modern 目录 ④ 清理 CSS 冗余。

背景方案详见根目录《modern模板单体系改造方案.md》。

12.1 方案 A:修复主题切换失效

症状:前台/后台切换 modern-* 配色变体无效,页面无任何样式变化。

根因(已定位):app/Views/layouts/main.php 中变体 CSS 的 file_exists 相对路径算错:

// ❌ 错误:__DIR__ = app/Views/layouts,上 4 级跑出项目外
if (file_exists(__DIR__ . '/../../../../' . $variantFile)) {
// ✅ 修复:上 3 级到 WWW 根,再进 assets
if (file_exists(__DIR__ . '/../../../assets/' . $variantFile)) {

该行代码是从 assets/themes/modern/templates/layouts/ 复制到 app/Views/layouts/ 时带过来的,

目录深度变化后相对路径失效,导致 modern-*.css 永远 file_exists=false,变体永不加载。

修复:改对相对路径 + 清模板缓存 + opcache_reset,验证 ?preview_theme=modern-azure/night/jade/crimson/purple 全部正常加载。

12.2 方案 B:整合 assets/themes/modern 目录并删除默认模板

步骤内容
① 复制模板assets/themes/modern/templates/ 全部复制到 app/Views/(覆盖同名默认视图,含 _components//emails/),分目录复制、分步验证
② 删除默认视图删除 app/Views/ 下默认模板视图,保留:admin/(后台)、`plugins/dice
③ 删除经典配色assets/css/themes/ 下 8 个经典配色(baby_blue/forum_legacy/slate_pro 等)删除,**保留 5 个 modern-* 变体**
④ 收口主题机制TemplateCompiler 移除主题检测/detectTheme() 死代码;Theme.php::getAvailable() 只保留 modern 系 + 硬编码 modern 元数据;后台 upload/delete 主题禁用;admin/themes.php 移除上传/删除区块
⑤ 配置与数据config.php 定义 DEFAULT_THEME='modern';SQL 统一 users.theme='modern';顺手修复 default_theme 拼写错误 'morden'→'modern'
⑥ 删除源目录assets/themes/modern/templates/ 整目录删除(验证后),assets/themes/modern/ 仅留 default.css/style.css
⑦ 整合 CSSdefault.css 的 --mn- 变量与 style.css 的 --primary- 旧变量全部并入 modern.css 末尾;布局移除 themes/modern/default.css 两处引用;删除无引用的 theme_settings.css;最后删除 assets/themes/modern/ 整个目录

⚠️ 关键发现:modern.css 本身不含 :root 变量定义(变量全靠 default.css/style.css 提供),

且基础 CSS(style/layout/forum/pages/blog/admin)大量引用 --primary-color/--bg-light 等旧变量——

整合时必须把两套变量都并入 modern.css,否则前台后台大面积丢样式。

12.3 CSS 冗余清理(assets/css 与 modern.css)

清理项依据
forum.css 的 .post-text-content pre/code/pre code/table/th/td/th/tr 整套modern.css 1044-1319 已有完整 Modern 覆盖
forum.css 移动端 .post-text-content h2/h3/h4与 modern.css 1416-1424 完全重复(此前两处都加了)
style.css 的 :root 旧变量块(--primary-color:#0066cc 等 14 个)被 modern.css 末尾整合的 #667eea 系变量覆盖,属残留
theme_settings.css全库无引用

保留:.post-text-content 基础规则(line-height/font-size,modern 无对应)、ul/ol/li、

components.css 的 .form-label(含 modern 未覆盖的 margin-bottom:8px)等有独立作用的规则。

12.4 本轮新增踩坑记录

  1. phpstudy OPcache 缓存旧文件:php-cgi 常驻进程开启 OPcache 且 validate_timestamps=1,

但改 PHP 文件后页面仍执行旧版(本次表现为"布局已是 Modern 版,页面仍输出默认 CSS")。

解决:临时脚本 opcache_reset() + 清模板缓存。改完 PHP 文件务必清 OPcache 再验证。

  1. Settings 缓存与数据库不一致:protected/settings_cache.php 缓存了 default_theme=modern-azure,

数据库已改 modern 但 Settings::get() 走缓存文件优先 → 页面仍加载变体 CSS。

解决:Settings::buildCache() 重建缓存(临时脚本),确认 cached default_theme = modern。

  1. 相对路径复制后失效:从 assets/themes/modern/templates/layouts/ 复制到 app/Views/layouts/ 后,

布局内 __DIR__ 深度变化,../../../../ 等相对路径全部需要重算。

  1. "默认模板"≠基础 CSS:style/layout/forum/pages/components/blog/home 这 7 个 CSS 是 Modern 布局也在加载的底座,

不是默认模板专属,不能删;真正可删的只有经典配色与无引用文件。

  1. 4 个插件视图不可删:dice/icon_generator/quiz_duel/user_profile 没有 modern 版,

删了前台直接 404——它们是默认视图但必须保留。

12.5 本轮验证结果

项目结果
全站页面✅ HOME/FORUM/THREAD/BLOG/QA/TAGS/LOGIN 200,ADMIN 302
主题切换✅ 5 个 modern-* 变体预览全部正常加载变体 CSS
默认首页 CSS✅ 仅基础 + modern.css,不含变体
CSS 括号配对✅ forum 238/238、style 16/16、modern 387/387
error.log✅ 无新错误


13. 第五部分:安全复查与遗留修复(2026-08-06)

依据本总结对重构后的代码做全量安全/逻辑复查(CSRF、XSS、CSP、控制器逻辑、主题机制、单模板体系残留),

并修复复查中发现的问题。复查结论:无高危漏洞与逻辑错误,修复 3 项遗留问题。

13.1 复查结论(安全项全部通过)

检查项结果
CSRF 防护✅ 所有 htmx POST 均带 csrf(表单用隐藏域、按钮用 hx-vals);ApiController/QaController 对 type/id/vote 均做白名单或强转校验
XSS 转义✅ htmx 返回片段全部转义或白名单:Dice/QuizDuel 错误提示 htmlspecialchars(ENT_QUOTES);QA 投票片段 $type 白名单(q/a)、$targetId/$vote 强制 int;骰子结果文本为服务端硬编码
CSP/安全头✅ view() 带完整 6 项安全头;'unsafe-eval'(Alpine 必需)与 'unsafe-inline'(htmx 片段 <script> 必需)属功能性权衡
控制器逻辑✅ QaController 五方法 htmx 分支完整(错误分支均有 HTTP_HX_REQUEST 判断);猜题扣分/发奖失败均有退款;签到有重复检查;收藏 toggle 返回空片段
主题机制✅ 兜底链(预览→用户→cookie→default_theme→DEFAULT_THEME)完整;网上空 default_theme 可安全兜底;布局变体加载带 file_exists 保护
单模板体系✅ settings 缓存 default_theme=modern 正确;布局无 themes/modern 残留引用;assets/themes 已空、css/themes 仅 5 个 modern-* 变体;ThemeController upload/delete 已禁用

13.2 发现并修复的问题

#问题位置修复
1Vote::renderButtons() 死代码中 hx-vals 硬编码 "csrf":""(方法已无调用者,未触发,但留有隐患)app/Helpers/Vote.php整段删除该方法(含注释)
2原生 onsubmit="return confirm(...)" 残留 3 处,未统一为 Alpine 模式blog/detail.php、message/inbox.php、message/sent.php统一改为 x-data x-on:submit.prevent="if(!confirm(...)) return; $el.submit()"
3themes.php 消息数组残留 9 条上传/删除文案(功能已禁用)app/Views/admin/themes.php清理为仅保留 default/css/invalid;ThemeController::upload() 重定向统一为 error=invalid

复查中的观察项(未修复、无需处理):viewRaw() 无 CSP 头(仅签到 htmx 片段使用,片段内脚本受宿主页面 CSP 约束)。

13.3 本轮验证结果

项目结果
php -l 6 个改动文件✅ 全部无语法错误
$el 提交扫描(全库 Alpine 表达式)✅ bad=0 / ok=34
原生 onsubmit 残留✅ app/ 与 plugins/ 已清零(仅文档提及)
renderButtons 引用✅ 代码零引用


文档结束 — 覆盖 计划 → 第一轮(07-20)→ 第二轮(08-06 补全)→ 第三轮(单模板体系 + CSS 清理)→ 第四轮(安全复查 + 遗留修复),备份位于 _backup_20260806/

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 2 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录