《AI大模型趣味实战》No2 : 快速搭建一个漂亮的AI家庭网站-相册/时间线/日历/多用户/个性化配色(中)

快速搭建一个漂亮的AI家庭网站-相册/时间线/日历/多用户/个性化配色(中)

摘要

在上一篇文章中,我们介绍了如何搭建一个基础的家庭网站(V1.0版本),包含了用户管理、相册管理、时间线和日历等功能。本文将继续深入,详细介绍V1.1和V1.2版本中新增的功能,主要包括博客系统的实现、富文本编辑器的集成、评论和点赞功能,以及日程管理系统的优化。通过这些功能的添加,我们的家庭网站将更加完善,为家庭成员提供更丰富的交流和记录方式。

代码仓 https://github.com/wyg5208/family_website_V1_2
在这里插入图片描述

一、V1.1版本新增功能

1. 博客系统

V1.1版本最重要的更新是添加了完整的博客系统,让家庭成员可以发表文章,分享生活点滴和心得体会。

1.1 数据模型设计

首先,我们需要设计博客相关的数据模型,包括文章、评论和点赞:

# app/models.pyclass Post(db.Model):id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(100), nullable=False)content = db.Column(db.Text, nullable=False)summary = db.Column(db.String(200))cover_image = db.Column(db.String(120))created_at = db.Column(db.DateTime, default=datetime.utcnow)updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)# 关系user = db.relationship('User', backref=db.backref('posts', lazy=True))comments = db.relationship('Comment', backref='post', lazy=True, cascade='all, delete-orphan')likes = db.relationship('Like', backref='post', lazy=True, cascade='all, delete-orphan')def __repr__(self):return f'<Post {self.title}>'@propertydef comment_count(self):return len(self.comments)@propertydef like_count(self):return len(self.likes)class Comment(db.Model):id = db.Column(db.Integer, primary_key=True)content = db.Column(db.Text, nullable=False)created_at = db.Column(db.DateTime, default=datetime.utcnow)user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)parent_id = db.Column(db.Integer, db.ForeignKey('comment.id'))# 关系user = db.relationship('User', backref=db.backref('comments', lazy=True))replies = db.relationship('Comment', backref=db.backref('parent', remote_side=[id]), lazy=True)def __repr__(self):return f'<Comment {self.id}>'class Like(db.Model):id = db.Column(db.Integer, primary_key=True)created_at = db.Column(db.DateTime, default=datetime.utcnow)user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)# 关系user = db.relationship('User', backref=db.backref('likes', lazy=True))__table_args__ = (db.UniqueConstraint('user_id', 'post_id', name='unique_user_post_like'),)def __repr__(self):return f'<Like {self.id}>'
1.2 博客首页实现

博客首页展示最新文章列表,包括文章标题、摘要、作者和发布时间等信息:

