网站开发

自定义文章类型和自定义字段:让 WordPress 不只是个博客

WordPress 默认只有两种内容:文章和页面。

但一个像样的企业站,内容类型远不止这两种:

  • 案例展示:有项目图、客户名、完成时间
  • FAQ:问题和答案两个字段
  • 团队介绍:姓名、职位、照片、简介
  • 产品手册下载:文件名、版本、下载链接
  • 客户评价:内容、客户名、公司、评分

如果把这些全塞进「文章」,你会遇到一堆麻烦:它们混在文章列表里、没法单独做列表页、没有专门的字段。

自定义文章类型(Custom Post Type,简称 CPT)就是为了解决这个问题。

一、什么时候该用 CPT

判断标准很简单,问自己三个问题:

  1. 这类内容会有多条(而不是单独一页)吗?
  2. 它需要自己的列表页吗?
  3. 它的字段结构和普通文章不一样吗?

三个都是「是」,就该用 CPT。

反例:「关于我们」只有一页,用页面就够了;「公司简介」「联系方式」这种一次性内容,用页面。

二、怎么建:插件方式(推荐)

用 Advanced Custom Fields(ACF) + Custom Post Type UI 这两个免费插件组合,全程后台操作,不用写代码。

步骤

  1. 装 Custom Post Type UI → 添加文章类型 → 填名字(比如 case)、复数标签(案例)、单数标签
  2. 装 ACF → 字段组 → 新建 → 位置规则设为「文章类型 = 案例」
  3. 给这个字段组加字段:项目图(图片类型)、客户名(文本)、完成时间(日期)
  4. 后台就能看到「案例」菜单,像发文章一样添加

关键设置:固定链接

建 CPT 时有个 has_archive 选项,建议设为 true,这样会有一个列表页(比如 /case/)。

另外 rewrite 设置 URL 前缀。建议用短一点、语义清楚的,比如 /cases/。

改完 CPT 设置后一定要去「设置 → 固定链接」点一下保存(不用改任何东西)——这会刷新 WordPress 的路由规则。不改的话新页面会 404。这是新手最常卡住的地方。

三、前台怎么显示

方法一:利用模板层级(最规范)

WordPress 有一套模板优先级规则。建这两个文件:

  • archive-case.php —— 案例列表页模板
  • single-case.php —— 单个案例页模板

命名规则:archive-{文章类型名}.php 和 single-{文章类型名}.php。

如果不建,WordPress 会用默认的 archive.php / single.php,也能显示,但布局和字段展示不会是你想要的。

方法二:用短代码插入任意位置(灵活)

写一个短代码,在文章/页面里用 [cases limit="6"] 调用:

add_shortcode( 'cases', function( $atts ) {
    $atts = shortcode_atts( array( 'limit' => 6 ), $atts );

    $q = new WP_Query( array(
        'post_type'      => 'case',
        'posts_per_page' => (int) $atts['limit'],
    ) );

    if ( ! $q->have_posts() ) return '';

    ob_start();
    echo '<div class="case-list">';
    while ( $q->have_posts() ) {
        $q->the_post();
        $client = get_field( 'client_name' );
        echo '<div class="case-item">';
        echo '<a href="' . get_permalink() . '">' . get_the_title() . '</a>';
        if ( $client ) echo '<span>' . esc_html( $client ) . '</span>';
        echo '</div>';
    }
    echo '</div>';
    wp_reset_postdata();
    return ob_get_clean();
});

注意最后的 wp_reset_postdata()——用完 WP_Query 必须调用它,否则会影响页面上后续的查询(比如侧栏的文章列表显示错乱)。这是个高频 bug。

方法三:用页面构建器(不写代码)

Elementor Pro、Divi 这些页面构建器都有 CPT 的循环模块,选一下字段就能显示。适合不想碰代码的人。

四、不用插件怎么建 CPT

如果你不想多装插件,用代码注册也很简单。

add_action( 'init', function() {
    register_post_type( 'case', array(
        'labels' => array(
            'name'          => '案例',
            'singular_name' => '案例',
            'add_new_item'  => '添加案例',
        ),
        'public'       => true,
        'has_archive'  => true,
        'rewrite'      => array( 'slug' => 'cases' ),
        'supports'     => array( 'title', 'editor', 'thumbnail' ),
        'show_in_rest' => true,
        'menu_icon'    => 'dashicons-portfolio',
    ) );
});

