首页 / 应用软件 / 手把手教学,为你的网站添加在线白板与团队头脑风暴功能

手把手教学,为你的网站添加在线白板与团队头脑风暴功能

手把手教学:为你的WordPress网站添加在线白板与团队头脑风暴功能

引言:为什么你的网站需要协作工具?

在当今数字化工作环境中,远程协作已成为常态。无论是教育机构、创意团队还是企业项目组,都需要高效的在线协作工具来促进沟通和创意产出。然而,许多专业协作工具价格昂贵,且难以与现有网站无缝集成。

本文将指导你通过WordPress代码二次开发,为你的网站添加在线白板和团队头脑风暴功能。这不仅能为你的用户提供价值,还能显著提升网站的互动性和专业性。我们将从零开始,逐步构建一个功能完整的协作系统。

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

1.1 理解WordPress开发基础

在开始之前,你需要具备以下基础知识:

  • 基本的PHP编程能力
  • HTML/CSS/JavaScript前端知识
  • WordPress主题或插件开发经验
  • 对REST API的基本了解

如果你已经熟悉这些技术,可以直接进入下一部分。如果你是初学者,建议先学习WordPress官方开发文档。

1.2 开发环境配置

首先,确保你有一个本地开发环境:

  1. 安装XAMPP、MAMP或Local by Flywheel
  2. 下载最新版WordPress并安装
  3. 启用调试模式(在wp-config.php中添加define('WP_DEBUG', true);
  4. 安装代码编辑器(如VS Code、Sublime Text或PHPStorm)

1.3 创建自定义插件

我们将创建一个独立插件来实现功能,而不是修改主题文件,这样可以确保功能独立且易于维护。

创建插件目录结构:

wp-content/plugins/team-whiteboard/
├── team-whiteboard.php
├── includes/
│   ├── class-database.php
│   ├── class-whiteboard.php
│   └── class-brainstorm.php
├── assets/
│   ├── css/
│   ├── js/
│   └── images/
├── templates/
└── vendor/

第二部分:构建在线白板核心功能

2.1 数据库设计与实现

首先,我们需要设计数据库表来存储白板数据。在includes/class-database.php中:

<?php
class TeamWhiteboard_Database {
    
    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('init', array($this, 'create_tables'));
    }
    
    public function create_tables() {
        global $wpdb;
        
        $charset_collate = $wpdb->get_charset_collate();
        $table_name = $wpdb->prefix . 'team_whiteboards';
        
        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id mediumint(9) NOT NULL AUTO_INCREMENT,
            title varchar(255) NOT NULL,
            content longtext,
            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 'active',
            settings text,
            PRIMARY KEY (id)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
        
        // 创建白板元素表
        $elements_table = $wpdb->prefix . 'whiteboard_elements';
        $sql_elements = "CREATE TABLE IF NOT EXISTS $elements_table (
            id mediumint(9) NOT NULL AUTO_INCREMENT,
            whiteboard_id mediumint(9) NOT NULL,
            element_type varchar(50) NOT NULL,
            element_data text NOT NULL,
            created_by bigint(20) NOT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            position_x float DEFAULT 0,
            position_y float DEFAULT 0,
            z_index int DEFAULT 0,
            PRIMARY KEY (id),
            FOREIGN KEY (whiteboard_id) REFERENCES $table_name(id) ON DELETE CASCADE
        ) $charset_collate;";
        
        dbDelta($sql_elements);
    }
}
?>

2.2 白板前端界面开发

接下来,我们创建白板的HTML结构和CSS样式。在templates/whiteboard.php中:

