返回

Vue+ElementUI:为WordPress插件打造Elementor式排版控件

vue.js

Vue.js + Element UI:给你的 WordPress 插件添加像 Elementor 一样的排版控制

你在用 Vue.js 和 Element UI 开发 WordPress 插件,想让用户能像用 Elementor 那样,方便地调整插件的字体、字号、行高、颜色等排版样式?你已经加了个颜色选择器,但 Element UI 好像没有现成的、能组合各种排版设置的“排版控件”。那个 Typography 组件只是用来展示文本的,不是用来做设置面板的。

你自己尝试写了个 Vue 组件,但它没在你的设置面板里显示出来。这事儿确实有点绕,咱们来捋一捋怎么搞定它。

问题出在哪?

  1. Element UI 缺少复合控件: Element UI 提供了基础的输入框 (el-input-number)、选择器 (el-select)、颜色选择器 (el-color-picker) 等。但它没有一个像 Elementor 那样把字体、字号、字重、行高、间距、颜色等打包在一起的“排版设置器”复合组件。你需要自己动手,把这些基础零件拼起来。
  2. 组件集成问题: 你写的 Vue 组件代码本身可能没大毛病,但它没显示出来,通常问题出在“集成”环节。比如:
    • Vue 组件没有在你的 WordPress 插件后台页面正确注册和挂载。
    • PHP 后端和 Vue 前端之间的数据传递可能没接上头。
    • 可能存在 JavaScript 错误阻止了组件渲染。
  3. 状态管理和数据流: 排版控件涉及多个设置项(字体、大小、颜色等),需要一个结构化的数据对象来管理这些状态,并且要确保用户在界面上的操作能正确更新这个对象,同时这个对象的变化也能反映回界面 (双向绑定)。当这些设置需要保存到 WordPress 数据库时,数据如何在 Vue 组件、父组件/页面、PHP 后端之间流动,也得设计好。

解决方案:打造自定义排版控件

既然没有现成的,咱们就自己造一个。核心思路是:创建一个 Vue 组件 (TypographyControl.vue),内部组合使用 Element UI 的各种表单控件,来分别控制不同的 CSS 排版属性。

1. 构建 Vue 组件 (TypographyControl.vue)

这个组件负责提供用户界面,让用户选择字体、字号、字重等等。它需要接收当前的排版设置作为 prop (通常通过 v-model 实现),并在用户修改设置时,通过 emit 事件将新的设置传出去。

你的代码 VUE 部分基本框架是对的,我们稍微完善一下,并确保它能通过 v-model 工作。

