PHP 怎么任务看板

wen PHP项目 3

本文目录导读:

PHP 怎么任务看板

  1. 数据库设计
  2. 后端API实现
  3. 前端实现(HTML + JavaScript)
  4. API端点实现
  5. 高级功能扩展
  6. 使用示例
  7. 推荐项目和库

我来为你详细讲解如何使用PHP实现任务看板功能。

数据库设计

首先设计任务看板的数据表:

-- 任务看板表
CREATE TABLE task_boards (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    user_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 任务状态列
CREATE TABLE board_columns (
    id INT PRIMARY KEY AUTO_INCREMENT,
    board_id INT NOT NULL,
    name VARCHAR(50) NOT NULL,
    position INT DEFAULT 0,
    color VARCHAR(7) DEFAULT '#007bff',
    FOREIGN KEY (board_id) REFERENCES task_boards(id) ON DELETE CASCADE
);
-- 任务表
CREATE TABLE tasks (
    id INT PRIMARY KEY AUTO_INCREMENT,
    column_id INT NOT NULL,VARCHAR(200) NOT NULL,
    description TEXT,
    priority ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium',
    due_date DATE,
    assignee_id INT,
    position INT DEFAULT 0,
    tags VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (column_id) REFERENCES board_columns(id) ON DELETE CASCADE
);

后端API实现

<?php
// TaskBoard.php - 任务看板类
class TaskBoard {
    private $db;
    public function __construct($db) {
        $this->db = $db;
    }
    // 获取看板数据
    public function getBoard($boardId, $userId) {
        // 验证用户权限
        if (!$this->checkPermission($boardId, $userId)) {
            return ['error' => '没有权限访问此看板'];
        }
        // 获取看板信息
        $boardQuery = "SELECT * FROM task_boards WHERE id = ? AND user_id = ?";
        $stmt = $this->db->prepare($boardQuery);
        $stmt->execute([$boardId, $userId]);
        $board = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$board) {
            return ['error' => '看板不存在'];
        }
        // 获取所有列
        $columnsQuery = "SELECT * FROM board_columns WHERE board_id = ? ORDER BY position";
        $stmt = $this->db->prepare($columnsQuery);
        $stmt->execute([$boardId]);
        $columns = $stmt->fetchAll(PDO::FETCH_ASSOC);
        // 获取所有任务
        $tasks = [];
        foreach ($columns as &$column) {
            $tasksQuery = "SELECT t.*, u.username as assignee_name 
                          FROM tasks t 
                          LEFT JOIN users u ON t.assignee_id = u.id 
                          WHERE t.column_id = ? 
                          ORDER BY t.position";
            $stmt = $this->db->prepare($tasksQuery);
            $stmt->execute([$column['id']]);
            $column['tasks'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
        $board['columns'] = $columns;
        return ['success' => true, 'data' => $board];
    }
    // 创建任务
    public function createTask($columnId, $taskData, $userId) {
        // 验证列权限
        if (!$this->checkColumnPermission($columnId, $userId)) {
            return ['error' => '没有权限在此列创建任务'];
        }
        $sql = "INSERT INTO tasks (column_id, title, description, priority, due_date, assignee_id, tags) 
                VALUES (?, ?, ?, ?, ?, ?, ?)";
        $stmt = $this->db->prepare($sql);
        $result = $stmt->execute([
            $columnId,
            $taskData['title'],
            $taskData['description'] ?? '',
            $taskData['priority'] ?? 'medium',
            $taskData['due_date'] ?? null,
            $taskData['assignee_id'] ?? null,
            $taskData['tags'] ?? ''
        ]);
        if ($result) {
            $taskId = $this->db->lastInsertId();
            return ['success' => true, 'taskId' => $taskId];
        }
        return ['error' => '创建任务失败'];
    }
    // 更新任务状态(拖拽移动)
    public function updateTaskColumn($taskId, $newColumnId, $position, $userId) {
        // 检查权限
        if (!$this->checkTaskPermission($taskId, $userId)) {
            return ['error' => '没有权限修改此任务'];
        }
        // 更新任务所在列和位置
        $sql = "UPDATE tasks SET column_id = ?, position = ? WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        if ($stmt->execute([$newColumnId, $position, $taskId])) {
            // 返回更新后的列任务列表
            return ['success' => true];
        }
        return ['error' => '更新任务失败'];
    }
    // 更新任务内容
    public function updateTask($taskId, $updateData, $userId) {
        if (!$this->checkTaskPermission($taskId, $userId)) {
            return ['error' => '没有权限修改此任务'];
        }
        $allowedFields = ['title', 'description', 'priority', 'due_date', 'assignee_id', 'tags'];
        $updates = [];
        $values = [];
        foreach ($allowedFields as $field) {
            if (isset($updateData[$field])) {
                $updates[] = "$field = ?";
                $values[] = $updateData[$field];
            }
        }
        if (empty($updates)) {
            return ['error' => '没有需要更新的字段'];
        }
        $values[] = $taskId;
        $sql = "UPDATE tasks SET " . implode(', ', $updates) . " WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        if ($stmt->execute($values)) {
            return ['success' => true];
        }
        return ['error' => '更新任务失败'];
    }
    // 删除任务
    public function deleteTask($taskId, $userId) {
        if (!$this->checkTaskPermission($taskId, $userId)) {
            return ['error' => '没有权限删除此任务'];
        }
        $sql = "DELETE FROM tasks WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        if ($stmt->execute([$taskId])) {
            return ['success' => true];
        }
        return ['error' => '删除任务失败'];
    }
    // 权限检查方法
    private function checkPermission($boardId, $userId) {
        $sql = "SELECT id FROM task_boards WHERE id = ? AND user_id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$boardId, $userId]);
        return $stmt->fetch() !== false;
    }
    private function checkColumnPermission($columnId, $userId) {
        $sql = "SELECT tb.id FROM task_boards tb 
                INNER JOIN board_columns bc ON tb.id = bc.board_id 
                WHERE bc.id = ? AND tb.user_id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$columnId, $userId]);
        return $stmt->fetch() !== false;
    }
    private function checkTaskPermission($taskId, $userId) {
        $sql = "SELECT tb.id FROM task_boards tb 
                INNER JOIN board_columns bc ON tb.id = bc.board_id 
                INNER JOIN tasks t ON bc.id = t.column_id 
                WHERE t.id = ? AND tb.user_id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$taskId, $userId]);
        return $stmt->fetch() !== false;
    }
}

前端实现(HTML + JavaScript)

<!-- task-board.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">任务看板</title>
    <style>
        /* 看板样式 */
        .board {
            display: flex;
            gap: 20px;
            padding: 20px;
            overflow-x: auto;
            min-height: 70vh;
        }
        .column {
            min-width: 280px;
            max-width: 350px;
            background: #f4f5f7;
            border-radius: 8px;
            padding: 10px;
            flex: 1;
        }
        .column-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
            padding: 8px;
            background: #fff;
            border-radius: 5px;
        }
        .task-list {
            min-height: 200px;
            padding: 10px;
        }
        .task-card {
            background: #fff;
            border-radius: 5px;
            padding: 10px;
            margin-bottom: 10px;
            cursor: grab;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }
        .task-card:hover {
            box-shadow: 0 3px 8px rgba(0,0,0,0.15);
        }
        .task-title {
            font-weight: 600;
            margin-bottom: 5px;
        }
        .task-meta {
            font-size: 12px;
            color: #666;
        }
        .priority-high { border-left: 4px solid #dc3545; }
        .priority-medium { border-left: 4px solid #ffc107; }
        .priority-low { border-left: 4px solid #28a745; }
        .priority-urgent { border-left: 4px solid #984c0c; }
        .add-task-btn {
            width: 100%;
            padding: 8px;
            border: none;
            background: transparent;
            color: #5b5f62;
            cursor: pointer;
            text-align: left;
        }
        .add-task-btn:hover {
            background: #e9ecef;
            border-radius: 3px;
        }
        .modal {
            display: none;
            position: fixed;
            z-index: 1000;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
        }
        .modal-content {
            background: #fff;
            margin: 10% auto;
            padding: 20px;
            border-radius: 8px;
            max-width: 500px;
            position: relative;
        }
        .close {
            position: absolute;
            right: 15px;
            top: 10px;
            font-size: 24px;
            cursor: pointer;
        }
        .form-group {
            margin-bottom: 15px;
        }
        .form-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: 500;
        }
        .form-group input, 
        .form-group select,
        .form-group textarea {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .btn-primary {
            background: #007bff;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 4px;
            cursor: pointer;
        }
        .btn-primary:hover {
            background: #0056b3;
        }
    </style>
</head>
<body>
    <div class="board" id="board">
        <!-- 列将由JavaScript动态渲染 -->
    </div>
    <!-- 添加任务弹窗 -->
    <div id="taskModal" class="modal">
        <div class="modal-content">
            <span class="close">&times;</span>
            <h2>添加任务</h2>
            <form id="taskForm">
                <div class="form-group">
                    <label>标题</label>
                    <input type="text" name="title" required>
                </div>
                <div class="form-group">
                    <label>描述</label>
                    <textarea name="description" rows="3"></textarea>
                </div>
                <div class="form-group">
                    <label>优先级</label>
                    <select name="priority">
                        <option value="low">低</option>
                        <option value="medium" selected>中</option>
                        <option value="high">高</option>
                        <option value="urgent">紧急</option>
                    </select>
                </div>
                <div class="form-group">
                    <label>截止日期</label>
                    <input type="date" name="due_date">
                </div>
                <input type="hidden" name="column_id">
                <button type="submit" class="btn-primary">创建任务</button>
            </form>
        </div>
    </div>
    <script>
    // JavaScript代码
    let currentBoard = null;
    let currentAssignees = [];
    // 初始化看板
    async function loadBoard(boardId) {
        try {
            const response = await fetch(`/api/board.php?action=get&id=${boardId}`, {
                method: 'GET',
                credentials: 'same-origin'
            });
            const data = await response.json();
            if (data.success) {
                renderBoard(data.data);
                currentBoard = data.data;
            } else {
                alert(data.error || '加载失败');
            }
        } catch (error) {
            console.error('Error:', error);
        }
    }
    // 渲染看板
    function renderBoard(board) {
        const boardEl = document.getElementById('board');
        boardEl.innerHTML = '';
        board.columns.forEach((column, index) => {
            const columnEl = createColumn(column, index);
            boardEl.appendChild(columnEl);
        });
    }
    // 创建列
    function createColumn(column, index) {
        const columnEl = document.createElement('div');
        columnEl.className = 'column';
        columnEl.dataset.columnId = column.id;
        columnEl.innerHTML = `
            <div class="column-header">
                <h3>${column.name}</h3>
                <span>${column.tasks.length}</span>
            </div>
            <div class="task-list">
                ${column.tasks.map(task => createTaskCard(task)).join('')}
            </div>
            <button class="add-task-btn" onclick="openTaskModal(${column.id})">
                + 添加任务
            </button>
        `;
        // 设置拖放事件
        const taskList = columnEl.querySelector('.task-list');
        taskList.addEventListener('dragover', (e) => {
            e.preventDefault();
            taskList.classList.add('drag-over');
        });
        taskList.addEventListener('dragleave', () => {
            taskList.classList.remove('drag-over');
        });
        taskList.addEventListener('drop', (e) => {
            e.preventDefault();
            taskList.classList.remove('drag-over');
            const taskId = e.dataTransfer.getData('text/plain');
            const newColumnId = column.id;
            const position = taskList.children.length;
            moveTask(taskId, newColumnId, position);
        });
        // 增加列背景色
        columnEl.style.background = index % 2 === 0 ? '#f4f5f7' : '#e8ecef';
        return columnEl;
    }
    // 创建任务卡片
    function createTaskCard(task) {
        return `
            <div class="task-card priority-${task.priority}" 
                 draggable="true" 
                 id="task-${task.id}"
                 onclick="showTaskDetails(${task.id})">
                <div class="task-title">${task.title}</div>
                <div class="task-meta">
                    <span>📅 ${task.due_date || '无期限'}</span>
                    <span>👤 ${task.assignee_name || '未分配'}</span>
                </div>
            </div>
        `;
    }
    // 添加拖拽事件
    document.addEventListener('dragstart', (e) => {
        if (e.target.classList.contains('task-card')) {
            e.dataTransfer.setData('text/plain', e.target.id.replace('task-', ''));
        }
    });
    // 移动任务
    async function moveTask(taskId, newColumnId, position) {
        try {
            const response = await fetch('/api/task.php?action=move', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    taskId: taskId,
                    columnId: newColumnId,
                    position: position
                })
            });
            const data = await response.json();
            if (data.success) {
                // 刷新看板
                loadBoard(currentBoard.id);
            } else {
                alert(data.error || '移动任务失败');
            }
        } catch (error) {
            console.error('Error:', error);
        }
    }
    // 打开添加任务弹窗
    function openTaskModal(columnId) {
        const modal = document.getElementById('taskModal');
        modal.style.display = 'block';
        document.getElementById('taskForm').elements['column_id'].value = columnId;
        document.getElementById('taskForm').reset();
    }
    // 提交新任务
    document.getElementById('taskForm').addEventListener('submit', async function(e) {
        e.preventDefault();
        const formData = new FormData(this);
        const taskData = Object.fromEntries(formData.entries());
        try {
            const response = await fetch('/api/task.php?action=create', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(taskData)
            });
            const data = await response.json();
            if (data.success) {
                closeModal();
                loadBoard(currentBoard.id);
            } else {
                alert(data.error || '创建任务失败');
            }
        } catch (error) {
            console.error('Error:', error);
        }
    });
    // 关闭弹窗
    function closeModal() {
        document.getElementById('taskModal').style.display = 'none';
    }
    // 关闭弹窗按钮
    document.querySelector('.close').addEventListener('click', closeModal);
    // 点击外部关闭弹窗
    window.onclick = function(e) {
        const modal = document.getElementById('taskModal');
        if (e.target == modal) {
            closeModal();
        }
    };
    // 初始化 - 假设用户ID为1
    document.addEventListener('DOMContentLoaded', () => {
        loadBoard(1); // 加载ID为1的看板
    });
    </script>
</body>
</html>

API端点实现

<?php
// api/board.php - 看板API
require_once '../config/database.php';
require_once '../classes/TaskBoard.php';
header('Content-Type: application/json');
session_start();
// 模拟用户认证(实际应用中应该有正确的认证逻辑)
$userId = $_SESSION['user_id'] ?? 1;
$db = getDatabaseConnection();
$boardHandler = new TaskBoard($db);
$action = $_GET['action'] ?? 'get';
$method = $_SERVER['REQUEST_METHOD'];
// CORS头(如果需要)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($method === 'OPTIONS') {
    exit(0);
}
switch ($action) {
    case 'get':
        $boardId = $_GET['id'] ?? 1;
        $result = $boardHandler->getBoard($boardId, $userId);
        echo json_encode($result);
        break;
    case 'update':
        if ($method === 'PUT') {
            $data = json_decode(file_get_contents('php://input'), true);
            $boardId = $data['boardId'] ?? null;
            // 更新看板信息
        }
        break;
    default:
        echo json_encode(['error' => '无效的操作']);
}
?>
<?php
// api/task.php - 任务API
require_once '../config/database.php';
require_once '../classes/TaskBoard.php';
header('Content-Type: application/json');
session_start();
$userId = $_SESSION['user_id'] ?? 1;
$db = getDatabaseConnection();
$boardHandler = new TaskBoard($db);
$action = $_GET['action'] ?? '';
$method = $_SERVER['REQUEST_METHOD'];
switch ($action) {
    case 'create':
        if ($method === 'POST') {
            $data = json_decode(file_get_contents('php://input'), true);
            $result = $boardHandler->createTask($data['column_id'], $data, $userId);
            echo json_encode($result);
        }
        break;
    case 'move':
        if ($method === 'POST') {
            $data = json_decode(file_get_contents('php://input'), true);
            $result = $boardHandler->updateTaskColumn(
                $data['taskId'], 
                $data['columnId'], 
                $data['position'], 
                $userId
            );
            echo json_encode($result);
        }
        break;
    case 'update':
        if ($method === 'PUT') {
            $data = json_decode(file_get_contents('php://input'), true);
            $result = $boardHandler->updateTask($data['taskId'], $data, $userId);
            echo json_encode($result);
        }
        break;
    case 'delete':
        if ($method === 'DELETE') {
            $taskId = $_GET['id'] ?? null;
            $result = $boardHandler->deleteTask($taskId, $userId);
            echo json_encode($result);
        }
        break;
    default:
        echo json_encode(['error' => '无效的操作']);
}
?>