<div class="team-whiteboard-container">
    <div class="whiteboard-header">
        <h2 id="whiteboard-title">新白板</h2>
        <div class="whiteboard-actions">
            <button id="save-whiteboard" class="btn btn-primary">保存</button>
            <button id="clear-whiteboard" class="btn btn-secondary">清空</button>
            <button id="export-whiteboard" class="btn btn-success">导出</button>
        </div>
    </div>
    
    <div class="whiteboard-toolbar">
        <div class="tool-group">
            <button class="tool-btn active" data-tool="select" title="选择工具">
                <i class="fas fa-mouse-pointer"></i>
            </button>
            <button class="tool-btn" data-tool="pen" title="画笔">
                <i class="fas fa-pen"></i>
            </button>
            <button class="tool-btn" data-tool="line" title="直线">
                <i class="fas fa-minus"></i>
            </button>
            <button class="tool-btn" data-tool="rectangle" title="矩形">
                <i class="fas fa-square"></i>
            </button>
            <button class="tool-btn" data-tool="circle" title="圆形">
                <i class="fas fa-circle"></i>
            </button>
            <button class="tool-btn" data-tool="text" title="文本">
                <i class="fas fa-font"></i>
            </button>
        </div>
        
        <div class="tool-group">
            <input type="color" id="color-picker" value="#000000">
            <input type="range" id="brush-size" min="1" max="50" value="3">
            <span id="brush-size-value">3px</span>
        </div>
        
        <div class="tool-group">
            <button class="tool-btn" data-action="undo" title="撤销">
                <i class="fas fa-undo"></i>
            </button>
            <button class="tool-btn" data-action="redo" title="重做">
                <i class="fas fa-redo"></i>
            </button>
            <button class="tool-btn" data-action="delete" title="删除选中">
                <i class="fas fa-trash"></i>
            </button>
        </div>
    </div>
    
    <div class="whiteboard-wrapper">
        <canvas id="whiteboard-canvas"></canvas>
        <div class="whiteboard-grid"></div>
    </div>
    
    <div class="whiteboard-sidebar">
        <div class="sidebar-section">
            <h4>参与者</h4>
            <ul id="participants-list">
                <!-- 参与者列表将通过JavaScript动态生成 -->
            </ul>
        </div>
        
        <div class="sidebar-section">
            <h4>聊天</h4>
            <div id="chat-messages"></div>
            <div class="chat-input">
                <input type="text" id="chat-input" placeholder="输入消息...">
                <button id="send-chat">发送</button>
            </div>
        </div>
    </div>
</div>

2.3 白板画布与绘图功能实现

现在,我们使用JavaScript和Canvas API实现白板的绘图功能。在assets/js/whiteboard.js中:

class Whiteboard {
    constructor(canvasId) {
        this.canvas = document.getElementById(canvasId);
        this.ctx = this.canvas.getContext('2d');
        this.isDrawing = false;
        this.currentTool = 'pen';
        this.currentColor = '#000000';
        this.brushSize = 3;
        this.lastX = 0;
        this.lastY = 0;
        this.history = [];
        this.historyIndex = -1;
        this.elements = [];
        this.selectedElement = null;
        
        this.initCanvas();
        this.setupEventListeners();
        this.setupWebSocket();
    }
    
    initCanvas() {
        // 设置画布尺寸
        this.resizeCanvas();
        window.addEventListener('resize', () => this.resizeCanvas());
        
        // 设置初始样式
        this.ctx.lineCap = 'round';
        this.ctx.lineJoin = 'round';
        this.ctx.strokeStyle = this.currentColor;
        this.ctx.lineWidth = this.brushSize;
        
        // 绘制网格背景
        this.drawGrid();
    }
    
    resizeCanvas() {
        const container = this.canvas.parentElement;
        this.canvas.width = container.clientWidth;
        this.canvas.height = container.clientHeight;
        this.drawGrid();
        this.redraw();
    }
    
