搜索全站
建站 34 分钟阅读

WordPress企业站案例展示模块:图文、筛选与详情页实现

案例页做成时间列表,客户根本找不到同行案例。本文给出案例模块的完整实现:案例 CPT + 行业分类、可收录的筛选链接、图文详情页模板、与产品互链的相关推荐,附可直接用的代码。

U
UFO_PF 2021-05-05 · 阅读 34 分钟
Wo 建站
阅读提示
本文约 13,220 字,预计阅读 34 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:案例是按行业找的,不是按时间翻的

一个做自动化集成的公司,官网案例页有 30 多个项目,按发布时间倒序排列,每页 10 条。销售反馈最多的一句是:「客户是做医药的,我得自己在页面上找医药相关的案例发给他,每次都要翻半天。」

问题的本质是分类维度错位。访客(以及帮他找资料的销售)是按「行业」找案例的,而不是按「什么时候做的」。行业内还关心规模(产值多少、几条产线)和成效(提升多少百分比)。按时间排的列表把这三个维度全部埋掉了。

案例模块在企业站里的作用非常明确:它是采购决策链上的「社会证明」环节。访客已经确认你有这类产品,接下来要看的是「有没有和我相似的人用过、效果如何」。所以案例模块的三个核心能力是:能按行业筛出来、详情页讲得清背景与成效、并且能反向链接回所用产品形成闭环。

原理分析:筛选该用链接还是 AJAX

这是案例模块第一个技术决策,影响上线后的收录与流量。

方案 优点 缺点 适用
分类归档链接 可被收录、可分享、无 JS 依赖 每次筛选刷新页面 绝大多数企业站
纯 AJAX 筛选 体验流畅 URL 不变、不被收录、需维护 JS 数量少且无需 SEO
链接 + AJAX 增强 兼顾 实现复杂度高 有开发资源

企业站一律推荐第一种。用分类法归档页承载筛选结果,URL 形如 /cases/industry/pharmaceutical/,每个行业组合都是一个可被搜索引擎收录的页面。这类页面带来的是「医药 自动化 案例」这样的长尾词,转化质量通常很高。

第二个决策是案例与产品的关联方式。两种做法:一是用共享分类法(产品和案例共用同一个行业分类法),二是用自定义字段存关联的产品 ID。前者简单但粗糙,后者精确。推荐后者——一个案例用了三个不同产品,靠共享分类无法表达这种精确到产品的关联。

第三个决策是图集怎么做。用自定义字段存附件 ID 数组最简单可控;直接把图片插入正文则编辑体验好但结构与展示分离困难。折中方案:正文写方案描述,图集单独用字段管理,模板里分区渲染。

保姆级实操

步骤 1:注册案例类型与行业分类

<?php
add_action( 'init', 'mmf_register_case' );
function mmf_register_case() {
    register_post_type( 'case', array(
        'labels' => array(
            'name'          => '客户案例',
            'singular_name' => '案例',
            'add_new_item'  => '添加案例',
        ),
        'public'        => true,
        'has_archive'   => 'cases',
        'rewrite'       => array( 'slug' => 'cases', 'with_front' => false ),
        'menu_icon'     => 'dashicons-awards',
        'menu_position' => 22,
        'supports'      => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'show_in_rest'  => true,
    ) );

    register_taxonomy( 'case_industry', 'case', array(
        'labels'       => array( 'name' => '行业', 'singular_name' => '行业' ),
        'public'       => true,
        'hierarchical' => true,
        'rewrite'      => array( 'slug' => 'cases/industry', 'with_front' => false ),
        'show_in_rest' => true,
        'show_admin_column' => true,
    ) );

    // 案例规模标签,用于细分筛选
    register_taxonomy( 'case_scale', 'case', array(
        'labels'       => array( 'name' => '项目规模' ),
        'public'       => true,
        'hierarchical' => false,
        'rewrite'      => array( 'slug' => 'cases/scale' ),
        'show_in_rest' => true,
    ) );
}

步骤 2:案例归档页,用链接筛选

<?php
// archive-case.php
get_header();

$industries = get_terms( array(
    'taxonomy'   => 'case_industry',
    'hide_empty' => true,
    'parent'     => 0,
) );

$scales = get_terms( array(
    'taxonomy'   => 'case_scale',
    'hide_empty' => true,
) );

$current = is_tax() ? get_queried_object() : null;
?>

