首页 / 应用软件 / 实战教程,集成网站实时协同编辑文档与电子表格应用

实战教程,集成网站实时协同编辑文档与电子表格应用

实战教程:集成网站实时协同编辑文档与电子表格应用,通过WordPress程序的代码二次开发实现常用互联网小工具功能

引言:为什么需要实时协同编辑功能?

在当今数字化工作环境中,实时协同编辑已成为提高团队效率的关键工具。无论是远程团队协作、在线教育还是客户服务,能够多人同时编辑文档和电子表格的功能都大大提升了工作效率和沟通效果。然而,对于许多中小型企业和个人网站所有者来说,集成专业的协同编辑工具往往面临高昂的成本和技术门槛。

WordPress作为全球最流行的内容管理系统,拥有超过40%的网站市场份额,其强大的可扩展性为我们提供了一个理想的平台。通过代码二次开发,我们可以在WordPress网站上集成实时协同编辑功能,同时实现其他常用互联网小工具,从而打造一个功能全面、成本可控的协作平台。

本教程将详细指导您如何通过WordPress代码二次开发,实现文档和电子表格的实时协同编辑功能,并集成其他实用工具,最终打造一个功能丰富的在线协作环境。

第一部分:准备工作与环境搭建

1.1 开发环境要求

在开始开发之前,我们需要确保具备以下环境:

  • WordPress 5.8或更高版本
  • PHP 7.4或更高版本(建议8.0+)
  • MySQL 5.6或更高版本
  • 支持WebSocket的服务器环境(Nginx或Apache)
  • SSL证书(HTTPS协议对于实时通信至关重要)
  • 代码编辑器(如VS Code、Sublime Text等)

1.2 创建自定义插件框架

首先,我们需要创建一个自定义插件来容纳所有功能代码:

  1. 在WordPress的wp-content/plugins/目录下创建新文件夹real-time-collab-tools
  2. 在该文件夹中创建主插件文件real-time-collab-tools.php
  3. 添加插件基本信息:
<?php
/**
 * Plugin Name: 实时协同编辑工具套件
 * Plugin URI: https://yourwebsite.com/
 * Description: 为WordPress网站添加实时协同编辑文档和电子表格功能,集成常用互联网小工具
 * Version: 1.0.0
 * Author: 您的名称
 * License: GPL v2 or later
 * Text Domain: rt-collab-tools
 */

// 防止直接访问
if (!defined('ABSPATH')) {
    exit;
}

// 定义插件常量
define('RT_COLLAB_VERSION', '1.0.0');
define('RT_COLLAB_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('RT_COLLAB_PLUGIN_URL', plugin_dir_url(__FILE__));

// 初始化插件
require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-init.php';
  1. 创建初始化类文件includes/class-init.php
<?php
class RT_Collab_Init {
    
    private static $instance = null;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    private function __construct() {
        $this->init_hooks();
        $this->load_dependencies();
    }
    
    private function init_hooks() {
        // 注册激活和停用钩子
        register_activation_hook(__FILE__, array($this, 'activate'));
        register_deactivation_hook(__FILE__, array($this, 'deactivate'));
        
        // 初始化插件
        add_action('plugins_loaded', array($this, 'init_plugin'));
    }
    
    private function load_dependencies() {
        // 加载依赖文件
        require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-websocket-server.php';
        require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-document-editor.php';
        require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-spreadsheet-editor.php';
        require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-tools-manager.php';
        require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-database.php';
    }
    
    public function activate() {
        // 创建必要的数据库表
        RT_Collab_Database::create_tables();
        
        // 设置默认选项
        update_option('rt_collab_version', RT_COLLAB_VERSION);
    }
    
    public function deactivate() {
        // 清理临时数据
        // 注意:不删除用户数据
    }
    
    public function init_plugin() {
        // 初始化各个组件
        RT_WebSocket_Server::get_instance();
        RT_Document_Editor::get_instance();
        RT_Spreadsheet_Editor::get_instance();
        RT_Tools_Manager::get_instance();
        
        // 加载文本域
        load_plugin_textdomain('rt-collab-tools', false, dirname(plugin_basename(__FILE__)) . '/languages');
    }
}

// 初始化插件
RT_Collab_Init::get_instance();

第二部分:实现WebSocket实时通信服务器

2.1 WebSocket服务器基础架构

实时协同编辑的核心是WebSocket通信。我们将使用PHP的Ratchet库来创建WebSocket服务器:

  1. 首先通过Composer安装Ratchet库:

    cd /path/to/your/plugin
    composer require cboden/ratchet
  2. 创建WebSocket服务器类includes/class-websocket-server.php
<?php
require_once RT_COLLAB_PLUGIN_DIR . 'vendor/autoload.php';

use RatchetMessageComponentInterface;
use RatchetConnectionInterface;
use RatchetServerIoServer;
use RatchetHttpHttpServer;
use RatchetWebSocketWsServer;

class RT_WebSocket_Server implements MessageComponentInterface {
    
    protected $clients;
    protected $documentSessions;
    protected $spreadsheetSessions;
    private static $instance = null;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function __construct() {
        $this->clients = new SplObjectStorage;
        $this->documentSessions = [];
        $this->spreadsheetSessions = [];
        
        // 启动WebSocket服务器
        $this->start_server();
    }
    
    private function start_server() {
        // 仅在特定条件下启动服务器(如通过WP-CLI)
        if (defined('WP_CLI') && WP_CLI) {
            $port = get_option('rt_collab_websocket_port', 8080);
            $server = IoServer::factory(
                new HttpServer(
                    new WsServer($this)
                ),
                $port
            );
            
            // 在后台运行服务器
            add_action('init', function() use ($server) {
                if (current_user_can('manage_options')) {
                    $server->run();
                }
            });
        }
    }
    
    public function onOpen(ConnectionInterface $conn) {
        // 存储新连接
        $this->clients->attach($conn);
        
        echo "新连接: {$conn->resourceId}n";
    }
    
    public function onMessage(ConnectionInterface $from, $msg) {
        // 解析消息
        $data = json_decode($msg, true);
        
        if (!$data || !isset($data['type'])) {
            return;
        }
        
        // 根据消息类型处理
        switch ($data['type']) {
            case 'join_document':
                $this->handleJoinDocument($from, $data);
                break;
            case 'document_edit':
                $this->handleDocumentEdit($from, $data);
                break;
            case 'join_spreadsheet':
                $this->handleJoinSpreadsheet($from, $data);
                break;
            case 'spreadsheet_edit':
                $this->handleSpreadsheetEdit($from, $data);
                break;
            case 'cursor_move':
                $this->handleCursorMove($from, $data);
                break;
        }
    }
    
    private function handleJoinDocument($conn, $data) {
        $docId = $data['document_id'];
        $userId = $data['user_id'];
        
        if (!isset($this->documentSessions[$docId])) {
            $this->documentSessions[$docId] = [
                'clients' => [],
                'content' => '',
                'revision' => 0
            ];
        }
        
        // 添加客户端到会话
        $this->documentSessions[$docId]['clients'][$conn->resourceId] = [
            'connection' => $conn,
            'user_id' => $userId,
            'cursor_position' => 0
        ];
        
        // 发送当前文档内容给新用户
        $conn->send(json_encode([
            'type' => 'document_content',
            'content' => $this->documentSessions[$docId]['content'],
            'revision' => $this->documentSessions[$docId]['revision']
        ]));
        
        // 通知其他用户有新成员加入
        $this->broadcastToDocument($docId, $conn->resourceId, [
            'type' => 'user_joined',
            'user_id' => $userId,
            'total_users' => count($this->documentSessions[$docId]['clients'])
        ]);
    }
    
    private function handleDocumentEdit($conn, $data) {
        $docId = $data['document_id'];
        
        if (!isset($this->documentSessions[$docId])) {
            return;
        }
        
        // 应用操作转换(OT)算法处理并发编辑
        $transformedOperation = $this->transformOperation(
            $data['operation'],
            $this->documentSessions[$docId]['revision']
        );
        
        // 更新文档内容
        $this->documentSessions[$docId]['content'] = $this->applyOperation(
            $this->documentSessions[$docId]['content'],
            $transformedOperation
        );
        
        $this->documentSessions[$docId]['revision']++;
        
        // 广播编辑操作给其他用户
        $this->broadcastToDocument($docId, $conn->resourceId, [
            'type' => 'document_update',
            'operation' => $transformedOperation,
            'revision' => $this->documentSessions[$docId]['revision'],
            'user_id' => $data['user_id']
        ]);
    }
    
    private function broadcastToDocument($docId, $excludeClientId, $message) {
        if (!isset($this->documentSessions[$docId])) {
            return;
        }
        
        foreach ($this->documentSessions[$docId]['clients'] as $clientId => $client) {
            if ($clientId != $excludeClientId) {
                $client['connection']->send(json_encode($message));
            }
        }
    }
    
    public function onClose(ConnectionInterface $conn) {
        // 从所有会话中移除客户端
        foreach ($this->documentSessions as $docId => $session) {
            if (isset($session['clients'][$conn->resourceId])) {
                unset($this->documentSessions[$docId]['clients'][$conn->resourceId]);
                
                // 通知其他用户
                $this->broadcastToDocument($docId, $conn->resourceId, [
                    'type' => 'user_left',
                    'user_id' => $session['clients'][$conn->resourceId]['user_id'],
                    'total_users' => count($this->documentSessions[$docId]['clients'])
                ]);
            }
        }
        
        $this->clients->detach($conn);
        echo "连接关闭: {$conn->resourceId}n";
    }
    
    public function onError(ConnectionInterface $conn, Exception $e) {
        echo "错误: {$e->getMessage()}n";
        $conn->close();
    }
    
    // 简化版的OT操作转换(实际项目应使用完整OT算法)
    private function transformOperation($operation, $revision) {
        // 这里实现操作转换逻辑
        // 由于篇幅限制,这里返回原始操作
        return $operation;
    }
    
    private function applyOperation($content, $operation) {
        // 应用操作到内容
        // 简化实现
        if ($operation['type'] == 'insert') {
            return substr($content, 0, $operation['position']) . 
                   $operation['text'] . 
                   substr($content, $operation['position']);
        }
        
        return $content;
    }
}

2.2 数据库设计与数据持久化

创建数据库表来存储文档和电子表格数据:

<?php
class RT_Collab_Database {
    
    public static function create_tables() {
        global $wpdb;
        
        $charset_collate = $wpdb->get_charset_collate();
        
        // 文档表
        $documents_table = $wpdb->prefix . 'rt_collab_documents';
        $sql_documents = "CREATE TABLE IF NOT EXISTS $documents_table (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            title varchar(255) NOT NULL,
            content longtext NOT NULL,
            owner_id bigint(20) NOT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            revision int(11) DEFAULT 0,
            permissions varchar(20) DEFAULT 'private',
            PRIMARY KEY (id),
            KEY owner_id (owner_id)
        ) $charset_collate;";
        
        // 电子表格表
        $spreadsheets_table = $wpdb->prefix . 'rt_collab_spreadsheets';
        $sql_spreadsheets = "CREATE TABLE IF NOT EXISTS $spreadsheets_table (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            title varchar(255) NOT NULL,
            data longtext NOT NULL,
            owner_id bigint(20) NOT NULL,
            rows int(11) DEFAULT 100,
            columns int(11) DEFAULT 26,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            permissions varchar(20) DEFAULT 'private',
            PRIMARY KEY (id),
            KEY owner_id (owner_id)
        ) $charset_collate;";
        
        // 文档访问记录表
        $document_access_table = $wpdb->prefix . 'rt_collab_document_access';
        $sql_document_access = "CREATE TABLE IF NOT EXISTS $document_access_table (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            document_id bigint(20) NOT NULL,
            user_id bigint(20) NOT NULL,
            last_access datetime DEFAULT CURRENT_TIMESTAMP,
            permission_level varchar(20) DEFAULT 'view',
            PRIMARY KEY (id),
            UNIQUE KEY document_user (document_id, user_id),
            KEY user_id (user_id)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql_documents);
        dbDelta($sql_spreadsheets);
        dbDelta($sql_document_access);
    }
}

第三部分:实现文档协同编辑器

3.1 前端编辑器界面

创建文档编辑器前端界面:

<?php
class RT_Document_Editor {
    
    private static $instance = null;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    private function __construct() {
        add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
        add_shortcode('collab_document', array($this, 'document_shortcode'));
        add_action('wp_ajax_save_document', array($this, 'save_document'));
        add_action('wp_ajax_nopriv_save_document', array($this, 'save_document'));
    }
    
    public function enqueue_scripts() {
        // 仅在有需要的页面加载
        if ($this->is_editor_page()) {
            // 加载CodeMirror编辑器
            wp_enqueue_style('codemirror-css', 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.0/codemirror.min.css');
            wp_enqueue_script('codemirror-js', 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.0/codemirror.min.js', array(), null, true);
            
            // 加载协同编辑前端脚本
            wp_enqueue_script('rt-collab-document', RT_COLLAB_PLUGIN_URL . 'assets/js/document-editor.js', 
                array('jquery', 'codemirror-js'), RT_COLLAB_VERSION, true);
            
            // 本地化脚本
            wp_localize_script('rt-collab-document', 'rtCollabConfig', array(
                'ajax_url' => admin_url('admin-ajax.php'),
                'websocket_url' => $this->get_websocket_url(),
                'current_user' => get_current_user_id(),
                'nonce' => wp_create_nonce('rt_collab_nonce')
            ));
        }
    }
    
    public function document_shortcode($atts) {
        $atts = shortcode_atts(array(
            'id' => 0,
            'title' => '协同文档',
            'height' => '500px'
        ), $atts, 'collab_document');
        
        if (!$atts['id']) {
            return '<p>请指定文档ID</p>';
        }
        
        // 检查用户权限
        if (!$this->check_document_access($atts['id'])) {
            return '<p>您没有权限访问此文档</p>';
        }
        
        ob_start();
        ?>
        <div class="rt-collab-document-editor" data-document-id="<?php echo esc_attr($atts['id']); ?>">
            <div class="document-header">
                <h3><?php echo esc_html($atts['title']); ?></h3>
                <div class="document-toolbar">
                    <button class="btn-save">保存</button>
                    <button class="btn-share">分享</button>
                    <span class="user-count">在线用户: <span class="count">1</span></span>
                </div>
            </div>

attr($atts['height']); ?>">

            <textarea id="document-editor-<?php echo esc_attr($atts['id']); ?>" class="document-editor"></textarea>
        </div>
        <div class="document-users">
            <h4>当前在线用户</h4>
            <ul class="user-list">
                <!-- 用户列表将通过JavaScript动态生成 -->
            </ul>
        </div>
        <div class="document-revision">
            版本: <span class="revision-number">0</span>
        </div>
    </div>
    <?php
    return ob_get_clean();
}

private function check_document_access($document_id) {
    // 简化权限检查,实际应更复杂
    if (is_user_logged_in()) {
        return true;
    }
    
    // 检查是否为公开文档
    global $wpdb;
    $table_name = $wpdb->prefix . 'rt_collab_documents';
    $document = $wpdb->get_row($wpdb->prepare(
        "SELECT permissions FROM $table_name WHERE id = %d",
        $document_id
    ));
    
    return $document && $document->permissions === 'public';
}

private function get_websocket_url() {
    $protocol = is_ssl() ? 'wss://' : 'ws://';
    $host = $_SERVER['HTTP_HOST'];
    $port = get_option('rt_collab_websocket_port', 8080);
    
    return $protocol . $host . ':' . $port;
}

private function is_editor_page() {
    global $post;
    if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'collab_document')) {
        return true;
    }
    return false;
}

public function save_document() {
    // 验证nonce
    if (!wp_verify_nonce($_POST['nonce'], 'rt_collab_nonce')) {
        wp_die('安全验证失败');
    }
    
    $document_id = intval($_POST['document_id']);
    $content = wp_kses_post($_POST['content']);
    $user_id = get_current_user_id();
    
    // 保存到数据库
    global $wpdb;
    $table_name = $wpdb->prefix . 'rt_collab_documents';
    
    $result = $wpdb->update(
        $table_name,
        array(
            'content' => $content,
            'updated_at' => current_time('mysql')
        ),
        array('id' => $document_id),
        array('%s', '%s'),
        array('%d')
    );
    
    if ($result !== false) {
        wp_send_json_success(array('message' => '文档保存成功'));
    } else {
        wp_send_json_error(array('message' => '保存失败'));
    }
}

}