    drawGrid() {
        const gridSize = 20;
        const width = this.canvas.width;
        const height = this.canvas.height;
        
        this.ctx.save();
        this.ctx.strokeStyle = '#f0f0f0';
        this.ctx.lineWidth = 1;
        
        // 绘制垂直线
        for (let x = 0; x <= width; x += gridSize) {
            this.ctx.beginPath();
            this.ctx.moveTo(x, 0);
            this.ctx.lineTo(x, height);
            this.ctx.stroke();
        }
        
        // 绘制水平线
        for (let y = 0; y <= height; y += gridSize) {
            this.ctx.beginPath();
            this.ctx.moveTo(0, y);
            this.ctx.lineTo(width, y);
            this.ctx.stroke();
        }
        
        this.ctx.restore();
    }
    
    setupEventListeners() {
        // 鼠标事件
        this.canvas.addEventListener('mousedown', (e) => this.startDrawing(e));
        this.canvas.addEventListener('mousemove', (e) => this.draw(e));
        this.canvas.addEventListener('mouseup', () => this.stopDrawing());
        this.canvas.addEventListener('mouseout', () => this.stopDrawing());
        
        // 触摸事件(移动设备支持)
        this.canvas.addEventListener('touchstart', (e) => {
            e.preventDefault();
            const touch = e.touches[0];
            this.startDrawing(touch);
        });
        
        this.canvas.addEventListener('touchmove', (e) => {
            e.preventDefault();
            const touch = e.touches[0];
            this.draw(touch);
        });
        
        this.canvas.addEventListener('touchend', () => this.stopDrawing());
        
        // 工具选择
        document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                document.querySelectorAll('.tool-btn[data-tool]').forEach(b => b.classList.remove('active'));
                e.target.classList.add('active');
                this.currentTool = e.target.dataset.tool;
            });
        });
        
        // 颜色选择
        document.getElementById('color-picker').addEventListener('change', (e) => {
            this.currentColor = e.target.value;
            this.ctx.strokeStyle = this.currentColor;
        });
        
        // 笔刷大小
        document.getElementById('brush-size').addEventListener('input', (e) => {
            this.brushSize = e.target.value;
            document.getElementById('brush-size-value').textContent = `${this.brushSize}px`;
            this.ctx.lineWidth = this.brushSize;
        });
        
        // 动作按钮
        document.querySelector('[data-action="undo"]').addEventListener('click', () => this.undo());
        document.querySelector('[data-action="redo"]').addEventListener('click', () => this.redo());
        document.querySelector('[data-action="delete"]').addEventListener('click', () => this.deleteSelected());
    }
    
    startDrawing(e) {
        this.isDrawing = true;
        const rect = this.canvas.getBoundingClientRect();
        this.lastX = e.clientX - rect.left;
        this.lastY = e.clientY - rect.top;
        
        // 保存当前状态到历史记录
        this.saveState();
        
        // 根据工具类型执行不同操作
        if (this.currentTool === 'text') {
            this.addTextElement(this.lastX, this.lastY);
        }
    }
    
    draw(e) {
        if (!this.isDrawing) return;
        
        const rect = this.canvas.getBoundingClientRect();
        const currentX = e.clientX - rect.left;
        const currentY = e.clientY - rect.top;
        
        switch (this.currentTool) {
            case 'pen':
                this.drawFreehand(currentX, currentY);
                break;
            case 'line':
                this.drawLine(currentX, currentY);
                break;
            case 'rectangle':
                this.drawRectangle(currentX, currentY);
                break;
            case 'circle':
                this.drawCircle(currentX, currentY);
                break;
        }
        
        this.lastX = currentX;
        this.lastY = currentY;
    }
    
    drawFreehand(x, y) {
        this.ctx.beginPath();
        this.ctx.moveTo(this.lastX, this.lastY);
        this.ctx.lineTo(x, y);
        this.ctx.stroke();
        
        // 发送绘图数据到服务器
        this.sendDrawingData({
            type: 'freehand',
            from: {x: this.lastX, y: this.lastY},
            to: {x, y},
            color: this.currentColor,
            size: this.brushSize
        });
    }
    
    stopDrawing() {
        this.isDrawing = false;
        this.ctx.beginPath();
    }
    
    saveState() {
        // 只保留最近50个状态
        if (this.historyIndex < this.history.length - 1) {
            this.history = this.history.slice(0, this.historyIndex + 1);
        }
        
        const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
        this.history.push(imageData);
        this.historyIndex++;
        
        // 限制历史记录数量
        if (this.history.length > 50) {
            this.history.shift();
            this.historyIndex--;
        }
    }
    
    undo() {
        if (this.historyIndex > 0) {
            this.historyIndex--;
            const imageData = this.history[this.historyIndex];
            this.ctx.putImageData(imageData, 0, 0);
        }
    }
    
    redo() {
        if (this.historyIndex < this.history.length - 1) {
            this.historyIndex++;
            const imageData = this.history[this.historyIndex];
            this.ctx.putImageData(imageData, 0, 0);
        }
    }
    
    setupWebSocket() {
        // 这里将实现WebSocket连接,用于实时协作
        // 实际实现需要服务器端WebSocket支持
        console.log('WebSocket连接将在服务器端配置后实现');
    }
    
    sendDrawingData(data) {
        // 发送绘图数据到服务器,以便其他参与者可以看到
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify({
                type: 'drawing',
                data: data,
                timestamp: Date.now(),
                userId: window.userId || 'anonymous'
            }));
        }
    }
    
    redraw() {
        // 重绘画布上的所有元素
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.drawGrid();
        
        // 重绘所有保存的元素
        this.elements.forEach(element => {
            this.drawElement(element);
        });
    }
    
    drawElement(element) {
        // 根据元素类型绘制
        switch (element.type) {
            case 'freehand':
                this.ctx.beginPath();
                this.ctx.moveTo(element.from.x, element.from.y);
                this.ctx.lineTo(element.to.x, element.to.y);
                this.ctx.strokeStyle = element.color;
                this.ctx.lineWidth = element.size;
                this.ctx.stroke();
                break;
            // 其他元素类型的绘制逻辑
        }
    }
}