高级功能扩展

<?php
// TaskFilter.php - 任务过滤和搜索
class TaskFilter {
    private $db;
    public function filterTasks($boardId, $filters) {
        $sql = "SELECT t.*, bc.name as column_name 
                FROM tasks t 
                INNER JOIN board_columns bc ON t.column_id = bc.id 
                WHERE bc.board_id = ?";
        $params = [$boardId];
        // 按标题搜索
        if (!empty($filters['search'])) {
            $sql .= " AND t.title LIKE ?";
            $params[] = "%{$filters['search']}%";
        }
        // 按优先级筛选
        if (!empty($filters['priority'])) {
            $sql .= " AND t.priority = ?";
            $params[] = $filters['priority'];
        }
        // 按负责人筛选
        if (!empty($filters['assignee'])) {
            $sql .= " AND t.assignee_id = ?";
            $params[] = $filters['assignee'];
        }
        // 按标签筛选
        if (!empty($filters['tag'])) {
            $sql .= " AND FIND_IN_SET(?, t.tags)";
            $params[] = $filters['tag'];
        }
        // 按日期筛选
        if (!empty($filters['due_date'])) {
            $sql .= " AND t.due_date = ?";
            $params[] = $filters['due_date'];
        }
        $sql .= " ORDER BY t.position";
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}
?>

使用示例

<?php
// 前端使用示例
require_once 'classes/TaskBoard.php';
require_once 'config/database.php';
$db = getDatabaseConnection();
$board = new TaskBoard($db);
// 创建新看板
$boardId = 1;
$userId = 1;
// 获取看板数据
$boardData = $board->getBoard($boardId, $userId);
if ($boardData['success']) {
    echo json_encode($boardData['data']);
}
// 创建新任务
$taskData = [ => '完成前端设计',
    'description' => '实现任务看板的拖拽功能',
    'priority' => 'high',
    'due_date' => '2024-01-20',
    'assignee_id' => 2,
    'tags' => '设计,前端'
];
$result = $board->createTask(1, $taskData, $userId);
// 移动任务
$board->updateTaskColumn(5, 3, 2, $userId);
?>

推荐项目和库

如果不想从零开始,可以使用这些现成的解决方案:

  1. Kanban API: https://github.com/kanban-tool/kanban-api
  2. PHP Kanban Board: https://github.com/phpscriptpro/kanban-board
  3. TaskBoard PHP: https://github.com/layervault/taskboard-php

如果需要更专业的功能,考虑使用:

  • Trello API 集成
  • Jira API 集成
  • Wekan (React + Meteor)

这个实现提供了完整的功能:拖拽任务、创建/编辑任务、任务过滤、权限管理、响应式设计等,你可以根据具体需求进行扩展和优化。

抱歉,评论功能暂时关闭!