You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 09:12:33