<template>
    <div class="typography-control">
        <!-- Font Family -->
        <div class="typography-control-section">
            <label for="font-family">字体</label>
            <el-select
                v-model="currentTypography.font_family"
                placeholder="选择字体"
                filterable  <!-- 允许用户搜索 -->
                allow-create <!-- 允许用户输入自定义字体 -->
                default-first-option <!-- 回车时选中第一个匹配项 -->
            >
                <el-option v-for="font in availableFonts" :key="font" :label="font" :value="font">
                </el-option>
            </el-select>
        </div>

        <!-- Font Size -->
        <div class="typography-control-section">
            <label for="font-size">字号</label>
            <div class="size-unit-input">
                <el-input-number
                    v-model="currentTypography.font_size.size"
                    :min="0"
                    :step="1"
                    controls-position="right"
                    placeholder="大小"
                    class="size-input"
                 />
                <el-select v-model="currentTypography.font_size.unit" class="unit-select">
                    <el-option v-for="unit in availableUnits" :key="unit" :label="unit" :value="unit">
                    </el-option>
                </el-select>
            </div>
        </div>

        <!-- Font Weight -->
        <div class="typography-control-section">
            <label for="font-weight">字重</label>
            <el-select v-model="currentTypography.font_weight" placeholder="默认">
                <el-option v-for="(label, value) in availableFontWeights" :key="value" :label="label" :value="value">
                </el-option>
            </el-select>
        </div>

        <!-- Text Transform -->
        <div class="typography-control-section">
            <label for="text-transform">文本转换</label>
            <el-select v-model="currentTypography.text_transform" placeholder="默认">
                <el-option v-for="(label, value) in availableTextTransforms" :key="value" :label="label" :value="value">
                </el-option>
            </el-select>
        </div>

        <!-- Font Style -->
        <div class="typography-control-section">
            <label for="font-style">字体样式</label>
            <el-select v-model="currentTypography.font_style" placeholder="默认">
                <el-option v-for="(label, value) in availableFontStyles" :key="value" :label="label" :value="value">
                </el-option>
            </el-select>
        </div>

        <!-- Line Height -->
        <div class="typography-control-section">
            <label for="line-height">行高</label>
            <div class="size-unit-input">
                <el-input-number
                    v-model="currentTypography.line_height.size"
                    :min="0"
                    :step="0.1"
                    controls-position="right"
                    placeholder="大小"
                    class="size-input"
                 />
                 <!-- 注意:行高单位通常用 em 或无单位,有时也用 px/% -->
                <el-select v-model="currentTypography.line_height.unit" class="unit-select">
                     <el-option label="-" value="" /> <!-- 无单位 -->
                     <el-option v-for="unit in ['px', 'em']" :key="unit" :label="unit" :value="unit">
                    </el-option>
                </el-select>
            </div>
        </div>

        <!-- Letter Spacing -->
        <div class="typography-control-section">
            <label for="letter-spacing">字间距</label>
            <div class="size-unit-input">
                <el-input-number
                    v-model="currentTypography.letter_spacing.size"
                    :min="-5" <!-- 允许负值 -->
                    :step="0.1"
                    controls-position="right"
                    placeholder="大小"
                    class="size-input"
                 />
                <el-select v-model="currentTypography.letter_spacing.unit" class="unit-select">
                     <el-option v-for="unit in ['px', 'em']" :key="unit" :label="unit" :value="unit">
                    </el-option>
                </el-select>
            </div>
        </div>

        <!-- Color Picker -->
        <div class="typography-control-section">
            <label for="color">文本颜色</label>
            <el-color-picker v-model="currentTypography.color" show-alpha></el-color-picker> <!-- 允许选择透明度 -->
        </div>

        <!-- Reset Button (Optional but Recommended) -->
         <div class="typography-control-section reset-section">
             <el-button size="small" @click="resetToDefaults">重置</el-button>
         </div>

    </div>
</template>

<script>
export default {
    name: 'TypographyControl',
    props: {
        // 使用 v-model 接收和发送设置对象
        value: {
            type: Object,
            default: () => ({}), // 父组件传入的当前值
        },
        // 默认设置,可以由父组件覆盖,或者从 PHP 传入
        defaultSettings: {
            type: Object,
            default: () => ({
                font_family: 'Arial',
                font_size: { size: 16, unit: 'px' },
                font_weight: '400',
                text_transform: '',
                font_style: 'normal',
                line_height: { size: 1.5, unit: '' }, // 行高常用无单位
                letter_spacing: { size: 0, unit: 'px' },
                color: '#333333',
            }),
        },
        // 字体列表等选项可以作为 prop 传入,增加灵活性
        availableFonts: {
            type: Array,
            default: () => ['Arial', 'Helvetica', 'Times New Roman', 'Verdana', 'Georgia', 'Tahoma', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'sans-serif'], // 包含一些常用系统字体
        },
        availableUnits: {
            type: Array,
            default: () => ['px', 'em', 'rem', '%', 'vw', 'vh'], // 添加更多常用单位
        },
        availableFontWeights: {
            type: Object,
            default: () => ({
                '': '默认',
                '100': 'Thin (100)', '200': 'Extra Light (200)', '300': 'Light (300)',
                '400': 'Normal (400)', '500': 'Medium (500)', '600': 'Semi Bold (600)',
                '700': 'Bold (700)', '800': 'Extra Bold (800)', '900': 'Black (900)',
            }),
        },
        availableTextTransforms: {
            type: Object,
            default: () => ({ '': '默认', 'uppercase': '大写', 'lowercase': '小写', 'capitalize': '首字母大写', 'none': '无' }),
        },
        availableFontStyles: {
             type: Object,
            default: () => ({ '': '默认', 'normal': 'Normal', 'italic': 'Italic', 'oblique': 'Oblique' }),
        }
    },
    data() {
        return {
            // 内部状态,混合默认值和传入值
            currentTypography: this.getInitialTypography(),
        };
    },
    watch: {
        // 监听外部传入的 value 变化,更新内部状态
        value: {
            deep: true,
            handler(newVal) {
                // 使用 structuredClone 避免引用问题,或进行深合并
                this.currentTypography = this.mergeDeep(JSON.parse(JSON.stringify(this.defaultSettings)), newVal || {});
            },
        },
        // 监听内部状态 currentTypography 的变化,通知父组件
        currentTypography: {
            deep: true,
            handler(newVal) {
                // 使用 'input' 事件配合 v-model
                this.$emit('input', newVal);
            },
        },
        // 如果 defaultSettings 可能动态改变,也需要监听
        defaultSettings: {
            deep: true,
            handler() {
                 // 如果当前值就是默认值(或未被用户修改过),则随着默认值的变化而变化
                 // 这个逻辑比较复杂,看实际需求。简单处理是:默认值变化不影响已设置的值。
                 // 或者提供一个重置按钮,让用户可以应用新的默认值。
                 // 这里我们只在初始化时使用 defaultSettings。
            }
        }
    },
    methods: {
        getInitialTypography() {
            // 深合并默认值和传入值,避免对象引用问题
             return this.mergeDeep(JSON.parse(JSON.stringify(this.defaultSettings)), this.value || {});
        },
         // 简易的深合并函数(生产环境建议用 lodash.merge 或类似库)
        mergeDeep(target, source) {
            for (const key in source) {
                if (Object.prototype.hasOwnProperty.call(source, key)) {
                    const targetValue = target[key];
                    const sourceValue = source[key];
                    if (sourceValue !== null && typeof sourceValue === 'object' && !Array.isArray(sourceValue)) {
                        // 如果目标值不是对象,则用源对象覆盖
                        if (typeof targetValue !== 'object' || targetValue === null || Array.isArray(targetValue)) {
                             target[key] = {};
                         }
                         this.mergeDeep(target[key], sourceValue);
                     } else {
                        target[key] = sourceValue;
                    }
                }
             }
            return target;
         },
         // 重置为默认设置
         resetToDefaults() {
             // 深拷贝默认设置,避免修改原始默认值
             this.currentTypography = JSON.parse(JSON.stringify(this.defaultSettings));
             // 这里会触发 watcher,自动 emit('input', ...)
         }
    },
    created() {
        // 组件创建时,确保初始值正确设置
        this.currentTypography = this.getInitialTypography();
    }
};
</script>