### 3.2 前端JavaScript协同逻辑

创建`assets/js/document-editor.js`:

(function($) {

'use strict';

class DocumentEditor {
    constructor(element) {
        this.element = element;
        this.documentId = element.data('document-id');
        this.editor = null;
        this.websocket = null;
        this.userId = rtCollabConfig.current_user;
        this.revision = 0;
        this.pendingOperations = [];
        this.isApplyingRemote = false;
        
        this.init();
    }
    
    init() {
        this.initEditor();
        this.initWebSocket();
        this.bindEvents();
        this.loadDocument();
    }
    
    initEditor() {
        // 初始化CodeMirror编辑器
        this.editor = CodeMirror.fromTextArea(
            this.element.find('.document-editor')[0],
            {
                lineNumbers: true,
                mode: 'text/html',
                theme: 'default',
                lineWrapping: true,
                autofocus: true
            }
        );
        
        // 监听本地编辑
        this.editor.on('change', (instance, changeObj) => {
            this.handleLocalEdit(changeObj);
        });
        
        // 监听光标移动
        this.editor.on('cursorActivity', (instance) => {
            this.sendCursorPosition();
        });
    }
    
    initWebSocket() {
        this.websocket = new WebSocket(rtCollabConfig.websocket_url);
        
        this.websocket.onopen = () => {
            console.log('WebSocket连接已建立');
            this.joinDocument();
        };
        
        this.websocket.onmessage = (event) => {
            this.handleWebSocketMessage(JSON.parse(event.data));
        };
        
        this.websocket.onerror = (error) => {
            console.error('WebSocket错误:', error);
        };
        
        this.websocket.onclose = () => {
            console.log('WebSocket连接已关闭');
            // 尝试重新连接
            setTimeout(() => this.initWebSocket(), 3000);
        };
    }
    
    joinDocument() {
        this.websocket.send(JSON.stringify({
            type: 'join_document',
            document_id: this.documentId,
            user_id: this.userId
        }));
    }
    
    handleLocalEdit(changeObj) {
        if (this.isApplyingRemote) {
            return;
        }
        
        const operation = {
            type: 'edit',
            revision: this.revision,
            changes: [{
                from: changeObj.from,
                to: changeObj.to,
                text: changeObj.text,
                removed: changeObj.removed
            }]
        };
        
        // 发送到服务器
        this.websocket.send(JSON.stringify({
            type: 'document_edit',
            document_id: this.documentId,
            user_id: this.userId,
            operation: operation
        }));
        
        // 添加到待处理队列
        this.pendingOperations.push(operation);
    }
    
    handleWebSocketMessage(message) {
        switch (message.type) {
            case 'document_content':
                this.loadContent(message.content);
                this.revision = message.revision;
                break;
                
            case 'document_update':
                this.applyRemoteEdit(message.operation, message.user_id);
                this.revision = message.revision;
                break;
                
            case 'user_joined':
                this.updateUserList(message);
                break;
                
            case 'user_left':
                this.updateUserList(message);
                break;
                
            case 'cursor_position':
                this.showRemoteCursor(message);
                break;
        }
    }
    
    loadContent(content) {
        this.isApplyingRemote = true;
        this.editor.setValue(content);
        this.isApplyingRemote = false;
    }
    
    applyRemoteEdit(operation, userId) {
        if (userId === this.userId) {
            // 自己的操作,从待处理队列中移除
            this.pendingOperations = this.pendingOperations.filter(op => 
                op.revision !== operation.revision
            );
            return;
        }
        
        this.isApplyingRemote = true;
        
        // 应用远程编辑
        operation.changes.forEach(change => {
            this.editor.replaceRange(
                change.text,
                change.from,
                change.to
            );
        });
        
        this.isApplyingRemote = false;
        
        // 更新待处理操作
        this.pendingOperations = this.pendingOperations.map(op => 
            this.transformOperation(op, operation)
        );
    }
    
    transformOperation(localOp, remoteOp) {
        // 简化版操作转换
        // 实际应实现完整的OT算法
        return localOp;
    }
    
    sendCursorPosition() {
        const cursor = this.editor.getCursor();
        this.websocket.send(JSON.stringify({
            type: 'cursor_move',
            document_id: this.documentId,
            user_id: this.userId,
            position: cursor
        }));
    }
    
    showRemoteCursor(message) {
        // 显示其他用户的光标位置
        // 实现光标可视化
    }
    
    updateUserList(message) {
        const userList = this.element.find('.user-list');
        const countElement = this.element.find('.user-count .count');
        
        countElement.text(message.total_users);
        
        // 更新用户列表显示
        // 实际实现中应从服务器获取完整用户列表
    }
    
    loadDocument() {
        // 从服务器加载文档
        $.ajax({
            url: rtCollabConfig.ajax_url,
            method: 'POST',
            data: {
                action: 'get_document',
                document_id: this.documentId,
                nonce: rtCollabConfig.nonce
            },
            success: (response) => {
                if (response.success) {
                    this.loadContent(response.data.content);
                }
            }
        });
    }
    
    bindEvents() {
        // 保存按钮
        this.element.find('.btn-save').on('click', () => {
            this.saveDocument();
        });
        
        // 分享按钮
        this.element.find('.btn-share').on('click', () => {
            this.shareDocument();
        });
    }
    
    saveDocument() {
        const content = this.editor.getValue();
        
        $.ajax({
            url: rtCollabConfig.ajax_url,
            method: 'POST',
            data: {
                action: 'save_document',
                document_id: this.documentId,
                content: content,
                nonce: rtCollabConfig.nonce
            },
            success: (response) => {
                if (response.success) {
                    alert('文档保存成功');
                } else {
                    alert('保存失败: ' + response.data.message);
                }
            }
        });
    }
    
    shareDocument() {
        // 生成分享链接
        const shareUrl = window.location.origin + 
                        '/?document=' + this.documentId;
        
        // 复制到剪贴板
        navigator.clipboard.writeText(shareUrl)
            .then(() => alert('分享链接已复制到剪贴板'))
            .catch(() => prompt('请手动复制链接:', shareUrl));
    }
}

// 初始化所有文档编辑器
$(document).ready(function() {
    $('.rt-collab-document-editor').each(function() {
        new DocumentEditor($(this));
    });
});

})(jQuery);