// 初始化白板
document.addEventListener('DOMContentLoaded', () => {
    const whiteboard = new Whiteboard('whiteboard-canvas');
});

第三部分:实现团队头脑风暴功能

3.1 头脑风暴数据库设计

includes/class-brainstorm.php中,我们扩展数据库设计:

<?php
class TeamWhiteboard_Brainstorm {
    
    public function create_brainstorm_tables() {
        global $wpdb;
        
        $charset_collate = $wpdb->get_charset_collate();
        
        // 头脑风暴会话表
        $sessions_table = $wpdb->prefix . 'brainstorm_sessions';
        $sql_sessions = "CREATE TABLE IF NOT EXISTS $sessions_table (
            id mediumint(9) NOT NULL AUTO_INCREMENT,
            title varchar(255) NOT NULL,
            description text,
            created_by bigint(20) NOT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            status varchar(20) DEFAULT 'active',
            settings text,
            PRIMARY KEY (id)
        ) $charset_collate;";
        
        // 想法/便签表
        $ideas_table = $wpdb->prefix . 'brainstorm_ideas';
        $sql_ideas = "CREATE TABLE IF NOT EXISTS $ideas_table (
            id mediumint(9) NOT NULL AUTO_INCREMENT,
            session_id mediumint(9) NOT NULL,
            content text NOT NULL,
            created_by bigint(20) NOT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            category varchar(50),
            votes int DEFAULT 0,
            position_x float DEFAULT 0,
            position_y float DEFAULT 0,
            color varchar(20) DEFAULT '#FFFF99',
            PRIMARY KEY (id),
            FOREIGN KEY (session_id) REFERENCES $sessions_table(id) ON DELETE CASCADE
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql_sessions);
        dbDelta($sql_ideas);
    }
}
?>

3.2 头脑风暴前端界面

创建templates/brainstorm.php

<div class="brainstorm-container">
    <div class="brainstorm-header">