<style scoped>
/* 添加一些简单的样式让控件看起来更规整 */
.typography-control-section {
    margin-bottom: 15px;
}
.typography-control-section label {
    display: block;
    margin-bottom: 5px;
    font-weight: 500;
    font-size: 13px;
    color: #606266;
}
.size-unit-input {
    display: flex;
    align-items: center;
}
.size-unit-input .size-input {
    flex-grow: 1;
    margin-right: 8px; /* 给单位选择器留点空间 */
     width: auto; /* 覆盖 Element UI 可能设置的固定宽度 */
}
/* 调整 InputNumber 内部宽度 */
.size-unit-input .size-input .el-input-number__input {
     padding-left: 5px;
     padding-right: 35px; /* 根据 controls 位置调整 */
}
.size-unit-input .unit-select {
    width: 70px; /* 固定单位选择器的宽度 */
    flex-shrink: 0;
}
.el-select {
    width: 100%; /* 让下拉选择器占满容器宽度 */
}
.reset-section {
     text-align: right; /* 重置按钮放右边 */
     margin-top: 20px;
}
</style>

代码说明:

  • v-model 支持: 组件通过 props.value 接收设置,通过 this.$emit('input', newValue) 发送更新,完美配合 v-model
  • 内部状态 currentTypography 组件内部维护一个状态,初始值混合了 defaultSettings 和传入的 value。所有控件都绑定到 currentTypography 的对应字段。
  • watch 监听:
    • 监听 value prop 的深度变化,当父组件修改了值,同步更新内部 currentTypography
    • 监听内部 currentTypography 的深度变化,当用户操作控件导致它改变时,通过 $emit('input', ...) 通知父组件。
  • 数据处理: 使用了简单的深合并 mergeDeep (注意其局限性) 和 JSON.parse(JSON.stringify(...)) 进行深拷贝,避免对象引用的坑。
  • 选项作为 Props: 字体列表 (availableFonts)、单位 (availableUnits) 等作为 props 传入,提高了组件的灵活性。你可以从 PHP 传递这些选项。
  • 用户体验改进:
    • 字体选择器加上了 filterable, allow-create, default-first-option,更方便。
    • InputNumber 的 controls-position="right" 让加减按钮在右侧。
    • 提供了可选的重置按钮。
  • 样式 (<style scoped>): 添加了一些基本的 CSS,让布局看起来舒服点。scoped 确保样式只作用于本组件。

