完整的PHP Todo List应用程序代码

将样式保存为独立CSS文件的方法

要将样式保存为独立的CSS文件供页面加载,你可以按照以下步骤操作:

1. 创建CSS文件

在你的项目目录中创建一个新的CSS文件,例如style.css,然后将所有<style>标签内的CSS代码复制到这个文件中。

2. 修改HTML文件

在你的PHP/HTML文件中,删除原有的<style>标签,改为使用<link>标签引入外部CSS文件:

1
<link rel="stylesheet" href="style.css">

3. 完整的CSS文件示例

将你提供的样式代码保存为style.css文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/* style.css */
:root {
--primary-color: #4a6fa5;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
--light-color: #f8f9fa;
--dark-color: #343a40;
--progress-low: #ff6b6b;
--progress-medium: #ffd166;
--progress-high: #06d6a0;
}

* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
padding: 0;
margin: 0;
}

/* 其余所有CSS样式... */

@media (max-width: 600px) {
.container {
padding: 10px;
}

h1 {
font-size: 24px;
}

.todo-form {
flex-direction: column;
}

.todo-input {
width: 100%;
margin-bottom: 0;
}

.todo-submit {
width: 100%;
padding: 12px;
}

.tabs {
justify-content: space-around;
}

.tab {
padding: 10px;
flex: 1;
text-align: center;
}

.todo-header {
padding: 10px;
}

.modal-content {
width: 95%;
max-height: 85vh;
}

.progress-badge {
display: none;
}
}

4. 在PHP文件中引入CSS

确保你的PHP文件头部有以下链接:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高级待办列表</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<link rel="stylesheet" href="style.css"> <!-- 引入本地CSS文件 -->
</head>

5. 额外建议

  1. CSS文件组织

    • 对于大型项目,可以按功能模块拆分CSS文件
    • 例如:main.cssmodal.cssforms.css
  2. 缓存控制

    • 在生产环境中,考虑添加版本号防止缓存问题:
    1
    <link rel="stylesheet" href="style.css?v=1.0.1">
  3. 性能优化

    • 压缩CSS文件以减小体积
    • 使用CDN加速静态资源加载
  4. 路径问题

    • 如果CSS文件在不同目录,确保路径正确
    • 例如:<link rel="stylesheet" href="css/style.css">

通过这种方式,你的样式将与HTML内容分离,使代码更加模块化、易于维护,并且可以被浏览器缓存以提高性能。

时间: 2025-03-25T19:21:34


👤 用户

给我一个完美的todo list的php代码或小程序

时间: 2025-03-25T12:47:27


🤖 DeepSeek (deepseek-chat)

完美的 Todo List PHP 应用程序

下面是一个完整的、功能完善的 Todo List PHP 应用程序,包含数据库连接、增删改查功能和简洁的界面。

功能特点

  • 添加新任务
  • 标记任务为已完成
  • 删除任务
  • 任务分类(可选)
  • 响应式设计
  • 数据库持久化

文件结构

1
2
3
4
/todo-app/
│── index.php # 主界面和逻辑
│── style.css # 样式表
│── init-db.php # 数据库初始化脚本(首次运行需要)

数据库设置 (MySQL)

首先运行 init-db.php 来创建数据库和表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
// init-db.php
$host = 'localhost';
$dbname = 'todo_app';
$username = 'root';
$password = '';

try {
$pdo = new PDO("mysql:host=$host", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// 创建数据库
$pdo->exec("CREATE DATABASE IF NOT EXISTS $dbname");
$pdo->exec("USE $dbname");

// 创建表
$pdo->exec("CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
is_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)");

echo "数据库和表创建成功!";
} catch (PDOException $e) {
die("数据库错误: " . $e->getMessage());
}
?>

主应用程序 (index.php)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<?php
// 数据库配置
$host = 'localhost';
$dbname = 'todo_app';
$username = 'root';
$password = '';

try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("数据库连接失败: " . $e->getMessage());
}

// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_task'])) {
$title = trim($_POST['title']);
$description = trim($_POST['description']);

if (!empty($title)) {
$stmt = $pdo->prepare("INSERT INTO tasks (title, description) VALUES (?, ?)");
$stmt->execute([$title, $description]);
}
} elseif (isset($_POST['delete_task'])) {
$id = $_POST['task_id'];
$stmt = $pdo->prepare("DELETE FROM tasks WHERE id = ?");
$stmt->execute([$id]);
} elseif (isset($_POST['toggle_complete'])) {
$id = $_POST['task_id'];
$stmt = $pdo->prepare("UPDATE tasks SET is_completed = NOT is_completed WHERE id = ?");
$stmt->execute([$id]);
}
}

// 获取所有任务
$stmt = $pdo->query("SELECT * FROM tasks ORDER BY is_completed, created_at DESC");
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>完美的 Todo List</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>我的 Todo List</h1>

<!-- 添加新任务表单 -->
<form method="POST" class="task-form">
<input type="text" name="title" placeholder="任务标题" required>
<textarea name="description" placeholder="任务描述(可选)"></textarea>
<button type="submit" name="add_task">添加任务</button>
</form>

<!-- 任务列表 -->
<div class="task-list">
<?php if (empty($tasks)): ?>
<p>暂无任务,添加一个吧!</p>
<?php else: ?>
<?php foreach ($tasks as $task): ?>
<div class="task <?= $task['is_completed'] ? 'completed' : '' ?>">
<div class="task-info">
<h3><?= htmlspecialchars($task['title']) ?></h3>
<?php if (!empty($task['description'])): ?>
<p><?= htmlspecialchars($task['description']) ?></p>
<?php endif; ?>
<small>创建于: <?= $task['created_at'] ?></small>
</div>
<div class="task-actions">
<form method="POST">
<input type="hidden" name="task_id" value="<?= $task['id'] ?>">
<button type="submit" name="toggle_complete" class="complete-btn">
<?= $task['is_completed'] ? '标记未完成' : '标记完成' ?>
</button>
<button type="submit" name="delete_task" class="delete-btn">删除</button>
</form>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</body>
</html>

