更新日志

Qi-theme 后台设置全面增强 + 独立构建器页面 · 升级报告


一、主题设置扩容:4 组 → 8 组,全部可视化

入口:后台 → Qi 主题 → 主题设置。所有改动保存即全站生效,零代码。

选项卡可配置项生效方式
颜色8 个主题色(取色器)CSS 变量覆盖 :root
布局容器宽度 / 卡片圆角 / 阴影 / 悬停动效CSS 变量 + 覆盖规则
字体标题/正文字体(5 字体栈)/ 字号 / 行高CSS 变量 + body 规则
间距区块留白 / 卡片间距 / 容器内边距CSS 变量
导航栏(新)通告条开关及文字 / 品牌名称与副标题 / 联系电话及显隐 / 吸顶开关header.php 动态读取
页脚(新)关于栏标题与简介 / 邮箱 / 地址 / 社交图标开关 / 版权文字 / ICP 备案号(自动链接工信部)footer.php 动态读取
SEO(新)首页 description / keywords / author / Open Graph 分享卡片开关wp_head 输出;装 SEO 插件留空即不冲突;内页 description 自动取摘要
动效与元素(新)入场动画总开关 / 过渡时长(200–1500ms 滑块)/ 顶部滚动进度条 / 返回顶部按钮覆盖 .reveal 等 CSS

安全:nonce + 按字段类型净化(hex/text/textarea/checkbox/数值钳制),每组独立恢复默认。

二、独立构建器页面(新增 inc/builder-page.php)

入口:后台 → Qi 主题 → Qi-Theme构建器

  • 页面选择器:任选 首页(自动标注)/ 全部页面 / 最近 30 篇文章,切换即载入其 Schema。
  • 全屏三栏工作区:与编辑页 metabox 共用同一套 builder-core.js 引擎——左侧组件库拖拽、中间画布(桌面/平板/手机响应式预览、双击改字)、右侧属性配置;撤销/重做/导入/导出/清空/预览齐全。
  • AJAX 保存:点「保存更改」直接写入该页面 _hualin_builder_schema,前台即时生效,无需进编辑页;有未保存修改时离开页面会提醒(beforeunload)。
  • 模板方案:
  • 当前布局可「存为模板…」(存 option qi_builder_templates,上限 30 个);
  • 任意页面一键「应用模板」(先确认,可撤销);
  • 可删除自建模板;内置「★ 默认首页方案」不可删除;
  • 首页无自定义 Schema 时自动载入默认首页方案,与前端渲染 100% 一致。
  • 权限与安全:edit_post / edit_theme_options 校验 + nonce + 复用 hualin_sanitize_builder_schema() 白名单净化。

三、改动文件清单

文件变更
inc/options.php新增 header/footer/seo/effects 4 组默认值、字段类型表、保存净化、4 个选项卡 UI、动效 CSS 输出、SEO meta/OG 输出
header.php通告条/品牌/电话/吸顶全部读设置项
footer.php关于栏/联系方式/社交开关/版权/ICP 全部读设置项
inc/builder-page.php新增 独立构建器页面(AJAX 保存 + 模板方案)
inc/theme-admin.php总览页更新两处入口说明
functions.phprequire builder-page.php

四、部署

  1. 后台 → 外观 → 主题 → 上传 Qi-theme-20260723-v3.zip(先删旧版或直接覆盖启用)。
  2. 全量 php -l 校验通过(PHP 8.2);ZIP 采用 PHP ZipArchive 正斜杠路径,宝塔/Linux 解压安全。
  3. 已有页面数据(Schema、设置项)不受影响——只新增选项,默认值与原硬编码文案一致。

五、本轮新增(模块管理 + 抖音宣传片 + UI 优化)

5.1 后台模块管理(集中化启停 / 排序)

入口:后台 → Qi 主题 → 模块管理(qi-theme-modules)

把首页与全部分类/归档页的模块聚合到一个界面统一管理:

  • 启用/禁用:首页模块写回页面构建器 Schema 的 hidden 标记(隐藏不删除,可随时恢复);归档页模块写入 option qi_archive_modules。
  • 两种排序:拖拽排序(jQuery UI sortable,抓 ⠿ 手柄)+ 数值排序(改序号后点「按序号排列」)。
  • 覆盖范围:首页全部区块;资讯 news / 项目 project / 项目分类 project_cat / 博客 blog 四类归档页(面包屑、标题区、筛选项、列表、分页等)。
  • 安全:nonce + current_user_can('edit_theme_options') + 仅允许注册表内的模块 key,升级时新模块自动追加启用。

5.2 企业宣传片模块 —— 抖音视频解析播放

入口:后台 → Qi 主题 → 主题设置 → 宣传片

  • 表单字段:模块总开关 video_enable、标题、说明、douyin_link(可粘贴整段抖音分享文案或纯链接)。
  • 保存时自动调用解析接口 https://v.xzbzq.com/jx/dy/?url1=<链接>(强制刷新缓存),12s 超时 + UA 伪装;解析状态回显到后台(成功绿条 / 失败黄条)。
  • 前端 hualin_block_video():命中抖音链接 → 解析 → 原生 <video controls playsinline> 内嵌播放;解析失败 → 占位 + 明确降级提示 + 「前往抖音观看」兜底链接;无抖音链接时回落原直链/占位逻辑。
  • 健壮性:qi_douyin_resolve() 兼容 JSON 多键 / 纯文本直链 / 302 Location;transient 缓存成功 2h、失败 10min;qi_douyin_extract_url() 从分享文案中抽链接。

5.3 UI 优化(rb-card / sec / container / video)

assets/builder/builder-blocks.css:

  • rb-card:卡片加顶部渐变强调条(悬停展开)、更精致的图标芯片(渐变底 + 内描边)、标题/描述层次、描述 3 行截断、链接悬停右移,整体更现代。
  • sec / rb-sec:区块上下留白桌面 64px、平板 52px、手机 44px(原固定 56px,更透气);深色/强调区块加径向高光层次,文字/容器内层级 z-index 提升。
  • container:补齐 box-sizing:border-box 与 width:100%;新增 .container.wide(1280px)/ .container.narrow(880px)宽度工具;移动端内边距收紧至 18px。
  • 视频模块:新增 .vid-live(内嵌播放器样式)、.vid-note、.vid-err(解析失败容错提示条)样式。

六、本轮改动文件清单(叠加于三)

文件变更
inc/modules-manager.php新增 模块管理页(首页 Schema + 归档页 option 双存储、拖拽/数值排序、保存处理)
inc/douyin.php新增 抖音链接抽取 / 多格式解析 / 缓存 / 状态持久化(修复单引号正则解析错误)
inc/options.php新增 video 选项组 + 「宣传片」选项卡 UI + 保存时自动解析回显
inc/builder-render.php重写 hualin_block_video()(抖音解析内嵌播放 + 容错);首页默认 Schema 注入「企业宣传片」模块
archive-news.php / archive-project.php / taxonomy-project_cat.php / index.php改为模块化 switch($qi_mod) 渲染,由 qi_archive_modules($context) 驱动(支持启停/排序)
assets/builder/builder-blocks.cssrb-card / sec / rb-sec / container / 视频模块样式优化
functions.phprequire 新增 douyin.php、modules-manager.php
inc/theme-admin.php总览页新增「模块管理」入口 + 宣传片选项卡说明

七、本轮新增(v4):样式预设 + 联系页整改 + 视频内嵌约束

7.1 后台「样式预设」可视化选择器

新增选项卡 后台 → Qi 主题 → 主题设置 → 样式预设,对两个高频元素各提供 5 套 可一键切换的方案(配色 / 排版 / 动效 / 装饰元素组合):

  • Hero(深蓝)预设 rb-hero.navy.s1–s5:s1 经典渐变(金光晕)/ s2 深空蓝紫(网格纹理 + 紫罗兰光)/ s3 金线商务(顶部金线 + 衬线大标题)/ s4 极光绿松(青绿渐变 + 流动极光)/ s5 暗夜描边(近黑底 + 描边边框 + 呼吸光 CTA)。
  • 宣传片(视频)预设 rb-video.sec.s1–s5:s1 默认卡片 / s2 毛玻璃浮窗(磨砂 + 浮层)/ s3 窄幅居中(浅色窄栏 + 描边)/ s4 深色沉浸(全宽无边框)/ s5 描边霓虹(霓虹描边 + 辉光)。

选择器为可视化卡片面板(每套带迷你渐变预览 + 名称 + 描述,选中态高亮),保存即全站即时生效(作用于所有深蓝 Hero / 宣传片视频区块)。prefers-reduced-motion 下自动关闭动效。

7.2 联系我们页(page-contact.php)全面排查与修复

排查维度原问题修复
表单提交<form action="#"> 提交即刷新、无处理、无反馈页面内 POST 处理:nonce 校验 + 蜜罐防机器人 + 成功/失败状态回显,提交后不清空异常、成功后清空防重复
字段校验仅 HTML5 required,无服务端校验服务端校验:姓名≥2 字、电话 6–20 位(含区号/空格)、邮箱格式(如填写)、留言≥5 字;错误字段红框高亮
各分辨率布局依赖 .reveal(JS 失败则内容不可见)+ 地图/栅格未充分验证去掉 reveal(内容直接可见);.contact-grid 移动端单列、≥640px 两列;新增表单状态/错误样式
联系方式数据渲染地址/电话/邮箱硬编码,与全站不一致统一读取「主题设置 → 联系」选项(地址/电话/邮箱/时间/地图坐标),未配回退默认
地图组件src="map.baidu.com/search/..." 非可嵌入 URL,加载异常改用 OpenStreetMap 免密钥嵌入(export/embed.html?bbox=...&marker=...),坐标来自选项,稳定加载

新增 「联系」选项卡(inc/options.php):可编辑地址、电话、邮箱、办公时间、地图经纬度与缩放级别。

7.3 视频播放强制页面内嵌(禁止外跳)

  • 抖音解析失败:移除原「前往抖音观看」target="_blank" 外链,改为纯内嵌占位 + 文案提示(不跳外链)。
  • 原直链逻辑:媒体文件(mp4/webm/ogg/m3u8)改为原生 <video controls playsinline> 页内播放;非媒体链接仅占位、不外跳。
  • 所有视频均为页内 <video> 内嵌,符合「禁止跳转外部链接 / 打开新窗口」约束。

八、v4 改动文件清单(叠加于六)

文件变更
inc/options.php新增 style(hero_navy_style / video_style)+ contact 选项组;select 类型 + 联系文本键;新增「样式预设」「联系」两个选项卡;可视化卡片选择器 UI + 后台 CSS;保存处理逻辑补 select 分支
inc/builder-render.phphualin_block_hero() 深蓝状态下挂预设类;hualin_block_video() 挂视频预设类 + 移除所有 target="_blank" 外跳(抖音失败提示 / 直链改内嵌 <video>)
page-contact.php重写:页面内表单处理(nonce+蜜罐+服务端校验+状态回显)、数据读自选项、OSM 地图嵌入、响应式与去 reveal
assets/builder/builder-blocks.css新增 Hero 深蓝 s1–s5、视频 s1–s5 预设样式(配色/排版/动效/装饰)+ 关键帧 + 动效偏好守卫;移除 .vid-err a 外链样式
assets/css/main.css新增 .contact-status 成功/错误、.form-field.has-error 红框、.contact-info .icon 尺寸

九、页脚多风格预设 + 移动端全面优化

9.1 页脚样式预设(后台可视化选择器,5 套)

入口:后台 → Qi 主题 → 主题设置 → 样式预设 → 页脚预设(与 Hero / 视频同一选择器面板)。
保存即全站页脚即时套用 class="site-footer f-s1…f-s5",每套含不同配色 / 排版 / 布局 / 装饰元素:

预设视觉特征布局结构装饰
s1 经典深蓝(默认)深蓝渐变 + 金色文字 hover四栏(关于/导航/服务/联系)+ 居中底栏顶部金色光线条
s2 浅色描边浅灰底 + 深字 + 列间分隔线四栏,列间竖向分隔顶部品牌蓝光线条
s3 深色霓虹近黑径向底 + 青紫霓虹 + 悬停辉光四栏,链接悬停发光顶部青紫渐变线
s4 品牌渐变带深蓝底 + 6px 金橙顶带关于栏横跨顶部成品牌带,下方三栏顶部金橙渐变带
s5 极简单栏深蓝底 + 大号字标 + 居中留白单栏居中:字标 + 单行链接 + 社交顶部 1px 细线

9.2 移动端页脚优化(响应式 / 触控 / 内容优先级 / 性能)

  • 响应式布局:≥1024 四栏 → ≥640 两栏 → <640 单栏;s4/s5 在移动端自动回退单列,避免拥挤。
  • 内容优先级:移动端用 order 把「关于 + 联系」置顶(最常用信息先到),导航/服务栏后置。
  • 触控友好:导航/服务栏改为 <details> 折叠(移动端点击展开,桌面端常开、无箭头);社交图标移动端放大至 40×40;链接整行可点(padding 10px);电话/邮箱变 tel:/mailto: 一键拨打/发信;底栏链接加内边距。
  • 字体与间距:移动端页脚字号 13.5px、底栏 12.5px、行高加大;内边距收紧。
  • 加载性能:纯 HTML <details> + CSS,零 JS、零图片请求(仅 CSS 渐变),不改变主题文件,前台无额外阻塞。