<main class="archive-case">
    <h1><?php post_type_archive_title(); ?></h1>

    <div class="case-filters">
        <div class="filter-row">
            <span class="filter-label">行业:</span>
            <a class="<?php echo is_post_type_archive( 'case' ) ? 'is-current' : ''; ?>"
               href="<?php echo esc_url( get_post_type_archive_link( 'case' ) ); ?>">全部</a>
            <?php foreach ( $industries as $t ) : ?>
                <a class="<?php echo ( $current && $current->term_id === $t->term_id ) ? 'is-current' : ''; ?>"
                   href="<?php echo esc_url( get_term_link( $t ) ); ?>">
                    <?php echo esc_html( $t->name ); ?>
                    <span class="count"><?php echo (int) $t->count; ?></span>
                </a>
            <?php endforeach; ?>
        </div>

        <div class="filter-row">
            <span class="filter-label">规模:</span>
            <?php foreach ( $scales as $t ) : ?>
                <a href="<?php echo esc_url( get_term_link( $t ) ); ?>"><?php echo esc_html( $t->name ); ?></a>
            <?php endforeach; ?>
        </div>
    </div>

    <div class="case-grid">
        <?php if ( have_posts() ) : while ( have_posts() ) : the_post();
            $result = get_post_meta( get_the_ID(), '_case_result', true );
        ?>
            <article class="case-card">
                <a href="<?php the_permalink(); ?>">
                    <?php the_post_thumbnail( 'mmf-card', array( 'loading' => 'lazy' ) ); ?>
                    <h2><?php the_title(); ?></h2>
                </a>
                <?php
                $ind = get_the_terms( get_the_ID(), 'case_industry' );
                if ( $ind && ! is_wp_error( $ind ) ) {
                    echo '<p class="case-meta">' . esc_html( $ind[0]->name ) . '</p>';
                }
                if ( $result ) {
                    echo '<p class="case-result">' . esc_html( $result ) . '</p>';
                }
                ?>
            </article>
        <?php endwhile; else : ?>
            <p>该分类下暂无案例。</p>
        <?php endif; ?>
    </div>

    <?php the_posts_pagination(); ?>
</main>

<?php get_footer();

「规模」那一行如果也想做成独立可收录的组合页,需要注册组合 rewrite 规则;简单场景下让它跳转到单分类归档即可,不要追求全组合(组合爆炸会产生大量内容稀薄页)。

步骤 3:案例详情页的四个板块

<?php
// single-case.php 主体
while ( have_posts() ) : the_post();
    $gallery = get_post_meta( get_the_ID(), '_case_gallery', true );
    $gallery = is_array( $gallery ) ? array_filter( $gallery, 'is_numeric' ) : array();

    $products = get_post_meta( get_the_ID(), '_case_products', true );
    $products = is_array( $products ) ? array_filter( $products, 'is_numeric' ) : array();
?>
    <article <?php post_class( 'single-case' ); ?>>
        <h1><?php the_title(); ?></h1>

        <dl class="case-facts">
            <?php
            $ind = get_the_terms( get_the_ID(), 'case_industry' );
            if ( $ind && ! is_wp_error( $ind ) ) : ?>
                <dt>行业</dt><dd><?php echo esc_html( $ind[0]->name ); ?></dd>
            <?php endif; ?>
            <?php $scale = get_the_terms( get_the_ID(), 'case_scale' );
            if ( $scale && ! is_wp_error( $scale ) ) : ?>
                <dt>规模</dt><dd><?php echo esc_html( $scale[0]->name ); ?></dd>
            <?php endif; ?>
        </dl>

        <section class="case-body">
            <h2>项目背景与方案</h2>
            <?php the_content(); ?>
        </section>

        <?php if ( $gallery ) : ?>
            <section class="case-gallery">
                <h2>现场图片</h2>
                <div class="gallery-grid">
                    <?php foreach ( $gallery as $att_id ) {
                        echo wp_get_attachment_image(
                            (int) $att_id,
                            'mmf-card',
                            false,
                            array( 'loading' => 'lazy', 'class' => 'case-img' )
                        );
                    } ?>
                </div>
            </section>
        <?php endif; ?>

        <?php if ( $products ) : ?>
            <section class="case-products">
                <h2>本案例使用的产品</h2>
                <ul>
                    <?php foreach ( $products as $pid ) :
                        $p = get_post( (int) $pid );
                        if ( ! $p || 'product' !== $p->post_type ) { continue; } ?>
                        <li>
                            <a href="<?php echo esc_url( get_permalink( $p ) ); ?>">
                                <?php echo esc_html( $p->post_title ); ?>
                            </a>
                        </li>
                    <?php endforeach; ?>
                </ul>
            </section>
        <?php endif; ?>
    </article>
<?php endwhile;

wp_get_attachment_image() 会自动输出 srcset 与 sizes,比手写 <img> 好得多,响应式和懒加载都能白拿。

步骤 4:相关案例推荐(同行业)

<?php
$related = new WP_Query( array(
    'post_type'      => 'case',
    'posts_per_page' => 3,
    'post__not_in'   => array( get_the_ID() ),
    'no_found_rows'  => true,
    'tax_query'      => array(
        array(
            'taxonomy' => 'case_industry',
            'field'    => 'term_id',
            'terms'    => $ind && ! is_wp_error( $ind ) ? $ind[0]->term_id : 0,
        ),
    ),
) );

if ( $related->have_posts() ) : ?>
    <aside class="related-cases">
        <h2>同行业案例</h2>
        <ul>
        <?php while ( $related->have_posts() ) : $related->the_post(); ?>
            <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
        <?php endwhile; wp_reset_postdata(); ?>
        </ul>
    </aside>
<?php endif; ?>