2. 在 WordPress 后台使用该组件

要在你的插件设置页面使用这个 TypographyControl 组件,你需要做几件事:

  1. 注册 Vue 组件: 确保你的构建工具 (Webpack, Vite 等) 能找到并打包 TypographyControl.vue,并且在你的主 Vue 实例或相关页面中全局或局部注册了这个组件。

    // 在你的 Vue 入口文件或相关页面组件中
    import TypographyControl from './components/TypographyControl.vue';
    
    // 全局注册 (如果多处使用)
    // Vue.component('typography-control', TypographyControl);
    
    // 或者在父组件中局部注册
    export default {
      components: {
        TypographyControl
      },
      data() {
        return {
          // 这个 typographySettings 会从 WordPress 后端获取,或者使用初始值
          typographySettings: {
            font_family: 'Georgia',
            font_size: { size: 18, unit: 'px' },
            // ... 其他设置
          },
          // (可选) 如果你的字体、单位等选项是动态的,也要从后端获取
           dynamicOptions: {
             fonts: ['Arial', 'Verdana', /* ... maybe fetched fonts */],
             // ... 其他选项
           }
        };
      },
      methods: {
          saveSettings() {
              // 这里调用 AJAX 或其他方法将 this.typographySettings 发送回 PHP 保存
              console.log('Saving:', this.typographySettings);
              // 例如使用 wp.ajax.post(...)
              wp.ajax.post('save_my_plugin_typography', {
                   _ajax_nonce: myPluginData.nonce, // 确保安全
                   settings: this.typographySettings
              }).done(function(response) {
                   console.log('Settings saved!', response);
                   // 可以显示保存成功的提示
              }).fail(function(error) {
                   console.error('Failed to save settings:', error);
              });
          }
      }
    }
    
  2. 在模板中使用: 在需要显示排版控件的地方,像使用普通 Element UI 组件一样使用它,并用 v-model 绑定到一个数据属性上(比如上面例子中的 typographySettings)。

    <template>
      <div>
        <h3>插件标题排版设置</h3>
        <typography-control
            v-model="typographySettings"
            :default-settings="/* 可选:传入特定的默认值 */"
            :available-fonts="dynamicOptions.fonts"
             /* :available-units="..." etc. */
        />
    
        <!-- 其他设置... -->
    
        <el-button type="primary" @click="saveSettings">保存设置</el-button>
    
         <!-- 预览区域 (可选) -->
         <div :style="computedStyleObject">预览文本:这是应用排版样式的效果</div>
      </div>
    </template>
    
    <script>
    // ... 接上面的 script 部分 ...
     export default {
        // ...
        computed: {
            // (可选) 根据 typographySettings 动态生成内联样式,用于预览
            computedStyleObject() {
                const style = {};
                const typo = this.typographySettings;
    
                if (typo.font_family) style.fontFamily = typo.font_family;
                if (typo.font_size && typo.font_size.size !== null) {
                    style.fontSize = `${typo.font_size.size}${typo.font_size.unit}`;
                }
                if (typo.font_weight) style.fontWeight = typo.font_weight;
                if (typo.text_transform) style.textTransform = typo.text_transform;
                if (typo.font_style) style.fontStyle = typo.font_style;
                 if (typo.line_height && typo.line_height.size !== null) {
                     style.lineHeight = `${typo.line_height.size}${typo.line_height.unit || ''}`; // 单位可能为空
                }
                if (typo.letter_spacing && typo.letter_spacing.size !== null) {
                     style.letterSpacing = `${typo.letter_spacing.size}${typo.letter_spacing.unit}`;
                 }
                if (typo.color) style.color = typo.color;
    
                return style;
            }
         }
    }
    </script>
    
  3. PHP 后端交互 (Typography.php 及相关逻辑):

    • 传递数据给 Vue: 你的 PHP Typography 类(或类似的机制)需要负责从 WordPress 数据库加载已保存的排版设置,并将这些设置以及可选的字体列表、单位等选项,通过 wp_localize_script 或直接嵌入页面的方式传递给 JavaScript。这样,Vue 实例创建时就能拿到初始数据。

      <?php
      // 在你的插件加载后台设置页面的 PHP 代码中
      
      // 1. 注册和入队你的 Vue App 的 JS 和 CSS 文件
      // wp_enqueue_script('my-plugin-vue-app', 'path/to/your/app.js', ['wp-element', 'wp-api-fetch'], '1.0.0', true);
      // wp_enqueue_style('my-plugin-vue-app-css', 'path/to/your/app.css');
      
      // 2. 使用 wp_localize_script 传递数据
      // 假设你的 Typography 字段类实例叫 $typography_field
      // 并且你的 Field 基类能提供获取设置值的方法 get_value()
      
      // 获取当前保存的值,如果没保存,则用 PHP 中定义的默认值
      $saved_settings = get_option('lifeline_donation_pro_typography', null);
       // 如果是首次加载或没有值,确保传递一个有效的结构,可能基于PHP的defaultSettings
       if ($saved_settings === null) {
           // 这里应该使用你的 PHP 类中定义的 defaultSettings
           // 确保你的 PHP 类能方便地访问到这些默认值
           // 假设你的 $typography_field 实例可以访问它们
           $field_definition = new WebinaneCommerce\Fields\Typography('typography_setting_name'); // 假设构造函数需要字段名
           $initial_settings = $field_definition->jsonSerialize()['defaultSettings']; // 获取默认值
           // 可能需要处理 PHP null 到 JS 期望的空字符串或特定值的转换
      } else {
          // 确保 $saved_settings 是数组或对象格式
          $initial_settings = is_array($saved_settings) ? $saved_settings : json_decode($saved_settings, true);
           // 这里也可能需要合并/验证,确保结构完整
           $field_definition = new WebinaneCommerce\Fields\Typography('typography_setting_name');
          $default_settings_from_php = $field_definition->jsonSerialize()['defaultSettings'];
           $initial_settings = array_merge($default_settings_from_php, $initial_settings ?: []);
       }
      
      $options_from_php = $field_definition->jsonSerialize(); // 包含 units, fontWeights 等
      
      wp_localize_script('my-plugin-vue-app', 'myPluginData', [
          'typography_initial_settings' => $initial_settings,
          'typography_options' => [ // 把字体列表、单位等也传过去
               'fonts' => ['Arial', 'Verdana'], // 可以从PHP动态生成或固定
               'units' => $options_from_php['units'],
               'fontWeights' => $options_from_php['fontWeights'],
               'textTransforms' => $options_from_php['textTransforms'],
               'fontStyles' => $options_from_php['fontStyles'],
               // ... 也可以传 PHP 的默认设置对象 $options_from_php['defaultSettings']
               'defaultSettings' => $options_from_php['defaultSettings']
          ],
          'nonce' => wp_create_nonce('wp_rest'), // 或自定义 nonce 用于 AJAX 保存
          'ajax_url' => admin_url('admin-ajax.php') // 如果用 admin-ajax
      ]);
      
      // 3. 在页面上准备一个根元素供 Vue 挂载
      // echo '<div id="my-plugin-settings-app"></div>';
      
      // 4. 你的 Vue app 初始化代码应该读取 myPluginData
      // new Vue({ el: '#my-plugin-settings-app', data: { typographySettings: myPluginData.typography_initial_settings, ... } });
      
    • 保存数据: 你需要设置一个 WordPress AJAX action 或者 REST API endpoint,当 Vue 应用发送保存请求时,由 PHP 代码接收 typographySettings 对象,做安全检查(比如 check_ajax_referer 或权限检查),然后用 update_option() 将其保存到数据库。你的 saveTypographySettings 方法就是干这个的。

      <?php
      // 在你的插件的 PHP 文件中
      
      // 注册 AJAX action (如果用 admin-ajax)
      add_action('wp_ajax_save_my_plugin_typography', 'handle_save_typography');
      
      function handle_save_typography() {
           // 1. 安全检查: Nonce 和权限
          check_ajax_referer('wp_rest', '_ajax_nonce'); // 检查 Nonce
           if (!current_user_can('manage_options')) { // 检查用户权限
               wp_send_json_error(['message' => '权限不足'], 403);
               return;
           }
      
          // 2. 获取并清理数据
           // 假设数据在 'settings' 参数中,并且是 JSON 字符串或数组
          $raw_settings = isset($_POST['settings']) ? $_POST['settings'] : null;
           if (is_string($raw_settings)) {
               $settings = json_decode(stripslashes($raw_settings), true); // 解码 JSON
           } elseif (is_array($raw_settings)) {
               $settings = $raw_settings; // 已经是数组
           } else {
               wp_send_json_error(['message' => '无效的设置数据'], 400);
               return;
           }
      
           // 3. (可选) 数据校验/净化
           // 你可以写一个函数来验证 $settings 对象的结构和每个字段的值是否符合预期
           // 例如: ensure_typography_settings_valid($settings);
      
          // 4. 保存到数据库
          // 使用你在 PHP 类中定义的方法,或者直接用 update_option
          // 确保键名 'lifeline_donation_pro_typography' 正确
          $result = update_option('lifeline_donation_pro_typography', $settings);
      
          if ($result) {
              wp_send_json_success(['message' => '设置已保存']);
           } else {
               // 注意:如果新值和旧值一样,update_option 返回 false,不代表失败
               // 最好比较一下 $settings 和 get_option 的值来判断是否真的出错了
               // 或者干脆直接返回成功,因为数据“现在”就是这个值了
               wp_send_json_success(['message' => '设置已更新(或未改变)']);
               // wp_send_json_error(['message' => '保存设置失败'], 500); // 只有在确定出错时
           }
      }
      