几个参数说明:

  • has_archive:要不要列表页。设 true
  • show_in_rest:要不要在 REST API 和 Gutenberg 编辑器里可用。建议设 true,否则新编辑器用不了,API 也读不到
  • supports:这个类型支持哪些功能。title 标题、editor 正文、thumbnail 缩略图、excerpt 摘要
  • menu_icon:后台菜单图标,可以用 WordPress 内置的 dashicons

自定义字段(不用 ACF)

// 后台加字段框
add_action( 'add_meta_boxes', function() {
    add_meta_box( 'case_meta', '案例信息', 'case_meta_html', 'case', 'normal' );
});

function case_meta_html( $post ) {
    $client = get_post_meta( $post->ID, '_client_name', true );
    wp_nonce_field( 'case_save', 'case_nonce' );
    echo '<label>客户名称</label>';
    echo '<input type="text" name="client_name" value="' . esc_attr( $client ) . '" style="width:100%">';
}

// 保存
add_action( 'save_post_case', function( $post_id ) {
    if ( ! isset( $_POST['case_nonce'] ) ||
         ! wp_verify_nonce( $_POST['case_nonce'], 'case_save' ) ) {
        return;
    }
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
    if ( ! current_user_can( 'edit_post', $post_id ) ) return;

    if ( isset( $_POST['client_name'] ) ) {
        update_post_meta( $post_id, '_client_name',
            sanitize_text_field( $_POST['client_name'] ) );
    }
});

注意保存函数里的三个检查:nonce 验证、自动保存跳过、权限检查。三个都不能少,否则有安全漏洞(任何人都能改你的数据)。

五、ACF 还是原生字段:怎么选

ACF 插件 原生自定义字段
上手难度 后台点几下 要写代码
字段类型 丰富(图片、日期、关联、 repeater) 基本只有文本
后台界面 好看、好用 丑,要自己做
性能 字段多了会稍慢 轻量
依赖 插件停用数据还在(存在 postmeta) 无依赖

我的建议:除非你有特殊理由,用 ACF。它的免费版功能已经很强,而且数据是存在 WordPress 原生的 postmeta 表里的,即使插件停用,数据也不会丢——这点很重要,不会被绑架。

六、常见坑

坑 1:新建 CPT 后页面 404

前面说过:去「设置 → 固定链接」点一次保存。90% 的 404 都是这个原因。

如果还不行,检查 has_archive 是否设了 true,以及 rewrite 的 slug 有没有和现有页面冲突(比如已经有了一个叫 /cases/ 的页面)。

坑 2:文章类型的名字冲突

CPT 的名字(case)不能用 WordPress 保留字,比如 post、page、attachment、revision。也不能太长(建议不超过 20 个字符,数据库字段有限制)。

坑 3:字段值取不到

用 ACF 时用 get_field('字段名');用原生时是 get_post_meta($id, '_字段名', true)。

注意原生方式的字段名前面有下划线(如果你存的时候加了)。两个函数不能混用——ACF 存的数据用 get_post_meta 也能取,但 ACF 的复杂字段(图片、关联)取出来的是 ID 不是对象。

坑 4:列表页只显示 10 条

CPT 的列表页默认受「设置 → 阅读 → 每页显示数量」限制。要改的话:

add_action( 'pre_get_posts', function( $q ) {
    if ( ! is_admin() && $q->is_main_query() && $q->is_post_type_archive( 'case' ) ) {
        $q->set( 'posts_per_page', 24 );
    }
});

七、SEO 相关:别让 CPT 变成孤岛

建了 CPT 之后,记得:

  • 确保它有列表页(has_archive),并且这个列表页有内容、有标题
  • 加到 sitemap 里——大部分 SEO 插件会自动包含公开 CPT,确认一下
  • 内链:从首页或其他页面链接到 CPT 列表页,否则搜索引擎可能抓不到
  • 每个 CPT 的内容要真的写好——空壳 CPT 页对 SEO 是负担不是帮助

最后

CPT 是把 WordPress 从「博客系统」变成「内容管理系统」的关键一步。

但有个提醒:不要过度使用。有些人建了七八种 CPT,结果后台复杂得自己都记不住。一个站有两三种 CPT 就够了,能合并的就合并。

内容结构的复杂度是有成本的——它体现在你日常发布内容的时间上。