Flask 和 Vue.js 开发及整合部署实例

本文主要参考 Flask和Vue.js构建全栈单页面web应用【通过Flask开发RESTful API】的前部分,英文原文在这里 Developing a Single Page App with Flask and Vue.js

开发过程中我们可以保持 Flask 和  Vue.js 为单独的两个项目,并启动各自的服务,比如 Flask 是 http://localhost:5000, Vue.js 项目通过 npm run serve 启动在 http://localhost:8080,借助于 node js 的功能,修改 Vue.js 项目的内容能够自动刷新网页。要是开发中把静态文件全放在  Flask 项目中,那么任何对静态文件的修改都必须重启 Flask  服务。虽然 Debug 模式启动的 Flask 在看到它的目录中有任何修改时也能自动重启,但对静态文件的修改重启 Flask 没这个必要性。 

但部署时需进一步整合,最终只需要启动 Flask  服务,而无须两个,方便部署。如果是以 Docker 容器的方式发布,使用 docker-compose 来编排两个容器来发布也还算不错。更专业的部署方式应该是 Vue.js 的静态内容放到专门的 Web 服务器,如  Apache/Nginx 中,Flask 也通过 wsgi 与 Web 服务器集成起来。

介于原文中所用的 Vue CLI 稍稍显老,所以实践中也有些区别,先注明本文写作时所依赖的各主要组件版本

  • Vue v2.6.11
  • Vue CLI v4.6.6
  • Node v14.4.0
  • npm v6.14.4
  • Flask v1.1.2
  • Python v3.7

创建 Flask 项目

创建项目目录

$ mkdir flask-vue-app
$ cd flask-vue-app

接下来创建 Python 虚拟环境

$ python3.7 -m venv .venv
$ source .venv/bin/activate

安装 Flask 和 Flask-CORS 扩展,前面说过,由于开发中启动了两个服务,需要跨域访问服务,所以要用到 Flask-CORS

(.venv) $ pip install flask-cors

Flask 本身会被自动安装,当前日期为 2020-06-30, 所安装的 flask-cors 版本为 3.0.8, Fask 为 1.1.2。也可以锁定版本来安装扩展,如  pip install flask-cors==3.0.8。现在查看下所有的第三方依赖

$ pip freeze
click==7.1.2
Flask==1.1.2
Flask-Cors==3.0.8
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
six==1.15.0
Werkzeug==1.0.1

有需要的话,保存为 requirements.txt 放到版本服务器上

现在在 flask-vue-app 下创建一个  backend 目录,并在其中创建文件 app.py, 内容为

 

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from flask import Flask, jsonify from flask_cors import CORS   DEBUG = True   app = Flask(__name__) app.config.from_object(__name__)   CORS(app, resources={r'/*': {'origins': '*'}})     @app.route('/api/ping', methods=['GET']) def ping_pong():     return jsonify('pong!')     @app.route('/') def index():     return app.send_static_file('index.html')     @app.route('/<path:fallback>') def fallback(fallback):       # Vue Router 的 mode 为 'hash' 时可移除该方法     if fallback.startswith('css/') or fallback.startswith('js/')\             or fallback.startswith('img/') or fallback == 'favicon.ico':         return app.send_static_file(fallback)     else:         return app.send_static_file('index.html')     if __name__ == '__main__':     app.run()

 

