网址提交

This commit is contained in:
浅念
2026-08-27 11:54:26 +08:00
commit f4fb024e98
177 changed files with 9100 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
# HESU 主题线上部署方案
> 适用:hesu-theme/hesu.zip(13 MB,图片压缩版,2026-08-19 验收见 docs/plans-audit-fix-acceptance.md)
> 主题要求:WordPress ≥ 6.1(测试至 7.0)、PHP ≥ 7.4(建议 8.x)、MySQL/MariaDB、HTTPS
---
## 一、部署前准备
| 项 | 要求 | 说明 |
|---|---|---|
| PHP | ≥7.4,建议 8.0+ | 需扩展:mysqli/gd/curl/mbstring/fileinfo/openssl(主流主机默认都有) |
| WordPress | ≥6.1 | 全新安装(见"已有站点"警告) |
| 上传限制 | ≥16MB 或走 FTP | wp-admin 上传 zip 受 `upload_max_filesize` 限制(常见默认 2–8M,13MB 会失败) |
| 插件 | Contact Form 7 | 唯一硬依赖(联系表单);**先装它再激活主题**(激活时自动建表单) |
---
## 二、全新站点部署(推荐路径)
### Step 1 — 安装 WordPress
1. 主机绑定域名 + SSL 证书(HTTPS)
2. 全新安装 WordPress(数据库新建;站点语言随意——前台内容是英文,管理后台语言不影响)
3. **设置 → 固定链接 → 选择"文章名"(/%postname%/)→ 保存**(seed 页面的友好 URL 依赖它)
### Step 2 — 安装 Contact Form 7
插件 → 安装插件 → 搜索 "Contact Form 7" → 安装并**激活**。
(此时 CF7 自带一个 "Contact form 1",无需管它;主题激活时会另建 "HESU Inquiry" 并自动绑定到联系页。)
### Step 3 — 上传主题(二选一)
- **方式 A(后台上传)**:先把 PHP 上传限制调到 ≥30M(宝塔/面板的 PHP 设置,或 `.user.ini`:`upload_max_filesize=32M` + `post_max_size=32M`)→ 外观 → 主题 → 添加 → 上传主题 → 选 `hesu.zip` → 现在安装
- **方式 B(FTP/文件管理器)**:解压 hesu.zip → 把 `hesu` 目录上传到 `wp-content/themes/hesu` → 外观 → 主题 → 找到 HESU → 启用
### Step 4 — 激活(自动完成的事)
点击"启用"后主题自动播种,**无需任何手动导入**:
- 创建 9 个页面(首页/四大业务/全球布局/关于我们/发展历程/联系)+ 全部内容与图片入库(约 97 张媒体)
- 创建 4 篇示例新闻 + 4 个分类 + 菜单(主导航/页脚,含 Mega Menu 卡片)+ HESU Inquiry 表单
- 把站点首页设为 Home(设置 → 阅读 里自动配置好)
### Step 5 — 部署后验证清单(5 分钟)
- [ ] 首页:数字滚动、四大业务磁贴、区域轮播可拖拽、Partner 自动轮播、新闻 4 卡
- [ ] 全球布局 `/global-presence/`:滚轮一屏一翻、导航高亮
- [ ] 关于我们 `/about-us/`:滚动视屏动画(标语淡入、logo 揭示)
- [ ] 新闻 `/news/`:Tab 切换、点进详情页、上一篇/下一篇
- [ ] 联系 `/contact/`:表单空提交显示红校验、地图与联系信息展示
- [ ] 悬停导航 Business/About Us:Mega Menu 展开(4 卡/2 卡)
- [ ] 手机宽度:汉堡抽屉菜单开合
### Step 6 — 站点配置(后台可改项)
| 位置 | 配置内容 |
|---|---|
| 外观 → 自定义 → HESU Global | 社交链接 ×4(TikTok/Facebook/Instagram/WhatsApp,空则 `#`)、页脚标语、版权文字、新闻页 banner 图 |
| 外观 → 自定义 → 站点身份 | Logo(不传则用主题内置 logo.png) |
| 联系 → Contact Form 7 → HESU Inquiry → 邮件 | 收件人改为真实邮箱(默认站长邮箱) |
| 设置 → 常规 | 站点标题/副标题/时区 |
**发信建议**:生产主机若没配邮件(多数虚拟主机 PHP mail() 不可靠),安装 WP Mail SMTP 类插件配置 SMTP,否则表单提交后管理员收不到通知(前台的成功/失败提示样式不依赖发信,但业务上收不到信=没线索)。
### Step 7 — 替换示例内容
- 4 篇新闻为占位示例(后台 → 新闻,可编辑/删除/新发;删空某分类会显示 "Coming soon" 空态,属正常设计)
- 页面文案/图片:后台 → 页面 → 任一页面,区块结构已锁定,点文字/图片直接改
- ⚠️ 不要"解锁"任何区块(解锁=破坏页面结构);管理员权限虽可解锁,修复需到 外观 → HESU Tools 重跑 seed(会覆盖所有页面内容)
---
## 三、已有站点安装(警告)
主题的激活播种**会把站点首页切换为新建的 Home 页**,并创建 9 个新页面/菜单/文章——适合全新站。若必须装到已有站点:
1. **先完整备份**(数据库 + 文件)
2. 最好先在临时域名/子目录演练一遍
3. 激活后到 设置 → 阅读 检查首页设置是否符合预期;多余的 seed 内容可手动删除(页面/新闻/菜单,媒体库图片可保留)
## 四、运维注意事项
### 主题升级与内容更新
**zip 覆盖安装 / FTP 覆盖主题目录 = 只更新代码**。数据库里已播种的页面/新闻/菜单内容**不会**被改动(内容是激活或重播种时写入数据库的,与主题文件分离)。
| 升级类型 | 是否重跑 seed | 说明 |
|---|---|---|
| 纯代码优化版(性能修复、CSS/JS 调整、本次优化版等) | **否** | 覆盖主题文件即生效,页面内容无需动 |
| 静态稿改版后的新版主题 | **是**(readme Changelog 会注明) | 需到 外观 → HESU Tools 重跑 seed 才能看到新版页面内容(seed markup 与 render.php 一起更新过);改版同步按 `docs/superpowers/plans/2026-08-17-static-v2-sync-report.md` 的模式(render.php + seed + 重播种),**不要直接改线上的 post_content** |
重跑 seed = **恢复出厂内容**:你在后台对预置页面/新闻/菜单做过的修改会被覆盖。执行前会自动导出预置内容备份(见下方"备份")。
### 日常
- **重跑 seed**:外观 → HESU Tools → 重新创建预置内容。会**覆盖全部 seed 页面内容**(有确认弹窗);执行前**清空回收站**(回收站里的预置页不会被识别复用,会产生重复页)。你后台改过的文案会被还原。
- **主题目录不可删改**:媒体库里的 seed 图片是激活时从主题目录复制的副本,但 CSS 引用约 100 张图直接读主题 `image/` 目录——删除主题=样式崩。
- **子页面必须设特色图**:Mega Menu 卡片图取子页面特色图,无图则显示灰色空占位。
### 备份
- **数据库定期备份**:seed 内容 + 你后续的后台修改**全部存在数据库里**——用主机快照或 UpdraftPlus 类插件定期备份;主题 zip 本身就是代码备份,库丢了内容才真丢。
- **reseed 自动备份**:每次在 HESU Tools 执行"重新创建演示内容",执行前自动导出预置内容快照(9 个页面含正文 + 全部 seed 新闻 + Primary/Footer 菜单结构,JSON 格式)到 `wp-content/uploads/hesu-backups/`,**保留最近 5 份**,旧文件自动清理。该目录已自动放置 `.htaccess` 拒绝 web 直读(Apache 生效)+ 空 `index.php`;**nginx 不识别 .htaccess**,建议在站点配置额外加一条:
```nginx
location ^~ /wp-content/uploads/hesu-backups/ { deny all; }
```
---
## 五、上线检查单(安全加固)
以下为 Step 6 站点配置的强化项,**正式对外上线前逐条确认**:
- [ ] **CF7 反垃圾(必配)**:设置 → Contact Form 7 → Integrate(集成)→ 配置 reCAPTCHA(invisible v2 或 v3),或安装蜜罐类反垃圾插件(如 "Contact Form 7 Honeypot")。**实测默认表单可被脚本无限提交**——不配则上线后会被垃圾提交灌爆、真实询盘被淹没。
- [ ] **装 Flamingo**:Contact Form 7 同作者官方配套插件(插件库搜 "Flamingo")。每次表单提交**存库一份**,防 SMTP 抖动/邮件丢失导致丢单——没收到邮件时后台还能找回。
- [ ] **SMTP 发信**:WP Mail SMTP 类插件配置真实 SMTP 账号(对应 Step 6 的发信建议)。PHP `mail()` 在多数主机上不可靠,上线后的询盘通知全靠这一步。
- [ ] **关闭 XML-RPC / 收紧 REST users**:堵住登录名枚举与 xmlrpc 放大攻击。可用安全插件(Wordfence 等),或把以下片段粘贴到代码片段插件(推荐 WPCode,主题升级不受影响)或子主题 functions.php:
```php
// 关闭 XML-RPC(防 xmlrpc.php 暴力破解 / pingback 放大)
add_filter( 'xmlrpc_enabled', '__return_false' );
// 未登录请求隐藏 REST users 端点(防 /wp/v2/users 枚举登录名)
add_filter( 'rest_endpoints', function ( $endpoints ) {
if ( ! is_user_logged_in() ) {
unset( $endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
}
return $endpoints;
} );
```
更彻底的做法是在 web 服务器层直接封禁 `xmlrpc.php`(返回 403)。
---
## 六、回滚
- 只删内容:外观 → HESU Tools 重跑(恢复出厂内容)
- 整站回滚:切换回其它主题(如二〇二五默认主题)→ 删除 HESU 主题;seed 页面/新闻/媒体保留在库中可手动清理
+100
View File
@@ -0,0 +1,100 @@
# HESU 主题综合审计报告与优化方案
- 日期:2026-08-17
- 对象:`hesu-theme/hesu/`(v2 同步版,commit 48b6a3a)+ 测试站实测(WP 7.0.4 / CF7 6.1.6 / PHP 8.2)
- 方法:三路并行审计(性能/SEO、安全/兼容/运维、代码遗留/编辑器 UX/可访问性),实测验证(curl/Playwright/DB 查询/无害表单提交),交叉去重后综合
---
## 一、总体结论
**架构层健康**:条件资产加载、三层结构锁定、转义纪律(v2 新增输出零违规)、幂等 seed、CF7 降级路径、编辑器加固实测全部成立。**真正的问题集中在四条线**:
1. **图片管线是性能瓶颈**——首页总传输 ~5.8MB(图片占 95%),手写 `<img>` 无 srcset、首屏下方大图无 lazy、LCP 走 CSS 背景最差模式、24 个 >500KB 未压缩图;测试库还有 123MB reseed 重复侧载尸体。
2. **SEO 三缺**——OG/Twitter 卡片全站缺失(外贸 B2B 分享场景直接损失)、single-news 双 H1 / about-us 无 H1、96 个附件 alt 全空。
3. **两处"文档/文案与事实不符"**——HESU Tools 页声称"编辑过的页面不受影响"(实际 reseed 会删并重建);readme 声称 15 篇新闻/有 Mega Menu 文案设置(实际 4 篇/已删)。
4. **公网表单滥用面**——无反垃圾,实测可脚本无限灌表(运维配置项,非代码缺陷)。
另有一批 **v1 遗留死代码**(v2 同步后 11 项可安全删除)与若干编辑器 UX 打磨点。
---
## 二、发现明细(去重后按严重度)
### 🔴 高
| # | 发现 | 证据 | 影响 |
|---|---|---|---|
| H1 | 手写 `<img>` 全部无 srcset/sizes | hero/business-intro/media-card/large-image/news hero 的 render.php 直出 `<img src>`(对照:news-grid 用 `the_post_thumbnail` 有 srcset ✓) | 手机端下载全尺寸大图,多耗 2.5-3MB |
| H2 | LCP 走 CSS 背景图(最差模式) | `style.css:367` hero-bg 引用 banner.jpg 908KB,CSS 背景无 preload/fetchpriority;WP 把 fetchpriority=high 给了页面底部新闻图 | 4G 移动端 LCP 预计 >4s |
| H3 | 首屏下方大图无懒加载 | global-map.png(1.4MB)/introfuction2.png(953KB)/media-card 卡图均无 `loading="lazy"` | 首屏外 ~2MB 提前下载 |
| H4 | 24 个图片 >500KB 未压缩 | global-map.png 1.4MB、home-bgs.png 1.2MB、banner.jpg 908KB 等;统一压缩+WebP 可 -70% | 全站每次访问多传 ~4MB |
| H5 | OG/Twitter 卡片全站缺失 | 4 页实测 `og:`/`twitter:` = 0 | 社交分享无标题无图 |
| H6 | 标题结构硬伤 | single-news 双 H1("NEWS" + 文章标题);about-us 全页 0 个 H1 | 搜索引擎语义混乱 |
| H7 | 表单可被脚本无限提交 | 实测 REST 直发全字段通过校验到发信阶段;无蜜罐/reCAPTCHA/频率限制 | 公网垃圾灌箱、SMTP 配额耗尽 |
| H8 | **HESU Tools 文案误导**(`settings-page.php:47`) | 称"手动新建/编辑过的页面与文章不受影响"——实际 reseed 按 slug/meta 删除重建 seed 页面与新闻,后台修改会被覆盖 | 真实数据丢失事故诱因 |
| H9 | **readme.txt 与事实不符** | 称"15 篇示例新闻"(实际 4);称自定义项含"Mega Menu 文案"(v2 已删) | 交付即误导 |
### 🟡 中
| # | 发现 | 证据 | 影响 |
|---|---|---|---|
| M1 | 96/96 附件 alt 全空;mediaPicker 无 alt 编辑 | DB 查询 with_alt=0;helpers.js onSelect 不含 alt 控件 | 图片搜索流量丢失;编辑 alt 需媒体库往返 |
| M2 | v1 死代码 11 项(可安全删除清单见附录 A) | section-heading 整块、stat bp 变体+4 SVG、hero showScroll/bgAlt、cap svgIcon、feature-card ae-marker+13 SVG、product-card 5 SVG+icon 枚举、banner-cell kicker、css/style.css 双胞胎等 | 维护面虚胖;inserter 噪音 |
| M3 | 结构化数据为零 | 无 Organization/Article/BreadcrumbList JSON-LD | 无富摘要资格 |
| M4 | reseed 无备份/导出机制 | cleanup 直接删(含用户修改),仅 JS confirm;无 dry-run/版本化 | 高危操作无兜底 |
| M5 | DEPLOYMENT.md 缺"升级主题→内容更新语义"章节 | `hesu_seeded` 守卫使 zip 升级不动 DB 内容;管理员不知道何时该 reseed | 升级后内容不同步的困惑 |
| M6 | 业务 JS 粗粒度加载 + CF7 全站加载 | 任一业务区块→4 个业务 JS(~6.3KB 冗余);CF7 css/js ~26KB 全站;wp-emoji 未移除 | ~30KB 冗余请求/页 |
| M7 | WP/CF7 升级回归点 5 处 | lock.js WP7 类名、Registry::get_instance(WP6 fatal)、canLockBlocks 键、apiVersion 3、CF7 6.x DOM 桥接 | 大版本升级需回归 |
| M8 | 无 screenshot.png / Theme URI 占位 / 37 区块 0 description | Theme Check REQUIRED 级瑕疵;inserter 33 区块无图标难辨认 | 合规与编辑体验 |
| M9 | 整页缓存与 news-grid 冲突 | 服务端 WP_Query,缓存下发新文章首页不刷新 | 上缓存插件时需配失效 |
### 🟢 低 / 记录在案
- 字体无 preload(建议 2 个展示 weight);sitemap 含 users-1.xml(建议关作者归档);archive 分页 meta 未验证(4 篇无第 2 页);首页 H1 纯品牌词(静态设计使然)
- phControl 暴露原始 CSS 类名给编辑者(au-gps ~15 个字段);presence 区块排序异常(注册元数据);GP 全屏无 prefers-reduced-motion 退出(与静态一致,超基线建议);DateTime::createFromFormat 无守卫;style.css 注释 mojibake
- 设计取舍(**明确不改**):mega 菜单键盘不可主动展开(与静态逐字节一致);菜单渲染绕过 wp_nav_menu(Polylang 菜单切换器等不生效);无子主题路径(按"直接改"交付);多语言未验证(范围外);index.js 剩余重复维持现状(YAGNI)
---
## 三、优化方案(分级)
### P0 必改(上线前,~1 天工作量)
| # | 动作 | 改动点 | 收益 |
|---|---|---|---|
| P0-1 | 修复 H8/H9:settings-page 文案改为如实描述("将删除并重建全部预置页面/新闻/菜单,后台修改会被覆盖,执行前请导出备份");readme 更正 4 篇新闻、删除 Mega Menu 文案描述 | 2 个文件 | 消除数据丢失诱因 |
| P0-2 | 修复 H6:single-news 的 hero H1 降为 `<p>`(视觉不变);about-us 的 au-title 升 H1 | 2 个模板各 1 行 | 语义硬伤 |
| P0-3 | 补 OG/Twitter 卡片:`wp_head` 输出 og:title/description/image(featured 或 hero bg)/url/type + twitter:card(~30 行,含 esc) | inc/setup.php 或新 inc/seo.php | 社交分享立即有卡 |
| P0-4 | 懒加载补齐:large-image/media-card/hub-card/presence map/business-intro 图加 `loading="lazy" decoding="async"`(hero 除外) | 各 render.php | 首屏外 -2MB |
| P0-5 | hero LCP:输出 `<link rel="preload" as="image" href=banner>` + hero bg img 加 `fetchpriority="high" decoding="async"`(保持 CSS 背景视觉不变,img 藏 aria-hidden 或改 preload 方案按改动最小者) | header.php wp_head / render | LCP 显著改善 |
| P0-6 | 图片压缩(构建步骤):24 个 >500KB 图批量压(jpg q78 / png→webp 1920px),重建 zip;测试库清空 uploads 后重新 seed(消 123MB 尸体) | image/ + 重播种 | 全站 -70% 传输 |
| P0-7 | 部署文档补"升级语义"章节 + 上线检查单加:CF7 reCAPTCHA/蜜罐 + Flamingo + SMTP + 真实收件箱 + xmlrpc 关闭/REST users 收口 | DEPLOYMENT.md | 运维防线 |
### P1 建议(上线后一周内,~1-2 天)
| # | 动作 | 收益 |
|---|---|---|
| P1-1 | 手写 img 补 srcset:区块图片属性已有 imgId,render 改走 `wp_get_attachment_image()`(带 srcset/sizes/loading 一并解决) | 移动端 -2.5MB |
| P1-2 | mediaPicker 加 alt TextControl(helpers.js ~10 行)+ seed 侧载时写 `_wp_attachment_image_alt`(标题现成) | 图片 SEO + 编辑体验 |
| P1-3 | 死代码清理:按附录 A 清单删 11 项(注意 feature-card/product-card 的模板耦合同步删) | 维护面收缩 |
| P1-4 | Organization + news Article JSON-LD | 富摘要资格 |
| P1-5 | reseed 安全化:执行前自动导出 seed 页面+新闻为 WXML 文件存 wp-content/uploads(或至少 post_content 序列化存 option),页面文案如实警示 | 高危操作兜底 |
| P1-6 | CF7 资产仅 contact 页加载;业务 JS 按页面精确映射(或合并单文件);移除 wp-emoji | -30KB/页 |
| P1-7 | screenshot.png(1200×900 首页截图)+ Theme URI 改真实域名 + 37 区块补 description 字段 | 合规 + 编辑器辨识 |
### P2 可选(按需)
- 字体 preload ×2;sitemap 关 users 归档;phControl 收进 Advanced 折叠组;presence 注册元数据清理;GP `prefers-reduced-motion` 退出劫持;DateTime 守卫;mojibake 清理;缓存插件配套(news 发布失效首页缓存)
### 附录 A:可安全删除清单(P1-3)
1. `blocks/section-heading/` 整目录(seed 零使用)
2. stat bp 分支 + 4 SVG、stats-bar bp 分支、business-intro `slot:'stats'`(成对遗骸,打包删)
3. hero `showScroll`(render+block.json+index.js 三处)+ `bgAlt` 属性
4. cap `svgIcon` + 4 SVG 映射(iconAlt 保留)
5. feature-card `ae-marker` 变体 + x/y/dir/hub 属性 + 13 内置 SVG(**必须连 feature-cards/index.js 模板 icon 预置一起删**——唯一耦合项)
6. feature-cards as-region 模板的 `flag:'CN/AE/GH'`(属性未注册,静默丢弃的死字节)
7. product-card 5 内置 SVG + `icon` 枚举(连 product-cards 模板一起删)
8. banner-cell `kicker` 三处
9. `css/style.css`(先把 editor-style.css 的 @import 改指根 `style.css`,消除双胞胎漂移)
+90
View File
@@ -0,0 +1,90 @@
# 交付前最终验收记录 — 批次 C+F(图片压缩 / 全量验收 / 重建交付包)
日期:2026-08-19 | 环境:`.test-env` SQLite 测试站(http://127.0.0.1:8090,PHP 内置服务器)| 结论:**全部通过,可交付**
---
## Part C — 图片压缩 + 清库重播
### C1 压缩统计(GD 原地重编码,`.test-env/compress.php`,不入库)
| 指标 | 数值 |
|---|---|
| 扫描(jpg+png) | 127 |
| 处理成功 | 47(jpg 46 + png 1) |
| 跳过 <100KB / logo.png / 小 png | 65 / 1 / 14 |
| 失败 / 压缩后反而变大回退 | 0 / 0 |
| 处理文件合计 | 20.83 MB → 4.18 MB(20.1%) |
| **主题 image/ 目录总大小** | **27.30 MB → 11.43 MB(-58%)** |
参数:jpg >1920w 等比缩 + `imagejpeg q78`;png >1600w 等比缩 + `imagepng 6`;全部临时文件写入后原子替换,校验输出有效性。
达标抽检:`home/banner.jpg` 908KB → 94.5KB(预期 <250KB ✓);`news/*` 230–692KB → 34–125KB。
**偏差项**:`home/global-map.png` 1.43MB → 1.28MB(仅 -8.8%,未达 <400KB 预期)。该图 73% 全透明 + 27% 半透明(GP 页叠加层地图,必须保 alpha 的 PNG);调色板量化实测 PSNR 10.4dB 且 alpha 被破坏(GD 的 palette PNG 仅二元透明),弃用。GD 约束下已是最优,遗留项见文末。
### C2 清库重播
| 步骤 | 结果 |
|---|---|
| 强删全部附件 | 96 个 |
| 清 uploads 年度目录(保留 hesu-backups/wpcf7_uploads) | ✓ |
| `delete_option(hesu_seeded)` + 重播 | ✓ |
| news 数 | 4 ✓ |
| **uploads 体积** | **124 MB → 17 MB**(新压缩图侧载) |
| hesu-backups 保留 | ✓(最新 json:pages 9 / news 4 / menus 2) |
---
## F1 — 审计修复项逐条验证(10/10 通过)
| # | 项 | 结果 | 实测 |
|---|---|---|---|
| 1 | H1 结构 | ✓ | news 详情 / about-us / news 列表页均 H1=1 |
| 2 | OG / JSON-LD | ✓ | 首页 og: 8 种(≥6)+ twitter:card=1;news 详情 ld+json 含 `"@type":"Article"`;RSS feed og=0 |
| 3 | lazy / preload | ✓ | building-materials:lazy=11、hero fetchpriority="high"×1、srcset=2;首页 preload banner=1 |
| 4 | CF7 收缩 | ✓ | 首页 contact-form-7 引用=0;/contact/ 表单正常(wpcf7×13) |
| 5 | emoji | ✓ | 首页 wp-emoji=0 |
| 6 | 业务 JS | ✓ | 首页仅 main.js + building-materials.js |
| 7 | news-grid alt | ✓ | 4 卡 alt=News1–4 非空;唯一空 alt 为装饰性 nw-hero__bg(合规) |
| 8 | 文案 | ✓ | settings-page.php 含"删除并重建";readme 无"15 篇" |
| 9 | 备份 | ✓ | hesu-backups 最新 json pages=9(news=4, menus=2) |
| 10 | screenshot.png | ✓ | 主题根存在(1200×900) |
## F2 — 全站回归(11 路由 + 交互抽查,全部通过)
路由 200:`/`、`/about-us/`、`/contact/`、`/development-history/`、`/global-presence/`、`/global-logistics/`、`/after-sales/`、`/auto-export/`、`/building-materials/`、`/news/`、`/news/<slug>/` —— 11/11 ✓
结构抽查:bp-cap×4 + bp-cap__label×4 ✓|au-gps__card×4 ✓|ct-map__info×2 ✓|nd- 结构(nd-article/body/figure/title/meta/nav)✓|Completed Delivery×1 ✓
Playwright 交互(5/5):
| 交互 | 结果 |
|---|---|
| 首页 stats 滚动计数 | ✓ 服务端输出 `15000`,渲染为 `15,000`(toLocaleString 证明 count-up 执行),4/4 触发 |
| 首页 network 拖拽 | ✓ 真实鼠标拖拽 translateX(-1134px)→(-1481px),按卡片步长吸附,克隆 12 卡 |
| GP 翻屏 | ✓ 滚轮 y 0→900→1800(5 屏),侧导航高亮 Middle East→Africa 联动 |
| CF7 空提交红校验 | ✓ 表单 invalid、6 字段红 tip(rgb(229,72,77))+ 红色汇总提示 |
| 移动端抽屉(390×844) | ✓ 开:main-nav open + backdrop + body 锁滚 + aria-expanded=true;backdrop 点击关:全部复位 |
控制台:首页 / global-presence / contact 均 0 error、0 请求失败。
## F3 — 重建 zip + 干净安装
| 项 | 结果 |
|---|---|
| zip 重建(tar -a,排除 .git) | **27 MB → 13.1 MB(13,084,014 字节,-52%)** |
| 干净安装(删 SQLite + uploads/2026 → core install + siteurl 修正 → CF7 → permalink → zip 装入激活) | ✓ |
| 安装后验证 | 首页 200 / Four Core Businessesk=1 / news=4 / ct-map__info-title=1 / og=9 / lazy=6 ✓ |
| 侧载健康 | 96 附件、0 重复 src、uploads 17MB(≈压缩前 124MB 的 14%) |
| junction 恢复 | ✓(PowerShell New-Item Junction),恢复后首页 200 |
安装过程共跑 3 轮:#1、#3 完全干净;#2 出现图片双份侧载(96→163 附件,66 个重复 src),根因为 seed 守卫"入口检查 hesu_seeded / 末尾置位"无跨进程锁——当时一个遗留的空闲浏览器标签在 seed 的 ~40 秒窗口内并发请求触发了第二次播种。单请求激活路径(生产场景:管理员一次点击激活)不受影响,两轮干净验证一致。已列遗留加固项。
---
## 遗留项(不阻塞交付)
1. **seed 并发加固**(建议下轮):`hesu_run_seed()` 入口守卫与 `update_option('hesu_seeded')` 之间无锁,激活播种窗口(~40s)内的并发请求会双份侧载图片。建议:入口先原子置位(如 `add_option` 幂等失败即退出)或加 transient 锁。生产单管理员激活流程不受影响。
2. **global-map.png**:GD 约束下仅 -8.8%(1.28MB,1600w truecolor+alpha)。如需 <400KB,需 pngquant 式带 alpha 量化(外部工具)或转 WebP(需改引用与兼容策略),超出本轮 GD 原地压缩边界。
3. **screenshot.png**(1.09MB,主题根):不在 image/ 压缩范围内;WP 会自动缩放展示,可下轮顺手压。
4. 主题 image/ 内若干与静态站根目录同名但静态侧已删除的文件按约定**全部保留**,待下一轮静态→主题同步时统一处理。
@@ -0,0 +1,74 @@
# HESU WordPress 主题 — 全量验收日志(Task 13)
日期:2026-08-17
环境:WP 7.0.4 + SQLite + CF7 6.1.6(http://127.0.0.1:8090);静态对照站 http://127.0.0.1:8080
方法:Playwright 浏览器实测 + wp-cli + curl;截图/控制台记录在 `.playwright-mcp/`
结论:**20/20 项通过(含 8 项发现即修复)**,无结构性阻塞。修复清单见文末。
## A. 前端对照(A1-A12)
| # | 项目 | 结果 | 证据 |
|---|------|------|------|
| A1 | 首页 | 通过 | stats data-count 20/15,000/72/30 动画生效;bento 4 卡+banner;news 4 卡;reveal 12 处 |
| A1i | 轮播 | 通过 | network 12 卡/8 克隆、真实拖拽 -1774→-2321px;partner 自动轮播 2→0 + 3 圆点可点 |
| A2 | 建材 | 通过 | is-bp 浅色底;5 张 SVG 产品卡;`#products` 锚点 + view-more |
| A3 | 汽车出口 | 通过 | 4 个 bp-cap SVG 能力卡;ae-pulse 2.6s 标注 5 个;hover 静态站同样无 — 记录不改 |
| A4 | 售后改装 | 通过 | 与静态结构一致(同 A3 组件) |
| A5 | 物流 | 通过 | bp-stat 计数 1250/12/99/80(iconKey 修复后图标齐全) |
| A6 | 翻屏 | 通过 | wheel 每屏 889px;nav data-i 1-4 active 跟随;点击直达屏 4(3556px) |
| A7 | 关于 | 通过 | au-1 pin intro 淡出 1→0;au-3 zoom 1→2.2 + reveal 0→1;au-gps 3 卡自动播+圆点 |
| A8 | 时间线 | 通过 | 5 段/3 节点,与静态一致(静态即纵向折叠式) |
| A9 | 联系 | 通过 | 7 个可见 label;空提交 6 字段 invalid 红 #e5484d;OSM iframe 1265x460(src 修复后与静态逐字一致);select chevron + 按钮箭头桥接 |
| A10 | 新闻 | 通过 | tab → /news-cat/delivery-cases/ 高亮+过滤;分页 1/2/3/› → /news/page/2/;single 面包屑 Home/News;最新一篇无 next(正确) |
| A11 | Header/Footer | 通过 | Business mega 8 卡、About mega 4 卡;移动抽屉 14 链接;footer 5 列+社交+年份;contact 页 footer-contact 行(修复后) |
| A12 | 菜单高亮 | 通过 | bm/after-sales → Business 高亮;/news/ 仅 News(双高亮修复后) |
JS 隐藏内容检查:10 路由均服务端渲染完整内容(禁 JS 时不缺内容)。
## B. 后台流程(B13-B17)
| # | 项目 | 结果 | 证据 |
|---|------|------|------|
| B13 | 新闻流程 | 通过 | 新建 post 802 → 首页网格出现 + exhibition-info 归档 → 删除 → 两处消失 |
| B14 | Customizer | 通过 | tiktok 改 → footer href 生效;新闻 hero 改 → aboutus2 图;copyright 改 → footer 文案;全部还原 |
| B15 | CF7 | 通过(含 2 修复) | 停用 → 前端占位提示(修复后);激活 → 表单回归;删除表单 152 → 再激活 → 自动重建 803 "HESU Inquiry" + formId 写回 + 7 label 齐全 |
| B16 | 编辑角色锁定 | 通过(含 1 修复) | 见下详述 |
| B17 | meta description | 通过(补丁) | 9 页 + 新闻归档均有 `<meta name="description">`(wp_head 钩子;页面用 excerpt) |
### B16 详述(编辑角色锁定)
- 建号 acceptor_editor(editor 角色)登录,打开建材页块编辑器:
- **无插入器**:`editor-document-tools__inserter-toggle` display:none(lock.js CSS + allowed_block_types_all 收窄 hesu/* 双保险);强制点击无反应。
- **page-shell/子块选项菜单无 Delete / Move to Trash / Duplicate**:仅 Rename/Hide。
- **发现**:WP 7.0.4 对 editor 开放 Lock/Unlock 模态框,实测可解除实例锁后 Delete(templateLock:'all' 不回护已解锁子块)→ 已修复:`block_editor_settings_all` 强制 `canLockBlocks=false`(非管理员),工具栏变为禁用 "Locked" 指示器,Unlock 入口消失。
- **文字可改**:Business Introduction → +X → 保存 → DB + 前端生效 → 还原 → DB 干净。
- 测毕删号:`wp user delete acceptor_editor --reassign=1`,仅剩 admin。
## C. 质量项(C18-C20)
| # | 项目 | 结果 | 证据 |
|---|------|------|------|
| C18 | php -l | 通过 | 55 个 PHP 文件递归,0 语法错误 |
| C19 | 控制台 | 通过(含 1 修复) | 10 路由 0 error/0 warning(contact 的 OSM iframe LngLat 报错系 `&` 损坏,修复后归零) |
| C20 | 死链/404/字体 | 通过 | 28 个内链 curl 全 200;锚点 #business/#gp-2..5/#products/#inquiry/#top 全存在;7 个 woff2 全 200;垃圾 URL 404 |
## 修复清单(本轮验收产出)
1. **meta description**(B17):`inc/setup.php` 加 `hesu_meta_description()`(wp_head 优先级 5;singular 用 excerpt、fallback 首段 30 词;新闻归档/分类固定文案);`inc/seed.php` 9 页补 excerpt。
2. **首页 Learn More 链接**:post 704 内 `?page_id=712` → `/contact/`(DB 手术)。
3. **物流页 stat 图标缺失**:`hesu/stat` 增加 iconKey 白名单内联 SVG(block.json/render.php/index.js + helpers.js 提供 RawHTML + seed/markup.php 传 iconKey),重生成 708。
4. **联系页 footer-contact 行**:`inc/walker-mega.php` contact 页 intro 列补 Email/Tel(对齐静态 contact.html 178)。
5. **/news/ 双高亮**:walker-mega 祖先高亮跳过与顶级项同 URL 的子项。
6. **CF7 停用裸 shortcode**(spec §12.8 违规):`blocks/contact-form/render.php` 增加 `$cf7_active` 插件在位判断,缺插件时输出可见引导占位;`css/contact.css` 补 select chevron + 提交按钮箭头。
7. **编辑角色锁绕过**:`inc/blocks.php` 加 `block_editor_settings_all → canLockBlocks=false`(非管理员)。
8. **种子写入剥转义(本轮最重)**:`wp_insert_post/wp_update_post` 按已转义数据处理,直接传序列化区块会把 `&` 的 `&` 转义剥成 `u0026`(实锤:联系页 OSM iframe src 损坏 → Leaflet "Invalid LngLat" 控制台报错、静态站同 URL 无错)。修复:`inc/seed.php` 三处写入(页面内容二次填充、新闻插入、formId 写回)统一 `wp_slash()` + 前后 `kses_remove_filters()/kses_init_filters()`;DB 中 712 已修复,坏 revision 804/809 已删。
## 已接受的偏差(静态 vs WP,非缺陷)
- auto-export bp-viewmore 用 div、静态用 section(样式无差);新闻 tab 为 All+4 分类(WP 数据驱动,静态是 3 个 JS 过滤 tab);footer 当前页高亮(静态无);contact 提交在无邮件环境走 failed 状态(静态语义本就如此,sent 绿色态已用 class 切换验证)。
- Footer 的 Privacy/Terms/Sitemap 保持静态站的 `#top` 占位链接(spec §6 曾设想 Customizer 化;与静态站 1:1 一致优先,未实现——如需后续补,加 3 个 URL 设置即可)。
- contact-map 的 iframe URL 可配置(管理员可嵌任意 https 页面);如需收紧可加 OpenStreetMap 域白名单。
## 遗留项
- 无阻塞遗留。备注:编辑角色对文章内容的原始 REST 写入权限属 WordPress 角色模型本身,主题锁定目标(防误操作)已达成。
@@ -0,0 +1,668 @@
# HESU WordPress 主题实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 把 `d:\wp\demojt` 的 10 页静态站迁移为 WordPress 经典主题 `hesu`(原生 Gutenberg 区块 + 结构锁定 + CPT 新闻 + 激活播种),在一次性 SQLite 测试站实跑验收。
**Architecture:** 经典 PHP 主题 + 22 个服务端渲染自定义区块(block.json + render.php + 无构建原生 wp.element 编辑器脚本),内容存 post_content 并用 `templateLock` + 实例锁锁死结构;CSS/JS 从静态站原样迁移、零改写(仅 main.js 小幅扩展);`after_switch_theme` 播种全部页面/菜单/新闻/图片。
**Tech Stack:** PHP ≥7.4、WordPress ≥6.1(测试站 6.x 最新)、Contact Form 7、便携 PHP(测试用,不入库)、wp-cli phar、wp-sqlite-db drop-in。
**规格文档(唯一需求来源):** `docs/superpowers/specs/2026-08-15-hesu-wordpress-theme-design.md` — 实现中所有取舍以 spec 为准;本计划任务与其 §编号 对应。
**重要约定(全任务适用):**
- 静态 HTML 是前端输出的**唯一真相**:每个区块 render.php 的输出必须与对应静态片段**逐 class 逐层级一致**。
- CSS 原样迁移;仅两处例外:main.js 追加 reveal 扩展、contact.css 末尾**追加** CF7 桥接规则(不改既有规则)。contact.js 迁移时**剥离表单校验分支**(CF7 接管),保留其余逻辑。
- RichText 属性一律普通 string(**禁止 `source:'html'`**,动态区块会丢内容)。
- **save 规则**:叶子区块 `save: () => null`;**容器区块(含 InnerBlocks)save 必须返回 `el(wp.blockEditor.InnerBlocks.Content)`**,否则序列化时内层内容全部丢失。
- 所有输出转义(esc_html/esc_attr/esc_url);`stat-num` 内数字文本节点必须是 firstChild。
- 测试环境位于 `d:\wp\demojt\.test-env\`(gitignore),主题位于 `d:\wp\demojt\hesu-theme\hesu\`,测试站以 junction 挂载(`cmd //c mklink /J`)。
- 无单元测试框架。每个任务的"测试"= `php -l` + curl/wp-cli 断言(先写断言命令,再实现,再跑),Task 13 全量验收兜底。
- 每个任务结束必须 commit(Task 0 建立 git)。
---
### Task 0: Git 仓库初始化
**Files:**
- Create: `d:\wp\demojt\.gitignore`
- [ ] **Step 1: 初始化仓库与 .gitignore**
`.gitignore` 内容:
```
.test-env/
*.zip
.playwright-mcp/
```
- [ ] **Step 2: 提交静态站基线**
```bash
cd d:/wp/demojt && git init && git add -A && git commit -m "chore: static site baseline (source of truth)"
```
Expected: 初始提交完成,`.test-env` 不入库。
---
### Task 1: 一次性测试环境(便携 PHP + WP + SQLite + CF7)
**Files:**(全部在 `.test-env/`,不 commit)
- `.test-env/php/`(便携 PHP)
- `.test-env/wp-cli.phar`
- `.test-env/wp/`(WordPress)
- `.test-env/wp/wp-content/db.php`(wp-sqlite-db drop-in)
- `.test-env/wp/wp-content/plugins/contact-form-7/`
- [ ] **Step 1: 下载便携 PHP(含 pdo_sqlite)**
从 https://windows.php.net/downloads/releases/ 下载最新 PHP 8.2 "VS16 x64 Non Thread Safe" zip,解压到 `.test-env/php/`。
- [ ] **Step 2: 配置 php.ini**
复制 `php.ini-development` 为 `php.ini`,确保以下扩展行去掉分号:`extension_dir="ext"`、`extension=pdo_sqlite`、`extension=sqlite3`、`extension=mbstring`、`extension=curl`、`extension=openssl`、`extension=fileinfo`、`extension=gd`。curl 需要 cacert:下载 https://curl.se/ca/cacert.pem 到 `.test-env/php/cacert.pem` 并设 `curl.cainfo=` 与 `openssl.cafile=`。
验证:`.test-env/php/php.exe -v` 输出版本;`php -m | grep -i sqlite` 有 pdo_sqlite。
- [ ] **Step 3: 下载 wp-cli 与 WordPress**
```bash
cd d:/wp/demojt/.test-env
curl -sOL https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
curl -sOL https://wordpress.org/latest.zip
unzip -q latest.zip && mv wordpress wp
php/php.exe wp-cli.phar --info --path=wp | head -5
```
- [ ] **Step 4: 安装 wp-sqlite-db drop-in**
下载 https://github.com/aaemnnosttv/wp-sqlite-db/raw/master/db.php(或其 src/db.php)到 `wp/wp-content/db.php`。同时创建空目录 `wp/wp-content/database/`。
- [ ] **Step 5: 安装 WP 与 CF7**
```bash
cd d:/wp/demojt/.test-env
php/php.exe wp-cli.phar core install --path=wp --url=http://127.0.0.1:8090 \
--title="HESU Dev" --admin_user=admin --admin_password=admin123 --admin_email=admin@example.com --skip-email
php/php.exe wp-cli.phar plugin install contact-form-7 --path=wp --activate
php/php.exe wp-cli.phar rewrite structure '/%postname%/' --hard --path=wp
```
- [ ] **Step 6: 启动开发服务器并验证**
```bash
cd d:/wp/demojt/.test-env/wp && ../php/php.exe -S 127.0.0.1:8090
```
(后台运行;后续所有 curl 断言打 `http://127.0.0.1:8090`)
```bash
curl -s http://127.0.0.1:8090/ | grep -o "<title>.*</title>"
```
Expected: 输出 HESU Dev 站点标题。数据库为 `.test-env/wp/wp-content/database/.ht.sqlite`(无 MySQL)。
- [ ] **Step 7: Commit**(仅提交可能的辅助脚本,`.test-env` 已 ignore)
```bash
cd d:/wp/demojt && git add -A && git commit -m "chore: disposable WP test env (ignored)" --allow-empty
```
---
### Task 2: 主题骨架 + 静态资产迁移 + header/footer
**Files:**
- Create: `hesu-theme/hesu/style.css`、`functions.php`、`header.php`、`footer.php`、`index.php`
- Create: `hesu-theme/hesu/inc/setup.php`、`inc/assets.php`
- Create: `hesu-theme/hesu/css/`、`js/`、`fonts/`、`image/`(从根目录原样拷贝,保持相对结构)
- Create: `.test-env/wp/wp-content/themes/hesu`(junction 指向 `d:/wp/demojt/hesu-theme/hesu`)
- [ ] **Step 1: 拷贝静态资产**
```bash
cd d:/wp/demojt && mkdir -p hesu-theme/hesu
cp -r css js fonts image hesu-theme/hesu/
```
注意:`style.css` 会被主题版覆盖(下一步),其余原样。
- [ ] **Step 2: style.css 主题头 + 原样式**
在拷入的 `css/style.css` 基础上,主题根 `style.css` = 以下头部 + `css/style.css` 全文:
```css
/*
Theme Name: HESU
Theme URI: https://example.com
Author: HESU
Description: HESU corporate theme — building materials export, auto export, after-sales & modification, global logistics.
Version: 1.0.0
Requires at least: 6.1
Requires PHP: 7.4
Text Domain: hesu
*/
```
(`6.1` 因 block.json `render` 属性需 WP 6.1+。style.css 中相对引用 `image/`、`fonts/` 的路径层级与静态站一致,路径不变。)
- [ ] **Step 3: index.php(WP 必需,缺则主题 broken)**
```php
<?php get_header(); ?>
<main class="page-content"><?php
while ( have_posts() ) { the_post(); the_content(); }
?></main>
<?php get_footer(); ?>
```
- [ ] **Step 4: functions.php + inc/setup.php**
`functions.php`:
```php
<?php
define( 'HESU_VERSION', '1.0.0' );
define( 'HESU_DIR', get_template_directory() );
define( 'HESU_URI', get_template_directory_uri() );
require HESU_DIR . '/inc/setup.php';
require HESU_DIR . '/inc/assets.php';
// 后续任务追加: cpt-news.php, blocks.php, walker-mega.php, customizer.php, seed.php
```
`inc/setup.php`:
```php
<?php
add_action( 'after_setup_theme', function () {
add_theme_support( 'title-tag' );
add_theme_support( 'post-thumbnails', array( 'post', 'page', 'news' ) );
add_theme_support( 'custom-logo', array( 'height' => 60, 'flex-width' => true ) );
add_theme_support( 'html5' );
add_post_type_support( 'page', 'excerpt' );
register_nav_menus( array(
'primary' => __( 'Primary Menu', 'hesu' ),
'footer' => __( 'Footer Menu', 'hesu' ),
) );
} );
```
- [ ] **Step 5: inc/assets.php(全站层)**
```php
<?php
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_style( 'hesu-fonts', HESU_URI . '/css/fonts.css', array(), HESU_VERSION );
wp_enqueue_style( 'hesu-style', get_stylesheet_uri(), array( 'hesu-fonts' ), HESU_VERSION );
wp_enqueue_script( 'hesu-main', HESU_URI . '/js/main.js', array(), HESU_VERSION, true );
} );
```
- [ ] **Step 6: header.php / footer.php**
以 `index.html:17-118`(header)与 `index.html:322-387`(footer)为模板移植:
- Logo:`the_custom_logo()`,无自定义 logo 时回退 `image/logo.png`。
- 导航部分此阶段先输出**静态占位结构**(保持 `.nav-item.has-mega`、`.mega` 全部 class/层级),菜单数据化在 Task 10 实现。
- `<span id="year">` 保留(main.js 用)。
- `wp_head()` / `wp_footer()` / `body_class()` 齐全;`<main>` 由区块渲染,模板不放。
- [ ] **Step 7: junction 挂载主题并激活**
```bash
cd d:/wp/demojt/.test-env/wp/wp-content/themes
cmd //c "mklink /J hesu d:\\wp\\demojt\\hesu-theme\\hesu"
cd d:/wp/demojt/.test-env && php/php.exe wp-cli.phar theme activate hesu --path=wp
```
- [ ] **Step 8: 断言**
```bash
cd d:/wp/demojt/hesu-theme/hesu && ../../.test-env/php/php.exe -l header.php && ../../.test-env/php/php.exe -l footer.php && ../../.test-env/php/php.exe -l index.php
curl -s http://127.0.0.1:8090/ | grep -c "site-header"
```
Expected: 语法通过;首页含 header 结构(此时首页为空内容,正常)。
- [ ] **Step 9: Commit**
```bash
cd d:/wp/demojt && git add hesu-theme && git commit -m "feat(theme): skeleton, assets, header/footer"
```
---
### Task 3: 新闻 CPT + 分类法
**Files:**
- Create: `hesu-theme/hesu/inc/cpt-news.php`(functions.php 追加 require)
- [ ] **Step 1: 实现**
```php
<?php
add_action( 'init', function () {
register_post_type( 'news', array(
'labels' => array(
'name' => __( 'News', 'hesu' ), 'singular_name' => __( 'News Article', 'hesu' ),
'add_new_item' => __( 'Add New News Article', 'hesu' ),
),
'public' => true, 'has_archive' => true, 'menu_icon' => 'dashicons-megaphone',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'rewrite' => array( 'slug' => 'news' ),
'show_in_rest' => true,
) );
register_taxonomy( 'news_cat', 'news', array(
'labels' => array( 'name' => __( 'News Categories', 'hesu' ) ),
'public' => true, 'hierarchical' => false,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'news-cat' ),
) );
} );
add_action( 'pre_get_posts', function ( $q ) {
if ( is_admin() || ! $q->is_main_query() ) return;
if ( $q->is_post_type_archive( 'news' ) || $q->is_tax( 'news_cat' ) ) {
$q->set( 'posts_per_page', 5 );
}
} );
```
- [ ] **Step 2: 断言**
```bash
curl -s "http://127.0.0.1:8090/?post_type=news" -o /dev/null -w "%{http_code}"
cd d:/wp/demojt/.test-env && php/php.exe wp-cli.phar taxonomy list --path=wp --format=csv | grep news_cat
```
Expected: 200;taxonomy 存在。刷新固定链接:`wp-cli.phar rewrite flush --path=wp`。
- [ ] **Step 3: Commit** `feat(news): CPT + taxonomy`
---
### Task 4: 区块框架 + 范例区块 section-heading
**Files:**
- Create: `hesu-theme/hesu/inc/blocks.php`(functions.php 追加 require)
- Create: `hesu-theme/hesu/blocks/editor/helpers.js`
- Create: `hesu-theme/hesu/blocks/section-heading/{block.json,render.php,index.js}`
- Create: `hesu-theme/hesu/editor-style.css`(此任务建基础版,Task 12 Step 2 扩充至全部页面 CSS)
- [ ] **Step 1: inc/blocks.php 自动注册**
```php
<?php
// 独立编辑器脚本不做任何构建:block.json 用 file: 引用,WP 自动生成 handle。
add_action( 'init', function () {
wp_register_script( 'hesu-blocks-helpers', HESU_URI . '/blocks/editor/helpers.js',
array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components' ), HESU_VERSION );
foreach ( glob( HESU_DIR . '/blocks/*', GLOB_ONLYDIR ) as $dir ) {
if ( file_exists( $dir . '/block.json' ) ) {
register_block_type( $dir );
}
}
} );
```
- [ ] **Step 2: helpers.js(全区块共用)**
```js
window.HESU = {
el: wp.element.createElement,
RichText: wp.blockEditor.RichText,
MediaUpload: wp.blockEditor.MediaUpload,
useBlockProps: wp.blockEditor.useBlockProps,
InspectorControls: wp.blockEditor.InspectorControls,
TextControl: wp.components.TextControl,
SelectControl: wp.components.SelectControl,
PanelBody: wp.components.PanelBody,
};
```
- [ ] **Step 3: 范例区块 section-heading 完整实现**
`blocks/section-heading/block.json`:
```json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "hesu/section-heading",
"title": "Section Heading",
"category": "hesu",
"render": "file:./render.php",
"editorScript": ["hesu-blocks-helpers", "file:./index.js"],
"attributes": {
"title": { "type": "string", "default": "" },
"lead": { "type": "string", "default": "" },
"btnLabel": { "type": "string", "default": "" },
"btnUrl": { "type": "string", "default": "" },
"center": { "type": "boolean", "default": true }
}
}
```
`render.php`(输出对齐 `index.html:201-208` 的 `.section-head.center` 结构):
```php
<?php
$center = $attributes['center'] ? ' center' : '';
?>
<header class="section-head<?php echo esc_attr( $center ); ?> reveal">
<?php if ( ! empty( $attributes['lead'] ) ) : ?>
<h2 class="section-title"><?php echo esc_html( $attributes['title'] ); ?></h2>
<p class="section-lead"><?php echo wp_kses( $attributes['lead'], array( 'strong' => array(), 'br' => array(), 'a' => array( 'href' => array() ) ) ); ?></p>
<?php else : ?>
<h2 class="section-title"><?php echo esc_html( $attributes['title'] ); ?></h2>
<?php endif; ?>
<?php if ( ! empty( $attributes['btnLabel'] ) ) : ?>
<a href="<?php echo esc_url( $attributes['btnUrl'] ); ?>" class="btn btn-outline"><?php echo esc_html( $attributes['btnLabel'] ); ?></a>
<?php endif; ?>
</header>
```
`index.js`(**这是所有区块编辑器脚本的模板范例**,后续区块同构):
```js
(function () {
var el = HESU.el, RichText = HESU.RichText, useBlockProps = HESU.useBlockProps,
InspectorControls = HESU.InspectorControls, TextControl = HESU.TextControl;
wp.blocks.registerBlockType('hesu/section-heading', {
edit: function (props) {
var attrs = props.attributes, set = props.setAttributes, bp = useBlockProps();
var panel = el(InspectorControls, {},
el(HESU.PanelBody, { title: 'Settings', initialOpen: true },
el(TextControl, { label: 'Button Label', value: attrs.btnLabel, onChange: function (v) { set({ btnLabel: v }); } }),
el(TextControl, { label: 'Button URL', value: attrs.btnUrl, onChange: function (v) { set({ btnUrl: v }); } })));
var view = el('header', bp,
el(RichText, { tagName: 'h2', className: 'section-title', value: attrs.title, onChange: function (v) { set({ title: v }); }, placeholder: 'Title' }),
el(RichText, { tagName: 'p', className: 'section-lead', value: attrs.lead, onChange: function (v) { set({ lead: v }); }, placeholder: 'Lead' }));
return [panel, view];
},
save: function () { return null; } // 动态渲染
});
})();
```
> **全计划通用模式**:`block.json`(属性表)+ `render.php`(照抄静态片段、属性注入)+ `index.js`(上例结构:Inspector 面板 + 内联预览)。
>
> **save 规则(关键)**:叶子区块(本例)`save: function () { return null; }`;**容器区块(含 InnerBlocks 的 hero-home/stats-bar/page-shell/business-intro/product-cards/feature-cards/bento-grid/hub-carousel/timeline/gp-shell/au-stage 等)save 必须返回 `el(wp.blockEditor.InnerBlocks.Content)`**——save 返回 null 的容器区块序列化时内层内容会全部丢失。
- [ ] **Step 4: editor-style.css 基础版**
`editor-style.css` = `@import url("css/fonts.css"); @import url("css/style.css");` 起步(后续任务把页面 CSS 逐个加入;**注意相对路径基于主题根**)。`inc/assets.php` 增加:
```php
add_action( 'enqueue_block_assets', function () {
if ( is_admin() ) {
wp_enqueue_style( 'hesu-editor', HESU_URI . '/editor-style.css', array( 'wp-edit-blocks' ), HESU_VERSION );
}
} );
add_filter( 'should_load_separate_core_block_assets', '__return_true' );
```
- [ ] **Step 5: 断言**
```bash
curl -s http://127.0.0.1:8090/wp-json/wp/v2/block-renderer/hesu/section-heading \
-H "Content-Type: application/json" --data '{"name":"hesu/section-heading","attributes":{"title":"T","center":true}}' | head -c 300
```
Expected: JSON 含 `rendered` 且输出 `section-head center reveal`。(需登录态——改用后台 wp-cli:`wp-cli.phar eval 'echo do_blocks("<!-- wp:hesu/section-heading {\"title\":\"T\"} /-->");' --path=wp`,输出 HTML 片段即通过。)
- [ ] **Step 6: Commit** `feat(blocks): framework + section-heading exemplar`
---
### Task 5: 通用区块组 + body_class 注入 + 资产映射
**Files:**
- Create: `blocks/hero/`、`blocks/stats-bar/`、`blocks/stat/`、`blocks/view-more/`、`blocks/page-shell/`
- Modify: `inc/assets.php`(has_block 映射表 + body_class 过滤器)
- Modify: `page.php`(新建,渲染 the_content)
**各区块要点(block.json 属性 / render 对齐来源):**
- [ ] **Step 1: hesu/hero** — 属性:`bgId`(number)、`bgUrl`(string)、`title`、`sub`(RichText string, 含换行)、`ctaLabel`、`ctaUrl`、`showScroll`(bool)、`variant`(select: `sub-left|sub-center|home|dh|ct`)。render 对齐:业务页 `building-materials.html:79-95`(`.bp-hero`)、居中 `auto-export.html:79-95`、home `index.html:121-152`(home 变体含 `<InnerBlocks/>` 槽位于 `.hero-stats` 内,接受 stats-bar;DH 用 `development-history.html:79-92` `.dh-hero`;CT 用 `contact.html:78-88` `.ct-hero`)。编辑器 JS:home 变体渲染 `el(wp.blockEditor.InnerBlocks, { allowedBlocks: ['hesu/stats-bar'], templateLock: 'all' })`。
- [ ] **Step 2: hesu/stats-bar + hesu/stat** — stats-bar:容器(`.stats-grid`/`.bp-stats` 按 `variant` home|bp),InnerBlocks 允许 `hesu/stat`,templateLock all。stat 属性:`iconId/iconUrl`(可选)、`num`(string)、`suffix`(string)、`label`。render 对齐 `index.html:134-149`(**数字文本节点必须 firstChild**:`<span class="stat-num" data-count="20">20<span class="plus">+</span></span>`)与 `building-materials.html:107-136`(bp 变体带 icon)。
- [ ] **Step 3: hesu/view-more** — 属性 `label/url`,对齐 `building-materials.html:243-245`。
- [ ] **Step 4: hesu/page-shell** — 属性 `className`(seed 写死)、`tag`(main|div,home 用 div)。render:`<main|div class="$className">InnerBlocks 内容</main|div>`。编辑器:InnerBlocks templateLock **'all'**,不提供任何 Inspector。
- [ ] **Step 5: page.php** — 标准循环 + `the_content()`。
- [ ] **Step 6: body_class 注入 + 资产映射**(inc/assets.php 追加)
用 `parse_blocks()` 解析(勿用正则——className 前有 lock 对象,属性顺序不可依赖):
```php
add_filter( 'body_class', function ( $classes ) {
if ( is_singular( 'page' ) && ( $post = get_post() ) ) {
$map = array( // seed 的 className 是受控集合 → 显式映射
'bp' => 'is-bp', // 4 业务页 + 发展历程(浅色基底)
'ct' => 'is-contact',
'gp-main' => 'is-gp', // 语义标记,CSS 无钩子
'au-main' => 'is-au', // 同上
);
foreach ( parse_blocks( $post->post_content ) as $b ) {
if ( 'hesu/page-shell' === $b['blockName']
&& isset( $b['attrs']['className'], $map[ $b['attrs']['className'] ] ) ) {
$classes[] = $map[ $b['attrs']['className'] ];
}
// 首页 className=page-content → 无 body class(静态站首页 body 无 class)
}
}
if ( is_post_type_archive( 'news' ) || is_singular( 'news' ) || is_tax( 'news_cat' ) ) $classes[] = 'is-news';
return $classes;
} );
```
(实现时按此语义写全映射;seed 的 className 值为受控集合,映射安全。)
资产映射表(`wp_enqueue_scripts` 中,按 `has_block()` 逐条;命中任一业务区块 → 追加加载 4 个业务页 JS):
| 条件 | 加载 |
|---|---|
| hero 或 business-intro | business.css |
| 任一业务区块(hero/business-intro/product-cards/brand-wall/showcase/model-list/feature-cards/large-image) | building-materials.js、auto-export.js、after-sales.js、global-logistics.js(自带守卫) |
| product-cards | building-materials.css |
| brand-wall/showcase/model-list | auto-export.css |
| feature-cards | after-sales.css + auto-export.css + global-logistics.css |
| timeline | development-history.css + js |
| gp-shell | global-presence.css + js |
| au-stage 或 au-gps | about-us.css + js |
| news-grid | news.css |
| contact-form | contact.css + js |
- [ ] **Step 7: main.js 扩展** — 追加对 `[data-reveal]` 与 `.nw-row` 的 IntersectionObserver reveal(复用现有 `.reveal` 逻辑参数 threshold 0.12)。**只追加,不改既有行**。
- [ ] **Step 8: 断言** — wp-cli eval 渲染 hero(含 bgUrl)输出含 `bp-hero__title`;`php -l` 全部新文件。
- [ ] **Step 9: Commit** `feat(blocks): hero/stats/view-more/page-shell, body_class, asset map`
---
### Task 6: 业务页区块组
**Files:**
- Create: `blocks/business-intro/`、`blocks/large-image/`、`blocks/feature-cards/`、`blocks/feature-card/`、`blocks/product-cards/`、`blocks/product-card/`、`blocks/showcase/`、`blocks/brand-wall/`、`blocks/model-list/`
**要点(render 对齐来源):**
- [ ] **Step 1: business-intro** — 属性:`heading`、`p1`、`p2`(RichText)、`imgId/imgUrl/imgAlt`、`withStats`(bool)。`withStats=true` 时文本栏内含 InnerBlocks(只允许 stats-bar,lock all)。对齐 `building-materials.html:100-143`。注意 `data-reveal` 属性照抄。
- [ ] **Step 2: large-image** — 属性 `imgId/imgUrl/imgAlt`、`variant`(stock|port)。对齐 `building-materials.html:148-152` 与 `global-logistics.html:148-152`。
- [ ] **Step 3: feature-cards + feature-card** — 容器属性 `variant`(as-services|ae-region|as-region|gl-services) → 决定容器 class;卡片属性 `iconUrl/iconId`、`title`、`desc`、可选 `link`。对齐 `after-sales.html:145-190`、`auto-export.html:192-219`、`after-sales.html:195-234`、`global-logistics.html:157-210`(实现前先读这四个片段确认每个变体的确切 DOM)。
- [ ] **Step 4: product-cards + product-card** — 容器固定 `.bm-products__grid`,容器属性含 `headTitle` 与 `anchor`(section id,seed 值 `products`,页脚/按钮锚点依赖);卡片属性 `icon`(select: hdpe-pipe|hdpe-fittings|pvc-pipe|pvc-fittings|welding → render.php 内置 5 段 SVG,从 `building-materials.html:164-239` 原样拷贝)、`imgUrl`(自定义图覆盖内置 SVG)、`name`。**所有容器区块统一加 `anchor` 属性**(输出 section id),seed 沿用静态站锚点值。
- [ ] **Step 5: showcase / brand-wall / model-list** — 实现前读 `auto-export.html:136-188` 与 `css/auto-export.css` 对应段;showcase=图+文案组合;brand-wall=容器 + InnerBlocks 子块 `hesu/brand-card`(图+alt,锁定列表);model-list=车型名 chip 列表(属性驱动)。
- [ ] **Step 6: 断言** — 每块 wp-cli eval do_blocks 渲染,grep 关键 class(`bp-intro__grid`、`bm-products__grid`、`ae-brands`…);php -l 全过。
- [ ] **Step 7: Commit** `feat(blocks): business page blocks`
---
### Task 7: 首页区块组
**Files:**
- Create: `blocks/bento-grid/`、`blocks/banner-cell/`、`blocks/media-card/`、`blocks/presence/`、`blocks/hub-carousel/`、`blocks/hub-card/`、`blocks/partner/`、`blocks/news-grid/`
- [ ] **Step 1: bento-grid** — 容器:`.bento-grid`,InnerBlocks 模板锁定为 `banner-cell + media-card ×4`(templateLock all + template 数组)。banner-cell 属性 `kicker/title`(对齐 `index.html:166-170`);media-card 属性 `imgUrl/imgId`、`title`、`link`、`phClass`(占位 class 如 ph-materials,图缺省时用)(对齐 `index.html:172-193`)。
- [ ] **Step 2: presence** — 属性 `title/lead(RichText 含 strong)/btnLabel/btnUrl/mapId/mapUrl`。对齐 `index.html:199-215`。
- [ ] **Step 3: hub-carousel + hub-card** — 容器输出 `.network-stage/.network-strip/.network-track`(**卡片必须是 track 直接子元素**,main.js 克隆逻辑依赖);hub-card 属性 `imgUrl/imgId/phClass/label/link`。对齐 `index.html:226-247`。
- [ ] **Step 4: partner** — 属性:slides(InnerBlocks 子块或图列表,输出 `.slide.ph.is-active` 结构)、`overlayTitle`(RichText 含 br)、features ×4(iconUrl+word)。结构复杂,实现前读 `index.html:250-284` 与 `css/style.css` partner 段;**轮播 dots 容器 `#partnerDots` 必须输出**。
- [ ] **Step 5: news-grid** — 动态查询:`new WP_Query(['post_type'=>'news','posts_per_page'=>4])`,卡片对齐 `index.html:294-314`(`news-card/news-img/news-title/news-meta`,meta= 首个 news_cat 词名 + `·` + 年份);属性仅 `moreUrl`。无文章时输出空态注释(seed 前正常)。
- [ ] **Step 6: 断言** — 建 2 篇测试新闻后 eval 渲染 news-grid 含 2 张卡;其余块 eval grep class;php -l。
- [ ] **Step 7: Commit** `feat(blocks): homepage blocks`
---
### Task 8: 特殊页区块组
**Files:**
- Create: `blocks/timeline/`、`blocks/timeline-item/`、`blocks/gp-shell/`、`blocks/gp-screen/`、`blocks/au-stage/`、`blocks/au-screen-1/`、`blocks/au-screen-2/`、`blocks/au-gps/`、`blocks/contact-form/`、`blocks/contact-map/`
- [ ] **Step 1: timeline + timeline-item** — 容器输出轨道结构(对齐 `development-history.html:96-191`,实现前通读);item 属性 `year/title/desc`(如有图则 `imgUrl`)。
- [ ] **Step 2: gp-shell + gp-screen** — shell 输出 `main.gp-main` 内层 `.gp-pagebg`;screen 属性:`variant`(overview|region)、`id`(gp-1..gp-5)、`title`、`lead`、`imgUrl`。**概览屏的 `gp-nav-list` 为锁定的 InnerBlocks 子列表**(spec 修正 #19:nav 是屏内容,每项=文案+`#gp-N` href+`data-i`,用锁定子块或固定 InnerBlocks 模板实现,对齐 `global-presence.html:109-171`,global-presence.js 零适配)。
- [ ] **Step 3: au-stage/au-screen-1/au-screen-2** — stage 输出 `main.au-main` 包裹;screen-1 属性:`bgUrl`(内联 style background-image)、`cornerImgUrl`、`title`、`text`、`slogan`、`sloganCn`;screen-2 属性:`zoomImgUrl`、`secTitle`、`secText`、`logoUrl`(默认主题 logo)、`revealText`。DOM 逐层对齐 `about-us.html:105-140`(`.au-bg/.au-corner-img/.au-inner.au-left/.au-mask/.au-slogan-layer/.au-zoom-img/.au-sec-head/.au-reveal/.au-logo-big`)。
- [ ] **Step 4: au-gps** — 4 层锁定结构(spec §4):`head(title+text)`、`cards×4(imgUrl)`、`wide(imgUrl)`、`split`(data 轮播卡 ×3:`imgUrl/title/desc` + `#auDots 类圆点容器`——实现前读 `about-us.html:143-200` 与 `js/about-us.js` 确认 dots 由 JS 生成还是静态标记)。
- [ ] **Step 5: contact-form** — 属性 `formId`(select,列 `wpcf7_contact_form` 文章);CF7 缺失输出 `.ct-form` 占位引导框(仅编辑者可见文案)。对齐容器 `contact.html:93-146` 的 section 包裹。
- [ ] **Step 6: contact.css 追加 CF7 桥接规则(spec §10)** — 在 `css/contact.css` **末尾追加**(不改既有规则):以 CF7 实际 DOM(`.wpcf7-form`、`.wpcf7-form-control-wrap`、`.wpcf7-not-valid`、`.wpcf7-validation-errors`、`.wpcf7-mail-sent-ok`、`.wpcf7-submit`)为目标复刻 `ct-form/ct-field/ct-label/ct-input/ct-selectwrap/ct-submit/ct-note` 视觉(字段 class 由 seed 挂 `class:ct-input` 等配合)。同时 `js/contact.js` 迁移时**剥离表单校验分支**(`#ctForm` 相关),保留其余。
- [ ] **Step 7: contact-map** — 属性 `embedUrl`(默认静态站 OSM iframe src,`contact.html:151-157`),输出 iframe。
- [ ] **Step 8: 断言** — eval 渲染各块 grep 关键 class(`gp-screen`、`au-pin`、`dh-timeline`、`ct-map__frame`);`grep -c "wpcf7" css/contact.css` ≥ 1;php -l 全过。
- [ ] **Step 9: Commit** `feat(blocks): special page blocks + CF7 bridge`
---
### Task 9: 新闻模板 + Customizer 新闻 Hero 图
**Files:**
- Create: `hesu-theme/hesu/archive-news.php`、`single-news.php`
- Create: `hesu-theme/hesu/inc/customizer.php`(本任务先加新闻 Hero 图设置,Task 10 扩充)
- Modify: `inc/assets.php`(archive/single 直接 enqueue news.css)
- [ ] **Step 1: archive-news.php** — 结构:`.nw-hero`(对齐 `news.html:78-93`,bg=theme mod `hesu_news_hero` 默认 `image/carexport/export-bg.jpg`)→ `.nw-tabs`(`get_terms(['taxonomy'=>'news_cat'])`,当前项由 `get_queried_object()` 判定,链接 `get_term_link`;全量 Tab=归档地址)→ `.nw-list` 循环输出 `.nw-row`(左文右图,对齐 `css/news.css` 的 `.nw-row` 结构与 `js/news.js:84-90` 生成的 DOM:`__body/__title/__excerpt/__more/__media`)→ `the_posts_pagination()`。行元素加 `data-reveal`。
- [ ] **Step 2: single-news.php** — 按设计稿 `demo-html/新闻详情1.jpg`:`.nw-hero`(同上 Hero + 面包屑 Home/News/当前)→ 文章头(分类 chip + 日期 + H1)→ 特色图 → `the_content()` → prev/next(`get_previous_post/get_next_post` 同类型)→ 返回列表按钮。样式复用 news.css,不足处在新文件 `css/news-single.css` 补(**不改 news.css 原有规则**)。
- [ ] **Step 3: 断言** — `curl http://127.0.0.1:8090/news/` 返回 200 且含 `nw-tabs`;建测试文章后 `/news/_slug/` 含 `the_content` 输出。
- [ ] **Step 4: Commit** `feat(news): archive + single templates`
---
### Task 10: Header/Footer 数据化 + Customizer + Mega Menu
**Files:**
- Modify: `header.php`(菜单数据化)、`footer.php`(页脚菜单数据化)
- Create: `inc/walker-mega.php`(自定义渲染函数 `hesu_render_mega( $menu_location, $group )`)
- Modify: `inc/customizer.php`(mega 左侧文案 ×2 组、社交 ×4、页脚标语、版权与底栏链接)
- [ ] **Step 1: Customizer 设置项** — section `hesu_global`:`social_tiktok/facebook/linkedin/whatsapp`(url)、`footer_slogan`、`footer_copyright`、`mega_business_kicker/title/desc/cta`、`mega_about_kicker/title/desc/cta`、`hesu_news_hero`(与 Task 9 的 theme mod **同名**)。
- [ ] **Step 2: mega 渲染** — `wp_get_nav_menu_items('primary')` 按 `menu_item_parent` 分组:顶级项=导航项(含 has-mega 判定:有子项即 mega);每个子项渲染 `.mega-card`:图=目标对象特色图(`get_post_thumbnail_id($item->object_id)`,非 page 对象或无图→输出 `.ph-*` 占位 div 带 `ph-label`)、文案=页面 excerpt 或菜单项 description、链接=`$item->url`。结构对齐 `index.html:28-104`。
- [ ] **Step 3: 菜单 active 类** —
```php
add_filter( 'nav_menu_css_class', function ( $classes, $item ) {
if ( in_array( 'current-menu-item', $classes, true ) ) $classes[] = 'active';
return $classes;
}, 10, 2 );
```
- [ ] **Step 4: footer 数据化** — footer 菜单(顶级=栏目标题 h4,子级=链接);socials 从 theme mods;News 栏链接 seed 时指向分类词。结构对齐 `index.html:322-387`。
- [ ] **Step 5: 断言** — wp-cli 设 theme mod 后 curl 验证:`wp-cli.phar option update social_facebook "https://x.com" --theme --path=wp`(或 `eval 'set_theme_mod(...)'`)→ `curl -s http://127.0.0.1:8090/ | grep -c "footer-social"`。Mega 渲染断言推迟到 Task 11 Step 5(依赖 seed 建菜单)。
- [ ] **Step 6: Commit** `feat(header/footer): data-driven menus, mega renderer, customizer`
---
### Task 11: Seed 播种 + 设置页
**Files:**
- Create: `hesu-theme/hesu/inc/seed.php`、`inc/settings-page.php`
- Create: `hesu-theme/hesu/seed/`(数据文件:15 篇新闻 JSON——从 `js/news.js` 顶部数据数组原样提取、页面区块标记模板 PHP 数组)
- Modify: `functions.php`(require)
- [ ] **Step 1: 图片入库 helper** — `hesu_sideload( $rel_path )`:`copy(HESU_DIR.'/'.$rel_path, $tmp)` + `media_handle_sideload($_FILES 风格数组, 0)`,返回 attachment_id;以 path 为键做静态缓存避免重复。
- [ ] **Step 2: 页面区块标记生成** — 每页一个 PHP 函数返回序列化 block markup(`seed/markup.php`)。示例(建材页骨架,其余同构):
```php
"<!-- wp:hesu/page-shell {\"lock\":{\"move\":true,\"remove\":true},\"className\":\"bp\",\"tag\":\"main\"} -->\n" .
"<!-- wp:hesu/hero {\"lock\":{\"remove\":true},\"variant\":\"sub-left\",\"bgId\":%d,\"title\":\"HESU PIPE\",...} /-->\n" .
"<!-- wp:hesu/business-intro {\"lock\":{\"remove\":true},...} -->\n" .
"<!-- wp:hesu/stats-bar {\"variant\":\"bp\"} -->\n<!-- wp:hesu/stat {...} /--> ×4\n<!-- /wp:hesu/stats-bar -->\n" .
"<!-- /wp:hesu/business-intro -->\n" .
"<!-- wp:hesu/large-image {...} /-->\n" .
"<!-- wp:hesu/product-cards --> ... <!-- /wp:hesu/product-cards -->\n" .
"<!-- wp:hesu/view-more {...} /-->\n" .
"<!-- /wp:hesu/page-shell -->\n"
```
内容默认值全部取静态站对应页面原文。首页 = hero(home,含 stats-bar 槽内 4 个 stat) + page-shell(`page-content`, tag div) 两个顶层块。9 页各一函数;内层子块一律带 `"lock":{"remove":true}`。
- [ ] **Step 3: 主流程** — 守卫 `get_option('hesu_seeded')` → 侧载图片 → 删除 WP 默认 "Sample Page"(`get_page_by_title('Sample Page')` 存在则 `wp_delete_post`)→ `wp_insert_post` 建 9 页(slug: home/building-materials/auto-export/after-sales/global-logistics/global-presence/about-us/development-history/contact;全部设特色图)→ `update_option('show_on_front','page')`/`page_on_front` → 4 分类词 → 15 篇新闻(标题/摘要/内容段落/特色图占位/分类/日期从 news.js 数据)→ 主菜单+页脚菜单(含 mega 卡片子项、News 链接=归档、GP 锚点链接)绑 location → CF7 激活则建默认表单 → `update_option('hesu_seeded', time())`。挂 `after_switch_theme`。
CF7 程序化建表单(机制):CF7 表单 = `post_type=wpcf7_contact_form` 的文章 + post meta:
```php
$id = wp_insert_post( array( 'post_type' => 'wpcf7_contact_form', 'post_title' => 'HESU Inquiry',
'post_status' => 'publish' ) );
$form = '<div class="ct-form__grid">' /* 按 contact.html:95-144 七个字段,形如: */
. '[select* business_type class:ct-input first_as_label "Select business type" "Building Materials Export" ...]'
. '[email* email class:ct-input placeholder "Enter your email"]' /* ... */ '</div>'
. '[submit class:ct-submit "Send Message"]';
update_post_meta( $id, '_form', $form );
update_post_meta( $id, '_mail', array( /* CF7 邮件默认数组,recipient=admin_email */ ) );
```
- [ ] **Step 4: 设置页 + 补建钩子** — `add_theme_page` + 表单按钮(admin-post + nonce + capability `manage_options` → 清守卫重跑 seed);`activated_plugin` 钩子:插件为 CF7 且默认表单缺失 → 补建。
- [ ] **Step 5: 断言(关键节点)**
```bash
cd d:/wp/demojt/.test-env
php/php.exe wp-cli.phar theme activate twentytwentyfour --path=wp # 切走
php/php.exe wp-cli.phar theme activate hesu --path=wp # 触发 seed
for s in home building-materials auto-export after-sales global-logistics global-presence about-us development-history contact; do
php/php.exe wp-cli.phar post list --post_type=page --name=$s --path=wp --format=ids | grep -q . && echo "$s OK" || echo "$s MISSING"
done
php/php.exe wp-cli.phar post list --post_type=news --path=wp --format=ids | wc -w
curl -s http://127.0.0.1:8090/ | grep -c "network-card"
curl -s http://127.0.0.1:8090/building-materials/ | grep -o '<body class="[^"]*"' | grep -c is-bp
```
Expected: 9 个 slug 全 OK;15 篇新闻;首页含 `network-card`;建材页 body 含 `is-bp` class(body_class 注入生效)。
- [ ] **Step 6: Commit** `feat(seed): activation seeding, settings page, CF7 bootstrap`
---
### Task 12: 编辑器加固 + editor-style 完整版
**Files:**
- Create: `blocks/editor/lock.js`(独立小脚本)
- Modify: `inc/blocks.php`(非管理员 enqueue lock.js)、`editor-style.css`(补全部页面 CSS import)
- [ ] **Step 1: 锁定加固(两层,方案固定)** — ① PHP 端:`allowed_block_types_all` 过滤器——非 `manage_options` 用户只返回 `hesu/*` 区块(插入器里看不到任何可插入项,已有内容不受影响);② `blocks/editor/lock.js`(对非管理员 enqueue):注入一段 CSS 隐藏 `.block-editor-inserter__toggle`(插入器按钮)与区块选项菜单中的移动/删除入口。验收两条:editor 角色编辑器无插入器;区块选项菜单无 Move to Trash/Delete。
- [ ] **Step 2: editor-style.css** — 追加 `@import url("css/xxx.css")` 全部页面 CSS(business/auto-export/after-sales/building-materials/global-logistics/development-history/news/contact/global-presence/about-us;**路径基于主题根**)。确认编辑器内业务页预览浅色基底生效(page-shell 容器 class 场景)。
- [ ] **Step 3: 断言** — 建一个 editor 角色测试账号(wp-cli),后台登录该账号打开建材页:无插入器按钮;管理员打开:结构树完整、板块不可删(选项菜单无删除项)。(人工/浏览器验证,记录截图或 curl admin-ajax 不必——用 wp-cli 建 editor:`wp user create ed ed@t.t --role=editor --user_pass=x`,此步允许人工浏览器确认。)
- [ ] **Step 4: Commit** `feat(editor): lock hardening + full editor styles`
---
### Task 13: 全量验收(spec §12 清单)
**Files:** Modify: 发现问题所涉文件;Create: `docs/superpowers/plans/2026-08-15-acceptance-log.md`(记录每条结果)
- [ ] **Step 1: 逐页对照** — 浏览器开 `http://127.0.0.1:8090` 与静态站(`cd d:/wp/demojt && ./.test-env/php/php.exe -S 127.0.0.1:8080`)逐页比对:板块顺序、`.reveal` 滚动动画、数字滚动、Mega Menu 悬停、partner/network 轮播、GP fullpage 翻屏+active 高亮、AU pinned 视屏、时间线横滑、移动端抽屉。每页记录 通过/问题。
- [ ] **Step 2: 功能项** — 新闻 Tab 过滤/分页/首页自动更新;CF7 提交态(邮件 dummy);Customizer 各字段;菜单高亮;editor 角色锁定(Task 12);CF7 停用再激活 → 联系页占位提示。
- [ ] **Step 3: 代码项** — `php -l` 全主题文件循环通过;grep 检查无未转义 echo(抽查 render.php);控制台无 JS 报错(浏览器)。
- [ ] **Step 4: 修复所有未通过项并复测,全部 PASS 后记录** — Commit `test: acceptance round complete`
---
### Task 14: 打包与文档
**Files:**
- Create: `hesu-theme/hesu/readme.txt`(安装说明:装 CF7→传主题→激活→完成;区块编辑器必须;"解锁=破坏页面"警示;seed 重跑按钮说明)
- Create: 打包脚本步骤(不 commit zip)
- [ ] **Step 1: readme.txt 编写**(中英双语要点)
- [ ] **Step 2: 打包验证** — `cd hesu-theme && zip -r hesu.zip hesu -x "*.DS_Store"`;解压到测试站重装走一遍激活流程(干净站点:重建 SQLite 库 `wp-content/database` 删除 + 重跑 wp core install)确认 zip 包自足。
- [ ] **Step 3: 清理测试环境的决定交给用户**(保留供后续开发 or 删除)
- [ ] **Step 4: Commit** `docs: readme, packaging verified`
---
## 任务依赖
0 → 1 → 2 → 3 → 4 → 5 →(6,7,8 可并行)→ 9,10(依赖 5;10 依赖 3 的菜单)→ 11(依赖全部区块)→ 12 → 13 → 14
@@ -0,0 +1,124 @@
# 静态 v2 → 主题同步 变更对照报告(2026-08-17)
基线:分支 `wordpress-theme` @ `66288c5`。本报告由变更分析代理产出,是 v2 同步的实施依据。
用户决策:① footer-top 按正确闭合处理(不复刻嵌套);② 两处原文疑似错误("Four Core Businessesk"、"99% Awards Winner")原样复刻。
---
## 〇、全局公共变更(10 页共有)
### Header — Business mega(inc/walker-mega.php + fallback)
- `.mega-left`:kicker/desc/cta 全部移除,只留 `<h3 class="mega-title">Four Core Businesses</h3>`。
- `.mega-right` 卡片重排(Auto Export → HESU PIPE → Global Logistics → After-sales),`mega-card-text` 只留 `<h4>`(无 p 描述),无 ph-label。
### Header — About mega
- `.mega-left` 完全清空(留空 div)。
- `.mega-right` 缩为 2 卡:Company Overview(about-us.html,ph-easterneurope)、Company History(development-history.html,ph-middleeast)。
### Footer(footer.php + walker-mega)
- `.footer-social`:4 个内联 SVG → `<img>`:TikTok.png/Facebook.png/Instagram.png/whatsapp.png(LinkedIn 被 Instagram 替换,aria-label 同步)。
- footer 按正确闭合处理(用户决策,不复刻旧 10 页的嵌套错误)。
- Business 栏 4 条指向真实业务页;About Us 栏 "Company Profile"→"Company Overview";News 栏 4 条 → 新闻详情(139th Canton Fair / Dubai Branch Opens / West Africa Factory / Clients Visit HQ)。
## 一、逐页正文变更
### index.html
- bento 标题改全大写 "STRONG RESOURCES, RELIABLE DELIVERY"(CSS 另加 text-transform 兜底)。
- `.cell-banner`:kicker 注释掉;banner-title = "Four Core Businessesk"(原样复刻错字);背景 image/home/Mask1.jpg(CSS)。
- 所有 `.ph-label` 删除。
- presence `.section-lead` 文案整段替换(无 strong)。
- network 卡 ph class → `ph-service1..4`。
- partner 特性 li 只留 `span.pf-ico > img`(strong 词删除)。
- news 卡:news-meta 注释掉;标题新文案;链接 → 详情页。
### about-us.html
- `#au-3` 新增 `div.au-zoom-mask`(JS 驱动 0→0.8);`.au-logo-big img` → image/hs.png;**theme js/about-us.js 照抄根 v2**。
- `.au-gps__cards` 4 卡:div 占位 → `<img class="au-gps__card-img" src="image/about-us/Global1..4.jpg">`(元素类型变了)。
- `.au-gps__wide-img` → `<img src="image/about-us/about.jpg">`。
- 数据轮播 3 卡 → 4 卡(新首卡 Auto Export/ph-aboutlb1;HESU PIPE/Cross-Border Logistics/After-sales & Modification)。
- `.au-gps__feature` 重构:feature-overlay 删除 → `.au-gps__feature-copy` > `p.au-gps__feature-title`("Core Strengths")+ `div.au-gps__feature-list` > 4 个 p(Integration Strength / Operational Know-how / One-Stop Export / Digital Management);feature-img 背景 image/about-us/right.jpg(CSS)。
### after-sales.html
- hero bg → image/After-sales/banner.jpg;`a.bp-hero__scroll` 删除。
- `.bp-cap__ico`:内联 SVG span → `<img src="image/After-sales/{...}.png">` ×4。
- intro 图 → image/After-sales/Business-Introduction.jpg。
- `.as-svc__ring`:内联 SVG → `<img src="image/After-sales/Service1..6.png">`(名称不变)。
- `.as-region__head` 文案替换。
- `.as-loc` ×3 大幅简化:body 全删,只剩 `div.as-loc__media > img` + `span.as-loc__title`("Zhengzhou<br/>China" 格式);图 After-sales/{Zhengzhou,Dhabi,ghana}.jpg。
### auto-export.html
- scroll 删除(hero 背景不变)。
- `.ae-models`:ul.ae-models__tags 整块删除;body 拆成两个 `p.ae-models__body`(无 bp-body),文案重写。
- `.ae-map`:5 个 ae-marker 与 ul.ae-map__legend 全删,只剩 `img.ae-map__img`。
### building-materials.html
- scroll 删除。
- `ul.bp-stats` → `ul.bp-caps`:`li.bp-cap` > `img.bp-cap__ico`(image/pipe/years|countries|parters|lines.png)+ 单个 `span.bp-cap__label`("25+ Years"/"100+ Countries"/"1,000+ Partners"/"116 Lines")。
- 产品卡 SVG → `<img src="image/pipe/Product1..5.png">`(名称不变)。
### contact.html
- hero bg → image/contact-us-bnner.jpg;`p.ct-hero__sub` 删除。
- `.ct-map` 完全重做:`div.container > div.ct-map__box` > [iframe.ct-map__frame + div.ct-map__veil + div.ct-map__info > h3.ct-map__info-title "Contact Information" + dl.ct-map__list > 3 × div.ct-map__item(img.ct-map__ico = tel.png/email.png/address.png + div.ct-map__item-text > dt+dd)]。数据:TEL/WhatsApp +8613526635577;Send Email @hesucar.com;Address Building 17-6, 279 Xisanhuan Road, Zhengzhou, Henan, China。背景 image/map-bg.png(CSS)。
### development-history.html / global-presence.html
- 前者正文无变化;后者仅 `.gp-hint`(span.mouse 滚动提示)注释掉。
### news.html
- hero bg → image/news/news-banner.jpg;`p.nw-hero__sub`、`a.nw-hero__scroll` 删除。
- 列表容器 DOM 未变(由 news.js 渲染)。
### news-detail.html(全新页,body.is-news)
```
main.nd-main
├ section.nw-hero(与列表页同款:bg=image/news/news-banner.jpg、title="NEWS";无 sub/scroll)
└ div.nd-body
├ a.nd-back "← Back to News"
├ div.nd-meta > span.nd-meta__cat#ndCat(日期 span 被注释——不显示)
├ h1.nd-title#ndTitle
├ div.nd-article#ndArticle(3 个 <p> 与 2 个 figure.nd-figure>img 交替;图 image/news/detiles{1..4}-{01,02}.jpg)
└ div.nd-nav#ndNav(a.nd-nav__item.nd-nav__prev/.nd-nav__next > span.nd-nav__label("Previous"/"Next") + span.nd-nav__title)
```
CSS: css/news-detail.css(113 行,.nd-* 全套:860px 版心、figure img 80% 宽居中、nd-nav 双列、680px 断点)。JS: js/news-detail.js(117 行,?id=N + 4 篇文章数据;**不迁移**——single-news.php 服务端渲染,reveal 由主题 main.js 扩展覆盖)。
## 二、CSS/JS 变更
- 主题 css/ 需照抄覆盖:style.css、business.css、about-us.css、after-sales.css、auto-export.css、building-materials.css、global-logistics.css、news.css(8 个)。
- contact.css:**合并**——根 v2 全文 + 重追加主题 CF7 桥接段(现 theme 版 L211-300,约 90 行,与 v2 无冲突)。
- 新建 css/news-detail.css;news-single.css 废弃删除(同步改 inc/assets.php)。
- js/about-us.js:照抄覆盖(+zoomMask 逻辑)。
- js/main.js、js/contact.js:根未改,主题版零动作。
- js/news.js:不迁移;但 archive-news.php 跟随——`.nw-row__more` 内 `<span class="arrow">→</span>` 删除、新增 `.nw-empty` 空态("Coming soon — no {cat} yet.",空分类时隐藏分页)。
- style.css `.ph-*` 新映射:ph-materials→HESU-PIPE.jpg、ph-auto→Auto-Export.jpg、**ph-aftermarket→Global-Logistics.jpg、ph-logistics→After-sales.jpg(名字对图互换,照抄)**、ph-middleeast→Company-History.jpg、ph-easterneurope→Company-Overview.jpg、ph-fair/dubai/factory/visit→home/new1..4.png;新增 ph-aboutusright/ph-aboutlb1/ph-service1..4;保留 ph-centralasia/ph-africa(home/Service2/4.jpg)/ph-warehouse/ph-logistics2/ph-exportcars。
## 三、图片资产
新增(复制进 theme image/):根目录散图(After-sales.jpg、Auto-Export.jpg、Company-History.jpg、Company-Overview.jpg、Global-Logistics.jpg、HESU-PIPE.jpg、Facebook.png、Instagram.png、TikTok.png、whatsapp.png、address.png、email.png、tel.png、hs.png、map-bg.png、contact-us-bnner.jpg)+ 目录(After-sales/、about-us/、global-logistics/、news/、pipe/Product1..5.png、home/Mask1.jpg+new1..4.png)。`image/history/development-history-bnner.jpg` 是孤儿文件(无引用,可跳过)。删除 image/aboutus2.jpg(主题侧载清单/markup/news-data 共 5 处引用需换新)。
## 四、差距总结(实施清单)
| 主题文件 | 动作 |
|---|---|
| theme css ×8 + news-detail.css | 照抄覆盖 / 新建 |
| theme css/contact.css | 合并(v2 + CF7 桥接段) |
| theme js/about-us.js | 照抄覆盖 |
| theme image/ | 复制新增 + 删 aboutus2.jpg |
| footer.php + inc/customizer.php | 社交 img 化 + linkedin→instagram 设置;新闻 hero 默认 → image/news/news-banner.jpg |
| inc/walker-mega.php | mega 重构(Business 只留 title;About 空 left + 2 卡;卡片无 p)+ fallback v2 |
| blocks/hero | block.json showScroll default → false |
| blocks/banner-cell | kicker 空则不输出 |
| blocks/partner | strong 词删除(或空不输出) |
| blocks/news-grid | news-meta 删除 |
| blocks/model-list | tags 删除;body 两段(p.ae-models__body ×2) |
| blocks/feature-cards | ae-marker 提取与 legend 输出删除 |
| blocks/feature-card | as-svc/gl-svc SVG→img;as-loc 重写(media + as-loc__title) |
| blocks/product-card | img 分支对齐 aria-hidden;seed 传 imgUrl |
| blocks/cap | 支持第二个 label(物流页双 label) |
| blocks/au-screen-2 | 加 .au-zoom-mask |
| blocks/au-gps | 重写(cards/wide img 化、4 数据卡、feature-copy) |
| blocks/gp-screen | gp-hint 可省略/删除 |
| blocks/contact-map | 完全重写(box/veil/info/dl) |
| archive-news.php | 去 hero sub/scroll;arrow span 删;nw-empty 空态 |
| single-news.php | 按 news-detail.html 重写(nd-*);assets.php 换 news-detail.css |
| seed/markup.php | 全页更新(含 showScroll:false、新 phClass、caps、新结构属性) |
| seed/news-data.php | 重写:4 篇 v2 文章(p/figure 交替正文) |
| inc/seed.php | 侧载清单增删;页面 thumb 更新(after-sales/global-logistics/contact) |
@@ -0,0 +1,64 @@
# S5 验收日志 — 静态 v2 同步最终验收
- 日期:2026-08-19
- 验收人:Claude (S5 final acceptance)
- 环境:WP 7.0.4 测试站 `http://127.0.0.1:8090`(php -S + router.php);静态对照站 `http://127.0.0.1:8080`
- 代码基线:静态 v2 = a68ff43;主题 S1-S4 = e7a8963 / 71ae42d / beef315 / ea49be1
- 结果:**24/24 通过**(其中 1 项修复后通过);1 个小修复已 commit;无结构性阻塞
- 截图证据:`.playwright-mcp/s5-*.png`
## A. 逐页结构对照(curl WP 渲染 vs 静态 v2 HTML)
| # | 项 | 结果 | 证据 |
|---|---|---|---|
| 1 | `/` hero / bento / presence / network / partner / news | 通过 | hero-title 渐变(linear-gradient + background-clip:text,WP=静态计算样式一致);banner-kicker 0 处、bento 4 cell-media、banner-title 同静态(含 "Businessesk" 拼写保真);presence section-lead 新文案一致;ph-service1..4 各 1;partner 特性 4 li 无 strong;news 4 卡、news-meta 0 处 |
| 1f | `/` partner 轮播 slide | **修复后通过** | 静态 v2 slide 为空 div;WP 初测多出 3 个 `ph-label`(Warehouse/Logistics/Export Vehicles)。修复:seed/markup.php 3 个 partner-slide label 置空 + block.json label 默认 "",重播种后 `class="slide is-active ph ph-warehouse"` 等与静态一致(0 ph-label) |
| 2 | `/building-materials/` | 通过 | bp-caps(4 cap:25+ Years / 100+ Countries / 1,000+ Partners / 116 Lines,img 图标 4);5 张产品 img 卡(HDPE/PVC/…/Welding,静态即 5 卡);stat-num/stats-grid 0 处;无 scroll;bp-viewmore div 两端一致 |
| 3 | `/auto-export/` | 通过 | ae-tag/ae-marker/ae-legend 0 处;ae-models 双段 body(同一行两个 `<p class="ae-models__body">`,文案一致);bp-viewmore WP 为 div、静态为 section(仅标签名差异,类名/位置一致,记录不改) |
| 4 | `/after-sales/` | 通过 | 新 hero bg(uploads banner-8.jpg 与 image/After-sales/banner.jpg **md5 相同** 9913e4…);4 img 图标 cap(Reliable-Car-Inventory.png 等);6 个 as-svc 均 `<img Service1..6.png>`;3 个 as-loc(media+title) |
| 5 | `/global-logistics/` | 通过 | 新 hero bg(banner-9.jpg 与静态 banner.jpg md5 相同 47476a…);4 cap 双 label("1,250+ Completed Delivery" 等,文案与静态逐字一致);6 个 Service img |
| 6 | `/global-presence/` | 通过 | gp-hint 0 处(静态仅存注释行) |
| 7 | `/about-us/` | 通过 | au-zoom-mask 1 处;au-gps 4 img 卡(Global1-4.jpg)+ 宽图 about.jpg;4 data-card;feature-copy(Core Strengths + 4 条 Integration Strength / Operational Know-how / One-Stop Export / Digital Management,逐字一致) |
| 8 | `/development-history/` | 通过 | 无变化确认:dh-* 类计数两端全同(2 rail / 5 stage / 3 node / fold / route / plus) |
| 9 | `/contact/` | 通过 | hero bg = contact-us-bnner.jpg(新图,200);ct-hero__sub 两端均无;ct-map 三层(frame>box / veil / info>list):dl 列表 3 项(TEL/WhatsApp +8613526635577、Send Email、地址行),markup 逐行一致 |
| 10 | `/news/` | 通过 | 新 banner(news/news-banner.jpg,主题目录,200);sub/scroll/arrow 0 处;默认 4 行 nw-row;5 个 tab(All + 4 分类);注:静态 nw-pagination 为 JS 客户端分页(5/页)占位,WP 服务端渲染 4 行无需分页,设计性差异非缺陷 |
| 11 | `/news/<slug>/` | 通过 | nd-back/meta(仅 cat)/title/article/body/nav 全结构;article p+figure 交替(p,figure,p,figure,p);nd-meta__cat=Company News(date 注释删除一致);首篇仅 prev 单侧 = 静态 JS 行为 |
| 12 | Header mega(悬停实测) | 通过 | Business:opacity 0→1 可见,4 卡顺序 Auto Export / HESU PIPE / Global Logistics / After-sales,无 mega-desc;About:mega-left 空(0 子节点)+ 2 卡(Company Overview→about-us、Company History→development-history)。截图 s5-mega-business/about.png |
| 13 | Footer 4 img 社交 | 通过 | `<img>` × TikTok/Facebook/Instagram/WhatsApp(含 Instagram),均 200 |
## B. 交互实测(Playwright,1440×900 桌面 + 390×844 移动)
| # | 项 | 结果 | 证据 |
|---|---|---|---|
| 14 | 首页三交互 | 通过 | stats:重载后快速采样 0→3,448→6,320→8,672→10,344→11,860→15,000(数字滚动+千分位);network 拖拽:右拖 -1761.5→-1511.5→snap -1347.67、左拖反向(infinite marquee + snap);partner 自动轮播:is-active 按 exportcars→warehouse→logistics2 每 ~4s 轮换,dots 3 个 |
| 15 | GP fullpage + AU pinned | 通过 | GP:滚轮步进 scrollY 0→900→1800 精确整屏吸附;nav 高亮跟随(Middle East@900、Africa@1800),与静态**逐项一致**。AU zoom-mask(重点):沿 #au-3 所在 .au-pin(300vh)逐 150px 采样,opacity 0→0.133→0.267→0.4→0.533→0.667→**0.8**,zoom scale 1→2.2、reveal 0→1,与静态**数值级一致** |
| 16 | 时间线 | 通过(注) | v2 静态 DH 已改为折叠双行时间线(无横拖实现,js 仅 reveal);WP 与静态结构一致(2 rail/5 stage/3 node);data-reveal 4/4 滚动触发 .in。清单"横拖"系 v1 行为,v2 已移除 |
| 17 | 新闻 Tab + 空分类 | 通过 | All=4 行;Company News=4 行(静态 news.js 4 篇均为 Company News,一致);Delivery Cases / Exhibition Info / Global Network = 0 行 + `nw-empty`:"Coming soon — no X yet.";tab is-active 正确切换 |
| 18 | 详情页 prev/next | 通过 | 中间篇(Dubai Branch)prev=West Africa + next=Canton Fair 双侧;点击跳转 URL 正确、可往返;news 为独立 CPT,默认 Hello world! 文章不会串入导航 |
| 19 | CF7 提交态 | 通过 | 空提交:form.invalid + 6 必填项 .wpcf7-not-valid + aria-invalid=true(phone 非必填不含)+ 红框提示 "One or more fields have an error…";正常填写提交:无 invalid,进入 failed 态(测试环境无 SMTP,"There was an error trying to send your message…",红框样式正常显示——状态机/样式通路验证 OK,实机 SMTP 下为 sent 绿态) |
| 20 | 移动端抽屉 (<860px) | 通过 | 390px:toggle 可见,点击 nav.open + aria-expanded=true + body 滚动锁定;Business 父级点击 preventDefault 展开 collapsible mega(不跳转);backdrop 点击关闭(class 复位/aria=false/滚动解锁)。注:抽屉打开后 hamburger 被右侧抽屉覆盖(z49),关闭途径为 backdrop/链接点击——**与静态 v2 行为完全一致**(设计如此)。截图 s5-mobile-*.png |
## C. 代码项
| # | 项 | 结果 | 证据 |
|---|---|---|---|
| 21 | php -l 递归 | 通过 | 55 个 PHP 文件全部 "No syntax errors" |
| 22 | 10 路由 + 详情页控制台 | 通过 | 11 路由逐页 console error + script/css requestfailed 监听:全部 CLEAN(含滚动触发 reveal 后复检) |
| 23 | 图片抽查 | 通过 | 11 页面全部 img src 去重 102 个 URL 全部 200(8 个 301 为 http→https 归一化跳转,跟随均为 200);点名新图:After-sales/banner.jpg(banner-8 md5 同)、global-logistics/banner.jpg(banner-9 md5 同)、Service1-6.png、Global1-4.jpg、Product1-5.png、contact-us-bnner.jpg、news-banner.jpg、Instagram.png 等 |
| 24 | 编辑器抽查 | 通过 | 建材页(post 274):canvas 17 区块,hesu/page-shell→hero/business-intro/caps(4 cap)/large-image/product-cards(5)/view-more 全可见,caps 4 label + 4 img 图标;GL 页(277)cap 双 label RichText(每 cap 2 个可编辑 span:"1,250+"+"Completed Delivery"…);About 页(279)au-gps Inspector 新分组:4 Cards (images) / Wide Image / Data Cards (carousel, 4) / Feature (Core Strengths),面板可展开控件正常(Cards img alt、Placeholder class c1-c4)。截图 s5-editor-*.png |
## 修复清单
| # | 文件 | 内容 | commit |
|---|---|---|---|
| 1 | `hesu-theme/hesu/seed/markup.php` | partner 3 个 partner-slide `label` 置空(v2 slide 无 ph-label) | 本轮 |
| 2 | `hesu-theme/hesu/blocks/partner-slide/block.json` | `label` 默认 "Warehouse"→""(编辑器新插入 slide 亦不输出 ph-label) | 本轮 |
| 3 | 测试站 DB | `wp eval "hesu_seed_cleanup(); hesu_run_seed();"` 重播种,复核 ph-label=0、slide class 与静态一致 | — |
## 已知差异(记录在案,不修改)
1. auto-export `bp-viewmore`:静态 `<section>` vs WP `<div>`(view-more 块固定 div 输出;类名/位置/内容一致,无视觉差异)。
2. news 归档:静态 nw-pagination 为客户端 JS 分页(5/页);WP 服务端按分类渲染,4 篇无分页需求。
3. 移动端抽屉打开后 hamburger 被覆盖,靠 backdrop/链接关闭——静态 v2 同款行为。
4. 测试站残留默认文章 "Hello world!"(post_type=post,与 news CPT 隔离,不出现在任何导航/列表;如需洁净可删除,非同步缺陷)。
5. WP 登录后访问 `/wp-admin/`(目录 URL)被 router.php 落到 index.php 渲染首页——直访 `/wp-admin/index.php` 或具体 `post.php` 等文件路径正常;系 .test-env router 的已知边界,与主题无关。
@@ -0,0 +1,249 @@
# HESU WordPress 主题设计文档
- 日期:2026-08-15
- 状态:已获用户批准(三轮设计审查修正后)
- 来源:`d:\wp\demojt` 下 10 页静态 HTML 模板 → WordPress 经典主题
## 1. 目标与约束
把现有静态站(HESU,外贸企业站,暗色+橙色主题,英文)1:1 迁移为 WordPress 主题:
- **固定模板**:页面结构锁死,编辑者在后台只能改内容(文字/图片/数字),不能增删或调换板块。
- **公共板块可复用**:抽为原生自定义 Gutenberg 区块(`block.json` + PHP `render.php`),内容存在 `post_content` 区块标记中(非 post meta)。
- **内容后台填充**:区块编辑器可视化编辑;新闻走完整文章系统。
- **不依赖 ACF / 付费插件**:唯一插件依赖 Contact Form 7(联系表单)。
- **严格复刻设计稿**:前端输出 HTML 与静态站逐字节对齐(项目既定原则)。
- 交付位置 `d:\wp\demojt\hesu-theme\hesu\`(zip 后即为标准主题包)。
- 环境:本机无 PHP、无 WP;验证采用"便携 PHP + WordPress + SQLite 插件"一次性测试站(用户已确认)。
## 2. 关键决策记录
| 决策 | 结论 | 备选与理由 |
|---|---|---|
| 主题形态 | 经典主题(PHP 模板)+ 原生自定义区块 | FSE 全区块主题需重写全部 JS 交互(fullpage/pinned/时间线),风险高,弃 |
| 区块实现 | 原生 Gutenberg 区块,无构建工具链(编辑器脚本用原生 `wp.element`,非 JSX) | ACF Pro 区块被用户排除;无 npm 依赖,交付即用 |
| 内容存储 | 区块标记写入 `post_content` | 所见即所得、导入导出原生支持 |
| 结构锁定 | 页面包裹区块 `InnerBlocks templateLock:"all"` + 全部区块实例级 `lock:{move:true,remove:true}` | 见 §5 锁定机制 |
| 新闻 | CPT `news` + 分类法 `news_cat`,Tab 用**分类归档地址**(非自定义 query var) | 原生分页/高亮/canonical 免费获得 |
| 联系表单 | Contact Form 7,seed 时程序化创建默认表单 | 未装 CF7 时显示引导占位 |
| 资产加载 | `has_block()` 判断内容区块条件加载 CSS/JS | 与 slug/模板解耦,编辑者改 slug 不影响 |
| Header/Footer | WP 菜单(自定义渲染)+ Customizer,不做区块 | 见 §6 |
| Seed | `after_switch_theme` 一次性播种(幂等守卫 + 管理员手动重跑按钮) | |
## 3. 主题目录结构
```
wp-content/themes/hesu/
├── style.css # 主题头信息 + 站点基础样式(原 style.css)
├── functions.php # 入口,require inc/ 各模块
├── inc/
│ ├── setup.php # 主题支持、菜单注册、add_post_type_support('page','excerpt')
│ ├── assets.php # has_block() 映射表条件加载 + 编辑器样式
│ ├── blocks.php # 扫描 blocks/ 注册区块
│ ├── cpt-news.php # CPT news + 分类法 news_cat
│ ├── customizer.php # 社交链接、页脚标语、版权、Mega Menu 左侧文案
│ ├── seed.php # 激活播种 + 主题设置页(重新创建预置页面按钮)
│ └── walker-mega.php # Mega Menu 自定义渲染
├── header.php / footer.php
├── page.php # 所有以区块组成的页面共用(唯一渲染入口)
├── archive-news.php # 新闻列表(Tab + 分页)
├── single-news.php # 新闻详情
├── css/ js/ fonts/ image/ # 静态站资产原样迁移(保持相对路径结构)
├── editor-style.css # 编辑器预览样式(含页面级 class 场景)
└── blocks/ # 每区块一个目录:block.json / render.php / index.js
```
无构建步骤;PHP 目标 7.4+,WP 6.0+(用到区块实例锁)。输出一律转义(`esc_html/esc_attr/esc_url`)。
**前端 body class 注入(关键)**:各页面基底 CSS 挂在 `body.is-*` 上(如 `body.is-bp { background:#fff; ... }` 切换浅色主题),page-shell 只在 `<main>` 上输出 class 覆盖不到 body。静态站实际只有 3 种 body 钩子:`is-bp`(4 个业务页**和发展历程页**,main 同为 `bp`)、`is-news`、`is-contact`;首页/全球布局/关于我们三页 `<body>` 无 class。方案:`page.php` 经 `body_class` 过滤器解析当前 `post_content` 中 page-shell 的变体,注入 `is-bp`(含发展历程)/ `is-contact`(main 为 `ct`)/ `is-gp` / `is-au`(后两者 CSS 无对应钩子,仅为语义标记);`archive-news.php` / `single-news.php` 直接注入 `is-news`。编辑器侧则由 page-shell 的容器 class 提供同场景(§10)。
## 4. 区块清单(`hesu/` 命名空间,22 个)
> *勘误(2026-08-17):标题的 22 为早期概数,下表实列 33 个;实现最终为 37 个(补充 brand-card/cap/caps/partner-slide 以对应真实静态 DOM)。以实现与 readme 为准。*
### 通用
| 区块 | 属性/结构 |
|---|---|
| `hesu/hero` | 背景图(ID)、标题、副标题(RichText)、CTA(文字+链接)、SCROLL 指示器开关、对齐变体(左/居中/home/dh/ct);home 变体内置锁定 InnerBlocks 槽位放 stats-bar(DOM 与静态站一致:stats 在 hero section 内) |
| `hesu/stats-bar` → 子块 `hesu/stat` | 子项:图标图(可选)、数字、后缀、标签;输出 `data-count` |
| `hesu/business-intro` | 固定两栏:左(标题+RichText 段落+stats-bar) 右(图片) |
| `hesu/section-heading` | 标题(+副文+可选外框按钮) |
| `hesu/view-more` | 文字+链接 |
| `hesu/page-shell` | 页面级锁定容器:`templateLock:"all"`,class 由 seed 写死(如 `is-bp`/`page-content`),渲染为语义化 `<main>`(home 的 `page-content` 为 div,与静态站一致),无编辑 UI |
### 首页专属
| 区块 | 说明 |
|---|---|
| `hesu/bento-grid` → `hesu/banner-cell` + `hesu/media-card` | 四大业务磁贴,4 卡结构锁死 |
| `hesu/presence` | 标题+副文(含加粗)+按钮+世界地图图 |
| `hesu/hub-carousel` → `hesu/hub-card` | 区域轮播卡(图+区域名+链接) |
| `hesu/partner` | 轮播 slide(图) + 橙色覆盖层(标题+4 特性项:图标+词) |
| `hesu/news-grid` | 动态查询块:自动拉最新 4 篇新闻(图=特色图、分类+年份自动),仅"查看更多"链接可配 |
所有带锚点目标的区块(`gp-screen` 的 `gp-1..gp-5`、产品区 `#products` 等)的 `id` 为可配置属性,seed 沿用静态站原值,保证页脚锚点链接(如 `global-presence#gp-2`)不失效。
### 业务页专属
| 区块 | 说明 |
|---|---|
| `hesu/large-image` | 全宽大图框(bm-stock/gl-port 共用) |
| `hesu/product-cards` → `hesu/product-card` | 产品分类卡:主题内置 SVG 图标集选择(或自定义图)+名称 |
| `hesu/feature-cards` → `hesu/feature-card` | 统一"图标+标题+描述"卡片组,变体 class 区分 as-services / ae-region / as-region / gl-services 四处样式 |
| `hesu/showcase` / `hesu/brand-wall` / `hesu/model-list` | 汽车出口页专属 |
### 特殊页面
| 区块 | 说明 |
|---|---|
| `hesu/timeline` → `hesu/timeline-item` | 发展历程横向时间线(年份/标题/描述) |
| `hesu/gp-shell` → `hesu/gp-screen` | 全球布局 fullpage。容器含 `.gp-pagebg` 背景层;每屏变体(概览/区域,id `gp-1..gp-5` 可配);**概览屏内部含 `gp-nav-list` 链接列表(屏内容的一部分,非独立导航)**——列表项为锁定子区块(文案+`#gp-N` 锚点+`data-i`)。global-presence.js **零适配**原样迁移(它只做 active 高亮,按 `data-i` 匹配) |
| `hesu/au-stage` → `hesu/au-screen-1` / `hesu/au-screen-2` | 关于我们 pinned 视屏:内部结构(背景图/角图/遮罩/标语层/缩放图/大 logo)固定,开放文案+图片编辑。注意 au-screen-1 背景为 style 内联 background-image |
| `hesu/au-gps` | 关于我们底部区,**内部 4 层锁定结构**:① head(标题+文案) ② 4 图一行 ③ 宽幅图 ④ 左右分栏——左侧数据轮播(`au-gps__data-card`: 图+标题+描述 ×3,圆点由 about-us.js 驱动) + 右侧图文覆盖卡(图+overlay 标题+文案) |
| `hesu/contact-form` | CF7 表单下拉选择器+容器样式;CF7 未装显示安装引导占位 |
| `hesu/contact-map` | 总部地图:默认嵌入静态站的 OpenStreetMap iframe(URL 可配置) |
### 编辑器脚本与渲染约定
- 主文案内联 RichText;数字/后缀/链接等用右侧栏 TextControl(控制代码量)。
- **RichText 属性存储(关键陷阱)**:本主题区块全部为动态渲染(render.php、无 save 函数),RichText 属性**禁止用 `source:'html'`**(无保存标记可解析会丢内容),一律普通 string 属性 + `value`/`onChange`。
- **stat-num DOM 微结构(关键)**:main.js 计数器以 `el.firstChild.nodeValue` 写数字——`<span class="stat-num" data-count="20">20<span class="plus">+</span></span>` 中数字文本节点必须是 firstChild,render.php 输出不得多包元素。
- 容器类动态区块 render.php 必须输出 `$content`(InnerBlocks 内容)。
- 不做 inserter `example` 配置(页面锁死,无人手动插入区块)。
- 共享 helper(createElement 包装、公共 Inspector)提为 `blocks/editor/helpers.js` 单 handle。
## 5. 结构锁定机制(核心)
1. **页面级**:每个页面 = `page-shell`(或首页 = `hero(home)` + `page-shell` 两个顶层块,因静态站 DOM 中 hero 在 `page-content` 之外)。顶层块带实例锁 `lock:{move:true,remove:true}`。
2. **容器级**:`page-shell` 内 `InnerBlocks templateLock:"all"`——不能增删/移动子板块,只能进入编辑内容。
3. **编辑器加固**:非管理员隐藏区块插入器与解锁入口(editor settings 过滤)。
4. **残余风险(已向用户说明)**:WP 核心无法阻止管理员手动解锁破坏结构;文档注明"解锁=破坏页面"。
## 6. Header / Footer 内容来源(不用区块)
| 部位 | 配置方式 |
|---|---|
| Logo | 原生 Custom Logo |
| 主导航 | WP 菜单;Mega Menu 用 `wp_get_nav_menu_items()` 自定义渲染:卡片 = 菜单项,卡片图 = 目标页特色图片(无图回退主题占位图,如指向归档/锚点的 News 卡),卡片文案 = 页面摘要(页面启用 excerpt)或菜单项描述 |
| Mega Menu 左侧文案 × 2 组 | Customizer 文本字段 |
| Contact Us 按钮 | 指向联系页 |
| Footer 5 栏 | 1 个 WP 菜单(4 个一级项=4 栏) + 标语走 Customizer |
| 社交图标 4 个 | Customizer(平台+URL) |
| 版权 / Privacy / Terms / Sitemap | Customizer |
| 菜单高亮 | `nav_menu_css_class` 过滤器补 `active` 类(静态 CSS 认 `.active`,WP 原生输出 `.current-menu-item`) |
Mega Menu 卡片恰好 4 张是布局约束,seed 保证初始 4 项;文档说明增删项会改变布局。
## 7. 页面编排(seed 写死顺序)
| 页面 | 组成 |
|---|---|
| 首页 Home | `hero(home,含stats)` + `page-shell(page-content)` → bento-grid → presence → hub-carousel → partner → news-grid |
| 建材出口 | `page-shell(is-bp)` → hero → business-intro(+stats,4 项 data-count) → large-image → product-cards → view-more |
| 汽车出口 | `page-shell(is-bp)` → hero(居中) → business-intro(无 stats,静态站此页没有) → showcase → brand-wall → model-list → feature-cards(region) → view-more |
| 售后改装 | `page-shell(is-bp)` → hero → business-intro(无 stats,静态站此页没有) → feature-cards(services) → feature-cards(region) |
| 国际物流 | `page-shell(is-bp)` → hero → business-intro(+stats,4 项 data-count) → large-image(port) → feature-cards(services) |
| 全球布局 | `page-shell(main 为 gp-main)` → gp-shell(1 概览屏+4 区域屏) |
| 关于我们 | `page-shell(main 为 au-main)` → au-stage(2 pinned 屏) → au-gps |
| 发展历程 | `page-shell(is-bp,静态站此页 body 即 is-bp)` → hero(dh) → timeline |
| 联系我们 | `page-shell(is-contact,main 为 ct)` → hero(ct) → contact-form → contact-map |
| 新闻列表/详情 | PHP 模板渲染,不用区块 |
## 8. 新闻系统
- CPT `news`:支持标题/编辑器/特色图片/摘要;归档 `/news/`(**不建 News 页面,避免 URL 冲突**;导航 News 链接指向 `get_post_type_archive_link('news')`)。
- 分类法 `news_cat`(非层级):seed 建 4 词——Company News / Delivery Cases / Exhibition Info / Global Network(第 4 个供首页卡片 meta 与页脚 News 栏使用;Tab 自动列出全部词,初始即 4 个,较静态站多出 Global Network 一项为有意取舍)。Tab = 全部分类词的**归档地址链接**;分页原生。
- `archive-news.php`:新闻 Hero → Tab → **左文右图行列表(`.nw-row`,与静态站一致,非卡片网格)** → 分页(每页 5 条,与静态 news.js 的 PER_PAGE 一致)。行内 `[data-reveal]`/`.nw-row` 默认 `opacity:0`,reveal 逻辑并入 main.js(见 §10)。**Hero 背景图来源**:Customizer 新增"新闻 Hero 图"设置(theme mod,默认主题资产 `image/carexport/export-bg.jpg`),归档与详情共用。
- `single-news.php`(按 `demo-html/新闻详情1.jpg` 已读图确认):新闻 Hero+面包屑 → 分类标签+日期+标题 → 特色图 → 正文(核心区块编辑器,零自定义区块)→ 上一篇/下一篇 → 返回列表。
- 首页 `news-grid` 自动拉最新 4 篇。
- 静态 `news.js`(207 行,客户端渲染 15 篇硬编码数据 + Tab 过滤 + 分页)服务端化后大部分作废,不迁移;仅 reveal 行为由 main.js 承接。
## 9. Seed 流程
```
守卫(option hesu_seeded) → 图片入库(image/ 内容图经临时文件 + media_handle_sideload)
→ 建 9 个页面(锁定区块标记,引用图片附件 ID;9 页全部设置特色图片,供 Mega Menu 卡片使用)
→ 设 show_on_front/page_on_front → 建 4 个分类词
→ 建 15 篇示例新闻(迁移 news.js 内置的全部 15 篇数据,保证分页可见 3 页)
→ 建主菜单+页脚菜单并绑定卡位 → CF7 已激活时创建默认表单(字段=静态表单)
→ 写入 hesu_seeded
```
- CF7 晚于主题激活的场景:另挂 `activated_plugin` 钩子,检测到 CF7 启用且默认表单缺失时补建;主题设置页"重新创建预置页面"按钮作为最终兜底。
- Mega Menu 卡片无特色图时的占位:复刻静态站 `ph-*` 占位样式(底色 + "Photo" 标签的 div),不使用裸 `<img>`。
- 主题设置页提供管理员"重新创建预置页面"按钮(幂等重跑)。
- 若在已有内容的站点激活:seed 仍会创建预置页面并设置首页,文档说明预期用于全新安装。
## 10. 资产加载
| 层级 | 策略 |
|---|---|
| 全站 | `style.css` + `fonts.css` + `main.js`(footer 加载,与静态站 body 尾一致;扩展:新增对 `[data-reveal]` 与 `.nw-row` 的 IntersectionObserver reveal——业务页 `[data-reveal]` 与新闻行默认 `opacity:0`,无 JS 则内容不可见) |
| 按区块 CSS | `business-intro→business.css`、`hero→business.css`、`product-cards→building-materials.css`、`brand-wall/showcase/model-list→auto-export.css`、`feature-cards→after-sales.css+auto-export.css+global-logistics.css`(交叉装载无害:各文件选择器以自身变体 class 作用域)、`timeline→development-history.css+js`、`gp-shell→global-presence.css+js`、`au-stage→about-us.css+js`、`news-grid→news.css`、`contact-form→contact.css+js` |
| 按区块 JS | 出现任一业务页区块(hero/business-intro/product-cards/brand-wall/showcase/model-list/feature-cards/large-image)→ 加载 4 个业务页 JS(building-materials.js / auto-export.js / after-sales.js / global-logistics.js,共约 6KB)。理由:各文件自带选择器守卫(无目标即 return),其中 data-count 计数(建材/物流两页的 bp-stats)与部分 reveal 由它们驱动,main.js 的 `.stat-num` 计数器不覆盖 `bp-stat__num` 结构 |
| news.js | 不迁移(客户端渲染/过滤/分页已服务端化),reveal 逻辑由 main.js 扩展承接 |
| 模板直载 | archive/single-news 直接 enqueue `news.css` |
| 编辑器 | 编辑器侧加载 `fonts.css` + `style.css` + **全部页面 CSS**(约 130KB,仅后台开销,无法按页条件);page-shell 输出页面级 class(如 `is-bp`)使作用域 CSS 在编辑器 iframe 生效 |
| 行为边界 | 交互(计数/轮播/fullpage)仅在.front 前端运行,编辑器内为静态预览——WP 服务端渲染区块的固有限制 |
### CF7 与静态表单的样式映射
静态表单是自绘结构(`ct-form/ct-field/ct-label/ct-input/ct-selectwrap` + `#ctForm` 客户端校验 + `#ctNote` 成功态);CF7 输出 `form.wpcf7-form` + `span.wpcf7-form-control-wrap` 包裹。约定:
- seed 建表单时为每个字段 shortcode 挂静态 class(如 `[text* your-name class:ct-input]`)。
- `contact.css` 增补桥接规则:以 CF7 实际 DOM(`.wpcf7-form` / `.wpcf7-form-control-wrap` / `.wpcf7-not-valid` / 响应元素 `.wpcf7-response-output`)为目标复刻 ct-* 视觉。CF7 6.x 起状态类(`invalid` / `sent` / `failed` / `aborted` / `unaccepted` / `spam`)挂在 form 元素上(如 `form.wpcf7-form.sent`),响应元素仅保留 `.wpcf7-response-output`,桥接规则按 `form.{invalid,sent,...} .wpcf7-response-output` 匹配;校验与提交反馈全部交给 CF7 原生状态 class,`contact.js` 的表单分支不迁移。
JS 依赖事实:静态站全部 JS 为原生手写(无 GSAP/fullpage 库),**全部原样迁移、零适配**(global-presence.js 的 active 高亮依赖 `gp-nav-list a[data-i]` 标记,由概览屏锁定子区块保证)。main.js 的 network 无限轮播克隆逻辑要求 `.network-card` 为 `.network-track` 直接子元素,hub-carousel 渲染须保持该层级。
## 11. 验证方案(用户已确认搭一次性环境)
1. 下载便携版 PHP(≥7.4,含 pdo_sqlite)解压至项目临时目录(不入库)。
2. WordPress + SQLite drop-in(如 wp-sqlite-db)搭建一次性站点,安装主题与 CF7。
3. 实跑验收清单(§12)。
4. 测完删除临时站点;若用户后续装正式环境可复用清单。
## 12. 验收清单
1. 干净 WP 激活 → 页面/菜单/示例新闻自动生成,首页即成型。
2. 10 个路由与静态站逐页对照:板块顺序、数字滚动、Mega Menu、轮播拖拽、fullpage 翻屏、pinned 视屏、时间线横滑全部工作(含 `.reveal` 滚动动画)。
3. 以"编辑"角色打开任意页面:结构不可增删/移动,文字图片可改;尝试删除被锁区块被拒绝。
4. 新闻:各类别发文 → 归档 Tab 过滤 + 分页 + 首页卡片自动更新。
5. 联系表单:样式按 §10 桥接规则还原静态视觉;测试环境将 CF7 邮件设为 dummy(便携 PHP 无邮件服务),验收标准为**提交成功态/校验态正确展示**,不要求真实发信。
6. 导航高亮、Customizer 各字段生效。
7. `php -l` 全过、输出转义审查、控制台无报错、本地字体正常加载。
8. CF7 缺失时联系页显示引导提示而非报错。
## 13. 设计审查修正记录(三轮)
| # | 级别 | 问题 | 修正 |
|---|---|---|---|
| 1 | 漏洞 | `templateLock` 锁不到顶层区块 | 全部区块实例级 `lock:{move,remove}` |
| 2 | 漏洞 | News 页面与 CPT 归档 `/news/` URL 冲突 | 不建 News 页面 |
| 3 | 漏洞 | 按 slug 条件加载资产脆弱 | 改 `has_block()` |
| 4 | 漏洞 | 页面级 CSS 依赖 body class,编辑器不生效 | page-shell 输出等价 class |
| 5 | 漏洞 | 首页 hero 与 page-content 的 DOM 层级套不进单容器 | 两个顶层锁死块;hero(home) 内嵌 stats 槽位 |
| 6 | 缺口 | Mega Menu News 卡片无特色图/摘要 | 占位图+菜单描述兜底 |
| 7 | 缺口 | 菜单高亮 `.active` vs `.current-menu-item` | nav_menu_css_class 过滤器 |
| 8 | 缺口 | wp_nav_menu 输出不了 mega 结构 | wp_get_nav_menu_items 自定义渲染 |
| 9 | 缺口 | 新闻 Tab 分页保参易错 | 改用分类归档地址,原生分页 |
| 10 | 缺口 | seed 无重跑出口 | 主题设置页按钮 |
| 11 | 缺口 | News 卡用 query_var 方案繁琐 | 改原生归档 URL |
| 12 | 矛盾 | 验收清单需实机 vs 本机无 PHP | 便携 PHP + SQLite 一次性测试站 |
| 13 | 漏洞 | 页面基底 CSS 挂在 `body.is-*`(如 body.is-bp 浅色主题),page-shell 的 main class 覆盖不到 body | `body_class` 过滤器按 page-shell 变体注入前端 body class(spec 审查发现) |
| 14 | 漏洞 | 4 个业务页 JS(data-count 计数/reveal)未纳入资产映射;`[data-reveal]` 默认 opacity:0,JS 缺失内容不可见 | has_block 命中业务区块即加载 4 个业务 JS;main.js 扩展承接 news reveal(spec 审查发现) |
| 15 | 错误 | 汽车出口/售后改装两页静态站没有 stats,编排误写 business-intro(+stats) | 改为无 stats(spec 审查发现) |
| 16 | 错误 | 新闻列表实为左文右图行、每页 5 条、news.js 内置 15 篇,spec 误写 3 列卡片/9 条/4 篇 | 按静态站修正;Tab 有意保留 4 词(spec 审查发现) |
| 17 | 缺口 | CF7 DOM 与静态表单差异未定义、CF7 晚激活无法补建表单、seed 未设页面特色图、锚点 id 保留未覆盖、测试环境无法真实发信 | §10 桥接规则、activated_plugin 补建、seed 设特色图+ph-* 占位、区块 id 可配置、验收措辞调整(spec 审查发现) |
| 18 | 错误 | body 变体名臆造:静态 CSS 只有 is-bp/is-news/is-contact 三种钩子;发展历程页实为 is-bp+main.bp,联系页为 is-contact+main.ct,不存在 is-dh/is-ct | §3/§7 按静态站事实改注 is-bp / is-contact(二审发现) |
| 19 | 错误 | gp"圆点导航由 JS 从 data-nav-title 生成"系错误假设:gp-nav-list 实为概览屏内部链接列表(屏内容),js 仅做 active 高亮,无需任何适配;导航文案与屏标题在静态站本就不一致,不应自动生成 | §4 改为概览屏锁定子区块,global-presence.js 零适配(四轮源码核查发现) |
| 20 | 不足 | au-gps 实为 4 层结构(head/4 图行/宽图/数据轮播+图文卡分栏),spec 仅一句带过 | §4 细化内部结构(四轮源码核查发现) |
| 21 | 缺口 | 动态区块 RichText 用 source:html 会丢内容;stat-num 的 firstChild 文本节点微结构未约定;编辑器 CSS 加载范围未明确;新闻 Hero 图来源未定义;contact-map 实为 OSM iframe | 编辑器约定补两条关键约定、§10 明确全量页面 CSS、§8 Customizer 新闻 Hero 图、contact-map 改 iframe(四轮源码核查发现) |
**内容备注(非缺陷)**:静态站 gp-3(Africa)屏的 lead 文案讲的是 Minsk/Belarus(静态站自身的复制粘贴错误)。迁移按原文保留,seed 后可在后台修正。
## 14. 范围外(明确不做)
- 多语言(结构不阻碍后续接多语言插件,内容在 post_content 中天然可翻译)。
- Classic Editor 插件兼容(文档注明必须使用区块编辑器)。
- 性能优化超出 §10 所列(CDN、缓存策略交由宿主环境)。
## 15. v2 同步记录(2026-08-17)
静态站改版 v2(commit a68ff43)后的主题同步:mega 精简(Business 只留 title/About 空 left 2 卡)、footer 社交 img 化(LinkedIn→Instagram)、hero scroll 全删、bp-stats→bp-caps(含物流双 label)、SVG 图标全面 img 化、as-loc 简化、ae-models 去 tags、ae-map 去 markers、au-gps 重构(img 化+4 数据卡+feature-copy)、au-zoom-mask 新增、ct-map 三层结构、新闻系统重做(4 篇/nd-* 详情页/nw-empty 空态)、news-banner 新图。详见 `plans/2026-08-17-static-v2-sync-report.md` 与验收日志。已接受偏差:footer 按正确闭合(不复刻旧页嵌套错误);"Four Core Businessesk"与"99% Awards Winner"原样复刻。
@@ -0,0 +1,116 @@
# 首页 GLOBAL PRESENCE 地图动画替换 — 设计文档
日期:2026-08-21
状态:已与用户确认(1:1 还原 demo)
## 轮播模式迭代:累积滑窗(2026-08-21 四次补充)
应用户要求,首页地图与 auto-export 地图的轮播从"独占切换"(同一时刻一个)改为**累积滑窗**:
- 每步激活下一个据点,**已激活的保持显示**
- 窗口满后(n-1=4 个),每次新激活时**最旧的一个淡出**(0.45s opacity 过渡)
- 稳态 = 同时显示 4 个:最新的播完整动画序列,最旧的渐隐离场
- 生命周期不变:可见才播 / 完全离开归零 / 每次进入从第一个完整累积
实现:main.js 两个地图块的 `step()`(tick % n 激活,tick ≥ n-1 时 (tick-(n-1)) % n 淡出);CSS 给 `.location`/`.marker`/`.line` 加 opacity 过渡。版本 bump `?v=20260821-6`。
验证:两页 t0→t4 逐步累积至 4 个、t4 时首个淡出、t5 滑窗循环、离场归零、回来重播,全过。
## 丝滑包升级(2026-08-22)
应用户要求做动效升级,四项全部落地(两张地图统一):
1. **弧线引线**:首页 5 条折线 `d` 改为三次贝塞尔曲线(水平出发→自然弯到点位),绘制缓动 `cubic-bezier(.3,.7,.2,1)`
2. **退场反擦除**:入场=animation(.active),退场=反向 animation(.out 类,main.js 的 exit() 添加、650ms 后移除):线倒收回(lineOut dashoffset 0→1)、圆点缩小(dotOut)、标签下滑淡出(labelOut)、光环衰减(haloOut)。⚠️ 不能用 transition 实现退场 — CSS 规范规定 animation 移除时的值跳变不触发 transition
3. **重叠时序**:label .28s@.02s / line .55s@.12s / dot .32s@.5s — 各元素交叉进场
4. **呼吸 + 双涟漪**:dot 落定后 CSS `r` 属性呼吸(breatheR 7.5→8.3px / ae-breatheS 5.5→6.3 / ae-breatheL 6→6.9,`r` 与 transform 不冲突可同元素并存);每个点加第二圈光环/涟漪(halo2 / pulse.p2,延迟错开)
结构变化:子元素自管隐藏基态(group 不再切 opacity);HTML 每点 +1 光晕圆。版本 `?v=20260821-9`。
**验证注意**:Playwright 窗口不可见时 Chrome 冻结动画时钟(currentTime 恒 0)— 动画类验证必须先 `Page.bringToFront`。带前台后实测退场淡出中间态(175ms 时 opacity 0.71)精确符合曲线。
## 目标
把 [index.html](../../../index.html) 首页 GLOBAL PRESENCE 板块的静态占位地图(`image/home/global-map.png`)替换为 [global-map-animation-demo.html](../../../global-map-animation-demo.html) 中的据点轮播动画效果,1:1 还原。
## 效果(以 demo 为准)
- 背景图:`image/home/Frame 119.png`(1219 × 661)
- SVG 覆盖层与背景图共享 `viewBox="0 0 1219 661"` 坐标系,所有点/线/文字用原图坐标
- 5 个据点:DUBAI、ABU DHABI、ETHIOPIA、GHANA、RUSSIA
- 每个据点 = 白色标签 + 橙色(`--orange-700` = `#ff6a00`)折线引线 + 圆点 + 脉冲光环
- **同一时刻只激活一个据点**,每 3 秒切换下一个(循环)
- 动画序列:标签滑入(.35s)→ 引线绘制(.85s 延迟 .18s)→ 圆点弹出(.4s 延迟 1s)→ 光环脉冲(1.4s 无限循环)
- 含大屏首帧防闪烁修复:非 active 线 `stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0`
## 改动文件
| 文件 | 改动 |
|---|---|
| `index.html` | 替换 `.map-wrap` 内部为 `img.map-bg` + `svg.map-overlay`(保留外层 `reveal`、`aria-hidden`、`loading="lazy"`) |
| `css/style.css` | GLOBAL PRESENCE 段合入 demo 样式:`.map-wrap` 改 `aspect-ratio: 1219/661`,新增 `.map-bg`/`.map-overlay`/`.location` 系列 + 4 个 keyframes(颜色用 `var(--orange-700)`);删除废弃 `.map-img`;删除 ≤560px 媒体查询里 `.map-wrap { height: 260px }`(与 aspect-ratio 冲突,比例自适应后手机端高度 ≈200px 与原设计一致) |
| `css/mobile.css` | `.map-wrap` 规则去掉写死 `height: 200px`,保留 margin |
| `js/main.js` | 新增独立 IIFE:查 `.map-overlay`,`.location` 每 3s 轮播 `active`(选择器限定,不影响其他页面) |
## 刻意不做的(YAGNI)
- 不做全部常显模式(用户已确认单点轮播)
- 不移植 demo 里 vestigial 的 `--path-length` JS:CSS 从未读取该变量,`pathLength="1"` 属性已完成路径长度归一化
- 不删旧图 `global-map.png`(可能被 WP 主题或其他地方引用)
- 不加 demo 工具栏按钮(那是调试用的)
## 降级行为
- `prefers-reduced-motion`:站点全局 `animation: none`,当前据点显示标签+圆点(静止),引线保持未绘制 — 可接受
- 无 JS:DUBAI 常显(HTML 里初始 `active`)— 可接受
## 防闪加固(2026-08-21 补充)
用户报告症状:大屏上到达地图时先闪一帧完整线路 → 重置 → 才正常播放。
**根因**(Playwright 取证确认):非 CSS 首帧竞态(demo 的 base-state 修复已移植且有效),而是**状态机时序**——轮播从页面加载就开始空转,加载 6s 后(地图仍在视口外 2380px 处)ABU DHABI 的引线已处于 `fill: both` 保持的完结状态(opacity 1 / dashoffset 0)。用户滚到时第一眼撞见这条"陈旧完结帧"(= 闪一下),3s 后下个 tick 归零重置(= 消失),下一个据点才从头播放(= 正常出现)。屏幕越大板块离首屏越近,越容易触发。
**修复**([js/main.js](../../../js/main.js)):
- init 时先 `show(-1)` 清掉 HTML 里 DUBAI 的 `active`(main.js 是 body 末尾同步脚本,先于首帧执行;无 JS 时保留 DUBAI 静态回退)
- 首次激活用 IntersectionObserver 门控(观察 `.map-wrap`,threshold 0.12,与站点 reveal/count-up 同模式),触发后**双 rAF** 再 `show(0)`——先让不可见 base 状态绘制一帧,动画永远从已沉淀的样式树起步
- 进入视口才开始 3s 轮播;无 IO 的老浏览器直接 begin()
## 防闪加固第二层:re-entry 重启 + 缓存击穿(2026-08-21 二次补充)
用户反馈"还是会出现"。系统排查后确认两条残余路径:
1. **re-entry 陈旧态**:第一层修复只管"首次到达"。轮播一旦启动永不停 → 用户滚走再滚回时,当前据点又处于完结状态 → 闪 → 重置 → 下一个正常播放(症状与首次完全相同)。
2. **旧版 main.js 缓存**:python http.server 等无缓存头环境下,普通 F5 会命中启发式缓存执行旧 JS(Playwright 验证时实测踩中)。
**修复**:
- 轮播改为**可见才播放**的生命周期:IO 持续观察(threshold 0.12),`isIntersecting` → `start()`(每次进入都从 DUBAI 从头播,双 rAF 起步);完全离开 → `stop()`(清定时器 + 归零到不可见 base 态)。首次到达、回访、F5 原位刷新(Chrome 恢复滚动位置)三种场景统一为"从 DUBAI 完整播放"
- `start()` 内 rAF 等待期若已离开视口则放弃启动(守卫 `if (!timer) return`)
- index.html 脚本标签加版本号 `main.js?v=20260821` 击穿缓存(今后 main.js 有实质改动时手动 bump)
**验证**(Playwright):
- 静置顶部不滚动:零激活、所有线 opacity 0
- 滚入 → DUBAI dashoffset 0.93(从 0 画起);3.3s 后 ABU DHABI
- 滚到页脚离开 7s:`active: null`(轮播已停、已归零)
- 滚回:DUBAI dashoffset 重现从 0 画起;3.3s 后 ABU DHABI —— re-entry 不再撞见完结帧
- 原位 reload(Chrome 恢复 scrollY=2309 地图在视口):t=135ms DUBAI 已干净启动,无闪帧;t=10.2s 轮到 GHANA,节奏精准
- 控制台 0 错误 0 警告
**A/B 验证**:
- 修复前:加载 6s 未滚入 → ABU DHABI 激活、线完整(陈旧完结帧)
- 修复后:加载 8s 未滚入 → 零激活、所有线 opacity 0
- 滚入 0.6s → DUBAI 从 dashoffset 0.83 开始绘制;3.1s → ABU DHABI;6.3s → ETHIOPIA,循环正常
- 控制台 0 错误 0 警告
## 相关:bento 业务格渐显重播(2026-08-21 三次补充)
用户要求 FOUR CORE BUSINESSES(STRONG RESOURCES, RELIABLE DELIVERY)板块的图片逐个渐显在**每次进入视口时重播**(原 reveal 一次即 unobserve)。
- index.html:5 个 `.cell.reveal` 追加 `reveal-repeat` 标记类(仅此板块)
- main.js reveal 观察器:带 `reveal-repeat` 的元素不 unobserve,按 `isIntersecting` 切换 `.in` — ≥12% 可见整组阶梯重播,完全离开归零;其余元素保持一次性 reveal
- 脚本版本 bump 至 `?v=20260821-2`
验证:滚入 5/5 in → 滚走 0/5 → 滚回 5/5 重播;标题等一次性 reveal 不受影响;控制台 0 错误。
## 验证
- Playwright 打开 index.html:控制台无报错、3s 轮播切换、桌面/移动端尺寸比例正常