样式表 (style.css)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/* style.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Arial', sans-serif;
}

body {
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
}

.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}

h1 {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
}

.task-form {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}

.task-form input[type="text"],
.task-form textarea {
width: 100%;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

.task-form textarea {
min-height: 80px;
resize: vertical;
}

.task-form button {
background: #3498db;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}

.task-form button:hover {
background: #2980b9;
}

.task-list {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
}

.task {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #eee;
}

.task:last-child {
border-bottom: none;
}

.task.completed {
opacity: 0.7;
}

.task.completed h3 {
text-decoration: line-through;
color: #888;
}

.task-info {
flex: 1;
}

.task-info h3 {
margin-bottom: 5px;
}

.task-info p {
color: #666;
margin-bottom: 5px;
}

.task-info small {
color: #999;
font-size: 12px;
}

.task-actions form {
display: flex;
gap: 5px;
}

.task-actions button {
padding: 5px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: opacity 0.3s;
}

.task-actions button:hover {
opacity: 0.8;
}

.complete-btn {
background: #2ecc71;
color: white;
}

.delete-btn {
background: #e74c3c;
color: white;
}

@media (max-width: 600px) {
.task {
flex-direction: column;
align-items: flex-start;
}

.task-actions {
margin-top: 10px;
width: 100%;
}

.task-actions form {
width: 100%;
}

.task-actions button {
flex: 1;
padding: 8px;
}
}

部署说明

  1. 确保你的服务器已安装 PHP 和 MySQL
  2. 将上述文件放在你的 web 服务器目录中(如 /var/www/html/todo-app/)
  3. 首先访问 init-db.php 来初始化数据库
  4. 然后访问 index.php 使用你的 Todo List 应用

扩展建议

如果你想进一步增强这个应用,可以考虑:

  1. 添加用户认证系统
  2. 增加任务分类或标签
  3. 添加任务优先级
  4. 实现任务搜索和过滤功能
  5. 添加截止日期功能

这个 Todo List 应用已经包含了基本功能,代码结构清晰,易于理解和扩展。

时间: 2025-03-25T12:47:27


👤 用户

好的,帮我根据你的扩展建议完善下相关代码。

时间: 2025-03-25T13:54:04


🤖 DeepSeek (deepseek-chat)

增强版 Todo List PHP 应用程序

下面是根据扩展建议完善后的增强版 Todo List 应用,包含用户认证、任务分类、优先级、截止日期和搜索功能。

新增功能

  1. 用户认证系统(简易版)
  2. 任务分类/标签
  3. 任务优先级
  4. 截止日期
  5. 搜索和过滤功能

更新后的文件结构

1
2
3
4
5
6
7
/todo-app/
│── index.php # 主界面和逻辑
│── style.css # 样式表
│── init-db.php # 数据库初始化脚本
│── auth.php # 认证处理
│── login.php # 登录页面
│── logout.php # 登出处理

更新后的数据库初始化 (init-db.php)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
// init-db.php
$host = 'localhost';
$dbname = 'todo_app_enhanced';
$username = 'root';
$password = '';

try {
$pdo = new PDO("mysql:host=$host", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// 创建数据库
$pdo->exec("CREATE DATABASE IF NOT EXISTS $dbname");
$pdo->exec("USE $dbname");

// 创建用户表
$pdo->exec("CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");

// 创建分类表
$pdo->exec("CREATE TABLE IF NOT EXISTS categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
color VARCHAR(7) DEFAULT '#3498db'
)");

// 创建任务表
$pdo->exec("CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
category_id INT,
priority ENUM('low', 'medium', 'high') DEFAULT 'medium',
due_date DATE,
is_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
)");

// 插入默认分类
$defaultCategories = [
['工作', '#e74c3c'],
['个人', '#3498db'],
['学习', '#2ecc71'],
['其他', '#9b59b6']
];

$stmt = $pdo->prepare("INSERT IGNORE INTO categories (name, color) VALUES (?, ?)");
foreach ($defaultCategories as $category) {
$stmt->execute($category);
}

// 创建测试用户 (用户名: test, 密码: test123)
$hashedPassword = password_hash('test123', PASSWORD_DEFAULT);
$pdo->exec("INSERT IGNORE INTO users (username, password) VALUES ('test', '$hashedPassword')");

echo "数据库和表创建成功!<br>";
echo "测试用户已创建: 用户名 'test', 密码 'test123'";
} catch (PDOException $e) {
die("数据库错误: " . $e->getMessage());
}
?>

认证系统 (auth.php, login.php, logout.php)

auth.php (认证处理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
// auth.php
session_start();

function isLoggedIn() {
return isset($_SESSION['user_id']);
}

function getUserID() {
return $_SESSION['user_id'] ?? null;
}

function redirectIfNotLoggedIn() {
if (!isLoggedIn()) {
header("Location: login.php");
exit();
}
}

// 数据库配置
$host = 'localhost';
$dbname = 'todo_app_enhanced';
$username = 'root';
$password = '';

try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("数据库连接失败: " . $e->getMessage());
}
?>

login.php (登录页面)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
require 'auth.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username']);
$password = trim($_POST['password']);

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();

if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
header("Location: index.php");
exit();
} else {
$error = "用户名或密码错误";
}
}
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - Todo List</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="login-form">
<h1>登录</h1>
<?php if (isset($error)): ?>
<div class="alert error"><?= $error ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn">登录</button>
</form>
<p>测试用户: 用户名 'test', 密码 'test123'</p>
</div>
</div>
</body>
</html>

logout.php (登出处理)

1
2
3
4
5
6
7
<?php
require 'auth.php';

session_destroy();
header("Location: login.php");
exit();
?>

更新后的主应用程序 (index.php)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
<?php
require 'auth.php';
redirectIfNotLoggedIn();

$userID = getUserID();

// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_task'])) {
$title = trim($_POST['title']);
$description = trim($_POST['description']);
$categoryID = $_POST['category_id'] ?? null;
$priority = $_POST['priority'] ?? 'medium';
$dueDate = $_POST['due_date'] ?? null;

if (!empty($title)) {
$stmt = $pdo->prepare("INSERT INTO tasks
(user_id, title, description, category_id, priority, due_date)
VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$userID, $title, $description, $categoryID, $priority, $dueDate]);
}
} elseif (isset($_POST['delete_task'])) {
$taskID = $_POST['task_id'];
$stmt = $pdo->prepare("DELETE FROM tasks WHERE id = ? AND user_id = ?");
$stmt->execute([$taskID, $userID]);
} elseif (isset($_POST['toggle_complete'])) {
$taskID = $_POST['task_id'];
$stmt = $pdo->prepare("UPDATE tasks SET is_completed = NOT is_completed
WHERE id = ? AND user_id = ?");
$stmt->execute([$taskID, $userID]);
}
}

// 获取搜索和过滤参数
$search = $_GET['search'] ?? '';
$filter = $_GET['filter'] ?? 'all';
$categoryFilter = $_GET['category'] ?? null;
$priorityFilter = $_GET['priority'] ?? null;

// 构建查询
$query = "SELECT t.*, c.name as category_name, c.color as category_color
FROM tasks t
LEFT JOIN categories c ON t.category_id = c.id
WHERE t.user_id = ?";
$params = [$userID];

// 添加搜索条件
if (!empty($search)) {
$query .= " AND (t.title LIKE ? OR t.description LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
}

// 添加状态过滤
if ($filter === 'active') {
$query .= " AND t.is_completed = FALSE";
} elseif ($filter === 'completed') {
$query .= " AND t.is_completed = TRUE";
}

// 添加分类过滤
if (!empty($categoryFilter)) {
$query .= " AND t.category_id = ?";
$params[] = $categoryFilter;
}

// 添加优先级过滤
if (!empty($priorityFilter)) {
$query .= " AND t.priority = ?";
$params[] = $priorityFilter;
}

// 排序
$query .= " ORDER BY
CASE WHEN t.due_date IS NULL THEN 1 ELSE 0 END,
t.due_date ASC,
CASE t.priority
WHEN 'high' THEN 1
WHEN 'medium' THEN 2
WHEN 'low' THEN 3
END,
t.created_at DESC";

// 执行查询
$stmt = $pdo->prepare($query);
$stmt->execute($params);
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 获取所有分类
$categories = $pdo->query("SELECT * FROM categories")->fetchAll(PDO::FETCH_ASSOC);
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>增强版 Todo List</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header>
<h1>我的增强版 Todo List</h1>
<div class="user-actions">
<a href="logout.php" class="btn logout-btn">登出</a>
</div>
</header>

<!-- 搜索和过滤表单 -->
<div class="filters">
<form method="GET" class="search-form">
<input type="text" name="search" placeholder="搜索任务..." value="<?= htmlspecialchars($search) ?>">
<button type="submit">搜索</button>
</form>

<div class="filter-options">
<a href="?filter=all" class="<?= $filter === 'all' ? 'active' : '' ?>">全部</a>
<a href="?filter=active" class="<?= $filter === 'active' ? 'active' : '' ?>">未完成</a>
<a href="?filter=completed" class="<?= $filter === 'completed' ? 'active' : '' ?>">已完成</a>

<select onchange="window.location.href = this.value ? '?priority=' + this.value : '?'">
<option value="">所有优先级</option>
<option value="high" <?= $priorityFilter === 'high' ? 'selected' : '' ?>>高优先级</option>
<option value="medium" <?= $priorityFilter === 'medium' ? 'selected' : '' ?>>中优先级</option>
<option value="low" <?= $priorityFilter === 'low' ? 'selected' : '' ?>>低优先级</option>
</select>

<select onchange="window.location.href = this.value ? '?category=' + this.value : '?'">
<option value="">所有分类</option>
<?php foreach ($categories as $category): ?>
<option value="<?= $category['id'] ?>" <?= $categoryFilter == $category['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($category['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>

<!-- 添加新任务表单 -->
<form method="POST" class="task-form">
<input type="text" name="title" placeholder="任务标题" required>
<textarea name="description" placeholder="任务描述(可选)"></textarea>

<div class="form-row">
<div class="form-group">
<label>分类</label>
<select name="category_id">
<option value="">无分类</option>
<?php foreach ($categories as $category): ?>
<option value="<?= $category['id'] ?>"><?= htmlspecialchars($category['name']) ?></option>
<?php endforeach; ?>
</select>
</div>

<div class="form-group">
<label>优先级</label>
<select name="priority">
<option value="medium">中</option>
<option value="high">高</option>
<option value="low">低</option>
</select>
</div>

<div class="form-group">
<label>截止日期</label>
<input type="date" name="due_date">
</div>
</div>

<button type="submit" name="add_task">添加任务</button>
</form>

<!-- 任务列表 -->
<div class="task-list">
<?php if (empty($tasks)): ?>
<p class="no-tasks">没有找到任务</p>
<?php else: ?>
<?php foreach ($tasks as $task): ?>
<div class="task <?= $task['is_completed'] ? 'completed' : '' ?>
priority-<?= $task['priority'] ?>">
<?php if ($task['category_id']): ?>
<div class="category-tag" style="background-color: <?= $task['category_color'] ?>">
<?= htmlspecialchars($task['category_name']) ?>
</div>
<?php endif; ?>

<div class="task-info">
<h3><?= htmlspecialchars($task['title']) ?></h3>
<?php if (!empty($task['description'])): ?>
<p><?= htmlspecialchars($task['description']) ?></p>
<?php endif; ?>

<div class="task-meta">
<?php if ($task['due_date']): ?>
<span class="due-date <?=
strtotime($task['due_date']) < time() && !$task['is_completed'] ? 'overdue' : ''
?>">
📅 <?= date('Y-m-d', strtotime($task['due_date'])) ?>
</span>
<?php endif; ?>

<span class="priority-badge priority-<?= $task['priority'] ?>">
<?=
$task['priority'] === 'high' ? '高优先级' :
($task['priority'] === 'medium' ? '中优先级' : '低优先级')
?>
</span>

<small>创建于: <?= date('Y-m-d H:i', strtotime($task['created_at'])) ?></small>
</div>
</div>

<div class="task-actions">
<form method="POST">
<input type="hidden" name="task_id" value="<?= $task['id'] ?>">
<button type="submit" name="toggle_complete" class="complete-btn">
<?= $task['is_completed'] ? '✓ 已完成' : '标记完成' ?>
</button>
<button type="submit" name="delete_task" class="delete-btn">删除</button>
</form>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</body>
</html>

更新后的样式表 (style.css)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/* 基础样式保持不变,新增以下样式 */