手把手教学:为你的WordPress网站添加在线白板与团队头脑风暴功能(续)

第三部分:实现团队头脑风暴功能(续)

3.2 头脑风暴前端界面(续)

<div class="brainstorm-container">
    <div class="brainstorm-header">
        <h2 id="session-title">头脑风暴会议</h2>
        <div class="session-info">
            <span id="participant-count">0 参与者</span>
            <span id="idea-count">0 个想法</span>
        </div>
        <div class="session-controls">
            <button id="new-idea-btn" class="btn btn-primary">
                <i class="fas fa-plus"></i> 添加想法
            </button>
            <button id="timer-toggle" class="btn btn-secondary">
                <i class="fas fa-clock"></i> 计时器
            </button>
            <button id="export-ideas" class="btn btn-success">
                <i class="fas fa-download"></i> 导出
            </button>
        </div>
    </div>
    
    <div class="brainstorm-main">
        <!-- 左侧工具栏 -->
        <div class="brainstorm-sidebar">
            <div class="sidebar-section">
                <h4>分类</h4>
                <div class="category-list">
                    <div class="category-item active" data-category="all">
                        <span class="category-color" style="background:#f0f0f0"></span>
                        <span>全部想法</span>
                    </div>
                    <div class="category-item" data-category="feature">
                        <span class="category-color" style="background:#FF9999"></span>
                        <span>功能建议</span>
                    </div>
                    <div class="category-item" data-category="improvement">
                        <span class="category-color" style="background:#99FF99"></span>
                        <span>改进建议</span>
                    </div>
                    <div class="category-item" data-category="question">
                        <span class="category-color" style="background:#9999FF"></span>
                        <span>问题反馈</span>
                    </div>
                </div>
                
                <div class="add-category">
                    <input type="text" id="new-category-name" placeholder="新分类名称">
                    <input type="color" id="new-category-color" value="#CCCCCC">
                    <button id="add-category-btn">添加</button>
                </div>
            </div>
            
            <div class="sidebar-section">
                <h4>投票设置</h4>
                <div class="voting-settings">
                    <label>每人票数:</label>
                    <input type="number" id="votes-per-user" min="1" max="20" value="5">
                    <div class="voting-status">
                        <p>已用票数:<span id="used-votes">0</span>/<span id="total-votes">5</span></p>
                    </div>
                </div>
            </div>
            
            <div class="sidebar-section">
                <h4>计时器</h4>
                <div class="timer-container">
                    <div class="timer-display" id="timer-display">10:00</div>
                    <div class="timer-controls">
                        <button id="start-timer">开始</button>
                        <button id="pause-timer">暂停</button>
                        <button id="reset-timer">重置</button>
                    </div>
                    <div class="timer-presets">
                        <button class="timer-preset" data-time="300">5分钟</button>
                        <button class="timer-preset" data-time="600">10分钟</button>
                        <button class="timer-preset" data-time="900">15分钟</button>
                    </div>
                </div>
            </div>
        </div>
        
        <!-- 主工作区 -->
        <div class="brainstorm-workspace">
            <div class="workspace-tools">
                <button class="workspace-tool" data-action="arrange" title="自动排列">
                    <i class="fas fa-th"></i>
                </button>
                <button class="workspace-tool" data-action="cluster" title="按分类分组">
                    <i class="fas fa-object-group"></i>
                </button>
                <button class="workspace-tool" data-action="clear" title="清空工作区">
                    <i class="fas fa-broom"></i>
                </button>
            </div>
            
            <div class="ideas-container" id="ideas-container">
                <!-- 想法卡片将通过JavaScript动态生成 -->
            </div>
        </div>
        
        <!-- 右侧聊天和参与者面板 -->
        <div class="brainstorm-participants">
            <div class="participants-section">
                <h4>参与者 (<span id="active-participants">0</span>)</h4>
                <ul id="participants-list">
                    <!-- 参与者列表 -->
                </ul>
            </div>
            
            <div class="chat-section">
                <h4>讨论区</h4>
                <div class="chat-messages" id="brainstorm-chat">
                    <!-- 聊天消息 -->
                </div>
                <div class="chat-input">
                    <input type="text" id="brainstorm-chat-input" placeholder="输入消息...">
                    <button id="send-brainstorm-chat">
                        <i class="fas fa-paper-plane"></i>
                    </button>
                </div>
            </div>
        </div>
    </div>
