文章目录
-
- 在当今数字化时代,许多中小型企业需要为其WordPress网站添加特定功能,但又不想投入大量资金开发完整插件。本教程将指导您创建一个支持客户自助设计的小批量定制插件,让用户能够通过简单界面自定义网站功能。 核心需求: 允许用户通过前端界面自定义内容展示 支持小批量配置(如颜色、文本、布局等) 数据安全且不影响网站性能 易于非技术人员操作
- 首先,我们创建插件的基本文件结构: <?php /** * Plugin Name: 客户自助设计工具 * Plugin URI: https://yourwebsite.com/ * Description: 允许客户通过前端界面自定义网站元素的小批量定制插件 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('CSD_PLUGIN_PATH', plugin_dir_path(__FILE__)); define('CSD_PLUGIN_URL', plugin_dir_url(__FILE__)); define('CSD_VERSION', '1.0.0'); // 初始化插件 class ClientSelfDesign { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->init_hooks(); } private function init_hooks() { // 注册激活和停用钩子 register_activation_hook(__FILE__, array($this, 'activate')); register_deactivation_hook(__FILE__, array($this, 'deactivate')); // 初始化插件功能 add_action('init', array($this, 'init')); // 添加管理菜单 add_action('admin_menu', array($this, 'add_admin_menu')); // 注册短代码 add_shortcode('csd_designer', array($this, 'render_designer_shortcode')); // 注册AJAX处理 add_action('wp_ajax_csd_save_design', array($this, 'save_design')); add_action('wp_ajax_nopriv_csd_save_design', array($this, 'save_design')); } public function activate() { // 创建数据库表 $this->create_database_table(); // 设置默认选项 add_option('csd_default_settings', array( 'primary_color' => '#3498db', 'secondary_color' => '#2ecc71', 'max_designs_per_user' => 5, 'allow_public_designs' => true )); } public function deactivate() { // 清理临时数据 delete_transient('csd_recent_designs'); } public function init() { // 加载文本域 load_plugin_textdomain('csd', false, dirname(plugin_basename(__FILE__)) . '/languages'); } // 其他方法将在下面实现 } // 启动插件 ClientSelfDesign::get_instance(); ?>
- 接下来,我们创建存储用户设计的数据库表: // 在ClientSelfDesign类中添加以下方法 private function create_database_table() { global $wpdb; $table_name = $wpdb->prefix . 'csd_designs'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, design_name varchar(100) NOT NULL, design_data longtext NOT NULL, design_type varchar(50) DEFAULT 'basic', is_public tinyint(1) DEFAULT 0, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY user_id (user_id), KEY design_type (design_type) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 添加版本号,便于后续升级 add_option('csd_db_version', '1.0'); }
- 创建用户自助设计的前端界面: // 在ClientSelfDesign类中添加以下方法 public function render_designer_shortcode($atts) { // 短代码属性处理 $atts = shortcode_atts(array( 'type' => 'basic', 'max_elements' => 10 ), $atts, 'csd_designer'); // 检查用户权限 if (!is_user_logged_in()) { return '<div class="csd-login-required">' . __('请先登录以使用设计工具', 'csd') . wp_login_url(get_permalink()) . '</div>'; } // 获取用户现有设计数量 $user_id = get_current_user_id(); $design_count = $this->get_user_design_count($user_id); $max_designs = get_option('csd_default_settings')['max_designs_per_user']; if ($design_count >= $max_designs) { return '<div class="csd-limit-reached">' . sprintf(__('您已达到最大设计数量(%d),请删除一些旧设计以创建新设计', 'csd'), $max_designs) . '</div>'; } // 输出设计器HTML ob_start(); ?> <div id="csd-designer-container" class="csd-designer" data-type="<?php echo esc_attr($atts['type']); ?>"> <div class="csd-header"> <h2><?php _e('自助设计工具', 'csd'); ?></h2> <div class="csd-design-count"> <?php printf(__('已使用: %d/%d', 'csd'), $design_count, $max_designs); ?> </div> </div> <div class="csd-toolbar"> <div class="csd-tool-group"> <button type="button" class="csd-tool-btn" data-tool="text"> <span class="dashicons dashicons-editor-textcolor"></span> <?php _e('添加文本', 'csd'); ?> </button> <button type="button" class="csd-tool-btn" data-tool="image"> <span class="dashicons dashicons-format-image"></span> <?php _e('添加图片', 'csd'); ?> </button> <button type="button" class="csd-tool-btn" data-tool="shape"> <span class="dashicons dashicons-forms"></span> <?php _e('添加形状', 'csd'); ?> </button> </div> <div class="csd-color-picker"> <label><?php _e('主题色:', 'csd'); ?></label> <input type="color" id="csd-primary-color" value="#3498db"> <input type="color" id="csd-secondary-color" value="#2ecc71"> </div> </div> <div class="csd-workspace"> <div class="csd-canvas" id="csd-design-canvas"> <!-- 设计元素将在这里动态添加 --> <div class="csd-default-message"> <?php _e('从工具栏选择工具开始设计', 'csd'); ?> </div> </div> <div class="csd-properties-panel"> <h3><?php _e('属性设置', 'csd'); ?></h3> <div id="csd-properties-form"> <!-- 属性表单将根据选中元素动态加载 --> </div> </div> </div> <div class="csd-footer"> <div class="csd-design-info"> <input type="text" id="csd-design-name" placeholder="<?php esc_attr_e('输入设计名称', 'csd'); ?>" maxlength="100"> <label> <input type="checkbox" id="csd-design-public"> <?php _e('公开此设计', 'csd'); ?> </label> </div> <div class="csd-actions"> <button type="button" id="csd-preview-btn" class="button"> <?php _e('预览', 'csd'); ?> </button> <button type="button" id="csd-save-btn" class="button button-primary"> <?php _e('保存设计', 'csd'); ?> </button> <button type="button" id="csd-reset-btn" class="button button-secondary"> <?php _e('重置', 'csd'); ?> </button> </div> </div> <!-- 预览模态框 --> <div id="csd-preview-modal" class="csd-modal" style="display:none;"> <div class="csd-modal-content"> <span class="csd-close-modal">×</span> <h3><?php _e('设计预览', 'csd'); ?></h3> <div id="csd-preview-content"></div> </div> </div> </div> <!-- 加载JavaScript --> <script type="text/javascript"> var csd_ajax_url = '<?php echo admin_url('admin-ajax.php'); ?>'; var csd_nonce = '<?php echo wp_create_nonce('csd_save_design_nonce'); ?>'; var csd_user_id = <?php echo $user_id; ?>; </script> <?php return ob_get_clean(); } // 获取用户设计数量 private function get_user_design_count($user_id) { global $wpdb; $table_name = $wpdb->prefix . 'csd_designs'; return $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE user_id = %d", $user_id )); }
- 创建前端交互逻辑: // 创建文件: assets/js/csd-designer.js jQuery(document).ready(function($) { 'use strict'; var CSD_Designer = { currentTool: null, selectedElement: null, designData: { elements: [], colors: { primary: '#3498db', secondary: '#2ecc71' }, settings: {} }, init: function() { this.bindEvents(); this.loadColorSettings(); }, bindEvents: function() { // 工具按钮点击 $('.csd-tool-btn').on('click', this.handleToolClick.bind(this)); // 颜色选择器变化 $('#csd-primary-color, #csd-secondary-color').on('change', this.handleColorChange.bind(this)); // 保存设计 $('#csd-save-btn').on('click', this.saveDesign.bind(this)); // 预览设计 $('#csd-preview-btn').on('click', this.previewDesign.bind(this)); // 重置设计 $('#csd-reset-btn').on('click', this.resetDesign.bind(this)); // 关闭模态框 $('.csd-close-modal').on('click', function() { $('#csd-preview-modal').hide(); }); }, handleToolClick: function(e) { var tool = $(e.currentTarget).data('tool'); this.currentTool = tool; switch(tool) { case 'text': this.addTextElement(); break; case 'image': this.addImageElement(); break; case 'shape': this.addShapeElement(); break; } }, addTextElement: function() { var elementId = 'element_' + Date.now(); var element = { id: elementId, type: 'text', content: '新文本', styles: { fontSize: '16px', color: this.designData.colors.primary, fontWeight: 'normal', textAlign: 'left' }, position: { x: 50, y: 50 } }; this.designData.elements.push(element); this.renderElement(element); this.selectElement(elementId); }, addImageElement: function() { // 创建图片上传逻辑 this.openMediaUploader(); }, addShapeElement: function() { var elementId = 'element_' + Date.now(); var element = { id: elementId, type: 'shape', shapeType: 'rectangle', styles: { backgroundColor: this.designData.colors.secondary, width: '100px', height: '100px', borderRadius: '0px' }, position: { x: 100, y: 100 } }; this.designData.elements.push(element); this.renderElement(element); this.selectElement(elementId); }, renderElement: function(element) { var $canvas = $('#csd-design-canvas'); var $element = $('<div class="csd-element"></div>') .attr('id', element.id) .css({ position: 'absolute', left: element.position.x + 'px', top: element.position.y + 'px' }) .data('element-data', element); switch(element.type) { case 'text': $element.addClass('csd-text-element') .text(element.content) .css(element.styles); break; case 'shape': $element.addClass('csd-shape-element') .css(element.styles); break; } $element.on('click', this.handleElementClick.bind(this)); $canvas.append($element); }, handleElementClick: function(e) { var elementId = $(e.currentTarget).attr('id'); this.selectElement(elementId); }, selectElement: function(elementId) { this.selectedElement = elementId; $('.csd-element').removeClass('selected'); $('#' + elementId).addClass('selected'); this.loadPropertiesForm(elementId); }, loadPropertiesForm: function(elementId) { var element = this.getElementById(elementId); if (!element) return; var formHtml = ''; switch(element.type) { case 'text': formHtml = this.getTextPropertiesForm(element); break; case 'shape': formHtml = this.getShapePropertiesForm(element); break; } $('#csd-properties-form').html(formHtml); this.bindPropertiesEvents(); }, getTextPropertiesForm: function(element) { return ` <div class="csd-property-group"> <label>文本内容:</label> <input type="text" class="csd-property-input" data-property="content" value="${element.content}"> </div> <div class="csd-property-group"> <label>字体大小:</label> <input type="range" class="csd-property-input" data-property="fontSize" min="8" max="72" value="${parseInt(element.styles.fontSize)}"> <span class="csd-property-value">${element.styles.fontSize}</span> </div> <div class="csd-property-group"> <label>文字颜色:</label> <input type="color" class="csd-property-input" data-property="color" value="${element.styles.color}"> </div> `; }, bindPropertiesEvents: function() { var self = this; $('.csd-property-input').on('input change', function() { var property = $(this).data('property'); var value = $(this).val(); if ($(this).attr('type') === 'range') { value = value + 'px'; $(this).siblings('.csd-property-value').text(value); } self.updateSelectedElement(property, value); }); }, updateSelectedElement: function(property, value) { if (!this.selectedElement) return; var element = this.getElementById(this.selectedElement); if (!element) return; if (property === 'content') { element.content = value; $('#' + this.selectedElement).text(value); } else { element.styles[property] = value; $('#' + this.selectedElement).css(property, value); } }, getElementById: function(elementId) { return this.designData.elements.find(function(el) { return el.id === elementId; }); }, handleColorChange: function(e) { var colorType = e.target.id.replace('csd-', '').replace('-color', ''); this.designData.colors[colorType] = $(e.target).val(); }, saveDesign: function() { var designName = $('#csd-design-name').val().trim(); var isPublic = $('#csd-design-public').is(':checked') ? 1 : 0; if (!designName) { alert('请输入设计名称'); return; } var designData = { name: designName, data: this.designData, is_public: isPublic }; $.ajax({ url: csd_ajax_url, type: 'POST', data: { action: 'csd_save_design', nonce: csd_nonce, user_id: csd_user_id, design_data: designData }, beforeSend: function() { true).text('保存中...'); }, success: function(response) { if (response.success) { alert('设计保存成功!'); // 刷新页面显示新设计 location.reload(); } else { alert('保存失败: ' + response.data); } }, error: function() { alert('网络错误,请重试'); }, complete: function() { $('#csd-save-btn').prop('disabled', false).text('保存设计'); } }); }, previewDesign: function() { var previewHtml = '<div class="csd-preview-container">'; this.designData.elements.forEach(function(element) { previewHtml += '<div class="csd-preview-element" style="'; previewHtml += 'position:absolute;'; previewHtml += 'left:' + element.position.x + 'px;'; previewHtml += 'top:' + element.position.y + 'px;'; for (var style in element.styles) { previewHtml += style + ':' + element.styles[style] + ';'; } previewHtml += '">'; previewHtml += element.content || ''; previewHtml += '</div>'; }); previewHtml += '</div>'; $('#csd-preview-content').html(previewHtml); $('#csd-preview-modal').show(); }, resetDesign: function() { if (confirm('确定要重置所有设计吗?')) { this.designData.elements = []; $('#csd-design-canvas').html('<div class="csd-default-message">从工具栏选择工具开始设计</div>'); $('#csd-properties-form').empty(); this.selectedElement = null; } }, loadColorSettings: function() { this.designData.colors.primary = $('#csd-primary-color').val(); this.designData.colors.secondary = $('#csd-secondary-color').val(); }, openMediaUploader: function() { var frame = wp.media({ title: '选择图片', multiple: false, library: { type: 'image' }, button: { text: '使用此图片' } }); frame.on('select', function() { var attachment = frame.state().get('selection').first().toJSON(); CSD_Designer.addImageFromMedia(attachment); }); frame.open(); }, addImageFromMedia: function(attachment) { var elementId = 'element_' + Date.now(); var element = { id: elementId, type: 'image', url: attachment.url, alt: attachment.alt, styles: { width: '200px', height: 'auto' }, position: { x: 150, y: 150 } }; this.designData.elements.push(element); var $canvas = $('#csd-design-canvas'); var $img = $('<img>') .attr('src', attachment.url) .attr('alt', attachment.alt) .css({ position: 'absolute', left: element.position.x + 'px', top: element.position.y + 'px', width: element.styles.width }) .addClass('csd-element csd-image-element') .attr('id', elementId) .data('element-data', element); $img.on('click', this.handleElementClick.bind(this)); $canvas.append($img); this.selectElement(elementId); } }; // 初始化设计器 CSD_Designer.init(); }); ## 六、后端数据处理与保存 实现AJAX保存功能: // 在ClientSelfDesign类中添加以下方法 public function save_design() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'csd_save_design_nonce')) { wp_die('安全验证失败'); } // 验证用户权限 $user_id = intval($_POST['user_id']); if ($user_id !== get_current_user_id()) { wp_die('用户权限错误'); } // 获取并清理数据 $design_data = json_decode(stripslashes($_POST['design_data']), true); if (!$design_data) { wp_send_json_error('无效的设计数据'); } // 验证设计数量限制 $max_designs = get_option('csd_default_settings')['max_designs_per_user']; $current_count = $this->get_user_design_count($user_id); if ($current_count >= $max_designs) { wp_send_json_error('已达到最大设计数量限制'); } // 保存到数据库 global $wpdb; $table_name = $wpdb->prefix . 'csd_designs'; $result = $wpdb->insert( $table_name, array( 'user_id' => $user_id, 'design_name' => sanitize_text_field($design_data['name']), 'design_data' => json_encode($design_data['data']), 'design_type' => 'custom', 'is_public' => $design_data['is_public'] ? 1 : 0 ), array('%d', '%s', '%s', '%s', '%d') ); if ($result) { // 清除缓存 delete_transient('csd_user_designs_' . $user_id); wp_send_json_success(array( 'design_id' => $wpdb->insert_id, 'message' => '设计保存成功' )); } else { wp_send_json_error('数据库保存失败'); } } // 添加获取用户设计的函数public function get_user_designs($user_id) { global $wpdb; $table_name = $wpdb->prefix . 'csd_designs'; $cache_key = 'csd_user_designs_' . $user_id; $designs = get_transient($cache_key); if (false === $designs) { $designs = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE user_id = %d ORDER BY created_at DESC", $user_id ), ARRAY_A); set_transient($cache_key, $designs, HOUR_IN_SECONDS); } return $designs; } ## 七、管理界面与样式优化 创建管理后台界面: // 在ClientSelfDesign类中添加以下方法 public function add_admin_menu() { add_menu_page( '客户设计管理', '客户设计', 'manage_options', 'csd-designs', array($this, 'render_admin_page'), 'dashicons-art', 30 ); add_submenu_page( 'csd-designs', '设计设置', '设置', 'manage_options', 'csd-settings', array($this, 'render_settings_page') ); } public function render_admin_page() { ?> <div class="wrap"> <h1><?php _e('客户设计管理', 'csd'); ?></h1> <div class="csd-admin-container"> <div class="csd-stats"> <?php global $wpdb; $table_name = $wpdb->prefix . 'csd_designs'; $total_designs = $wpdb->get_var("SELECT COUNT(*) FROM $table_name"); $total_users = $wpdb->get_var("SELECT COUNT(DISTINCT user_id) FROM $table_name"); $public_designs = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE is_public = 1"); ?> <div class="csd-stat-card"> <h3><?php echo $total_designs; ?></h3> <p>总设计数</p> </div> <div class="csd-stat-card"> <h3><?php echo $total_users; ?></h3> <p>使用用户数</p> </div> <div class="csd-stat-card"> <h3><?php echo $public_designs; ?></h3> <p>公开设计数</p> </div> </div> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>ID</th> <th>设计名称</th> <th>用户</th> <th>类型</th> <th>公开</th> <th>创建时间</th> <th>操作</th> </tr> </thead> <tbody> <?php $designs = $wpdb->get_results( "SELECT d.*, u.user_login FROM $table_name d LEFT JOIN {$wpdb->users} u ON d.user_id = u.ID ORDER BY d.created_at DESC LIMIT 50" ); foreach ($designs as $design) { ?> <tr> <td><?php echo $design->id; ?></td> <td><?php echo esc_html($design->design_name); ?></td> <td><?php echo $design->user_login ?: '游客'; ?></td> <td><?php echo $design->design_type; ?></td> <td><?php echo $design->is_public ? '是' : '否'; ?></td> <td><?php echo date('Y-m-d H:i', strtotime($design->created_at)); ?></td> <td> <button class="button button-small csd-preview-btn" data-design-id="<?php echo $design->id; ?>"> 预览 </button> <button class="button button-small button-link-delete csd-delete-btn" data-design-id="<?php echo $design->id; ?>"> 删除 </button> </td> </tr> <?php } ?> </tbody> </table> </div> </div> <?php } public function render_settings_page() { $settings = get_option('csd_default_settings', array()); if ($_SERVER['REQUEST_METHOD'] === 'POST' && check_admin_referer('csd_settings_update')) { $new_settings = array( 'primary_color' => sanitize_hex_color($_POST['primary_color']), 'secondary_color' => sanitize_hex_color($_POST['secondary_color']), 'max_designs_per_user' => intval($_POST['max_designs_per_user']), 'allow_public_designs' => isset($_POST['allow_public_designs']) ? true : false ); update_option('csd_default_settings', $new_settings); $settings = $new_settings; echo '<div class="notice notice-success"><p>设置已保存</p></div>'; } ?> <div class="wrap"> <h1><?php _e('设计工具设置', 'csd'); ?></h1> <form method="post" action=""> <?php wp_nonce_field('csd_settings_update'); ?> <table class="form-table"> <tr> <th scope="row">主色调</th> <td> <input type="color" name="primary_color" value="<?php echo esc_attr($settings['primary_color']); ?>"> </td> </tr> <tr> <th scope="row">辅色调</th> <td> <input type="color" name="secondary_color" value="<?php echo esc_attr($settings['secondary_color']); ?>"> </td> </tr> <tr> <th scope="row">用户最大设计数</th> <td> <input type="number" name="max_designs_per_user" value="<?php echo esc_attr($settings['max_designs_per_user']); ?>" min="1" max="50"> <p class="description">每个用户最多可以保存的设计数量</p> </td> </tr> <tr> <th scope="row">允许公开设计</th> <td> <label> <input type="checkbox" name="allow_public_designs" value="1" <?php checked($settings['allow_public_designs']); ?>> 允许用户将设计设为公开 </label> </td> </tr> </table> <?php submit_button(); ?> </form> </div> <?php } ## 八、CSS样式文件 创建插件样式文件: / 创建文件: assets/css/csd-designer.css / / 设计器主容器 /.csd-designer { background: #fff; border: 1px solid #ddd; border-radius: 4px; padding: 20px; margin: 20px 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } / 头部样式 /.csd-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid #f0f0f0; } .csd-header h2 { margin: 0; color: #333; } .csd-design-count { background: #f7f7f7; padding: 5px 10px; border-radius: 3px; font-size: 14px; color: #666; } / 工具栏样式 /.csd-toolbar { background: #f9f9f9; padding: 15px; border-radius: 4px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; } .csd-tool-group { display: flex; gap: 10px; } .csd-tool-btn { display: flex; align-items: center; gap: 5px; padding: 8px 15px; background: #fff; border: 1px solid #ddd; border-radius: 3px; cursor: pointer; transition: all 0.3s ease; } .csd-tool-btn:hover { background: #f0f0f0; border-color: #999; } .csd-tool-btn .dashicons { font-size: 16px; width: 16px; height: 16px; } .csd-color-picker { display: flex; align-items: center; gap: 10px; } .csd-color-picker label { font-weight: 600; color: #555; } .csd-color-picker input[type="color"] { width: 40px; height: 40px; border: 2px solid #ddd; border-radius: 4px; cursor: pointer; } / 工作区样式 /.csd-workspace { display: grid; grid-template-columns: 3fr 1fr; gap: 20px; min-height: 500px; } .csd-canvas { background: #f8f9fa; border: 2px dashed #dee2e6; border-radius: 4px; position: relative; min-height: 500px; } .csd-default-message { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #6c757d; font-size: 16px; } / 设计元素样式 /.csd-element { cursor: move; user-select: none; transition: all 0.2s ease; } .csd-element.selected { outline: 2px solid #007cba; outline-offset: 2px; } .csd-text-element { padding: 5px; min-width: 50px; min-height: 20px; } .csd-shape-element { background: #3498db; } / 属性面板样式 /.csd-properties-panel { background: #fff; border: 1px solid #ddd; border-radius: 4px; padding: 15px; } .csd-properties-panel h3 { margin-top: 0; margin-bottom: 15px; color: #333; font-size: 16px; } .csd-property-group { margin-bottom: 15px; } .csd-property-group label { display: block; margin-bottom: 5px; font-weight: 600; color: #555; font-size: 14px; } .csd-property-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px; font-size: 14px; } .csd-property-value { display: inline-block; margin-left: 10px; font-size: 14px; color: #666; } / 底部样式 /.csd-footer { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; } .csd-design-info { display: flex; gap: 15px; align-items: center; }
- padding: 8px 12px; border: 1px solid #ddd; border-radius: 3px; width: 200px; } .csd-design-info label { display: flex; align-items: center; gap: 5px; font-size: 14px; color: #555; } .csd-actions { display: flex; gap: 10px; } / 模态框样式 /.csd-modal { display: none; position: fixed; z-index: 1000; left
在当今数字化时代,许多中小型企业需要为其WordPress网站添加特定功能,但又不想投入大量资金开发完整插件。本教程将指导您创建一个支持客户自助设计的小批量定制插件,让用户能够通过简单界面自定义网站功能。
核心需求:
- 允许用户通过前端界面自定义内容展示
- 支持小批量配置(如颜色、文本、布局等)
- 数据安全且不影响网站性能
- 易于非技术人员操作
首先,我们创建插件的基本文件结构:
<?php
/**
* Plugin Name: 客户自助设计工具
* Plugin URI: https://yourwebsite.com/
* Description: 允许客户通过前端界面自定义网站元素的小批量定制插件
* Version: 1.0.0
* Author: 您的名称
* License: GPL v2 or later
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('CSD_PLUGIN_PATH', plugin_dir_path(__FILE__));
define('CSD_PLUGIN_URL', plugin_dir_url(__FILE__));
define('CSD_VERSION', '1.0.0');
// 初始化插件
class ClientSelfDesign {
private static $instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_hooks();
}
private function init_hooks() {
// 注册激活和停用钩子
register_activation_hook(__FILE__, array($this, 'activate'));
register_deactivation_hook(__FILE__, array($this, 'deactivate'));
// 初始化插件功能
add_action('init', array($this, 'init'));
// 添加管理菜单
add_action('admin_menu', array($this, 'add_admin_menu'));
// 注册短代码
add_shortcode('csd_designer', array($this, 'render_designer_shortcode'));
// 注册AJAX处理
add_action('wp_ajax_csd_save_design', array($this, 'save_design'));
add_action('wp_ajax_nopriv_csd_save_design', array($this, 'save_design'));
}
public function activate() {
// 创建数据库表
$this->create_database_table();
// 设置默认选项
add_option('csd_default_settings', array(
'primary_color' => '#3498db',
'secondary_color' => '#2ecc71',
'max_designs_per_user' => 5,
'allow_public_designs' => true
));
}
public function deactivate() {
// 清理临时数据
delete_transient('csd_recent_designs');
}
public function init() {
// 加载文本域
load_plugin_textdomain('csd', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
// 其他方法将在下面实现
}
// 启动插件
ClientSelfDesign::get_instance();
?>
接下来,我们创建存储用户设计的数据库表:
// 在ClientSelfDesign类中添加以下方法
private function create_database_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'csd_designs';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
design_name varchar(100) NOT NULL,
design_data longtext NOT NULL,
design_type varchar(50) DEFAULT 'basic',
is_public tinyint(1) DEFAULT 0,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY user_id (user_id),
KEY design_type (design_type)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
// 添加版本号,便于后续升级
add_option('csd_db_version', '1.0');
}
创建用户自助设计的前端界面:
// 在ClientSelfDesign类中添加以下方法
public function render_designer_shortcode($atts) {
// 短代码属性处理
$atts = shortcode_atts(array(
'type' => 'basic',
'max_elements' => 10
), $atts, 'csd_designer');
// 检查用户权限
if (!is_user_logged_in()) {
return '<div class="csd-login-required">' .
__('请先登录以使用设计工具', 'csd') .
wp_login_url(get_permalink()) .
'</div>';
}
// 获取用户现有设计数量
$user_id = get_current_user_id();
$design_count = $this->get_user_design_count($user_id);
$max_designs = get_option('csd_default_settings')['max_designs_per_user'];
if ($design_count >= $max_designs) {
return '<div class="csd-limit-reached">' .
sprintf(__('您已达到最大设计数量(%d),请删除一些旧设计以创建新设计', 'csd'), $max_designs) .
'</div>';
}
// 输出设计器HTML
ob_start();
?>
<div id="csd-designer-container" class="csd-designer" data-type="<?php echo esc_attr($atts['type']); ?>">
<div class="csd-header">
<h2><?php _e('自助设计工具', 'csd'); ?></h2>
<div class="csd-design-count">
<?php printf(__('已使用: %d/%d', 'csd'), $design_count, $max_designs); ?>
</div>
</div>
<div class="csd-toolbar">
<div class="csd-tool-group">
<button type="button" class="csd-tool-btn" data-tool="text">
<span class="dashicons dashicons-editor-textcolor"></span>
<?php _e('添加文本', 'csd'); ?>
</button>
<button type="button" class="csd-tool-btn" data-tool="image">
<span class="dashicons dashicons-format-image"></span>
<?php _e('添加图片', 'csd'); ?>
</button>
<button type="button" class="csd-tool-btn" data-tool="shape">
<span class="dashicons dashicons-forms"></span>
<?php _e('添加形状', 'csd'); ?>
</button>
</div>
<div class="csd-color-picker">
<label><?php _e('主题色:', 'csd'); ?></label>
<input type="color" id="csd-primary-color" value="#3498db">
<input type="color" id="csd-secondary-color" value="#2ecc71">
</div>
</div>
<div class="csd-workspace">
<div class="csd-canvas" id="csd-design-canvas">
<!-- 设计元素将在这里动态添加 -->
<div class="csd-default-message">
<?php _e('从工具栏选择工具开始设计', 'csd'); ?>
</div>
</div>
<div class="csd-properties-panel">
<h3><?php _e('属性设置', 'csd'); ?></h3>
<div id="csd-properties-form">
<!-- 属性表单将根据选中元素动态加载 -->
</div>
</div>
</div>
<div class="csd-footer">
<div class="csd-design-info">
<input type="text" id="csd-design-name"
placeholder="<?php esc_attr_e('输入设计名称', 'csd'); ?>"
maxlength="100">
<label>
<input type="checkbox" id="csd-design-public">
<?php _e('公开此设计', 'csd'); ?>
</label>
</div>
<div class="csd-actions">
<button type="button" id="csd-preview-btn" class="button">
<?php _e('预览', 'csd'); ?>
</button>
<button type="button" id="csd-save-btn" class="button button-primary">
<?php _e('保存设计', 'csd'); ?>
</button>
<button type="button" id="csd-reset-btn" class="button button-secondary">
<?php _e('重置', 'csd'); ?>
</button>
</div>
</div>
<!-- 预览模态框 -->
<div id="csd-preview-modal" class="csd-modal" style="display:none;">
<div class="csd-modal-content">
<span class="csd-close-modal">×</span>
<h3><?php _e('设计预览', 'csd'); ?></h3>
<div id="csd-preview-content"></div>
</div>
</div>
</div>
<!-- 加载JavaScript -->
<script type="text/javascript">
var csd_ajax_url = '<?php echo admin_url('admin-ajax.php'); ?>';
var csd_nonce = '<?php echo wp_create_nonce('csd_save_design_nonce'); ?>';
var csd_user_id = <?php echo $user_id; ?>;
</script>
<?php
return ob_get_clean();
}
// 获取用户设计数量
private function get_user_design_count($user_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'csd_designs';
return $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table_name WHERE user_id = %d",
$user_id
));
}
创建前端交互逻辑:
// 创建文件: assets/js/csd-designer.js
jQuery(document).ready(function($) {
'use strict';
var CSD_Designer = {
currentTool: null,
selectedElement: null,
designData: {
elements: [],
colors: {
primary: '#3498db',
secondary: '#2ecc71'
},
settings: {}
},
init: function() {
this.bindEvents();
this.loadColorSettings();
},
bindEvents: function() {
// 工具按钮点击
$('.csd-tool-btn').on('click', this.handleToolClick.bind(this));
// 颜色选择器变化
$('#csd-primary-color, #csd-secondary-color').on('change', this.handleColorChange.bind(this));
// 保存设计
$('#csd-save-btn').on('click', this.saveDesign.bind(this));
// 预览设计
$('#csd-preview-btn').on('click', this.previewDesign.bind(this));
// 重置设计
$('#csd-reset-btn').on('click', this.resetDesign.bind(this));
// 关闭模态框
$('.csd-close-modal').on('click', function() {
$('#csd-preview-modal').hide();
});
},
handleToolClick: function(e) {
var tool = $(e.currentTarget).data('tool');
this.currentTool = tool;
switch(tool) {
case 'text':
this.addTextElement();
break;
case 'image':
this.addImageElement();
break;
case 'shape':
this.addShapeElement();
break;
}
},
addTextElement: function() {
var elementId = 'element_' + Date.now();
var element = {
id: elementId,
type: 'text',
content: '新文本',
styles: {
fontSize: '16px',
color: this.designData.colors.primary,
fontWeight: 'normal',
textAlign: 'left'
},
position: { x: 50, y: 50 }
};
this.designData.elements.push(element);
this.renderElement(element);
this.selectElement(elementId);
},
addImageElement: function() {
// 创建图片上传逻辑
this.openMediaUploader();
},
addShapeElement: function() {
var elementId = 'element_' + Date.now();
var element = {
id: elementId,
type: 'shape',
shapeType: 'rectangle',
styles: {
backgroundColor: this.designData.colors.secondary,
width: '100px',
height: '100px',
borderRadius: '0px'
},
position: { x: 100, y: 100 }
};
this.designData.elements.push(element);
this.renderElement(element);
this.selectElement(elementId);
},
renderElement: function(element) {
var $canvas = $('#csd-design-canvas');
var $element = $('<div class="csd-element"></div>')
.attr('id', element.id)
.css({
position: 'absolute',
left: element.position.x + 'px',
top: element.position.y + 'px'
})
.data('element-data', element);
switch(element.type) {
case 'text':
$element.addClass('csd-text-element')
.text(element.content)
.css(element.styles);
break;
case 'shape':
$element.addClass('csd-shape-element')
.css(element.styles);
break;
}
$element.on('click', this.handleElementClick.bind(this));
$canvas.append($element);
},
handleElementClick: function(e) {
var elementId = $(e.currentTarget).attr('id');
this.selectElement(elementId);
},
selectElement: function(elementId) {
this.selectedElement = elementId;
$('.csd-element').removeClass('selected');
$('#' + elementId).addClass('selected');
this.loadPropertiesForm(elementId);
},
loadPropertiesForm: function(elementId) {
var element = this.getElementById(elementId);
if (!element) return;
var formHtml = '';
switch(element.type) {
case 'text':
formHtml = this.getTextPropertiesForm(element);
break;
case 'shape':
formHtml = this.getShapePropertiesForm(element);
break;
}
$('#csd-properties-form').html(formHtml);
this.bindPropertiesEvents();
},
getTextPropertiesForm: function(element) {
return `
<div class="csd-property-group">
<label>文本内容:</label>
<input type="text" class="csd-property-input"
data-property="content"
value="${element.content}">
</div>
<div class="csd-property-group">
<label>字体大小:</label>
<input type="range" class="csd-property-input"
data-property="fontSize" min="8" max="72"
value="${parseInt(element.styles.fontSize)}">
<span class="csd-property-value">${element.styles.fontSize}</span>
</div>
<div class="csd-property-group">
<label>文字颜色:</label>
<input type="color" class="csd-property-input"
data-property="color"
value="${element.styles.color}">
</div>
`;
},
bindPropertiesEvents: function() {
var self = this;
$('.csd-property-input').on('input change', function() {
var property = $(this).data('property');
var value = $(this).val();
if ($(this).attr('type') === 'range') {
value = value + 'px';
$(this).siblings('.csd-property-value').text(value);
}
self.updateSelectedElement(property, value);
});
},
updateSelectedElement: function(property, value) {
if (!this.selectedElement) return;
var element = this.getElementById(this.selectedElement);
if (!element) return;
if (property === 'content') {
element.content = value;
$('#' + this.selectedElement).text(value);
} else {
element.styles[property] = value;
$('#' + this.selectedElement).css(property, value);
}
},
getElementById: function(elementId) {
return this.designData.elements.find(function(el) {
return el.id === elementId;
});
},
handleColorChange: function(e) {
var colorType = e.target.id.replace('csd-', '').replace('-color', '');
this.designData.colors[colorType] = $(e.target).val();
},
saveDesign: function() {
var designName = $('#csd-design-name').val().trim();
var isPublic = $('#csd-design-public').is(':checked') ? 1 : 0;
if (!designName) {
alert('请输入设计名称');
return;
}
var designData = {
name: designName,
data: this.designData,
is_public: isPublic
};
$.ajax({
url: csd_ajax_url,
type: 'POST',
data: {
action: 'csd_save_design',
nonce: csd_nonce,
user_id: csd_user_id,
design_data: designData
},
beforeSend: function() {
true).text('保存中...');
},
success: function(response) {
if (response.success) {
alert('设计保存成功!');
// 刷新页面显示新设计
location.reload();
} else {
alert('保存失败: ' + response.data);
}
},
error: function() {
alert('网络错误,请重试');
},
complete: function() {
$('#csd-save-btn').prop('disabled', false).text('保存设计');
}
});
},
previewDesign: function() {
var previewHtml = '<div class="csd-preview-container">';
this.designData.elements.forEach(function(element) {
previewHtml += '<div class="csd-preview-element" style="';
previewHtml += 'position:absolute;';
previewHtml += 'left:' + element.position.x + 'px;';
previewHtml += 'top:' + element.position.y + 'px;';
for (var style in element.styles) {
previewHtml += style + ':' + element.styles[style] + ';';
}
previewHtml += '">';
previewHtml += element.content || '';
previewHtml += '</div>';
});
previewHtml += '</div>';
$('#csd-preview-content').html(previewHtml);
$('#csd-preview-modal').show();
},
resetDesign: function() {
if (confirm('确定要重置所有设计吗?')) {
this.designData.elements = [];
$('#csd-design-canvas').html('<div class="csd-default-message">从工具栏选择工具开始设计</div>');
$('#csd-properties-form').empty();
this.selectedElement = null;
}
},
loadColorSettings: function() {
this.designData.colors.primary = $('#csd-primary-color').val();
this.designData.colors.secondary = $('#csd-secondary-color').val();
},
openMediaUploader: function() {
var frame = wp.media({
title: '选择图片',
multiple: false,
library: {
type: 'image'
},
button: {
text: '使用此图片'
}
});
frame.on('select', function() {
var attachment = frame.state().get('selection').first().toJSON();
CSD_Designer.addImageFromMedia(attachment);
});
frame.open();
},
addImageFromMedia: function(attachment) {
var elementId = 'element_' + Date.now();
var element = {
id: elementId,
type: 'image',
url: attachment.url,
alt: attachment.alt,
styles: {
width: '200px',
height: 'auto'
},
position: { x: 150, y: 150 }
};
this.designData.elements.push(element);
var $canvas = $('#csd-design-canvas');
var $img = $('<img>')
.attr('src', attachment.url)
.attr('alt', attachment.alt)
.css({
position: 'absolute',
left: element.position.x + 'px',
top: element.position.y + 'px',
width: element.styles.width
})
.addClass('csd-element csd-image-element')
.attr('id', elementId)
.data('element-data', element);
$img.on('click', this.handleElementClick.bind(this));
$canvas.append($img);
this.selectElement(elementId);
}
};
// 初始化设计器
CSD_Designer.init();
});
## 六、后端数据处理与保存
实现AJAX保存功能:
// 在ClientSelfDesign类中添加以下方法
public function save_design() {
// 验证nonce
if (!wp_verify_nonce($_POST['nonce'], 'csd_save_design_nonce')) {
wp_die('安全验证失败');
}
// 验证用户权限
$user_id = intval($_POST['user_id']);
if ($user_id !== get_current_user_id()) {
wp_die('用户权限错误');
}
// 获取并清理数据
$design_data = json_decode(stripslashes($_POST['design_data']), true);
if (!$design_data) {
wp_send_json_error('无效的设计数据');
}
// 验证设计数量限制
$max_designs = get_option('csd_default_settings')['max_designs_per_user'];
$current_count = $this->get_user_design_count($user_id);
if ($current_count >= $max_designs) {
wp_send_json_error('已达到最大设计数量限制');
}
// 保存到数据库
global $wpdb;
$table_name = $wpdb->prefix . 'csd_designs';
$result = $wpdb->insert(
$table_name,
array(
'user_id' => $user_id,
'design_name' => sanitize_text_field($design_data['name']),
'design_data' => json_encode($design_data['data']),
'design_type' => 'custom',
'is_public' => $design_data['is_public'] ? 1 : 0
),
array('%d', '%s', '%s', '%s', '%d')
);
if ($result) {
// 清除缓存
delete_transient('csd_user_designs_' . $user_id);
wp_send_json_success(array(
'design_id' => $wpdb->insert_id,
'message' => '设计保存成功'
));
} else {
wp_send_json_error('数据库保存失败');
}
}
// 添加获取用户设计的函数
public function get_user_designs($user_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'csd_designs';
$cache_key = 'csd_user_designs_' . $user_id;
$designs = get_transient($cache_key);
if (false === $designs) {
$designs = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table_name WHERE user_id = %d ORDER BY created_at DESC",
$user_id
), ARRAY_A);
set_transient($cache_key, $designs, HOUR_IN_SECONDS);
}
return $designs;
}
## 七、管理界面与样式优化
创建管理后台界面:
// 在ClientSelfDesign类中添加以下方法
public function add_admin_menu() {
add_menu_page(
'客户设计管理',
'客户设计',
'manage_options',
'csd-designs',
array($this, 'render_admin_page'),
'dashicons-art',
30
);
add_submenu_page(
'csd-designs',
'设计设置',
'设置',
'manage_options',
'csd-settings',
array($this, 'render_settings_page')
);
}
public function render_admin_page() {
?>
<div class="wrap">
<h1><?php _e('客户设计管理', 'csd'); ?></h1>
<div class="csd-admin-container">
<div class="csd-stats">
<?php
global $wpdb;
$table_name = $wpdb->prefix . 'csd_designs';
$total_designs = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
$total_users = $wpdb->get_var("SELECT COUNT(DISTINCT user_id) FROM $table_name");
$public_designs = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE is_public = 1");
?>
<div class="csd-stat-card">
<h3><?php echo $total_designs; ?></h3>
<p>总设计数</p>
</div>
<div class="csd-stat-card">
<h3><?php echo $total_users; ?></h3>
<p>使用用户数</p>
</div>
<div class="csd-stat-card">
<h3><?php echo $public_designs; ?></h3>
<p>公开设计数</p>
</div>
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>ID</th>
<th>设计名称</th>
<th>用户</th>
<th>类型</th>
<th>公开</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php
$designs = $wpdb->get_results(
"SELECT d.*, u.user_login
FROM $table_name d
LEFT JOIN {$wpdb->users} u ON d.user_id = u.ID
ORDER BY d.created_at DESC
LIMIT 50"
);
foreach ($designs as $design) {
?>
<tr>
<td><?php echo $design->id; ?></td>
<td><?php echo esc_html($design->design_name); ?></td>
<td><?php echo $design->user_login ?: '游客'; ?></td>
<td><?php echo $design->design_type; ?></td>
<td><?php echo $design->is_public ? '是' : '否'; ?></td>
<td><?php echo date('Y-m-d H:i', strtotime($design->created_at)); ?></td>
<td>
<button class="button button-small csd-preview-btn"
data-design-id="<?php echo $design->id; ?>">
预览
</button>
<button class="button button-small button-link-delete csd-delete-btn"
data-design-id="<?php echo $design->id; ?>">
删除
</button>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
</div>
</div>
<?php
}
public function render_settings_page() {
$settings = get_option('csd_default_settings', array());
if ($_SERVER['REQUEST_METHOD'] === 'POST' && check_admin_referer('csd_settings_update')) {
$new_settings = array(
'primary_color' => sanitize_hex_color($_POST['primary_color']),
'secondary_color' => sanitize_hex_color($_POST['secondary_color']),
'max_designs_per_user' => intval($_POST['max_designs_per_user']),
'allow_public_designs' => isset($_POST['allow_public_designs']) ? true : false
);
update_option('csd_default_settings', $new_settings);
$settings = $new_settings;
echo '<div class="notice notice-success"><p>设置已保存</p></div>';
}
?>
<div class="wrap">
<h1><?php _e('设计工具设置', 'csd'); ?></h1>
<form method="post" action="">
<?php wp_nonce_field('csd_settings_update'); ?>
<table class="form-table">
<tr>
<th scope="row">主色调</th>
<td>
<input type="color" name="primary_color"
value="<?php echo esc_attr($settings['primary_color']); ?>">
</td>
</tr>
<tr>
<th scope="row">辅色调</th>
<td>
<input type="color" name="secondary_color"
value="<?php echo esc_attr($settings['secondary_color']); ?>">
</td>
</tr>
<tr>
<th scope="row">用户最大设计数</th>
<td>
<input type="number" name="max_designs_per_user"
value="<?php echo esc_attr($settings['max_designs_per_user']); ?>"
min="1" max="50">
<p class="description">每个用户最多可以保存的设计数量</p>
</td>
</tr>
<tr>
<th scope="row">允许公开设计</th>
<td>
<label>
<input type="checkbox" name="allow_public_designs" value="1"
<?php checked($settings['allow_public_designs']); ?>>
允许用户将设计设为公开
</label>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
## 八、CSS样式文件
创建插件样式文件:
/ 创建文件: assets/css/csd-designer.css /
/ 设计器主容器 /
.csd-designer {
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
padding: 20px;
margin: 20px 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
/ 头部样式 /
.csd-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #f0f0f0;
}
.csd-header h2 {
margin: 0;
color: #333;
}
.csd-design-count {
background: #f7f7f7;
padding: 5px 10px;
border-radius: 3px;
font-size: 14px;
color: #666;
}
/ 工具栏样式 /
.csd-toolbar {
background: #f9f9f9;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.csd-tool-group {
display: flex;
gap: 10px;
}
.csd-tool-btn {
display: flex;
align-items: center;
gap: 5px;
padding: 8px 15px;
background: #fff;
border: 1px solid #ddd;
border-radius: 3px;
cursor: pointer;
transition: all 0.3s ease;
}
.csd-tool-btn:hover {
background: #f0f0f0;
border-color: #999;
}
.csd-tool-btn .dashicons {
font-size: 16px;
width: 16px;
height: 16px;
}
.csd-color-picker {
display: flex;
align-items: center;
gap: 10px;
}
.csd-color-picker label {
font-weight: 600;
color: #555;
}
.csd-color-picker input[type="color"] {
width: 40px;
height: 40px;
border: 2px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
/ 工作区样式 /
.csd-workspace {
display: grid;
grid-template-columns: 3fr 1fr;
gap: 20px;
min-height: 500px;
}
.csd-canvas {
background: #f8f9fa;
border: 2px dashed #dee2e6;
border-radius: 4px;
position: relative;
min-height: 500px;
}
.csd-default-message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #6c757d;
font-size: 16px;
}
/ 设计元素样式 /
.csd-element {
cursor: move;
user-select: none;
transition: all 0.2s ease;
}
.csd-element.selected {
outline: 2px solid #007cba;
outline-offset: 2px;
}
.csd-text-element {
padding: 5px;
min-width: 50px;
min-height: 20px;
}
.csd-shape-element {
background: #3498db;
}
/ 属性面板样式 /
.csd-properties-panel {
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
}
.csd-properties-panel h3 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
font-size: 16px;
}
.csd-property-group {
margin-bottom: 15px;
}
.csd-property-group label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #555;
font-size: 14px;
}
.csd-property-input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 14px;
}
.csd-property-value {
display: inline-block;
margin-left: 10px;
font-size: 14px;
color: #666;
}
/ 底部样式 /
.csd-footer {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.csd-design-info {
display: flex;
gap: 15px;
align-items: center;
}
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 3px;
width: 200px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 3px;
width: 200px;
}
.csd-design-info label {
display: flex;
align-items: center;
gap: 5px;
font-size: 14px;
color: #555;
}
.csd-actions {
display: flex;
gap: 10px;
}
/ 模态框样式 /
.csd-modal {
display: none;
position: fixed;
z-index: 1000;
left