/* 头部和用户操作 */
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}

.user-actions {
display: flex;
gap: 10px;
}

.logout-btn {
background: #e74c3c;
color: white;
text-decoration: none;
padding: 8px 15px;
border-radius: 4px;
font-size: 14px;
}

/* 搜索和过滤 */
.filters {
margin-bottom: 20px;
}

.search-form {
display: flex;
margin-bottom: 15px;
}

.search-form input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
}

.search-form button {
padding: 10px 15px;
background: #3498db;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}

.filter-options {
display: flex;
gap: 10px;
flex-wrap: wrap;
}

.filter-options a {
padding: 5px 10px;
background: #eee;
border-radius: 4px;
text-decoration: none;
color: #333;
}

.filter-options a.active {
background: #3498db;
color: white;
}

.filter-options select {
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}

/* 表单行 */
.form-row {
display: flex;
gap: 15px;
margin-bottom: 15px;
}

.form-group {
flex: 1;
}

.form-group label {
display: block;
margin-bottom: 5px;
font-size: 14px;
color: #666;
}

.form-group select,
.form-group input[type="date"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}

/* 任务样式增强 */
.task {
position: relative;
padding-top: 15px;
}

.category-tag {
position: absolute;
top: 0;
left: 0;
padding: 2px 8px;
border-radius: 4px 0 4px 0;
color: white;
font-size: 12px;
}

.task-meta {
margin-top: 8px;
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
font-size: 13px;
color: #666;
}

.due-date {
display: inline-flex;
align-items: center;
gap: 3px;
}

.due-date.overdue {
color: #e74c3c;
font-weight: bold;
}

.priority-badge {
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
}

.priority-badge.priority-high {
background: #e74c3c;
color: white;
}

.priority-badge.priority-medium {
background: #f39c12;
color: white;
}

.priority-badge.priority-low {
background: #2ecc71;
color: white;
}

/* 优先级指示 */
.task.priority-high {
border-left: 4px solid #e74c3c;
}

.task.priority-medium {
border-left: 4px solid #f39c12;
}

.task.priority-low {
border-left: 4px solid #2ecc71;
}

/* 无任务提示 */
.no-tasks {
text-align: center;
padding: 30px;
color: #999;
background: #f9f9f9;
border-radius: 8px;
}

/* 响应式调整 */
@media (max-width: 768px) {
.form-row {
flex-direction: column;
gap: 10px;
}

.filter-options {
flex-direction: column;
}

.task-actions form {
flex-direction: column;
}

.task-actions button {
width: 100%;
}
}

使用说明

  1. 首先运行 init-db.php 初始化数据库
  2. 访问 login.php 登录(测试用户:test/test123)
  3. 使用增强版 Todo List 功能

功能说明

  1. 用户认证:必须登录才能使用应用
  2. 任务分类:可以为任务选择分类,分类会以彩色标签显示
  3. 优先级:高、中、低三种优先级,会影响任务排序和显示样式
  4. 截止日期:设置任务截止日期,过期未完成会显示为红色
  5. 搜索和过滤
    • 按关键词搜索
    • 按状态过滤(全部/未完成/已完成)
    • 按分类过滤
    • 按优先级过滤

这个增强版 Todo List 应用现在具备了更完整的功能,代码结构清晰,易于维护和进一步扩展。

时间: 2025-03-25T13:54:04


👤 用户

数据库错误: could not find driver

时间: 2025-03-25T18:36:21


🤖 DeepSeek (deepseek-chat)

解决 “数据库错误: could not find driver” 问题

这个错误表明 PHP 缺少必要的数据库驱动来连接 MySQL 数据库。以下是完整的解决方案:

1. 确认错误原因

错误信息 “could not find driver” 通常意味着:

  • 没有安装 PHP 的 MySQL PDO 扩展
  • 安装的扩展没有启用
  • PHP 配置有问题

2. 解决方案

对于 Windows 系统:

  1. 打开 php.ini 文件

    • 找到你的 PHP 安装目录(如 C:\php
    • 打开 php.ini 文件
  2. 取消注释以下行(去掉前面的分号):

    1
    2
    extension=pdo_mysql
    extension=mysqli
  3. 保存文件并重启 web 服务器(Apache/Nginx)

  4. 验证安装

    • 创建一个 phpinfo.php 文件,内容为:
      1
      <?php phpinfo(); ?>
    • 在浏览器中访问该文件
    • 搜索 “pdo_mysql”,确认它已启用

对于 Linux 系统(Ubuntu/Debian):

  1. 安装必要的扩展

    1
    2
    sudo apt-get update
    sudo apt-get install php-mysql
  2. 对于 PHP 7.x/8.x

    1
    sudo apt-get install php7.x-mysql  # 将 7.x 替换为你的 PHP 版本
  3. 重启 web 服务器

    1
    2
    sudo service apache2 restart  # 对于 Apache
    sudo service nginx restart # 对于 Nginx

对于 macOS 系统:

  1. 如果你使用 Homebrew:

    1
    2
    brew install php
    brew services restart php
  2. 确保以下行在 php.ini 中取消注释:

    1
    extension=pdo_mysql

3. 验证解决方案

创建测试文件 test_db.php

1
2
3
4
5
6
7
8
<?php
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
echo "数据库连接成功!";
} catch (PDOException $e) {
echo "连接失败: " . $e->getMessage();
}
?>

如果看到”数据库连接成功!”,说明问题已解决。

4. 其他可能的问题

  1. PHP 版本不匹配

    • 确保你安装的扩展版本与 PHP 版本匹配
  2. 多个 PHP 版本

    • 如果你有多个 PHP 版本,确保修改了正确的 php.ini 文件
  3. Web 服务器缓存

    • 有时需要完全重启电脑才能使更改生效
  4. 缺少 MySQL 客户端库

    • Linux 上可能需要安装:sudo apt-get install default-libmysqlclient-dev

5. 替代方案

如果仍然无法解决,可以考虑:

  1. 使用 MySQLi 代替 PDO:
1
$conn = new mysqli('localhost', 'username', 'password', 'database');
  1. 使用其他数据库如 SQLite(不需要额外驱动):
1
$pdo = new PDO('sqlite:/path/to/database.sqlite');

6. 完整的安装检查清单

  1. PHP 已安装并运行
  2. MySQL 服务器已安装并运行
  3. PHP MySQL 扩展已安装
  4. php.ini 中已启用扩展
  5. Web 服务器已重启
  6. 防火墙允许 PHP 连接 MySQL(默认端口 3306)

按照这些步骤操作后,”could not find driver” 问题应该能够得到解决。如果仍有问题,可以提供你的操作系统和 PHP 版本信息,我可以给出更具体的解决方案。

时间: 2025-03-25T18:36:21


👤 用户

数据库错误: SQLSTATE[HY000]: General error: 1293 Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause

时间: 2025-03-25T18:39:12


🤖 DeepSeek (deepseek-chat)

解决 MySQL 时间戳列定义错误

这个错误是因为在 MySQL 5.6.5 之前的版本中,每个表只能有一个 TIMESTAMP 列使用 CURRENT_TIMESTAMP 作为默认值或自动更新值。

错误原因

在你的 init-db.php 文件中,你有两个 TIMESTAMP 列都使用了 CURRENT_TIMESTAMP:

1
2
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

这在旧版 MySQL 中是不允许的。

解决方案

方案1: 升级 MySQL (推荐)

升级到 MySQL 5.6.5 或更高版本,这些版本支持多个 TIMESTAMP 列使用 CURRENT_TIMESTAMP

方案2: 修改表结构 (兼容旧版本)

修改你的 init-db.php 文件,只保留一个自动更新的 TIMESTAMP 列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$pdo->exec("CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
category_id INT,
priority ENUM('low', 'medium', 'high') DEFAULT 'medium',
due_date DATE,
is_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
)");

// 然后添加触发器来自动更新 updated_at
$pdo->exec("CREATE TRIGGER update_tasks_timestamp
BEFORE UPDATE ON tasks
FOR EACH ROW
SET NEW.updated_at = CURRENT_TIMESTAMP");

方案3: 使用 DATETIME 替代 TIMESTAMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$pdo->exec("CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
category_id INT,
priority ENUM('low', 'medium', 'high') DEFAULT 'medium',
due_date DATE,
is_completed BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
)");

方案4: 只保留一个自动更新的列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$pdo->exec("CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
category_id INT,
priority ENUM('low', 'medium', 'high') DEFAULT 'medium',
due_date DATE,
is_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
)");

最佳实践建议

  1. 升级 MySQL:如果可能,升级到 5.6.5 或更高版本,这是最简单的解决方案。

  2. 使用 DATETIME:如果你需要存储超过 2038 年的日期,或者需要更大的日期范围,使用 DATETIME 类型更好。

  3. 添加触发器:如果你必须使用旧版 MySQL 并且需要两个时间戳列都自动更新,触发器是一个可靠的解决方案。

  4. 测试环境一致性:确保开发、测试和生产环境的 MySQL 版本一致,避免这类兼容性问题。

选择哪种方案取决于你的具体环境要求和对未来兼容性的考虑。

时间: 2025-03-25T18:39:12


👤 用户

你看这样,你能不能结合我给你的代码,更新一下布局,包括支持所见即所得 支持markdown

时间: 2025-03-25T18:50:13


🤖 DeepSeek (deepseek-chat)

高级待办列表应用 - 支持Markdown的增强版

我已经分析了你的代码,并进行了以下改进,特别是增强了Markdown编辑功能和整体布局:

主要改进

  1. 完全集成的Markdown编辑器 - 使用SimpleMDE实现所见即所得的编辑体验
  2. 改进的UI布局 - 更现代化的设计,更好的响应式支持
  3. 增强的任务详情编辑 - 支持Markdown格式的任务详情
  4. 优化的代码结构 - 更清晰的PHP和JavaScript分离

完整代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
<?php
// 初始化待办列表数组
$todos = [];

// 检查是否有已保存的待办事项
if (file_exists('todos.json')) {
$todos = json_decode(file_get_contents('todos.json'), true);
}

// 处理添加新待办事项
if (isset($_POST['new_todo']) && !empty($_POST['new_todo'])) {
$newTodo = [
'id' => uniqid(),
'text' => htmlspecialchars($_POST['new_todo']),
'details' => '',
'progress' => 0,
'completed' => false,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
array_unshift($todos, $newTodo);
saveTodos($todos);
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}

// 处理删除待办事项
if (isset($_GET['delete'])) {
$id = $_GET['delete'];
$todos = array_filter($todos, function($todo) use ($id) {
return $todo['id'] !== $id;
});
saveTodos(array_values($todos));
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}

// 处理切换完成状态
if (isset($_GET['toggle'])) {
$id = $_GET['toggle'];
foreach ($todos as &$todo) {
if ($todo['id'] === $id) {
$todo['completed'] = !$todo['completed'];
$todo['updated_at'] = date('Y-m-d H:i:s');
break;
}
}
saveTodos($todos);
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}

// 处理更新待办事项
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
$id = $_POST['id'];
foreach ($todos as &$todo) {
if ($todo['id'] === $id) {
if (isset($_POST['text'])) {
$todo['text'] = htmlspecialchars($_POST['text']);
}
if (isset($_POST['details'])) {
$todo['details'] = $_POST['details'];
}
if (isset($_POST['progress'])) {
$todo['progress'] = intval($_POST['progress']);
}
$todo['updated_at'] = date('Y-m-d H:i:s');
break;
}
}
saveTodos($todos);

if (isset($_POST['ajax'])) {
header('Content-Type: application/json');
echo json_encode(['status' => 'success']);
exit;
} else {
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}
}

// 保存待办事项到文件
function saveTodos($todos) {
file_put_contents('todos.json', json_encode($todos, JSON_PRETTY_PRINT));
}

// 分离已完成和未完成的任务
$activeTodos = array_filter($todos, function($todo) {
return !$todo['completed'];
});
$completedTodos = array_filter($todos, function($todo) {
return $todo['completed'];
});

// 当前显示标签
$currentTab = isset($_GET['tab']) ? $_GET['tab'] : 'active';

// 根据进度获取颜色
function getProgressColor($progress) {
if ($progress < 30) {
return '#ff6b6b'; // 红色
} elseif ($progress < 70) {
return '#ffd166'; // 黄色
} else {
return '#06d6a0'; // 绿色
}
}
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高级待办列表</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<!-- 引入SimpleMDE Markdown编辑器 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<style>
:root {
--primary-color: #4a6fa5;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
--light-color: #f8f9fa;
--dark-color: #343a40;
--progress-low: #ff6b6b;
--progress-medium: #ffd166;
--progress-high: #06d6a0;
}

* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
padding: 0;
margin: 0;
}

.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}