9.3 改动文件

文件改动
inc/options.phpstyle 组新增 footer_style;select 类型加入 footer_style;样式预设面板新增「页脚预设」5 套卡片(标签/描述/预览色)
footer.php页脚挂 f-<style> 类;导航/服务栏包 <details open>(移动端折叠);关于+联系常驻(优先级);CTA 改为 .footer-cta 类(去内联白字,交由预设控制);联系方式包 tel:/mailto:
assets/css/main.css页脚基础增强(position:relative、链接内边距、底栏降亮);.footer-cta/.f-about-text/.f-contact-list;<details> 折叠样式(桌面常开、箭头);f-s1…f-s5 五套预设(配色/排版/布局/装饰 + prefers-reduced-motion 守卫)
assets/css/responsive.css<768 新增:列重排(关于/联系置顶)、整行可点链接、社交 40px、字号/间距缩放、s4/s5 移动端单列回退

十、标题层级优化 + 模块图片可编辑 + 抖音解析修复(v6)

10.1 一级 / 二级 / 三级标题视觉层级

  • assets/builder/builder-blocks.css 新增全局标题体系:h1(800 字重 / 44px / 字距收紧)、h2(700 / 34px / 居中 + 品牌金橙强调短线)、h3(700 / 21px / 深蓝 800)、h4(700 / 18px)。三级层级在字号、字重、字色、装饰上明显区分;左对齐区块(关于 / 图文布局 / 图文英雄 / 极简 CTA)的 h2 强调线自动左对齐。

10.2 模块图片全部改为可编辑(媒体库 + 外链)

  • 构建器新增 image 字段类型(buildField):支持 WordPress 媒体库选择(wp.media) 与 外链 URL 输入 两种方式,存为 {id, url};后台已 wp_enqueue_media()。
  • 统一渲染辅助 imgTag()(JS)/ hualin_img_tag()(PHP):优先用媒体库附件 id(自动 srcset),其次外链 url,皆空则降级为占位图;兼容旧版纯文本 caption(非 http 视为占位文案)。
  • 覆盖模块:关于图文(about)、图文英雄(hero-split)、图文特性(feature-split)、图文布局(mediatext)、图片块(imagebox)、图片(image,原仅占位现已可设图)。imagebox 的 url 字段与 mediatext 的 imgurl 字段均升级为媒体库/外链双模式。
  • 配套样式:.rb-about .ab img、.rb-hero-split .hs-img img、.rb-feature-split .fs-img img 真实图片填充(object-fit:cover + 圆角);后台图片字段 UI 样式(预览/选择/清除/外链)写入 builder-admin.css。

10.3 抖音解析视频无法播放(根因 + 修复)

  • 根因:解析 API v.xzbzq.com/jx/dy/ 返回的是 HTML 播放器页面而非直链;服务器侧 wp_remote_get 在多数主机被防火墙拦截 → 取不到直链 → 旧逻辑落入「占位 + 解析失败」死路,视频始终不播放。
  • 修复:inc/douyin.php 增强直链提取(<video src> / <source src> / aweme 特征),服务器超时由 12s 降至 6s;hualin_block_video() 改为「服务器取到直链 → 原生 <video> 内嵌;否则 → 页面内 <iframe> 内嵌官方解析播放器」(浏览器端解析,绕开服务器防火墙,仍在站内、不跳外链)。已 curl 验证该 API 无 X-Frame-Options/CSP,可正常 iframe 嵌入。

10.4 改动文件

文件改动
assets/builder/builder-blocks.css全局 h1/h2/h3/h4 层级体系 + 强调短线;about/hero-split/feature-split 真实图片填充样式
assets/builder/builder-admin.css图片字段 UI(预览/媒体库选择/清除/外链输入)样式
assets/builder/builder-core.js新增 imgTag();buildField 新增 image 字段类型(wp.media);about/hero-split/feature-split/mediatext/imagebox/image 区块接入可编辑图片
inc/builder-render.php新增 hualin_img_tag();about/hero-split/feature-split/mediatext/imagebox/image 渲染接入媒体库/外链图片
inc/builder.php构建器后台 wp_enqueue_media() 启用媒体库
inc/douyin.php直链提取增强 + 超时降至 6s
inc/builder-render.php(video 分支)抖音解析失败兜底为站内 iframe 嵌入官方播放器

打包:Qi-theme-20260723-v6.zip(根目录 qi-theme/,56 个文件)。

十一、构建器样式扩展 + 首屏幻灯片切换修复 + 图片设置优化(v7)

11.1 构建器区块样式扩展

在多个区块新增可选样式变体,用户无需写代码即可切换视觉风格(JS 渲染 builder-core.js 与 PHP 渲染 builder-render.php 1:1 对应,前后台一致):

区块新增样式说明
关于图文(about)banner(整图背景 + 文字浮层)、overlap(图与文字交叠卡片)原「徽标+图文」版式保留
服务/卡片(services)bordered(描边)、soft(柔和浅底)默认 card
行动号召(cta)split(深色渐变条 + 左文右按钮左右分栏)—
图片块(imagebox)frame(白边框)、shadow(投影)默认 plain
首屏幻灯片(hero-slider)kenburns(缓慢缩放入场动效)—

