使用 Flask 和 Vue.js 来构建全栈单页应用

file

在这个教程中,我将向你展示如何将 Vue 的单页面应用和Flask后端连接起来。

简单的来说,如果想在 Flask 中使用 Vue 框架是没有什么问题的。 但在实际中存在一个明显的问题就是 Flask 的模版引擎 Jija 和 Vue 一样使用双花括号来渲染, 对于 Jinja 模板和 Vue 的语法冲突问题,这里有一个很好的解决方案  here

我想做个不一样的。 做一个用 Vue.js 做前端(用单页组件,HTML5历史模式的「vue-router」,以及其他好的特性),用 Flask 做后端的单页应用怎么样? 简单地说,这个应用应该是这样的:

  • Flask用来驱动一个包含 Vue.js app 的「index.html」,
  • 前端开发过程中我用到 Webpack 和它提供的所有酷的特性
  • Flask 有我能从 SPA 访问到的 API 端口
  • 在我开发前端时,我能运行 Node.js 来访问 api 端口

听起来很有意思吧?我们开始吧。

以下是所有代码的链接
https://github.com/oleg-agapov/flask-vue-s...

客户端

为了生成基本的Vue.js文件结构,我将使用vue-cli。 如果你没有安装它,请运行下边的命令:

$ npm install -g vue-cli

客户端和后端代码将会被拆分到不同的文件夹中, 请运行下边命令初始化前端部分:

$ mkdir flaskvue
$ cd flaskvue
$ vue init webpack frontend

下边是安装过程中我的设置:

  • Vue build --- Runtime only
  • Install vue-router? --- Yes
  • Use ESLint to lint your code? --- Yes
  • Pick an ESLint preset --- Standard
  • Setup unit tests with Karma + Mocha? --- No
  • Setup e2e tests with Nightwatch? --- No

下一步:

$ cd frontend
$ npm install
# 安装完成后运行下边命令
$ npm run dev

到这里,你应该安装好Vue.js了吧!那就让我们添加一些页面。

 在frontend/src/components 文件夹中添加Home.vue 和 About.vue两个文件。 并添加如下内容到对应的文件中:

// Home.vue文件的内容
<template>
  <div>
    <p>主页</p>
  </div>
</template>

// About.vue文件的内容
<template>
  <div>
    <p>关于</p>
  </div>
</template>

我们将使用它们正确地识别我们当前的位置(根据地址栏)。现在,我们需要更改frontend/src/router/index.js 文件来呈现我们的新组件:

import Vue from 'vue'
import Router from 'vue-router'
const routerOptions = [
  { path: '/', component: 'Home' },
  { path: '/about', component: 'About' }
]
const routes = routerOptions.map(route => {
  return {
    ...route,
    component: () => import(`@/components/${route.component}.vue`)
  }
})
Vue.use(Router)
export default new Router({
  routes,
  mode: 'history'
})

如果您尝试输入localhost:8080localhost:8080/about,您应该会看到相应的页面。
file

为了创建一个包含静态资产的包,我们几乎已经准备好构建一个项目了。在此之前,让我们为它们重新定义输出目录。在前端 frontend/config/index.js 索引。找到下一个设置

index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),

然后把它们变成下面这样

index: path.resolve(__dirname, '../../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../../dist'),

因此,带有html/css/js包的 /dist文件夹将与/frontend具有相同的级别。现在您可以运行 $ npm run build 来创建一个包。

gIwSvo62o6.png!large

Back-end

我将使用python 3.6来进行flask 应用程序开发. 在根目录 /flaskvue下创建一个子目录来放后端代码, 并在子目录中初始化一个虚环境: 

$ mkdir backend
$ cd backend
$ virtualenv -p python3 venv

执行下面的命令来激活虚环境 (macOs操作系统):

$ source venv/bin/activate

在windows中激活虚环境请参考此文档 docs.

在虚环境中安装flask:

(venv) pip install Flask

现在我们开始开发flask应用程序. 在根目录下创建run.py 文件:

(venv) cd ..
(venv) touch run.py

将下面代码添加到这个文件中:

from flask import Flask, render_template
app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")
@app.route('/')
def index():
    return render_template("index.html")