h1 {
text-align: center;
color: var(--primary-color);
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--primary-color);
}

.todo-form {
display: flex;
margin-bottom: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}

.todo-input {
flex: 1;
padding: 12px 15px;
font-size: 16px;
border: none;
outline: none;
}

.todo-submit {
padding: 12px 20px;
background-color: var(--primary-color);
color: white;
border: none;
cursor: pointer;
transition: background-color 0.3s;
}

.todo-submit:hover {
background-color: #3a5a8a;
}

.tabs {
display: flex;
margin-bottom: 20px;
border-bottom: 1px solid #ddd;
}

.tab {
padding: 10px 20px;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s;
font-weight: 500;
}

.tab.active {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
}

.tab:hover:not(.active) {
background-color: rgba(0,0,0,0.05);
}

.todo-list {
list-style: none;
padding: 0;
}

.todo-item {
background: white;
margin-bottom: 10px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
transition: transform 0.3s, box-shadow 0.3s;
}

.todo-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

.todo-header {
display: flex;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid #eee;
position: relative;
cursor: pointer;
}

.progress-indicator {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background: linear-gradient(90deg, var(--progress-low), var(--progress-high));
width: 0%;
}

.todo-checkbox {
margin-right: 10px;
width: 20px;
height: 20px;
cursor: pointer;
}

.todo-title {
flex: 1;
font-weight: 500;
padding-right: 10px;
}

.todo-title.completed {
text-decoration: line-through;
color: var(--secondary-color);
}

.progress-badge {
background-color: #f0f0f0;
border-radius: 12px;
padding: 2px 8px;
font-size: 12px;
font-weight: bold;
margin-left: 10px;
}

.todo-actions {
display: flex;
}

.todo-btn {
background: none;
border: none;
cursor: pointer;
padding: 5px;
margin-left: 5px;
color: var(--secondary-color);
transition: color 0.3s;
}

.todo-btn.edit:hover {
color: var(--primary-color);
}

.todo-btn.delete:hover {
color: var(--danger-color);
}

.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}

.modal-content {
background: white;
width: 90%;
max-width: 700px;
max-height: 90vh;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
animation: modalFadeIn 0.3s;
display: flex;
flex-direction: column;
}

@keyframes modalFadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}

.modal-header {
padding: 15px;
background-color: var(--primary-color);
color: white;
display: flex;
justify-content: space-between;
align-items: center;
}

.modal-close {
background: none;
border: none;
color: white;
font-size: 20px;
cursor: pointer;
}

.modal-body {
padding: 20px;
overflow-y: auto;
flex: 1;
}

.form-group {
margin-bottom: 15px;
}

.form-label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}

.form-input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: inherit;
}

.editor-toolbar {
display: flex;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}

.editor-mode-btn {
background: none;
border: 1px solid #ddd;
padding: 5px 10px;
margin-right: 5px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}

.editor-mode-btn.active {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}

.editor-container {
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}

.preview-container {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
min-height: 200px;
display: none;
}

.progress-container {
margin-top: 15px;
}

.progress-label {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 14px;
}

.progress-bar {
height: 10px;
background-color: #e9ecef;
border-radius: 5px;
overflow: hidden;
}

.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--progress-low), var(--progress-high));
width: 0%;
transition: width 0.3s;
}

.modal-footer {
padding: 15px;
background-color: #f8f9fa;
display: flex;
justify-content: flex-end;
}

.save-btn {
background-color: var(--primary-color);
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}

.save-btn:hover {
background-color: #3a5a8a;
}

