搜索全站
主题 20 分钟阅读

主题选项面板怎么选:Customizer、自建设置页、框架对比

主题的 Logo、颜色、布局开关该放哪里?对比 Customizer、自建设置页、Kirki/Redux 框架三种方案的实现成本、数据归属与迁移代价,附选型清单与代码骨架。

U
UFO_PF 2025-09-07 · 阅读 20 分钟
主题 主题
阅读提示
本文约 7,636 字,预计阅读 20 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:主题选项写好了,换主题后设置全没了

用 get_theme_mod() 做了一套主题选项:Logo、主色、页脚版权、布局开关,客户填了三十多项。半年后换主题,所有设置归零,客户以为网站被重置了,投诉到运营。

问题不在代码,在选型:theme_mods 是按主题存储的,换主题必然丢失。这类”设置存在哪里”的问题,必须在动手写第一行代码之前就想清楚。

原理分析:三种方案的本质区别

方案 数据存储 换主题后 实时预览 实现成本
Customizer(theme_mod) wp_options 的 theme_mods_{stylesheet} 丢失 支持 中
Customizer(option) wp_options 独立 option 保留 支持(需 postMessage) 中
自建设置页 wp_options(Settings API) 保留 不支持 高
框架(Kirki / Redux / Carbon Fields) 取决于配置 取决于配置 支持 低

关键点解释:

theme_mods_{stylesheet} 是”每个主题一份”的设置包。 key 里带主题目录名,换主题就是换一个 option,旧数据还在数据库里(可迁移),但新主题读不到。这是 WordPress 的设计意图:主题选项应该”跟着主题走”,比如”这个主题要不要显示侧边栏”。

get_theme_mod() vs get_option():

// theme_mod:按主题隔离,多数带默认值的样式开关适合用它
$color = get_theme_mod( 'mmf_primary_color', '#1a73e8' );

// option:全局,与主题无关的数据(如备案号、统计 ID)适合用它
$icp = get_option( 'mmf_icp_number', '' );

WP_Customize_Setting 的 type 参数决定存哪儿:'theme_mod'(默认)或 'option'。

Customizer 相比自建设置页的独特价值是”实时预览”:不需要保存就能看到改动效果,靠的是 transport => 'postMessage' + 一段 JS。自建设置页做不到这一点,除非自己写。

保姆级实操

方案 A:Customizer(官方推荐,wordpress.org 强制)

add_action( 'customize_register', function ( $wp_customize ) {
    // 1. 面板
    $wp_customize->add_panel( 'mmf_options', array(
        'title'       => '主题选项',
        'description' => '站点外观相关设置',
        'priority'    => 160,
    ) );

    // 2. 区块
    $wp_customize->add_section( 'mmf_colors', array(
        'title'    => '颜色',
        'panel'    => 'mmf_options',
        'priority' => 10,
    ) );

    // 3. 设置项(theme_mod 类型)
    $wp_customize->add_setting( 'mmf_primary_color', array(
        'default'           => '#1a73e8',
        'sanitize_callback' => 'sanitize_hex_color',   // 必须提供清理函数
        'transport'         => 'postMessage',          // 实时预览
    ) );

    // 4. 控件
    $wp_customize->add_control( new WP_Customize_Color_Control(
        $wp_customize,
        'mmf_primary_color',
        array(
            'label'    => '主色调',
            'section'  => 'mmf_colors',
            'settings' => 'mmf_primary_color',
        )
    ) );

    // 与主题无关的数据:用 option 类型存
    $wp_customize->add_setting( 'mmf_icp_number', array(
        'type'              => 'option',
        'default'           => '',
        'sanitize_callback' => 'sanitize_text_field',
    ) );
    $wp_customize->add_control( 'mmf_icp_number', array(
        'label'   => 'ICP 备案号',
        'section' => 'mmf_colors',
        'type'    => 'text',
    ) );
} );

// 5. 实时预览脚本
add_action( 'customize_preview_init', function () {
    wp_enqueue_script(
        'mmf-customizer',
        get_stylesheet_directory_uri() . '/assets/js/customizer.js',
        array( 'customize-preview' ),
        '1.0.0',
        true
    );
} );

customizer.js:

( function ( $ ) {
    wp.customize( 'mmf_primary_color', function ( value ) {
        value.bind( function ( newval ) {
            $( ':root' ).css( '--mmf-primary', newval );
        } );
    } );
} )( jQuery );

前台读取与输出:

add_action( 'wp_head', function () {
    $color = get_theme_mod( 'mmf_primary_color', '#1a73e8' );
    printf( '<style>:root{--mmf-primary:%s;}</style>', esc_attr( $color ) );
} );

方案 B:自建设置页(Settings API)

add_action( 'admin_menu', function () {
    add_menu_page(
        '站点设置',
        '站点设置',
        'manage_options',
        'mmf-settings',
        'mmf_render_settings_page',
        'dashicons-admin-generic',
        60
    );
} );

add_action( 'admin_init', function () {
    register_setting( 'mmf_settings_group', 'mmf_icp_number', array(
        'type'              => 'string',
        'sanitize_callback' => 'sanitize_text_field',
        'default'           => '',
    ) );

    add_settings_section( 'mmf_section_basic', '基础信息', function () {
        echo '<p>与主题无关、换主题也需保留的信息。</p>';
    }, 'mmf-settings' );

    add_settings_field( 'mmf_icp_number', 'ICP 备案号', function () {
        printf(
            '<input type="text" name="mmf_icp_number" value="%s" class="regular-text">',
            esc_attr( get_option( 'mmf_icp_number', '' ) )
        );
    }, 'mmf-settings', 'mmf_section_basic' );
} );

