1. 功能介绍
论坛这种内容管理系统必不可少的一个功能模块就是个人中心,在个人中心中可以查看自己的账号基本信息、以及个人在社区的一些操作记录,比如发帖、评论、收藏等等,本论坛的个人中心功能如下图所示
2. 页面布局
个人中心的页面布局如下图所示,上面个人基本信息栏为个人中心所有页面通用部分,不管在个人中心的哪个栏目,上面通用部分的信息内容是不会改变的,下方的四个tab分别对应不同的功能模块,是差异性内容。
2.1 通用部分
在Jinja2的模板语法中,我们可以通过include关键字来将其他的模板文件引入到我们目标模板文件中,因为个人中心的上半部分的信息是通用,因此我们可以将其抽离出来,放入单独的html模板文件中去。除此之外Jinja2还支持macro块,macro的语法如下所示
# macro.html
{% macro sample() %}
<p>I am macro function!</p>
{% endmacro %}
# other.html
{% from macro.html import sample with context %}
<div>
{{ sample() }}
</div>
为了方便管理,我们把所有的macro相关的都放到了templates/macro.html文件中,在文件中嵌入下面的代码
{% macro profile_header() %}
<div class="row">
<div class="col-md-3 col-lg-3 text-center">
<img class="img-profile-bg" alt="{{ user.nickname }}" src="{{ user.avatar }}">
</div>
<div class="col-md-9 col-lg-9 div-center-md">
<h3 class="mb-0">{{ user.nickname }}</h3>
<p class="text-muted mb-2">@{{ user.username }}</p>
{% if user.slogan %}
<p class="mb-2">{{ user.slogan }}</p>
{% endif %}
{% if user.website or user.location %}
<p class="mb-2">
{% if user.website %}
<a class="text-decoration-none social-label" href="{{ user.website }}" target="_blank"
title="{{ user.nickname }}的个人主页"><i class="fa fa-home fa-fw mr-1"></i>{{ user.website }}</a>
{% endif %}
{% if user.location %}
<span class="social-label d-lg-inline d-none"><i
class="fa fa-map-marker fa-fw mr-1"></i>{{ user.location }}
</span>
<span class="social-label d-lg-none d-md-block" title="{{ user.location }}"><i
class="fa fa-map-marker fa-fw mr-1"></i>{{ user.location|truncate(8) }}
</span>
{% endif %}
</p>
{% endif %}
<p>
<span class="mr-2">{{ user.post|length }}篇帖子</span>
<a href="{{ url_for('profile.follower', user_id=user.id) }}"
class="mr-2 span-hand a-link text-decoration-none">{{ user.followers.count() }}位粉丝</a>
<a href="{{ url_for('profile.following', user_id=user.id) }}"
class="mr-2 span-hand a-link text-decoration-none">{{ user.following.count() }}个关注</a>
</p>
{% if current_user.id == user.id %}
<a class="btn btn-outline-success" href="{{ url_for('user.index', user_id=current_user.id) }}">个人主页</a>
{% else %}
{% if current_user.is_following(user) %}
<a class="btn btn-outline-info" href="/profile/unfollow/{{ user.id }}/">取消关注</a>
{% else %}
<a class="btn btn-outline-info" href="/profile/follow/{{ user.id }}/">关注TA</a>
{% endif %}
<!-- 弹出私信窗口 -->
<a class="btn btn-outline-primary" data-toggle="modal" data-userid="{{ user.id }}"
data-target="#privacyChat">私信</a>
{% if not blocked %}
<a class="btn btn-outline-danger" data-toggle="modal" data-userid="{{ user.id }}"
data-target="#confirmBlockUser">Block</a>
{% else %}
<a class="btn btn-primary disabled" data-toggle="modal" data-userid="{{ user.id }}"
data-target="#confirmBlockUser">Blocked</a>
{% endif %}
{% endif %}
</div>
</div>
{% endmacro %}
个人中心除了用户自己的,用户同样也可以访问其他用户的个人中心主页,因此在上面的代码中加入了判断,当前个人中心主页是否为当前用户个人的主页,如果不是,则在下方添加不同的功能按钮。
2.2 TAB部分
上面的通用部分内容既可以通过macro实现也可以通过include去实现,tab部分的内容就通过macro实现就比较方便了,在macro中我们也可以传入参数,同样的在macro.html中增加下列代码
{% macro profile_moment(tabName) %}
<div class="mt-4">
<ul class="nav nav-tabs nav-justified">
<li class="nav-item">
<a class="nav-link {% if tabName == '帖子' %}active{% endif %}" href="/profile/user/{{ user.id }}/">帖子</a>
</li>
<li class="nav-item">
<a class="nav-link {% if tabName == '评论' %}active{% endif %}"
href="/profile/comment/{{ user.id }}/">评论</a>
</li>
<li class="nav-item">
<a class="nav-link {% if tabName == '收藏' %}active{% endif %}"
href="/profile/collect/{{ user.id }}/">收藏</a>
</li>
<li class="nav-item">
<a class="nav-link {% if tabName == '社交' %}active{% endif %}"
href="/profile/social/{{ user.id }}/">社交</a>
</li>
</ul>
</div>
{% endmacro %}
这里与上面稍微有些不同,这里的macro函数带有参数输入,我们在函数体中通过判断参数的不同实现对应ab的被选中,当然这里也可以通过include去实现,可以通过endpoint来设置对应tab被选中。
3. 关注、取消关注用户
很多社交网站都会给用户提供关注的功能,论坛也提供了这个功能,用户既可以被别人关注,同时用户也可以关注他们,所以是多对多的关系,Follow表结构如下
class Follow(db.Model):
__tablename__ = 't_follow'
id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)
follower_id = db.Column(db.INTEGER, db.ForeignKey('t_user.id'))
followed_id = db.Column(db.INTEGER, db.ForeignKey('t_user.id'))
# 正在关注用户的人
follower = db.relationship('User', foreign_keys=[follower_id], back_populates='following', lazy='joined')
# 用户自己正在关注的人
followed = db.relationship('User', foreign_keys=[followed_id], back_populates='followers', lazy='joined')
class User(db.Model, db.UserMixin):
...
following = db.relationship('Follow', foreign_keys=[Follow.follower_id], back_populates='follower',
lazy='dynamic', cascade='all')
followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], back_populates='followed',
lazy='dynamic', cascade='all')
这里需要注意的是,由于Follow表中的两个字段follower_id, followed_id都是user表的外键,因此我们在定义的时候需要指定每个关系所对应的字段,不然ORM框架会识别不了,follower指定为followe_id,followed指定为followed_id,与此同时,在user表中同样也是指定。
创建好Follow表之后,关注的逻辑就比较简单了,在blueprint/profile.py文件中增加下面的代码。代码逻辑十分简单,就是通过向Follow表中新增或者删除相应的数据,实现关注或者取消关注功能,同时还增加了自己不能关注自己的逻辑。
@profile_bp.route('/follow/<user_id>/', methods=['GET', 'POST'])
@login_required
def follow_user(user_id):
user = User.query.get_or_404(user_id)
if user.id == current_user.id:
flash('我关注我自己?禁止套娃!', 'info')
return redirect(request.referrer)
if current_user.is_following(user):
flash('你已经关注TA了!', 'info')
return redirect(request.referrer)
current_user.follow(user)
flash('关注成功!', 'success')
return redirect(request.referrer)
@profile_bp.route('/unfollow/<user_id>/', methods=['GET', 'POST'])
@login_required
def unfollow_user(user_id):
user = User.query.get_or_404(user_id)
if current_user.is_following(user):
current_user.unfollow(user)
if request.method == 'POST':
return jsonify({'tag': 1})
flash('取关成功!', 'success')
return redirect(request.referrer)
4. 用户帖子
如上面所说,个人中心用户即可以访问自己的也可以访问他人,因此需要在帖子列表上做区分,在frontend/profile.py文件中增加下面的代码
@profile_bp.route('/user/<user_id>/')
@login_required
def index(user_id):
page = request.args.get('page', default=1, type=int)
user = User.query.get_or_404(user_id)
per_page = current_app.config['BBS_PER_PAGE']
# 其他人查看用户信息时候屏蔽用户匿名发表的帖子
if current_user.id == int(user_id):
pagination = Post.query.filter(Post.author_id == user_id, Post.status_id == 1).order_by(
Post.create_time.desc()).paginate(page=page, per_page=per_page)
posts = pagination.items
else:
pagination, posts = get_range_post(user.id,
page=page,
per_page=per_page,
range_day=TIME_RANGE.get(user.range_post.name))
blocked = BlockUser.query.filter(BlockUser.user_id == current_user.id,
BlockUser.block_user_id == user_id).all()
return render_template('frontend/profile/profile.html', user=user, tag=pagination.total > per_page,
pagination=pagination, posts=posts, blocked=blocked)
代码的逻辑很简单,如下
- 通过路由参数中的user_id来获取对应的user,如果没有找到user则返回404;
- 判断当前user是否为路由参数中的user,如果不是,则只获取该用户状态为正常的帖子;
- 如果是则获取所有的状态的帖子;
上面还出现了BlockUser的相关代码,这个代码在后续用户主页的相关文章中进行讲解,这里暂时不做讲解!
后端后去数据后,需要通过Jinja2模板进行渲染了,在frontend/profile/profile.html中新增下面的代码
{% extends "frontend/base.html" %}
{% from "macro.html" import profile_header, profile_moment, render_pagination, post_list with context %}
{% block head %}
{{ super() }}
<style>
.a-title {
font-size: 22px;
font-weight: bold;
}
.a-title-sm {
font-size: 16px;
font-weight: bold;
}
.f-12-b {
font-weight: bold;
font-size: 12px;
}
.span-hand {
cursor: pointer;
}
</style>
{% endblock %}
{% block title %}
{{ user.nickname }}的个人主页
{% endblock %}
{% block content %}
{{ moment.locale(auto_detect=True) }}
<body>
<main>
<div class="container mt-2">
{% include "_flash.html" %}
{{ profile_header() }}
{{ profile_moment('帖子') }}
{% if posts %}
<div class="mt-2">
{{ post_list(posts) }}
{% if user.range_post.name != '全部' and current_user.id != user.id %}
<div class="mt-2 text-center">
<p class="text-muted"><b>该用户仅展示最近{{ user.range_post.name }}的帖子</b></p>
</div>
{% endif %}
</div>
{% else %}
{% if current_user.id == user.id %}
<div class="text-center mt-5">
<p class="text-muted">你还没有发送过帖子!</p>
</div>
{% else %}
<div class="text-center mt-5">
{% if user.available_post_counts()|length %}
{% if user.range_post.name == '隐藏' %}
<p class="text-muted"><b>该用户{{ user.range_post.name }}了帖子</b></p>
{% else %}
<p class="text-muted"><b>该用户仅展示最近{{ user.range_post.name }}的帖子</b></p>
{% endif %}
{% else %}
<p class="text-muted"><b>他还没有发送过帖子!</b></p>
{% endif %}
</div>
{% endif %}
{% endif %}
{% include "follow-modal.html" %}
</div>
</main>
</body>
{% include "frontend/profile/contact-modal.html" %}
{% include "frontend/profile/block-user-modal.html" %}
{% endblock %}
博客文章中的代码只截取了部分关键代码,很多细节代码没有贴出来,如果想要在本地机器上运行调试可以访问我的github仓库或者gitee仓库仓库,如果想要看到实际效果请访问二狗学院!
Coder_Liuu 用户 2022-09-25T15:48:57
顶一顶!博主好强!