.no-tasks {
text-align: center;
padding: 20px;
color: var(--secondary-color);
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

@media (max-width: 600px) {
.container {
padding: 10px;
}

h1 {
font-size: 24px;
}

.todo-form {
flex-direction: column;
}

.todo-input {
width: 100%;
margin-bottom: 0;
}

.todo-submit {
width: 100%;
padding: 12px;
}

.tabs {
justify-content: space-around;
}

.tab {
padding: 10px;
flex: 1;
text-align: center;
}

.todo-header {
padding: 10px;
}

.modal-content {
width: 95%;
max-height: 85vh;
}

.progress-badge {
display: none;
}
}
</style>
</head>
<body>
<div class="container">
<h1>高级待办列表</h1>

<form class="todo-form" method="post">
<input type="text" class="todo-input" name="new_todo" placeholder="添加新任务..." required>
<button type="submit" class="todo-submit">添加</button>
</form>

<div class="tabs">
<div class="tab <?= $currentTab === 'active' ? 'active' : '' ?>" onclick="window.location.href='?tab=active'">
<i class="fas fa-tasks"></i> 待办事项
</div>
<div class="tab <?= $currentTab === 'completed' ? 'active' : '' ?>" onclick="window.location.href='?tab=completed'">
<i class="fas fa-check-circle"></i> 已完成
</div>
</div>

<ul class="todo-list">
<?php if ($currentTab === 'active'): ?>
<?php if (empty($activeTodos)): ?>
<div class="no-tasks">
<i class="fas fa-check-circle" style="font-size: 48px; color: #ddd; margin-bottom: 10px;"></i>
<p>没有待办事项</p>
</div>
<?php else: ?>
<?php foreach ($activeTodos as $todo): ?>
<li class="todo-item" data-id="<?= $todo['id'] ?>">
<div class="todo-header" onclick="openEditModal('<?= $todo['id'] ?>')">
<div class="progress-indicator" style="width: <?= $todo['progress'] ?>%"></div>
<input
type="checkbox"
class="todo-checkbox"
onclick="event.stopPropagation(); window.location.href='?toggle=<?= $todo['id'] ?>&tab=active'"
>
<div class="todo-title"><?= $todo['text'] ?></div>
<span class="progress-badge" style="color: <?= getProgressColor($todo['progress']) ?>">
<?= $todo['progress'] ?>%
</span>
<div class="todo-actions">
<button class="todo-btn delete" onclick="event.stopPropagation(); if(confirm('确定删除吗?')) window.location.href='?delete=<?= $todo['id'] ?>&tab=active'">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</li>
<?php endforeach; ?>
<?php endif; ?>
<?php else: ?>
<?php if (empty($completedTodos)): ?>
<div class="no-tasks">
<i class="fas fa-clipboard-list" style="font-size: 48px; color: #ddd; margin-bottom: 10px;"></i>
<p>没有已完成事项</p>
</div>
<?php else: ?>
<?php foreach ($completedTodos as $todo): ?>
<li class="todo-item" data-id="<?= $todo['id'] ?>">
<div class="todo-header" onclick="openEditModal('<?= $todo['id'] ?>')">
<div class="progress-indicator" style="width: 100%; background: var(--success-color)"></div>
<input
type="checkbox"
class="todo-checkbox"
checked
onclick="event.stopPropagation(); window.location.href='?toggle=<?= $todo['id'] ?>&tab=completed'"
>
<div class="todo-title completed"><?= $todo['text'] ?></div>
<span class="progress-badge" style="color: var(--success-color)">
100%
</span>
<div class="todo-actions">
<button class="todo-btn delete" onclick="event.stopPropagation(); if(confirm('确定删除吗?')) window.location.href='?delete=<?= $todo['id'] ?>&tab=completed'">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</li>
<?php endforeach; ?>
<?php endif; ?>
<?php endif; ?>
</ul>
</div>

<!-- 编辑模态框 -->
<div class="modal" id="editModal">
<div class="modal-content">
<div class="modal-header">
<h3>编辑任务</h3>
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<form id="editForm">
<input type="hidden" id="editId" name="id">
<div class="form-group">
<label for="editText" class="form-label">任务标题</label>
<input type="text" id="editText" name="text" class="form-input" required>
</div>
<div class="form-group">
<label class="form-label">任务详情</label>
<div class="editor-toolbar">
<button type="button" class="editor-mode-btn active" id="editModeBtn" onclick="setEditorMode('edit')">
<i class="fas fa-edit"></i> 编辑
</button>
<button type="button" class="editor-mode-btn" id="previewModeBtn" onclick="setEditorMode('preview')">
<i class="fas fa-eye"></i> 预览
</button>
</div>
<div class="editor-container">
<textarea id="editDetails" name="details"></textarea>
</div>
<div class="preview-container" id="previewContainer"></div>
</div>
<div class="form-group progress-container">
<label for="editProgress" class="form-label">进度</label>
<div class="progress-label">
<span id="progressValue">0%</span>
</div>
<input type="range" id="editProgress" name="progress" min="0" max="100" value="0" class="form-input" oninput="updateProgressValue(this.value)">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="save-btn" onclick="saveTodo()">
<i class="fas fa-save"></i> 保存
</button>
</div>
</div>
</div>

<!-- 引入Markdown解析器和编辑器 -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>

<script>
let simpleMDE;
let currentEditorMode = 'edit';

// 打开编辑模态框
function openEditModal(id) {
// 从PHP数组中获取任务数据
const todos = <?= json_encode($todos) ?>;
const todo = todos.find(t => t.id === id);
if (!todo) return;

// 填充模态框
document.getElementById('editId').value = todo.id;
document.getElementById('editText').value = todo.text;
document.getElementById('editProgress').value = todo.progress || 0;
updateProgressValue(todo.progress || 0);

// 初始化Markdown编辑器
if (simpleMDE) {
simpleMDE.toTextArea();
simpleMDE = null;
}

const textarea = document.getElementById('editDetails');
textarea.value = todo.details || '';

simpleMDE = new SimpleMDE({
element: textarea,
spellChecker: false,
toolbar: ["bold", "italic", "heading", "|", "quote", "unordered-list", "ordered-list", "|", "link", "image", "|", "preview", "guide"],
autoDownloadFontAwesome: false
});

// 显示模态框
document.getElementById('editModal').style.display = 'flex';
document.getElementById('previewContainer').innerHTML = marked.parse(todo.details || '');
}

// 设置编辑器模式
function setEditorMode(mode) {
currentEditorMode = mode;

if (mode === 'edit') {
document.getElementById('editModeBtn').classList.add('active');
document.getElementById('previewModeBtn').classList.remove('active');
document.querySelector('.editor-container').style.display = 'block';
document.getElementById('previewContainer').style.display = 'none';
} else {
document.getElementById('editModeBtn').classList.remove('active');
document.getElementById('previewModeBtn').classList.add('active');
document.querySelector('.editor-container').style.display = 'none';
document.getElementById('previewContainer').style.display = 'block';

// 更新预览内容
const markdown = simpleMDE.value();
document.getElementById('previewContainer').innerHTML = marked.parse(markdown);
}
}

// 关闭模态框
function closeModal() {
document.getElementById('editModal').style.display = 'none';
if (simpleMDE) {
simpleMDE.toTextArea();
simpleMDE = null;
}
}

// 更新进度显示
function updateProgressValue(value) {
document.getElementById('progressValue').textContent = `${value}%`;
document.getElementById('progressFill').style.width = `${value}%`;

// 更新进度条颜色
let color;
if (value < 30) {
color = 'var(--progress-low)';
} else if (value < 70) {
color = 'var(--progress-medium)';
} else {
color = 'var(--progress-high)';
}
document.getElementById('progressFill').style.background = color;
}

// 保存待办事项
function saveTodo() {
const form = document.getElementById('editForm');
const formData = new FormData(form);

// 获取Markdown内容
if (simpleMDE) {
formData.set('details', simpleMDE.value());
}

fetch(window.location.href, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
closeModal();
window.location.reload();
}
});
}

// 点击模态框外部关闭
window.addEventListener('click', function(event) {
const modal = document.getElementById('editModal');
if (event.target === modal) {
closeModal();
}
});

// 按ESC键关闭模态框
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
</script>
</body>
</html>

主要功能说明

  1. Markdown编辑器集成

    • 使用SimpleMDE提供所见即所得的Markdown编辑体验
    • 支持实时预览切换
    • 包含常用Markdown格式工具栏
  2. 改进的任务详情

    • 每个任务可以包含Markdown格式的详细描述
    • 详情支持图片、链接、列表等丰富格式
  3. 增强的UI功能

    • 现代化的模态框设计
    • 进度条可视化
    • 响应式布局适配各种设备
    • 动画效果提升用户体验
  4. 数据持久化

    • 使用JSON文件存储数据
    • 支持任务增删改查所有操作
    • 自动保存所有更改

使用说明

  1. 将代码保存为index.php文件
  2. 确保PHP环境已配置好
  3. 访问该文件即可使用
  4. 首次使用会自动创建todos.json数据文件

这个增强版待办列表应用现在具备了完整的Markdown支持,界面更加现代化,功能更加完善,同时保持了代码的简洁和高效。

时间: 2025-03-25T18:50:13


👤 用户

大哥,你是理解错了我的意思,我的意思是让你研究下我的代码的分割展示,在你之前的代码基础上帮我使用我给你的代码的展示风格,并且在任务中可以编辑详细进度,要求支持所见即所得和markdown。不是让你重新换种实现方式,还是使用原来的数据库实现。

时间: 2025-03-25T19:02:18


🤖 DeepSeek (deepseek-chat)

基于数据库的增强版Todo List(保留原有数据库实现)

我理解你的需求了 - 你想保留原有的MySQL数据库实现,但采用你提供的代码中的UI风格和功能(特别是Markdown编辑和进度管理)。以下是整合后的代码:

更新后的 index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
<?php
// 数据库配置
$host = 'localhost';
$dbname = 'todo_app';
$username = 'root';
$password = '';

try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("数据库连接失败: " . $e->getMessage());
}

// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_task'])) {
$title = trim($_POST['title']);
$description = isset($_POST['description']) ? trim($_POST['description']) : '';
$progress = isset($_POST['progress']) ? intval($_POST['progress']) : 0;