简单说明一下上面的代码

  1. CORS(app, resources={r'/*': {'origins': '*'}}) 允许来自于 Vue 的跨域访问请求
  2. 定义以 /api/* 开头的 Flask 的路由,由 Flask 来处理
  3. / 请求直接发送一个静态文件 /index.html,由于不会用到 Flask 的模板系统,所以也就无需调用  render_template() 方法去渲染。
  4. 后面会将到在 backend 目录中会建立一个到 Vue.js 项目打包后的 dist 目录的符号链接 static, 所以其中有 index.html 等
  5. @app.rout('/<path:fallback>') 里是个关键,凡是 Flask 未定义的路由都会落到这里来。如果访问的是 static(dis) 中的 css, js, img 或 favicon.ico 文件,直接送出内容,其他的请求转到 Vue 的入口 index.html, 最后将由 Vue 中定义的路由来处理
  6. 如果 Vue 的 Router 工作在 hash 模式的话,fallback 方法可以不要,因为 /#/home 到 /#/about 的切换本身不产生 HTTP 请求,Flask 只需要 / 一个路由进入 Vue 入口页面

运行 Flask

(.venv) $ python backend/app.py

Flask 会在 localhost:5000 中启动服务,用 curl 命令验证

$ curl http://localhost:5000/api/ping
"pong!"

创建 Vue 项目

开始转到 Vue 项目来,将使用 Vue CLI 工具来生成它,首先是安装  Vue CLI

$ npm install -g @vue/cli

当前日期 2020-06-30, 安装后用 vue --version 看到的版本是 @vue/cli 4.4.6。安装时欲锁定版本用命令 npm install -g @vue/cli@4.4.6

正式创建项目 frontend,在 flask-vue-app 目录下运行

$ vue create frontend                  # 选择 Manually select features, 接下回答几个问题

启动 Vue 服务

$ cd frontend
$ npm run serve

打开浏览器访问 http://localhost:8080 会有一个  "Wellcome to Your Vue.js App" 的界面。后面对 frontend 项目的修改会自动刷新网页。

下面是如何在 Vue.js(8080) 中调用到 Flask(5000) 的 /api/ping 服务,当前在 frontend 目录中

创建 src/components/Ping.vue 文件,内容为

 

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template>   <div>     <p>{{ msg }}</p>   </div> </template>   <script> export default {   name: 'Ping',   data() {     return {       msg: 'Hello!',     };   }, }; </script>

 

编辑 src/router/index.js 文件,高亮行为新加的内容

 

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import Vue from 'vue'; import VueRouter from 'vue-router'; import Home from '../views/Home.vue'; import Ping from '../components/Ping.vue';   Vue.use(VueRouter);   const routes = [   {     path: '/',     name: 'Home',     component: Home,   },   {     path: '/about',     name: 'About',     // route level code-splitting     // this generates a separate chunk (about.[hash].js) for this route     // which is lazy-loaded when the route is visited.     component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),   },   {     path: '/ping',   # 用来调用 Flask 的 "/api/ping" API     name: 'Ping',     component: Ping,   },   {     path: '/ping_xyz', # 这个用来测试,非 Flask 中定义的路由,可被 Vue 进行处理     name: 'Ping',     component: Ping,   }, ];   const router = new VueRouter({   mode: 'history',   base: process.env.BASE_URL,   routes, });   export default router;

 

对 src/App.vue 的 <template> 中的导航部分删除,内容变为

 

 
1 2 3 4 5 <template>   <div id="app">     <router-view/>   </div> </template>

 

浏览器中访问 http://localhost:8080/ping, "Hello!" 显示的还是 src/components/Ping.vue 中 data 的内容

现在开始将  Ping.vue 与 Flask 的 /api/ping API 进行连接,Vue 中要用 Ajax 来访问,先要安装 axios,命令如下

$ npm install axios --save

 

目前安装的是 axios@0.19.2, 安装后可在 package.json 里看到 dependencies 中的 "axios": "^0.19.2"

编辑 src/components/Ping.vue 文件,修改为

 

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <script> import axios from 'axios';   export default {   name: 'Ping',   data() {     return {       msg: '',     };   },   methods: {     getMessage() {       const path = 'http://localhost:5000/ping';       axios.get(path)         .then((res) => {           this.msg = res.data;         })         .catch((error) => {           // eslint-disable-next-line           console.error(error);         });     },   },   created() {     this.getMessage();   }, }; </script>

 

高亮行为新加的代码, 保存后 http://localhost:8080/ping 窗口中的内容自动刷新为

pong! 消息是来自于 Flask 的 /api/ping API 的响应。由于我们前面是以 Debug 模式启动的 Flask backend 应用, 所以在控制台也能够看到一个对 /api/ping 的请求

127.0.0.1 - - [01/Jul/2020 02:53:06] "GET /api/ping HTTP/1.1" 200 -

访问 http://localhost:8080/ping_xyz 指向了同一个 Vue 组件,所以效果上与 http://localhost:8080/ping 是一样的。

Flask 与 Vue.js  整合

开发的时候启动两个服务很方面,但我们希望在部署后只启动一个 Flask  服务,那么可以这样做

首先用 npm 对 fronend 中的静态内容打包

$ npm run build

将会在 frontend 下生成 dist 目录,其下内容为

css      favicon.ico   img  index.html   js

绿色为目录

这时修只要在 backend 中创建一个符号链接

$ ln -s ../front/dist static

创建后在 backend 目录中的内容为

-rw-r--r-- 1  yanbin root 690 Jul 1 01:01 app.py
lrwxr-xr-x 1 yanbin root 16 Jun 30 22:33 static -> ../frontend/dist

因为 Flask 是以 Debug 模式启动的,对 Flask 项目 backend 的改动也可能会触发  Flask 的重新启动,需要的话手动重启  Flask (CTRL+C 退出再重启)

$ python backend/app.py

现在  Vue.js 那个服务可以停止了,不管是 Flask 还是 Vue.js  的路由都能够通过  http://localhost:5000 来访问了

http://localhost:5000/ping

 

http://localhost:5000/ping_xyz

Flask + Vue 对 http://localhost:5000/ping 和  http://localhost:5000/ping_xyz 的处理过程是

  1. 对 localhost:5000 的请求发往 Flask, Flask 的  @app.route('/<path:fallback>') 进行处理
  2. 不是 css/js/img 和  favicon.iso 的请求,交由 Vue.js 的入口 index.html 处理
  3. Vue.js 在自己的路由表中找到了 /ping 和 /ping_xyz, 进它们进行渲染
  4. 如随意一个 http://localhost:5000/abc,也会转给 Vue.js 的入口 index.html,但 Vue.js 未定 /abc 路由,页面得不到渲染,一面空白

最后,Flask 与 Vue.js 这样整合后,Vue.js 路由中访问 Flask API 要与 Flask 实际启动的 IP 端口保持一致,因为只有一个服务也就不存在跨域访问的问题,允许跨域相关的 Python 代码也就可以移除掉了。

本文演示的是一个 Vue.js 多页面程序,如果是单页面程序(用 /#/abc) 导引的,在 Flask 中处理起来还稍微简单些,只要 "/" 请求交给 Vue.js 的入口 index.html, 其他全当是静态文件,Flask 的 API 还是最好约定为 /api/* 的形式。

VueRouter 的 history 和 hash 模式

如果 VueRouter  使用 hash 模式,在服务端可以更简单的些,前面说过在 app.py 中的 fallback() 方法可以不需要了。Vue 默认的模式是 hash, 只是用 vue 命令生成的项目设置成了 history 模式,重新启用 hash 模式的方法是修改 src/router/index.js 文件中,把 mode 值改为 hash 或去掉 mode 行

 

 
1 2 3 4 5 const router = new VueRouter({   // mode: 'history',   或改为 mode: 'hash', 默认为 'hash'   base: process.env.BASE_URL,   routes, });

 

这时候打开 http://localhost:8080 会自动跳转到 http://localhost:8080/#/, 其他的路由也加上了 #, 如 /#/ping 

浏览时看到原来的 localhost:8080/ping 变成了 localhost:8080/#/ping, 使用 hash 的好处是每次 Vue 的路由跳转其时是一个锚点链接(anchor),它相当于当前页的位置跳转,不会重新刷新整个页面,且本身不会产生与服务端的 HTTP 请求,所以可减少许多的因 Vue  跳转而产生的交互,虽然前也简单的跳转回 Vue 的入口文件 index.html,但怎么着也是省了不少来回。

接下来将在 Vue.js 中试验 Bootstrap 和 BootstrapVue 的集成。

本实例代码已推送到了 github, 仓库地址为 https://github.com/yabqiu/flask-vue-app.git,姓没变,欢迎检阅

相关链接:

    1. Flask和Vue.js构建全栈单页面web应用【通过Flask开发RESTful API】
    2. Developing a Single Page App with Flask and Vue.js
    3. Vue SPA and Flask together
    4. Best practices to deploy a Flask and Vue app?

原文地址:https://www.cnblogs.com/caicaizi/p/14326148.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Jinja2:是Python的Web项目中被广泛应用的模板引擎,是由Python实现的模板语言,Jinja2 的作者也是 Flask 的作者。他的设计思想来源于Django的模板引擎,并扩展了其语法和一系列强大的功能,其是Flask内置的模板语言。
Fullcalendar日历使用,包括视图选择、事件插入、编辑事件、事件状态更改、事件添加和删除、事件拖动调整,自定义头部,加入el-popover显示图片、图片预览、添加附件链接等,支持手机显示。
监听QQ消息并不需要我们写代码,因为市面上已经有很多开源QQ机器人框架,在这里我们使用go-cqhttp官方文档:go-cqhttp如果您感兴趣的话,可以阅读一下官方文档,如果不想看,直接看我的文章即可。
【Flask框架】—— 视图和URL总结
python+web+flask轻量级框架的实战小项目。登录功能,后续功能可自行丰富。
有了这个就可以配置可信IP,关键是不需要企业认证,个人信息就可以做。
本专栏是对Flask官方文档中个人博客搭建进行的归纳总结,与官方文档结合事半功倍。 本人经验,学习一门语言或框架时,请首先阅读官方文档。学习完毕后,再看其他相关文章(如本系列文章),才是正确的学习道路。
本专栏是对Flask官方文档中个人博客搭建进行的归纳总结,与官方文档结合事半功倍。基础薄弱的同学请戳Flask官方文档教程 本人经验,学习一门语言或框架时,请首先阅读官方文档。学习完毕后,再看其他相关文章(如本系列文章),才是正确的学习道路。 如果python都完全不熟悉,一定不要着急学习框架,请首先学习python官方文档,一步一个脚印。要不然从入门到放弃是大概率事件。 Python 官方文档教程
快到年末了 相信大家都在忙着处理年末数据 刚好有一个是对超市的商品库存进行分析的学员案例 真的非常简单~
一个简易的问答系统就这样完成了,当然,这个项目还可以进一步完善,比如 将数据存入Elasticsearch,通过它先进行初步的检索,然后再通过这个系统,当然我们也可以用其他的架构实现。如果你对这系统还有其他的疑问,也可以再下面进行留言!!!
#模版继承和页面之间的调用@app.route(&quot;/bl&quot;)def bl(): return render_template(&quot;file_2.html&quot;)主ht
#form表达提交@app.route(&quot;/data&quot;,methods=[&#39;GET&#39;,&#39;POST&#39;]) #methods 让当前路由支持GET 和
#form表达提交@app.route(&quot;/data&quot;,methods=[&#39;GET&#39;,&#39;POST&#39;]) #methods 让当前路由支持GET 和
#session 使用app.secret_key = &quot;dsada12212132dsad1232113&quot;app.config[&#39;PERMANENT_SESSION_LI
#文件上传@app.route(&quot;/file&quot;,methods=[&#39;GET&#39;,&#39;POST&#39;])def file(): if request.meth
#跳转操作:redirect@app.route(&quot;/red&quot;)def red(): return redirect(&quot;/login&quot;)
#session 使用app.secret_key = &quot;dsada12212132dsad1232113&quot;app.config[&#39;PERMANENT_SESSION_LI
@app.route(&quot;/req&quot;,methods=[&#39;GET&#39;,&#39;POST&#39;])def req(): print(request.headers)
#模版继承和页面之间的调用@app.route(&quot;/bl&quot;)def bl(): return render_template(&quot;file_2.html&quot;)主ht
#文件操作:send_file,支持图片 视频 mp3 文本等@app.route(&quot;/img&quot;)def img(): return send_file(&quot;1.jpg&q