# 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 ".*" ``` 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
``` - [ ] **Step 4: functions.php + inc/setup.php** `functions.php`: ```php 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 ` 保留(main.js 用)。 - `wp_head()` / `wp_footer()` / `body_class()` 齐全;`
` 由区块渲染,模板不放。 - [ ] **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 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

array(), 'br' => array(), 'a' => array( 'href' => array() ) ) ); ?>

``` `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("");' --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 变体含 `` 槽位于 `.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**:`20+`)与 `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:`InnerBlocks 内容`。编辑器: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 "\n" . "\n" . "\n" . "\n ×4\n\n" . "\n" . "\n" . " ... \n" . "\n" . "\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 = '
' /* 按 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"]' /* ... */ '
' . '[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 '