这一期继续编写flask后端,并且完成echarts折线图、柱状图和饼图的对接。
1 新增一些依赖
pip install Flask-SQLAlchemy Flask-Marshmallow pymysql
修改 init.py文件,下面给出完整代码:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
db = SQLAlchemy()
ma = Marshmallow()
def create_app():
app = Flask(__name__)
app.config.from_object('app.config.Config')
db.init_app(app)
ma.init_app(app)
from .routes import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
这里还加入了数据库的配置信息,需要修改app.config:
class Config:
# scrapy_demo 就是之前旅游爬虫教程中建的数据库,如果不清楚,可以去看这个教程
# 视频:https://www.bilibili.com/video/BV1Vx4y147wQ
# 博客:https://blog.csdn.net/roccreed?type=blog
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:12345678@localhost/scrapy_demo?charset=utf8'
SQLALCHEMY_TRACK_MODIFICATIONS = False
2 后端模型
通过模型,可以在Flask后端系统里以面向对象的形式来操作数据库里的数据。
首先,新建app/models.py:
from . import db
class Tour(db.Model):
__tablename__ = 'tb_tour'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
title_en = db.Column(db.String(255))
img = db.Column(db.String(255))
score = db.Column(db.Float)
comments = db.Column(db.Integer)
comment_url = db.Column(db.String(255))
rank_title = db.Column(db.String(255))
ranks = db.Column(db.Integer)
select_user = db.Column(db.String(255))
select_comment = db.Column(db.Text)
nation = db.Column(db.String(255))
city = db.Column(db.String(255))
创建schemas.py 文件,作用是数据在发送给前端的时候进行序列化,新建app/schemas.py:
from . import ma
from .models import Tour
class TourSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Tour
load_instance = True
tour_schema = TourSchema()
tours_schema = TourSchema(many=True)
修改routes.py 文件:
from flask import Blueprint, jsonify
from app.models import Tour
from app.schemas import tours_schema
main = Blueprint('main', __name__)
# 这个测试的后面就不需要了,可以删除
@main.route('/test', methods=['GET'])
def test():
data = [{'id': 1, 'name': 'John'}, {'id': 2, 'name': 'Jane'}]
return jsonify(data)
# 十大热门景点
@main.route('/commentsRank', methods=['GET'])
def getCommentsRank():
top_tours = Tour.query.order_by(Tour.comments.desc()).limit(10).all()
return tours_schema.jsonify(top_tours)
3 测试一下
在浏览器里输入 localhost:8080/commentsRank 访问,得到如下结果:
可以看到,访问后端是可以获取到数据的。
4 返回封装
在开始和前端对接前,先对后端的返回进行一定的优化
创建app/utils.py 文件
from flask import jsonify
def make_response(data=None, code=0, message='Success'):
response = {
'code': code,
'message': message,
'data': data if data is not None else []
}
return jsonify(response)
修改routes.py,之前测试的方法这边删除了:
# 十大热门景点(按照评论数排名)
@main.route('/top-tours', methods=['GET'])
def get_top_tours():
try:
top_tours = Tour.query.order_by(Tour.comments.desc()).limit(10).all()
result = tours_schema.dump(top_tours)
return make_response(data=result)
except Exception as e:
return make_response(code=1, message=str(e))
在测试一下,就可以发现结果封装在data里面了。
然后,下一步编写前端文件。
5 折线图的对接
上一小节已经写好了折线图的后端代码,现在开始写前端代码,修改LineChart.vue,这边我给出完整的源码了,另外两个图我只给出修改部分源码,因为原本的data部分不需要做任何修改:
<template>
<div>
<v-chart :option="chartOptions" style="width: 100%; height: 300px;"></v-chart>
</div>
</template>
<script>
import {getCommentsRank} from "@/api/tour"
export default {
name: 'TouristSpotRanking',
data() {
return {
chartOptions: {
title: {
text: '旅游景点评论排名',
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['评论数'],
},
xAxis: {
type: 'category',
data: ['景点A', '景点B', '景点C', '景点D', '景点E'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '评论数',
type: 'line',
data: [820, 932, 901, 934, 1290],
},
],
},
};
},
mounted() {
getCommentsRank().then(res => {
console.log(res.data.data);
this.chartOptions.xAxis.data = res.data.data.map(item => item.title);
this.chartOptions.series[0].data = res.data.data.map(item => item.comments);
})
}
};
</script>
<style scoped>
/* 添加一些样式使图表看起来更好 */
</style>
在tour.js中添加方法:
// 排名前十的景点
export function getCommentsRank() {
return request({
url: '/commentsRank',
method: 'get'
})
}
实现效果如下,可以看到景点按照评论数的折线图:
6 柱状图的对接
tour.js中添加方法:
// 按照城市排名
export function getCityRank() {
return request({
url: '/cityRank',
method: 'get'
})
}
修改routes.py:
# 景点按照评分排名
@main.route('/scoreRank', methods=['GET'])
def getScoreRank():
try:
top_tours = Tour.query.filter(Tour.comments>1000).order_by(Tour.score.desc()).limit(5).all()
result = tours_schema.dump(top_tours)
return make_response(data=result)
except Exception as e:
return make_response(code=1, message=str(e))
修改BarChart.vue:
import {getScoreRank} from "@/api/tour";
mounted() {
getScoreRank().then(res => {
// console.log(res.data.data);
this.chartOptions.xAxis.data = res.data.data.map(item => item.title);
this.chartOptions.series[0].data = res.data.data.map(item => item.score);
})
}
效果:
7 饼图的对接
tour.js中添加方法:
// 按照评分排名
export function getScoreRank() {
return request({
url: '/scoreRank',
method: 'get'
})
}
修改routes.py:
# 由于评分都很近,因此这边限制了评论数超过1000的景点再按照评分来排名
# 这样区分度大,不至于前端的柱状图都是10分
# 景点按照城市统计
@main.route('/cityRank', methods=['GET'])
def getCityRank():
try:
ret = db.session.query(Tour.city.label('name'),
db.func.count(Tour.id).label('value')).group_by(Tour.city).order_by(db.desc('value')).all()
result = chart_schema.dump(ret)
return make_response(data=result)
except Exception as e:
return make_response(code=1, message=str(e))
修改schemas.py,用来封装饼图数据的,添加:
class ChartData(ma.Schema):
class Meta:
fields = ('name', 'value')
chart_schema = ChartData(many=True)
修改PieChart.vue:
import {getCityRank} from "@/api/tour";
mounted() {
getCityRank().then(res => {
// console.log(res.data);
this.chartOptions.series[0].data = res.data.data
})
}
效果:
8 小结
这期完成了对三个echarts图形的前后端对接,后端实现了从数据库获取数据(利用sqlalchemy + pymsyql 方式),并且对返回也是对了封装的,总的来说成果斐然 👍🏻。
三个图都实现了前后端对接: