搞过网站内容架构的兄弟都懂,那种一开始只设计了两三个分类的知识库,后面想塞进更多类型的内容时,有多想砸键盘。CSS-Tricks的Almanac板块就是这么个典型例子,原先只放选择器和属性,现在想把伪类、函数、@规则统统加进去,结果发现底层结构直接焊死了。这篇就来盘一盘,怎么把一个基于WordPress页面(Page)搭建的旧参考文档,从只支持两个分组硬改成能无限扩展的多分组架构,全程实操不带虚的。
问题拆解
原来的Almanac用WordPress的页面层级来组织内容,路径长这样:Almanac首页 → 属性分类页 → 字母A页 → 具体属性条目。这套逻辑在只有两个分组(选择器和属性)的时候跑得挺顺,但一碰到要新增分组就抓瞎——因为旧模板里写死了只查询两个分类的页面ID。更酸爽的是,每个字母页(比如A、B、C)都得手动创建空页面作为父级,才能把属性条目挂上去。搞了78个新字母页,手都快抽筋了。
旧代码痛点
翻出原来的核心查询函数,里面藏着个大坑。那个函数叫letterOutput(),同时接收两个父页面ID(一个给选择器,一个给属性),然后在同一个模板里循环输出两坨内容。代码大概长这样(已做变量名混淆和结构拆分):
function groupDisplay($letter, $groupA_id, $groupB_id) {
$query_groupA = new WP_Query([
'post_type' => 'page',
'post_parent' => $groupA_id,
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC'
]);
$html = '<div class="group-wrap">';
$html .= '<div class="group-letter"><a id="letter-' . $letter . '">' . $letter . '</a></div>';
$html .= '<div class="group-list">';
while ($query_groupA->have_posts()) : $query_groupA->the_post();
$html .= '<details>';
$html .= '<summary><h2><code>' . get_the_title() . '</code></h2></summary>';
$html .= get_the_excerpt();
$html .= '<pre><code class="language-css">' . get_post_meta(get_the_id(), 'code_example', true) . '</code></pre>';
$html .= '<a class="btn" href="' . get_the_permalink() . '">继续读</a>';
$html .= '</details>';
endwhile;
$html .= '</div></div>';
return $html;
}注意看,这个函数只处理了$query_groupA,实际代码里还要再复制一遍给groupB。每次调用要传26个字母,每个字母传俩ID,模板里直接硬编码。想加第三个分组?除非把函数改成接收三个参数,但模板里所有循环都得重写,而且while里面塞不进灵活的if分支,一改就炸。
新方案出炉
直接放弃修修补补,把单模板拆成两套:一个专门给Almanac首页用,另一个给各个分组(属性、选择器、伪类等)的字母索引页用。核心改动是把groupDisplay()函数里的双ID参数砍成单ID,每个分组独立查询。
操作步骤拆解
第一步:建立分组父页面
在WordPress后台新建页面,比如叫“伪类选择器”,记下它的页面ID(假设是9876)。然后手动创建26个字母页(A、B、C……Z),每个字母页的“父级页面”属性都选刚才建的“伪类选择器”。这一步虽然机械但必须做,因为页面层级要靠父子关系来生成URL路径,像/almanac/pseudo-selectors/a/这种结构才能自动生效。
第二步:改写单分组查询函数
写个新函数singleGroupQuery($letter, $parent_page_id),只查一个父级下的所有子页面:
function singleGroupQuery($letter, $parentId) {
$subpages = new WP_Query([
'post_type' => 'page',
'post_parent' => $parentId,
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
'post_status' => 'publish'
]);
if (!$subpages->have_posts()) return '';
$output = '<div class="almanac-letter-block">';
$output .= '<div class="letter-badge" id="letter-' . $letter . '">' . $letter . '</div>';
$output .= '<div class="items-list">';
while ($subpages->have_posts()) : $subpages->the_post();
$item_title = get_the_title();
$item_excerpt = get_the_excerpt();
$demo_code = get_post_meta(get_the_id(), 'demo_snippet', true);
$output .= '<div class="ref-item">';
$output .= '<h3><code>' . esc_html($item_title) . '</code></h3>';
$output .= '<p>' . esc_html($item_excerpt) . '</p>';
if ($demo_code) {
$output .= '<pre class="code-sample"><code class="language-css">' . esc_html($demo_code) . '</code></pre>';
}
$output .= '<a href="' . get_permalink() . '" class="more-link">瞅瞅完整版 →</a>';
$output .= '</div>';
endwhile;
$output .= '</div></div>';
wp_reset_postdata();
return $output;
}注意这里把原来<details>标签换成了普通div,因为字母索引页需要一次性展示所有条目,折叠效果反而碍事。另外get_post_meta的键名从almanac_example_code改成了demo_snippet,避免跟旧模板冲突。
第三步:创建分组专属模板
新建一个WordPress模板文件,比如page-almanac-group.php。在文件头部写上模板名称,然后在页面编辑里把“伪类选择器”这个父页面指定用该模板。模板内容很简单:获取当前页面的ID,然后循环调用singleGroupQuery输出A到Z所有字母块。
<?php
/* Template Name: 分组字母索引 */
get_header();
$current_page_id = get_the_ID();
$alphabet = range('A', 'Z');
?>
<div class="group-index-wrap">
<h1><?php the_title(); ?></h1>
<?php
foreach ($alphabet as $letter) {
echo singleGroupQuery($letter, $current_page_id);
}
?>
</div>
<?php get_footer(); ?>因为每个字母页(比如/almanac/pseudo-selectors/a/)的父页面就是“伪类选择器”,所以直接拿当前页面ID去查子页面——等等,这里有个巨坑:字母页本身是空页面,它下面没有子页面。真正存内容的是具体条目页面(比如:hover伪类),它们是以字母页为父级创建的。所以上面的模板实际上是给字母页用的,而不是给“伪类选择器”父页面用的。
第四步:修正逻辑——给字母页套模板
正确做法:每个字母页(A、B、C……)单独套用同一个模板。在模板里获取当前字母页的ID,然后查询该ID下的所有子页面(即具体条目)。修改模板代码:
$current_letter_page_id = get_the_ID();
$entries = new WP_Query([
'post_parent' => $current_letter_page_id,
'post_type' => 'page',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC'
]);然后在循环里输出每个条目的标题、摘要和示例代码。这样每个字母页就自动变成该字母下所有条目的列表页了。
第五步:处理首页导航
旧版首页同时展示了选择器和属性两个大板块,现在要改成展示多个分组(选择器、属性、伪类、函数、@规则)。直接写个静态导航栏,用硬编码链接指向各个分组的父页面。虽然不够优雅(没法在后台菜单里调),但胜在简单粗暴。导航栏代码示例:
<div class="section-nav">
<a href="/almanac/selectors/">选择器</a>
<a href="/almanac/properties/">属性</a>
<a href="/almanac/pseudo-selectors/">伪类</a>
<a href="/almanac/functions/">函数</a>
<a href="/almanac/at-rules/">@规则</a>
</div>每个分组父页面下再挂26个字母页,字母页里才是具体条目。三层结构:分组父页 → 字母页 → 条目页。面包屑导航自动继承父级关系,用户点起来不会迷路。
细节避坑指南
手动创建78个字母页(3个新分组×26字母)的时候,记得把每个字母页的“别名”(slug)统一设置成小写字母,不然URL会出现/A/和/a/混用的情况。另外每个字母页的页面标题建议写成“属性 A – Z”这种格式,方便搜索引擎识别。批量创建可以用WP All Import插件导入CSV,字段映射里把“父级页面”列填上对应分组的ID,比手动点26次快一倍。
那个singleGroupQuery函数里用了wp_reset_postdata(),千万不能省。因为WP_Query会污染全局$post对象,不重置的话页面正文内容会显示错乱。还有示例代码的存储,原来用的是自定义字段almanac_example_code,迁移时记得批量把旧数据复制到新字段名demo_snippet,或者直接修改模板里的键名保持一致。
首页导航如果后期分组越来越多(比如加到10个),硬编码会变成维护噩梦。建议换成wp_nav_menu,先在后台创建一个“Almanac分组菜单”,把每个分组父页面作为菜单项加进去,然后在模板里调用这个菜单。不过这次改版时间紧,先硬顶着,下次迭代再优化。
单个条目页侧边栏也做了升级,原先只显示当前分组(比如属性)下的其他条目,现在改成显示所有分组的快速跳转。实现方式是在侧边栏模板里分别查询每个分组父页面下的所有子页面(即字母页),再遍历字母页下的条目,生成一个多层嵌套的目录树。查询量会变大,建议加个WP_Transient缓存,设置12小时过期,避免每次加载都跑几十次数据库查询。
