首页 / 应用软件 / 详细指南,开发网站内嵌的在线流程图与思维导图协作工具

详细指南,开发网站内嵌的在线流程图与思维导图协作工具

详细指南:开发WordPress内嵌在线流程图与思维导图协作工具

引言:为什么在WordPress中集成协作工具?

在当今数字化工作环境中,可视化协作工具已成为团队沟通和项目管理的重要组成部分。流程图和思维导图能够帮助团队清晰地表达复杂概念、规划项目流程和激发创意。然而,许多团队面临工具碎片化的问题——使用外部工具导致数据分散、协作不便和额外成本。

将在线流程图与思维导图工具直接集成到WordPress网站中,可以解决这些问题。用户无需离开网站即可创建、编辑和协作,所有数据集中存储,与现有用户系统无缝集成。本指南将详细介绍如何通过WordPress代码二次开发,实现这一功能强大的协作工具。

第一部分:项目规划与技术选型

1.1 功能需求分析

在开始开发前,我们需要明确工具的核心功能:

  1. 流程图功能

    • 基本图形绘制(矩形、圆形、菱形等)
    • 连接线与箭头
    • 文本编辑与格式化
    • 拖拽与缩放界面
    • 图层管理与分组
  2. 思维导图功能

    • 中心主题与分支节点
    • 节点折叠/展开
    • 主题样式与颜色
    • 关系连接线
    • 图标与图片插入
  3. 协作功能

    • 实时协同编辑
    • 用户权限管理
    • 版本历史与恢复
    • 评论与批注系统
    • 导出与分享功能
  4. WordPress集成

    • 用户系统对接
    • 数据存储与检索
    • 短代码嵌入
    • 媒体库集成
    • 响应式设计

1.2 技术架构设计

基于功能需求,我们选择以下技术栈:

  • 前端框架:React.js + Redux(用于复杂状态管理)
  • 绘图库:JointJS或GoJS(专业图表库)
  • 实时协作:Socket.io或Pusher(实时通信)
  • WordPress集成:自定义插件架构
  • 数据存储:WordPress自定义数据库表 + 文件系统
  • 样式框架:Tailwind CSS(快速UI开发)

1.3 开发环境搭建

  1. 安装本地WordPress开发环境(推荐使用Local by Flywheel或Docker)
  2. 设置代码编辑器(VS Code推荐)
  3. 配置Node.js环境用于前端开发
  4. 安装Git进行版本控制
  5. 准备测试数据库

第二部分: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');
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5194.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

工作时间:周一至周五,9:00-17:30,节假日休息
返回顶部