本文是《WordPress主题开发从入门到精通》系列教程的第十篇。我们将学习如何创建自定义文章类型(CPT),为你的主题添加产品展示、团队介绍等高级内容功能。
你是否曾经觉得WordPress默认的"文章"和"页面"不够用?比如你想创建一个产品展示网站,或者团队介绍页面,或者房地产列表?这时候就需要自定义文章类型(Custom Post Types) 了。
今天我们就来学习如何在主题中创建和使用自定义文章类型,让你的主题能够处理各种特殊内容。
什么是自定义文章类型?为什么需要它?
自定义文章类型是WordPress最强大的功能之一。它允许你创建不同于标准"文章"和"页面"的内容类型。
想象一下这些场景:
- 产品展示:创建一个"产品"文章类型,包含价格、规格等字段
- 团队介绍:创建一个"团队成员"文章类型,包含职位、社交媒体链接
- 房地产列表:创建一个"房产"文章类型,包含价格、面积、位置
- 作品集:创建一个"项目"文章类型,包含项目类型、客户信息
使用自定义文章类型而不是普通文章的好处是:内容分离、专用功能、更好的组织结构。
第一步:创建第一个自定义文章类型
我们在主题的functions.php文件中注册自定义文章类型。让我们创建一个"产品"文章类型:
/**
* 注册自定义文章类型:产品
*/
function mft_register_product_post_type() {
$labels = array(
'name' => _x( '产品', '产品通用名称', 'my-first-theme' ),
'singular_name' => _x( '产品', '产品单数名称', 'my-first-theme' ),
'menu_name' => __( '产品', 'my-first-theme' ),
'name_admin_bar' => __( '产品', 'my-first-theme' ),
'add_new' => __( '添加新产品', 'my-first-theme' ),
'add_new_item' => __( '添加新产品', 'my-first-theme' ),
'new_item' => __( '新产品', 'my-first-theme' ),
'edit_item' => __( '编辑产品', 'my-first-theme' ),
'view_item' => __( '查看产品', 'my-first-theme' ),
'all_items' => __( '所有产品', 'my-first-theme' ),
'search_items' => __( '搜索产品', 'my-first-theme' ),
'parent_item_colon' => __( '父级产品:', 'my-first-theme' ),
'not_found' => __( '未找到产品', 'my-first-theme' ),
'not_found_in_trash' => __( '回收站中没有产品', 'my-first-theme' ),
'featured_image' => __( '产品图片', 'my-first-theme' ),
'set_featured_image' => __( '设置产品图片', 'my-first-theme' ),
'remove_featured_image' => __( '移除产品图片', 'my-first-theme' ),
'use_featured_image' => __( '使用作为产品图片', 'my-first-theme' ),
'archives' => __( '产品归档', 'my-first-theme' ),
'insert_into_item' => __( '插入到产品', 'my-first-theme' ),
'uploaded_to_this_item' => __( '上传到此产品', 'my-first-theme' ),
'filter_items_list' => __( '筛选产品列表', 'my-first-theme' ),
'items_list_navigation' => __( '产品列表导航', 'my-first-theme' ),
'items_list' => __( '产品列表', 'my-first-theme' ),
);
$args = array(
'labels' => $labels,
'description' => __( '产品自定义文章类型', 'my-first-theme' ),
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'products' ),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 20,
'menu_icon' => 'dashicons-cart', // WordPress Dashicons
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ),
'show_in_rest' => true, // 支持Gutenberg编辑器
);
register_post_type( 'product', $args );
}
add_action( 'init', 'mft_register_product_post_type' );
关键参数解释:
public:是否在后台和前台显示rewrite:URL别名,这里产品详情页URL会是yoursite.com/products/some-product/has_archive:是否有归档页面(产品列表页)menu_icon:后台菜单图标,使用WordPress的Dashiconssupports:支持哪些功能(标题、编辑器、特色图片等)show_in_rest:是否支持Gutenberg编辑器
现在刷新后台,你应该能看到一个新的"产品"菜单项!
第二步:创建自定义分类法
文章类型通常需要自己的分类系统。让我们为产品添加"产品分类":
/**
* 注册产品分类法
*/
function mft_register_product_taxonomy() {
$labels = array(
'name' => _x( '产品分类', '分类法通用名称', 'my-first-theme' ),
'singular_name' => _x( '产品分类', '分类法单数名称', 'my-first-theme' ),
'search_items' => __( '搜索产品分类', 'my-first-theme' ),
'all_items' => __( '所有产品分类', 'my-first-theme' ),
'parent_item' => __( '父级分类', 'my-first-theme' ),
'parent_item_colon' => __( '父级分类:', 'my-first-theme' ),
'edit_item' => __( '编辑分类', 'my-first-theme' ),
'update_item' => __( '更新分类', 'my-first-theme' ),
'add_new_item' => __( '添加新分类', 'my-first-theme' ),
'new_item_name' => __( '新分类名称', 'my-first-theme' ),
'menu_name' => __( '产品分类', 'my-first-theme' ),
);
$args = array(
'hierarchical' => true, // 像分类一样,false则像标签一样
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'product-category' ),
'show_in_rest' => true,
);
register_taxonomy( 'product_category', array( 'product' ), $args );
}
add_action( 'init', 'mft_register_product_taxonomy' );
第三步:创建文章类型模板
现在我们需要为自定义文章类型创建专门的模板文件。WordPress会自动按照模板层级寻找合适的模板。
创建single-product.php(产品详情页模板):
<?php
/**
* 单产品模板
*
* @package My_First_Theme
*/
get_header(); ?>
<div class="content-wrapper product-single-layout">
<main class="main-content product-single-content">
<?php while ( have_posts() ) : the_post(); ?>
<article id="product-<?php the_ID(); ?>" <?php post_class( 'product-item' ); ?>>
<header class="product-header">
<h1 class="product-title"><?php the_title(); ?></h1>
<div class="product-meta">
<?php
$price = get_post_meta( get_the_ID(), 'product_price', true );
$sku = get_post_meta( get_the_ID(), 'product_sku', true );
if ( $price ) : ?>
<span class="product-price"><?php echo number_format( $price, 2 ); ?>元</span>
<?php endif;
if ( $sku ) : ?>
<span class="product-sku">SKU: <?php echo esc_html( $sku ); ?></span>
<?php endif; ?>
</div>
</header>
<div class="product-content-sections">
<!-- 产品图片 -->
<div class="product-gallery">
<?php if ( has_post_thumbnail() ) : ?>
<div class="product-main-image">
<?php the_post_thumbnail( 'large' ); ?>
</div>
<?php endif; ?>
</div>
<!-- 产品内容 -->
<div class="product-details">
<div class="product-description">
<?php the_content(); ?>
</div>
<!-- 产品规格 -->
<div class="product-specs">
<h3>产品规格</h3>
<?php
$specs = get_post_meta( get_the_ID(), 'product_specifications', true );
if ( $specs ) : ?>
<ul>
<?php foreach ( $specs as $spec ) : ?>
<li><?php echo esc_html( $spec ); ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</div>
</div>
</article>
<?php endwhile; ?>
</main>
<?php get_sidebar(); ?>
</div>
<?php get_footer(); ?>
创建archive-product.php(产品列表页模板):
<?php
/**
* 产品归档模板
*
* @package My_First_Theme
*/
get_header(); ?>
<div class="content-wrapper product-archive-layout">
<main class="main-content product-archive-content">
<header class="page-header">
<h1 class="page-title">我们的产品</h1>
<?php the_archive_description( '<div class="archive-description">', '</div>' ); ?>
</header>
<div class="product-grid">
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?>
<article id="product-<?php the_ID(); ?>" <?php post_class( 'product-card' ); ?>>
<div class="product-card-inner">
<!-- 产品图片 -->
<?php if ( has_post_thumbnail() ) : ?>
<div class="product-card-image">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail( 'medium' ); ?>
</a>
</div>
<?php endif; ?>
<div class="product-card-content">
<!-- 产品标题 -->
<h2 class="product-card-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h2>
<!-- 产品价格 -->
<?php
$price = get_post_meta( get_the_ID(), 'product_price', true );
if ( $price ) : ?>
<div class="product-card-price">
<?php echo number_format( $price, 2 ); ?>元
</div>
<?php endif; ?>
<!-- 产品摘要 -->
<div class="product-card-excerpt">
<?php the_excerpt(); ?>
</div>
<!-- 查看详情按钮 -->
<a href="<?php the_permalink(); ?>" class="product-card-button">
查看详情
</a>
</div>
</div>
</article>
<?php endwhile; ?>
<!-- 分页 -->
<div class="product-pagination">
<?php the_posts_pagination(); ?>
</div>
<?php else : ?>
<div class="no-products">
<p>暂无产品</p>
</div>
<?php endif; ?>
</div>
</main>
<?php get_sidebar(); ?>
</div>
<?php get_footer(); ?>
第四步:添加自定义元字段
让我们为产品添加价格、SKU等自定义字段。我们将使用WordPress的add_meta_box功能:
/**
* 添加产品元字段
*/
function mft_add_product_meta_boxes() {
add_meta_box(
'product_details',
__( '产品详情', 'my-first-theme' ),
'mft_product_meta_box_callback',
'product',
'normal',
'high'
);
}
add_action( 'add_meta_boxes', 'mft_add_product_meta_boxes' );
/**
* 元字段回调函数
*/
function mft_product_meta_box_callback( $post ) {
// 添加安全验证
wp_nonce_field( 'mft_save_product_meta', 'mft_product_meta_nonce' );
// 获取现有值
$price = get_post_meta( $post->ID, 'product_price', true );
$sku = get_post_meta( $post->ID, 'product_sku', true );
$specs = get_post_meta( $post->ID, 'product_specifications', true );
$specs = $specs ? $specs : array();
?>
<div class="product-meta-fields">
<div class="meta-field">
<label for="product_price"><?php _e( '价格', 'my-first-theme' ); ?></label>
<input type="number" step="0.01" id="product_price" name="product_price"
value="<?php echo esc_attr( $price ); ?>" />
</div>
<div class="meta-field">
<label for="product_sku"><?php _e( 'SKU', 'my-first-theme' ); ?></label>
<input type="text" id="product_sku" name="product_sku"
value="<?php echo esc_attr( $sku ); ?>" />
</div>
<div class="meta-field">
<label><?php _e( '规格', 'my-first-theme' ); ?></label>
<div id="product_specifications">
<?php foreach ( $specs as $index => $spec ) : ?>
<input type="text" name="product_specifications[]"
value="<?php echo esc_attr( $spec ); ?>" />
<?php endforeach; ?>
<input type="text" name="product_specifications[]" value="" />
</div>
<button type="button" class="button add-spec-field">添加更多规格</button>
</div>
</div>
<script>
jQuery(document).ready(function($) {
$('.add-spec-field').click(function() {
$('#product_specifications').append('<input type="text" name="product_specifications[]" value="" />');
});
});
</script>
<?php
}
/**
* 保存元字段数据
*/
function mft_save_product_meta( $post_id ) {
// 检查权限和安全验证
if ( ! isset( $_POST['mft_product_meta_nonce'] ) ||
! wp_verify_nonce( $_POST['mft_product_meta_nonce'], 'mft_save_product_meta' ) ||
defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ||
! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// 保存价格
if ( isset( $_POST['product_price'] ) ) {
update_post_meta( $post_id, 'product_price', sanitize_text_field( $_POST['product_price'] ) );
}
// 保存SKU
if ( isset( $_POST['product_sku'] ) ) {
update_post_meta( $post_id, 'product_sku', sanitize_text_field( $_POST['product_sku'] ) );
}
// 保存规格
if ( isset( $_POST['product_specifications'] ) ) {
$specs = array_filter( array_map( 'sanitize_text_field', $_POST['product_specifications'] ) );
update_post_meta( $post_id, 'product_specifications', $specs );
}
}
add_action( 'save_post', 'mft_save_product_meta' );
第五步:添加样式和脚本
在style.css中添加产品样式:
/* 产品归档页样式 */
.product-archive-layout {
max-width: 1200px;
margin: 0 auto;
}
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
margin: 2rem 0;
}
.product-card {
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.product-card:hover {
transform: translateY(-5px);
}
.product-card-image {
height: 200px;
overflow: hidden;
}
.product-card-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.product-card:hover .product-card-image img {
transform: scale(1.05);
}
.product-card-content {
padding: 1.5rem;
}
.product-card-title {
font-size: 1.2rem;
margin-bottom: 0.5rem;
}
.product-card-title a {
color: #333;
text-decoration: none;
}
.product-card-title a:hover {
color: #007cba;
}
.product-card-price {
font-size: 1.3rem;
font-weight: bold;
color: #e74c3c;
margin-bottom: 1rem;
}
.product-card-excerpt {
color: #666;
margin-bottom: 1.5rem;
line-height: 1.6;
}
.product-card-button {
display: inline-block;
padding: 0.75rem 1.5rem;
background: #007cba;
color: white;
text-decoration: none;
border-radius: 4px;
transition: background 0.3s ease;
}
.product-card-button:hover {
background: #005a87;
}
/* 产品详情页样式 */
.product-single-layout {
max-width: 1200px;
margin: 0 auto;
}
.product-header {
text-align: center;
margin-bottom: 3rem;
padding-bottom: 2rem;
border-bottom: 1px solid #eee;
}
.product-title {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.product-meta {
font-size: 1.1rem;
}
.product-price {
font-size: 1.5rem;
font-weight: bold;
color: #e74c3c;
margin-right: 1rem;
}
.product-sku {
color: #666;
font-style: italic;
}
.product-content-sections {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
margin-bottom: 3rem;
}
.product-gallery {
position: relative;
}
.product-main-image {
border-radius: 8px;
overflow: hidden;
}
.product-main-image img {
width: 100%;
height: auto;
}
.product-details {
padding: 1rem;
}
.product-description {
line-height: 1.8;
margin-bottom: 2rem;
}
.product-specs h3 {
margin-bottom: 1rem;
color: #333;
}
.product-specs ul {
list-style: none;
padding: 0;
}
.product-specs li {
padding: 0.5rem 0;
border-bottom: 1px solid #f0f0f0;
}
.product-specs li:last-child {
border-bottom: none;
}
/* 后台元字段样式 */
.product-meta-fields {
padding: 1rem 0;
}
.meta-field {
margin-bottom: 1.5rem;
}
.meta-field label {
display: block;
font-weight: bold;
margin-bottom: 0.5rem;
}
.meta-field input[type="text"],
.meta-field input[type="number"] {
width: 100%;
max-width: 400px;
padding: 0.5rem;
}
/* 响应式设计 */
@media (max-width: 768px) {
.product-grid {
grid-template-columns: 1fr;
}
.product-content-sections {
grid-template-columns: 1fr;
gap: 2rem;
}
.product-title {
font-size: 2rem;
}
}
第六步:创建小工具显示最新产品
让我们创建一个小工具来显示最新产品:
/**
* 最新产品小工具
*/
class MFT_Recent_Products_Widget extends WP_Widget {
function __construct() {
parent::__construct(
'mft_recent_products',
__( '最新产品', 'my-first-theme' ),
array( 'description' => __( '显示最新产品列表', 'my-first-theme' ) )
);
}
public function widget( $args, $instance ) {
$title = apply_filters( 'widget_title', $instance['title'] );
$number = ! empty( $instance['number'] ) ? absint( $instance['number'] ) : 5;
echo $args['before_widget'];
if ( ! empty( $title ) ) {
echo $args['before_title'] . $title . $args['after_title'];
}
$products = new WP_Query( array(
'post_type' => 'product',
'posts_per_page' => $number,
'post_status' => 'publish'
) );
if ( $products->have_posts() ) {
echo '<ul class="recent-products-list">';
while ( $products->have_posts() ) {
$products->the_post();
$price = get_post_meta( get_the_ID(), 'product_price', true );
?>
<li class="recent-product-item">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
<?php if ( $price ) : ?>
<span class="product-widget-price"><?php echo number_format( $price, 2 ); ?>元</span>
<?php endif; ?>
</li>
<?php
}
echo '</ul>';
wp_reset_postdata();
} else {
echo '<p>' . __( '暂无产品', 'my-first-theme' ) . '</p>';
}
echo $args['after_widget'];
}
public function form( $instance ) {
$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
$number = ! empty( $instance['number'] ) ? $instance['number'] : 5;
?>
<p>
<label for="<?php echo $this->get_field_id( 'title' ); ?>">
<?php _e( '标题:', 'my-first-theme' ); ?>
</label>
<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>"
name="<?php echo $this->get_field_name( 'title' ); ?>"
type="text" value="<?php echo esc_attr( $title ); ?>" />
</p>
<p>
<label for="<?php echo $this->get_field_id( 'number' ); ?>">
<?php _e( '显示数量:', 'my-first-theme' ); ?>
</label>
<input id="<?php echo $this->get_field_id( 'number' ); ?>"
name="<?php echo $this->get_field_name( 'number' ); ?>"
type="number" value="<?php echo esc_attr( $number ); ?>"
min="1" max="20" />
</p>
<?php
}
public function update( $new_instance, $old_instance ) {
$instance = array();
$instance['title'] = ! empty( $new_instance['title'] ) ? sanitize_text_field( $new_instance['title'] ) : '';
$instance['number'] = ! empty( $new_instance['number'] ) ? absint( $new_instance['number'] ) : 5;
return $instance;
}
}
// 注册小工具
function mft_register_product_widget() {
register_widget( 'MFT_Recent_Products_Widget' );
}
add_action( 'widgets_init', 'mft_register_product_widget' );
总结:自定义文章类型的强大功能
通过今天的学习,你已经掌握了创建自定义文章类型的完整流程:
- 注册文章类型:使用
register_post_type()创建新的内容类型 - 添加分类法:使用
register_taxonomy()创建分类系统 - 创建模板:按照模板层级创建专用模板文件
- 添加元字段:使用
add_meta_box()添加自定义字段 - 样式和功能:为文章类型添加专用样式和功能
现在你的主题已经具备了创建复杂内容网站的能力!你可以用同样的方法创建:
- 团队成员文章类型
- 服务项目文章类型
- 房地产列表文章类型
- 作品集文章类型
在下一篇文章中,我们将学习Meta Box开发,深入了解如何为文章和页面添加更复杂的自定义字段。

评论框