<!-- app/templates/blog/index.html -->
{% extends 'base.html' %}{% block title %}家庭博客 - 家庭网站{% endblock %}{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4"><h1><i class="fas fa-blog me-2"></i>家庭博客</h1>{% if current_user.is_authenticated %}<a href="{{ url_for('blog.create') }}" class="btn btn-primary"><i class="fas fa-pen-to-square me-1"></i>写文章</a>{% endif %}
</div>{% if posts %}
<div class="row row-cols-1 row-cols-md-2 g-4">{% for post in posts %}<div class="col"><div class="card h-100 blog-card">{% if post.cover_image %}<img src="{{ url_for('static', filename='uploads/blog/' + post.cover_image) }}" class="card-img-top" alt="{{ post.title }}">{% endif %}<div class="card-body"><h5 class="card-title">{{ post.title }}</h5><p class="card-text text-muted small"><i class="far fa-user me-1"></i>{{ post.user.username }} | <i class="far fa-calendar me-1"></i>{{ post.created_at.strftime('%Y-%m-%d') }}</p><p class="card-text">{{ post.summary or (post.content|striptags|truncate(150)) }}</p></div><div class="card-footer d-flex justify-content-between align-items-center"><div><span class="badge bg-primary me-1"><i class="far fa-comment me-1"></i>{{ post.comment_count }}</span><span class="badge bg-danger"><i class="far fa-heart me-1"></i>{{ post.like_count }}</span></div><a href="{{ url_for('blog.view', post_id=post.id) }}" class="btn btn-sm btn-outline-primary">阅读全文</a></div></div></div>{% endfor %}
</div><!-- 分页控件 -->
<nav aria-label="Page navigation" class="mt-4"><ul class="pagination justify-content-center">{% if pagination.has_prev %}<li class="page-item"><a class="page-link" href="{{ url_for('blog.index', page=pagination.prev_num) }}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>{% else %}<li class="page-item disabled"><a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>{% endif %}{% for page in pagination.iter_pages() %}{% if page %}{% if page != pagination.page %}<li class="page-item"><a class="page-link" href="{{ url_for('blog.index', page=page) }}">{{ page }}</a></li>{% else %}<li class="page-item active"><a class="page-link" href="#">{{ page }}</a></li>{% endif %}{% else %}<li class="page-item disabled"><a class="page-link" href="#">...</a></li>{% endif %}{% endfor %}{% if pagination.has_next %}<li class="page-item"><a class="page-link" href="{{ url_for('blog.index', page=pagination.next_num) }}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>{% else %}<li class="page-item disabled"><a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>{% endif %}</ul>
</nav>
{% else %}
<div class="alert alert-info"><i class="fas fa-info-circle me-2"></i>目前还没有博客文章。{% if current_user.is_authenticated %}<a href="{{ url_for('blog.create') }}" class="alert-link">点击这里</a>创建第一篇文章!{% endif %}
</div>
{% endif %}
{% endblock %}
1.3 集成CKEditor 5富文本编辑器

为了提供更好的写作体验,我们集成了CKEditor 5富文本编辑器:

<!-- app/templates/blog/edit.html -->
{% extends 'base.html' %}{% block title %}{{ '编辑文章' if post else '创建文章' }} - 家庭网站{% endblock %}{% block styles %}
<style>.ck-editor__editable {min-height: 300px;}
</style>
{% endblock %}{% block content %}
<nav aria-label="breadcrumb" class="mb-4"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{ url_for('main.index') }}">首页</a></li><li class="breadcrumb-item"><a href="{{ url_for('blog.index') }}">博客</a></li><li class="breadcrumb-item active" aria-current="page">{{ '编辑文章' if post else '创建文章' }}</li></ol>
</nav><div class="card"><div class="card-header"><h2>{{ '编辑文章' if post else '创建文章' }}</h2></div><div class="card-body"><form id="postForm" method="POST" enctype="multipart/form-data">{{ form.hidden_tag() }}<div class="mb-3">{{ form.title.label(class="form-label") }}{{ form.title(class="form-control" + (" is-invalid" if form.title.errors else "")) }}{% for error in form.title.errors %}<div class="invalid-feedback">{{ error }}</div>{% endfor %}</div><div class="mb-3">{{ form.summary.label(class="form-label") }}{{ form.summary(class="form-control" + (" is-invalid" if form.summary.errors else "")) }}{% for error in form.summary.errors %}<div class="invalid-feedback">{{ error }}</div>{% endfor %}<div class="form-text">简短摘要,不超过200字符。如不填写,将自动从内容中提取。</div></div><div class="mb-3">{{ form.cover_image.label(class="form-label") }}{{ form.cover_image(class="form-control" + (" is-invalid" if form.cover_image.errors else "")) }}{% for error in form.cover_image.errors %}<div class="invalid-feedback">{{ error }}</div>{% endfor %}{% if post and post.cover_image %}<div class="mt-2"><img src="{{ url_for('static', filename='uploads/blog/' + post.cover_image) }}" alt="当前封面图" class="img-thumbnail" style="max-height: 100px;"><div class="form-text">当前封面图。上传新图片将替换此图。</div></div>{% endif %}</div><div class="mb-3">{{ form.content.label(class="form-label") }}{{ form.content(class="form-control" + (" is-invalid" if form.content.errors else ""), id="editor") }}{% for error in form.content.errors %}<div class="invalid-feedback">{{ error }}</div>{% endfor %}</div><div class="d-flex justify-content-between"><a href="{{ url_for('blog.view', post_id=post.id) if post else url_for('blog.index') }}" class="btn btn-secondary">取消</a><button type="submit" class="btn btn-primary">保存文章</button></div></form></div>
</div>
{% endblock %}{% block scripts %}
<script src="https://cdn.ckeditor.com/ckeditor5/35.1.0/classic/ckeditor.js"></script>
<script>document.addEventListener('DOMContentLoaded', function() {let editor;ClassicEditor.create(document.querySelector('#editor')).then(newEditor => {editor = newEditor;}).catch(error => {console.error(error);});const form = document.getElementById('postForm');const contentTextarea = document.querySelector('#editor');form.addEventListener('submit', function(e) {e.preventDefault();// 获取CKEditor内容const editorData = editor.getData();// 检查内容是否为空if (!editorData.trim()) {alert('文章内容不能为空!');return;}// 将CKEditor内容设置到原始textareacontentTextarea.value = editorData;// 提交表单this.submit();});});
</script>
{% endblock %}
1.4 评论和回复功能

博客系统支持评论和嵌套回复功能,让家庭成员可以互动交流:

<!-- 评论部分 (app/templates/blog/view.html的一部分) -->
<div class="card mt-4"><div class="card-header"><h3><i class="far fa-comments me-2"></i>评论 ({{ post.comment_count }})</h3></div><div class="card-body">{% if current_user.is_authenticated %}<form method="POST" action="{{ url_for('blog.add_comment', post_id=post.id) }}">{{ comment_form.hidden_tag() }}<div class="mb-3">{{ comment_form.content.label(class="form-label") }}{{ comment_form.content(class="form-control" + (" is-invalid" if comment_form.content.errors else ""), rows=3) }}{% for error in comment_form.content.errors %}<div class="invalid-feedback">{{ error }}</div>{% endfor %}</div><button type="submit" class="btn btn-primary">发表评论</button></form>{% else %}<div class="alert alert-info"><a href="{{ url_for('auth.login', next=request.path) }}">登录</a>后发表评论。</div>{% endif %}<hr><!-- 评论列表 -->{% if post.comments %}<div class="comments-container">{% for comment in top_level_comments %}<div class="comment mb-3"><div class="d-flex"><div class="flex-shrink-0"><img src="{{ url_for('static', filename='img/default-avatar.png') }}" class="rounded-circle" width="50" height="50" alt="{{ comment.user.username }}"></div><div class="flex-grow-1 ms-3"><div class="d-flex justify-content-between"><h5 class="mt-0">{{ comment.user.username }}</h5><small class="text-muted">{{ comment.created_at.strftime('%Y-%m-%d %H:%M') }}</small></div><p>{{ comment.content }}</p>{% if current_user.is_authenticated %}<button class="btn btn-sm btn-outline-secondary reply-btn" data-comment-id="{{ comment.id }}"><i class="fas fa-reply me-1"></i>回复</button><div class="reply-form mt-2 d-none" id="reply-form-{{ comment.id }}"><form method="POST" action="{{ url_for('blog.add_reply', post_id=post.id, comment_id=comment.id) }}">{{ reply_form.hidden_tag() }}<div class="mb-2">{{ reply_form.content(class="form-control", rows=2, placeholder="回复 " + comment.user.username) }}</div><div class="d-flex justify-content-end"><button type="button" class="btn btn-sm btn-secondary me-2 cancel-reply-btn">取消</button><button type="submit" class="btn btn-sm btn-primary">提交回复</button></div></form></div>{% endif %}<!-- 嵌套回复 -->{% if comment.replies %}<div class="replies mt-3">{% for reply in comment.replies %}<div class="reply d-flex mt-2"><div class="flex-shrink-0"><img src="{{ url_for('static', filename='img/default-avatar.png') }}" class="rounded-circle" width="40" height="40" alt="{{ reply.user.username }}"></div><div class="flex-grow-1 ms-2"><div class="d-flex justify-content-between"><h6 class="mt-0">{{ reply.user.username }}</h6><small class="text-muted">{{ reply.created_at.strftime('%Y-%m-%d %H:%M') }}</small></div><p>{{ reply.content }}</p></div></div>{% endfor %}</div>{% endif %}</div></div></div>{% endfor %}</div>{% else %}<div class="alert alert-light">暂无评论,快来发表第一条评论吧!</div>{% endif %}</div>
</div>
1.5 点赞功能

为了增加互动性,我们添加了文章点赞功能:

# app/blog/routes.py@bp.route('/like/<int:post_id>', methods=['POST'])
@login_required
def like_post(post_id):post = Post.query.get_or_404(post_id)# 检查用户是否已经点赞existing_like = Like.query.filter_by(user_id=current_user.id, post_id=post_id).first()if existing_like:# 如果已经点赞,则取消点赞db.session.delete(existing_like)flash('已取消点赞', 'info')else:# 如果未点赞,则添加点赞like = Like(user_id=current_user.id, post_id=post_id)db.session.add(like)flash('点赞成功', 'success')db.session.commit()return redirect(url_for('blog.view', post_id=post_id))
<!-- 点赞按钮 (app/templates/blog/view.html的一部分) -->
<div class="d-flex justify-content-between align-items-center mt-4"><div><span class="badge bg-primary me-2"><i class="far fa-comment me-1"></i>{{ post.comment_count }} 评论</span><span class="badge bg-danger"><i class="far fa-heart me-1"></i>{{ post.like_count }} 点赞</span></div>{% if current_user.is_authenticated %}<form method="POST" action="{{ url_for('blog.like_post', post_id=post.id) }}"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><button type="submit" class="btn btn-sm {% if has_liked %}btn-danger{% else %}btn-outline-danger{% endif %}"><i class="{% if has_liked %}fas{% else %}far{% endif %} fa-heart me-1"></i>{% if has_liked %}已点赞{% else %}点赞{% endif %}</button></form>{% endif %}
</div>
1.6 用户博客页面

我们还添加了用户博客页面,展示特定用户发表的所有文章:

<!-- app/templates/blog/user_posts.html -->
{% extends 'base.html' %}{% block title %}{{ user.username }}的博客 - 家庭网站{% endblock %}{% block styles %}
<style>.user-info {background-color: var(--bs-tertiary-bg);border-radius: 10px;padding: 20px;margin-bottom: 30px;}.user-avatar {width: 100px;height: 100px;border-radius: 50%;object-fit: cover;border: 3px solid var(--bs-primary);}.user-stats {display: flex;gap: 20px;margin-top: 15px;}.stat-item {text-align: center;}.stat-value {font-size: 1.5rem;font-weight: bold;color: var(--bs-primary);}.stat-label {font-size: 0.9rem;color: var(--bs-secondary-color);}.blog-card {transition: transform 0.3s ease, box-shadow 0.3s ease;}.blog-card:hover {transform: translateY(-5px);box-shadow: 0 10px 20px rgba(0,0,0,0.1);}.blog-card .card-img-top {height: 180px;object-fit: cover;}
</style>
{% endblock %}{% block content %}
<nav aria-label="breadcrumb" class="mb-4"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{ url_for('main.index') }}">首页</a></li><li class="breadcrumb-item"><a href="{{ url_for('blog.index') }}">博客</a></li><li class="breadcrumb-item active" aria-current="page">{{ user.username }}的博客</li></ol>
</nav><!-- 用户信息 -->
<div class="user-info"><div class="d-flex align-items-center"><img src="{{ url_for('static', filename='img/default-avatar.png') }}" alt="{{ user.username }}" class="user-avatar me-4"><div><h2>{{ user.username }}</h2><p class="text-muted">{{ user.email }}</p><div class="user-stats"><div class="stat-item"><div class="stat-value">{{ user.posts|length }}</div><div class="stat-label">文章</div></div><div class="stat-item"><div class="stat-value">{{ total_comments }}</div><div class="stat-label">收到的评论</div></div><div class="stat-item"><div class="stat-value">{{ total_likes }}</div><div class="stat-label">收到的点赞</div></div></div></div></div>
</div><h3 class="mb-4">{{ user.username }}的文章</h3>{% if current_user.is_authenticated and current_user.id == user.id %}
<div class="mb-4"><a href="{{ url_for('blog.create') }}" class="btn btn-primary"><i class="fas fa-pen-to-square me-1"></i>写新文章</a>
</div>
{% endif %}{% if posts %}
<div class="row row-cols-1 row-cols-md-2 g-4">{% for post in posts %}<div class="col"><div class="card h-100 blog-card">{% if post.cover_image %}<img src="{{ url_for('static', filename='uploads/blog/' + post.cover_image) }}" class="card-img-top" alt="{{ post.title }}">{% endif %}<div class="card-body"><h5 class="card-title">{{ post.title }}</h5><p class="card-text text-muted small"><i class="far fa-calendar me-1"></i>{{ post.created_at.strftime('%Y-%m-%d') }}</p><p class="card-text">{{ post.summary or (post.content|striptags|truncate(150)) }}</p></div><div class="card-footer d-flex justify-content-between align-items-center"><div><span class="badge bg-primary me-1"><i class="far fa-comment me-1"></i>{{ post.comment_count }}</span><span class="badge bg-danger"><i class="far fa-heart me-1"></i>{{ post.like_count }}</span></div><a href="{{ url_for('blog.view', post_id=post.id) }}" class="btn btn-sm btn-outline-primary">阅读全文</a></div></div></div>{% endfor %}
</div><!-- 分页控件 -->
<nav aria-label="Page navigation" class="mt-4"><ul class="pagination justify-content-center">{% if pagination.has_prev %}<li class="page-item"><a class="page-link" href="{{ url_for('blog.user_posts', username=user.username, page=pagination.prev_num) }}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>{% else %}<li class="page-item disabled"><a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>{% endif %}{% for page in pagination.iter_pages() %}{% if page %}{% if page != pagination.page %}<li class="page-item"><a class="page-link" href="{{ url_for('blog.user_posts', username=user.username, page=page) }}">{{ page }}</a></li>{% else %}<li class="page-item active"><a class="page-link" href="#">{{ page }}</a></li>{% endif %}{% else %}<li class="page-item disabled"><a class="page-link" href="#">...</a></li>{% endif %}{% endfor %}{% if pagination.has_next %}<li class="page-item"><a class="page-link" href="{{ url_for('blog.user_posts', username=user.username, page=pagination.next_num) }}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>{% else %}<li class="page-item disabled"><a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>{% endif %}</ul>
</nav>
{% else %}
<div class="alert alert-info"><i class="fas fa-info-circle me-2"></i>{{ user.username }}还没有发表任何文章。
</div>
{% endif %}
{% endblock %}

二、V1.2版本新增功能

1. 日程管理系统优化

V1.2版本主要对日程管理系统进行了优化,解决了数据模型不一致的问题。

1.1 统一日程模型

在之前的版本中,我们有两个不同的模型(ScheduleCalendarEvent)来表示日程事件,这导致了数据不一致的问题。在V1.2版本中,我们统一使用Schedule模型:

# app/main/routes.py@bp.route('/')
def index():# 获取轮播图carousel_items = CarouselItem.query.order_by(CarouselItem.order).all()# 获取即将到来的事件# 如果用户已登录,只显示当前用户的日程if current_user.is_authenticated:upcoming_events = Schedule.query.filter(Schedule.user_id == current_user.id,Schedule.start_time >= datetime.now()).order_by(Schedule.start_time).limit(3).all()else:upcoming_events = []# 获取最近的时间线事件recent_events = TimelineEvent.query.order_by(TimelineEvent.date.desc()).limit(3).all()# 获取最新博客文章latest_posts = Post.query.order_by(Post.created_at.desc()).limit(3).all()return render_template('main/index.html', carousel_items=carousel_items,upcoming_events=upcoming_events,recent_events=recent_events,latest_posts=latest_posts)
1.2 改进日历视图

在V1.2版本中,我们移除了日历视图的月份限制,现在可以显示所有日程:

# app/calendar/routes.py@bp.route('/')
@login_required
def index():# 获取当前年月year = request.args.get('year', datetime.now().year, type=int)month = request.args.get('month', datetime.now().month, type=int)# 计算当前月的第一天和最后一天first_day = datetime(year, month, 1)# 获取下一个月的第一天if month == 12:next_month_first_day = datetime(year + 1, 1, 1)else:next_month_first_day = datetime(year, month + 1, 1)# 获取当前用户的所有日程,不再限制月份schedules = Schedule.query.filter_by(user_id=current_user.id).order_by(Schedule.start_time).all()# 计算上个月和下个月的链接prev_month = month - 1 if month > 1 else 12prev_year = year if month > 1 else year - 1next_month = month + 1 if month < 12 else 1next_year = year if month < 12 else year + 1# 生成日历数据cal = calendar.monthcalendar(year, month)return render_template('calendar/index.html',year=year,month=month,calendar=cal,schedules=schedules,prev_month=prev_month,prev_year=prev_year,next_month=next_month,next_year=next_year,current_date=datetime.now().date())
1.3 扩展"即将到来的日程"页面时间范围

我们将"即将到来的日程"页面的时间范围从7天扩展到30天,以显示更多的日程:

# app/calendar/routes.py@bp.route('/upcoming')
@login_required
def upcoming():# 获取当前日期和30天后的日期today = datetime.now().date()next_month = today + timedelta(days=30)# 获取未来30天内的日程schedules = Schedule.query.filter(Schedule.user_id == current_user.id,Schedule.start_time >= datetime.combine(today, time.min),Schedule.start_time <= datetime.combine(next_month, time.max)).order_by(Schedule.start_time).all()return render_template('calendar/upcoming.html', schedules=schedules)
1.4 修复jQuery加载问题

在V1.2版本中,我们修复了jQuery加载问题,确保页面正常加载:

<!-- app/templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN" data-bs-theme="dark" data-bs-theme-color="blue">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{% block title %}家庭网站{% endblock %}</title><!-- Bootstrap CSS --><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"><!-- Font Awesome --><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"><!-- 自定义CSS --><link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">{% block styles %}{% endblock %}
</head>
<body><!-- 导航栏 -->{% include 'includes/navbar.html' %}<!-- 主内容区 --><main class="container mt-4"><!-- 闪现消息 -->{% include 'includes/flash_messages.html' %}<!-- 页面内容 -->{% block content %}{% endblock %}</main><!-- 页脚 -->{% include 'includes/footer.html' %}<!-- Bootstrap JS --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script><!-- jQuery --><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><!-- 主题切换JS --><script src="{{ url_for('static', filename='js/theme.js') }}"></script><!-- 色彩主题切换JS --><script src="{{ url_for('static', filename='js/color-theme.js') }}"></script><!-- 自定义JS --><script src="{{ url_for('static', filename='js/main.js') }}"></script>{% block scripts %}{% endblock %}
</body>
</html>
1.5 改进日程删除功能

我们使用现代JavaScript事件处理方式改进了日程删除功能:

<!-- app/templates/calendar/upcoming.html -->
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {const deleteButtons = document.querySelectorAll('.delete-btn');deleteButtons.forEach(button => {button.addEventListener('click', function(e) {e.preventDefault();if (confirm('确定要删除这个日程吗?此操作不可撤销。')) {window.location.href = this.getAttribute('data-delete-url');}});});
});
</script>
{% endblock %}
<!-- 删除按钮部分 -->
<a href="#" class="btn btn-sm btn-outline-danger delete-btn" data-delete-url="{{ url_for('calendar.delete_schedule', schedule_id=schedule.id) }}"><i class="fas fa-trash-alt"></i>
</a>

2. 首页"即将到来的事件"显示优化

我们优化了首页"即将到来的事件"的显示,使其更加美观和信息丰富:

<!-- app/templates/main/index.html (部分) -->
<!-- 即将到来的事件 -->
<div class="col-md-4"><div class="card h-100"><div class="card-header bg-primary text-white"><h5 class="card-title mb-0"><i class="fas fa-calendar-alt me-2"></i>即将到来的事件</h5></div><div class="card-body">{% if upcoming_events %}<div class="list-group list-group-flush">{% for event in upcoming_events %}<div class="list-group-item px-0"><div class="d-flex w-100 justify-content-between"><h5 class="mb-1">{{ event.title }}</h5><small class="text-muted">{% if event.all_day %}<span class="badge bg-info">全天</span>{% else %}{{ event.start_time.strftime('%H:%M') }}{% endif %}</small></div><p class="mb-1 text-muted"><i class="far fa-calendar me-1"></i>{{ event.start_time.strftime('%Y-%m-%d') }}</p>{% if event.description %}<p class="mb-1">{{ event.description|truncate(100) }}</p>{% endif %}<a href="{{ url_for('calendar.view_schedule', schedule_id=event.id) }}" class="btn btn-sm btn-outline-primary mt-2"><i class="fas fa-eye me-1"></i>查看详情</a></div>{% endfor %}</div>{% else %}<div class="alert alert-info"><i class="fas fa-info-circle me-2"></i>未来没有即将到来的事件。{% if current_user.is_authenticated %}<a href="{{ url_for('calendar.create_schedule') }}" class="alert-link">点击这里</a>添加新日程!{% endif %}</div>{% endif %}</div><div class="card-footer"><a href="{{ url_for('calendar.upcoming') }}" class="btn btn-outline-primary btn-sm w-100"><i class="fas fa-calendar-week me-1"></i>查看更多日程</a></div></div>
</div>

三、技术实现细节

1. CKEditor 5集成

CKEditor 5是一个现代化的富文本编辑器,我们通过以下步骤集成到博客系统中:

  1. 在模板中引入CKEditor 5的CDN:
<script src="https://cdn.ckeditor.com/ckeditor5/35.1.0/classic/ckeditor.js"></script>
  1. 初始化编辑器并处理表单提交:
ClassicEditor.create(document.querySelector('#editor')).then(newEditor => {editor = newEditor;}).catch(error => {console.error(error);});const form = document.getElementById('postForm');
const contentTextarea = document.querySelector('#editor');form.addEventListener('submit', function(e) {e.preventDefault();// 获取CKEditor内容const editorData = editor.getData();// 检查内容是否为空if (!editorData.trim()) {alert('文章内容不能为空!');return;}// 将CKEditor内容设置到原始textareacontentTextarea.value = editorData;// 提交表单this.submit();
});

2. 嵌套评论实现

嵌套评论功能通过以下数据模型和查询实现:

class Comment(db.Model):id = db.Column(db.Integer, primary_key=True)content = db.Column(db.Text, nullable=False)created_at = db.Column(db.DateTime, default=datetime.utcnow)user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)parent_id = db.Column(db.Integer, db.ForeignKey('comment.id'))# 关系user = db.relationship('User', backref=db.backref('comments', lazy=True))replies = db.relationship('Comment', backref=db.backref('parent', remote_side=[id]), lazy=True)

在视图函数中,我们获取顶级评论和它们的回复:

@bp.route('/view/<int:post_id>')
def view(post_id):post = Post.query.get_or_404(post_id)# 获取顶级评论(没有父评论的评论)top_level_comments = Comment.query.filter_by(post_id=post_id, parent_id=None).order_by(Comment.created_at.desc()).all()# 创建评论表单和回复表单comment_form = CommentForm()reply_form = ReplyForm()# 检查当前用户是否已点赞has_liked = Falseif current_user.is_authenticated:has_liked = Like.query.filter_by(user_id=current_user.id, post_id=post_id).first() is not Nonereturn render_template('blog/view.html', post=post, top_level_comments=top_level_comments,comment_form=comment_form,reply_form=reply_form,has_liked=has_liked)

3. 日程管理系统优化

日程管理系统的优化主要涉及以下几个方面:

  1. 统一使用Schedule模型,避免数据不一致
  2. 修改查询逻辑,确保正确显示日程
  3. 改进前端交互,提升用户体验

关键代码示例:

# 获取未来30天内的日程
schedules = Schedule.query.filter(Schedule.user_id == current_user.id,Schedule.start_time >= datetime.combine(today, time.min),Schedule.start_time <= datetime.combine(next_month, time.max)
).order_by(Schedule.start_time).all()

4. 现代JavaScript事件处理

我们使用现代JavaScript事件处理方式改进了交互体验:

document.addEventListener('DOMContentLoaded', function() {const deleteButtons = document.querySelectorAll('.delete-btn');deleteButtons.forEach(button => {button.addEventListener('click', function(e) {e.preventDefault();if (confirm('确定要删除这个日程吗?此操作不可撤销。')) {window.location.href = this.getAttribute('data-delete-url');}});});
});

在这里插入图片描述

四、总结

在V1.1和V1.2版本中,我们对家庭网站进行了全面升级和优化:

  1. V1.1版本添加了完整的博客系统,包括:

    • 文章创建、编辑和删除功能
    • CKEditor 5富文本编辑器集成
    • 评论系统,支持嵌套回复
    • 点赞功能
    • 用户博客页面
  2. V1.2版本优化了日程管理系统,包括:

    • 统一日程模型,解决数据不一致问题
    • 改进日历视图,移除月份限制
    • 优化首页"即将到来的事件"显示
    • 扩展"即将到来的日程"页面时间范围
    • 修复jQuery加载问题
    • 改进日程删除功能

这些功能的添加和优化使我们的家庭网站更加完善,为家庭成员提供了更丰富的交流和记录方式。博客系统让家庭成员可以分享生活点滴和心得体会,评论和点赞功能增强了互动性。日程管理系统的优化则提高了用户体验,使日程安排更加便捷和可靠。

在下一篇文章中,我们将继续探索如何进一步扩展家庭网站的功能,包括添加家庭成员管理、家庭财务管理、家庭聊天功能等,敬请期待!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/34587.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React(二):JSX语法解析+综合案例

事件绑定 this绑定方式 问题&#xff1a;在事件执行后&#xff0c;需获取当前类的对象中相关属性&#xff0c;此时需要this——当打印时&#xff0c;发现this为undefined,这又是为啥&#xff1f; 假设有一个btnClick函数&#xff0c;但它并不是我们主动调用的&#xff0c;而是…

One of the configured repositories failed (未知), and yum doesn‘t have enough cached data to continue

centos操作系统运行yum命令是出现如下报错&#xff1a; 解决办法&#xff1a; 由于CentOS的源地址内容已移除&#xff0c;CentOS 操作系统结束了生命周期&#xff0c;源地址内容已移除。 只需要将它的base源换成其他可用源&#xff0c;我这里将它换成了阿里的base源 备份原来…

【蓝图使用】绘制mesh顶点的法线

文章目录 绘制法线Normal准备工作UE5资源制作蓝图制作 参考 绘制法线Normal 参考[1]打算用蓝图走一遍渲染管线&#xff0c;还是可以的 准备工作 Blender制作一个三个顶点的模型 要不要材质无所谓&#xff0c;就一个三个顶点的mesh即可&#xff0c;参考[2] 找到一个法线贴…

202503执行jmeter压测数据库(ScyllaDB,redis,lindorm,Mysql)

一、Mysql 1 、 准备MySQL 连接内容 2 、 下载连接jar包 准备 mysql-connector-java-5.1.49.jar 放到 D:\apache-jmeter-5.6.3\lib\ext 目录下面; 3 、 启动jmeter ,配置脚本 添加线程组---》JDBC Connection Configuration---》JDBC Request---》查看结果树。 1)测…

f-string高级字符串格式化与string Template()

f-string 高级字符串格式化 f-string无法替换带有${name}的字符串&#xff0c;会保留\$ def test_fstring():"""f-string&#xff0c;高级字符串格式化的方式"""s "my name is {name}".format(name李白)print(s)# 无法替换$s &quo…

【Java 优选算法】分治-归并排序

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 数组分块如二叉树的前序遍历, 而归并排序就如二叉树的后序遍历 912. 排序数组 解法 使用归并算法 根据中间点划分区间, mid (right left ) / 2将左右区间排序合并两个有…

docker入门篇

使用docker可以很快部署相同的环境,这也是最快的环境构建,接下来就主要对docker中的基础内容进行讲解.Docker 是一个用于开发、交付和运行应用程序的开源平台&#xff0c;它可以让开发者将应用程序及其依赖打包到一个容器中&#xff0c;然后在任何环境中运行这个容器&#xff0…

Learning vtkjs之ContourLoopExtraction

过滤器 等高线轮廓提取 介绍 这个过滤器可以获取一个cut的相交的循环的线&#xff0c;目前这个案例cut是一个平面&#xff0c;应该是可以支持更多隐式公式 效果 可以设置这个平面的原点Origin 法线方向Normal&#xff0c;然后就可以求交了 核心代码 需要实现这个代码主要…