if (!empty($title)) {
$stmt = $pdo->prepare("INSERT INTO tasks (title, description, progress) VALUES (?, ?, ?)");
$stmt->execute([$title, $description, $progress]);
}
} elseif (isset($_POST['delete_task'])) {
$id = $_POST['task_id'];
$stmt = $pdo->prepare("DELETE FROM tasks WHERE id = ?");
$stmt->execute([$id]);
} elseif (isset($_POST['toggle_complete'])) {
$id = $_POST['task_id'];
$stmt = $pdo->prepare("UPDATE tasks SET is_completed = NOT is_completed, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->execute([$id]);
} elseif (isset($_POST['update_task'])) {
$id = $_POST['task_id'];
$title = trim($_POST['title']);
$description = trim($_POST['description']);
$progress = intval($_POST['progress']);

$stmt = $pdo->prepare("UPDATE tasks SET title = ?, description = ?, progress = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->execute([$title, $description, $progress, $id]);

if (isset($_POST['ajax'])) {
header('Content-Type: application/json');
echo json_encode(['status' => 'success']);
exit;
}
}
}

// 获取所有任务并按状态分类
$stmt = $pdo->query("SELECT * FROM tasks ORDER BY is_completed, created_at DESC");
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);

$activeTasks = array_filter($tasks, function($task) {
return !$task['is_completed'];
});

$completedTasks = array_filter($tasks, function($task) {
return $task['is_completed'];
});

// 当前显示标签
$currentTab = isset($_GET['tab']) ? $_GET['tab'] : 'active';

// 根据进度获取颜色
function getProgressColor($progress) {
if ($progress < 30) {
return '#ff6b6b';
} elseif ($progress < 70) {
return '#ffd166';
} else {
return '#06d6a0';
}
}
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高级待办列表</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<style>
/* 保留你提供的CSS样式,这里省略以节省空间 */
/* 请使用你提供的完整CSS样式部分 */
</style>
</head>
<body>
<div class="container">
<h1>高级待办列表</h1>

<form class="todo-form" method="post">
<input type="text" class="todo-input" name="title" placeholder="添加新任务..." required>
<button type="submit" name="add_task" class="todo-submit">添加</button>
</form>

<div class="tabs">
<div class="tab <?= $currentTab === 'active' ? 'active' : '' ?>" onclick="window.location.href='?tab=active'">
<i class="fas fa-tasks"></i> 待办事项
</div>
<div class="tab <?= $currentTab === 'completed' ? 'active' : '' ?>" onclick="window.location.href='?tab=completed'">
<i class="fas fa-check-circle"></i> 已完成
</div>
</div>

<ul class="todo-list">
<?php if ($currentTab === 'active'): ?>
<?php if (empty($activeTasks)): ?>
<div class="no-tasks">
<i class="fas fa-check-circle" style="font-size: 48px; color: #ddd; margin-bottom: 10px;"></i>
<p>没有待办事项</p>
</div>
<?php else: ?>
<?php foreach ($activeTasks as $task): ?>
<li class="todo-item" data-id="<?= $task['id'] ?>">
<div class="todo-header" onclick="openEditModal('<?= $task['id'] ?>')">
<div class="progress-indicator" style="width: <?= $task['progress'] ?>%"></div>
<input
type="checkbox"
class="todo-checkbox"
onclick="event.stopPropagation(); window.location.href='?toggle=<?= $task['id'] ?>&tab=active'"
>
<div class="todo-title"><?= htmlspecialchars($task['title']) ?></div>
<span class="progress-badge" style="color: <?= getProgressColor($task['progress']) ?>">
<?= $task['progress'] ?>%
</span>
<div class="todo-actions">
<button class="todo-btn delete" onclick="event.stopPropagation(); if(confirm('确定删除吗?')) window.location.href='?delete=<?= $task['id'] ?>&tab=active'">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</li>
<?php endforeach; ?>
<?php endif; ?>
<?php else: ?>
<?php if (empty($completedTasks)): ?>
<div class="no-tasks">
<i class="fas fa-clipboard-list" style="font-size: 48px; color: #ddd; margin-bottom: 10px;"></i>
<p>没有已完成事项</p>
</div>
<?php else: ?>
<?php foreach ($completedTasks as $task): ?>
<li class="todo-item" data-id="<?= $task['id'] ?>">
<div class="todo-header" onclick="openEditModal('<?= $task['id'] ?>')">
<div class="progress-indicator" style="width: 100%; background: var(--success-color)"></div>
<input
type="checkbox"
class="todo-checkbox"
checked
onclick="event.stopPropagation(); window.location.href='?toggle=<?= $task['id'] ?>&tab=completed'"
>
<div class="todo-title completed"><?= htmlspecialchars($task['title']) ?></div>
<span class="progress-badge" style="color: var(--success-color)">
100%
</span>
<div class="todo-actions">
<button class="todo-btn delete" onclick="event.stopPropagation(); if(confirm('确定删除吗?')) window.location.href='?delete=<?= $task['id'] ?>&tab=completed'">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</li>
<?php endforeach; ?>
<?php endif; ?>
<?php endif; ?>
</ul>
</div>

<!-- 编辑模态框 -->
<div class="modal" id="editModal">
<div class="modal-content">
<div class="modal-header">
<h3>编辑任务</h3>
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<form id="editForm">
<input type="hidden" name="task_id" id="editId">
<input type="hidden" name="update_task" value="1">
<div class="form-group">
<label for="editText" class="form-label">任务标题</label>
<input type="text" id="editText" name="title" class="form-input" required>
</div>
<div class="form-group">
<label class="form-label">任务详情</label>
<div class="editor-toolbar">
<button type="button" class="editor-mode-btn active" id="editModeBtn" onclick="setEditorMode('edit')">
<i class="fas fa-edit"></i> 编辑
</button>
<button type="button" class="editor-mode-btn" id="previewModeBtn" onclick="setEditorMode('preview')">
<i class="fas fa-eye"></i> 预览
</button>
</div>
<div class="editor-container">
<textarea id="editDetails" name="description"></textarea>
</div>
<div class="preview-container" id="previewContainer"></div>
</div>
<div class="form-group progress-container">
<label for="editProgress" class="form-label">进度</label>
<div class="progress-label">
<span id="progressValue">0%</span>
</div>
<input type="range" id="editProgress" name="progress" min="0" max="100" value="0" class="form-input" oninput="updateProgressValue(this.value)">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="save-btn" onclick="saveTodo()">
<i class="fas fa-save"></i> 保存
</button>
</div>
</div>
</div>

<!-- 引入Markdown解析器和编辑器 -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>

<script>
let simpleMDE;
let currentEditorMode = 'edit';

// 打开编辑模态框
function openEditModal(id) {
// 使用AJAX获取任务详情
fetch(`get_task.php?id=${id}`)
.then(response => response.json())
.then(task => {
// 填充模态框
document.getElementById('editId').value = task.id;
document.getElementById('editText').value = task.title;
document.getElementById('editProgress').value = task.progress || 0;
updateProgressValue(task.progress || 0);

// 初始化Markdown编辑器
if (simpleMDE) {
simpleMDE.toTextArea();
simpleMDE = null;
}

const textarea = document.getElementById('editDetails');
textarea.value = task.description || '';

simpleMDE = new SimpleMDE({
element: textarea,
spellChecker: false,
toolbar: ["bold", "italic", "heading", "|", "quote", "unordered-list", "ordered-list", "|", "link", "image", "|", "preview", "guide"],
autoDownloadFontAwesome: false
});

// 显示模态框
document.getElementById('editModal').style.display = 'flex';
document.getElementById('previewContainer').innerHTML = marked.parse(task.description || '');
});
}

// 设置编辑器模式
function setEditorMode(mode) {
currentEditorMode = mode;

if (mode === 'edit') {
document.getElementById('editModeBtn').classList.add('active');
document.getElementById('previewModeBtn').classList.remove('active');
document.querySelector('.editor-container').style.display = 'block';
document.getElementById('previewContainer').style.display = 'none';
} else {
document.getElementById('editModeBtn').classList.remove('active');
document.getElementById('previewModeBtn').classList.add('active');
document.querySelector('.editor-container').style.display = 'none';
document.getElementById('previewContainer').style.display = 'block';

// 更新预览内容
const markdown = simpleMDE.value();
document.getElementById('previewContainer').innerHTML = marked.parse(markdown);
}
}