11.2 首屏幻灯片切换功能(根因 + 修复)

  • 根因:幻灯片 .hs-slide 用 position:absolute;inset:0 叠放在 position:relative 的轨道上,但轨道没有高度 → 整个首屏 <section> 塌陷为 0 高;且切换完全依赖 CSS animation:hsFade 内联 animation-delay,无 JS 驱动、圆点不可点、无自动播放 → 视觉上「静止、不切换、且高度塌掉」。
  • 修复:
  • builder-blocks.css:首屏区按尺寸档 hs-h-sm/md/lg 给 min-height(340/460/620px),轨道 position:absolute;inset:0 撑满;幻灯片改用 opacity 过渡 + .is-active 激活类(不再依赖 CSS 动画)。
  • assets/js/main.js:新增 JS 幻灯片引擎——自动播放(4.5s)、圆点点按切换、鼠标悬停暂停、尊重 prefers-reduced-motion;编辑态(#hualin-builder-app 预览)自动排除,便于静态编辑。
  • 渲染层(builder-core.js / builder-render.php)首屏首张幻灯片输出 is-active,移除内联动画样式。

11.3 图片设置优化(来源 / 尺寸 / 一致性)

  • 来源(v6 已完成,v7 补充引导):媒体库选择 + 外链 URL 双模式,存为 {id, url}(见 §10.2)。
  • 尺寸约束(防超大图导致排版错乱):图片字段新增建议尺寸提示「建议宽度 ≤ 1600px、单图 ≤ 300KB」;媒体库选中后实时校验 filesizeInBytes / width,超限(>2MB 或宽 >2400px)时显示橙色警示条,提醒压缩后再用。
  • 一致性(不同尺寸图片稳定排版):builder-blocks.css 全局安全规则 .rb img{max-width:100%;height:auto;display:block} + 占位容器 .rb .ph{overflow:hidden};各图片模块统一 object-fit:cover 填充,banner/overlap/cta-split 等版式均锁定容器尺寸,避免任意图片撑破布局。

11.4 改动文件

文件改动
assets/builder/builder-core.js区块样式扩展(about/services/cta/imagebox/hero-slider 新增变体);首屏 is-active 输出;图片字段新增尺寸提示 + 超限警示(checkSize)
inc/builder-render.php对应样式变体 PHP 渲染(banner/overlap/bord/soft/cta-split/ib-style-*);首屏首张 is-active
assets/builder/builder-blocks.css首屏 min-height + 轨道/幻灯片过渡;banner/overlap/cta-split/ib-style 样式;hsKen 缩放动效;全局 .rb img{max-width:100%} 安全规则;移动端适配
assets/builder/builder-admin.css图片字段尺寸提示 .img-tip 与超限警示 .img-warn 样式
assets/js/main.js新增首屏幻灯片 JS 引擎(自动播放/圆点导航/悬停暂停/reduced-motion 守卫)

打包:Qi-theme-20260724-v7.zip(根目录 qi-theme/)。

十二、移动端专项优化(全模块排查与修复)

逐模块排查 首屏 / 关于 / 服务 / 案例 / 资讯 / 定价 / CTA / 视频 / 时间线 / 流程 / 团队 / 客户 / 常见问题 / 标签 / 表格 / 图标 / 统计 / 面包屑 / 分栏 / 区块容器 等在 375 / 768 / 1024 主流分辨率下的显示与交互,修复如下:

12.1 布局错位(移动端单列化与内距收敛)

  • 关于 style-split / style-stats 此前因更高优先级规则在移动端仍保持两列 → 现强制单列(gap 24px)。
  • 案例 / 资讯「首条大图」(pj-feat / nw-feat) 内部 1.4fr 1fr 两列 → 移动端单列。
  • 案例 / 资讯「列表行」固定 120px 首列在窄屏过挤 → 收敛为首图 84px + 标题,箭头隐藏。
  • 区块预设大留白 pad-lg(96px) / pad-md(60px) 优先级高于 .rb .sec 移动覆盖 → 移动端收敛为 64 / 48px。
  • 服务列表行、定价卡、CTA 卡片、首屏卡片内距在移动端收敛;标签/选项卡改为横向滚动(scroll-snap)。

12.2 字体适配

  • 标题普遍采用 clamp() 流式字号;小屏(≤480) 再收一档(h1 26–34px),防止超长标题换行错位。
  • 首屏正文 18px → 16px(移动端);英雄幻灯片 h1 流式下限 24px。

12.3 触摸交互响应

  • 全局 touch-action: manipulation + 清除点按高亮(构建器模块 builder-blocks.css 与主题壳 responsive.css 双处),去除 300ms 点击延迟。
  • 首屏幻灯片圆点放大可点区域(::before 向外扩展 10px),并新增 触摸左右滑动切换(main.js)。
  • 标签/选项卡移动端横向滚动;折叠面板(FAQ / 手风琴 / 页脚列)<details> 原生点按展开,点按区域充足。

12.4 跨设备分辨率适配

  • 统一以 767px 为移动主断点(与主题 responsive.css 一致),新增 480px 小屏精修档。
  • 小屏(≤480):主按钮(首屏 / CTA 卡片)占满宽度便于拇指点击;卡片内距再收紧;横幅图文降高;图标信息块行内布局改竖排居中。
  • 横向内容(表格 .tbl-wrap{overflow-x:auto}、合作伙伴跑马灯/轮播、图集 Lightbox)在移动端均可横向滑动查看,无横向溢出。

12.5 改动文件

文件改动
assets/builder/builder-blocks.css新增「移动端专项优化」整段:全局触摸规则 + 767/480 媒体查询(逐模块单列化、内距收敛、横幅降高、圆点可点区域、标签横滚、按钮占满)
assets/css/responsive.css追加全局 touch-action / 清除点按高亮(主题壳:导航/页脚/按钮)
assets/js/main.js首屏幻灯片新增触摸滑动切换(touchstart/touchend 方向判定)

打包:Qi-theme-20260724-v7.zip(v7 代码 + 本移动端专项优化,根目录 qi-theme/)。


十三、图片上传修复 + 架构重构 + 三级导航重设计(2026-07-29)

13.1 修复「请在 WordPress 后台使用图片选择功能」提示

  • 根因:构建器有两套加载入口 —— inc/builder.php(文章/页面编辑页 metabox)与 inc/builder-page.php(独立构建器页面)。前者在 admin_enqueue_scripts 中调用了 wp_enqueue_media(),后者漏调,导致独立页面点「从媒体库选择」时 window.wp.media 为 undefined,触发 builder-core.js 内的硬 alert 并 return。
  • 修复:将资源入队统一收敛到共享模块(见 13.2),其中无条件入队 wp_enqueue_media()(is_admin() 下),两个入口均生效;图片选择恢复正常。
  • 健壮性:builder-core.js 的 image 字段不再用 alert 中断,改为在警示区写入友好提示并允许外链地址兜底,避免任何环境下操作被硬卡死。

13.2 架构重构(提升可维护性 / 模块化)

  • 新增 inc/builder-loader.php,提供两个单一来源函数:
  • hualin_builder_enqueue( $schema_json, $field_id, $extra ) —— 统一注册 builder-blocks.css / builder-admin.css、入队 wp_enqueue_media()、注册并本地化 builder-core.js / builder-admin.js(含 HualinBuilderData,并可注入额外对象如 QiBuilderPage)。
  • hualin_builder_app_markup( $args ) —— 统一输出 #hualin-builder-app 三栏界面(顶栏/画布/配置 + 弹窗/提示/隐藏字段),通过 field_name / field_value 参数适配 metabox 与独立页面差异。
  • inc/builder.php、inc/builder-page.php 改为调用上述函数,删除约 60 行重复代码;functions.php 在加载 builder 前 require 共享模块。
  • 重构后:媒体库加载、界面 DOM、本地化逻辑三处单一来源,后续维护不再需要双端同步。

13.3 一/二/三级导航重设计

  • 新建 assets/css/nav.css(独立导航样式层,体现模块化),从 main.css / responsive.css 抽离导航专属规则,职责解耦:
  • main.css 保留头部结构布局与一级下划线(下划线已收敛为仅顶级项 .nav-primary > ul > li > a)。
  • responsive.css 保留导航容器在移动端的显示/隐藏(汉堡)。
  • nav.css 专注菜单层级视觉 + 交互反馈 + 各断点适配。
  • 视觉层级:一级悬停/当前项高亮 + 下划线;含子菜单项显示 chevron 指示符;二级下拉为白色卡片容器(阴影/圆角/分隔);三级 .sub-menu .sub-menu 在桌面端向右 flyout,形成清晰第三层。
  • 交互反馈:链接统一 transition;展开时 chevron 翻转朝上并变主题色;下拉 opacity/transform 渐显;桌面端 pointer-events 桥接消除父子菜单 4px 间隙中断;支持键盘 focus-within。
  • 响应式适配:移动端(≤767)子菜单改为堆叠,三级用左侧竖线 + 缩进明显区分层级;深层级箭头用中性灰避免与展开态混淆;与汉堡菜单无缝衔接。

13.4 改动文件清单

文件改动
inc/builder-loader.php(新增)共享模块:hualin_builder_enqueue() + hualin_builder_app_markup(),统一媒体库入队与界面 DOM
inc/builder.phpenqueue 与界面标记改为调用共享函数
inc/builder-page.phpenqueue 与界面标记改为调用共享函数(修复漏调 wp_enqueue_media)
functions.php引入 builder-loader.php;注册并加载 assets/css/nav.css(位于 main 与 responsive 之间)
assets/builder/builder-core.jsimage 字段:硬 alert 改为友好内联提示 + 外链兜底
assets/css/nav.css(新增)一/二/三级导航完整样式与交互层
assets/css/main.css一级下划线收敛为仅顶级项;删除原 sub-menu 重复规则
assets/css/responsive.css删除重复的 sub-menu 规则(统一到 nav.css)

打包:Qi-theme-20260729.zip(v7 + 移动端 + 13.1~13.3,根目录 qi-theme/,58 文件,184.1KB)。


十四、2026-08-30 全栈主动审计(布局 / 间距 / 字体 / 颜色 / 交互 / 响应式)

按「全权自行决定 + 主动发现问题」的授权,对主题前端做了一次横切审计。审计手段:

  • 桩化 WP 函数真实渲染 inc/builder-render.php 的默认首页 Schema(16 区块全跑通,无运行时告警);
  • 静态扫描 5 个核心 CSS 文件的「token 引用一致性」与「响应式断点分布」;
  • 本地 php -S 起服务验证页面与资源均 HTTP 200;
  • PHP 全量 php -l、JS node --check、CSS 大括号平衡校验。

14.1 发现的问题与根因

颜色 / 无障碍(真实 WCAG 缺陷)

#问题根因严重度
C1主按钮 .btn-primary 金底白字对比度仅 2.09:1(AA 要求 ≥4.5:1)白色压在 #F5A524 金底上高
C2.rb-hero.accent / .rb-sec.bg-accent 区块正文仍为灰蓝 --c-muted,压在金色渐变底上对比度约 1.1:1,几乎不可见accent 底色变体只处理了 h2/eyebrow,漏了 .lead/p高
C3金色 #E08E12(--c-accent-d)用于小字号正文/标签/链接/图标,白底对比度仅 2.61:1装饰强调色被当作文字色滥用(≥12 处)高
C4引用了未定义的设计 token --c-navy-800、--c-navy-200增强层 token 补充时遗漏中
C5--c-accent-d 多处 fallback 写成 #C77A00,与真值 #E08E12 不一致,token 偶发未加载时颜色跳变早期硬编码 fallback低

交互(致命)

#问题根因严重度
I1移动端子菜单是「常驻展开」的静态堆叠,而父项本身是链接,点击父项直接跳转 → 子菜单实际点不到nav.css 移动端 position:static;opacity:1 且 JS 仅 $nav.on('click','a',...) 收起,无独立展开控件高
I2长菜单顶出视口、无法滚动;打开抽屉后背景仍可滚动移动端抽屉无 max-height/overflow、无 body 锁滚高
I3汉堡按钮展开后图标不变(无 X 反馈);aria-controls="primary" 指向不存在的 idheader.php 缺 id="primary"、无图标切换中
I4键盘无法到达三级菜单;Esc / 点外部无法关闭抽屉子菜单 visibility:hidden 阻断 Tab;抽屉无关闭通道中
I5滚动事件每次同步读取 scrollHeight/innerHeight,高频滚动掉帧未做 rAF 合帧低
I6跳转链接 .skip-link 聚焦时仍在视口外,键盘用户看不到去向header.php 用内联 left:-9999px 覆盖,未提供聚焦态中

布局 / 一致性

#问题根因严重度
L1粘性头部(72px)遮挡所有锚点跳转目标缺 scroll-margin-top中
L2.section-head 在 style.css 与 main.css 重复定义且值不一致(3.4vw vs 3.5vw)历史重复低
L3构建器页面容器固定 1120px,原生模板页面 1200/1320px → 跨页内容宽度跳动两套容器宽度中
L4.rb-hero-slider.hs-minimal 左对齐基线写死 1140px,与容器 1200 差 20px硬编码低
L5断点 max-width:768px 与主题主断点 767px 差 1px,768px 视口会同时命中两套规则出现错位off-by-one低
L6导航浮层 top:72px 写死,品牌/Logo 高度变化时错位未用 top:100%低

字体 / 性能

#问题根因严重度
T1同站二级标题两套字号(.section-head clamp 34 / 构建器 .rb h2 clamp 34,基准 3.5vw vs 3.6vw)未统一低
T2Google Fonts 无 preconnect,首屏字体阻塞缺预连接低

14.2 修复清单(已落地)

  • C1/C3/C5:新增 --c-on-accent:#12233A(金底上 7.9:1)、--c-accent-ink:#9A5B00(白底 5.43:1)两个「可访问色」。.btn-primary 改用 --c-on-accent;构建器内 12 处小字号/链接/图标由 --c-accent-d 改 --c-accent-ink;--c-accent-d 残留 #C77A00 fallback 统一为 #E08E12。
  • C2:.rb-hero.accent / .rb-sec.bg-accent 统一收敛为深墨蓝文字体系(h1/h2/eyebrow/p/.lead/h3/h4/btn-ghost 全部覆盖)。
  • C4:在 :root 补定义 --c-navy-800:#1B3A63、--c-navy-200:#CBD9E8(与 fallback 同值,消除「引用未定义 token」告警)。
  • I1/I2/I3/I4/I6:重写 nav.css 移动端 → 抽屉 max-height:calc(100dvh - var(--header-h)) + overflow-y:auto;为每个含子菜单的 li 由 main.js 注入独立 .sub-toggle(44×44 触摸目标,aria-expanded/controls 联动,同级互斥);html.nav-open 锁背景滚动;汉堡图标在「三横杠/叉」间切换并同步 aria-label;新增 Esc 关闭、点外部关闭、视口放大到桌面自动清理;桌面端 arrow 指示器保留,focus-within 仍保证键盘可达;靠右一级项下拉改为右对齐避免溢出。header.php 补 id="primary"、去掉 skip-link 内联隐藏。
  • I5:滚动逻辑改为 requestAnimationFrame 合帧 + 原生 passive 监听。
  • L1::root{--header-h:72px} + [id]{scroll-margin-top:calc(var(--header-h) + 16px)},移动端 --header-h 收敛为 64。
  • L2:删除 style.css 中重复的 .section-head,唯一定义留在 main.css。
  • L3/L4:.rb .container 改为 var(--container);.container.wide = calc(var(--container)+80px);.hs-minimal /hs-split 左对齐基线改用 var(--container)。
  • L5/L6:@media (max-width:768px) → 767px;导航浮层 top:72px → top:100%。
  • T1:构建器 .rb h2 基准统一为 clamp(26px,3.5vw,36px)。
  • T2:functions.php 注入 preconnect(googleapis / gstatic)+ dns-prefetch。
  • 额外:表格横向滚动容器补 tabindex/role/aria-label 使键盘可滚动;页脚社交图标 34→38/44 触摸目标。

14.3 验证结果

  • PHP 全量 php -l:通过(41 个文件)。
  • JS node --check main.js:通过。
  • 桩化渲染默认首页:16 区块渲染成功,无运行时告警;设计 token 引用未定义项:归零。
  • CSS 大括号平衡:5 个核心文件全部 OK。
  • 本地 php -S 服务:冒烟页与全部 CSS/JS 资源 HTTP 200。

14.4 改动文件清单

文件改动
style.css补 --c-navy-800/--c-navy-200/--c-on-accent;.btn-primary 改深字;[id] 锚点偏移;去重 .section-head;skip-link 聚焦可见
assets/css/nav.css重写移动端抽屉 + 子菜单折叠 + 汉堡图标切换 + 边缘溢出保护
assets/css/main.css统一 .section-head;页脚社交图标触摸目标放大
assets/css/responsive.css768→767 断点;--header-h 移动端 64;抽屉 top:100%;社交图标 44
assets/builder/builder-blocks.cssaccent 底文字对比度;12 处强调色改 --c-accent-ink;容器 var(--container);断点 767;h2 字号统一;表格滚动焦点
assets/js/main.js注入 .sub-toggle + 抽屉关闭行为(Esc/外部/互斥);rAF 滚动节流
header.phpnav#primary id;去掉 skip-link 内联隐藏;汉堡按钮加 data-label / 双图标
inc/builder-render.php表格容器加 tabindex/role/aria-label
functions.php字体 preconnect / dns-prefetch

打包:Qi-theme-20260830.zip(根目录 qi-theme/,58 文件,188.3KB)。冒烟工具:tools/smoke.php(桩化渲染 + token/断点自检)、本地预览 tools/smoke-out/index.html。


十五、高级动效系统(GSAP 3.13 + ScrollTrigger,2026-09-26)

目标:从「普通淡入」升级为高端设计作品集 / 创意机构官网级别的运镜,且全部可在后台控制。

15.1 后台设置(新增「高级动效」设置页)

inc/options.php 新增 motion 设置组,共 20 项,全部在后台「高级动效」标签页可视化调节:

类别设置项
总控启用高级动效、预设(含蓄 / 招牌 / 编辑感 / 张扬)、强度 50–150%
节奏缓动(expo / quint / quart / smooth)、时长 400–2400ms、错峰 0–260ms、位移 0–160px
首屏开幕开关、形式(遮罩揭开 / 压缩归位 / 幕布下落 / 大幅位移)、内容分层、滚动视差
区块标题开关、形式(遮罩 / 压缩 / 位移)
卡片依次错峰开关
图片开关、形式(遮罩 / 缩放 / 幕布)、轻微视差开关 + 强度 0–20%
细节导航栏入场、悬停微交互

预设与后台数值是相乘关系而非覆盖(shift / tempo 系数),保证「换预设」与「单独微调」都符合预期。所有字符串型设置走白名单枚举校验,数值走范围钳制,非法值回落到默认值。

15.2 架构(四个文件,单一来源)

文件职责
inc/options.php默认值 + 保存净化(唯一数据源)
inc/motion.php预设 × 强度 × 缓动 → 具体数值;状态类下发;资源入队
assets/css/motion.css元素的初始隐藏态与悬停微交互
assets/js/motion.js运镜引擎(GSAP 优先,内置 IntersectionObserver 降级)

GSAP 与 ScrollTrigger 随主题本地分发(assets/vendor/,3.13.0),不走 CDN —— 避免国内 CDN 不可用时整站动效失效。

15.3 三个关键机制(这三条是本系统能站得住的原因)

  1. 首个绘制前判定隐藏:初始隐藏态不写死在 HTML 里,而是由 <head> 中一段极小的内联脚本,赶在样式表解析前判定「是否运镜」再挂类。因此 JS 关闭 / 用户开启「减弱动态效果」时状态类根本不存在,内容直接可见 —— 连 <noscript> 都不需要。
  2. 注册表 + 交接(qi-in):CSS 的隐藏态选择器统一带 :not(.qi-in);元素动画结束时由 JS 挂上 .qi-in 并清除内联样式,等于把这个元素交还给常规样式表。没有这一步,GSAP 写下的内联 transform 会永久顶掉卡片悬停位移 / 图片悬停放大等既有交互。
  3. 孤儿扫描兜底:用与 CSS 完全相同的选择器再查一遍,凡「被隐藏了却没有任何进场认领」的元素补一次独立揭示。作用是把「选择器契约必须完美」降级成「契约 + 校验」—— 即使日后新增嵌套区块结构,最坏结果也只是少一段动画,不会变成内容看不见。

15.4 设计阶段自查并修正的 8 个缺陷

#缺陷后果修法
1bail() 先加 .qi-motion-off 又立刻被 markReady() 剥掉引擎异常时内容永久隐藏拆开「置就绪」与「强制显示」两个函数
2补间用 clearProps: 'clipPath'清掉内联裁切后 CSS 隐藏规则重新生效,文字进场后又消失保留内联终态;改为动画结束时走 qi-in 交接
3跳过 naturalWidth === 0 的图片所有 loading="lazy" 图片永不揭示移除该判断(给未加载完的图片做补间无害)
4卡片 / 图片补间未 paused加载瞬间就播完,滚动到根本看不到全部改 paused: true,由进入视口触发
5标题组只取每块首个 h2rb-section / rb-columns 嵌套块、rb-hero-split 的后续标题永久隐藏改为「遍历全部标题元素 + 按最近区块分组」
6选择器用 [class$="-row"]漏匹配 class="aw-row g3" 这类带修饰类的行容器改 [class*="-row"]
7GSAP 内联 transform 顶掉 CSS 悬停悬停位移 / 放大全部失效引入 qi-in 交接机制
8降级路径用负 rootMargin 观察被裁元素隐藏态的位移把元素自锁在触发区外;clip-path 还会让 IO 判不出相交改观察宿主容器 + threshold: 0,不用负 margin

15.5 独立验证(CDP 真实时间驱动,28 项断言全通过)

工具:tools/verify/run-motion.js(用 CDP 驱动真实浏览器,不走 --virtual-time-budget —— 虚拟时钟下 performance.now() 不推进,GSAP 时间线永远停在第 0 帧,终态断言会「照过」而实际一个动画都没播)。

验证台:tools/motion-lab.php → tools/motion-out/index.html,桩化 WP 函数后加载真实的 options.php + motion.php,把真实配置、真实状态类、真实 DOM 组装成可打开的页面。

四个场景 × 28 条断言:

场景断言要点结果
正常路径触发线内元素全部处于隐藏初态(证动画确实挂上);整页滚完 89 个目标元素全部回到设计态(不透明度 / 裁切 / 位移三项);幕布已从 DOM 移除;qi-in 全部交接;视差在两个滚动位置位移不同;卡片无内联 transform 残留20 项全过
减弱动态效果没有任何元素停在隐藏态;未挂动效状态类2 项全过
完全禁用 JS88 个元素无一个停在隐藏态(无死白块);html 上无任何动效状态类2 项全过
GSAP 被屏蔽降级路径把内容全部显示出来;交接完成2 项全过

验证发现并修复的真 bug:首屏轮播由 main.js 每 4.5s 自动轮换,而「激活页」的隐藏态规则是动态选择器 —— 换页后新激活页的内容会被 CSS 重新捕获为隐藏态,降级路径没有任何机制揭示它,第 2 页起整页看不见内容(GSAP 路径原本的监听还被「内容分层」开关误锁住了)。已改为两条路径共用统一的换页监听,并补了「换页后新激活页内容被正确揭示」的回归断言。

15.6 改动文件

文件改动
inc/options.php新增 motion 设置组(20 项)、白名单校验函数、范围钳制、后台标签页
inc/motion.php新增:配置解析 + 状态类下发 + 资源入队
assets/css/motion.css新增:初始隐藏态、悬停微交互、无障碍兜底
assets/js/motion.js新增:运镜引擎(GSAP 路径 + 降级路径 + 孤儿扫描)
assets/vendor/gsap.min.js新增:GSAP 3.13.0(本地分发,70.7KB)
assets/vendor/ScrollTrigger.min.js新增:ScrollTrigger 3.13.0(本地分发,43.1KB)
functions.php注册 inc/motion.php

十六、2026-09-26 六维审查(布局 / 间距 / 字体 / 颜色 / 交互 / 响应式)

工具:tools/verify/run-audit.js(三档视口 390×844 / 768×1024 / 1440×900 实测,量化取证后再改)。

16.1 发现的问题与根因

#问题量化证据根因
1导航当前项 / 悬停颜色对比度严重不足#E08E12 白底仅 2.61:1(AA 需 4.5:1)nav.css 用了装饰色 --c-accent-d 渲染文字;而主题早已约定「accent-d 只用于大面积填充/装饰,小字号文字用 --c-accent-ink」
2全站正文链接悬停同类问题同类色值,同一成因style.css 的全局 a:hover 也用了 --c-accent-d
3后台「全站容器最大宽度」在 ≥1024px 静默失效实测 1440px 视口容器恒为 1320px,与后台设置无关responsive.css(后加载)里的 :root{--container} 盖掉了后台注入的内联变量(同特异度按源码顺序胜出)
4移动端汉堡按钮命中区偏小实测 33×33仅靠 padding 撑尺寸;WCAG 2.5.8 AA 要求 24×24(勉强达标),44×44 才是舒适触达尺寸

16.2 修复

  1. nav.css:当前项 / 悬停 / 折叠指示统一改用 --c-accent-ink(#9A5B00,白底约 5.0:1)。
  2. style.css:全局 a:hover 改 --c-accent-ink;深色底场景(顶栏 / 页脚)各自已有的覆盖规则继续生效,未受影响。
  3. 容器宽度分级(≥1024px → 1200px,≥1440px → 1320px)从 responsive.css 移到最先加载的 style.css,让后台设置的内联变量能正常胜出;设计上的大屏分级得以保留。
  4. main.css:汉堡按钮加 min-width/min-height: 44px + 居中。

16.3 验证结果

指标修复前修复后
对比度不达标3 条0 条
命中区 <40×401 条(33×33)0 条
横向溢出(三档视口)无无(scrollWidth == clientWidth)
无法自动计算的项14 项(渐变底)人工核实:首屏渐变 #0F2A4A → #1B3A63 上白字 14.3:1 / 11.4:1,副标题 92% 白仍 >10:1,全部安全

静态复核:--container 现在只出现在最先加载的 style.css(级联根因已消除);保留的 --c-accent-d 用法只剩大面积填充(.btn-primary:hover 背景)与渐变,均为正确用途。

16.4 改动文件

文件改动
style.css容器分级迁入;全局 a:hover 换对比度达标色
assets/css/nav.css导航文字色改用 --c-accent-ink
assets/css/responsive.css移除会盖掉后台设置的 --container 覆盖
assets/css/main.css汉堡按钮命中区提升到 44×44

打包:Qi-theme-20260926.zip(根目录 qi-theme/,63 文件,254.9KB,含动效三件套与本地 GSAP/ScrollTrigger)。
打包脚本 package-qi-theme-v8.php 内置读回自检:条目数与源一致、路径前缀正确、无工装残留、关键文件齐全、每条与磁盘源码逐字节一致(CRC32 + 长度 + 条目头 CRC),全部通过。
验证工具(不参与部署):tools/motion-lab.php、tools/verify/run-motion.js、tools/verify/run-audit.js,报告落在 tools/verify/motion-report.txt 与 tools/verify/audit-report.txt。

16.5 已知技术债

  • 首屏轮播换页时的内容揭示依赖 MutationObserver 监听 .is-active 类变化;若日后把轮播改为改变 transform(而非切换类)来实现,需同步调整该监听。
  • 孤儿扫描的兜底揭示是「元素级淡入」,不含各自区块的设计化运镜(如遮罩揭开)。它只在选择器契约出现偏差时才会触发,属于安全网而非正常路径。

十七、2026 区块变体扩展(2026-09-26,用户授权「自行优化并实现」)

17.1 先定该补什么,再动手

对 assets/builder/builder-core.js 的注册表做了盘点:53 类区块里只有 14 类带样式变体。
逐个看下来,剩下的大致分三类,只有第三类值得动:

类别例子结论
纯排版件分隔线、间距、按钮、面包屑、标签组变体没有意义,保持单一形态
已有等效表达图片横幅 / 图片(两个键)+ 视觉重复的提示框合并比加变体更该做,但涉及历史数据兼容,本轮不动
形态单一 + 对转化关键发展历程、客户评价、关键数据、案例、服务、优势、图集、资讯、团队本批目标

判断标准落在一句上:这一块是不是「决策者会停下来读」的那一段。B2B 页面上真正被逐字读的
就是「案例怎么做的 / 别人怎么评价 / 你的数字是多少 / 你干过多久」——这几块如果只有一种长相,
等于把最重要的信息做成了最不显眼的样子。

17.2 第一批(v10):把「尺寸=重要性」这件事落到网格上

区块新增变体解决的问题
服务网格 servicesbento四块等大卡片分不清主次,首格放大成 2×2 后版式有了重心
图标特性 featuresbento同上
数据优势 advbento数值本身做视觉锚点,不再给数字套图标框
图片画廊 gallerybento首图放大,避免「九宫格缩略图墙」的均质感
案例网格 projectsstory从「图 + 标题」改成 挑战 → 方案 → 成果 三段式,带量化指标行
资讯列表 newseditorial大标题无图的编辑式排版:减少图片请求,LCP 更友好
专家团队 teameditorial去掉圆形头像框(小团队没有合规工装照时,圆框里塞一个字反而显敷衍)
图文英雄 hero-split新增 proof 字段首屏三秒要回答「你是谁 / 能做什么 / 凭什么信」,第三条最常被漏;证据条补它

配套的三项通用升级(都是全站生效,不属于某个区块):

  • 容器查询(container-type: inline-size + @container)
    解决「区块被拖进分栏半边后,视口断点感知不到容器只剩 ~550px,.g3 硬排三列 → 每格约 160px,
    文字挤成一柱」。
    只把「嵌套用的窄容器」声明为查询容器(.rb-columns .col / .rb-sec .sec-inner),
    顶层区块行为完全不变 —— 这是本次能做到「零回归」的关键。
  • 排版细节:text-wrap: balance 让标题不再留孤字;text-wrap: pretty 优化正文尾行;
    font-variant-numeric: tabular-nums 让价格 / 指标 / 表格数字等宽对齐。
  • 表格窄屏卡片化:B2B 询盘 60% 以上来自手机,宽表不该让用户横向拖。
    数据仍来自真实 <td>(配 data-label 提供字段名),SEO 与读屏不受影响。
  • 原生页面切换过渡:@view-transition { navigation: auto },纯 CSS、零 JS。
    刻意只做退场淡出:入场交给已有的 GSAP 首屏动画,避免同一层出现两种动画语言。

17.3 第二批(v11):补齐剩下三块「关键但形态单一」的

区块新增变体依据
发展历程 timelinerail 中轴轨道经典竖线把所有节点压在左侧,节点一多就是一条密不透风的长文;中轴交替把纵向长度砍掉近一半,年份放大成等宽数字后「这是时间线」一眼就懂
客户评价 testimonialswall 评价墙同质化等高卡片阵会让整块被眼睛跳过;让其中一条先站出来(尺寸=重要性),整块才有被读的可能
数据指标 counterband 冲击带常规四列把数字当「内容」放进页面流;决策者需要的是把数字当「证据」。独立深底、字号拉到 34~60px、项间发丝线分隔,并留数据来源脚注——没有出处的数字只会被当成广告语

三块共性:只复用已有设计语言(bento 的网格语言、tabular-nums、容器查询降级),
不引入新的机制、不引入 JS、三块都不含位移/缩放,因此没有新增动效需要被
prefers-reduced-motion 关闭。

17.4 三处同构契约(这是主题最容易出问题的地方)

每加一个变体都要同时改三处,缺一处就会出现「后台预览和前端不一样」:

  1. assets/builder/builder-core.js —— 后台实时预览(JS render + 字段定义 + 默认值)
  2. inc/builder-render.php —— 前端渲染(PHP 同构分支)
  3. inc/demo-schemas.php —— 演示内容(否则变体永远没有可核对的样本)

本批新增的 bentoCells / metricPairs(JS)与 hualin_bento_cells / hualin_metric_pairs(PHP)
是刻意写成一一对应的,字段名、分隔符(| 分条、: 分主次)都一致。


十八、本轮挖出的真实缺陷(都不是本次改动引入的)

18.1 bento 网格行高被撑到 316px,格内留下近 200px 死白

初版给网格写了 grid-auto-rows: minmax(148px, auto),想让每行至少 148px。
实测(tools/verify/check-variants.js 读计算值)行高变成 316px 316px、首格 648px。
根因:跨 2 行的首格会让 minmax(148px, auto) 的最小值按跨行数重复计入——
148 × 2 + 16(gap) ≈ 312,正好是被撑出来的高度。
修复:删掉 grid-auto-rows,行高完全交给内容,另用 min-height: 148px 给每格兜底。
修复后实测 rows=148/148 格高=148~312。

18.2 容器查询阈值不能照抄视口断点

若把视口断点(767 / 1023)直接当容器阈值,会出现 768~782 视口区间容器只有 ~753px、
却按「容器 > 767 就三列」判断的错配。
修复:容器规则只服务嵌套场景,阈值按内容宽度取(480 / 640 / 880 / 1000),
与视口断点之间不存在一致性约束,也就不可能错配。

18.3 对比度检查器把半透明底色当成不透明色(工具 bug,会产生假警报)

区块总览页报出 .rb .btn-ghost「白字白底 1:1」。查证结果是工具的问题:
run-audit.js 的 toRGB() 里写着 cx.fillStyle='#000'; cx.fillStyle=v; cx.fillRect(...)——
canvas 的 fillStyle 赋值不会落笔,必须 fillRect 才生效,中间少了那一次 fillRect,
黑色底压根没画,于是半透明色直接在透明画布上落笔,取色拿到的是「未预乘的原始通道」,
rgba(255,255,255,.12) 被读成了纯白。
这意味着所有半透明背景(ghost 按钮、毛玻璃卡片)都会被误判。
修复两处:

  1. toRGB() 改为 clearRect 后直接落笔,alpha 交给新增的 alphaOf() 单独判定;
  2. bgOf() 增加 alpha ≥ 0.95 门槛——半透明底只是叠在真背景上的一层,遇到就继续往上找,
    直到找到真正承托文字的实底。否则会得出「白字白底」这种假警报(真实是白字叠在深蓝大区上)。

18.4 首屏大区只有渐变、没有实底(主题短板)

.rb-hero.navy / .rb-hero.navy.s1–s5 / .rb-hero.accent / .rb-hero-slider 都只用
background: linear-gradient(...) 简写。简写会把 background-color 重置为 transparent,
于是这些区块没有任何实底,两个后果:

  • 不支持渐变的渲染环境里,白字直接落在白底上;
  • 对比度工具无法为「白字叠深蓝」这类最关键的组合算出比值,只能标「无法计算」跳过——
    最该被盯住的地方反而没人管。

修复:为全部深底大区补 background-color 基色,一律取对文字对比度最不利的那一端渐变停靠色
(白字配深底取较浅端,深字配亮橙取较深端)。同时补 .bento > .bnt.hero / .bnt.pic / .sty-pic
(bento 强调格偏偏是数字和标题存在感最强的地方)。
注意:.rb-hero.navy.s1–s5 的优先级是三个类(0,3,0),必须逐条补,只写 .rb-hero.navy 盖不住。
效果:区块总览页「无法计算」从 12 项降到 0 项,深底白字组合全部进入真实校验。

18.5 验证脚本的 CDP 调用没有超时,Chrome 一死脚本永久挂起

现场表现很有迷惑性:node 进程还在、chrome 进程已经没了、什么都不输出。
根因:send() 既不设超时、也不监听 socket 关闭,pending 里的 Promise 永远不 settle。
修复:五个验证脚本统一补「超时即抛错 + 连接关闭即刻让所有在途调用失败」,
并给未被 await 的调用挂空 catch,避免 unhandledRejection 直接把进程带走。

18.6 两处「凭断点习惯猜期望值」的断言写错

  • 想当然认为「768 视口下顶层 .g3 该是三列」,实测两列被判失败——
    回到源码发现主题 768~1023 本来就是两列(builder-blocks.css 第 91 行)。
  • 拿「整页带 data-label 的 td 数」去比「第一个表的 td 数」——两个口径不同域,必然对不上。

教训一致:期望值必须回到源码或同域数据里取,不能凭经验补。
前者已改成按源码三段断点推导,后者已拆成「页内总数相等」+「首个表格无漏标」两条同域断言。

18.7 修完 CDP 超时后,还有第二条挂起路径(websocket 既不开也不报错)

补完 18.5 之后仍然复现了一次同样的表象:node 还在、chrome 没了、零输出,
而且这次一挂 10 分 44 秒。逐段排查后确认不是同一处:

launch() 里的顺序是「轮询 /json/list 拿到目标 → connect(wsUrl)」。
/json/list 有端口探测超时兜底,但 connect() 只给 error / close 事件挂了失败回调——
假定 websocket 一定会走到 open 或 error。Chrome 在拿到目标之后、ws 握手完成之前退出时,
这个假设不成立:socket 停在 CONNECTING,两条回调都不触发,connect() 的 Promise 永久 pending。
偏偏 launch() 这一句在 main() 的 try 外面,连「抛错退出」这条退路都没有。

也就是说:逐点补超时是在打地鼠,每补一处就暴露下一处。修复分两层:

  1. 补上缺口 —— 五个脚本的 connect() 统一给 open 事件加 15s 超时(成功路径行为不变);
  2. 加全局看门狗(真正的兜底)—— run-motion.js 在 main() 入口挂 210s 定时器,
    到点先把已完成的断言落盘再以 exit 3 强退,并打印 WATCHDOG_TIMEOUT。
    原则从「想办法证明它不会挂」改成「保证要么出结论、要么明确报超时」,
    永远不再出现静默挂十几分钟、还误以为是产品问题的情况。

补丁落在 tools/verify/_patch-opentimeout.js(一次性脚本,已执行并核对 5 个文件语法),
跑完即删;经验同时写进 19.7 技术债第 4 条。

18.8 环境坑:残留 Chrome 会占住固定调试端口

固定 --remote-debugging-port=9333 的副作用:上一次会话遗留的 Chrome 没退干净时,
新脚本会连上旧实例——这比挂起更危险,因为它量的是旧页面,结论看起来「正常」但完全错。
排查动作固定为两步:

netstat -ano | grep ":9333"          # 端口有没有被占
tasklist | grep -i chrome            # 有没有孤儿 chrome

确认空闲再跑。运行期若出现「node 在 / chrome 无 / 端口不监听」,
直接判定为挂起,taskkill 掉 node 后重跑,不要等它自己好。


十九、验证与交付

19.1 变体布局断言(新增 tools/verify/check-variants.js)

之所以不再沿用「命令行传表达式给探测脚本」的用法:表达式要过 shell 一层,
里面的双引号会被吞掉(querySelector(".x") 变成 querySelector(.x) → 语法错误),
异常又被吞进 stderr,最后只看到「空输出」,白白怀疑 CSS。断言一律写进文件,由 node 直接投给 CDP。

覆盖:2 个页面 × 3 个视口(1440 / 768 / 390),153 条断言,四条主线:

  1. 横向溢出 —— bento / 卡片化表格最容易顶破视口(含越界元素定位输出)
  2. 容器查询按内容宽降列 —— 期望列数由容器内容宽推导,不写某一视口的快照;
    并反向断言顶层 .g3 仍按视口断点排布(证明容器规则没外溢)
  3. bento 行高 —— 防止 316px 死白回归(18.1)
  4. 变体可见性与关键布局 —— 中轴时间线节点是否真的左右分列、
    评价墙首条是否真的跨了两列(宽度实测 ≈ 单格 ×2)、冲击带列数 / 脚注、
    以及节点数跨视口完全一致(CSS 只该改布局,不该让内部节点凭空消失)

19.2 六维审查(布局 / 间距 / 字体 / 颜色 / 交互 / 响应式)

tools/verify/run-audit.js 增加 AUDIT_PAGE=... 支持,可审任意页面,
报告写入 audit-report-<页面>.txt,不会覆盖动效页的报告;
CONTRAST_SEL 补入全部新变体的文字元素(深底带这类「自定义底色 + 白字」组合最需要真算一遍)。

结果(三视口,均为修复 18.3 / 18.4 后的定稿数据):

页面横向溢出对比度不达标对比度无法计算交互目标 <24×24
区块总览 demo-out/blocks.html000(104 项全数真算)0
动效实验页 motion-out/index.html000(51~52 项全数真算)0

两页的「无法计算」都从两位数降到 0,意味着深底白字、页脚浅字这些最该被盯住的组合
全部进入了真实比值校验,而不再是「跳过 = 没人管」。修复前的对照:
区块总览页 12 项无法计算 + 1 项假警报;动效页 11 项无法计算。

页脚预设的修复同样落在这里:.site-footer.f-s1 / .f-s3 原来也是只有渐变没有基色,
页脚是每页都出现、最该纳入 WCAG 的表面,现已补基色(assets/css/main.css 末尾)。

19.3 动效契约回归

tools/verify/run-motion.js:28 / 28 通过,exit 0(4 场景:正常 / reduced-motion / 无 JS / 无 GSAP),
在补完页脚基色(18.4 最后一项)之后的最终代码上重跑确认,未因底色改动退化。
本轮新增的 bento 与 editorial 标记已经出现在动效验证台的真实首页 Schema 里
(12 个 bento 格子、3 条编辑式资讯),因此动效契约是在新变体上被验证的,
不是「拿旧页面测新代码」。关键不变量仍全部成立:
.qi-in 交接 104 个元素零未交接、卡片交接后无内联 transform(悬停位移不会被顶掉)、
滚动不改变任何属性、三种降级路径下均无元素停在隐藏态。

这次重跑同时暴露并修掉了 18.7 那条挂起路径:第一轮后台串行跑时
run-motion.js 卡在 connect() 上 10 分钟零输出;补 open 超时 + 全局看门狗后重跑,
全程有界并拿到 28/28。这也是为什么最终结论只认这一轮的 exit 0。

19.4 渲染校验台

tools/demo-check.php:9 个演示页 / 156 个顶层区块(新增 3 个演示区块)/
53 / 53 类区块覆盖 / 存盘往返零丢失 / 0 PHP 告警 / 0 乱码,PASS(exit 0)。

19.5 改动文件

文件改动
assets/builder/builder-core.js新增 bento / story / editorial 变体;timeline 加 rail;testimonials 加 wall;counter 加 band + note 字段;hero-split 加 proof 字段;table 输出 data-label
assets/builder/builder-blocks.css追加 v10(容器查询 / bento / story / editorial / 证据条 / 表格卡片化 / 排版细节)与 v11(rail / wall / band / 深底基色)两大段
inc/builder-render.php与 JS 一一对应的 PHP 同构分支;hualin_bento_cells / hualin_metric_pairs 两个辅助函数
inc/demo-schemas.php「NEW · 2026」区块总览示范段(v10 八块 + v11 三块)、首页 features→bento、news→editorial
assets/css/main.css页脚预设 .f-s1 / .f-s3 补 background-color 基色(同 18.4 的成因)
tools/verify/check-variants.js新增:变体布局断言器(153 条)
tools/verify/shot-variants.js新增:变体截图核对器(15 个变体 × 2 视口)
tools/verify/probe.js新增:页面探查器(布局问题先量再说)
tools/verify/run-audit.js支持 AUDIT_PAGE;修 toRGB 半透明色 bug;bgOf 加 alpha 门槛;对比度选择器补新变体;CDP 超时
tools/verify/run-motion.js / shot-variants.js / probe.jsCDP 超时 + 断连即失败 + websocket open 超时;run-motion.js 另加 210s 全局看门狗
tools/verify/zip-selfcheck.php新增:交付包读回自检(逐条 CRC32 + 关键文件命中关键字 + 根目录形态)

19.6 交付产物

package-qi-theme-v9.php(php package-qi-theme-v9.php Qi-theme-20260926-v4.zip):

  • Qi-theme-20260926-v4.zip —— 64 条目 / 287.4 KB / 根目录 qi-theme/(正斜杠)
  • 打包前对 43 个 PHP 文件跑 php -l,全部通过(语法错误的主题包上传即白屏,必须本机拦掉)
  • 读回自检全部通过:条目数与源一致、前缀正确、无 tools / _ud / *.bak 等工装残留、
    关键文件齐全、每条与磁盘源码逐字节一致(CRC32 + 长度 + 条目头 crc/size 同步核对)
  • 另跑 tools/verify/zip-selfcheck.php 做内容级读回确认(定稿结果):
    包内条目 64,CRC 逐条校验通过 64 / 根目录 qi-theme / 包体 287.4KB,
    且 main.css(页脚基色)、builder-blocks.css(v11 三变体)、demo-schemas.php、
    builder-core.js、builder-render.php 五份关键文件逐一命中最新改动关键字 → SELFCHECK_PASS
  • 历史包(v2 / v3 与更早)均保留未删,别传错

19.7 已知技术债

  1. 图片横幅 image 与 imagebox(图片块)、三个视觉重复的提示框:
    属于「已有等效表达」,合并比继续加变体更该做,但涉及历史 schema 兼容,本轮未动。
  2. 表格卡片化只覆盖窄容器与窄视口:若日后有人把宽表放进两栏页面且单栏宽度在 641~880px,
    仍会出现横向滚动(tbl-wrap 自带 overflow-x:auto,不会破版,但需要横拖)。
  3. @view-transition 的降级面:仅在支持该特性的浏览器生效,其余浏览器就是普通换页,
    属纯渐进增强;但若日后给入场也加页面过渡,必须先把 GSAP 首屏动画让开,否则同层两种动画语言。
  4. 验证脚本依赖本机 Chrome 的默认安装路径(C:/Program Files\Google\Chrome\Application\chrome.exe),
    换机器需要改常量。挂起这道坎共踩两次(18.5 / 18.7),现在有三层防护:
    CDP 调用超时 → websocket open 超时 → 全局看门狗(到点落盘 + exit 3)。
    首选仍是前台串行执行,不要嵌套在后台任务链里跑。
  5. zip-selfcheck.php 是交付前的最后一道闸(tools/verify/zip-selfcheck.php):
    逐条 CRC32 校验包内 64 个条目 + 按关键字核对 5 份关键文件确实含最新改动 + 确认根目录形态。
    编译型工具链(php -l / node --check)只能证明源码合法,
    证明不了「用户下载到的那个 ZIP 里装的是这份源码」——这道检查补的就是这个空档。

二十、移动端排版精修 + 页脚重做(2026-09-26)

用户诉求原话:「现在修复移动端的排版间隔这些,做精细一些,然后优化移动端的页脚,现在的太丑了。」
结果:移动端排版/间隔全部量化达标,页脚从「能看」做到有结构层级;
过程中另外挖出两处与移动端无关、但桌面端一直存在的错位缺陷,一并修掉。

20.1 移动端排版与间隔(精修层)

上一轮已在 builder-blocks.css 末尾建了「移动端精修层」(仅 @media (max-width:767px),
桌面零改),本轮把剩余的不一致收敛干净,全部以度量收口(tools/verify/mobile-audit.js,390px):

指标结果
间距栅格(4px 基准)合规5 / 5 页全部合规(首页 / 区块总览 / 页脚 3 个预设)
页脚交互目标 < 44×440 个(修复前 18 个)
首屏幻灯片单列 358px、gap 24、容器内距 16、文字左边缘唯一(16)
区块纵向节奏紧凑 28 / 标准 40 / 舒展 52 三档,无一刀切
卡片内距统一 16px(= 页面容器内距,缩进正好一档)

关键取舍:卡片内距初稿取 18px,度量时发现自己违反了"4px 栅格"这条自定规矩(18 不是 4 的倍数),
改为 16px —— 既回到栅格上,又让「容器内距 → 卡片内距」构成干净的 16 → 32 缩进关系。
这类"自己立的规矩要自己守"的检查必须由脚本做,靠眼睛看不出来。

20.2 移动端页脚重做

嫌丑的根因是结构缺失:原来就是「几栏文字 + 一行版权」平铺,没有层级、没有触达尺寸、没有分隔。

重做后的移动端页脚(footer.php + assets/css/main.css + assets/css/responsive.css):

  • 语义化底部两行:版权行与链接行(隐私政策 / 网站地图 / ICP 备案)拆成 .fb-copy + .fb-links
    两个独立结构块,分隔符交给 CSS 发丝线(.fb-links li + li::before),
    不再靠硬编码全角「|」拼字符串 —— 后者在换行、无障碍朗读、多语言下都是坑。
  • 栏标题带强调条:h4::before 3px 高亮竖条(深底预设自动换品牌金)。
  • 栏间发丝线分隔:.footer-cols > *{border-top:1px solid rgba(255,255,255,.14)},
    浅色预设走深色线;折叠栏 summary 与栏体各 24px 内距,视觉分组明确。
  • CTA 不再全宽长条:改 inline-flex 自适应宽 + min-height:44px(原来是一条横贯屏幕的色块)。
  • 链接两列网格:grid-template-columns:repeat(2,1fr)、行高 44,17 条链接全部达标;
    联系方式一行一条、min-height:44px,且不拆「电话:」与号码(避免孤字换行)。
  • 移动端无 hover,补 :active 按压反馈。
  • 总高由 1181px 收敛到 995px(内容更少留白更克制),同时触达目标 18 → 0。

20.3 本轮挖出的两处桌面端缺陷(都不是本次改动引入的)

方法说明:新写了两个探针 tools/verify/align-audit.js(对齐一致性)与
tools/verify/hero-grid.js(首屏网格落位)。它们一上线就把移动端之外的旧问题翻了出来——
这说明「按模块量」比「按投诉量」能挖得更深。

(a) 眉标与标题对齐不一致(.rb-sec)

rb-sec 区块渲染时给眉标硬写了 style="text-align:left",但它的 h2 / .lead 都是居中版式
(.rb h2{text-align:center},且 h2 下方那条品牌强调线是 margin:14px auto 0 居中排布)。
结果:眉标贴左、标题正文居中,实测偏差 -179px ~ -360px,1440/1024/768/390 四个视口一致复现。

修法:去掉该内联样式(inc/builder-render.php + assets/builder/builder-core.js 两处同构渲染)。
同时确认另外 4 个确实该左对齐的区块(rb-hero / rb-about / rb-hero-split / rb-mediatext)
保留内联左对齐 —— 它们的 h2 本身就是左对齐,写左对齐是对的,不要跟着一起改。

(b) 首屏幻灯片桌面端被 CSS Grid 打散成 2×2

.rb-hero-slider.hs-split .hs-in 是 2 列网格(1.1fr .9fr,::after 是右侧那块装饰面板),
但文字元素是平铺的直接子元素,Grid 会逐个自动落位:

修复前 @1440:   文字左边缘集合 = [84, 802]  ⚠ 不止一列
  span.ey   top=155 left=  84  678×20      右列还有 h1 与按钮
  h1        top=109 left= 802  554×119     ← 标题跑到右列与眉标左右并列
  p         top=279 left=  84  678×32
  a.btn     top=280 left= 802  554×51
  ::after(装饰面板)落在第 3 行左列

即屏幕上渲染成「眉标在左、大标题在右、段落回左、按钮在右、装饰块沉底」——
与「文字一列 + 装饰图一列」的设计意图完全不符。1440 / 1024 / 768 全部复现,只有 ≤767 单列时正常。

修法:给文字加 .hs-txt 包裹层,让它成为唯一的网格项
(inc/builder-render.php + assets/builder/builder-core.js;
结构对齐已经做对了的 rb-hero-split,它用的是 .hs-t 包裹层)。
修复后:文字左边缘集合 [84],hs-in 高度由 497px 收敛到 222px(文字列 207 + 面板 220 对齐居中)。

⚠️ 加这一层包裹触发了 12 处选择器联动(这是本轮最值得记的一条教训)——
凡把「容器的直接子元素」当目标集合的选择器,语义会整体失效:

文件处数形态
assets/css/motion.css3隐藏初态 容器 > *:not(.qi-in)
assets/js/motion.js3TARGETS.heroLayers / SLIDE_LAYERS / 换页重播 q()
tools/verify/run-motion.js5验证台自持的 HIDDEN_SEL + 内联探针
tools/motion-lab.php1渲染台内联探针

漏改的后果很真实:只改动画文件不动验证台,run-motion 立刻报
FAIL 全部目标元素均已完成 qi-in 交接 [未交接 1: div.hs-txt] ——
这是验证台选择器漂移造成的假失败(引擎实际交接的是 .hs-txt > * 四个真实层)。
判据:报错元素是包裹层自己时,先怀疑验证台选择器没同步,而不是运行时坏了。
同步后 qi-in=104 未交接 0、换页层数恢复 n=4,28 / 28 通过。

20.4 本轮新增/改动文件

文件改动
assets/builder/builder-blocks.css移动端精修层补眉标左对齐(hs-split / hs-minimal);新增 .hs-txt 网格项规则(min-width:0)
inc/builder-render.phprb-sec 眉标去掉硬写左对齐;hero-slider 补 .hs-txt 包裹层
assets/builder/builder-core.js同上两处(与 PHP 1:1 同构)
assets/js/motion.js3 处首屏分层选择器改为 .hs-in > .hs-txt > *
assets/css/motion.css3 处隐藏态选择器同步为 .hs-in > .hs-txt > *
tools/verify/align-audit.js新增:对齐一致性审计(4 视口 × 2 页,Range 文字测量)
tools/verify/hero-grid.js新增:首屏网格落位探针(矩形相交判「并列」)
tools/verify/run-motion.jsHIDDEN_SEL + 4 处内联探针选择器同步;补"改结构要同步 4 处"的警示注释
tools/motion-lab.php内联探针选择器同步
tools/verify/zip-selfcheck.php改为可传参(默认取最新 zip)+ 关键字清单扩到 9 份文件、覆盖本轮全部修复

20.5 验证与交付(本轮定稿数据)

全部为同一份冻结代码上的前台串行结果:

检查结果
tools/demo-check.php 渲染校验台PASS(9 页 / 156 顶层区块 / 53-53 类覆盖 / 0 告警 / 0 乱码)
tools/verify/check-variants.js153 条断言,失败 0 条
tools/verify/run-audit.js 六维审查横向溢出 0 / 对比度不达标 0 / <24×24 目标 0
tools/verify/run-motion.js 动效契约28 / 28 通过,exit 0
tools/verify/align-audit.js(新增)ALIGN_AUDIT_PASS —— 4 视口 × 2 页,0 组错位(修复前 24 组)
tools/verify/hero-grid.js(新增)HERO_GRID_PASS —— 4 视口子元素均上下堆叠(修复前 1440/1024 为两列)
tools/verify/mobile-audit.js 移动端度量5 / 5 页 4px 栅格合规、页脚 0 个 <44×44 目标

⚠️ 执行纪律(又踩一次):把 6 个验证器串在一个后台任务里跑,check-variants 卡死 11 分钟零输出
(多个 Chrome 实例共用/争抢 profile 目录)。改用前台逐个串行后全部秒级完成。
结论:验证器一次只跑一个,不要嵌套进后台任务链。

交付产物(php package-qi-theme-v9.php Qi-theme-20260926-v5.zip):

  • Qi-theme-20260926-v5.zip —— 64 条目 / 296 KB / 根目录 qi-theme/(正斜杠)
  • 打包前对 43 个 PHP 文件跑 php -l,全部通过
  • 打包器读回自检:条目数与源一致、前缀正确、无 tools/_ud/*.bak 残留、关键文件齐全、
    每条与磁盘源码逐字节一致(CRC32 + 长度 + 条目头 crc/size 同步核对)
  • tools/verify/zip-selfcheck.php 内容级读回:64 条 CRC 全部通过 /
    9 份关键文件逐一命中最新改动关键字 / 根目录 qi-theme / 包体 296KB → SELFCHECK_PASS
  • 历史包(v2 / v3 / v4)均保留未删,别传错

二十一、页脚再精修:去竖线 + 窄屏收起(2026-09-27,v6)

用户看过 v5 后的原话:「页脚的 fb-links 太丑了,很突兀,然后移动端的页脚依旧很拖沓」。
两条反馈对应两个独立根因,逐个定位、逐个量化。

21.1 根因一:fb-links 的竖线分隔符

v5 版权行的分隔符是 CSS 生成的 .fb-links li + li::before
(width:1px; height:11px; margin:0 12px; background:currentColor; opacity:.32)。
它在深蓝渐变底上是一条比文字还亮的竖棍,插在「隐私政策」「网站地图」「ICP 备案号」之间 ——
正是用户说的「突兀」。另外整块 .footer-bottom 是 flex-direction: column; align-items: center,
一个居中堆叠的小块挂在通栏分隔线下面,和上方左对齐的四栏栅格不同轴,观感上「飘」。

改法:

项改前改后
分隔符li + li::before 1px 竖线彻底删除,改用 gap: 0 20px 间距分组
桌面布局column + align-items:center(上下两行居中)row + justify-content:space-between(左版权 / 右链接,与四栏同轴)
移动布局居中两行保持居中两行(窄屏没有「左右」可分),gap:0 16px

21.2 根因二:移动端「拖沓」= 折叠栏默认全展

实测(390×844):<details class="f-col f-col-nav" open> 与 f-col-services open 两个都带 open,
默认全展开,各占 241px,两栏合计 482px;整块页脚 995px —— 一屏高度(844)还装不下。

改法:折叠栏改为窄屏默认收起、桌面常开。这里的取舍值得写清楚:

  • ❌ 不用 CSS 反杀。想「HTML 写 open、CSS 在窄屏关掉」是做不到的;反过来「HTML 不写 open、
    CSS 在桌面强制展开」也不行 —— Chrome 131+ 把 <details> 折叠体挪进了 ::details-content
    并施加 content-visibility: hidden,老那套 details:not([open]) > *{display:block} 已经压不住
    (本机 Chrome 154 实测)。而且一旦 CSS 失效,桌面端会出现「折叠着且点不动」的死页脚
    (桌面 summary 是 pointer-events:none),属于高危失败模式。
  • ✅ 用 JS 判断视口,HTML 语义一个字不改(<details open> 原样保留):
  1. 无 JS 时页脚照常全展开,不会出现「页脚少两栏链接」的静默故障(实测停在 915px);
  2. main.js 由 functions.php 以 in_footer=true 输出,是阻塞式脚本且位于 body 末尾,
    执行发生在首个 paint 之前,所以窄屏收起不会闪一下;
  3. 用户手动展开过的栏打 data-qi-user 标记,resize 时不再被自动收起
    (桌面 summary 已是 pointer-events:none,所以这个标记只可能在窄屏产生)。

配套收紧留白(全部落在 4px 栅格):栏内上下内距 24 → 16px、页脚内距 24/20 → 20/16px、
取消 summary 多余的 8px 下边距(展开时正文自带 padding-top:8px,收起时那 8px 就是纯白空)。

21.3 顺带修掉一个 WCAG AA 违规(门禁盲区暴露)

把 run-audit.js 首次指向真实页脚页(AUDIT_PAGE=tools/footer-out/s1.html)后立刻报出:

✗ 命中区小于 24×24(低于 WCAG 2.5.8 AA 底线)共 3:a(52×22)「隐私政策」 a(52×22)「网站地图」 a(143×22)「豫ICP备20260」

桌面端版权链接是 13px 字 × line-height 1.7 ⇒ 22px 高,低于 AA 的 24×24。
这是既有缺陷,不是本轮引入的:该门禁此前只跑过 motion-out / demo-out 两类页面,
而它们用的是 get_footer() 空桩 —— 根本没有主题页脚,所以这条一直没被看见。
修法:.footer-bottom a 加 min-height: 24px(移动端另有 44px 规则,同选择器更靠后)。
教训:审计器覆盖不到的表面 = 没有审计,页脚页要固定进门禁。

21.4 本轮改动文件

文件改动
footer.php仅注释更新(说明窄屏收起由 JS 完成、HTML 语义不变);结构零改动
assets/css/main.css.footer-bottom 改左右信息条 + 去掉 li+li::before 竖线 + 链接补 min-height:24px;新增 .f-s5 底部居中兜底
assets/css/responsive.css移动端版权区居中两行(去竖线);栏内距 24→16;页脚内距 24/20→20/16;summary 去 8px 下边距;f-s4 22→20px(顺手补上 4px 栅格)
assets/js/main.js新增第 7 节:页脚折叠栏「桌面常开 / 窄屏收起」
tools/footer-lab.php内联真实 jQuery(WP 自带 3.7.1) + 真实 main.js;缺 JS 依赖直接 exit 1;额外产出 s1-nojs.html 降级页
tools/vendor/jquery.min.js新增(dev-only,取自本机 WP 的 wp-includes/js/jquery,不进交付包)
tools/verify/mobile-audit.js页面清单 5 → 8:补 s4 / s5 两个非网格预设 + s1-nojs 无 JS 降级门禁
tools/verify/zip-selfcheck.php关键字清单加 main.js 等 4 份文件;新增 $absent 否定清单(给「删掉的规则」上护栏,防被加回来)

为什么给 footer-lab.php 装上 jQuery:它是只内联 CSS、不跑 JS 的台子,
量出来的永远是「全展开」的旧状态 —— 拿它验「窄屏收起」等于自欺。
现在真实执行 main.js,并打 data-qi-js 标记自证脚本确实跑过。

21.5 验证与交付(v6 定稿数据)

全部为同一份冻结代码上的前台串行结果:

检查结果
tools/footer-lab.php7 页产出 OK(s1…s5 + 长文案 + 无 JS 降级)
tools/verify/mobile-audit.js 3908/8 页 4px 栅格全合规、页脚 0 个 <44×44 目标
├ 页脚总高s1 995 → 635px · s2 995 → 635 · s1-long 1071 → 711 · s4 639 · s5 648 · 无 JS 降级 915px(守住了)
├ 折叠栏高度快速导航 / 项目服务 各 241 → 77px(390);桌面 1200 下 230 / 194px(仍常开)
├ 版权区高 85 → 81px;结构行数 2;链接 3 条一行排开
probe.js 窄屏(390)open:false、boxH:77、链接处 elementFromPoint 返回 DETAILS.f-col-services ⇒ 内容真的不渲染、不可点(不是「只对不上高度」)
probe.js 桌面(1200)open:true、clickable:true、页脚 389px ⇒ 桌面无回归
probe.js 无 JS(390)qi-js=NO、open:true、clickable:true、915px ⇒ 降级正确
tools/verify/run-audit.js(AUDIT_PAGE=tools/footer-out/s1.html)exit 0 —— 三视口 横向溢出 0 / 对比度不达标 0 / <24×24 目标 0(修前 6)
tools/verify/run-audit.js(AUDIT_PAGE=tools/demo-out/blocks.html)exit 0 —— 横向溢出 0 / 对比度 104 项全过 / <24×24 目标 0 ⇒ 无回归
tools/demo-check.phpPASS(9 页 / 156 顶层区块 / 53-53 类 / 0 告警)
打包器 + zip-selfcheck.phpSELFCHECK_PASS

交付产物(php package-qi-theme-v9.php Qi-theme-20260927-v6.zip):

  • Qi-theme-20260927-v6.zip —— 64 条目 / 297.8 KB / 根目录 qi-theme/(正斜杠)
  • 打包前 43 个 PHP 文件 php -l 全通过;读回自检每条与磁盘源码逐字节一致(CRC32 + 长度 + 条目头)
  • zip-selfcheck.php:64 条 CRC 全过 / 11 份文件正向关键字命中 / 2 份文件否定关键字确认已删除规则未回归 → SELFCHECK_PASS
  • 视觉对照:Qi-theme-页脚改版对比.html(自包含,改前/改后 × 移动/桌面 四张真实渲染截图)
  • 历史包(v2 / v3 / v4 / v5)均保留未删,别传错

21.6 遗留 / 技术债

  1. run-audit.js 一次只能审一页(AUDIT_PAGE 单值)。页脚页已能审,但要手动指定;
    理想是支持多页列表并纳入默认。本轮未做,已在 21.3 记录教训。
  2. mobile-audit.js 的「体高」列对折叠状态的 <details> 不可用(Chrome 新版会给出
    子元素几何盒子而非绘制高度,s1 出现 h=77 却 体高=140 的矛盾值)——
    判断收起与否要看 h 列,或直接用 probe.js 打 elementFromPoint。已在本轮说明中标注。
  3. footer-lab.php 依赖 tools/vendor/jquery.min.js 这份 dev-only 副本;
    该目录不进交付包,但换机器需重新从 WP 的 wp-includes/js/jquery/ 复制一次。

二十二、修复「构建器后台无法切换编辑目标」(2026-09-27,v7)

22.1 现象

在独立构建器页(后台子菜单「Qi-Theme构建器」,页面 slug builder)里,顶部「编辑目标」
下拉框选另一个页面 → 点「切换」→ 画布内容纹丝不动,看着就像切换按钮失效。
PHP 侧其实是好的:表单是 method="get",qi_builder_page_target() 能正确读出 ?target=ID,
HualinBuilderData 里注入的也是目标页面的 schema。问题不在服务端。

22.2 根因:脚本依赖方向写反 ⇒ 初始 schema 被 localStorage 脏草稿顶掉

关键在 inc/builder-loader.php 的 wp_register_script() 依赖声明。原写法把方向写反了:

// ❌ 错误:把 admin 声明成「依赖 core」
wp_register_script( 'hualin-builder-core',  $base . 'builder-core.js',  array( 'jquery' ), $ver, true );
wp_register_script( 'hualin-builder-admin', $base . 'builder-admin.js', array( 'hualin-builder-core' ), $ver, true );

WP 的规则是被依赖的先打印。上面这种写法会让打印顺序变成 core → admin,于是:

  1. builder-core.js 是 IIFE,在自身执行期的末尾(restore(),文件底部 state.blocks.forEach 之前)
    同步读取 window.HUALIN_BUILDER_INITIAL / window.HUALIN_BUILDER_NOSEED;
  2. 这两个全局是 builder-admin.js 注入的 —— 可它此刻还没执行(排在 core 后面);
  3. restore() 见 INITIAL 不存在,静默回落去读 localStorage['hualin_builder_schema'];
  4. 而这个 localStorage 键是全局唯一的(不分目标页面),里面是上一次编辑留下的草稿;
  5. ⇒ 无论选哪个目标,core 拿到的都是同一份脏草稿 ⇒ 画布不变。

一句话:独立构建器页的「切换目标」= 改 URL 上的 ?target=,而 ?target= 只影响服务端注入的
INITIAL,一旦 core 根本不看 INITIAL,切换自然无效。metabox 入口同样共享本 loader,只是 metabox 里
「切换目标」不成立,所以问题只在独立页面暴露。

22.3 修复:把依赖方向倒过来,localize 挂 admin

// ✅ 正确:admin 无依赖、先打印;core 依赖 ['jquery','hualin-builder-admin']
wp_register_script( 'hualin-builder-admin', $base . 'builder-admin.js', array(), $ver, true );
wp_register_script( 'hualin-builder-core',  $base . 'builder-core.js',  array( 'jquery', 'hualin-builder-admin' ), $ver, true );

// 本地化数据必须**挂 admin**:wp_localize_script 的数据紧贴该 handle 的 <script> 之前输出,
// 挂 admin 才能保证 core 执行前 HualinBuilderData 已就绪。
wp_localize_script( 'hualin-builder-admin', 'HualinBuilderData', array( 'schema' => ..., 'fieldId' => ... ) );
foreach ( (array) $extra as $key => $data ) { wp_localize_script( 'hualin-builder-admin', $key, $data ); }

wp_enqueue_script( 'hualin-builder-core' );   // 依赖链自动带 jquery + admin ⇒ 顺序 jquery → admin → core

一行注释写死在代码里(依赖方向不能反),把这个坑连同后果写清楚,防后人再改回去;
zip-selfcheck.php 的否定清单同时加了护栏。

22.4 这个 bug 为什么一直没被发现

  • 它不是崩溃,没有报错、没有白屏 —— core 读不到 INITIAL 时是合理地回落到草稿,行为自洽;
  • 只有在「独立构建器页 + 该浏览器 localStorage 里已有草稿 + 切到别的目标」三者同时成立时才显形;
    全新浏览器(无草稿)下 core 会走 seedDemo(),看起来也「有内容」,进一步掩盖了问题;
  • 依赖顺序这种东西肉眼读代码看不出来,必须按 WP 的规则实算打印顺序。

22.5 验证台 tools/builder-lab.php(新增)

不手写脚本顺序,而是桩掉 WP 函数并 require 真实的 inc/builder-loader.php,
再调真实的 hualin_builder_enqueue(),从 $deps 声明推导打印顺序:

  • 门禁断言:admin 位置 < core 位置,否则 exit 1 打印 LAB_ORDER_FAIL;
  • 产出 3 个可直接在浏览器打开的页面(按推导出的顺序内联真实 jquery + admin + core):
页面场景修前修后
case-a.html目标 A、无草稿画布显示 seedDemo() 示例内容正确显示「目标页面 A 的标题」
case-b.html目标 B、无草稿同上正确显示「目标页面 B 的标题」
case-b-polluted.html目标 B、localStorage 先埋脏草稿画布显示「脏草稿-来自其它页面」正确显示「目标页面 B 的标题」,脏草稿被忽略

case-b-polluted 是关键用例:它把「core 有没有老老实实读 INITIAL」逼成了一道非黑即白的题。

22.6 本轮改动文件

文件改动
inc/builder-loader.php反转依赖方向(admin 先于 core)+ 本地化显式挂 admin + 写死防回退注释
tools/builder-lab.php新增:桩化 WP、按真实依赖推导打印顺序、脏草稿用例、LAB_ORDER_PASS/FAIL 门禁
tools/verify/zip-selfcheck.php$checks 加 builder-loader.php 3 个正向标记;$absent 加反向依赖否定标记

22.7 验证与交付(v7 定稿数据)

全部为同一份冻结代码上的前台串行结果:

检查结果
tools/builder-lab.phpLAB_ORDER_PASS —— 推导顺序 jquery → hualin-builder-admin → hualin-builder-core,admin 位置 1 < core 位置 2
probe.js(case-a.html)hasTargetA:true / hasDemo:false
probe.js(case-b-polluted.html)hasTargetB:true / hasDraft:false —— 脏草稿被忽略,直取注入的目标 B
tools/demo-check.phpPASS(9 页 / 156 顶层区块 / 53-53 类 / 0 告警)
移动端基线(mobile-report.txt)页脚 0 个 <44×44 目标、间距全部落在 4px 栅格 ⇒ 本轮为纯后台 PHP 改动,前端零回归
打包器 + zip-selfcheck.phpSELFCHECK_PASS

交付产物(php package-qi-theme-v9.php Qi-theme-20260927-v7.zip):

  • Qi-theme-20260927-v7.zip —— 64 条目 / 298.4 KB / 根目录 qi-theme/(正斜杠)
  • 打包前 43 个 PHP 文件 php -l 全通过;读回自检每条与磁盘源码逐字节一致(CRC32 + 长度 + 条目头)
  • zip-selfcheck.php:64 条 CRC 全过 / 11 份文件正向关键字命中(新增 builder-loader.php 3 标记)/
    3 份文件否定关键字确认(含「反向依赖不得回归」)→ SELFCHECK_PASS
  • v7 同时含上一轮页脚 v6 的全部改动;历史包(v2 / v3 / v4 / v5 / v6)均保留未删,别传错

22.8 遗留 / 技术债

  1. builder-lab.php 目前只覆盖启动顺序这一条契约。core/admin 之间还有别的隐式约定
    (如 HualinBuilderData.fieldId 缺省、HUALIN_BUILDER_ONCHANGE 回调)尚无门禁,
    后续可逐条补成断言。
  2. localStorage 草稿键 hualin_builder_schema 全局唯一,天然有「A 页草稿串到 B 页」的隐患。
    本轮已让 INITIAL 优先、把风险压到「注入失败时」,但更彻底的做法是按目标分键
    (如 hualin_builder_schema_<targetId>),需改动 core 的存取两处,本轮未做。
  3. 探针(probe.js)仍需手动跑、且一次只跑一个页面(多页并发会因 Chrome profile 争抢被 SIGTERM);
    与 mobile-audit.js 的「一次只跑一个」纪律一致。

二十三、图标库支持图片 + SVG(2026-09-27,v8)

用户指令:「优化图标库,支持图片和svg」。

23.1 改造前的真实状态:图标库只认 ICO / SVG

翻代码后发现「只支持 svg」是两层都缺,不是加个上传后缀就完事:

层改造前后果
上传(icon-manager.php)upload_mimes 只放开 svg / ico;accept=".ico,.svg"图片根本传不进图标库
渲染(builder-render.php / builder-core.js)正则写死 \.(ico|svg)(\?|$) —— 三处(hualin_icon_markup()、hualin_block_iconlist()、iconHTML())即使手填 PNG 地址,也会走 esc_html() 分支,把一长串 URL 当文字打印到页面上

第二层才是关键:只放开上传而不改渲染,用户看到的会是「图标位置出现一行网址」。
所以「支持图片」= 上传 + 渲染两侧同时改,且渲染侧的扩展名白名单在 PHP 与 JS 之间必须严格 1:1。

23.2 判别规则:什么算「图片类图标地址」

新增唯一权威判别 hualin_icon_is_image_url()(PHP)与 iconIsImageUrl() + ICON_IMG_RE(JS),
两侧扩展名集合完全一致(由 icon-check.php 强制比对,分叉即 FAIL):

  • 接受的写法:https://…/x.png、http://…、协议相对 //cdn…、站内绝对路径 /wp-content/…;
    可带 ?query 或 #fragment
  • 白名单扩展名:svg svgz ico png jpg jpeg webp gif avif bmp
  • 刻意不接受的写法:裸相对路径(assets/x.png)、无扩展名、ftp://、data: URI、
    以及 ★ ✓ 🛡 无 这类字符 —— 否则用户随手填的 README.png 也会被当成图片

实现细节:PHP 侧正则分隔符用 ~ 而非 #(模式里有 [?#],用 # 会被 PCRE 提前截断,
报 Unknown modifier ']');字符类用 \x27/\x5c 代替 '/\,绕开转义地狱。

23.3 顺带修出两个「一直没被看见」的老 bug

① iconlist 的图片图标从来没生效过(转义反了)

// 原实现:$m 两种情况都是字符串 ⇒ 条件恒真 ⇒ <img> 被 esc_html 转义成文本
$m = '<img class="il-m-img" src="…">';
… . ( is_string( $m ) ? esc_html( $m ) : $m ) . …

is_string( $m ) 想表达「是不是图片」,但 $m 不是字符串就是字符串 —— 于是 <img …> 被转义,
页面上显示的是源码文本。现在统一交给 hualin_icon_markup() 裁决转义,icon-check.php 已上护栏。

② list 区块画布与前端长得不一样(class 分叉)

  • 画布(JS)用 <span class="il-m"> —— 那是 rb-iconlist 的样式,rb-list 里没有对应规则 ⇒ 画布标记无样式;
  • 前端(PHP)用 <span class="ck-ic">,且完全忽略 marker 设置、还缺图片支持。

现已统一为 ck-ic / ck-ic-img,语义统一为「逐条图标优先,留空才回退标记样式的默认字符」。

23.4 后台图标库的「优化」清单

能力改造前现在
上传格式ICO / SVG+ PNG / JPG / JPEG / WEBP / GIF / AVIF
上传方式单文件、原生 input多选 + 拖拽(拖进来的文件塞回原生 input,不引入新上传接口)
收已有图片无 —— 只能重新上传「从媒体库添加」:wp.media 多选 → 标记 _hualin_icon,只接受 image/*
找图标无搜索(名称)+ 格式筛选芯片,芯片带计数,顺序固定(SVG→ICO→PNG→JPG→WEBP→GIF→AVIF)
类型展示硬编码 'image/x-icon' ? 'ICO' : 'SVG' ⇒ PNG 会被标成 SVG按 MIME 映射徽标 + 扩展名兜底,7 种格式各自配色
空态 / 无匹配混为一谈两个状态分开(「库里是空的」vs「筛选后没匹配」)
构建器弹窗只有列表 + 插入+ 搜索 / 格式筛选 / 从媒体库添加(AJAX 回传,不刷新页面)/ 计数
删除确认浏览器原生 confirm保留(文案写清「文件与记录一并删除」)

「从媒体库添加」为什么不刷新页面:管理页无所谓,但构建器编辑页刷新会丢掉未保存的编辑。
所以做成 wp_ajax_hualin_icon_list:带 add[] 时顺带纳入图标库,返回 icons(弹窗重渲染)
与 html(管理页整格替换)。卡片 HTML 抽成 hualin_icon_card_html(),
首屏 PHP 渲染与 AJAX 回传共用同一份模板,避免「刷新后少了删除按钮 / nonce」这类只在异步路径暴露的偏差。

「插入到当前字段」原来是坏的:原实现用 document.activeElement,可点弹窗按钮时焦点已经跑到按钮上
⇒ 永远走「复制」分支,用户点「插入地址」只看到复制提示。改为记住最后一次聚焦过的构建器字段
(#config / #canvas 里的 input / textarea / contenteditable),点按钮时插回去;
contenteditable 走 execCommand('insertText') 以保留撤销栈。

23.5 短代码 [hualin_icon] 增强

  • 新增 link="…":外层包 <a class="hualin-icon-link">
  • 位图 + mode="inline" 必须回退 <img>:原先不判断类型,会把 PNG 二进制喂给 SVG 净化器再当 SVG 输出
    (既无意义也会污染页面)。现在只有 image/svg+xml / svg|svgz 才走内联。

23.6 新增验证基建

工具作用
tools/icon-check.php离线门禁:判别函数表驱动(15 正 / 13 反例)、hualin_icon_markup() 输出、8 个区块级渲染、短代码 9 项、白名单与徽标、钩子注册、卡片模板注入转义、PHP↔JS 扩展名集合一致性比对
tools/icon-lab.php视觉台:纯 PHP 手写真实 PNG(本机无 GD)+ 真实 hualin_icon_manager_page() 标记 + 真实 CSS/JS,产出 icon-out/blocks.html 与 icon-out/admin.html

icon-lab 的两个设计点值得记下来:

  1. 必须用真图。<img> 加载失败时自然尺寸为 0,量出来的「尺寸断言」全是假的 ——
    所以用 zlib + 手写 IHDR/IDAT/IEND 生成真有尺寸的 PNG(256×256 / 720×120 / 128×128)。
  2. 素材地址必须带前导 /。第一次跑实验台 0 张图 —— 因为写的是裸相对路径 assets/icon-square.png,
    而判别函数刻意不认裸相对路径(防误判)。这是实验台自己的坑,不是代码 bug。

23.7 验证与交付(v8 定稿数据)

全部为同一份冻结代码上的前台串行结果:

检查结果
tools/icon-check.phpPASS(exit 0)—— 含「PHP 与 JS 扩展名集合完全一致」
tools/demo-check.phpPASS(9 页 / 156 顶层区块 / 53-53 类 / 0 告警)
tools/builder-lab.phpLAB_ORDER_PASS(上一轮构建器修复未回归)
probe.js(icon-out/blocks.html)8 个图标容器全部 <img> + contain:iconbox 31×31 · list 18×18 · iconlist 18×18 · alert 20×20 · socials 22×22 · features 30×30 · card 28×28 · bento 31×31
├ 关键项256×256 的 PNG 被约束到 18×18(naturalWidth=256)⇒ 大图不会撑破布局;转义残留 0(唯一命中是我自己写的说明文字)
probe.js(icon-out/admin.html)网格 7 列 ×188px / 10 张卡 / 缩略图 112px / 7 种徽标配色各自区分 / 拖拽区 dashed / 搜索框胶囊 999px / 芯片计数 all:10 svg:1 ico:1 png:4 jpg:1 webp:1 gif:1 avif:1
probe.js(构建器弹窗)打开后 display:grid、盒子 920px、5 列、10 卡、芯片同序、计数「(10)」
├ 功能级聚焦字段 → 点「插入地址」⇒ 字段值 前缀-/tools/…/icon-square.png(修复前只会复制);「短代码」⇒ 追加 [hualin_icon id="1"];搜索 round ⇒ 1 张;筛选 png ⇒ 4 张
probe.js(390 窄屏后台)2 列 ×170px / 缩略图 92px / 按钮整行 144px / 搜索整行 350px / 横向溢出 0
mobile-audit.js 3908/8 页 4px 栅格合规、页脚 0 个 <44×44(页脚高度 635/635/711/639 与 v6 定稿一致 ⇒ 无回归)
run-audit.js(tools/demo-out/blocks.html)exit 0 —— 三视口 横向溢出 0 / 对比度 104 项全过 / <24×24 目标 0
打包器 + zip-selfcheck.phpSELFCHECK_PASS

交付产物(php package-qi-theme-v9.php Qi-theme-20260927-v8.zip):

  • Qi-theme-20260927-v8.zip —— 64 条目 / 310.6 KB / 根目录 qi-theme/(正斜杠)
  • 打包前 43 个 PHP 文件 php -l 全通过;读回自检每条与磁盘源码逐字节一致(CRC32 + 长度 + 条目头)
  • zip-selfcheck.php:64 条 CRC 全过 / 14 份文件正向关键字命中(新增图标库三件套)/
    5 份文件否定关键字确认(含「只认 ico|svg 的旧正则」「list 画布的旧 il-m 容器」不得回归)→ SELFCHECK_PASS
  • v8 含 v6 页脚 + v7 构建器修复的全部改动;历史包(v2…v7)均保留未删,别传错

23.8 改动文件

文件改动
inc/builder-render.php新增 HUALIN_ICON_IMAGE_EXT + hualin_icon_is_image_url();hualin_icon_markup() 加 $alt 参数;list / iconlist / alertbox / socials 四个区块统一走共享渲染;修掉 iconlist 的转义反转 bug
inc/icon-manager.php格式白名单唯一来源 + MIME 映射 + 徽标映射;多文件上传;hualin_icon_mark_as_icons();wp_ajax_hualin_icon_list;hualin_icon_card_html() / hualin_icon_grid_html();管理页加工具栏 / 拖拽区 / 双空态;短代码加 link、修 inline 类型判断
assets/builder/builder-core.jsiconHTML() 换成 ICON_IMG_RE + iconIsImageUrl();list / iconlist / alertbox / socials 四处渲染对齐 PHP;5 个图标字段标签改为「Emoji / 字符 / 图片地址」;iconlist 补上逐条图标字段(PHP 早有、画布没暴露)
assets/builder/builder-blocks.css新增 5 条图标图片兜底尺寸规则(bnt-ic-img / il-m-img / ck-ic-img / al-ic-img / so-img)
assets/admin/icon-manager.css整体重写:工具栏 / 拖拽上传区 / 7 种徽标配色 / 卡片悬浮 / 双空态 / 灯箱 / 弹窗搜索筛选栏 / 782px 窄屏
assets/admin/icon-manager.js整体重写:共用筛选与芯片、lastField 插入修复、拖拽上传、wp.media 多选 + AJAX 刷新、构建器弹窗增强、全部字符串走 esc()
tools/icon-check.php新增(离线门禁,含跨文件同构比对)
tools/icon-lab.php新增(视觉台,手写真实 PNG + 真实页面标记)

23.9 遗留 / 技术债

  • icon-check.php 的 PHP↔JS 比对靠正则抓 builder-core.js 的扩展名分组。如果以后有人改写成
    new RegExp(...) 字符串拼接,抓取会失败 —— 那时会报 FAIL(不是静默通过),但仍需顺手更新抓取式。
  • 图标库没有分页。几百个图标以内没问题(全部走客户端筛选),上千个时首屏 HTML 会偏大;
    届时应改服务端搜索 + 分页。
  • _hualin_icon 只是一个 meta 标记,图标与普通图片同住一个媒体库。这是刻意的(复用 WP 的
    裁剪/替换/删除链路),代价是删图标会连带删文件,且「媒体库」里仍能看到它。
  • icon-manager.js 里 .data() 读 data-name 时 jQuery 会尝试类型推断(纯数字文件名会变 number)。
    当前用 String(...) 包住,实际无影响,但属于潜在陷阱。