技术求助:如何在PyCharm中使用Flask、SQL、CSS、JavaScript、HTML创建带Google Keep式复选框的动态待办事项应用并存储清单当前价格至数据库
Hey Lucy, great choice going with Flask for your to-do app—super flexible and perfect for this kind of project. Let’s walk through building your Google Keep-style checklist with price tracking, step by step right in PyCharm.
First, let’s get your environment sorted in PyCharm:
- Open PyCharm, create a new Pure Python project.
- Set up a virtual environment (PyCharm will prompt you to do this automatically—just pick the Python version you prefer).
- Install required packages via the terminal (or PyCharm’s package manager):
We’ll usepip install flask flask-sqlalchemyflask-sqlalchemyfor easy database integration without writing raw SQL—way faster for development.
Create a new file app.py in your project root—this will be your main Flask app file. Let’s set up the database and define a Todo model that includes price tracking:
from flask import Flask, render_template, request, jsonify from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # Configure SQLite database (easy for development; switch to PostgreSQL/MySQL later if needed) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todos.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) # Todo Model class Todo(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) completed = db.Column(db.Boolean, default=False) price = db.Column(db.Float, nullable=False) # Store price for each todo item # Create database tables (run this once) with app.app_context(): db.create_all()
- The
completedfield handles the checkbox state, andpricestores your item’s price value. - Run this script once to generate the
todos.dbfile in your project folder.
Add these routes to app.py to handle displaying todos, adding new ones, toggling completion, and deleting items:
# Home Route: Display all todos @app.route('/') def index(): todos = Todo.query.all() return render_template('index.html', todos=todos) # Add New Todo (AJAX-friendly for seamless updates) @app.route('/add', methods=['POST']) def add_todo(): title = request.json.get('title') price = request.json.get('price') if title and price: new_todo = Todo(title=title, price=price) db.session.add(new_todo) db.session.commit() return jsonify({'id': new_todo.id, 'title': new_todo.title, 'price': new_todo.price, 'completed': False}) return jsonify({'error': 'Missing title or price'}), 400 # Toggle Todo Completion @app.route('/toggle/<int:todo_id>', methods=['POST']) def toggle_todo(todo_id): todo = Todo.query.get_or_404(todo_id) todo.completed = not todo.completed db.session.commit() return jsonify({'completed': todo.completed}) # Delete Todo @app.route('/delete/<int:todo_id>', methods=['POST']) def delete_todo(todo_id): todo = Todo.query.get_or_404(todo_id) db.session.delete(todo) db.session.commit() return jsonify({'success': True})
- We’re using JSON responses for AJAX calls so the UI updates without reloading the page—just like Google Keep’s smooth experience.
Create a templates folder in your project root, then make index.html inside it. This will handle the frontend with that familiar Keep checkbox style:
<!DOCTYPE html> <html> <head> <title>Google Keep-Style Todo List</title> <style> /* Google Keep-inspired checkbox */ .todo-checkbox { appearance: none; width: 20px; height: 20px; border: 2px solid #ccc; border-radius: 50%; cursor: pointer; position: relative; margin-right: 10px; } .todo-checkbox:checked { border-color: #2196F3; background-color: #2196F3; } .todo-checkbox:checked::after { content: '✓'; color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 14px; font-weight: bold; } /* Todo item styling */ .todo-item { display: flex; align-items: center; padding: 12px; margin: 8px 0; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); background: white; } .todo-completed { text-decoration: line-through; color: #666; } .todo-price { margin-left: auto; font-weight: bold; color: #333; } .delete-btn { margin-left: 10px; background: #ff4444; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; } #add-todo-form { margin-bottom: 20px; display: flex; gap: 8px; } #add-todo-form input { padding: 8px; border: 1px solid #ccc; border-radius: 4px; flex: 1; } #add-todo-form button { background: #2196F3; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; } </style> </head> <body> <div style="max-width: 600px; margin: 20px auto;"> <h1>My Todo List</h1> <form id="add-todo-form"> <input type="text" id="todo-title" placeholder="Enter todo title" required> <input type="number" id="todo-price" placeholder="Enter price" step="0.01" required> <button type="submit">Add Todo</button> </form> <div id="todo-list"> {% for todo in todos %} <div class="todo-item" data-id="{{ todo.id }}"> <input type="checkbox" class="todo-checkbox" {% if todo.completed %}checked{% endif %}> <span class="todo-title {% if todo.completed %}todo-completed{% endif %}">{{ todo.title }}</span> <span class="todo-price">${{ "%.2f"|format(todo.price) }}</span> <button class="delete-btn">Delete</button> </div> {% endfor %} </div> </div> <script> // Add new todo document.getElementById('add-todo-form').addEventListener('submit', async (e) => { e.preventDefault(); const title = document.getElementById('todo-title').value; const price = document.getElementById('todo-price').value; const response = await fetch('/add', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({title, price}) }); if (response.ok) { const todo = await response.json(); const todoList = document.getElementById('todo-list'); const todoItem = document.createElement('div'); todoItem.className = 'todo-item'; todoItem.dataset.id = todo.id; todoItem.innerHTML = ` <input type="checkbox" class="todo-checkbox"> <span class="todo-title">${todo.title}</span> <span class="todo-price">$${todo.price.toFixed(2)}</span> <button class="delete-btn">Delete</button> `; todoList.appendChild(todoItem); // Reset form document.getElementById('add-todo-form').reset(); } }); // Toggle todo completion document.getElementById('todo-list').addEventListener('change', async (e) => { if (e.target.classList.contains('todo-checkbox')) { const todoItem = e.target.closest('.todo-item'); const todoId = todoItem.dataset.id; const response = await fetch(`/toggle/${todoId}`, {method: 'POST'}); if (response.ok) { const data = await response.json(); const titleSpan = todoItem.querySelector('.todo-title'); titleSpan.classList.toggle('todo-completed', data.completed); } } }); // Delete todo document.getElementById('todo-list').addEventListener('click', async (e) => { if (e.target.classList.contains('delete-btn')) { const todoItem = e.target.closest('.todo-item'); const todoId = todoItem.dataset.id; const response = await fetch(`/delete/${todoId}`, {method: 'POST'}); if (response.ok) { todoItem.remove(); } } }); </script> </body> </html>
- The CSS mimics Google Keep’s rounded checkboxes with a blue fill when checked, plus soft shadows for that card-like feel.
- JavaScript uses async/await to make AJAX calls, so the app feels responsive without page reloads.
- In PyCharm, go to Run > Edit Configurations.
- Click the + icon, select Flask Server.
- Set the Target to your
app.pyfile, and the Flask app toapp:app. - Click Apply then Run.
- Open your browser and go to
http://localhost:5000—you’ll see your todo list in action!
- For production, switch from SQLite to a more robust database like PostgreSQL or MySQL.
- Add validation for price inputs (e.g., ensure they’re positive numbers).
- Style the app further to match Google Keep’s exact look (warm accent colors, hover effects, etc.).
内容的提问来源于stack exchange,提问作者Lucy Sunday