如何高效解决 Java 内存泄漏问题方法论

目录 一、系统化的诊断与优化方法论 二、获取内存快照&#xff1a;内存泄漏的第一步 &#xff08;一&#xff09;自动生成 Heap Dump &#xff08;二&#xff09;手动生成 Heap Dump 三、导入分析工具&#xff1a;MAT 和 JProfiler &#xff08;一&#xff09;MAT (Memor…

新手村:数据预处理-异常值检测方法

机器学习中异常值检测方法 一、前置条件 知识领域要求编程基础Python基础&#xff08;变量、循环、函数&#xff09;、Jupyter Notebook或PyCharm使用。统计学基础理解均值、中位数、标准差、四分位数、正态分布、Z-score等概念。机器学习基础熟悉监督/无监督学习、分类、聚类…

大模型-提示词调优

什么是提示词 提示词&#xff08;Prompt&#xff09;在大模型应用中扮演着关键角色&#xff0c;它是用户输入给模型的一段文本指令 。简单来说&#xff0c;就是我们向大模型提出问题、请求或描述任务时所使用的文字内容。例如&#xff0c;当我们想让模型写一篇关于春天的散文&a…

VS2022输入 scanf 报错解决方法

1.第一种解决办法&#xff08;不推荐&#xff09; •将 scanf 替换为 scanf_s •scanf_s 是VS提供的一个函数&#xff0c;scanf_s函数的使用和scanf是有区别的 •scanf_s 是VS提供的一个函数&#xff0c;其他的编译器可能不认识这个函数&#xff0c;那么我们所写的代码就存在跨…

