文章目录[隐藏]
详细指南:开发WordPress内嵌在线流程图与思维导图协作工具
引言:为什么在WordPress中集成协作工具?
在当今数字化工作环境中,可视化协作工具已成为团队沟通和项目管理的重要组成部分。流程图和思维导图能够帮助团队清晰地表达复杂概念、规划项目流程和激发创意。然而,许多团队面临工具碎片化的问题——使用外部工具导致数据分散、协作不便和额外成本。
将在线流程图与思维导图工具直接集成到WordPress网站中,可以解决这些问题。用户无需离开网站即可创建、编辑和协作,所有数据集中存储,与现有用户系统无缝集成。本指南将详细介绍如何通过WordPress代码二次开发,实现这一功能强大的协作工具。
第一部分:项目规划与技术选型
1.1 功能需求分析
在开始开发前,我们需要明确工具的核心功能:
-
流程图功能:
- 基本图形绘制(矩形、圆形、菱形等)
- 连接线与箭头
- 文本编辑与格式化
- 拖拽与缩放界面
- 图层管理与分组
-
思维导图功能:
- 中心主题与分支节点
- 节点折叠/展开
- 主题样式与颜色
- 关系连接线
- 图标与图片插入
-
协作功能:
- 实时协同编辑
- 用户权限管理
- 版本历史与恢复
- 评论与批注系统
- 导出与分享功能
-
WordPress集成:
- 用户系统对接
- 数据存储与检索
- 短代码嵌入
- 媒体库集成
- 响应式设计
1.2 技术架构设计
基于功能需求,我们选择以下技术栈:
- 前端框架:React.js + Redux(用于复杂状态管理)
- 绘图库:JointJS或GoJS(专业图表库)
- 实时协作:Socket.io或Pusher(实时通信)
- WordPress集成:自定义插件架构
- 数据存储:WordPress自定义数据库表 + 文件系统
- 样式框架:Tailwind CSS(快速UI开发)
1.3 开发环境搭建
- 安装本地WordPress开发环境(推荐使用Local by Flywheel或Docker)
- 设置代码编辑器(VS Code推荐)
- 配置Node.js环境用于前端开发
- 安装Git进行版本控制
- 准备测试数据库
第二部分:WordPress插件基础架构
2.1 创建插件基本结构
首先,在WordPress的wp-content/plugins目录下创建插件文件夹collab-diagram-tool,并建立以下结构:
collab-diagram-tool/
├── collab-diagram-tool.php # 主插件文件
├── includes/ # PHP包含文件
│ ├── class-database.php # 数据库处理
│ ├── class-shortcodes.php # 短代码处理
│ ├── class-api.php # REST API端点
│ └── class-admin.php # 后台管理
├── assets/ # 静态资源
│ ├── css/
│ ├── js/
│ └── images/
├── src/ # React前端源码
│ ├── components/
│ ├── redux/
│ ├── utils/
│ └── App.js
├── templates/ # 前端模板
└── vendor/ # 第三方库
2.2 主插件文件配置
<?php
/**
* Plugin Name: 协作流程图与思维导图工具
* Plugin URI: https://yourwebsite.com/
* Description: 在WordPress中嵌入在线流程图与思维导图协作工具
* Version: 1.0.0
* Author: 您的名称
* License: GPL v2 or later
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('CDT_VERSION', '1.0.0');
define('CDT_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('CDT_PLUGIN_URL', plugin_dir_url(__FILE__));
// 自动加载类文件
spl_autoload_register(function ($class) {
$prefix = 'CDT_';
$base_dir = CDT_PLUGIN_DIR . 'includes/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . 'class-' . strtolower(str_replace('_', '-', $relative_class)) . '.php';
if (file_exists($file)) {
require $file;
}
});
// 初始化插件
function cdt_init() {
// 检查依赖
if (!function_exists('register_rest_route')) {
add_action('admin_notices', function() {
echo '<div class="notice notice-error"><p>协作流程图工具需要WordPress 4.7+版本支持REST API。</p></div>';
});
return;
}
// 初始化组件
CDT_Database::init();
CDT_Shortcodes::init();
CDT_API::init();
CDT_Admin::init();
// 加载文本域
load_plugin_textdomain('cdt', false, dirname(plugin_basename(__FILE__)) . '/languages/');
}
add_action('plugins_loaded', 'cdt_init');
// 激活/停用钩子
register_activation_hook(__FILE__, ['CDT_Database', 'create_tables']);
register_deactivation_hook(__FILE__, ['CDT_Database', 'cleanup']);
第三部分:数据库设计与实现
3.1 数据库表结构
我们需要创建多个表来存储图表数据、用户协作信息等:
class CDT_Database {
public static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 图表主表
$table_diagrams = $wpdb->prefix . 'cdt_diagrams';
$sql1 = "CREATE TABLE IF NOT EXISTS $table_diagrams (
id bigint(20) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
type enum('flowchart','mindmap') NOT NULL DEFAULT 'flowchart',
content longtext,
settings text,
created_by bigint(20) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
status varchar(20) DEFAULT 'draft',
PRIMARY KEY (id),
KEY created_by (created_by),
KEY status (status)
) $charset_collate;";
// 协作权限表
$table_collaborators = $wpdb->prefix . 'cdt_collaborators';
$sql2 = "CREATE TABLE IF NOT EXISTS $table_collaborators (
id bigint(20) NOT NULL AUTO_INCREMENT,
diagram_id bigint(20) NOT NULL,
user_id bigint(20) NOT NULL,
permission enum('view','edit','admin') NOT NULL DEFAULT 'view',
invited_by bigint(20) NOT NULL,
invited_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY diagram_user (diagram_id, user_id),
KEY user_id (user_id)
) $charset_collate;";
// 版本历史表
$table_versions = $wpdb->prefix . 'cdt_versions';
$sql3 = "CREATE TABLE IF NOT EXISTS $table_versions (
id bigint(20) NOT NULL AUTO_INCREMENT,
diagram_id bigint(20) NOT NULL,
version int(11) NOT NULL,
content longtext,
created_by bigint(20) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
change_log text,
PRIMARY KEY (id),
KEY diagram_version (diagram_id, version)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql1);
dbDelta($sql2);
dbDelta($sql3);
}
}
3.2 数据模型类
创建数据操作类来处理CRUD操作:
class CDT_Diagram_Model {
private $wpdb;
private $table;
public function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
$this->table = $wpdb->prefix . 'cdt_diagrams';
}
public function create($data) {
$defaults = [
'title' => '未命名图表',
'type' => 'flowchart',
'content' => '{}',
'settings' => '{}',
'created_by' => get_current_user_id(),
'status' => 'draft'
];
$data = wp_parse_args($data, $defaults);
$this->wpdb->insert($this->table, $data);
if ($this->wpdb->insert_id) {
$diagram_id = $this->wpdb->insert_id;
// 自动添加创建者为管理员
$this->add_collaborator($diagram_id, $data['created_by'], 'admin');
return $diagram_id;
}
return false;
}
public function update($id, $data) {
$data['updated_at'] = current_time('mysql');
return $this->wpdb->update($this->table, $data, ['id' => $id]);
}
public function get($id) {
return $this->wpdb->get_row(
$this->wpdb->prepare("SELECT * FROM $this->table WHERE id = %d", $id)
);
}
public function get_by_user($user_id, $limit = 20, $offset = 0) {
$collaborator_table = $this->wpdb->prefix . 'cdt_collaborators';
$query = $this->wpdb->prepare(
"SELECT d.*, c.permission
FROM $this->table d
INNER JOIN $collaborator_table c ON d.id = c.diagram_id
WHERE c.user_id = %d
ORDER BY d.updated_at DESC
LIMIT %d OFFSET %d",
$user_id, $limit, $offset
);
return $this->wpdb->get_results($query);
}
private function add_collaborator($diagram_id, $user_id, $permission) {
$table = $this->wpdb->prefix . 'cdt_collaborators';
return $this->wpdb->insert($table, [
'diagram_id' => $diagram_id,
'user_id' => $user_id,
'permission' => $permission,
'invited_by' => get_current_user_id()
]);
}
}
第四部分:前端编辑器开发
4.1 React应用架构
在src/目录下创建React应用:
// src/App.js
import React, { useState, useEffect } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './redux/reducers';
import DiagramEditor from './components/DiagramEditor';
import Toolbar from './components/Toolbar';
import Sidebar from './components/Sidebar';
import CollaborationPanel from './components/CollaborationPanel';
import './styles/main.css';
const store = createStore(rootReducer, applyMiddleware(thunk));
function App({ diagramId, userId, userPermission }) {
const [isLoaded, setIsLoaded] = useState(false);
const [diagramData, setDiagramData] = useState(null);
useEffect(() => {
// 加载图表数据
fetchDiagramData(diagramId).then(data => {
setDiagramData(data);
setIsLoaded(true);
});
}, [diagramId]);
if (!isLoaded) {
return <div className="loading">加载中...</div>;
}
return (
<Provider store={store}>
<div className="cdt-app">
<Toolbar
diagramId={diagramId}
permission={userPermission}
/>
<div className="cdt-main-area">
<Sidebar />
<DiagramEditor
data={diagramData}
diagramId={diagramId}
userId={userId}
/>
<CollaborationPanel
diagramId={diagramId}
permission={userPermission}
/>
</div>
</div>
</Provider>
);
}
export default App;
4.2 流程图编辑器组件
// src/components/DiagramEditor/FlowchartEditor.js
import React, { useRef, useEffect } from 'react';
import * as joint from 'jointjs';
import 'jointjs/dist/joint.css';
const FlowchartEditor = ({ data, onUpdate, readOnly }) => {
const containerRef = useRef(null);
const graphRef = useRef(null);
const paperRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
// 初始化JointJS图形
const graph = new joint.dia.Graph();
graphRef.current = graph;
// 创建画布
const paper = new joint.dia.Paper({
el: containerRef.current,
model: graph,
width: '100%',
height: '100%',
gridSize: 10,
drawGrid: true,
background: {
color: '#f8f9fa'
},
interactive: !readOnly
});
paperRef.current = paper;
// 加载现有数据
if (data && data.elements) {
graph.fromJSON(data);
}
// 监听变化
graph.on('change', () => {
if (onUpdate) {
onUpdate(graph.toJSON());
}
});
// 添加工具面板
if (!readOnly) {
initTools(paper, graph);
}
return () => {
paper.remove();
graph.clear();
};
}, [data, readOnly]);
const initTools = (paper, graph) => {
// 创建图形工具
const shapes = {
rectangle: new joint.shapes.standard.Rectangle(),
circle: new joint.shapes.standard.Circle(),
ellipse: new joint.shapes.standard.Ellipse(),
rhombus: new joint.shapes.standard.Rhombus()
};
// 设置默认样式
Object.values(shapes).forEach(shape => {
shape.attr({
body: {
fill: '#ffffff',
stroke: '#333333',
strokeWidth: 2
},
label: {
text: '文本',
fill: '#333333',
fontSize: 14,
fontFamily: 'Arial'
}
});
});
// 连接线
const link = new joint.shapes.standard.Link();
link.attr({
line: {
stroke: '#333333',
strokeWidth: 2,
targetMarker: {
type: 'path',
d: 'M 10 -5 0 0 10 5 z'
}
}
});
// 将工具暴露给全局,供工具栏使用
window.cdtTools = { shapes, link, graph, paper };
};
return (
<div className="flowchart-editor">
<div ref={containerRef} className="joint-paper-container" />
</div>
);
};
export default FlowchartEditor;
4.3 思维导图编辑器组件
// src/components/DiagramEditor/MindmapEditor.js
import React, { useState, useRef, useEffect } from 'react';
import MindNode from './MindNode';
const MindmapEditor = ({ data, onUpdate, readOnly }) => {
const [nodes, setNodes] = useState(data?.nodes || []);
const [connections, setConnections] = useState(data?.connections || []);
const containerRef = useRef(null);
// 添加新节点
const addNode = (parentId = null) => {
const newNode = {
id: `node_${Date.now()}`,
content: '新节点',
x: parentId ? 200 : 400,
y: parentId ? 150 : 300,
width: 120,
height: 40,
color: '#ffffff',
borderColor: '#4a90e2',
parentId
};
setNodes(prev => [...prev, newNode]);
if (parentId) {
setConnections(prev => [...prev, {
id: `conn_${Date.now()}`,
from: parentId,
to: newNode.id,
type: 'straight'
}]);
}
if (onUpdate) {
onUpdate({ nodes: [...nodes, newNode], connections });
}
};
// 更新节点
const updateNode = (id, updates) => {
const updatedNodes = nodes.map(node =>
node.id === id ? { ...node, ...updates } : node
);
setNodes(updatedNodes);
if (onUpdate) {
onUpdate({ nodes: updatedNodes, connections });
}
};
// 删除节点
const deleteNode = (id) => {
// 递归删除子节点
const getChildIds = (parentId) => {
const children = nodes.filter(n => n.parentId === parentId);
let ids = children.map(c => c.id);
children.forEach(child => {
ids = [...ids, ...getChildIds(child.id)];
});
return ids;
};
const idsToDelete = [id, ...getChildIds(id)];
const updatedNodes = nodes.filter(n => !idsToDelete.includes(n.id));
const updatedConnections = connections.filter(
c => !idsToDelete.includes(c.from) && !idsToDelete.includes(c.to)
);
setNodes(updatedNodes);
setConnections(updatedConnections);
if (onUpdate) {
onUpdate({ nodes: updatedNodes, connections: updatedConnections });
}
};
// 绘制连接线
useEffect(() => {
if (!containerRef.current) return;
const canvas = containerRef.current;
const ctx = canvas.getContext('2d');
// 设置Canvas尺寸
const resizeCanvas = () => {
const container = canvas.parentElement;
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
};
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// 绘制函数
const drawConnections = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#999';
ctx.lineWidth = 2;
connections.forEach(conn => {
const fromNode = nodes.find(n => n.id === conn.from);
const toNode = nodes.find(n => n.id === conn.to);
if (!fromNode || !toNode) return;
const startX = fromNode.x + fromNode.width;
const startY = fromNode.y + fromNode.height / 2;
const endX = toNode.x;
const endY = toNode.y + toNode.height / 2;
// 绘制贝塞尔曲线
ctx.beginPath();
ctx.moveTo(startX, startY);
const cp1x = startX + (endX - startX) / 3;
const cp2x = startX + 2 * (endX - startX) / 3;
ctx.bezierCurveTo(
cp1x, startY,
cp2x, endY,
endX, endY
);
ctx.stroke();
// 绘制箭头
const angle = Math.atan2(endY - startY, endX - startX);
const arrowLength = 10;
ctx.beginPath();
ctx.moveTo(endX, endY);
ctx.lineTo(
endX - arrowLength * Math.cos(angle - Math.PI / 6),
endY - arrowLength * Math.sin(angle - Math.PI / 6)
);
ctx.moveTo(endX, endY);
ctx.lineTo(
endX - arrowLength * Math.cos(angle + Math.PI / 6),
endY - arrowLength * Math.sin(angle + Math.PI / 6)
);
ctx.stroke();
});
};
drawConnections();
return () => {
window.removeEventListener('resize', resizeCanvas);
};
}, [nodes, connections]);
return (
<div className="mindmap-editor">
<canvas
ref={containerRef}
className="connections-canvas"
/>
<div className="nodes-container">
{nodes.map(node => (
<MindNode
key={node.id}
node={node}
onUpdate={updateNode}
onDelete={deleteNode}
onAddChild={() => addNode(node.id)}
readOnly={readOnly}
/>
))}
</div>
{!readOnly && (
<button
className="add-root-node"
onClick={() => addNode()}
>
+ 添加根节点
</button>
)}
</div>
);
};
export default MindmapEditor;
第五部分:实时协作功能实现
5.1 WebSocket服务器集成
由于WordPress本身不是实时服务器,我们需要集成WebSocket服务:
// includes/class-websocket.php
class CDT_WebSocket {
private static $instance = null;
private $server;
public static function init() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
add_action('init', [$this, 'start_websocket_server']);
add_action('wp_enqueue_scripts', [$this, 'enqueue_websocket_client']);
}
public function start_websocket_server() {
// 检查是否应该启动WebSocket服务器
if (defined('DOING_AJAX') && DOING_AJAX) {
return;
}
// 使用外部WebSocket服务或启动内置服务器
if (apply_filters('cdt_use_external_websocket', false)) {
$this->init_external_websocket();
} else {
$this->init_builtin_websocket();
}
}
private function init_external_websocket() {
// 集成Pusher或Socket.io服务
$service = get_option('cdt_websocket_service', 'pusher');
if ($service === 'pusher') {
$this->init_pusher();
} elseif ($service === 'socketio') {
$this->init_socketio();
}
}
private function init_pusher() {
// Pusher.com集成
$app_id = get_option('cdt_pusher_app_id');
$key = get_option('cdt_pusher_key');
$secret = get_option('cdt_pusher_secret');
$cluster = get_option('cdt_pusher_cluster', 'mt1');
if ($app_id && $key && $secret) {
// 注册Pusher PHP库
if (!class_exists('PusherPusher')) {
require_once CDT_PLUGIN_DIR . 'vendor/autoload.php';
}
$this->pusher = new PusherPusher(
$key,
$secret,
$app_id,
['cluster' => $cluster]
);
}
}
public function enqueue_websocket_client() {
if (is_singular() && has_shortcode(get_post()->post_content, 'collab_diagram')) {
$service = get_option('cdt_websocket_service', 'pusher');
if ($service === 'pusher') {
wp_enqueue_script(
'pusher-js',
'https://js.pusher.com/7.0/pusher.min.js',
[],
'7.0',
true
);
wp_add_inline_script('pusher-js', '
document.addEventListener("DOMContentLoaded", function() {
const pusher = new Pusher("' . get_option('cdt_pusher_key') . '", {
cluster: "' . get_option('cdt_pusher_cluster', 'mt1') . '"
});
window.cdtPusher = pusher;
});
');
}
}
}
// 发送实时更新
public function broadcast_update($diagram_id, $data, $exclude_user = null) {
$channel = 'cdt-diagram-' . $diagram_id;
$event = 'diagram-update';
if (isset($this->pusher)) {
$this->pusher->trigger($channel, $event, [
'data' => $data,
'timestamp' => time(),
'exclude' => $exclude_user
]);
}
}
}
5.2 前端协作逻辑
// src/utils/collaboration.js
class CollaborationManager {
constructor(diagramId, userId) {
this.diagramId = diagramId;
this.userId = userId;
this.pusher = null;
this.channel = null;
this.lastUpdateTime = 0;
this.updateQueue = [];
this.isProcessing = false;
this.initWebSocket();
}
initWebSocket() {
if (window.cdtPusher) {
this.pusher = window.cdtPusher;
this.channel = this.pusher.subscribe(`cdt-diagram-${this.diagramId}`);
this.channel.bind('diagram-update', (data) => {
// 排除自己发送的更新
if (data.exclude === this.userId) return;
this.handleRemoteUpdate(data.data);
});
this.channel.bind('user-joined', (data) => {
this.onUserJoined(data.user);
});
this.channel.bind('user-left', (data) => {
this.onUserLeft(data.user);
});
}
}
// 发送本地更新
sendUpdate(updateData, isImmediate = false) {
const now = Date.now();
// 防抖处理:避免发送过多更新
if (!isImmediate && now - this.lastUpdateTime < 100) {
this.updateQueue.push(updateData);
if (!this.isProcessing) {
this.processQueue();
}
return;
}
this.lastUpdateTime = now;
// 发送到服务器
fetch(cdtApi.diagramUpdate(this.diagramId), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': cdtApi.nonce
},
body: JSON.stringify({
update: updateData,
userId: this.userId,
timestamp: now
})
}).then(response => response.json())
.then(data => {
if (data.success && this.pusher) {
// 广播给其他用户,排除自己
this.pusher.trigger(`cdt-diagram-${this.diagramId}`, 'diagram-update', {
data: updateData,
exclude: this.userId
});
}
});
}
processQueue() {
if (this.updateQueue.length === 0) {
this.isProcessing = false;
return;
}
this.isProcessing = true;
// 合并队列中的更新
const mergedUpdate = this.mergeUpdates(this.updateQueue);
this.updateQueue = [];
// 发送合并后的更新
this.sendUpdate(mergedUpdate, true);
// 继续处理可能的新更新
setTimeout(() => this.processQueue(), 50);
}
mergeUpdates(updates) {
// 根据具体数据结构实现合并逻辑
// 这里简化为返回最后一个更新
return updates[updates.length - 1];
}
handleRemoteUpdate(remoteData) {
// 处理远程更新,与本地状态合并
// 这里需要根据具体业务逻辑实现冲突解决
if (this.onRemoteUpdate) {
this.onRemoteUpdate(remoteData);
}
}
onUserJoined(user) {
console.log(`用户 ${user.name} 加入了协作`);
if (this.onUserJoinedCallback) {
this.onUserJoinedCallback(user);
}
}
onUserLeft(user) {
console.log(`用户 ${user.name} 离开了协作`);
if (this.onUserLeftCallback) {
this.onUserLeftCallback(user);
}
}
disconnect() {
if (this.channel) {
this.channel.unbind_all();
this.channel.unsubscribe();
}
}
}
export default CollaborationManager;
5.3 协同光标与选择指示
// src/components/Collaboration/CursorIndicator.js
import React, { useEffect, useRef } from 'react';
const CursorIndicator = ({ userId, userName, color, x, y, isActive }) => {
const cursorRef = useRef(null);
useEffect(() => {
if (cursorRef.current && x !== undefined && y !== undefined) {
cursorRef.current.style.transform = `translate(${x}px, ${y}px)`;
}
}, [x, y]);
if (!isActive) return null;
return (
<div
ref={cursorRef}
className="cursor-indicator"
style={{
'--cursor-color': color,
position: 'absolute',
left: 0,
top: 0,
zIndex: 1000,
pointerEvents: 'none',
transition: 'transform 0.1s ease-out'
}}
>
<svg width="24" height="24" viewBox="0 0 24 24">
<path
d="M3 3L10 18L13 13L18 10L3 3Z"
fill={color}
stroke="#fff"
strokeWidth="1"
/>
</svg>
<div className="cursor-label" style={{
backgroundColor: color,
color: '#fff',
padding: '2px 6px',
borderRadius: '3px',
fontSize: '12px',
whiteSpace: 'nowrap',
transform: 'translate(5px, 5px)'
}}>
{userName}
</div>
</div>
);
};
// 用户状态管理
const UserPresenceManager = ({ diagramId, currentUser }) => {
const [users, setUsers] = useState([]);
const collaborationRef = useRef(null);
useEffect(() => {
collaborationRef.current = new CollaborationManager(diagramId, currentUser.id);
collaborationRef.current.onUserJoinedCallback = (user) => {
setUsers(prev => {
const exists = prev.find(u => u.id === user.id);
if (exists) return prev;
return [...prev, { ...user, active: true, lastSeen: Date.now() }];
});
};
collaborationRef.current.onUserLeftCallback = (user) => {
setUsers(prev =>
prev.map(u =>
u.id === user.id
? { ...u, active: false, lastSeen: Date.now() }
: u
)
);
};
// 定期清理不活跃用户
const cleanupInterval = setInterval(() => {
const now = Date.now();
setUsers(prev =>
prev.filter(u => u.active || now - u.lastSeen < 300000) // 5分钟
);
}, 60000);
return () => {
clearInterval(cleanupInterval);
if (collaborationRef.current) {
collaborationRef.current.disconnect();
}
};
}, [diagramId, currentUser.id]);
// 发送光标位置
const sendCursorPosition = useCallback((x, y) => {
if (collaborationRef.current) {
collaborationRef.current.sendUpdate({
type: 'cursor_move',
position: { x, y },
userId: currentUser.id,
timestamp: Date.now()
}, true); // 立即发送光标更新
}
}, [currentUser.id]);
return (
<div className="user-presence">
<div className="active-users">
{users.filter(u => u.active).map(user => (
<div key={user.id} className="user-badge" style={{
backgroundColor: user.color,
color: '#fff'
}}>
{user.name.charAt(0)}
</div>
))}
</div>
</div>
);
};
第六部分:WordPress REST API集成
6.1 创建自定义API端点
// includes/class-api.php
class CDT_API {
public static function init() {
add_action('rest_api_init', [self::class, 'register_routes']);
}
public static function register_routes() {
// 图表CRUD端点
register_rest_route('cdt/v1', '/diagrams', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_diagrams'],
'permission_callback' => [self::class, 'check_permission'],
'args' => [
'page' => [
'required' => false,
'default' => 1,
'sanitize_callback' => 'absint'
],
'per_page' => [
'required' => false,
'default' => 20,
'sanitize_callback' => 'absint'
]
]
],
[
'methods' => 'POST',
'callback' => [self::class, 'create_diagram'],
'permission_callback' => [self::class, 'check_permission']
]
]);
register_rest_route('cdt/v1', '/diagrams/(?P<id>d+)', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_diagram'],
'permission_callback' => [self::class, 'check_diagram_permission']
],
[
'methods' => 'PUT',
'callback' => [self::class, 'update_diagram'],
'permission_callback' => [self::class, 'check_diagram_edit_permission']
],
[
'methods' => 'DELETE',
'callback' => [self::class, 'delete_diagram'],
'permission_callback' => [self::class, 'check_diagram_admin_permission']
]
]);
// 实时更新端点
register_rest_route('cdt/v1', '/diagrams/(?P<id>d+)/update', [
'methods' => 'POST',
'callback' => [self::class, 'update_diagram_content'],
'permission_callback' => [self::class, 'check_diagram_edit_permission']
]);
// 协作管理端点
register_rest_route('cdt/v1', '/diagrams/(?P<id>d+)/collaborators', [
'methods' => 'GET',
'callback' => [self::class, 'get_collaborators'],
'permission_callback' => [self::class, 'check_diagram_permission']
]);
register_rest_route('cdt/v1', '/diagrams/(?P<id>d+)/collaborators/(?P<user_id>d+)', [
'methods' => 'PUT',
'callback' => [self::class, 'update_collaborator'],
'permission_callback' => [self::class, 'check_diagram_admin_permission']
]);
}
public static function check_permission($request) {
return is_user_logged_in();
}
public static function check_diagram_permission($request) {
$diagram_id = $request->get_param('id');
