Files
hesu-JT/docs/superpowers/plans/2026-08-15-hesu-wordpress-theme.md
T
2026-08-27 11:54:26 +08:00

39 KiB
Raw Blame History

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: 提交静态站基线
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
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
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: 启动开发服务器并验证
cd d:/wp/demojt/.test-env/wp && ../php/php.exe -S 127.0.0.1:8090

(后台运行;后续所有 curl 断言打 http://127.0.0.1:8090)

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)
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: 拷贝静态资产

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 全文:

/*
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 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
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
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
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 挂载主题并激活

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: 断言
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
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
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: 断言
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
// 独立编辑器脚本不做任何构建: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(全区块共用)
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:

{
  "$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
$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(这是所有区块编辑器脚本的模板范例,后续区块同构):

(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 增加:

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: 断言
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 对象,属性顺序不可依赖):

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 类 —

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)。示例(建材页骨架,其余同构):

"<!-- 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:

$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: 断言(关键节点)
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