no_found_rows => true 可以跳过总数统计,省一次 SQL,在不需要分页的场景下必加。

步骤 5:图集与关联产品的元框

<?php
add_action( 'add_meta_boxes', function() {
    add_meta_box( 'mmf-case-extra', '案例附加信息', 'mmf_case_box', 'case', 'normal', 'default' );
} );

function mmf_case_box( $post ) {
    wp_nonce_field( 'mmf_save_case', 'mmf_case_nonce' );

    $products = get_post_meta( $post->ID, '_case_products', true );
    $products = is_array( $products ) ? array_map( 'intval', $products ) : array();

    $all = get_posts( array(
        'post_type'      => 'product',
        'posts_per_page' => -1,
        'orderby'        => 'title',
        'order'          => 'ASC',
        'fields'         => 'ids',
    ) );
    ?>
    <p>
        <label for="mmf_case_result">成效一句话(如:产能提升 32%,减少 4 名操作工)</label><br>
        <input type="text" id="mmf_case_result" name="mmf_case_result"
               value="<?php echo esc_attr( get_post_meta( $post->ID, '_case_result', true ) ); ?>"
               style="width:100%">
    </p>

    <p><strong>关联产品(可多选)</strong></p>
    <select name="mmf_case_products[]" multiple size="8" style="width:100%">
        <?php foreach ( $all as $pid ) : ?>
            <option value="<?php echo (int) $pid; ?>" <?php selected( in_array( (int) $pid, $products, true ) ); ?>>
                <?php echo esc_html( get_the_title( $pid ) ); ?>
            </option>
        <?php endforeach; ?>
    </select>
    <?php
}

add_action( 'save_post_case', 'mmf_save_case' );
function mmf_save_case( $post_id ) {
    if ( ! isset( $_POST['mmf_case_nonce'] )
        || ! wp_verify_nonce( sanitize_key( $_POST['mmf_case_nonce'] ), 'mmf_save_case' ) ) {
        return;
    }
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    if ( isset( $_POST['mmf_case_result'] ) ) {
        update_post_meta(
            $post_id,
            '_case_result',
            sanitize_text_field( wp_unslash( $_POST['mmf_case_result'] ) )
        );
    }

    $ids = isset( $_POST['mmf_case_products'] )
        ? array_map( 'intval', (array) $_POST['mmf_case_products'] )
        : array();
    update_post_meta( $post_id, '_case_products', $ids );
}

图集字段可以用 WordPress 媒体上传器(3.5 之后有 wp.media),也可以让编辑直接把图片插入正文后用 get_attached_media( 'image', $post_id ) 取出来渲染,后者省掉一套 JS。

步骤 6:给案例页加结构化数据

<?php
add_action( 'wp_head', 'mmf_case_schema' );
function mmf_case_schema() {
    if ( ! is_singular( 'case' ) ) {
        return;
    }
    $data = array(
        '@context'    => 'https://schema.org',
        '@type'       => 'Article',
        'headline'    => wp_strip_all_tags( get_the_title() ),
        'datePublished' => get_the_date( 'c' ),
    );
    if ( has_post_thumbnail() ) {
        $data['image'] = get_the_post_thumbnail_url( null, 'full' );
    }
    echo '<script type="application/ld+json">'
        . wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE )
        . '</script>';
}

常见踩坑

  1. 筛选用 AJAX 且 URL 不变:行业页无法被收录,等于白做。
  2. 所有行业组合都生成页面:组合爆炸后产生大量内容稀薄的页面,反而拉低整站质量评分。只做单维度归档。
  3. 案例正文里没有成效数字:只有「提升了效率」这类定性描述,说服力极弱。必须量化。
  4. 忘了 wp_reset_postdata():相关案例循环后,后续的 the_title() 会取到错误的文章。
  5. 案例: 某站点用共享分类法关联产品与案例(都用 case_industry),上线后发现「医药」分类下既有产品又有案例,归档页混在一起,产品页和案例页的 URL 也互相冲突。后来改成:案例独立用 case_industry,产品关联改用自定义字段存产品 ID。教训:分类法负责「分类」,跨类型关系用字段表达,不要混用。
  6. 图集用 <img> 手写且没有 srcset:移动端下载原图,页面体积几十 MB。一律用 wp_get_attachment_image()。
  7. 案例图没写 alt:对无障碍与图片搜索都不友好。用 wp_get_attachment_image() 时会自动取附件的 alt,前提是上传时填了。

总结

案例模块的三要素:按行业(而非时间)分类、筛选走可收录的分类归档链接、详情页包含背景—方案—成效—关联产品四段,并通过相关案例形成同行业内链。

验收清单:每个行业分类页有独立 URL 且收录正常;详情页首图 eager、图集 lazy;关联产品链接可点击且有效;相关案例至少 2 条;所有输出点做了转义。

产品与案例互链构成站内权重主干,产品侧的做法见 企业站产品中心CPT搭建教程;模块上线后用 WordPress主题性能度量方法 跑一次归档页与详情页,确认图集没有把 LCP 拖垮。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。