## 第四部分:实现电子表格协同编辑器

### 4.1 电子表格后端处理

创建`includes/class-spreadsheet-editor.php`:

<?php
class RT_Spreadsheet_Editor {


private static $instance = null;

public static function get_instance() {
    if (null === self::$instance) {
        self::$instance = new self();
    }
    return self::$instance;
}

private function __construct() {
    add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
    add_shortcode('collab_spreadsheet', array($this, 'spreadsheet_shortcode'));
    add_action('wp_ajax_save_spreadsheet', array($this, 'save_spreadsheet'));
    add_action('wp_ajax_get_spreadsheet', array($this, 'get_spreadsheet'));
}

public function enqueue_scripts() {
    if ($this->is_spreadsheet_page()) {
        // 加载Handsontable电子表格库
        wp_enqueue_style('handsontable-css', 
            'https://cdn.jsdelivr.net/npm/handsontable@latest/dist/handsontable.full.min.css');
        wp_enqueue_script('handsontable-js', 
            'https://cdn.jsdelivr.net/npm/handsontable@latest/dist/handsontable.full.min.js', 
            array(), null, true);
        
        // 加载协同电子表格脚本
        wp_enqueue_script('rt-collab-spreadsheet', 
            RT_COLLAB_PLUGIN_URL . 'assets/js/spreadsheet-editor.js', 
            array('jquery', 'handsontable-js'), RT_COLLAB_VERSION, true);
        
        wp_localize_script('rt-collab-spreadsheet', 'rtSpreadsheetConfig', array(
            'ajax_url' => admin_url('admin-ajax.php'),
            'websocket_url' => $this->get_websocket_url(),
            'current_user' => get_current_user_id(),
            'nonce' => wp_create_nonce('rt_spreadsheet_nonce')
        ));
    }
}

public function spreadsheet_shortcode($atts) {
    $atts = shortcode_atts(array(
        'id' => 0,
        'title' => '协同电子表格',
        'rows' => 100,
        'cols' => 26,
        'height' => '500px'
    ), $atts, 'collab_spreadsheet');
    
    if (!$atts['id']) {
        // 创建新电子表格
        $atts['id'] = $this->create_new_spreadsheet($atts);
    }
    
    if (!$this->check_spreadsheet_access($atts['id'])) {
        return '<p>您没有权限访问此电子表格</p>';
    }
    
    ob_start();
    ?>
    <div class="rt-collab-spreadsheet-editor" 
         data-spreadsheet-id="<?php echo esc_attr($atts['id']); ?>"
         data-rows="<?php echo esc_attr($atts['rows']); ?>"
         data-cols="<?php echo esc_attr($atts['cols']); ?>">
        <div class="spreadsheet-header">
            <h3><?php echo esc_html($atts['title']); ?></h3>
            <div class="spreadsheet-toolbar">
                <button class="btn-save-sheet">保存</button>
                <button class="btn-export">导出Excel</button>
                <button class="btn-add-sheet">添加工作表</button>
                <span class="user-count">在线用户: <span class="count">1</span></span>
            </div>
        </div>
        <div class="spreadsheet-container" style="height: <?php echo esc_attr($atts['height']); ?>">
            <div id="spreadsheet-<?php echo esc_attr($atts['id']); ?>" class="hot"></div>
        </div>
        <div class="spreadsheet-info">
            <div class="sheet-tabs">
                <!-- 工作表标签将通过JavaScript生成 -->
            </div>
            <div class="cell-info">
                选中: <span class="selected-range">A1</span>
            </div>
        </div>
    </div>
    <?php
    return ob_get_clean();
}

private function create_new_spreadsheet($atts) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'rt_collab_spreadsheets';
    