这段代码与Flask starter Hello world 代码略有不同。主要的不同之处在于,我们指定了静态和模板文件夹来用前端包指向 /dist 文件夹,在根文件夹中运行 Flask 服务:

(venv) FLASK_APP=run.py FLASK_DEBUG=1 flask run

这将在 localhost:5000 上启动一个web服务器。FLASK_APP 指向服务器启动文件,FLASK_DEBUG=1 将在调试模式下运行。如果一切都是正确的,您将看到熟悉的主页,您在Vue上所做的。

与此同时,如果你试图添加一个 /about 页面。 Flask 将抛出一个页面未找到的错误。 确实如此,因为我们在 vue-router 中使用了 HTML5 历史模式,我们需要去 配置我们的服务器 让所有路由跳转到 index.html. 这个在 Flask 中很容易做到 。将现有的路由修改为如下内容:

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    return render_template("index.html")

新的 URL 链接 localhost:5000/about 将会跳转到 index.html ,并且 vue-router 将会自己处理其余的事情。

添加 404 页面

因为我们定义了一个将所有请求跳转到 index.html 的路由,因此 Flask 将无法捕获到 404 错误(以及不存在的页面),将一些找不到页面的请求也跳转到 index.html。所以我们需要在 Vue.js 的路由文件中设置一条路由规则去处理这种情况。

在 frontend/src/router/index.js 中添加一行:

const routerOptions = [
  { path: '/', component: 'Home' },
  { path: '/about', component: 'About' },
  { path: '*', component: 'NotFound' }
]

这里的 '*' 是 vue-router 中的通配符,用以代表任何除了我们已经定义好的路由之外的其他情况。 接下来我们在 /components 文件夹中创建一个 NotFound.vue 文件,并写几行简单的代码:

// NotFound.vue
<template>
  <div>
    <p>404 - Not Found</p>
  </div>
</template>

现在通过运行 npm run dev 来重新运行前端服务器,并尝试一些不存在的 URL 链接,例如 localhost:8080/gljhewrgoh 。你就可以看到“Not Found”的消息提示了.

添加API端点

我的 'Vue.js/Flask'的最后一个例子。 'Vue.js/Flask'教程将在服务器端创建API并在客户端发送。我将创建一个简单的端点,它将返回一个从1到100的随机数。

打开 run.py 并添加:

from flask import Flask, render_template, jsonify
from random import *
app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")
@app.route('/api/random')
def random_number():
    response = {
        'randomNumber': randint(1, 100)
    }
    return jsonify(response)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    return render_template("index.html")

首先,我从'Flask'库导入了'random'库和'jsonify'函数。然后我添加了新的路由' /api/random '来返回JSON,如下所示:

{
  "randomNumber": 36
}

您可以通过导航到localhost:5000/api/random来测试此路由。

此时,服务器端工作已经完成。是时候在客户端展示了。我会改Home.vue组成来显示我的随机数:

<template>
  <div>
    <p>Home page</p>
    <p>Random number from backend: {{ randomNumber }}</p>
    <button @click="getRandom">New random number</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      randomNumber: 0
    }
  },
  methods: {
    getRandomInt (min, max) {
      min = Math.ceil(min)
      max = Math.floor(max)
      return Math.floor(Math.random() * (max - min + 1)) + min
    },
    getRandom () {
      this.randomNumber = this.getRandomInt(1, 100)
    }
  },
  created () {
    this.getRandom()
  }
}
</script>

在这个阶段,我只是在客户端模拟随机数生成过程。所以,这个组件是这样工作的:

  • 初始化变量 randomNumber等于 0
  • 在 methods部分 ,我们又 getRandomInt(min, max) 方法, 它将返回一个指定范围内的数字, getRandom 函数,将调度之前的函数,并将其值赋给 randomNumber
  • 创建组件方法后,将调用 getRandom 来初始化 randomNumber
  • 触发按钮事件后,我们将调用 getRandom 获取新数字

在前端,现在在首页你应该看到我们的随机数产生。让我们把它连接到后端。
为此,我们将使用' axios'库,它允许我们发出HTTP请求并返回带有JSON响应的JavaScriptPromise。让我们安装它:

(venv) cd frontend
(venv) npm install --save axios

再次打开 Home.vue 文件并 在 <script> 区域添加一些更改:

import axios from 'axios'
methods: {
  getRandom () {
    // this.randomNumber = this.getRandomInt(1, 100)
    this.randomNumber = this.getRandomFromBackend()
  },
  getRandomFromBackend () {
    const path = `http://localhost:5000/api/random`
    axios.get(path)
    .then(response => {
      this.randomNumber = response.data.randomNumber
    })
    .catch(error => {
      console.log(error)
    })
  }
}

在最开始我们导入axios库.然后有一个新方法getrandomfrombackend,它将使用AXIOS异步访问API并检索结果。最后,方法getRandom现在应该使用getRandomFromBackend函数来获取随机值。

保存文件,转到浏览器中,再次运行开发服务器,刷新localhost:8080然后…您应该在控制台中看到一个错误,并且没有随机值。但别担心,一切都正常。我们得到[cors](http s://developer.mozilla.org/en-us/docs/web/http/cors)错误,这意味着我们的flask服务器API默认关闭到其他Web服务器(在我们的情况下,它是运行vue.js应用程序的node.js服务器)。如果您使用npm run build创建一个bundle并打开localhost:5000(就是flask服务器),您将看到正在工作的应用程序。但是,每次对客户端应用程序进行一些更改时,创建一个包并不十分方便。

让我们使用Flask的CORS插件,这将允许我们为API访问创建规则。 插件名为[flask-cors](http://flask-cors.readthedocs.io/en/latest/),让我们安装它

(venv) pip install -U flask-cors

您可以阅读插件的文档,文档中更好地说明了再服务器上启用CORS的方法。 我将使用特定于资源的方法并将{“origin”“:”*“}应用于所有/ api / *路由(所以每个人都可以使用我的/ api端点)。在run.py中:

from flask_cors import CORS
app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

通过以上更改,您可以直接从前端开发服务器调用Flask API。

更新:

实际上,如果你通过Flask提供静态文件,则不需要更新CORS扩展。 感谢[Carson Gee](https://github.com/carsongee)这个技巧

解决思路如下。 如果应用程序处于调试模式,它将只代理我们的前端服务器。 否则(在生产模式)提供静态文件。 以下是实现的代码:

import requests

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    if app.debug:
        return requests.get('http://localhost:8080/{}'.format(path)).text
    return render_template("index.html")

实现方式简单而优雅,像魔术一样✨!

现在,您拥有一个使用自己喜欢的技术构建的全栈应用程序啦。

Q9mPWiSqp9.png!largeid7NjI9Gdd.png!large

后记

最后,我想就如何改进此解决方案说几句话。

首先,只有在您想要让 API 可供外部服务器访问时才使用 CORS 扩展。 否则只需使用代理前端开发服务器的技巧。

另一项改进是避免在前端硬编码 API 路由。 也许您需要创建一个包含 API 路由名称的词汇集。 因此,当您更改 API 路由时,您只需刷新这个词汇集即可。 前端关于路由名称的代码不需要更改。

通常在开发过程中,您将至少需要两个终端窗口:一个用于 Flask ,另一个用于 Vue.js 。 在生产环境中,你将不需要为 Vue 运行单独的 Node.js 服务器。

源代码:https://github.com/oleg-agapov/flask-vue-s...

感谢您的阅读!

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://codeburst.io/full-stack-single-p...

译文地址:https://learnku.com/python/t/24985

本帖已被设为精华帖!
本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 2
akirago

有没有人按照文档写一遍代码的,我把前端代码打包之后,运行访问失败,提示找不到index.html页面

4年前 评论
pardon110

@QinJin 确保正确配置 frontend/config/index.js build 指向项目目录,而非默认的前端目录。先在 frontend 下构建,后运行flask生产服务即可。这样不需要运行单独的 node 服务,显而易见亦无需配置 debug 环境变量。下面是 windows 平台 示例

cd frontend/
npm run build
...

cd ../backend/
venv\Scripts\activate
cd ../
set Flask_App=run.py
flask run
4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!