function mmf_render_settings_page() {
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <?php settings_errors(); ?>
        <form method="post" action="options.php">
            <?php
            settings_fields( 'mmf_settings_group' );
            do_settings_sections( 'mmf-settings' );
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

方案 C:用框架加速

框架 特点 适合
Kirki 封装 Customizer API,控件丰富,配置式写法 想用 Customizer 又嫌原生啰嗦
Redux Framework 独立设置面板,控件最多,体积较大 商业主题、选项特别多
Carbon Fields 支持 Customizer + 后台页面 + term meta,无界面依赖 需要大量自定义字段
Codestar Framework 轻量独立面板,配置项清晰 商业主题快速搭建
ACF PRO 选项页 用熟悉的字段组搭后台页面 站点已有 ACF 生态

Kirki 的写法示例(配置式,比原生少一半代码):

Kirki::add_config( 'mmf_config', array(
    'capability'  => 'edit_theme_options',
    'option_type' => 'theme_mod',
) );

Kirki::add_section( 'mmf_colors', array(
    'title' => '颜色',
    'panel' => '',
) );

Kirki::add_field( 'mmf_config', array(
    'type'      => 'color',
    'settings'  => 'mmf_primary_color',
    'label'     => '主色调',
    'section'   => 'mmf_colors',
    'default'   => '#1a73e8',
    'transport' => 'auto',       // Kirki 自动处理 postMessage 预览
    'output'    => array(
        array(
            'element'  => ':root',
            'property' => '--mmf-primary',
        ),
    ),
) );

注意 Kirki 是面向 Customizer 的封装,数据仍按 option_type 落到 theme_mod 或 option。

第 4 步:按数据性质决定存哪儿(选型清单)

数据类型 例子 存哪 方案
纯外观,换主题理应重置 主色、字号、布局开关、Logo theme_mod Customizer
与主题无关的业务信息 备案号、统计 ID、客服电话、公司名 option Customizer(option) 或设置页
需要复杂表单/多步骤 邮件模板、API 对接配置 option 自建设置页
大量字段(几十项以上) 商业主题全套配置 视情况 框架

第 5 步:换主题时迁移 theme_mods

数据其实还在数据库里,可以导出再导入:

# 导出旧主题的设置(JSON)
wp option get theme_mods_oldtheme --format=json > old-mods.json

# 把需要的键合并到新主题
wp eval '
$old = json_decode( file_get_contents( "old-mods.json" ), true );
$new = get_option( "theme_mods_newtheme", array() );
foreach ( array( "mmf_icp_number", "custom_logo", "background_color" ) as $k ) {
    if ( isset( $old[ $k ] ) ) { $new[ $k ] = $old[ $k ]; }
}
update_option( "theme_mods_newtheme", $new );
echo "迁移完成\n";
'

菜单位置指派(nav_menu_locations)也存在 theme_mods 里,换主题后需要重新指派,这是最容易漏的一项,注册与指派的完整说明见register_nav_menus 注册菜单位置。

常见踩坑

  1. 所有选项都用 theme_mod。 换主题后备案号、统计代码、客服电话全丢,这类数据应该用 option。
  2. sanitize_callback 缺失。 Customizer 保存时不做清理,颜色字段存进奇怪的值,前台输出未转义直接进入 CSS,形成注入点。核心提供了 sanitize_hex_color、sanitize_text_field、absint 等现成函数,直接传函数名即可。
  3. 自定义 sanitize 回调忘了返回默认值。 校验失败时应返回 $setting->default,否则存进空值。
  4. transport => 'postMessage' 但没写预览 JS。 后台改了颜色前台不动,需要刷新才看到,等于”假实时”。
  5. 把框架打包进主题却没处理更新。 Redux、Kirki 这类框架若随主题分发,主题更新时框架版本不动,长期会积累安全隐患;用 TGM Plugin Activation 之类的推荐安装机制更稳妥。
  6. 案例: 某商业主题用独立框架做了六十多项设置,客户要求换主题时发现:主色、Logo、页脚文案全丢,更要命的是”客服微信二维码””备案号””统计代码”也一起消失,而这些本应与主题无关。补救只能从数据库里把 theme_mods 老 option 导出后逐项手工迁移,耗时一天。教训是:写选项之前先按”换主题后该不该保留”把字段分两类,业务类一律走 option。这类数据归属的判断,与functions.php 与插件职责划分里的思路是同一个。
  7. 每个控件都单独写一段 wp_head 输出。 几十个 add_action('wp_head') 让 <head> 里塞满 <style> 片段。应统一收集后一次性输出,或用 Kirki 的 output 参数自动生成。

总结

选型标准只有一条:换主题之后这个设置该不该保留? 不该保留的(颜色、布局、Logo)用 Customizer + theme_mod;该保留的(备案号、统计 ID、联系方式)用 option,放 Customizer 或自建设置页都行。面向 wordpress.org 分发必须用 Customizer(官方硬性要求);商业主题选项量大、要求独立面板时用 Kirki / Redux / Carbon Fields 这类框架加速。无论哪种方案,每个设置项都必须提供 sanitize_callback,每个输出都必须转义——选项面板是主题里最容易出现注入的地方。

U
UFO_PF作者

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

留下你的想法

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