如何在PyCharm中使用Flask、SQL、CSS、JavaScript、HTML开发带Google Keep风格复选框的动态待办清单应用(支持数据库存储清单对应实时价格)
Hey there! Let's build that dynamic to-do list app with Google Keep-style checkboxes, plus database storage for tasks and their real-time price data. I'll walk you through each part using Flask, SQLAlchemy, HTML, CSS, and JavaScript right in PyCharm.
1. Project Setup in PyCharm
First, create a new Flask project in PyCharm (File > New Project > Flask). Then install the required packages via PyCharm's terminal:
pip install flask flask-sqlalchemy requests
(We'll use requests later if you need to fetch real-time prices from an API, but we'll cover that option too.)
2. Database Model Setup
Create a models.py file to define your database schema. We'll use SQLAlchemy for ORM, which makes working with SQL super straightforward:
from flask_sqlalchemy import SQLAlchemy from datetime import datetime db = SQLAlchemy() class Task(db.Model): id = db.Column(db.Integer, primary_key=True) content = db.Column(db.String(200), nullable=False) is_completed = db.Column(db.Boolean, default=False) price = db.Column(db.Float, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) def __repr__(self): return f'<Task {self.id}>'
Then update your app.py to initialize the database:
from flask import Flask, render_template, request, jsonify from models import db, Task app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db' # Using SQLite for simplicity app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db.init_app(app) # Create database tables (run once) with app.app_context(): db.create_all() # Routes will go here
3. Flask Backend Routes
Add these routes to app.py to handle CRUD operations and price handling:
Home Route (Render the App)
@app.route('/') def index(): tasks = Task.query.order_by(Task.created_at).all() return render_template('index.html', tasks=tasks)
Add a New Task
@app.route('/add-task', methods=['POST']) def add_task(): content = request.json.get('content') price = request.json.get('price') # Or fetch real-time price here # Optional: Fetch real-time price from an API (example) # import requests # response = requests.get('https://api.example.com/price') # price = response.json().get('price') if content and price: new_task = Task(content=content, price=price) db.session.add(new_task) db.session.commit() return jsonify({'id': new_task.id, 'content': new_task.content, 'price': new_task.price, 'is_completed': new_task.is_completed}) return jsonify({'error': 'Missing content or price'}), 400
Toggle Task Completion
@app.route('/toggle-task/<int:task_id>', methods=['POST']) def toggle_task(task_id): task = Task.query.get_or_404(task_id) task.is_completed = not task.is_completed db.session.commit() return jsonify({'is_completed': task.is_completed})
Delete a Task
@app.route('/delete-task/<int:task_id>', methods=['DELETE']) def delete_task(task_id): task = Task.query.get_or_404(task_id) db.session.delete(task) db.session.commit() return jsonify({'success': True})
4. Frontend: Google Keep-Style UI
Create a templates folder in your project, then add index.html with the following code:
HTML Structure
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Keep-Style To-Do List</title> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> </head> <body> <div class="container"> <h1>My To-Do List</h1> <div class="task-input"> <input type="text" id="taskContent" placeholder="Add a new task..."> <input type="number" id="taskPrice" placeholder="Enter price..."> <button id="addTaskBtn">Add Task</button> </div> <div id="tasksContainer"> {% for task in tasks %} <div class="task-item {% if task.is_completed %}completed{% endif %}" data-id="{{ task.id }}"> <label class="checkbox-container"> <input type="checkbox" {% if task.is_completed %}checked{% endif %}> <span class="checkmark"></span> </label> <div class="task-content"> <p>{{ task.content }}</p> <span class="task-price">$ {{ "%.2f"|format(task.price) }}</span> </div> <button class="delete-btn">×</button> </div> {% endfor %} </div> </div> <script src="{{ url_for('static', filename='app.js') }}"></script> </body> </html>
CSS (Create static/style.css)
This replicates Google Keep's checkbox style and clean aesthetic:
body { font-family: Arial, sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px; } .container { max-width: 600px; margin: 0 auto; } h1 { color: #333; text-align: center; } .task-input { display: flex; gap: 10px; margin-bottom: 20px; } .task-input input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } .task-input button { padding: 10px 20px; background-color: #4285f4; color: white; border: none; border-radius: 4px; cursor: pointer; } .task-item { background-color: white; padding: 15px; margin-bottom: 10px; border-radius: 8px; display: flex; align-items: center; gap: 15px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .task-item.completed .task-content p { text-decoration: line-through; color: #888; } /* Google Keep-style checkbox */ .checkbox-container { display: block; position: relative; padding-left: 30px; cursor: pointer; user-select: none; } .checkbox-container input { position: absolute; opacity: 0; cursor: pointer; } .checkmark { position: absolute; top: 0; left: 0; height: 20px; width: 20px; border: 2px solid #ddd; border-radius: 50%; } .checkbox-container:hover input ~ .checkmark { border-color: #4285f4; } .checkbox-container input:checked ~ .checkmark { background-color: #4285f4; border-color: #4285f4; } .checkmark:after { content: ""; position: absolute; display: none; } .checkbox-container input:checked ~ .checkmark:after { display: block; } .checkbox-container .checkmark:after { left: 6px; top: 2px; width: 5px; height: 10px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); } .task-content { flex: 1; } .task-content p { margin: 0 0 5px 0; font-size: 16px; color: #333; } .task-price { font-size: 14px; color: #666; } .delete-btn { background-color: transparent; border: none; font-size: 24px; color: #888; cursor: pointer; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; } .delete-btn:hover { color: #d93025; }
JavaScript (Create static/app.js)
Handle frontend interactions and AJAX calls to the backend:
document.addEventListener('DOMContentLoaded', () => { const addTaskBtn = document.getElementById('addTaskBtn'); const tasksContainer = document.getElementById('tasksContainer'); // Add new task addTaskBtn.addEventListener('click', async () => { const content = document.getElementById('taskContent').value.trim(); const price = parseFloat(document.getElementById('taskPrice').value); if (!content || isNaN(price)) { alert('Please enter valid task content and price'); return; } const response = await fetch('/add-task', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ content, price }), }); if (response.ok) { const task = await response.json(); addTaskToDOM(task); // Clear inputs document.getElementById('taskContent').value = ''; document.getElementById('taskPrice').value = ''; } else { const error = await response.json(); alert(error.error); } }); // Toggle task completion tasksContainer.addEventListener('change', async (e) => { if (e.target.type === 'checkbox') { const taskItem = e.target.closest('.task-item'); const taskId = parseInt(taskItem.dataset.id); const response = await fetch(`/toggle-task/${taskId}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, }); if (response.ok) { const data = await response.json(); taskItem.classList.toggle('completed', data.is_completed); } } }); // Delete task tasksContainer.addEventListener('click', async (e) => { if (e.target.classList.contains('delete-btn')) { const taskItem = e.target.closest('.task-item'); const taskId = parseInt(taskItem.dataset.id); const response = await fetch(`/delete-task/${taskId}`, { method: 'DELETE', }); if (response.ok) { taskItem.remove(); } } }); // Helper function to add task to DOM function addTaskToDOM(task) { const taskItem = document.createElement('div'); taskItem.className = `task-item ${task.is_completed ? 'completed' : ''}`; taskItem.dataset.id = task.id; taskItem.innerHTML = ` <label class="checkbox-container"> <input type="checkbox" ${task.is_completed ? 'checked' : ''}> <span class="checkmark"></span> </label> <div class="task-content"> <p>${task.content}</p> <span class="task-price">$ ${task.price.toFixed(2)}</span> </div> <button class="delete-btn">×</button> `; tasksContainer.appendChild(taskItem); } });
5. Run the App
In PyCharm, run your app.py file. Open your browser and go to http://localhost:5000 – you'll see your Google Keep-style to-do list! You can add tasks with prices, toggle completion, delete tasks, and all data is stored in the tasks.db SQLite database.
Notes on Real-Time Prices
If you need to fetch real-time prices automatically (instead of manual input), replace the price input in the frontend with a way to select a product, then in the /add-task route, use an API like CoinGecko (for crypto), Alpha Vantage (for stocks), or any relevant price API to fetch the current price. For example:
import requests @app.route('/add-task', methods=['POST']) def add_task(): content = request.json.get('content') product = request.json.get('product') # e.g., 'bitcoin' # Fetch real-time price from CoinGecko response = requests.get(f'https://api.coingecko.com/api/v3/simple/price?ids={product}&vs_currencies=usd') price = response.json()[product]['usd'] if content and price: new_task = Task(content=content, price=price) db.session.add(new_task) db.session.commit() return jsonify({'id': new_task.id, 'content': new_task.content, 'price': new_task.price, 'is_completed': new_task.is_completed}) return jsonify({'error': 'Missing content or product'}), 400
Just update the frontend to accept a product name instead of a manual price input.
内容的提问来源于stack exchange,提问作者Lucy Sunday

