本文目录导读:

我来为你详细讲解如何使用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">×</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);
?>
推荐项目和库
如果不想从零开始,可以使用这些现成的解决方案:
- Kanban API: https://github.com/kanban-tool/kanban-api
- PHP Kanban Board: https://github.com/phpscriptpro/kanban-board
- TaskBoard PHP: https://github.com/layervault/taskboard-php
如果需要更专业的功能,考虑使用:
- Trello API 集成
- Jira API 集成
- Wekan (React + Meteor)
这个实现提供了完整的功能:拖拽任务、创建/编辑任务、任务过滤、权限管理、响应式设计等,你可以根据具体需求进行扩展和优化。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播