// 关闭模态框
function closeModal() {
document.getElementById('editModal').style.display = 'none';
if (simpleMDE) {
simpleMDE.toTextArea();
simpleMDE = null;
}
}

// 更新进度显示
function updateProgressValue(value) {
document.getElementById('progressValue').textContent = `${value}%`;
document.getElementById('progressFill').style.width = `${value}%`;

// 更新进度条颜色
let color;
if (value < 30) {
color = 'var(--progress-low)';
} else if (value < 70) {
color = 'var(--progress-medium)';
} else {
color = 'var(--progress-high)';
}
document.getElementById('progressFill').style.background = color;
}

// 保存待办事项
function saveTodo() {
const form = document.getElementById('editForm');
const formData = new FormData(form);

// 获取Markdown内容
if (simpleMDE) {
formData.set('description', simpleMDE.value());
}

fetch(window.location.href, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
closeModal();
window.location.reload();
}
});
}

// 点击模态框外部关闭
window.addEventListener('click', function(event) {
const modal = document.getElementById('editModal');
if (event.target === modal) {
closeModal();
}
});

// 按ESC键关闭模态框
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
</script>
</body>
</html>

新增的 get_task.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
// 数据库配置
$host = 'localhost';
$dbname = 'todo_app';
$username = 'root';
$password = '';

try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("数据库连接失败: " . $e->getMessage());
}

if (isset($_GET['id'])) {
$stmt = $pdo->prepare("SELECT * FROM tasks WHERE id = ?");
$stmt->execute([$_GET['id']]);
$task = $stmt->fetch(PDO::FETCH_ASSOC);

header('Content-Type: application/json');
echo json_encode($task);
exit;
}

header('HTTP/1.1 400 Bad Request');
echo json_encode(['error' => '缺少任务ID']);
?>

数据库结构调整

你需要确保你的tasks表有以下字段:

1
2
3
4
5
6
7
8
9
CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
progress INT DEFAULT 0,
is_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

主要改进点

  1. 保留原有MySQL数据库实现 - 继续使用PDO和MySQL存储数据
  2. 采用新的UI风格 - 使用你提供的CSS样式和布局
  3. 增强任务编辑功能
    • 集成SimpleMDE Markdown编辑器
    • 支持所见即所得的编辑体验
    • 添加进度条管理功能
  4. 新增AJAX支持 - 通过get_task.php单独获取任务详情
  5. 响应式设计 - 保留你提供的完整响应式布局

这个版本完美结合了你提供的UI设计和原有的数据库实现,同时增加了Markdown编辑和进度管理功能。

时间: 2025-03-25T19:02:18


👤 用户

SQL 查询: 复制

CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
progress INT DEFAULT 0,
is_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
MySQL 返回: 文档

#1293 - Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause

时间: 2025-03-25T19:10:51


🤖 DeepSeek (deepseek-chat)

解决 MySQL 时间戳列定义错误

这个错误是因为在 MySQL 5.6.5 之前的版本中,每个表只能有一个 TIMESTAMP 列使用 CURRENT_TIMESTAMP 作为默认值或自动更新值。

解决方案

以下是几种解决方法,选择其中一种即可:

方案1: 升级 MySQL (推荐)

升级到 MySQL 5.6.5 或更高版本,这些版本支持多个 TIMESTAMP 列使用 CURRENT_TIMESTAMP

方案2: 修改表结构 (兼容旧版本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
progress INT DEFAULT 0,
is_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP
);

-- 添加触发器来自动更新 updated_at
DELIMITER //
CREATE TRIGGER update_tasks_timestamp
BEFORE UPDATE ON tasks
FOR EACH ROW
BEGIN
SET NEW.updated_at = CURRENT_TIMESTAMP;
END//
DELIMITER ;

方案3: 使用 DATETIME 替代 TIMESTAMP

1
2
3
4
5
6
7
8
9
CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
progress INT DEFAULT 0,
is_completed BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

方案4: 只保留一个自动更新的列

1
2
3
4
5
6
7
8
9
CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
progress INT DEFAULT 0,
is_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

最佳实践建议

  1. 升级 MySQL:如果可能,升级到 5.6.5 或更高版本,这是最简单的解决方案。

  2. 使用 DATETIME:如果你需要存储超过 2038 年的日期,或者需要更大的日期范围,使用 DATETIME 类型更好。

  3. 添加触发器:如果你必须使用旧版 MySQL 并且需要两个时间戳列都自动更新,触发器是一个可靠的解决方案。

  4. 测试环境一致性:确保开发、测试和生产环境的 MySQL 版本一致,避免这类兼容性问题。

选择哪种方案取决于你的具体环境要求和对未来兼容性的考虑。

时间: 2025-03-25T19:10:51


👤 用户

怎么把style保存为css供页面加载?

时间: 2025-03-25T19:21:34


🤖 DeepSeek (deepseek-chat)

将样式保存为独立CSS文件的方法

要将样式保存为独立的CSS文件供页面加载,你可以按照以下步骤操作:

1. 创建CSS文件

在你的项目目录中创建一个新的CSS文件,例如style.css,然后将所有<style>标签内的CSS代码复制到这个文件中。

2. 修改HTML文件

在你的PHP/HTML文件中,删除原有的<style>标签,改为使用<link>标签引入外部CSS文件:

1
<link rel="stylesheet" href="style.css">

3. 完整的CSS文件示例

将你提供的样式代码保存为style.css文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/* style.css */
:root {
--primary-color: #4a6fa5;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
--light-color: #f8f9fa;
--dark-color: #343a40;
--progress-low: #ff6b6b;
--progress-medium: #ffd166;
--progress-high: #06d6a0;
}

* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
padding: 0;
margin: 0;
}

/* 其余所有CSS样式... */

@media (max-width: 600px) {
.container {
padding: 10px;
}

h1 {
font-size: 24px;
}

.todo-form {
flex-direction: column;
}

.todo-input {
width: 100%;
margin-bottom: 0;
}

.todo-submit {
width: 100%;
padding: 12px;
}

.tabs {
justify-content: space-around;
}

.tab {
padding: 10px;
flex: 1;
text-align: center;
}

.todo-header {
padding: 10px;
}

.modal-content {
width: 95%;
max-height: 85vh;
}

.progress-badge {
display: none;
}
}

4. 在PHP文件中引入CSS

确保你的PHP文件头部有以下链接:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高级待办列表</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<link rel="stylesheet" href="style.css"> <!-- 引入本地CSS文件 -->
</head>

5. 额外建议

  1. CSS文件组织

    • 对于大型项目,可以按功能模块拆分CSS文件
    • 例如:main.cssmodal.cssforms.css
  2. 缓存控制

    • 在生产环境中,考虑添加版本号防止缓存问题:
    1
    <link rel="stylesheet" href="style.css?v=1.0.1">
  3. 性能优化

    • 压缩CSS文件以减小体积
    • 使用CDN加速静态资源加载
  4. 路径问题

    • 如果CSS文件在不同目录,确保路径正确
    • 例如:<link rel="stylesheet" href="css/style.css">

通过这种方式,你的样式将与HTML内容分离,使代码更加模块化、易于维护,并且可以被浏览器缓存以提高性能。

时间: 2025-03-25T19:21:34



对话统计

  • 消息总数: 16 条
  • 处理状态: 成功转换