文章目录[隐藏]
一步步实现:为WordPress打造内嵌的在线流程图绘制与原型设计工具
引言:为什么WordPress需要内置流程图与原型设计工具?
在当今数字化时代,可视化沟通已成为网站开发、项目管理和内容创作中不可或缺的一环。无论是网站架构规划、用户流程设计,还是产品原型展示,流程图和原型设计工具都能显著提高工作效率和沟通效果。
然而,大多数WordPress用户目前面临一个共同困境:当需要在文章中插入流程图或原型设计时,他们不得不依赖外部工具(如Lucidchart、Figma或Draw.io)创建图表,然后以图片形式导入WordPress。这种方法存在明显缺陷:无法直接编辑、图片质量损失、缺乏交互性,并且破坏了内容创作的一体化体验。
本文将通过详细的代码实现步骤,展示如何为WordPress打造一个完全内嵌的在线流程图绘制与原型设计工具,让用户无需离开WordPress后台即可创建、编辑和发布专业的可视化图表。
第一部分:项目规划与技术选型
1.1 功能需求分析
在开始编码之前,我们需要明确工具的核心功能:
- 基本绘图功能:支持常见流程图元素(矩形、圆形、菱形、箭头等)
- 原型设计组件:按钮、输入框、导航栏等UI元素库
- 实时协作:支持多用户同时编辑(可选高级功能)
- 导出与分享:支持PNG、SVG、JSON等多种格式导出
- WordPress集成:与文章编辑器无缝对接,支持短代码嵌入
- 版本控制:保存编辑历史,支持版本回退
- 响应式设计:确保在不同设备上都能良好工作
1.2 技术架构设计
考虑到开发效率和功能完整性,我们采用混合技术方案:
- 前端绘图库:使用开源的Draw.io(mxGraph)核心库,这是一个功能强大且成熟的图形绘制库
- WordPress集成:通过自定义插件方式集成到WordPress
- 数据存储:使用WordPress自定义数据表结合文章元数据
- 后端API:REST API处理图表保存、加载和用户权限管理
1.3 开发环境准备
在开始开发前,确保你的环境满足以下要求:
- WordPress 5.0+(支持Gutenberg编辑器)
- PHP 7.4+
- MySQL 5.6+
- 基本的Web开发知识(HTML、CSS、JavaScript、PHP)
第二部分:创建WordPress插件基础框架
2.1 初始化插件文件结构
首先,在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 # 插件卸载脚本
2.2 编写主插件文件
创建主插件文件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']);
第三部分:实现数据库层
3.1 创建数据库表
在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编辑器
4.1 引入mxGraph库
由于Draw.io基于mxGraph库,我们需要将其集成到插件中。可以从GitHub获取mxGraph库:
- 访问 https://github.com/jgraph/mxgraph
- 下载最新版本
- 将
javascript目录复制到public/js/mxgraph/
4.2 创建编辑器界面
在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;
}
wpfd-editor-container {
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