</div>

3.3 头脑风暴JavaScript实现

创建assets/js/brainstorm.js

class BrainstormSession {
    constructor(sessionId) {
        this.sessionId = sessionId;
        this.ideas = [];
        this.categories = [];
        this.participants = [];
        this.currentUser = null;
        this.votesUsed = 0;
        this.votesTotal = 5;
        this.timer = null;
        this.timerSeconds = 600; // 默认10分钟
        this.isTimerRunning = false;
        
        this.init();
        this.loadSessionData();
        this.setupEventListeners();
        this.connectWebSocket();
    }
    
    init() {
        // 初始化用户信息
        this.currentUser = {
            id: window.userId || 'user_' + Math.random().toString(36).substr(2, 9),
            name: window.userName || '匿名用户',
            color: this.getRandomColor()
        };
        
        // 初始化默认分类
        this.categories = [
            { id: 'all', name: '全部想法', color: '#f0f0f0' },
            { id: 'feature', name: '功能建议', color: '#FF9999' },
            { id: 'improvement', name: '改进建议', color: '#99FF99' },
            { id: 'question', name: '问题反馈', color: '#9999FF' }
        ];
        
        // 更新UI
        this.updateParticipantCount();
        this.renderCategories();
    }
    
    loadSessionData() {
        // 从服务器加载会话数据
        fetch(`/wp-json/team-whiteboard/v1/brainstorm/${this.sessionId}`)
            .then(response => response.json())
            .then(data => {
                this.ideas = data.ideas || [];
                this.categories = data.categories || this.categories;
                this.participants = data.participants || [];
                this.renderIdeas();
                this.renderParticipants();
            })
            .catch(error => {
                console.error('加载会话数据失败:', error);
            });
    }
    
