[layout_diy]页面组件 DIY热
- 页面组件 DIYID layout_diy版本 2.3.1插件制作者 cxk123售价 30 积分6008 行 / 376.1 KBHook / 路由 / 后台页2.3.0:**运行期结构自愈**(把新版文件覆盖进插件目录、没跑 install 时,老库缺表缺列不再让核心把整个插件自动停用 —— 组件目录页与后台 tab 先走「零查询内存快路径 → settings → 才 DDL」三道门,落后时按安装同一份定义幂等补齐 3 张表 / 4 枚索引 / 9 个增量列)。把站内已有的区块变成可以拖拽摆放的「页面组件」:五个槽位(首页顶 / 首页中 / 首页底 / 全站侧栏 / 页脚),三类组件来源(内置组件 / 别的插件通过私有 Hook diy_layout.components 自愿注册 / 把其它插件既有 entries 的 feature_links、profile_card、top_actions 自动包装成链接类组件),后台原生拖拽排序(不依赖任何前端框架),桌面端与移动端各存一套布局,支持多套布局与一键恢复默认。1.2.0 新增:帖子列表类组件六种样式(列表 / 两列紧凑 / 卡片封面 / 图文横排 / 杂志错落 / 信息流,颜色圆角字号跟随主题 CSS 变量)、封面来源三选一(主题首图零额外查询 / 附件图走 attachment.topic_images 契约整页一次批查 / 自定义链接)与比例圆角可配、没有封面的行自动回退成无图版式、四套一键套用的布局预设、版块列表组件(列表 / 卡片 + 描述与主题数回复数开关)、只对管理团队开放且默认关闭的自定义 HTML 组件(入库白名单过滤,脚本事件属性外链一律清掉)。三条硬约束:①查询预算 —— 每块声明 cost,保存时逐块累计、超上限拒绝保存并点名是哪一块贵,渲染时逐块实测查询数、超支的块降级为占位而不是拖垮整页;②失败隔离 —— 单块异常或空输出只留占位,输出缓冲 + try/catch 保证块内直接 echo 也破坏不了页面结构;③权限与隐私 fail-closed —— 块级可见范围(游客 / 登录 / 管理 / 版块)全部在服务端判定,未知值一律不渲染,组件能力与块范围取交集,管理向组件对游客永不渲染,匿名与屏蔽内容走既有遮蔽契约(anon_board.mask_rows)。1.3.0 新增:**组件自定义参数**(组件声明自己的参数白名单 —— 条数 3-20 / 显示哪些字段 / 开关等,后台逐块可视化编辑,未知键与越界值一律拒绝保存,参数带来的成本计入「有效声明成本」并与渲染期核对共用同一个函数;参数只改 SQL 的绑定值,不改查询条数)、**组件级缓存**(同一组件在同一请求内只渲染一次;跨请求可选短 TTL 缓存,缓存键含访客档与设备档,默认关闭时前台零额外查询,后台一键清空)、以及四处真缺陷修复(删除布局漏清快照 / 封面契约异常不再把整页变成错误页 / 站内链接白名单的反斜杠绕过 / 渲染账目跨钩子不同源)。2.0.0 新增:**全站任意页的槽位**(槽位从「首页三槽 + 全站侧栏 + 页脚」扩到五类页面 —— 首页 / 版块页 ?a=forum / 主题页 ?a=topic / 用户主页 ?a=user / 插件路由页(白名单挂载,默认空),每类页面**独立的开关与独立的预算**,预算 = 该类页面的槽位 + 全站槽位摊销,保存期按类校验、超了拒绝保存并点名是哪一块、哪一类页面;未知页面与未知槽位一律不注入、不报错、字节不变)、**组件级缓存的页面档**(缓存键增加页面档维度;后台按页面档看行数与命中、可按页面档清空;后台页面只读不写,清空后刷新后台不会又把页脚/侧栏的块写回来)、**渲染期参数白名单真正生效**(交给第三方渲染回调的块配置在渲染前统一过白名单:未知键丢掉、非法值退回默认、没提交过的键不凭空补),以及两处真缺陷修复(**站点标记自愈** —— 库里有启用中的布局而 `live` 标记为 0 时,前台按 `live` + `live_rows` 两个纯内存标记比对后就地重算,不再「库里有数据却永远显示空」;后台另给一个「按真实数据重算站点标记」的按钮)。2.2.0 新增:**块级条件显示**(每个块可带用户组 / 积分区间 / 注册天数 / 只给楼主 / 时段 / 星期,全部在服务端用请求级内存判定 —— 不发查询、不摊成本;未知键与非法值一律 fail-closed 不渲染,保存期与渲染期共用同一套归一化)、**布局 A/B 分桶**(同一套槽位挂 A/B 两份块清单,按登录用户 id / 游客浏览器 cookie 稳定哈希分桶,比例 0-100 可配、非法值按 0 = 关闭处理;分桶是纯内存判定,前台查询数不变;桶**不进缓存键** —— 缓存签名按块算,两桶天然分开,两桶里相同的块继续共用同一行缓存;B 桶某槽位为空时回落 A 桶;后台 A/B 面板可切桶编辑、一键把 A 桶复制到 B 桶,概览里点名「配了比例但 B 桶是空的」这种半开启状态)、**组件级缓存的定向失效**(缓存行带 component_id / slot,按「组件 / 槽位 / 页面档」任意组合定向删,空条件拒绝执行,不再「改一个组件的参数就把整站缓存全清」)、**保存前差异预览**(把编辑器里的块清单与当前已保存的那一份逐槽位比对,列出新增 / 删除 / 移动 / 参数变化 / 成本变化,纯服务端渲染,无 JS 路径照旧)。快照同时留档 A/B 两桶(回滚是完整回滚)。不启用任何布局时对前台零影响:不注入、不发查询、页面字节不变。收费插件安装说明购买后点击“授权码”获取一次性授权码;在后台 > 插件广场点击在线安装或更新,并填写该授权码。已购买用户更新免费:每次在线安装或更新前,重新点击“授权码”获取新的一次性授权码即可,无需再次购买;也可直接下载新版后用后台“插件上传”更新。尊重并感谢他人劳动,请勿传播代码。app/plugins/layout_diy/plugin.php插件预览6 张开发日志最新 10 条
此项目接受所有登录用户协助维护和提交更新。
主楼点赞与点评1我心所向投币1个4天前
版本 1.0.0 更新:
【页面组件 DIY 1.0.0 · 初次上架】一句话:把站内已有的区块变成可以拖拽摆放的「页面组件」——后台拖进五个槽位,前台就按槽位渲染。别的插件零改动即可被摆进来。
■ 五个槽位(1.0.0 的可 DIY 范围)
· 首页顶 / 首页中 / 首页底 / 全站侧栏 / 页脚。
· 首页三槽只在首页第 1 页生效(判定读核心模板上下文,不解析 URL);侧栏与页脚是全站的。
· 注入用核心稳定锚点,锚点不存在时不注入、不报错、不改一个字节 —— 宁可不摆,也不会插错位置。■ 组件来源 = 三条路,缺一不可
- 内置组件:站点统计 · 热门主题 · 最新主题 · 最新加入 · 版块导航(装完就有东西可拖,不依赖任何插件)。
- 私有 Hook
diy_layout.components:别的插件自愿注册自己的组件(id/名称/说明/声明成本/渲染回调/支持范围)。
宿主逐条白名单校验(id 形状与注册方前缀、render 可调用、范围合法),不合法的丢弃并在后台留痕;
注册回调本身抛异常也会被接住并记账,不会把整块面板拖下水。- 既有 entries 自动包装:
feature_links/profile_card/top_actions三类 entry 自动变成
「链接类组件」——今天站上装着的插件一行代码都不用改。链接取的是插件自己渲染的真实数据
(标签、图标、角标一致),不是猜 URL。■ 三条硬约束(本插件的核心)
① 查询预算:每个组件声明会用掉几条查询。后台保存时按槽位顺序逐块累计,超上限拒绝保存并点名是哪一块贵
(累计值、上限、块名、组件 id 一起给出),不做部分保存;渲染时再逐块实测查询数,
实测超过声明就把这一块降级成占位,而不是拖垮整页。声明是作者写的常量,实测才是事实。
② 失败隔离:单块异常 / 空输出只留注释占位(<!--layout-diy-degraded:组件:原因-->),
输出缓冲 + try/catch 保证块内直接 echo 也破坏不了页面结构;异常绝不冒泡到核心
(核心的规矩是「插件抛异常即整插件停用」,块级异常不能被升级成站点事故)。
③ 权限与隐私 fail-closed:块级可见范围(游客 / 登录 / 管理 / 版块)全部在服务端判定,
未知值一律不渲染;组件能力与块范围取交集(管理向组件即使被写成「游客 + 登录」也不给游客);
版块限制在其它页面一律不渲染;匿名与屏蔽内容走既有遮蔽契约anon_board.mask_rows
(整批一次,不逐行),遮蔽后的行不给真实主页链接。■ 后台与多布局
· 原生 HTML5 拖拽排序(无框架、无外部依赖),支持跨槽位拖动、每块列宽(整宽/半宽/三分之一)、
可见范围、版块限制;无 JS 也能用(隐藏字段顺序即 DOM 顺序 + ↑/↓ 按钮 + 纯服务端「添加组件」表单)。
· 多套布局并存;桌面端与移动端各一套(UA 判定,移动端没启用时回落桌面套);一键恢复默认;复制到另一设备维度。
· 后台自检面板:三枚探针把「预算降级 / 异常隔离 / 空输出降级」三条守卫各真跑一遍,
守卫坏掉时自检自己会变红。■ 零影响承诺
未安装、或已启用但没有任何启用中的布局(安装后的默认状态)时:三个前台钩子读完一个整表缓存的记号
就原样返回 —— 不注入、不发查询、页面字节不变。要生效必须由管理团队在后台显式启用某套布局。■ 兼容与迁移
· 不修改任何核心表结构;自有表plugin_layout_diy_layouts,卸载时一并清理自己的设置记号。
· 未装本插件时其它插件行为逐字节不变;diy_layout.components这个 Hook 没人发也没人听。1.0.0 是初版:全站任意页插槽、组件自定义参数、组件级缓存留到 2.0.0。
插件质量报告
插件:
layout_diyHook 功能范围 循环 使用频率 文件读写 修改系统表 读写系统表 读写自己的表 page.footer 全站页面页脚 回调内 5 - - app_topics 修正❗️ 写 plugin_layout_diy_layouts, plugin_layout_diy_cache, plugin_layout_diy_snapshots 修正❗️ sidebar.feature_links 首页/侧栏快捷功能 否 5 - - - - sidebar.stack 全站侧栏组件栈 回调内 5 - - app_topics 修正❗️ 写 plugin_layout_diy_layouts, plugin_layout_diy_cache, plugin_layout_diy_snapshots 修正❗️ page.template 非系统hook 回调内 1 - - app_topics 写 plugin_layout_diy_layouts, plugin_layout_diy_cache, plugin_layout_diy_snapshots 修正❗️ topic.index_template 非系统hook 回调内 1 - - app_topics 修正❗️ 写 plugin_layout_diy_layouts, plugin_layout_diy_cache, plugin_layout_diy_snapshots 修正❗️ topic.template 非系统hook 回调内 1 - - app_topics 修正❗️ 写 plugin_layout_diy_layouts, plugin_layout_diy_cache, plugin_layout_diy_snapshots 修正❗️ 颜色说明:黄色表示有数据读写,红色表示 Hook 在系统循环中执行,浅蓝色表示有文件写入。“列表跳过”表示回调在列表路径会提前返回,“回调内”表示操作位于回调自身或其帮助函数的循环体内。“文件批处理”表示逐个处理用户一次提交的文件,属于必要操作,不标记修正。只有操作实际处于需要优化的循环路径时,才会标记“修正❗️”。
数据字典
plugin_layout_diy_layouts字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_layout_diy_snapshots字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_layout_diy_cache字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 沙发🛋
可以自定义页面吗?自定义好了后,可以设置首页、栏目页、内容页
主要是手机端
没效果图?
@dasheyang 好问题,分开说清楚,避免买了才发现范围不对:
1) 能不能自定义「页面」?
1.0.0 里可 DIY 的不是「新建页面」,而是往既有页面里摆组件,范围是五个槽位:首页顶 / 首页中 / 首页底 / 全站侧栏 / 页脚。首页那三个只在首页第 1 页生效,侧栏与页脚是全站的。2) 栏目页 / 内容页呢?
这正是下一个大版本(2.0.0)的第一优先级:把槽位机制从「首页 + 侧栏 + 页脚」扩到全站任意页(版块页、主题页、用户主页…)。机制上是同一套槽位注册与预算校验,不需要重写,所以这条不会被拖很久。到时你想要的「首页一套、版块页一套、内容页一套」就是三套布局配置的事。3) 手机端(这条现在就能用)
1.0.0 已经按设备维度分开存布局:同一个槽位可以给手机端单独摆一套组件,桌面摆桌面那套,互不影响。所以你「主要是手机端」的需求不需要等 2.0.0。4) 顺带两个容易忽略的点
- 其它插件零改动即可被摆进来:插件的
feature_links/profile_card/top_actions会被自动包装成「链接类组件」,链接取的是插件自己渲染的真实数据(标签、图标、角标一致),不是猜 URL。 - 装完就有的内置组件:站点统计 / 热门主题 / 最新主题 / 最新加入 / 版块导航——不依赖任何其它插件,空站也能拖起来。
三条硬约束也写在说明里:查询预算(每块声明用掉几条查询,超预算直接拒绝保存并指出是哪一块)、失败隔离(单块抛错只留占位,不影响整页)、权限 fail-closed(块级可见范围在服务端判定,管理向内容对游客一律不渲染)。1.0.0 的坑与实测数据都在 README 里,欢迎继续拍砖 🙌
- 其它插件零改动即可被摆进来:插件的
版本 1.1.0 更新:
【页面组件 DIY 1.1.0 · 小更新】十项新功能 + 五处修复■ 先修的五处缺陷(都先复现再修)
① 输出缓冲泄漏:第三方组件自己开了输出缓冲却没关时,宿主只收回一层,剩下的缓冲留在栈上 —— 之后整页输出都会进那一层、直到请求结束才被倒出来(顺序错乱、可能落到 </html> 之后),更糟的是会被后来者的 ob_get_clean() 当成自己的内容收走。现在会把回调留下的每一层都收干净(内容按原顺序拼回),并把「多开了几层」记进渲染报告。
② 表单静默截断:保存时超过 200 个块会被悄悄丢掉,而校验只看得到截断后的数量,提示里的块数也是错的。现在超上限会明确报错,并给出真实块数。
③ 后台自检会污染请求级账目:自检真跑渲染管线却只还原了报告主体,把「共享预取 / 跨插件契约」两笔账抹成 0。现在三本账整组拷走再还原。
④ 游客页面留 DIY 痕迹:降级占位的 HTML 注释对游客也输出。运维信息不该出现在对外页面,新增开关(默认关闭)让非管理团队一律看不到任何占位痕迹。
⑤ 块数与注册表不同源:插件停用/卸载后「块数 / 声明成本」与真实块清单对不上,管理员只能猜。现在后台概览按当前注册表重算并显示「失效块」数量,编辑器里失效块标红可移除。■ 十项新功能
- 布局导出:后台给出只读 JSON(槽位、顺序、列宽、可见范围、版块限制、标题、条数、排期全在里面),可整段复制到另一个站点。
- 布局导入:粘贴 JSON 即可导入,走同一套组件白名单与预算校验,存成一套新布局(默认停用,绝不覆盖现有布局);非法 JSON、不存在的组件、超预算一律拒绝并说明原因。
- 快照与一键回滚:每次覆盖保存/回滚之前自动留档(默认保留最近 5 份,可配 0-20);回滚走同一条校验路径,且回滚本身也能被回滚。
- 块级自定义标题:每个块可以改标题(留空 = 用组件默认标题),只在块确实渲染出标题时替换,替不掉就保持原样、绝不吞内容。
- 块级条数上限:每个块可单独设 1-20 条,内置组件全部支持。
- 布局排期:每套布局可设生效时间段;窗口外等于「没启用任何布局」——不注入、零查询、页面字节不变;判定在请求期做,只读整表缓存的配置。
- 管理团队前台预览:?a=home&diy_preview=布局ID 可以在真实页面看未启用的布局(服务端判定,仅管理团队;游客带同样参数完全无效),不改任何站点状态。
- 预算水位告警与降级通知:保存时用量达到上限 80% 就会在提示里告警;渲染期出现降级会给布局作者发一条站内通知(同一批降级只发一次,不写死某个管理员账号)。
- 移动端独立列宽:每个块可单独指定窄屏列宽(整宽/半宽/三分之一),桌面列宽不受影响。
- 查询预算体检:后台一键把当前布局逐块真跑一遍(与前台同一条渲染管线),给出「声明 / 实测 / 结果」对照表,并把三笔账(组件实测 / 共享预取 / 跨插件契约)分开列出 —— 声明是常量,实测才是事实。
■ 验收
· 六道静态门禁全绿;离线夹具 123 + 107 项全绿;实验室套件从 242 项扩到 300 项 / 0 失败。
· 1.0.0 的槽位、组件契约、三条硬约束(查询预算 / 失败隔离 / 权限与隐私 fail-closed)与「未启用布局时前台零影响」全部不变。版本 1.1.0 更新:
预览图补传(源码未变)
附件空间恢复后补传 4 张真实渲染的上架预览图(1280 宽无头 Chromium 渲染,非示意图);源码与市场上已登记的 1.1.0 逐字节一致(版本 / 行数 / 字节 / sha256 均未变),仅预览图集合更新。
01-admin-layout.png—— 后台布局编辑器:五个槽位(首页顶 / 首页中 / 首页底 / 全站侧栏 / 页脚)+ 逐块的「列宽 / 可见范围 / 版块 / 移动端列宽 / 自定义标题 / 条数上限」,声明成本预算显示 8 / 30,以及内置组件、私有 Hook 注册、既有 entries 自动包装三组可用组件清单(19 条)。02-budget-audit.png—— 1.1.0「查询预算体检」:逐块真跑一遍的「声明 / 实测 / 结果」对照表(7/7 块正常)+ 三笔账(组件实测 4 条 + 共享预取 0 条 + 跨插件契约 2 条)。03-front-slots.png—— 前台首页五槽位实际渲染:首页顶「站点统计」、首页中「最新发布 / 本周热议(块级标题与条数上限覆盖)」、首页底「活动快捷链接」、全站侧栏「版块导航 / 最新加入」、页脚「日历快捷链接」;后两者是其它插件 entries 自动包装出来的链接组件,插件零改动。04-export-snapshot.png—— 布局导出 JSON(含槽位、顺序、列宽、可见范围、版块、标题、条数、排期,可整段复制到另一站点)+ 每次保存自动留档的快照与一键回滚。
演示布局由后台真实写路径(保存 / 启用)在实验室生成:5 个内置组件 + 2 个由其它插件 entries 自动包装的链接组件,共 7 块 / 声明成本 8。
版本 1.1.1 更新:
1.1.1 修复:后台「开始体检」「运行预算与隔离自检」在真实浏览器里点不动- 现象:后台点「开始体检」只弹一个「操作失败」,体检面板永远不出现;「运行预算与隔离自检」同样。curl 直接 POST 完全正常 —— 所以带 SQL_COUNT 断言的 300 项套件全绿也测不到。
- 根因:①核心
app/assets/index.js的全局 submit 监听器对所有method="post"表单统一preventDefault()改走fetch+JSON.parse,唯一豁免是表单上的data-no-ajax="1";②这两个动作的响应是整页 HTML(结果面板拼进页面后照常渲染),不是{"ok":…}信封。 - 修法:按核心的逃生口给这两张表单补
data-no-ajax="1"(原生提交,等价于无 JS 浏览器那条路)。服务端零改动,无 JS 路径与套件断言的页面字节完全不变。选「原生提交」而不是「改成 JSON 信封」的理由写在 README E0.6:结果本来就是一整块页面面板,改信封要再造一条只在 JS 下才走的渲染分支。 - 同批修掉的另一半:后台「保存设置」校验失败时也 fall through 成整页 HTML,真正的错误提示被笼统的「操作失败」吞掉 → 改为 AJAX 请求回
{"ok":0,"message":…}信封,无 JS 浏览器仍是原来的整页渲染。 - 验收:
tools/lab_verify_layout.sh313 项 / 0 失败(新增 §11:静态逃生口守卫、AJAX 信封 / 无 JS 整页对照、真机点击体检+自检、原生提交对照组);六道静态门禁全绿;plugin/dist/layout_diy_1.1.1.php1与源码逐字节一致。 - 预览图:01 由 1280×4463 的整页长图改成「概览 + 编辑器」的 1280×2368 短图(191.8KB)—— 上架缩略图是 4:3 cover,长图只会显示中段;02-04 未改动。
辛苦了!感谢您的付出与努力👏
@dasheyang 收到,两条都记下了,一并回:
1) 图文样式(#13) 明确会做。现在
layout_diy的组件输出偏「纯文本块」,确实缺视觉形态。计划在下一个版本加一组组件样式(skin),每个组件可选:列表(现状:紧凑纯文本,最省空间)卡片网格(2/3/4 列,带封面缩略图 + 标题 + 摘要 + 作者/时间)图文横排(左图右文,适合「近期活动/精选主题」)大图头条(第一张满宽大图,后面跟若干小条目)
配套:封面来源可选「主题首图 / 附件图 / 自定义链接」,缩略图比例与圆角可配;不给游客增加查询——缩略图走一次整页批查(没有封面信息的行回退成无图版式),服务端渲染、不依赖前端 JS。
2) 你给的参考站(#14) 谢谢,我看了它的思路:卡片化列表 + 明确的层级与留白,正是上面「卡片网格 / 大图头条」这一组要的效果。我会按这个方向做形态,但不会照抄皮肤细节——
layout_diy的样式会走本站既有的 CSS 变量(颜色/圆角/字号跟随主题),这样换主题时不会和站点撞色。3) 时间预期:我手上这一轮正在做另一个插件(举报与处置中心),做完就轮到
layout_diy的样式版本;做的时候会按惯例:先修上一轮发现的缺陷 → 再补这批样式 → 六道门禁 + 验收套件(含「无封面时零额外查询」「行数翻倍查询数不变」断言)→ 上架时补新预览图。上线后我会在这里回帖,把可选的样式清单和配置项逐条列出来。@dasheyang 补一条更正 + 更具体的承诺:我原以为 #14 那个链接是「卡片化列表」的样式参考,点开看了才知道它是 Lume 轻论坛程序的「DIY 首页编辑器」教程——正好是
layout_diy同一个目标,参考价值比样式本身大得多。它讲的几件事我认同,也会做:1) 模块化 + 可视化编辑:它把首页拆成「模块」(帖子列表 / 排行榜 / 统计 / 签到 / 广告 / 自定义 HTML),拖拽摆放并调大小。
layout_diy已有前一半(五槽位 + 拖拽 + 组件清单 + 预算校验),缺的是模块类型不够多、样式太单一——这正是要补的。2) 帖子列表的多种样式(它列了十几种,我挑最实用的做):
列表(现状)·两列紧凑(一屏更多)·卡片封面(图片封面卡片,适合摄影/设计类版块)·图文横排(左图右文,适合活动/精选)·杂志错落(每组 1 大 + 2 小)·信息流(头像 + 标题摘要,社交媒体感)- 封面来源可选「主题首图 / 附件图 / 自定义链接」,比例与圆角可配;没有封面的行自动回退成无图版式,且缩略图走一次整页批查、不给游客增加查询。
3) 预设方案一键切换:它给了「仿双栏 / 仿现代风 / 杂志风 / 信息流 / 门户风」几套整体方案。我会做成布局预设:一键套用一套完整的槽位+组件+样式组合,站长再按需微调——比一个个手动摆快得多。
4) 版块列表组件(它那节也提到了):显示样式可选 列表/卡片、可带版块描述与主题数/回复数。
5) 两条我不会照抄的地方(说清楚免得落差):① 皮肤不抄,样式走本站已有的 CSS 变量(颜色/圆角/字号跟随主题),换主题不会撞色;② 「自定义 HTML 模块」这类能力只对管理团队开放且入库时做白名单过滤——它是 XSS 的正门,我不会给普通用户开这个口子。
时间:这些样式与预设我排在
layout_diy的下一个版本(现在手上在做「举报与处置中心」)。做的时候照老规矩:先修缺陷 → 再补样式 → 六道门禁 + 验收套件(会加「无封面时零额外查询」「行数翻倍查询数不变」两条断言)→ 上架补新预览图。做完我在这里回帖,把样式清单与配置项逐条列出来。谢了,这个参考比我预想的有用 👍谢谢你的付出!💥
版本 1.2.0 更新:
页面组件 DIY 1.2.0
大版本:组件样式(skin)+ 布局预设 + 版块列表组件 + 自定义 HTML;顺带修掉两处真缺陷。
一、帖子列表类组件的六种样式(按组件可选,后台可配)
- 列表(默认,最省空间,与 1.1.x 完全一致)· 两列紧凑 · 卡片封面 · 图文横排 · 杂志错落(每组 1 大 + 2 小)· 信息流(头像 + 标题摘要)。
- 样式只写结构类名,颜色 / 圆角 / 字号全部引用站点既有的 CSS 变量 —— 换主题时跟着一起变,
不写死色值、不引任何外部皮肤(套件里有静态断言守着这条)。
- 后台多了「组件样式一览」面板:用同一份示例数据把六款样式各渲染一遍(不发查询),不用先去前台试。
二、封面:三种来源 + 比例圆角可配 + 没有就回退
- 来源:主题首图(正文第一张图,零额外查询)· 附件图(跨插件只读契约
attachment.topic_images,
整页一次批查)· 自定义链接(支持
{id}占位)· 不显示。- 没有封面的行自动回退成无图版式,不会留空洞;
javascript:/data:/ 外链协议在渲染期被拒。 - 承诺并实测的两条:无封面时零额外查询;行数翻倍查询数不变(3 行封面与 6 行封面查询数完全相等,
多出来的只有那 1 条整页批查)。封面批查记在「共享预取」账上,不摊给任何块,不占游客的查询预算。
三、布局预设(一键套用,之后照样能改)
- 四套完整预设:仿双栏 / 现代风 / 杂志风 / 信息流,每套 = 槽位 + 组件 + 样式 + 列宽 + 封面 + 摘要。
- 「套用为新布局」(新建一套停用的,方便继续微调)/「套用并启用」;
走与手工保存完全相同的一条路(组件白名单 + 预算校验 + 自动留快照),只写本插件自己的布局表。
四、版块列表组件(
core:forum_list)- 样式 列表 / 卡片,可开关版块描述与主题数 / 回复数。
- 版块信息读核心内存缓存(零查询);勾了统计才发一条聚合查询(与版块数、主题行数都无关)。
五、自定义 HTML 组件(默认关闭)
- 只对管理团队渲染(服务端判定,游客与普通用户永远拿不到),默认关闭——关着时组件不进注册表。
- 入库做白名单过滤:危险标签连内容删掉、
on*事件属性与style一律丢、href/src只放行站内相对地址。 - 说清楚:这是本站 XSS 的正门,风险自负 —— 白名单挡得住已知形态,真正决定风险的是「谁在管理团队里」。
六、修掉的两处缺陷
- 杂志错落是非法 HTML 嵌套(
<li>里嵌<li>):真浏览器解析时会直接闭合外层、结构散架,
curl 与字符串断言完全看不出来 → 大条目改成
div,并留下静态断言。- 版块导航印假数据:老代码取
$forum['topic_count']而核心缓存里根本没有这一列 → 每行都写「主题 0」;
现在只在勾选开关时展示真数据。
三条硬约束(查询预算 / 失败隔离 / 权限 fail-closed)与「不启用布局时对前台零影响」的承诺一律不变。
点赞与点评1dasheyang投币1个强烈赞同4天前
版本 1.3.0 更新:
1.3.0:把 DIY 从摆设变成平台
先修四处真缺陷(都先复现、再修、各留回归断言)
- 删除布局时从属快照一条不剩(以前只删布局行,快照留成孤儿;安装/升级补扫一次历史残留);
- 第三方封面契约抛异常时页面不再变成「操作失败」错误面板(封面批查跑在核心最后一个页面钩子上,块级 try/catch 覆盖不到;现在拿不到封面就按无图版式渲染);
- 站内链接白名单堵住反斜杠绕过(真机实测
/\evil.example/x会被浏览器解析成http://evil.example/x,与「只放行站内相对地址」的承诺相反); - 渲染账目跨钩子同源(报告原先落在
page.footer、封面批查发生在它之后的page.template,持久化的「共享预取」永远少一条;统一挪到最后一个页面钩子)。
四款已上架插件原生接入(不再只是「链接块」)
anon_board:plaza匿名广场(声明 3 条查询)·community_calendar:today今日日程(2)·event_rsvp:upcoming近期活动(3)·qa_bounty:board悬赏榜(2)。- 每款都是「具名渲染回调 + 声明成本 + 无数据零查询降级 + 不抛异常」;匿名广场只给匿名号、不给任何主页链接;今日日程沿用插件自己的页面白名单与站点标记。
- 未装 layout_diy 时这四款插件行为逐字节不变:套件做的是「旧版 vs 新版、两边都停用 layout_diy」的 A/B 逐字节比对(20 个页面快照,含查询数)。
组件自定义参数
- 组件声明自己的参数白名单(整数 / 单选 / 多选 / 开关 / 文本),后台逐块可视化编辑(每参数一个隐藏域,无 JS 也能保存);
- 未知键与越界值拒绝保存并点名,渲染期再查一遍(fail-closed);
- 有效声明成本 = 组件基础成本 + 参数成本,后台 / 保存期 / 渲染期三处读同一个函数;参数只改 SQL 绑定值,查询条数不变。
组件级缓存
- 同一组件在同一请求内只渲染一次(块上标出来源);
- 跨请求可选短 TTL:读由站点标记短路(默认关闭时前台零额外查询),读与写都是循环外的一次批量 IN / 一次多值 INSERT,开销与块数无关;
- 缓存键含访客档 + 设备档(游客 / 登录 / 管理不互相串内容);后台一键清空,布局写路径自动清。
其他
- 后台新增「组件级缓存」面板与逐块参数控件;查询预算体检的「声明」列改用有效成本;
- 拖拽这条路径第一次有了真机验收(无头 Chromium 真鼠标拖拽 / 箭头排序 / 移除 / 参数同步 / 成本徽标 / 套用预设),已纳入
tools/lab_verify_layout.sh(607 项断言)。
版本 2.0.0 更新:
2.0.0:全站任意页 —— 槽位从「五个」扩到「五类页面」
这一版把「只能改首页」这件事做完了:版块页、主题页、用户主页、以及白名单里的插件路由页都能摆组件,
每类页面有自己的开关与自己的预算;同时补上缓存的页面档、把渲染期参数白名单真正落地,并修掉两处真缺陷。全站任意页的槽位
- 槽位:首页三槽(首页顶 / 首页中 / 首页底)+ 版块页两槽 / 主题页两槽 / 用户主页两槽 / 插件路由页两槽
+ 全站侧栏 / 页脚(1.0.0 的五个槽位顺序与标签逐字节不变,老布局照旧)。
- 每类页面独立开关(关掉某一类,它的槽位一律不渲染,块仍留在布局里)与独立预算:
某一类页面的预算 = 该类页面自己的槽位 + 全站槽位(侧栏 / 页脚在每一类页面上都会渲染,所以摊销进来);
预算填 0 = 跟随全局上限。首页那一栏恰好等于 1.x 的口径,升级后首页预算行为完全一致。
超预算拒绝保存,提示点名是哪一块、哪一类页面,并给出「本页面 X + 全站槽位 Y / 上限 Z」。- 插件路由白名单(默认空):后台用复选框列出已启用插件声明的前台路由,
核心自己的路由一律不收(保存期与渲染期两侧都判定)。不勾选就等于 1.3.0 的行为。
- 未知页面 / 未知槽位一律不注入、不报错、字节不变:搜索页、登录页、后台页与核心其它路由判成
other;
直接改库塞进来的未知槽位键在读出来时丢掉,就算塞进已发布载荷也只会去读白名单里的键(套件有对照断言)。
- 页面级槽位只在第 1 页生效;注入仍用核心的稳定锚点,锚点缺失就不注入。
组件级缓存的页面档
- 缓存键从「渲染签名 + 访客档 + 设备档」扩到再加一维 页面档:同一个组件在首页与主题页渲染出来的东西
不一定一样,只按签名缓存就是把一个页面的内容端到另一个页面。
- 后台按页面档看行数 / 命中 / 占用,也能只清某一档(未知页面档一律被拒);标记按真实剩余重算。
- 后台页面只读不写缓存:清空之后刷新后台,不会又把刚渲染过的页脚 / 侧栏块写回去(1.3.0 留档过的那条)。
渲染期参数白名单:这次是真的生效了
1.3.0 说「交付给渲染回调的参数一定过过白名单」,但宿主当时只在自己算渲染选项时清洗 ——
直接改库塞一个越界参数,第三方回调读到的是没洗过的值。现在渲染前统一过一遍:
未声明的键丢掉、越界值退回默认、块里没提交过的键不凭空补默认(fields这类参数靠这个区分语义)。
保存期仍然从严(未知键 / 越界值拒绝保存并点名)。先修的两处真缺陷
- 库里有数据但站点标记为 0 → 前台永远显示空(1.3.0 留档过):直接改库(导入备份 / 恢复快照 /
UPDATE … is_active=1)之后live标记还是 0,整站一块都不渲染且没有任何提示。现在多记一个
live_rows,前台把两个标记纯内存比对(一致时零查询),不同源就地重算;打开后台也会自动重算
(管理员不用点按钮),另给一个手动按钮。- 两个原生组件的站点标记自愈缺口:
anon_board:plaza与community_calendar:today直接读站点标记,
而同名侧栏卡片走的是带自愈的访问器 —— 标记被清成 0 或内容是从别处导入的时,库里明明有数据却永远显示空。
两处改用自愈访问器(anon_board_has_posts()的存在性探针同时合并成一条语句,成本恒为 1 条),
两个组件各升一个补丁(anon_board 4.0.3 / community_calendar 3.0.3),声明成本按最大值报。其它
- 「七类缺陷模式」逐类复核留档(0 命中 / 2 处已修)。
- 未启用任何布局时对前台零影响:不注入、不发查询、页面字节不变。
版本 2.0.1 更新:
【2.0.1】站点标记收口(本轮「边界项收口」)- 渲染自愈补第四条免费判据:标记说「没有启用中的布局」、但 app_settings 里还留着一份能用的已发布载荷
(只可能来自带外改动:从旧备份恢复设置 / 先写载荷后写标记)→ 判为不同源,就地重算并照常渲染。
- 关键写点收口:10 个会改布局表的后台动作(保存 / 加块 / 启用 / 停用 / 删除 / 复制 / 恢复默认 /
套用预设 / 导入 / 回滚快照)收尾统一按真实数据重算一次站点标记(第二道,幂等)——
「写点靠每个 helper 自觉」这条不可验证的假设被替换成可验证的收口。- 后台新增「前台显示空自检」:五条判据(库里启用行数 / 两个站点标记 / 已发布载荷 / 排期窗口 / 五类页面开关)
逐条给出当前值,并直接给结论「会渲染」或「显示空 + 原因」;带外改库(导入备份 / 直接 UPDATE is_active)
这一族只有后台能发现,README §F 写明了「什么条件下会显示空」的完整清单。- 离线夹具 pure_check_layout.php 扩到 373 项(新增 §14 八条:判据 D 四态 + 自检清单三态);
tools/lab_verify_layout.sh 新增 §27(静态 + 判据 D 端到端 + 9 个写点的标记同源矩阵 + 自检各态)。
- 前台零影响承诺不变:载荷全空时判据 D 为假,与 2.0.0 逐字节等价;未启用布局时仍不发一条查询。
版本 2.1.0 更新:
【2.1.0】组件生态续篇:私有 Hookdiy_layout.components与三款插件的原生组件- 新增私有 Hook
diy_layout.components:已上架的三款插件原生实现自己的组件,宿主只做登记与渲染,
插件停用即整块跳过(不报错、零查询、页面照常):
·rate_pick:picks「高分 / 精选」——高分主题带评分人数,精选条带推荐理由;声明成本 6(实测 6);
·daily_quest:today「今日任务 / 连续天数」——进度 + 我的连续天数;声明成本 6(实测 6),per_user;
·hot_rank:board「热榜 TOP N」——当期榜 + 升降箭头,与榜单页同一个渲染器;声明成本 7(实测 7)。- 先遮蔽、再判可见性:三款组件都先过
anon_board.mask_rows再决定昵称 / 主页链接 / 可见性;
匿名把整批遮蔽当一次批量契约(缺
id字段会静默空转,这条坑在三个实现里都写了回归守卫)。- 按人渲染不进跨请求缓存(修复 L:跨用户缓存串人):组件声明
per_user、或scopes不含guest时
自动按人渲染,缓存键带上查看者;块上打
data-layout-diy-peruser="1",后台概览直出「按人渲染的组件 N 个」。- 缓存标记的渲染期自愈(修复 M):缓存行过期被清掉时,就地按真实数据重算站点标记,不再等下一次后台写;
- 缓存页面档「看得见清不掉」收口(修复 N):统计 / 清空 / 标签共用同一个页面档判据(
other/global也认)。 - 空态判据修正:只有五类页面开关全关才判「前台显示空」,避免误报。
- 无数据即零查询降级(游客提示 / 占位文案),失败隔离:单块异常只留占位,同页其它块与页面结构不受影响。
- 未装 layout_diy 时三款插件行为逐字节不变(各套件里都有断言守着)。
- 验收:
tools/lab_verify_layout.sh§28 / §29(注册 · 成本 · 真渲染 · 降级 · 预算 · 匿名先遮蔽 · 按人渲染 ·
插件停用降级 · 缓存页面档);离线夹具
pure_check_layout*.php扩到 374 / 197 项。- 新增私有 Hook
版本 2.2.0 更新:
【2.2.0】把布局变成可运营的实验台:块级条件显示 · 布局 A/B 分桶 · 缓存定向失效 · 保存前差异预览- 块级条件显示:每个块可带「用户组 / 积分区间 / 注册天数 / 只给楼主 / 时段(支持跨零点)/ 星期」。
全部在服务端用请求级内存判定(核心已取回的当前用户 + 当前时间)→ 一条查询都不发;
条件不满足时这一块直接不渲染(连块内那一条查询都不发,实测整页 SQL 数严格更少)。
未知键丢弃、越界值夹进合法域;渲染期再判一次,格式非法的时段一律不渲染(带外改库塞进来的脏值也挡住)。
登录类条件(用户组 / 积分 / 注册天数 / 只给楼主)对游客一律不满足;时间类条件对游客同样可判定。
条件不参与成本计算:保存期预算仍按最大值算,声明多少条就还是多少条。- 布局 A/B 分桶:一套布局挂 A/B 两份块清单(
ab_blocks_json+ab_ratio0-100)。
分桶 = 「布局 id + 设备 + 访客标识 + 固定盐」的 sha256 → 0-99,纯内存、不含时间(含时间桶会抖);
登录用户按 uid、游客按核心已下发的访客 cookie(只读,不新建 cookie)。
fail-closed:比例非法一律按 0 = 关闭 A/B(与 2.1.0 行为一致);游客拿不到稳定标识 → 固定 A 桶;
B 桶某槽位为空 → 那个槽位回落 A 桶(宁可给正式版,不给空白)。
桶不进缓存键:组件缓存签名是按块算的(组件 id + 渲染参数),两桶天然分开 ——
实测 A 桶独有块与 B 桶独有块各占一行、两桶共有的同参数块只占一行,桶塞进键只会让缓存白白翻倍。
后台可直接切「编辑 A 桶 / 编辑 B 桶」(同一套网格与校验)、一键「把 A 桶复制到 B 桶」,
概览里点名「配了比例但 B 桶是空的」这种半开启状态;快照把两桶一起留档,回滚是完整回滚。- 组件级缓存的定向失效:缓存行带
component_id/slot,新增
layout_diy_cache_clear_target(组件, 槽位, 页面档)—— 按任意组合定向删,空条件一律拒绝(要全清走「一键清空」);
后台缓存面板多一条「定向清空」表单:只清被改过的那一个组件,别的组件继续命中(实测其它组件行数一条不差)。
未知槽位 / 未知页面档不删任何行(fail-closed 在发语句之前)。- 保存前差异预览:编辑器里「预览差异(不保存)」把当前内容与已保存的那一份逐槽位比对,
纯服务端渲染一张表:新增 / 删除 / 移动 / 参数 / 条件 / 成本,一个字节都不写库;
没有差异时明说「没有任何结构性差异」。顺手修掉一处真缺陷:条件比较原先用数组转字符串
(会触发 "Array to string conversion" 警告),现在走json_encode稳定比较。- 真机回归顺手修掉两处「只有开着 JS 的浏览器才暴露」的问题:① A/B 面板原先在编辑器表单内部
又放了一张表单(HTML 不允许表单嵌套),浏览器会提前闭合外层表单,导致「保存布局 / 预览差异」两个按钮
点了没反应 —— 现在复制桶的按钮挪到编辑器表单之外的「布局操作」里;② 「预览差异」在 AJAX 提交下返回的是
整页 HTML,核心 JS 解析不了、弹不出东西 —— 现在 AJAX 走 JSON 信封并用弹窗呈现差异表(无 JS 仍是整页渲染)。
另外修掉一处静默空转:缓存行原先没写component_id/slot,定向清空一条也匹配不到(现在写入时就带上身份)。- 验收:实验室套件新增 §30(静态接线 + 条件五态 + 「不满足 ⇒ 不发查询」 +
「游客看到的桶 = 纯函数按 cookie 盐算出的桶」+ 非法比例 fail-closed + 定向清空「只删目标」+ 差异预览「只看不写」);
离线夹具扩到 433 / 197 项。不启用任何布局时对前台仍是零影响(不注入、不发查询、页面字节不变)。这个谁来讲讲作用是什么?
版本 2.3.0 更新:
2.3.0 · 覆盖文件升级自愈- 修复(升级路径):布局/组件/缓存三张表的列在 1.1.0 与 1.3.0 两代各有新增,而补列只在
install内执行 ⇒ 覆盖文件升级后后台保存布局会直接抛 SQL 错并被自动停用(结构标记此前只写不读,等于没有护栏)。现在运行期入口按三道门(内存副本 → 设置标记 → 才 DDL)幂等补齐,结构标记改为一读一写。 - 口径:稳态零查询;不挂读钩子,页面预算不变;前台组件渲染与既有版式逐字节不变。
- 修复(前台首页):站点的两个前台标记(
live与启用中的布局行数)打架时,前台会走一次重算;而重算要读布局表 —— 覆盖文件升级后表缺失的那一发就是PDOException,核心会把整个插件停用。要害在于这条支路由前台渲染钩子触发:整站首页先炸一次、插件被停用后首页才恢复,而纯前台流量(从不点插件页)永远等不到自愈。现在把自愈挂在"标记已经打架"的那一支的第一句 —— 稳态下两标记一致、该函数一次都不会被调用,首页预算口径不变(实测自愈前后首页语句数 10 → 10)。
- 修复(升级路径):布局/组件/缓存三张表的列在 1.1.0 与 1.3.0 两代各有新增,而补列只在
版本 2.3.1 更新:
2.3.1 · 卸载卫生:不再把两个历史记号"建"回数据库- 修复(卸载残留):卸载逻辑里原本用一次「把记号清零」的写入来收拾现场,而那次写入走的是upsert(有则改、无则新建)——
其中
plugin_layout_diy_check/plugin_layout_diy_cache_hit这两个名字是历史遗留:现在的代码里既没有人写、也没有人读。
后果:在不保留数据卸载这条路径上(核心只有在keep_plugin_data=0时才会调用插件的单参数uninstall回调),
全新安装的站点反而被"造出"这两行空值设置,留在app_settings里当垃圾。- 现在:这两个名字改成显式删除 —— 老站上如果真有残留就顺手清掉,没有就什么都不做(幂等,且不会创造数据)。
其余记号的清零行为一个字没改。
- 影响面:只在卸载这一条路径上生效;安装、升级、前台渲染、后台面板与查询预算全部不变(页面可见行为零变化)。
- 这条是工具找出来的:
tools/settings_key_audit.php("写了不读"= 不存在的护栏 / 写了没人读 = 死数据)在人
工复核时定位到"卸载清单里的死条目",取证用 A/B 对照(旧版本臂预期卸载后出现 2 行、新版本臂预期 0 行)。
插件说明太长,请不要查过200个字符。