鸿蒙开发-一多开发之媒体查询功能

在HarmonyOS中&#xff0c;使用ArkTS语法实现响应式布局的媒体查询是一个强大的功能&#xff0c;它允许开发者根据不同的设备特征&#xff08;如屏幕尺寸、屏幕方向等&#xff09;动态地调整UI布局和样式。以下是一个使用媒体查询实现响应式布局的实例&#xff1a; 1. 导入必要…

火语言RPA--列表项内容获取

【组件功能】&#xff1a;获取列表中某项数据内容 配置预览 配置说明 获取 获取数据方式 首项&#xff1a;列表第一条数据 末项&#xff1a;列表最后一条数据 随机项&#xff1a;随机获取列表中一条数据 指定索引项&#xff1a;根据索引获取列表对象中数据。 索引项目位置 …

基于Python+Flask+MySQL+HTML的爬取豆瓣电影top-250数据并进行可视化的数据可视化平台

FlaskMySQLHTML 项目采用前后端分离技术&#xff0c;包含完整的前端&#xff0c;以flask作为后端 Pyecharts、jieba进行前端图表展示 通过MySQL收集格列数据 通过Pyecharts制作数据图表 这是博主b站发布的详细讲解&#xff0c;感兴趣的可以去观看&#xff1a;【Python爬虫可…

