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.php | require builder-page.php |
四、部署
- 后台 → 外观 → 主题 → 上传
Qi-theme-20260723-v3.zip(先删旧版或直接覆盖启用)。 - 全量
php -l校验通过(PHP 8.2);ZIP 采用 PHP ZipArchive 正斜杠路径,宝塔/Linux 解压安全。 - 已有页面数据(Schema、设置项)不受影响——只新增选项,默认值与原硬编码文案一致。
五、本轮新增(模块管理 + 抖音宣传片 + UI 优化)
5.1 后台模块管理(集中化启停 / 排序)
入口:后台 → Qi 主题 → 模块管理(qi-theme-modules)
把首页与全部分类/归档页的模块聚合到一个界面统一管理:
- 启用/禁用:首页模块写回页面构建器 Schema 的
hidden标记(隐藏不删除,可随时恢复);归档页模块写入 optionqi_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.css | rb-card / sec / rb-sec / container / 视频模块样式优化 |
functions.php | require 新增 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.php | hualin_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.php | style 组新增 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 高;且切换完全依赖 CSSanimation: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.php | enqueue 与界面标记改为调用共享函数 |
inc/builder-page.php | enqueue 与界面标记改为调用共享函数(修复漏调 wp_enqueue_media) |
functions.php | 引入 builder-loader.php;注册并加载 assets/css/nav.css(位于 main 与 responsive 之间) |
assets/builder/builder-core.js | image 字段:硬 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、JSnode --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" 指向不存在的 id | header.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) | 未统一 | 低 |
| T2 | Google 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残留#C77A00fallback 统一为#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.css | 768→767 断点;--header-h 移动端 64;抽屉 top:100%;社交图标 44 |
assets/builder/builder-blocks.css | accent 底文字对比度;12 处强调色改 --c-accent-ink;容器 var(--container);断点 767;h2 字号统一;表格滚动焦点 |
assets/js/main.js | 注入 .sub-toggle + 抽屉关闭行为(Esc/外部/互斥);rAF 滚动节流 |
header.php | nav#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 三个关键机制(这三条是本系统能站得住的原因)
- 首个绘制前判定隐藏:初始隐藏态不写死在 HTML 里,而是由
<head>中一段极小的内联脚本,赶在样式表解析前判定「是否运镜」再挂类。因此 JS 关闭 / 用户开启「减弱动态效果」时状态类根本不存在,内容直接可见 —— 连<noscript>都不需要。 - 注册表 + 交接(
qi-in):CSS 的隐藏态选择器统一带:not(.qi-in);元素动画结束时由 JS 挂上.qi-in并清除内联样式,等于把这个元素交还给常规样式表。没有这一步,GSAP 写下的内联transform会永久顶掉卡片悬停位移 / 图片悬停放大等既有交互。 - 孤儿扫描兜底:用与 CSS 完全相同的选择器再查一遍,凡「被隐藏了却没有任何进场认领」的元素补一次独立揭示。作用是把「选择器契约必须完美」降级成「契约 + 校验」—— 即使日后新增嵌套区块结构,最坏结果也只是少一段动画,不会变成内容看不见。
15.4 设计阶段自查并修正的 8 个缺陷
| # | 缺陷 | 后果 | 修法 |
|---|---|---|---|
| 1 | bail() 先加 .qi-motion-off 又立刻被 markReady() 剥掉 | 引擎异常时内容永久隐藏 | 拆开「置就绪」与「强制显示」两个函数 |
| 2 | 补间用 clearProps: 'clipPath' | 清掉内联裁切后 CSS 隐藏规则重新生效,文字进场后又消失 | 保留内联终态;改为动画结束时走 qi-in 交接 |
| 3 | 跳过 naturalWidth === 0 的图片 | 所有 loading="lazy" 图片永不揭示 | 移除该判断(给未加载完的图片做补间无害) |
| 4 | 卡片 / 图片补间未 paused | 加载瞬间就播完,滚动到根本看不到 | 全部改 paused: true,由进入视口触发 |
| 5 | 标题组只取每块首个 h2 | rb-section / rb-columns 嵌套块、rb-hero-split 的后续标题永久隐藏 | 改为「遍历全部标题元素 + 按最近区块分组」 |
| 6 | 选择器用 [class$="-row"] | 漏匹配 class="aw-row g3" 这类带修饰类的行容器 | 改 [class*="-row"] |
| 7 | GSAP 内联 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 项全过 |
| 完全禁用 JS | 88 个元素无一个停在隐藏态(无死白块);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 修复
nav.css:当前项 / 悬停 / 折叠指示统一改用--c-accent-ink(#9A5B00,白底约 5.0:1)。style.css:全局a:hover改--c-accent-ink;深色底场景(顶栏 / 页脚)各自已有的覆盖规则继续生效,未受影响。- 容器宽度分级(≥1024px → 1200px,≥1440px → 1320px)从
responsive.css移到最先加载的style.css,让后台设置的内联变量能正常胜出;设计上的大屏分级得以保留。 main.css:汉堡按钮加min-width/min-height: 44px+ 居中。
16.3 验证结果
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 对比度不达标 | 3 条 | 0 条 |
| 命中区 <40×40 | 1 条(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):把「尺寸=重要性」这件事落到网格上
| 区块 | 新增变体 | 解决的问题 |
|---|---|---|
服务网格 services | bento | 四块等大卡片分不清主次,首格放大成 2×2 后版式有了重心 |
图标特性 features | bento | 同上 |
数据优势 adv | bento | 数值本身做视觉锚点,不再给数字套图标框 |
图片画廊 gallery | bento | 首图放大,避免「九宫格缩略图墙」的均质感 |
案例网格 projects | story | 从「图 + 标题」改成 挑战 → 方案 → 成果 三段式,带量化指标行 |
资讯列表 news | editorial | 大标题无图的编辑式排版:减少图片请求,LCP 更友好 |
专家团队 team | editorial | 去掉圆形头像框(小团队没有合规工装照时,圆框里塞一个字反而显敷衍) |
图文英雄 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):补齐剩下三块「关键但形态单一」的
| 区块 | 新增变体 | 依据 |
|---|---|---|
发展历程 timeline | rail 中轴轨道 | 经典竖线把所有节点压在左侧,节点一多就是一条密不透风的长文;中轴交替把纵向长度砍掉近一半,年份放大成等宽数字后「这是时间线」一眼就懂 |
客户评价 testimonials | wall 评价墙 | 同质化等高卡片阵会让整块被眼睛跳过;让其中一条先站出来(尺寸=重要性),整块才有被读的可能 |
数据指标 counter | band 冲击带 | 常规四列把数字当「内容」放进页面流;决策者需要的是把数字当「证据」。独立深底、字号拉到 34~60px、项间发丝线分隔,并留数据来源脚注——没有出处的数字只会被当成广告语 |
三块共性:只复用已有设计语言(bento 的网格语言、tabular-nums、容器查询降级),
不引入新的机制、不引入 JS、三块都不含位移/缩放,因此没有新增动效需要被prefers-reduced-motion 关闭。
17.4 三处同构契约(这是主题最容易出问题的地方)
每加一个变体都要同时改三处,缺一处就会出现「后台预览和前端不一样」:
assets/builder/builder-core.js—— 后台实时预览(JS render + 字段定义 + 默认值)inc/builder-render.php—— 前端渲染(PHP 同构分支)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 按钮、毛玻璃卡片)都会被误判。
修复两处:
toRGB()改为clearRect后直接落笔,alpha 交给新增的alphaOf()单独判定;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 外面,连「抛错退出」这条退路都没有。
也就是说:逐点补超时是在打地鼠,每补一处就暴露下一处。修复分两层:
- 补上缺口 —— 五个脚本的
connect()统一给 open 事件加 15s 超时(成功路径行为不变); - 加全局看门狗(真正的兜底)——
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 条断言,四条主线:
- 横向溢出 —— bento / 卡片化表格最容易顶破视口(含越界元素定位输出)
- 容器查询按内容宽降列 —— 期望列数由容器内容宽推导,不写某一视口的快照;
并反向断言顶层.g3仍按视口断点排布(证明容器规则没外溢) - bento 行高 —— 防止 316px 死白回归(18.1)
- 变体可见性与关键布局 —— 中轴时间线节点是否真的左右分列、
评价墙首条是否真的跨了两列(宽度实测 ≈ 单格 ×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.html | 0 | 0 | 0(104 项全数真算) | 0 |
动效实验页 motion-out/index.html | 0 | 0 | 0(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.js | CDP 超时 + 断连即失败 + 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 已知技术债
- 图片横幅
image与imagebox(图片块)、三个视觉重复的提示框:
属于「已有等效表达」,合并比继续加变体更该做,但涉及历史 schema 兼容,本轮未动。 - 表格卡片化只覆盖窄容器与窄视口:若日后有人把宽表放进两栏页面且单栏宽度在 641~880px,
仍会出现横向滚动(tbl-wrap自带overflow-x:auto,不会破版,但需要横拖)。 @view-transition的降级面:仅在支持该特性的浏览器生效,其余浏览器就是普通换页,
属纯渐进增强;但若日后给入场也加页面过渡,必须先把 GSAP 首屏动画让开,否则同层两种动画语言。- 验证脚本依赖本机 Chrome 的默认安装路径(
C:/Program Files\Google\Chrome\Application\chrome.exe),
换机器需要改常量。挂起这道坎共踩两次(18.5 / 18.7),现在有三层防护:
CDP 调用超时 → websocket open 超时 → 全局看门狗(到点落盘 + exit 3)。
首选仍是前台串行执行,不要嵌套在后台任务链里跑。 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×44 | 0 个(修复前 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::before3px 高亮竖条(深底预设自动换品牌金)。 - 栏间发丝线分隔:
.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.css | 3 | 隐藏初态 容器 > *:not(.qi-in) |
assets/js/motion.js | 3 | TARGETS.heroLayers / SLIDE_LAYERS / 换页重播 q() |
tools/verify/run-motion.js | 5 | 验证台自持的 HIDDEN_SEL + 内联探针 |
tools/motion-lab.php | 1 | 渲染台内联探针 |
漏改的后果很真实:只改动画文件不动验证台,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.php | rb-sec 眉标去掉硬写左对齐;hero-slider 补 .hs-txt 包裹层 |
assets/builder/builder-core.js | 同上两处(与 PHP 1:1 同构) |
assets/js/motion.js | 3 处首屏分层选择器改为 .hs-in > .hs-txt > * |
assets/css/motion.css | 3 处隐藏态选择器同步为 .hs-in > .hs-txt > * |
tools/verify/align-audit.js | 新增:对齐一致性审计(4 视口 × 2 页,Range 文字测量) |
tools/verify/hero-grid.js | 新增:首屏网格落位探针(矩形相交判「并列」) |
tools/verify/run-motion.js | HIDDEN_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.js | 153 条断言,失败 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>原样保留):
- 无 JS 时页脚照常全展开,不会出现「页脚少两栏链接」的静默故障(实测停在 915px);
main.js由functions.php以in_footer=true输出,是阻塞式脚本且位于 body 末尾,
执行发生在首个 paint 之前,所以窄屏收起不会闪一下;- 用户手动展开过的栏打
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.php | 7 页产出 OK(s1…s5 + 长文案 + 无 JS 降级) |
tools/verify/mobile-audit.js 390 | 8/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.php | PASS(9 页 / 156 顶层区块 / 53-53 类 / 0 告警) |
打包器 + zip-selfcheck.php | SELFCHECK_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 遗留 / 技术债
run-audit.js一次只能审一页(AUDIT_PAGE单值)。页脚页已能审,但要手动指定;
理想是支持多页列表并纳入默认。本轮未做,已在 21.3 记录教训。mobile-audit.js的「体高」列对折叠状态的<details>不可用(Chrome 新版会给出
子元素几何盒子而非绘制高度,s1 出现h=77却体高=140的矛盾值)——
判断收起与否要看h列,或直接用probe.js打elementFromPoint。已在本轮说明中标注。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,于是:
builder-core.js是 IIFE,在自身执行期的末尾(restore(),文件底部state.blocks.forEach之前)
同步读取window.HUALIN_BUILDER_INITIAL/window.HUALIN_BUILDER_NOSEED;- 这两个全局是
builder-admin.js注入的 —— 可它此刻还没执行(排在 core 后面); restore()见 INITIAL 不存在,静默回落去读localStorage['hualin_builder_schema'];- 而这个 localStorage 键是全局唯一的(不分目标页面),里面是上一次编辑留下的草稿;
- ⇒ 无论选哪个目标,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.php | LAB_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.php | PASS(9 页 / 156 顶层区块 / 53-53 类 / 0 告警) |
移动端基线(mobile-report.txt) | 页脚 0 个 <44×44 目标、间距全部落在 4px 栅格 ⇒ 本轮为纯后台 PHP 改动,前端零回归 |
打包器 + zip-selfcheck.php | SELFCHECK_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.php3 标记)/
3 份文件否定关键字确认(含「反向依赖不得回归」)→SELFCHECK_PASS- v7 同时含上一轮页脚 v6 的全部改动;历史包(v2 / v3 / v4 / v5 / v6)均保留未删,别传错
22.8 遗留 / 技术债
builder-lab.php目前只覆盖启动顺序这一条契约。core/admin 之间还有别的隐式约定
(如HualinBuilderData.fieldId缺省、HUALIN_BUILDER_ONCHANGE回调)尚无门禁,
后续可逐条补成断言。- localStorage 草稿键
hualin_builder_schema全局唯一,天然有「A 页草稿串到 B 页」的隐患。
本轮已让 INITIAL 优先、把风险压到「注入失败时」,但更彻底的做法是按目标分键
(如hualin_builder_schema_<targetId>),需改动 core 的存取两处,本轮未做。 - 探针(
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 的两个设计点值得记下来:
- 必须用真图。
<img>加载失败时自然尺寸为 0,量出来的「尺寸断言」全是假的 ——
所以用zlib+ 手写 IHDR/IDAT/IEND 生成真有尺寸的 PNG(256×256 / 720×120 / 128×128)。 - 素材地址必须带前导
/。第一次跑实验台 0 张图 —— 因为写的是裸相对路径assets/icon-square.png,
而判别函数刻意不认裸相对路径(防误判)。这是实验台自己的坑,不是代码 bug。
23.7 验证与交付(v8 定稿数据)
全部为同一份冻结代码上的前台串行结果:
| 检查 | 结果 |
|---|---|
tools/icon-check.php | PASS(exit 0)—— 含「PHP 与 JS 扩展名集合完全一致」 |
tools/demo-check.php | PASS(9 页 / 156 顶层区块 / 53-53 类 / 0 告警) |
tools/builder-lab.php | LAB_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 390 | 8/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.php | SELFCHECK_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.js | iconHTML() 换成 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(...)包住,实际无影响,但属于潜在陷阱。