    $default_data = array(
        'sheets' => array(
            'Sheet1' => array(
                'data' => array(),
                'rows' => intval($atts['rows']),
                'cols' => intval($atts['cols'])
            )
        ),
        'activeSheet' => 'Sheet1'
    );
    
    $wpdb->insert(
        $table_name,
        array(
            'title' => $atts['title'],
            'data' => json_encode($default_data),
            'owner_id' => get_current_user_id(),
            'rows' => $atts['rows'],
            'columns' => $atts['cols']
        ),
        array('%s', '%s', '%d', '%d', '%d')
    );
    
    return $wpdb->insert_id;
}

private function check_spreadsheet_access($spreadsheet_id) {
    // 权限检查逻辑
    return true;
}

public function save_spreadsheet() {
    if (!wp_verify_nonce($_POST['nonce'], 'rt_spreadsheet_nonce')) {
        wp_die('安全验证失败');
    }
    
    $spreadsheet_id = intval($_POST['spreadsheet_id']);
    $data = json_decode(stripslashes($_POST['data']), true);
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'rt_collab_spreadsheets';
    
    $result = $wpdb->update(
        $table_name,
        array(
            'data' => json_encode($data),
            'updated_at' => current_time('mysql')
        ),
        array('id' => $spreadsheet_id),
        array('%s', '%s'),
        array('%d')
    );
    
    if ($result !== false) {
        wp_send_json_success(array('message' => '保存成功'));
    } else {
        wp_send_json_error(array('message' => '保存失败'));
    }
}