解锁MySQL 8.0.14源码调试:Mac 11.6+CLion 2024.3.4实战指南

文章目录 解锁MySQL 8.0.41源码调试&#xff1a;Mac 11.6CLion 2024.3.4实战指南前期准备环境搭建详细步骤安装 CLion安装 CMake 3.30.5准备 MySQL 8.0.14 源码配置 CMake 选项构建 MySQL 项目 调试环境配置与验证配置 LLDB 调试器启动调试验证调试环境 总结与拓展 解锁MySQL 8…

81.HarmonyOS NEXT 状态管理与响应式编程:@Observed深度解析

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; HarmonyOS NEXT 状态管理与响应式编程&#xff1a;Observed深度解析 文章目录 HarmonyOS NEXT 状态管理与响应式编程&#xff1a;Observed深度解析…

【快速入门】MyBatis

一.基础操作 1.准备工作 1&#xff09;引入依赖 一个是mysql驱动包&#xff0c;一个是mybatis的依赖包&#xff1a; <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><vers…

RabbitMQ可靠性进制

文章目录 1.生产者可靠性生产者重连生产者确认小结 2. MQ的可靠性数据持久化LazyQueue小结 3. 消费者的可靠性消费者确认机制消费者失败处理方案业务幂等性唯一消息ID业务判断 兜底方案业务判断 兜底方案 1.生产者可靠性 生产者重连 在某些场景下由于网络波动&#xff0c;可能…

【专项测试】限流测试

简介 限流的目的是防止恶意请求、恶意攻击&#xff0c;或者防止流量超出系统峰值时保护系统免受灭顶之灾。 限流的具体做法是是通过对并发访问/请求进行限速或者对一个时间窗口的请求进行限速在保护系统&#xff0c;一旦达到限制速率则可以拒绝服务&#xff08;定向到错误页&a…