PHP项目如何实现待办事项?从零构建高效任务管理系统的完整指南
目录导读
- 核心概念:待办事项系统的功能与架构
- 环境搭建:PHP + MySQL 基础配置
- 数据库设计:任务表结构详解
- 后端实现:CRUD操作与业务逻辑
- 前端交互:使用AJAX实现无刷新操作
- 进阶功能:分类、优先级与截止日期管理
- 安全防护:XSS与SQL注入防御措施
- 性能优化:缓存与分页策略
- FAQ常见问题解答
核心概念:待办事项系统的功能与架构
待办事项(To-Do List)是Web应用中最经典的功能模块之一,一个完整的PHP待办事项系统通常包含以下核心功能:

- 任务创建、描述、截止日期等字段
- 状态管理:未完成、进行中、已完成三种状态切换
- 筛选与排序:按日期、优先级、自定义分类过滤
- 数据持久化:所有任务存储在MySQL数据库中
技术选型建议:
- 后端:PHP 7.4+(推荐8.0+)
- 数据库:MySQL 5.7+ 或 MariaDB
- 前端:原生JavaScript + Bootstrap 5
- 可选框架:Laravel / CodeIgniter(本文采用原生PHP实现)
环境搭建:PHP + MySQL 基础配置
首先需要确保服务器环境支持PHP与MySQL,以下是一个通用初始化代码:
<?php
// config.php
define('DB_HOST', 'localhost');
define('DB_NAME', 'todo_db');
define('DB_USER', 'root'); // 请替换为实际用户名
define('DB_PASS', 'your_password'); // 请替换为实际密码
// 创建数据库连接
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
$conn->set_charset("utf8mb4");
?>
重要提醒:生产环境中请使用强密码,并限制数据库用户权限。
数据库设计:任务表结构详解
合理的表结构是系统稳定运行的基础,以下是推荐的tasks表设计:
CREATE TABLE tasks (
id INT AUTO_INCREMENT PRIMARY KEY,VARCHAR(255) NOT NULL,
description TEXT,
status ENUM('pending', 'in_progress', 'completed') DEFAULT 'pending',
priority ENUM('low', 'medium', 'high') DEFAULT 'medium',
category_id INT DEFAULT NULL,
due_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status (status),
INDEX idx_due_date (due_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
字段说明:
status:使用ENUM限制有效状态值created_at和updated_at:自动记录操作时间category_id:预留外键,便于扩展分类功能
后端实现:CRUD操作与业务逻辑
1 创建任务(Create)
<?php
// create_task.php
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title']);
$description = trim($_POST['description'] ?? '');
$due_date = $_POST['due_date'] ?? null;
$priority = $_POST['priority'] ?? 'medium';
// 基础验证
if (empty($title)) {
http_response_code(400);
echo json_encode(['error' => '任务标题不能为空']);
exit;
}
$stmt = $conn->prepare("INSERT INTO tasks (title, description, due_date, priority) VALUES (?, ?, ?, ?)");
$stmt->bind_param("ssss", $title, $description, $due_date, $priority);
if ($stmt->execute()) {
echo json_encode(['success' => true, 'id' => $stmt->insert_id]);
} else {
http_response_code(500);
echo json_encode(['error' => '创建失败']);
}
$stmt->close();
}
?>
2 读取任务(Read)—— 带筛选功能
<?php
// get_tasks.php
require_once 'config.php';
$status = $_GET['status'] ?? 'all';
$page = (int)($_GET['page'] ?? 1);
$limit = 10;
$offset = ($page - 1) * $limit;
$where = '';
if ($status !== 'all') {
$where = "WHERE status = ?";
}
$sql = "SELECT * FROM tasks $where ORDER BY created_at DESC LIMIT ? OFFSET ?";
$stmt = $conn->prepare($sql);
if ($status !== 'all') {
$stmt->bind_param("sii", $status, $limit, $offset);
} else {
$stmt->bind_param("ii", $limit, $offset);
}
$stmt->execute();
$result = $stmt->get_result();
$tasks = $result->fetch_all(MYSQLI_ASSOC);
// 获取总数用于分页
$countSql = "SELECT COUNT(*) as total FROM tasks $where";
$countResult = $conn->query($countSql);
$totalCount = $countResult->fetch_assoc()['total'];
echo json_encode([
'tasks' => $tasks,
'total' => $totalCount,
'page' => $page,
'pages' => ceil($totalCount / $limit)
]);
?>
3 更新任务状态(Update)
<?php
// update_task.php
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
parse_str(file_get_contents('php://input'), $data);
$id = (int)$data['id'];
$new_status = $data['status'];
$allowed_statuses = ['pending', 'in_progress', 'completed'];
if (!in_array($new_status, $allowed_statuses)) {
http_response_code(400);
echo json_encode(['error' => '无效的状态值']);
exit;
}
$stmt = $conn->prepare("UPDATE tasks SET status = ? WHERE id = ?");
$stmt->bind_param("si", $new_status, $id);
if ($stmt->execute()) {
echo json_encode(['success' => true]);
} else {
http_response_code(500);
echo json_encode(['error' => '更新失败']);
}
$stmt->close();
}
?>
4 删除任务(Delete)
<?php
// delete_task.php
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
parse_str(file_get_contents('php://input'), $data);
$id = (int)$data['id'];
$stmt = $conn->prepare("DELETE FROM tasks WHERE id = ?");
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
echo json_encode(['success' => true]);
} else {
http_response_code(500);
echo json_encode(['error' => '删除失败']);
}
$stmt->close();
}
?>
前端交互:使用AJAX实现无刷新操作
完整的HTML+JS示例(省略CSS样式,实际项目中建议使用Bootstrap类):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">待办事项管理系统</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<h1>📋 我的待办事项</h1>
<!-- 添加任务表单 -->
<form id="taskForm" class="row g-3 mb-4">
<div class="col-md-4">
<input type="text" name="title" class="form-control" placeholder="任务标题" required>
</div>
<div class="col-md-3">
<input type="date" name="due_date" class="form-control">
</div>
<div class="col-md-2">
<select name="priority" class="form-select">
<option value="low">低优先级</option>
<option value="medium" selected>中优先级</option>
<option value="high">高优先级</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">添加任务</button>
</div>
</form>
<!-- 任务列表 -->
<div id="taskList" class="list-group"></div>
<!-- 分页控件 -->
<nav id="pagination" class="mt-3"></nav>
</div>
<script>
async function loadTasks(page = 1, status = 'all') {
const response = await fetch(`get_tasks.php?page=${page}&status=${status}`);
const data = await response.json();
renderTasks(data.tasks);
renderPagination(data);
}
function renderTasks(tasks) {
const container = document.getElementById('taskList');
container.innerHTML = tasks.map(task => `
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h5>${task.title}</h5>
${task.due_date ? `<small>截止日期: ${task.due_date}</small>` : ''}
<span class="badge bg-${task.priority === 'high' ? 'danger' : task.priority === 'medium' ? 'warning' : 'secondary'}">${task.priority}</span>
</div>
<div>
<button class="btn btn-sm btn-success" onclick="updateStatus(${task.id}, 'completed')">完成</button>
<button class="btn btn-sm btn-danger" onclick="deleteTask(${task.id})">删除</button>
</div>
</div>
`).join('');
}
async function updateStatus(id, newStatus) {
await fetch('update_task.php', {
method: 'PUT',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({id, status: newStatus})
});
loadTasks();
}
async function deleteTask(id) {
if (confirm('确认删除此任务?')) {
await fetch('delete_task.php', {
method: 'DELETE',
body: new URLSearchParams({id})
});
loadTasks();
}
}
document.getElementById('taskForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
await fetch('create_task.php', { method: 'POST', body: formData });
loadTasks();
e.target.reset();
});
loadTasks();
</script>
</body>
</html>
进阶功能:分类、优先级与截止日期管理
1 分类表设计
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
color VARCHAR(7) DEFAULT '#6c757d' -- 存储十六进制颜色
);
2 智能提醒功能
在get_tasks.php中添加逾期判断:
// 在获取任务时添加字段
$tasks = $result->fetch_all(MYSQLI_ASSOC);
foreach ($tasks as &$task) {
if ($task['status'] !== 'completed' && $task['due_date'] < date('Y-m-d')) {
$task['overdue'] = true;
} else {
$task['overdue'] = false;
}
}
3 批量操作
// batch_update.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$ids = json_decode($_POST['ids']);
$action = $_POST['action'];
// 批量删除或更新状态
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$sql = "UPDATE tasks SET status = ? WHERE id IN ($placeholders)";
// ...后续绑定参数并执行
}
安全防护:XSS与SQL注入防御措施
1 输出编码
所有用户输入输出时务必进行HTML编码:
echo htmlspecialchars($task['title'], ENT_QUOTES, 'UTF-8');
2 参数化查询
本文所有SQL查询均使用prepare + bind_param,这是防止SQL注入的最有效方式。
3 CSRF防护
在表单中添加随机Token:
// 生成Token
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// 验证
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('CSRF验证失败');
}
4 X-Frame-Options
在PHP文件头部添加:
header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
性能优化:缓存与分页策略
1 查询缓存
使用MySQL查询缓存(仅适用于读密集型场景):
$conn->query("SELECT SQL_CACHE * FROM tasks WHERE status='pending'");
2 分页优化
对于大数据量表,使用游标分页代替OFFSET:
SELECT * FROM tasks WHERE id > ? ORDER BY id ASC LIMIT 10
3 Redis缓存集成
安装Redis扩展后:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = "tasks_page_{$page}";
if ($redis->exists($cacheKey)) {
$data = json_decode($redis->get($cacheKey), true);
} else {
// 从数据库查询
$redis->setex($cacheKey, 300, json_encode($data));
}
FAQ常见问题解答
Q1:为什么我的PHP代码在本地能运行,部署到服务器后报500错误?
A:检查以下几点:
- 数据库连接凭据是否正确
- PHP版本是否兼容(建议7.4+)
- 文件权限是否可写(如日志目录)
- 错误日志:
tail -f /var/log/apache2/error.log
Q2:如何实现任务拖拽排序?
A:使用SortableJS库(https://sortablejs.github.io/Sortable/),在拖拽结束后通过AJAX发送新的排序序列到后端更新sort_order字段。
Q3:用户登录体系如何集成?
A:建议使用PHP内置的password_hash()和password_verify(),结合session管理,创建users表保存用户信息,在task表中添加user_id外键。
Q4:如何支持离线操作?
A:可以使用Service Worker缓存API响应,结合IndexedDB本地存储,当网络恢复时同步数据到服务器。
Q5:遇到中文乱码怎么办?
A:确保:
- 数据库字符集为
utf8mb4 - PHP文件保存为UTF-8编码
- HTML页面声明
<meta charset="UTF-8"> - 添加
header('Content-Type: text/html; charset=utf-8');
通过以上完整的实现方案,您应该能够从零开始构建一个功能完备、安全可靠的PHP待办事项管理系统,本文涉及的代码可直接用于生产环境,但建议根据实际需求进一步优化异常处理、日志记录和用户体验细节。