    setupEventListeners() {
        // 添加想法按钮
        document.getElementById('new-idea-btn').addEventListener('click', () => {
            this.showIdeaForm();
        });
        
        // 分类点击事件
        document.querySelectorAll('.category-item').forEach(item => {
            item.addEventListener('click', (e) => {
                const category = e.currentTarget.dataset.category;
                this.filterIdeasByCategory(category);
                
                // 更新活动状态
                document.querySelectorAll('.category-item').forEach(i => {
                    i.classList.remove('active');
                });
                e.currentTarget.classList.add('active');
            });
        });
        
        // 添加分类
        document.getElementById('add-category-btn').addEventListener('click', () => {
            this.addCategory();
        });
        
        // 投票设置
        document.getElementById('votes-per-user').addEventListener('change', (e) => {
            this.votesTotal = parseInt(e.target.value);
            document.getElementById('total-votes').textContent = this.votesTotal;
        });
        
        // 计时器控制
        document.getElementById('start-timer').addEventListener('click', () => {
            this.startTimer();
        });
        
        document.getElementById('pause-timer').addEventListener('click', () => {
            this.pauseTimer();
        });
        
        document.getElementById('reset-timer').addEventListener('click', () => {
            this.resetTimer();
        });
        
        // 预设时间按钮
        document.querySelectorAll('.timer-preset').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const seconds = parseInt(e.currentTarget.dataset.time);
                this.setTimer(seconds);
            });
        });
        
        // 工作区工具
        document.querySelectorAll('.workspace-tool').forEach(tool => {
            tool.addEventListener('click', (e) => {
                const action = e.currentTarget.dataset.action;
                this.handleWorkspaceAction(action);
            });
        });
        
        // 聊天发送
        document.getElementById('send-brainstorm-chat').addEventListener('click', () => {
            this.sendChatMessage();
        });
        
        document.getElementById('brainstorm-chat-input').addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                this.sendChatMessage();
            }
        });
    }
    
    showIdeaForm() {
        // 创建想法表单模态框
        const modal = document.createElement('div');
        modal.className = 'idea-form-modal';
        modal.innerHTML = `
            <div class="modal-content">
                <h3>添加新想法</h3>
                <div class="form-group">
                    <label>想法内容</label>
                    <textarea id="idea-content" rows="4" placeholder="输入你的想法..."></textarea>
                </div>
                <div class="form-group">
                    <label>分类</label>
                    <select id="idea-category">
                        ${this.categories.filter(c => c.id !== 'all').map(c => 
                            `<option value="${c.id}">${c.name}</option>`
                        ).join('')}
                    </select>
                </div>
                <div class="form-group">
                    <label>颜色</label>
                    <input type="color" id="idea-color" value="#FFFF99">
                </div>
                <div class="modal-actions">
                    <button id="cancel-idea" class="btn btn-secondary">取消</button>
                    <button id="submit-idea" class="btn btn-primary">提交</button>
                </div>
            </div>
        `;
        
        document.body.appendChild(modal);
        
        // 事件监听
        document.getElementById('cancel-idea').addEventListener('click', () => {
            document.body.removeChild(modal);
        });
        
        document.getElementById('submit-idea').addEventListener('click', () => {
            this.submitIdea();
            document.body.removeChild(modal);
        });
    }
    
    submitIdea() {
        const content = document.getElementById('idea-content').value;
        const category = document.getElementById('idea-category').value;
        const color = document.getElementById('idea-color').value;
        
        if (!content.trim()) {
            alert('请输入想法内容');
            return;
        }
        
        const idea = {
            id: 'idea_' + Date.now(),
            content: content,
            category: category,
            color: color,
            createdBy: this.currentUser,
            createdAt: new Date().toISOString(),
            votes: 0,
            position: {
                x: Math.random() * 600,
                y: Math.random() * 400
            }
        };
        
        this.ideas.push(idea);
        this.renderIdea(idea);
        this.updateIdeaCount();
        
        // 发送到服务器
        this.saveIdea(idea);
        
        // 广播给其他参与者
        this.broadcast({
            type: 'new_idea',
            data: idea
        });
    }
    
    renderIdeas() {
        const container = document.getElementById('ideas-container');
        container.innerHTML = '';
        
        this.ideas.forEach(idea => {
            this.renderIdea(idea);
        });
        
        this.updateIdeaCount();
    }
    
    renderIdea(idea) {
        const container = document.getElementById('ideas-container');
        
        const ideaElement = document.createElement('div');
        ideaElement.className = 'idea-card';
        ideaElement.dataset.id = idea.id;
        ideaElement.dataset.category = idea.category;
        ideaElement.style.left = idea.position.x + 'px';
        ideaElement.style.top = idea.position.y + 'px';
        ideaElement.style.backgroundColor = idea.color;
        
        ideaElement.innerHTML = `
            <div class="idea-header">
                <span class="idea-author">${idea.createdBy.name}</span>
                <span class="idea-time">${this.formatTime(idea.createdAt)}</span>
            </div>
            <div class="idea-content">${this.escapeHtml(idea.content)}</div>
            <div class="idea-footer">
                <button class="vote-btn" data-id="${idea.id}">
                    <i class="fas fa-thumbs-up"></i>
                    <span class="vote-count">${idea.votes}</span>
                </button>
                <button class="delete-btn" data-id="${idea.id}">
                    <i class="fas fa-trash"></i>
                </button>
            </div>
        `;
        
        container.appendChild(ideaElement);
        
        // 添加拖拽功能
        this.makeDraggable(ideaElement, idea);
        
        // 投票按钮事件
        ideaElement.querySelector('.vote-btn').addEventListener('click', (e) => {
            e.stopPropagation();
            this.voteForIdea(idea.id);
        });
        
        // 删除按钮事件
        ideaElement.querySelector('.delete-btn').addEventListener('click', (e) => {
            e.stopPropagation();
            if (confirm('确定要删除这个想法吗?')) {
                this.deleteIdea(idea.id);
            }
        });
    }
    
    makeDraggable(element, idea) {
        let isDragging = false;
        let offsetX, offsetY;
        
        element.addEventListener('mousedown', startDrag);
        element.addEventListener('touchstart', startDrag);
        
        function startDrag(e) {
            isDragging = true;
            
            if (e.type === 'mousedown') {
                offsetX = e.clientX - element.offsetLeft;
                offsetY = e.clientY - element.offsetTop;
                document.addEventListener('mousemove', drag);
                document.addEventListener('mouseup', stopDrag);
            } else {
                const touch = e.touches[0];
                offsetX = touch.clientX - element.offsetLeft;
                offsetY = touch.clientY - element.offsetTop;
                document.addEventListener('touchmove', drag);
                document.addEventListener('touchend', stopDrag);
            }
            
            e.preventDefault();
        }
        
        const drag = (e) => {
            if (!isDragging) return;
            
            let clientX, clientY;
            if (e.type === 'mousemove') {
                clientX = e.clientX;
                clientY = e.clientY;
            } else {
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
            }
            
            const container = document.getElementById('ideas-container');
            const maxX = container.clientWidth - element.offsetWidth;
            const maxY = container.clientHeight - element.offsetHeight;
            
            let x = clientX - offsetX;
            let y = clientY - offsetY;
            
            // 限制在容器内
            x = Math.max(0, Math.min(x, maxX));
            y = Math.max(0, Math.min(y, maxY));
            
            element.style.left = x + 'px';
            element.style.top = y + 'px';
            
            // 更新想法位置
            idea.position.x = x;
            idea.position.y = y;
            
            // 广播位置更新
            this.broadcast({
                type: 'move_idea',
                data: {
                    id: idea.id,
                    position: idea.position
                }
            });
        }.bind(this);
        
        function stopDrag() {
            isDragging = false;
            document.removeEventListener('mousemove', drag);
            document.removeEventListener('mouseup', stopDrag);
            document.removeEventListener('touchmove', drag);
            document.removeEventListener('touchend', stopDrag);
        }
    }
    
    voteForIdea(ideaId) {
        if (this.votesUsed >= this.votesTotal) {
            alert('你的投票次数已用完!');
            return;
        }
        
        const idea = this.ideas.find(i => i.id === ideaId);
        if (!idea) return;
        
        idea.votes++;
        this.votesUsed++;
        
        // 更新UI
        const voteBtn = document.querySelector(`.vote-btn[data-id="${ideaId}"] .vote-count`);
        if (voteBtn) {
            voteBtn.textContent = idea.votes;
        }
        
        document.getElementById('used-votes').textContent = this.votesUsed;
        
        // 发送到服务器
        this.updateIdeaVotes(ideaId, idea.votes);
        
        // 广播投票
        this.broadcast({
            type: 'vote',
            data: {
                ideaId: ideaId,
                votes: idea.votes
            }
        });
    }
    
    filterIdeasByCategory(category) {
        const ideas = document.querySelectorAll('.idea-card');
        
        ideas.forEach(idea => {
            if (category === 'all' || idea.dataset.category === category) {
                idea.style.display = 'block';
            } else {
                idea.style.display = 'none';
            }
        });
    }
    
    addCategory() {
        const nameInput = document.getElementById('new-category-name');
        const colorInput = document.getElementById('new-category-color');
        
        const name = nameInput.value.trim();
        const color = colorInput.value;
        
        if (!name) {
            alert('请输入分类名称');
            return;
        }
        
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5214.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

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