3. 应用排版样式

拿到排版设置 (typographySettings 对象) 后,怎么把它真正应用到插件的前端元素上呢?

  • 后端生成 CSS: 在插件输出前端 HTML 时,PHP 可以读取保存的排版设置,生成一段 <style> 标签或者内联 style 属性,直接应用样式。

  • CSS 自定义属性 (CSS Variables): 这是更现代和灵活的方式。

    1. PHP 读取设置,并生成 CSS 自定义属性的定义,通常放在 :root 或者插件的顶层容器上。

      <?php
      // 在插件前端输出 HTML 或 CSS 的地方
      $settings = get_option('lifeline_donation_pro_typography', $defaults); // 获取设置
      
      echo '<style>';
      echo ':root { /* 或者插件的特定选择器 .my-plugin-wrapper */';
       if (!empty($settings['font_family'])) echo '--my-plugin-font-family: ' . esc_attr($settings['font_family']) . ';';
       if (!empty($settings['color'])) echo '--my-plugin-text-color: ' . esc_attr($settings['color']) . ';';
       if (!empty($settings['font_size']['size'])) echo '--my-plugin-font-size: ' . esc_attr($settings['font_size']['size']) . esc_attr($settings['font_size']['unit']) . ';';
      if (!empty($settings['line_height']['size'])) echo '--my-plugin-line-height: ' . esc_attr($settings['line_height']['size']) . esc_attr($settings['line_height']['unit']) . ';'; // 单位可能为空
      // ... 为其他所有设置生成 CSS 变量 ...
      echo '}';
       echo '</style>';
      ?>
      
    2. 在你的插件的常规 CSS 文件里,使用这些变量。

      /* 在你的插件 CSS 文件里 */
      .my-plugin-title {
        font-family: var(--my-plugin-font-family, sans-serif); /* 提供默认值 */
        color: var(--my-plugin-text-color, #333);
        font-size: var(--my-plugin-font-size, 1.5em);
        line-height: var(--my-plugin-line-height, 1.4);
        /* ... 应用其他变量 ... */
      }
      
      .my-plugin-description {
         /* 可能使用不同的变量或继承 */
         font-size: var(--my-plugin-body-font-size, 1em); /* 举例,可以定义多套变量 */
      }
      

    这样做的好处是,你只需要输出变量定义,而具体样式规则都在 CSS 文件里,结构清晰,而且方便用浏览器的开发者工具调试。

总结一下关键步骤

  1. 创建一个 Vue 组件 TypographyControl.vue ,组合 Element UI 的表单控件,用 v-model 处理数据绑定。
  2. 在 WordPress 后台页面正确加载和使用这个 Vue 组件 ,确保数据能从 PHP 传到 Vue (用 wp_localize_script),并且 Vue 的修改能发送回 PHP (用 AJAX 或 REST API)。
  3. PHP 后端要处理数据的加载和保存 (get_option, update_option),并配合前端的 AJAX 请求。
  4. 选择一种方式 (后端生成 CSS 或 CSS 变量) 将用户选择的排版设置应用到插件的实际前端显示上。

你的 Vue 代码起点不错,主要问题可能出在组件的注册、挂载,以及 PHP 和 Vue 之间的数据流通环节。仔细检查这些集成点,特别是 wp_localize_script 是否正确传递了初始数据,以及 Vue 应用是否能正确读取并使用这些数据。另外,浏览器开发者工具的 Console 和 Network 面板是你调试这些问题的最好帮手。