public function get_spreadsheet() {
    $spreadsheet_id = intval($_GET['spreadsheet_id']);
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'rt_collab_spreadsheets';
    
    $spreadsheet = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM $table_name WHERE id = %d",
        $spreadsheet_id
    ));
    
    if ($spreadsheet) {
        $data = json_decode($spreadsheet->data, true);
        wp_send_json_success(array(
            'data' => $data,
            'title' => $spreadsheet->title,
            'rows' => $spreadsheet->rows,
            'columns' => $spreadsheet->columns
        ));
    } else {
        wp_send_json_error(array('message' => '电子表格不存在'));
    }
}

private function is_spreadsheet_page() {
    global $post;
    return is_a($post, 'WP_Post') && 
           (has_shortcode($post->post_content, 'collab_spreadsheet') ||
            has_shortcode($post->post_content, 'collab_document'));
}

}


### 4.2 电子表格前端协同逻辑

创建`assets/js/spreadsheet-editor.js`:

(function($) {

'use strict';

class SpreadsheetEditor {
    constructor(element) {
        this.element = element;
        this.spreadsheetId = element.data('spreadsheet-id');
        this.hot = null;
        this.websocket = null;
        this.userId = rtSpreadsheetConfig.current_user;
        this.data = null;
        this.pendingChanges = [];
        
        this.init();
    }
    
    init() {
        this.loadSpreadsheet().then(() => {
            this.initHandsontable();
            this.initWebSocket();
            this.bindEvents();
        });
    }
    
    async loadSpreadsheet() {
        try {
            const response = await $.ajax({
                url: rtSpreadsheet
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5138.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

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