文章目录
-
- 在当今数字化时代,可视化沟通已成为网站开发、项目管理和内容创作中不可或缺的一环。无论是网站架构规划、用户流程设计,还是产品原型展示,流程图和原型设计工具都能显著提高工作效率和沟通效果。 然而,大多数WordPress用户目前面临一个共同困境:当需要在文章中插入流程图或原型设计时,他们不得不依赖外部工具(如Lucidchart、Figma或Draw.io)创建图表,然后以图片形式导入WordPress。这种方法存在明显缺陷:无法直接编辑、图片质量损失、缺乏交互性,并且破坏了内容创作的一体化体验。 本文将通过详细的代码实现步骤,展示如何为WordPress打造一个完全内嵌的在线流程图绘制与原型设计工具,让用户无需离开WordPress后台即可创建、编辑和发布专业的可视化图表。
-
- 在开始编码之前,我们需要明确工具的核心功能: 基本绘图功能:支持常见流程图元素(矩形、圆形、菱形、箭头等) 原型设计组件:按钮、输入框、导航栏等UI元素库 实时协作:支持多用户同时编辑(可选高级功能) 导出与分享:支持PNG、SVG、JSON等多种格式导出 WordPress集成:与文章编辑器无缝对接,支持短代码嵌入 版本控制:保存编辑历史,支持版本回退 响应式设计:确保在不同设备上都能良好工作
- 考虑到开发效率和功能完整性,我们采用混合技术方案: 前端绘图库:使用开源的Draw.io(mxGraph)核心库,这是一个功能强大且成熟的图形绘制库 WordPress集成:通过自定义插件方式集成到WordPress 数据存储:使用WordPress自定义数据表结合文章元数据 后端API:REST API处理图表保存、加载和用户权限管理
- 在开始开发前,确保你的环境满足以下要求: WordPress 5.0+(支持Gutenberg编辑器) PHP 7.4+ MySQL 5.6+ 基本的Web开发知识(HTML、CSS、JavaScript、PHP)
-
- 首先,在WordPress的wp-content/plugins/目录下创建插件文件夹wp-flowchart-designer,并建立以下文件结构: wp-flowchart-designer/ ├── wp-flowchart-designer.php # 主插件文件 ├── includes/ │ ├── class-database.php # 数据库处理类 │ ├── class-shortcode.php # 短代码处理器 │ ├── class-rest-api.php # REST API处理器 │ └── class-admin.php # 后台管理类 ├── admin/ │ ├── css/ │ │ └── admin-style.css # 后台样式 │ └── js/ │ └── admin-script.js # 后台脚本 ├── public/ │ ├── css/ │ │ └── public-style.css # 前台样式 │ ├── js/ │ │ ├── public-script.js # 前台脚本 │ │ └── mxgraph/ # mxGraph库文件 │ └── partials/ # 公共模板部分 ├── assets/ # 静态资源 └── uninstall.php # 插件卸载脚本
- 创建主插件文件wp-flowchart-designer.php: <?php /** * Plugin Name: WordPress流程图与原型设计工具 * Plugin URI: https://yourwebsite.com/wp-flowchart-designer * Description: 为WordPress添加内嵌的在线流程图绘制与原型设计功能 * Version: 1.0.0 * Author: 你的名字 * License: GPL v2 or later * Text Domain: wp-flowchart-designer */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('WPFD_VERSION', '1.0.0'); define('WPFD_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('WPFD_PLUGIN_URL', plugin_dir_url(__FILE__)); define('WPFD_PLUGIN_BASENAME', plugin_basename(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class_name) { $prefix = 'WPFD_'; $base_dir = WPFD_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class_name, $len) !== 0) { return; } $relative_class = substr($class_name, $len); $file = $base_dir . 'class-' . strtolower(str_replace('_', '-', $relative_class)) . '.php'; if (file_exists($file)) { require $file; } }); // 初始化插件 function wpfd_init() { // 检查WordPress版本 if (version_compare(get_bloginfo('version'), '5.0', '<')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>'; echo __('WordPress流程图与原型设计工具需要WordPress 5.0或更高版本。', 'wp-flowchart-designer'); echo '</p></div>'; }); return; } // 初始化各个组件 WPFD_Database::init(); WPFD_Shortcode::init(); WPFD_REST_API::init(); if (is_admin()) { WPFD_Admin::init(); } } add_action('plugins_loaded', 'wpfd_init'); // 激活插件时创建数据库表 register_activation_hook(__FILE__, ['WPFD_Database', 'create_tables']); // 卸载插件时清理数据 register_uninstall_hook(__FILE__, ['WPFD_Database', 'drop_tables']);
-
- 由于Draw.io基于mxGraph库,我们需要将其集成到插件中。可以从GitHub获取mxGraph库: 访问 https://github.com/jgraph/mxgraph 下载最新版本 将javascript目录复制到public/js/mxgraph/
- 在admin/js/admin-script.js中,我们创建编辑器初始化代码: (function($) { 'use strict'; // 全局编辑器实例 var wpfdEditor = null; // 初始化流程图编辑器 function initFlowchartEditor(containerId, initialData) { // 检查mxGraph库是否已加载 if (typeof mxClient === 'undefined') { console.error('mxGraph库未加载'); return; } // 创建编辑器容器 var container = document.getElementById(containerId); if (!container) { console.error('找不到容器: ' + containerId); return; } // 禁用右键菜单 mxEvent.disableContextMenu(container); // 创建图形模型 var model = new mxGraphModel(); var graph = new mxGraph(container, model); // 配置图形 graph.setConnectable(true); graph.setMultigraph(false); graph.setAllowDanglingEdges(false); graph.setDropEnabled(true); graph.setPanning(true); graph.setTooltips(true); // 启用缩放 new mxKeyHandler(graph); new mxRubberband(graph); // 设置样式 var style = graph.getStylesheet().getDefaultVertexStyle(); style[mxConstants.STYLE_FONTFAMILY] = 'Helvetica, Arial, sans-serif'; style[mxConstants.STYLE_FONTSIZE] = '12'; style[mxConstants.STYLE_STROKECOLOR] = '#2D3748'; style[mxConstants.STYLE_FILLCOLOR] = '#EDF2F7'; // 加载初始数据 if (initialData && initialData.xml) { try { var doc = mxUtils.parseXml(initialData.xml); var codec = new mxCodec(doc); codec.decode(doc.documentElement, graph.getModel()); } catch (e) { console.error('解析图表数据失败:', e); } } // 保存编辑器实例 wpfdEditor = { graph: graph, model: model, container: container }; return wpfdEditor; } // 获取图表数据 function getDiagramData() { if (!wpfdEditor) { return null; } var encoder = new mxCodec(); var node = encoder.encode(wpfdEditor.model); return { xml: mxUtils.getXml(node), bounds: wpfdEditor.graph.getGraphBounds(), cells: wpfdEditor.model.getDescendants(wpfdEditor.model.getRoot()) }; } // 导出为图片 function exportAsImage(format, bgColor) { if (!wpfdEditor) { return null; } var bounds = wpfdEditor.graph.getGraphBounds(); var scale = wpfdEditor.graph.view.scale; // 创建临时canvas var canvas = document.createElement('canvas'); canvas.width = bounds.width * scale; canvas.height = bounds.height * scale; var imgExport = new mxImageExport(); var ctx = canvas.getContext('2d'); // 设置背景色 if (bgColor) { ctx.fillStyle = bgColor; ctx.fillRect(0, 0, canvas.width, canvas.height); } // 导出图形 imgExport.drawState(wpfdEditor.graph.getView().getState(wpfdEditor.model.getRoot()), ctx); return canvas.toDataURL('image/' + format); } // 添加形状到工具栏 function addShapeToToolbar(toolbarId, shapeConfig) { var toolbar = document.getElementById(toolbarId); if (!toolbar) return; var button = document.createElement('button'); button.type = 'button'; button.className = 'wpfd-toolbar-btn'; button.innerHTML = shapeConfig.icon || shapeConfig.label; button.title = shapeConfig.label; button.addEventListener('click', function() { if (!wpfdEditor) return; wpfdEditor.graph.stopEditing(); var parent = wpfdEditor.graph.getDefaultParent(); wpfdEditor.model.beginUpdate(); try { var vertex = wpfdEditor.graph.insertVertex( parent, null, shapeConfig.label, 20, 20, shapeConfig.width || 120, shapeConfig.height || 60, shapeConfig.style || '' ); // 将新形状置于视图中心 var geo = wpfdEditor.model.getGeometry(vertex); var bounds = wpfdEditor.graph.getGraphBounds(); geo.x = Math.max(20, (bounds.width - geo.width) / 2); geo.y = Math.max(20, (bounds.height - geo.height) / 2); wpfdEditor.graph.setSelectionCell(vertex); } finally { wpfdEditor.model.endUpdate(); } }); toolbar.appendChild(button); } // 初始化工具栏 function initToolbar(toolbarId) { // 预定义形状 var shapes = [ { label: '开始/结束', style: 'shape=ellipse', width: 100, height: 60 }, { label: '过程', style: '', width: 120, height: 60 }, { label: '判断', style: 'shape=rhombus', width: 100, height: 80 }, { label: '数据', style: 'shape=parallelogram', width: 120, height: 60 }, { label: '文档', style: 'shape=document', width: 100, height: 80 }, { label: '子流程', style: 'shape=process;perimeter=rectanglePerimeter', width: 140, height: 80 } ]; shapes.forEach(function(shape) { addShapeToToolbar(toolbarId, shape); }); } // WordPress集成 $(document).ready(function() { // 初始化编辑器 if ($('#wpfd-editor-container').length) { var initialData = window.wpfdInitialData || {}; wpfdEditor = initFlowchartEditor('wpfd-editor-container', initialData); ('#wpfd-toolbar').length) { initToolbar('wpfd-toolbar'); } } // 保存图表 $('#wpfd-save-diagram').on('click', function() { var diagramData = getDiagramData(); var title = $('#wpfd-diagram-title').val() || '未命名图表'; if (!diagramData) { alert('无法获取图表数据'); return; } // 显示保存中状态 var $button = $(this); var originalText = $button.text(); $button.text('保存中...').prop('disabled', true); // 发送保存请求 $.ajax({ url: wpfd_ajax.ajax_url, type: 'POST', data: { action: 'wpfd_save_diagram', nonce: wpfd_ajax.nonce, title: title, diagram_data: JSON.stringify(diagramData), diagram_type: 'flowchart' }, success: function(response) { if (response.success) { alert('图表保存成功!'); if (response.data.redirect) { window.location.href = response.data.redirect; } } else { alert('保存失败: ' + response.data.message); } }, error: function() { alert('保存请求失败,请检查网络连接'); }, complete: function() { $button.text(originalText).prop('disabled', false); } }); }); // 导出功能 $('.wpfd-export-btn').on('click', function() { var format = $(this).data('format'); var imageData = exportAsImage(format, '#ffffff'); if (imageData) { // 创建下载链接 var link = document.createElement('a'); link.download = 'diagram.' + format; link.href = imageData; document.body.appendChild(link); link.click(); document.body.removeChild(link); } }); // 插入到文章 $('#wpfd-insert-to-post').on('click', function() { var diagramData = getDiagramData(); if (!diagramData) { alert('无法获取图表数据'); return; } // 生成短代码 var shortcode = '[wpfd_diagram id="' + wpfd_ajax.diagram_id + '"]'; // 发送到编辑器 if (typeof wp !== 'undefined' && wp.media && wp.media.editor) { wp.media.editor.insert(shortcode); } else { // 备用方案:复制到剪贴板 var tempInput = document.createElement('input'); tempInput.value = shortcode; document.body.appendChild(tempInput); tempInput.select(); document.execCommand('copy'); document.body.removeChild(tempInput); alert('短代码已复制到剪贴板'); } }); }); // 暴露公共API window.WPFD_Editor = { init: initFlowchartEditor, getData: getDiagramData, exportImage: exportAsImage, addShape: addShapeToToolbar }; })(jQuery); ### 4.3 创建编辑器CSS样式 在`admin/css/admin-style.css`中添加编辑器样式: / 流程图编辑器主容器 /.wpfd-editor-wrapper { display: flex; flex-direction: column; height: 800px; border: 1px solid #ccd0d4; border-radius: 4px; overflow: hidden; background: #f8f9fa; } / 编辑器工具栏 /.wpfd-editor-toolbar { background: #fff; border-bottom: 1px solid #ccd0d4; padding: 10px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-height: 60px; } .wpfd-toolbar-group { display: flex; align-items: center; gap: 4px; padding: 0 10px; border-right: 1px solid #e0e0e0; } .wpfd-toolbar-group:last-child { border-right: none; } .wpfd-toolbar-btn { background: #f0f0f0; border: 1px solid #ddd; border-radius: 3px; padding: 6px 12px; cursor: pointer; font-size: 13px; color: #333; transition: all 0.2s; display: flex; align-items: center; gap: 5px; } .wpfd-toolbar-btn:hover { background: #e0e0e0; border-color: #ccc; } .wpfd-toolbar-btn.active { background: #007cba; color: white; border-color: #007cba; } .wpfd-toolbar-btn i { font-size: 16px; } / 编辑器主区域 /.wpfd-editor-main { display: flex; flex: 1; overflow: hidden; } / 左侧形状面板 /.wpfd-shapes-panel { width: 200px; background: #fff; border-right: 1px solid #ccd0d4; padding: 15px; overflow-y: auto; } .wpfd-shapes-category { margin-bottom: 20px; } .wpfd-shapes-category h4 { margin: 0 0 10px 0; font-size: 13px; color: #666; text-transform: uppercase; font-weight: 600; } .wpfd-shapes-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; } .wpfd-shape-item { background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 4px; padding: 10px; cursor: move; text-align: center; font-size: 12px; transition: all 0.2s; } .wpfd-shape-item:hover { background: #e9ecef; border-color: #007cba; } / 画布区域 /.wpfd-canvas-container { flex: 1; position: relative; overflow: hidden; background: #fff; }
- width: 100%; height: 100%; min-height: 600px; } / 右侧属性面板 /.wpfd-properties-panel { width: 300px; background: #fff; border-left: 1px solid #ccd0d4; padding: 15px; overflow-y: auto; } .wpfd-property-group { margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid #eee; } .wpfd-property-group:last-child { border-bottom: none; margin-bottom: 0; } .wpfd-property-group h4 { margin: 0 0 12px 0; font-size: 14px; color: #333; font-weight: 600; } .wpfd-property-item { margin-bottom: 10px; } .wpfd-property-item label { display: block; margin-bottom: 5px; font-size: 13px; color: #555; } .wpfd-property-item input[type="text"],.wpfd-property-item input[type="number"],.wpfd-property-item select { width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 3px; font-size: 13px; } .wpfd-property-item input[type="color"] { width: 100%; height: 35px; padding: 2px; border: 1px solid #ddd; border-radius: 3px; } / 编辑器底部状态栏 /.wpfd-editor-statusbar { background: #fff; border-top: 1px solid #ccd0d4; padding: 8px 15px; font-size: 12px; color: #666; display: flex; justify-content: space-between; align-items: center; } / 响应式设计 /@media (max-width: 1200px) { .wpfd-editor-wrapper { height: 600px; } .wpfd-shapes-panel { width: 180px; } .wpfd-properties-panel { width: 250px; } } @media (max-width: 768px) { .wpfd-editor-main { flex-direction: column; } .wpfd-shapes-panel { width: 100%; height: 150px; border-right: none; border-bottom: 1px solid #ccd0d4; } .wpfd-properties-panel { width: 100%; height: 200px; border-left: none; border-top: 1px solid #ccd0d4; } .wpfd-shapes-list { grid-template-columns: repeat(4, 1fr); } } / 图标字体 /@font-face { font-family: 'wpfd-icons'; src: url('../fonts/wpfd-icons.woff2') format('woff2'), url('../fonts/wpfd-icons.woff') format('woff'); font-weight: normal; font-style: normal; } .wpfd-icon { font-family: 'wpfd-icons'; speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .wpfd-icon-rectangle:before { content: "e900"; }.wpfd-icon-circle:before { content: "e901"; }.wpfd-icon-diamond:before { content: "e902"; }.wpfd-icon-arrow:before { content: "e903"; }.wpfd-icon-text:before { content: "e904"; }.wpfd-icon-line:before { content: "e905"; }.wpfd-icon-save:before { content: "e906"; }.wpfd-icon-export:before { content: "e907"; }.wpfd-icon-undo:before { content: "e908"; }.wpfd-icon-redo:before { content: "e909"; }.wpfd-icon-zoom-in:before { content: "e90a"; }.wpfd-icon-zoom-out:before { content: "e90b"; } ## 第五部分:实现REST API接口 ### 5.1 创建REST API处理器 在`includes/class-rest-api.php`中实现API接口: <?phpclass WPFD_REST_API { public static function init() { add_action('rest_api_init', [__CLASS__, 'register_routes']); } public static function register_routes() { // 图表CRUD接口 register_rest_route('wpfd/v1', '/diagrams', [ [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_diagrams'], 'permission_callback' => [__CLASS__, 'check_permission'], 'args' => [ 'per_page' => [ 'default' => 20, 'validate_callback' => function($param) { return is_numeric($param) && $param > 0 && $param <= 100; } ], 'page' => [ 'default' => 1, 'validate_callback' => function($param) { return is_numeric($param) && $param > 0; } ] ] ], [ 'methods' => 'POST', 'callback' => [__CLASS__, 'create_diagram'], 'permission_callback' => [__CLASS__, 'check_permission'] ] ]); register_rest_route('wpfd/v1', '/diagrams/(?P<id>d+)', [ [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_diagram'], 'permission_callback' => [__CLASS__, 'check_permission'] ], [ 'methods' => 'PUT', 'callback' => [__CLASS__, 'update_diagram'], 'permission_callback' => [__CLASS__, 'check_permission'] ], [ 'methods' => 'DELETE', 'callback' => [__CLASS__, 'delete_diagram'], 'permission_callback' => [__CLASS__, 'check_permission'] ] ]); // 导出接口 register_rest_route('wpfd/v1', '/export/(?P<id>d+)', [ [ 'methods' => 'GET', 'callback' => [__CLASS__, 'export_diagram'], 'permission_callback' => [__CLASS__, 'check_permission'], 'args' => [ 'format' => [ 'default' => 'png', 'validate_callback' => function($param) { return in_array($param, ['png', 'jpg', 'svg', 'pdf']); } ] ] ] ]); // 搜索接口 register_rest_route('wpfd/v1', '/search', [ [ 'methods' => 'GET', 'callback' => [__CLASS__, 'search_diagrams'], 'permission_callback' => [__CLASS__, 'check_permission'] ] ]); } public static function check_permission($request) { // 检查用户是否登录 if (!is_user_logged_in()) { return new WP_Error('rest_forbidden', __('请先登录'), ['status' => 401]); } // 检查用户权限 $method = $request->get_method(); $user_id = get_current_user_id(); // 对于GET请求,允许所有登录用户访问 if ($method === 'GET') { return true; } // 对于其他请求,需要编辑权限 if (!current_user_can('edit_posts')) { return new WP_Error('rest_forbidden', __('权限不足'), ['status' => 403]); } return true; } public static function get_diagrams($request) { $params = $request->get_params(); $per_page = intval($params['per_page']); $page = intval($params['page']); $offset = ($page - 1) * $per_page; global $wpdb; $table_name = $wpdb->prefix . 'wpfd_diagrams'; $user_id = get_current_user_id(); // 获取图表总数 $total = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table_name} WHERE user_id = %d", $user_id ) ); // 获取图表列表 $results = $wpdb->get_results( $wpdb->prepare( "SELECT id, title, diagram_type, created_at, updated_at FROM {$table_name} WHERE user_id = %d ORDER BY updated_at DESC LIMIT %d OFFSET %d", $user_id, $per_page, $offset ), ARRAY_A ); // 准备响应数据 $diagrams = []; foreach ($results as $row) { $diagrams[] = [ 'id' => intval($row['id']), 'title' => $row['title'], 'type' => $row['diagram_type'], 'created_at' => $row['created_at'], 'updated_at' => $row['updated_at'], 'edit_url' => admin_url('admin.php?page=wpfd-edit&id=' . $row['id']) ]; } $response = new WP_REST_Response($diagrams); $response->header('X-WP-Total', $total); $response->header('X-WP-TotalPages', ceil($total / $per_page)); return $response; } public static function get_diagram($request) { $id = intval($request['id']); $diagram = WPFD_Database::get_diagram($id); if (!$diagram) { return new WP_Error('not_found', __('图表不存在'), ['status' => 404]); } // 检查权限:只能访问自己的图表 if ($diagram['user_id'] != get_current_user_id() && !current_user_can('manage_options')) { return new WP_Error('forbidden', __('无权访问此图表'), ['status' => 403]); } return rest_ensure_response($diagram); } public static function create_diagram($request) { $data = $request->get_json_params(); // 验证数据 if (empty($data['title'])) { return new WP_Error('invalid_data', __('标题不能为空'), ['status' => 400]); } if (empty($data['diagram_data'])) { return new WP_Error('invalid_data', __('图表数据不能为空'), ['status' => 400]); } // 准备保存数据 $save_data = [ 'title' => sanitize_text_field($data['title']), 'diagram_data' => $data['diagram_data'], 'diagram_type' => isset($data['diagram_type']) ? sanitize_text_field($data['diagram_type']) : 'flowchart', 'settings' => isset($data['settings']) ? $data['settings'] : [] ]; // 如果有post_id,关联到文章 if (isset($data['post_id']) && $data['post_id
在当今数字化时代,可视化沟通已成为网站开发、项目管理和内容创作中不可或缺的一环。无论是网站架构规划、用户流程设计,还是产品原型展示,流程图和原型设计工具都能显著提高工作效率和沟通效果。
然而,大多数WordPress用户目前面临一个共同困境:当需要在文章中插入流程图或原型设计时,他们不得不依赖外部工具(如Lucidchart、Figma或Draw.io)创建图表,然后以图片形式导入WordPress。这种方法存在明显缺陷:无法直接编辑、图片质量损失、缺乏交互性,并且破坏了内容创作的一体化体验。
本文将通过详细的代码实现步骤,展示如何为WordPress打造一个完全内嵌的在线流程图绘制与原型设计工具,让用户无需离开WordPress后台即可创建、编辑和发布专业的可视化图表。
在开始编码之前,我们需要明确工具的核心功能:
- 基本绘图功能:支持常见流程图元素(矩形、圆形、菱形、箭头等)
- 原型设计组件:按钮、输入框、导航栏等UI元素库
- 实时协作:支持多用户同时编辑(可选高级功能)
- 导出与分享:支持PNG、SVG、JSON等多种格式导出
- WordPress集成:与文章编辑器无缝对接,支持短代码嵌入
- 版本控制:保存编辑历史,支持版本回退
- 响应式设计:确保在不同设备上都能良好工作
考虑到开发效率和功能完整性,我们采用混合技术方案:
- 前端绘图库:使用开源的Draw.io(mxGraph)核心库,这是一个功能强大且成熟的图形绘制库
- WordPress集成:通过自定义插件方式集成到WordPress
- 数据存储:使用WordPress自定义数据表结合文章元数据
- 后端API:REST API处理图表保存、加载和用户权限管理
在开始开发前,确保你的环境满足以下要求:
- WordPress 5.0+(支持Gutenberg编辑器)
- PHP 7.4+
- MySQL 5.6+
- 基本的Web开发知识(HTML、CSS、JavaScript、PHP)
首先,在WordPress的wp-content/plugins/目录下创建插件文件夹wp-flowchart-designer,并建立以下文件结构:
wp-flowchart-designer/
├── wp-flowchart-designer.php # 主插件文件
├── includes/
│ ├── class-database.php # 数据库处理类
│ ├── class-shortcode.php # 短代码处理器
│ ├── class-rest-api.php # REST API处理器
│ └── class-admin.php # 后台管理类
├── admin/
│ ├── css/
│ │ └── admin-style.css # 后台样式
│ └── js/
│ └── admin-script.js # 后台脚本
├── public/
│ ├── css/
│ │ └── public-style.css # 前台样式
│ ├── js/
│ │ ├── public-script.js # 前台脚本
│ │ └── mxgraph/ # mxGraph库文件
│ └── partials/ # 公共模板部分
├── assets/ # 静态资源
└── uninstall.php # 插件卸载脚本
创建主插件文件wp-flowchart-designer.php:
<?php
/**
* Plugin Name: WordPress流程图与原型设计工具
* Plugin URI: https://yourwebsite.com/wp-flowchart-designer
* Description: 为WordPress添加内嵌的在线流程图绘制与原型设计功能
* Version: 1.0.0
* Author: 你的名字
* License: GPL v2 or later
* Text Domain: wp-flowchart-designer
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('WPFD_VERSION', '1.0.0');
define('WPFD_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPFD_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WPFD_PLUGIN_BASENAME', plugin_basename(__FILE__));
// 自动加载类文件
spl_autoload_register(function ($class_name) {
$prefix = 'WPFD_';
$base_dir = WPFD_PLUGIN_DIR . 'includes/';
$len = strlen($prefix);
if (strncmp($prefix, $class_name, $len) !== 0) {
return;
}
$relative_class = substr($class_name, $len);
$file = $base_dir . 'class-' . strtolower(str_replace('_', '-', $relative_class)) . '.php';
if (file_exists($file)) {
require $file;
}
});
// 初始化插件
function wpfd_init() {
// 检查WordPress版本
if (version_compare(get_bloginfo('version'), '5.0', '<')) {
add_action('admin_notices', function() {
echo '<div class="notice notice-error"><p>';
echo __('WordPress流程图与原型设计工具需要WordPress 5.0或更高版本。', 'wp-flowchart-designer');
echo '</p></div>';
});
return;
}
// 初始化各个组件
WPFD_Database::init();
WPFD_Shortcode::init();
WPFD_REST_API::init();
if (is_admin()) {
WPFD_Admin::init();
}
}
add_action('plugins_loaded', 'wpfd_init');
// 激活插件时创建数据库表
register_activation_hook(__FILE__, ['WPFD_Database', 'create_tables']);
// 卸载插件时清理数据
register_uninstall_hook(__FILE__, ['WPFD_Database', 'drop_tables']);
在includes/class-database.php中,我们创建存储图表数据的数据表:
<?php
class WPFD_Database {
private static $table_name;
public static function init() {
global $wpdb;
self::$table_name = $wpdb->prefix . 'wpfd_diagrams';
}
public static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS " . self::$table_name . " (
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
post_id bigint(20) UNSIGNED DEFAULT NULL,
user_id bigint(20) UNSIGNED NOT NULL,
title varchar(255) NOT NULL,
diagram_data longtext NOT NULL,
diagram_type varchar(50) DEFAULT 'flowchart',
settings text,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY post_id (post_id),
KEY user_id (user_id),
KEY diagram_type (diagram_type)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
// 添加版本选项,便于后续升级
add_option('wpfd_db_version', '1.0');
}
public static function drop_tables() {
global $wpdb;
$sql = "DROP TABLE IF EXISTS " . self::$table_name;
$wpdb->query($sql);
delete_option('wpfd_db_version');
}
public static function save_diagram($data) {
global $wpdb;
$defaults = [
'post_id' => null,
'user_id' => get_current_user_id(),
'title' => '未命名图表',
'diagram_data' => '',
'diagram_type' => 'flowchart',
'settings' => '{}'
];
$data = wp_parse_args($data, $defaults);
if (isset($data['id']) && $data['id'] > 0) {
// 更新现有图表
$wpdb->update(
self::$table_name,
[
'title' => sanitize_text_field($data['title']),
'diagram_data' => wp_json_encode($data['diagram_data']),
'diagram_type' => sanitize_text_field($data['diagram_type']),
'settings' => wp_json_encode($data['settings']),
'updated_at' => current_time('mysql')
],
['id' => $data['id']],
['%s', '%s', '%s', '%s', '%s'],
['%d']
);
return $data['id'];
} else {
// 插入新图表
$wpdb->insert(
self::$table_name,
[
'post_id' => $data['post_id'],
'user_id' => $data['user_id'],
'title' => sanitize_text_field($data['title']),
'diagram_data' => wp_json_encode($data['diagram_data']),
'diagram_type' => sanitize_text_field($data['diagram_type']),
'settings' => wp_json_encode($data['settings']),
'created_at' => current_time('mysql'),
'updated_at' => current_time('mysql')
],
['%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s']
);
return $wpdb->insert_id;
}
}
public static function get_diagram($id) {
global $wpdb;
$result = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM " . self::$table_name . " WHERE id = %d",
$id
),
ARRAY_A
);
if ($result) {
$result['diagram_data'] = json_decode($result['diagram_data'], true);
$result['settings'] = json_decode($result['settings'], true);
}
return $result;
}
public static function get_user_diagrams($user_id = null, $limit = 20, $offset = 0) {
global $wpdb;
if (!$user_id) {
$user_id = get_current_user_id();
}
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM " . self::$table_name . "
WHERE user_id = %d
ORDER BY updated_at DESC
LIMIT %d OFFSET %d",
$user_id, $limit, $offset
),
ARRAY_A
);
foreach ($results as &$result) {
$result['diagram_data'] = json_decode($result['diagram_data'], true);
$result['settings'] = json_decode($result['settings'], true);
}
return $results;
}
public static function delete_diagram($id) {
global $wpdb;
return $wpdb->delete(
self::$table_name,
['id' => $id],
['%d']
);
}
}
由于Draw.io基于mxGraph库,我们需要将其集成到插件中。可以从GitHub获取mxGraph库:
- 访问 https://github.com/jgraph/mxgraph
- 下载最新版本
- 将
javascript目录复制到public/js/mxgraph/
在admin/js/admin-script.js中,我们创建编辑器初始化代码:
(function($) {
'use strict';
// 全局编辑器实例
var wpfdEditor = null;
// 初始化流程图编辑器
function initFlowchartEditor(containerId, initialData) {
// 检查mxGraph库是否已加载
if (typeof mxClient === 'undefined') {
console.error('mxGraph库未加载');
return;
}
// 创建编辑器容器
var container = document.getElementById(containerId);
if (!container) {
console.error('找不到容器: ' + containerId);
return;
}
// 禁用右键菜单
mxEvent.disableContextMenu(container);
// 创建图形模型
var model = new mxGraphModel();
var graph = new mxGraph(container, model);
// 配置图形
graph.setConnectable(true);
graph.setMultigraph(false);
graph.setAllowDanglingEdges(false);
graph.setDropEnabled(true);
graph.setPanning(true);
graph.setTooltips(true);
// 启用缩放
new mxKeyHandler(graph);
new mxRubberband(graph);
// 设置样式
var style = graph.getStylesheet().getDefaultVertexStyle();
style[mxConstants.STYLE_FONTFAMILY] = 'Helvetica, Arial, sans-serif';
style[mxConstants.STYLE_FONTSIZE] = '12';
style[mxConstants.STYLE_STROKECOLOR] = '#2D3748';
style[mxConstants.STYLE_FILLCOLOR] = '#EDF2F7';
// 加载初始数据
if (initialData && initialData.xml) {
try {
var doc = mxUtils.parseXml(initialData.xml);
var codec = new mxCodec(doc);
codec.decode(doc.documentElement, graph.getModel());
} catch (e) {
console.error('解析图表数据失败:', e);
}
}
// 保存编辑器实例
wpfdEditor = {
graph: graph,
model: model,
container: container
};
return wpfdEditor;
}
// 获取图表数据
function getDiagramData() {
if (!wpfdEditor) {
return null;
}
var encoder = new mxCodec();
var node = encoder.encode(wpfdEditor.model);
return {
xml: mxUtils.getXml(node),
bounds: wpfdEditor.graph.getGraphBounds(),
cells: wpfdEditor.model.getDescendants(wpfdEditor.model.getRoot())
};
}
// 导出为图片
function exportAsImage(format, bgColor) {
if (!wpfdEditor) {
return null;
}
var bounds = wpfdEditor.graph.getGraphBounds();
var scale = wpfdEditor.graph.view.scale;
// 创建临时canvas
var canvas = document.createElement('canvas');
canvas.width = bounds.width * scale;
canvas.height = bounds.height * scale;
var imgExport = new mxImageExport();
var ctx = canvas.getContext('2d');
// 设置背景色
if (bgColor) {
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 导出图形
imgExport.drawState(wpfdEditor.graph.getView().getState(wpfdEditor.model.getRoot()), ctx);
return canvas.toDataURL('image/' + format);
}
// 添加形状到工具栏
function addShapeToToolbar(toolbarId, shapeConfig) {
var toolbar = document.getElementById(toolbarId);
if (!toolbar) return;
var button = document.createElement('button');
button.type = 'button';
button.className = 'wpfd-toolbar-btn';
button.innerHTML = shapeConfig.icon || shapeConfig.label;
button.title = shapeConfig.label;
button.addEventListener('click', function() {
if (!wpfdEditor) return;
wpfdEditor.graph.stopEditing();
var parent = wpfdEditor.graph.getDefaultParent();
wpfdEditor.model.beginUpdate();
try {
var vertex = wpfdEditor.graph.insertVertex(
parent,
null,
shapeConfig.label,
20, 20,
shapeConfig.width || 120,
shapeConfig.height || 60,
shapeConfig.style || ''
);
// 将新形状置于视图中心
var geo = wpfdEditor.model.getGeometry(vertex);
var bounds = wpfdEditor.graph.getGraphBounds();
geo.x = Math.max(20, (bounds.width - geo.width) / 2);
geo.y = Math.max(20, (bounds.height - geo.height) / 2);
wpfdEditor.graph.setSelectionCell(vertex);
} finally {
wpfdEditor.model.endUpdate();
}
});
toolbar.appendChild(button);
}
// 初始化工具栏
function initToolbar(toolbarId) {
// 预定义形状
var shapes = [
{ label: '开始/结束', style: 'shape=ellipse', width: 100, height: 60 },
{ label: '过程', style: '', width: 120, height: 60 },
{ label: '判断', style: 'shape=rhombus', width: 100, height: 80 },
{ label: '数据', style: 'shape=parallelogram', width: 120, height: 60 },
{ label: '文档', style: 'shape=document', width: 100, height: 80 },
{ label: '子流程', style: 'shape=process;perimeter=rectanglePerimeter', width: 140, height: 80 }
];
shapes.forEach(function(shape) {
addShapeToToolbar(toolbarId, shape);
});
}
// WordPress集成
$(document).ready(function() {
// 初始化编辑器
if ($('#wpfd-editor-container').length) {
var initialData = window.wpfdInitialData || {};
wpfdEditor = initFlowchartEditor('wpfd-editor-container', initialData);
('#wpfd-toolbar').length) {
initToolbar('wpfd-toolbar');
}
}
// 保存图表
$('#wpfd-save-diagram').on('click', function() {
var diagramData = getDiagramData();
var title = $('#wpfd-diagram-title').val() || '未命名图表';
if (!diagramData) {
alert('无法获取图表数据');
return;
}
// 显示保存中状态
var $button = $(this);
var originalText = $button.text();
$button.text('保存中...').prop('disabled', true);
// 发送保存请求
$.ajax({
url: wpfd_ajax.ajax_url,
type: 'POST',
data: {
action: 'wpfd_save_diagram',
nonce: wpfd_ajax.nonce,
title: title,
diagram_data: JSON.stringify(diagramData),
diagram_type: 'flowchart'
},
success: function(response) {
if (response.success) {
alert('图表保存成功!');
if (response.data.redirect) {
window.location.href = response.data.redirect;
}
} else {
alert('保存失败: ' + response.data.message);
}
},
error: function() {
alert('保存请求失败,请检查网络连接');
},
complete: function() {
$button.text(originalText).prop('disabled', false);
}
});
});
// 导出功能
$('.wpfd-export-btn').on('click', function() {
var format = $(this).data('format');
var imageData = exportAsImage(format, '#ffffff');
if (imageData) {
// 创建下载链接
var link = document.createElement('a');
link.download = 'diagram.' + format;
link.href = imageData;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
});
// 插入到文章
$('#wpfd-insert-to-post').on('click', function() {
var diagramData = getDiagramData();
if (!diagramData) {
alert('无法获取图表数据');
return;
}
// 生成短代码
var shortcode = '[wpfd_diagram id="' + wpfd_ajax.diagram_id + '"]';
// 发送到编辑器
if (typeof wp !== 'undefined' && wp.media && wp.media.editor) {
wp.media.editor.insert(shortcode);
} else {
// 备用方案:复制到剪贴板
var tempInput = document.createElement('input');
tempInput.value = shortcode;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand('copy');
document.body.removeChild(tempInput);
alert('短代码已复制到剪贴板');
}
});
});
// 暴露公共API
window.WPFD_Editor = {
init: initFlowchartEditor,
getData: getDiagramData,
exportImage: exportAsImage,
addShape: addShapeToToolbar
};
})(jQuery);
### 4.3 创建编辑器CSS样式
在`admin/css/admin-style.css`中添加编辑器样式:
/ 流程图编辑器主容器 /
.wpfd-editor-wrapper {
display: flex;
flex-direction: column;
height: 800px;
border: 1px solid #ccd0d4;
border-radius: 4px;
overflow: hidden;
background: #f8f9fa;
}
/ 编辑器工具栏 /
.wpfd-editor-toolbar {
background: #fff;
border-bottom: 1px solid #ccd0d4;
padding: 10px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
min-height: 60px;
}
.wpfd-toolbar-group {
display: flex;
align-items: center;
gap: 4px;
padding: 0 10px;
border-right: 1px solid #e0e0e0;
}
.wpfd-toolbar-group:last-child {
border-right: none;
}
.wpfd-toolbar-btn {
background: #f0f0f0;
border: 1px solid #ddd;
border-radius: 3px;
padding: 6px 12px;
cursor: pointer;
font-size: 13px;
color: #333;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 5px;
}
.wpfd-toolbar-btn:hover {
background: #e0e0e0;
border-color: #ccc;
}
.wpfd-toolbar-btn.active {
background: #007cba;
color: white;
border-color: #007cba;
}
.wpfd-toolbar-btn i {
font-size: 16px;
}
/ 编辑器主区域 /
.wpfd-editor-main {
display: flex;
flex: 1;
overflow: hidden;
}
/ 左侧形状面板 /
.wpfd-shapes-panel {
width: 200px;
background: #fff;
border-right: 1px solid #ccd0d4;
padding: 15px;
overflow-y: auto;
}
.wpfd-shapes-category {
margin-bottom: 20px;
}
.wpfd-shapes-category h4 {
margin: 0 0 10px 0;
font-size: 13px;
color: #666;
text-transform: uppercase;
font-weight: 600;
}
.wpfd-shapes-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.wpfd-shape-item {
background: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 10px;
cursor: move;
text-align: center;
font-size: 12px;
transition: all 0.2s;
}
.wpfd-shape-item:hover {
background: #e9ecef;
border-color: #007cba;
}
/ 画布区域 /
.wpfd-canvas-container {
flex: 1;
position: relative;
overflow: hidden;
background: #fff;
}
width: 100%;
height: 100%;
min-height: 600px;
width: 100%;
height: 100%;
min-height: 600px;
}
/ 右侧属性面板 /
.wpfd-properties-panel {
width: 300px;
background: #fff;
border-left: 1px solid #ccd0d4;
padding: 15px;
overflow-y: auto;
}
.wpfd-property-group {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.wpfd-property-group:last-child {
border-bottom: none;
margin-bottom: 0;
}
.wpfd-property-group h4 {
margin: 0 0 12px 0;
font-size: 14px;
color: #333;
font-weight: 600;
}
.wpfd-property-item {
margin-bottom: 10px;
}
.wpfd-property-item label {
display: block;
margin-bottom: 5px;
font-size: 13px;
color: #555;
}
.wpfd-property-item input[type="text"],
.wpfd-property-item input[type="number"],
.wpfd-property-item select {
width: 100%;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 13px;
}
.wpfd-property-item input[type="color"] {
width: 100%;
height: 35px;
padding: 2px;
border: 1px solid #ddd;
border-radius: 3px;
}
/ 编辑器底部状态栏 /
.wpfd-editor-statusbar {
background: #fff;
border-top: 1px solid #ccd0d4;
padding: 8px 15px;
font-size: 12px;
color: #666;
display: flex;
justify-content: space-between;
align-items: center;
}
/ 响应式设计 /
@media (max-width: 1200px) {
.wpfd-editor-wrapper {
height: 600px;
}
.wpfd-shapes-panel {
width: 180px;
}
.wpfd-properties-panel {
width: 250px;
}
}
@media (max-width: 768px) {
.wpfd-editor-main {
flex-direction: column;
}
.wpfd-shapes-panel {
width: 100%;
height: 150px;
border-right: none;
border-bottom: 1px solid #ccd0d4;
}
.wpfd-properties-panel {
width: 100%;
height: 200px;
border-left: none;
border-top: 1px solid #ccd0d4;
}
.wpfd-shapes-list {
grid-template-columns: repeat(4, 1fr);
}
}
/ 图标字体 /
@font-face {
font-family: 'wpfd-icons';
src: url('../fonts/wpfd-icons.woff2') format('woff2'),
url('../fonts/wpfd-icons.woff') format('woff');
font-weight: normal;
font-style: normal;
}
.wpfd-icon {
font-family: 'wpfd-icons';
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.wpfd-icon-rectangle:before { content: "e900"; }
.wpfd-icon-circle:before { content: "e901"; }
.wpfd-icon-diamond:before { content: "e902"; }
.wpfd-icon-arrow:before { content: "e903"; }
.wpfd-icon-text:before { content: "e904"; }
.wpfd-icon-line:before { content: "e905"; }
.wpfd-icon-save:before { content: "e906"; }
.wpfd-icon-export:before { content: "e907"; }
.wpfd-icon-undo:before { content: "e908"; }
.wpfd-icon-redo:before { content: "e909"; }
.wpfd-icon-zoom-in:before { content: "e90a"; }
.wpfd-icon-zoom-out:before { content: "e90b"; }
## 第五部分:实现REST API接口
### 5.1 创建REST API处理器
在`includes/class-rest-api.php`中实现API接口:
<?php
class WPFD_REST_API {
public static function init() {
add_action('rest_api_init', [__CLASS__, 'register_routes']);
}
public static function register_routes() {
// 图表CRUD接口
register_rest_route('wpfd/v1', '/diagrams', [
[
'methods' => 'GET',
'callback' => [__CLASS__, 'get_diagrams'],
'permission_callback' => [__CLASS__, 'check_permission'],
'args' => [
'per_page' => [
'default' => 20,
'validate_callback' => function($param) {
return is_numeric($param) && $param > 0 && $param <= 100;
}
],
'page' => [
'default' => 1,
'validate_callback' => function($param) {
return is_numeric($param) && $param > 0;
}
]
]
],
[
'methods' => 'POST',
'callback' => [__CLASS__, 'create_diagram'],
'permission_callback' => [__CLASS__, 'check_permission']
]
]);
register_rest_route('wpfd/v1', '/diagrams/(?P<id>d+)', [
[
'methods' => 'GET',
'callback' => [__CLASS__, 'get_diagram'],
'permission_callback' => [__CLASS__, 'check_permission']
],
[
'methods' => 'PUT',
'callback' => [__CLASS__, 'update_diagram'],
'permission_callback' => [__CLASS__, 'check_permission']
],
[
'methods' => 'DELETE',
'callback' => [__CLASS__, 'delete_diagram'],
'permission_callback' => [__CLASS__, 'check_permission']
]
]);
// 导出接口
register_rest_route('wpfd/v1', '/export/(?P<id>d+)', [
[
'methods' => 'GET',
'callback' => [__CLASS__, 'export_diagram'],
'permission_callback' => [__CLASS__, 'check_permission'],
'args' => [
'format' => [
'default' => 'png',
'validate_callback' => function($param) {
return in_array($param, ['png', 'jpg', 'svg', 'pdf']);
}
]
]
]
]);
// 搜索接口
register_rest_route('wpfd/v1', '/search', [
[
'methods' => 'GET',
'callback' => [__CLASS__, 'search_diagrams'],
'permission_callback' => [__CLASS__, 'check_permission']
]
]);
}
public static function check_permission($request) {
// 检查用户是否登录
if (!is_user_logged_in()) {
return new WP_Error('rest_forbidden', __('请先登录'), ['status' => 401]);
}
// 检查用户权限
$method = $request->get_method();
$user_id = get_current_user_id();
// 对于GET请求,允许所有登录用户访问
if ($method === 'GET') {
return true;
}
// 对于其他请求,需要编辑权限
if (!current_user_can('edit_posts')) {
return new WP_Error('rest_forbidden', __('权限不足'), ['status' => 403]);
}
return true;
}
public static function get_diagrams($request) {
$params = $request->get_params();
$per_page = intval($params['per_page']);
$page = intval($params['page']);
$offset = ($page - 1) * $per_page;
global $wpdb;
$table_name = $wpdb->prefix . 'wpfd_diagrams';
$user_id = get_current_user_id();
// 获取图表总数
$total = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE user_id = %d",
$user_id
)
);
// 获取图表列表
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT id, title, diagram_type, created_at, updated_at
FROM {$table_name}
WHERE user_id = %d
ORDER BY updated_at DESC
LIMIT %d OFFSET %d",
$user_id, $per_page, $offset
),
ARRAY_A
);
// 准备响应数据
$diagrams = [];
foreach ($results as $row) {
$diagrams[] = [
'id' => intval($row['id']),
'title' => $row['title'],
'type' => $row['diagram_type'],
'created_at' => $row['created_at'],
'updated_at' => $row['updated_at'],
'edit_url' => admin_url('admin.php?page=wpfd-edit&id=' . $row['id'])
];
}
$response = new WP_REST_Response($diagrams);
$response->header('X-WP-Total', $total);
$response->header('X-WP-TotalPages', ceil($total / $per_page));
return $response;
}
public static function get_diagram($request) {
$id = intval($request['id']);
$diagram = WPFD_Database::get_diagram($id);
if (!$diagram) {
return new WP_Error('not_found', __('图表不存在'), ['status' => 404]);
}
// 检查权限:只能访问自己的图表
if ($diagram['user_id'] != get_current_user_id() && !current_user_can('manage_options')) {
return new WP_Error('forbidden', __('无权访问此图表'), ['status' => 403]);
}
return rest_ensure_response($diagram);
}
public static function create_diagram($request) {
$data = $request->get_json_params();
// 验证数据
if (empty($data['title'])) {
return new WP_Error('invalid_data', __('标题不能为空'), ['status' => 400]);
}
if (empty($data['diagram_data'])) {
return new WP_Error('invalid_data', __('图表数据不能为空'), ['status' => 400]);
}
// 准备保存数据
$save_data = [
'title' => sanitize_text_field($data['title']),
'diagram_data' => $data['diagram_data'],
'diagram_type' => isset($data['diagram_type']) ? sanitize_text_field($data['diagram_type']) : 'flowchart',
'settings' => isset($data['settings']) ? $data['settings'] : []
];
// 如果有post_id,关联到文章
if (isset($data['post_id']) && $data['post_id


