使用 Flask 和 Vue 搭建在线商店
作者:拉斐尔·乌古✏️
在本教程中,我们将学习如何使用Vue和Flask (一个用 Python 编写的微型 Web 框架)构建一个电子商务网站。我们的网站将能够通过 Flask 支持的服务器端 RESTful API 来创建、读取、更新和删除内容。
应用要求
如果您具备 Vue 和 Python 的基础知识,并希望学习如何使用各种框架构建炫酷的应用,那么这里将是您理想的起点。我们的应用将主要依赖以下框架和库:
Vue:一种用于构建用户界面的渐进式框架Vue CLIVue 的命令行界面为快速 Vue JS 开发提供了一个强大的系统。NodeJavaScript 的运行时环境,用于在浏览器之外执行 JavaScript 代码。npmNodeJS 是 JavaScript 运行时环境的默认包管理器。Flask一个用 Python 编写的微型 Web 框架Python一种用于开发各种应用程序的通用编程语言virtualenv一个用于在 Python 及其相关框架中创建虚拟环境的模块
首先,您应该检查您的计算机上是否已安装 Python 和 virtualenv。如果没有,您可以点击此处了解更多信息:
设置 Flask
在安装 Flask 之前,我们首先通过终端创建项目目录:
$ mkdir vue-flask-store
$ cd vue-flask-store
在 中vue-flask-store,创建一个新目录,并将其命名为server。然后使用 virtualenvvenv命令在 中创建一个虚拟环境vue-flask-store:
$ python3.7 -m venv env
$ source env/bin/activate
现在,安装 Flask 以及Flask-CORS扩展,这将有助于我们处理跨域请求:
(env)$ pip install Flask Flask-Cors
安装完成后,您应该会看到类似这样的文件夹结构:
├── VUE-FLASK-STORE
└── env
├── bin
├── include
├── lib / python 3.7 / site-packages
├── pyvenv.cfg
└── server (env)$ pip install Flask Flask-Cors
设置 Vue
我们将使用 Vue CLI 创建一个 Vue 项目。打开终端并全局安装 Vue CLI:
$ npm install -g @vue/cli
然后,仍然在终端中,导航到vue-flask-store项目目录并初始化一个新的 Vue 项目,我们将其命名为 `<project_name>` frontend。当提示从一系列选项中进行选择时,请按照以下格式操作:
Vue CLI v3.7.0
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Linter
? Use history mode for router? Yes
? Pick a linter / formatter config: Airbnb
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? (y/N) No
完成后,我们将得到一个生成的 Vue 项目,其文件夹结构类似于这样:
├── VUE-FLASK-STORE
└── frontend
└── public
├── index.html
└── src
└── assets
├── logo.png
└── components
├── HelloWorld.vue
└── router
├── index.js
├── App.vue
├── main.js
├── .editorconfig
├── .gitignore
├── .babel.config.js
├── package-lock.json
├── package.json
└── server
在终端中,导航至frontend并启动开发服务器:
$ cd client
$ npm run serve
然后,在浏览器中访问http://localhost:8080。您应该会看到类似这样的页面:
为了将前端(我们的 Vue 应用)发出的请求连接到后端(我们的 Flask 应用),我们需要axios在项目中包含该库。仍然在终端中,axios在以下文件夹中安装frontend:
$ npm install axios --save
最后,为了添加一些样式,我们将引入bootstrap-vueBootstrap(VueJS 的一个实现)。安装方式与之前bootstrap-vue相同axios:
$ npm install bootstrap-vue --save
然后通过导入来启用它frontend/src/main.js:
// frontend/src/main.js
import BootstrapVue from "bootstrap-vue";
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
Vue.use(BootstrapVue);
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App)
}).$mount("#app");
至此,我们已经拥有了所有需要的依赖项,接下来就可以构建构成我们应用程序的所有路由了。
构建应用程序
我们的应用程序开发完成后,界面应该类似于这样:
我们想搭建一个提供 JavaScript 课程的电商网站。用户应该能够执行以下操作:
- 选择他们是否需要课程的纸质版。
- 从课程列表中删除课程
- 添加他们想要购买但不在默认列表中的课程(我们将在 Flask 服务器中创建该列表)。
在 Flask 中创建服务器
第一步是在我们的 Flask 应用中创建一个服务器。该服务器将包含应用中的默认课程列表,以及用户可能想要访问的任何路由的处理程序。导航到serverFlask 应用的 `<app_name>` 文件夹,并创建一个名为 `<app_name>` 的文件app.py。在 `<app_name>` 文件中,我们首先要app.py添加默认课程列表:
// server/app.py
COURSES = [
{
'title': 'Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript ',
'author': 'David Herman',
'paperback': True
},
{
'title': 'JavaScript: The Good Parts',
'author': 'Douglas Crockford',
'paperback': False
},
{
'title': 'Eloquent JavaScript: A Modern Introduction to Programming',
'author': 'Marijn Haverbeke',
'paperback': True
}
]
然后我们将添加一个路由处理程序,将路由返回到此列表:
from flask import Flask, jsonify
@app.route('/courses', methods=['GET'])
def all_courses():
return jsonify({
'status': 'success',
'courses': COURSES
})
打开终端,输入以下命令运行 Flask 应用(在你的虚拟环境中):
(env) server % python app.py
在浏览器中,测试一下http://localhost:5000/courses这个路由。你应该会看到类似这样的页面:
更新 Vue 应用
服务器创建完成后,下一步是将服务器内容更新到前端。在src/componentsVue 应用的文件夹中,创建一个名为 `.vue.js` 的文件Courses.vue。在这里,我们将创建一个模板,其样式由 `.vue.js` 中的类定义bootstrap-vue。我们还将使用Vue 指令来处理一些逻辑:
<!-- src/components/Courses.vue -->
<template>
<div class="container">
<h1>Courses</h1>
<hr>
<button type="button" class="btn btn-success btn-sm">Add Course</button>
<table class="table table-hover">
<tr>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Paperback</th>
</tr>
<tbody>
<tr v-for="(course, index) in courses" :key="index">
<td>{{ course.title }}</td>
<td>{{ course.author }}</td>
<td>
<span v-if="course.paperback">Yes</span>
<span v-else>No</span>
</td>
<td>
<button type="button" class="btn btn-info btn-sm">Update</button>
<button type="button" class="btn btn-danger btn-sm">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
在模板中,v-for`is` 用于遍历课程列表,并将index值作为键。然后,v-if`is` 用于渲染Yes或No显示选项,以指示用户是否需要纸质版课程。
在script组件的这一部分,我们将创建一个名为 ` getCourses()where`的方法axios,用于向我们的服务器发出GET请求。然后,我们将使用 Vue 的created生命周期钩子从我们用 Flask 构建的服务器获取课程:
<!-- src/components/Courses.vue -->
<script>
import axios from 'axios';
export default {
data() {
return {
courses: [],
};
},
methods: {
getCourses() {
const path = 'http://localhost:5000/courses';
axios.get(path)
.then((res) => {
this.courses = res.data.courses;
})
.catch((error) => {
console.error(error);
});
},
},
created() {
this.getCourses();
},
};
</script>
保存后Course.vue,在终端中运行npm run serve。你应该会看到一个与此非常相似的界面:
请注意,除了按钮之外,我们已经解释了所有内容Add Course。我们将在下一节中处理按钮,届时我们将学习如何修改服务器和前端,以处理用户添加新课程的请求。
修改服务器以处理 POST 请求
用户要向现有课程列表添加课程,POST需要向服务器发送请求。我们需要修改服务器server/app.py以接受这些请求。在 `<path>` 标签中app.py,更新现有的路由处理程序all_courses以处理POST添加新课程的请求:
from flask import Flask, jsonify, request
@app.route('/courses', methods=['GET', 'POST'])
def all_courses():
response_object = {'status': 'success'}
if request.method == 'POST':
post_data = request.get_json()
COURSES.append({
'title': post_data.get('title'),
'author': post_data.get('author'),
'paperback': post_data.get('paperback')
})
response_object['message'] = 'Course added!'
else:
response_object['courses'] = COURSES
return jsonify(response_object)
接下来,Courses我们将在组件中添加一个模态框到前端,使用户能够将课程添加到默认列表中:
<!-- src/components/Courses.vue -->
<template>
<b-modal ref="addCourseModal"
id="course-modal"
title="Add a new course"
hide-footer>
<b-form @submit="onSubmit" @reset="onReset" class="w-100">
<b-form-group id="form-title-group"
label="Title:"
label-for="form-title-input">
<b-form-input id="form-title-input"
type="text"
v-model="addCourseForm.title"
required
placeholder="Enter title">
</b-form-input>
</b-form-group>
<b-form-group id="form-author-group"
label="Author:"
label-for="form-author-input">
<b-form-input id="form-author-input"
type="text"
v-model="addCourseForm.author"
required
placeholder="Enter author">
</b-form-input>
</b-form-group>
<b-form-group id="form-read-group">
<b-form-checkbox-group v-model="addCourseForm.paperback" id="form-checks">
<b-form-checkbox value="true">Paperback</b-form-checkbox>
</b-form-checkbox-group>
</b-form-group>
<b-button type="submit" variant="primary">Submit</b-button>
<b-button type="reset" variant="danger">Reset</b-button>
</b-form>
</b-modal>
</template>
在上面的代码示例中,我们创建了一个模态框,并使用v-model指令将用户输入的任何值绑定到应用程序的状态中。现在,让我们更新代码块,使其包含通过指令绑定到用户输入的script状态。当用户更新输入时,此状态也会随之更新:addCourseFormv-model
<!-- src/components/Courses.vue -->
<script>
import axios from 'axios';
export default {
data() {
return {
courses: [],
addCourseForm: {
title: '',
author: '',
paperback: [],
},
};
},
created() {
this.getCourses();
},
};
</script>
接下来,我们将创建不同的方法来处理用户执行的每个操作。以下是我们将要创建的一些方法以及它们如何帮助我们处理每个操作:
addCourse此方法发送 POST 请求,将/courses新课程添加到现有课程列表中。initForm此方法会将要添加到课程中的所有详细信息附加到addCourseFormonSubmit此方法在用户成功添加课程时执行。首先,通过 `setup()` 阻止浏览器的默认行为e.preventDefault(),然后使用 `close()` 成功关闭用于添加表单的模态框this.refs.addCourseModal.hide(),addCourse执行 `setup()` 方法,并使用 `clear()` 重新初始化并清除表单。initForm()
// src/components/Courses.vue
methods: {
addCourse(payload) {
const path = "http://localhost:5000/courses";
axios
.post(path, payload)
.then(() => {
this.getCourses();
})
.catch(error => {
// eslint-disable-next-line
console.log(error);
this.getCourses();
});
},
initForm() {
this.addCourseForm.title = "";
this.addCourseForm.author = "";
this.addCourseForm.paperback = [];
},
onSubmit(e) {
e.preventDefault();
this.$refs.addCourseModal.hide();
let paperback = false;
if (this.addCourseForm.paperback[0]) paperback = true;
const payload = {
title: this.addCourseForm.title,
author: this.addCourseForm.author,
paperback
};
this.addCourse(payload);
this.initForm();
}
}
完成后,我们来更新Add Course模板中的按钮,以便在点击按钮时显示模态框:
<!-- src/components/Courses.vue -->
<template>
<button type="button" class="btn btn-success btn-sm" v-b-modal.course-modal>
Add Course
</button>
</template>
现在,我们尝试添加一门课程,看看会发生什么:
添加警报组件
如果用户在课程添加、更新或删除等操作发生时能够收到某种形式的提醒或弹出窗口,那就太好了。为了在我们的应用中添加提醒功能,我们首先创建一个名为 `<alert>` 的新组件Alert.vue。在这个组件中,我们将使用 ` <alert> b-alert`(一个内置组件)bootstrap-vue来显示我们希望在提醒弹出时显示的消息:
<!-- src/components/Alert.vue -->
<template>
<div>
<b-alert variant="success" show>{{ message }}</b-alert>
<br />
</div>
</template>
<script>
export default {
props: ["message"]
};
</script>
此时,您可能想知道这里发生了什么。我们指定了一个messageprop,它可以接收来自Courses组件的更新,因为我们将在此处导出和处理Alert组件。在 ` <head>` 标签中Courses.vue,导入Alert组件并将该messageprop 添加到data()对象中:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
data() {
return {
courses: [],
addCourseForm: {
title: "",
author: "",
paperback: []
},
message: "",
};
}
};
</script>
接下来,在addCourse方法中,我们将使用所需内容更新消息:
<!-- src/components/Courses.vue -->
...
addCourse(payload) {
const path = 'http://localhost:5000/courses';
axios.post(path, payload)
.then(() => {
this.getCourses();
this.message = 'Course added!';
})
.catch((error) => {
console.log(error);
this.getCourses();
});
}
...
在该data()对象中,我们将包含另一个属性showMessage,该属性将根据课程是否已添加来决定是否显示提示信息。showMessage该属性的初始布尔值为false:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
data() {
return {
courses: [],
addCourseForm: {
title: "",
author: "",
paperback: []
},
message: "",
showMessage: false,
};
}
};
</script>
然后,在我们的模板中,我们将Alert使用一个指令更新组件,该指令会根据以下两个值之一v-if进行条件渲染:Alerttruefalse
<!-- src/components/Courses.vue -->
<template>
<button>
<alert :message=message v-if="showMessage"></alert>
</button>
</template>
最后,我们将更新该AddCourse方法,并showMessage在每次成功添加课程时将其设置为 true:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
methods: {
addCourse(payload) {
const path = "http://localhost:5000/courses";
axios
.post(path, payload)
.then(() => {
this.getCourses();
this.message = "Course added!";
this.showMessage = true;
})
.catch(error => {
console.log(error);
this.getCourses();
});
}
}
};
</script>
现在,我们来看看是否会显示提示框。保存你的工作,npm run serve在终端运行命令,然后打开浏览器:
设置唯一标识符
我们可能会遇到两个课程名称完全相同的情况。为了解决这个问题,我们需要确保用户每次更新课程信息时都拥有一个唯一的标识符。为此,我们将使用uuidPython 库中的一个模块 `unique_id`,它可以生成唯一的 ID。让我们更新服务器,使所有默认课程都拥有随机 ID:
# server/app.py
from flask import Flask, jsonify, request
import uuid
COURSES = [
{
'id': uuid.uuid4().hex,
'title': 'Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript ',
'author': 'David Herman',
'paperback': True
},
{
'id': uuid.uuid4().hex,
'title': 'JavaScript: The Good Parts',
'author': 'Douglas Crockford',
'paperback': False
},
{
'id': uuid.uuid4().hex,
'title': 'Eloquent JavaScript: A Modern Introduction to Programming',
'author': 'Marijn Haverbeke',
'paperback': True
}
]
接下来,我们将配置all_courses为在用户每次添加新课程时处理随机 ID:
@app.route('/courses', methods=['GET', 'POST'])
def all_courses():
response_object = {'status': 'success'}
if request.method == 'POST':
post_data = request.get_json()
COURSES.append({
'id': uuid.uuid4().hex,
'title': post_data.get('title'),
'author': post_data.get('author'),
'paperback': post_data.get('paperback'),
})
response_object['message'] = 'Course added!'
else:
response_object['courses'] = COURSES
return jsonify(response_object)
更新现有课程
假设我们的用户想要修改应用中已有的课程,我们可以通过创建一个新的模态框Courses.vue来实现。在 `<module>` 中,在`<form>` 下方Courses.vue创建一个名为 `<form>` 的模态框。我们将所有要创建的表单属性都包裹在 `<form>` 下,`<form>`是由 `<form>` 提供的表单元素。我们要创建的第一个输入框属性是课程标题。在这里,我们可以将标题修改为我们想要的内容:editCourseModaladdCourseModalb-formbootstrap-vue
<b-form-group id="form-title-edit-group" label="Title:"
label-for="form-title-edit-input"
>
<b-form-input id="form-title-edit-input" type="text"
v-model="editForm.title"
required
placeholder="Enter title"
>
</b-form-input>
</b-form-group>
接下来,我们将创建一个输入字段,用于更新作者姓名:
<b-form-group id="form-author-edit-group" label="Author:"
label-for="form-author-edit-input"
>
<b-form-input id="form-author-edit-input" type="text"
v-model="editForm.author"
required
placeholder="Enter author"
>
</b-form-input>
</b-form-group>
然后我们将创建一个复选框,用户可以选择是否修改课程是否应以纸质版形式提供:
<b-form-group id="form-read-edit-group">
<b-form-checkbox-group v-model="editForm.paperback" id="form-checks">
<b-form-checkbox value="true">Paperback</b-form-checkbox>
</b-form-checkbox-group>
</b-form-group>
然后我们将创建两个按钮——Update一个用于更新我们所做的更改,另一个Cancel用于在不再需要进行更改时关闭模态框:
<b-button-group>
<b-button type="submit" variant="primary">Update</b-button>
<b-button type="reset" variant="danger">Cancel</b-button>
</b-button-group>
最后,我们将刚刚创建的所有元素包裹在同b-form一个元素中:
<template>
<b-modal ref="editCourseModal" id="course-update-modal" title="Update">
<b-form @submit="onSubmitUpdate" @reset="onResetUpdate" class="w-100"
<!--Place all the elements here-->
</b-form>
</b-modal>
</template>
接下来,在script标签中Courses.vue,我们将更新此模态框的状态:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
data() {
return {
courses: [],
addCourseForm: {
title: "",
author: "",
paperback: []
},
editForm: {
id: "",
title: "",
author: "",
paperback: []
},
message: "",
showMessage: false
};
}
};
</script>
接下来,我们将修改“更新课程”Update和Cancel“删除课程”按钮,使其能够更新课程或从列表中删除课程。首先,对于“更新课程”Update按钮,我们将创建一个方法来更新以下变量的值editForm:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
methods: {
editCourse(course) {
this.editForm = course;
},
}
};
</script>
接下来,我们将创建一个方法来处理包含更新后详细信息的表单的提交:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
methods: {
editCourse(course) {
this.editForm = course;
},
onSubmitUpdate(e) {
e.preventDefault();
this.$refs.editCourseModal.hide();
let paperback = false;
if (this.editForm.paperback[0]) paperback = true;
const payload = {
title: this.editForm.title,
author: this.editForm.author,
paperback
};
this.updateBook(payload, this.editForm.id);
}
}
};
</script>
然后我们将使用axios它来请求更新我们的服务器。我们还会添加一条消息,每次添加书籍时都会以提示的形式显示:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
methods: {
updateCourse(payload, courseID) {
const path = `http://localhost:5000/courses/${courseID}`;
axios
.put(path, payload)
.then(() => {
this.getCourses();
this.message = 'Course updated!';
this.showMessage = true;
})
.catch(error => {
console.error(error);
this.getCourses();
});
}
}
};
</script>
要取消更新,我们将创建一个方法,该方法关闭editForm模态框,重新初始化表单,并发出请求以获取当前课程列表:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
methods: {
onResetUpdate(evt) {
evt.preventDefault();
this.$refs.editBookModal.hide();
this.initForm();
this.getBooks();
}
}
};
</script>
然后我们将更新initForm以包含以下属性editForm:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
methods: {
initForm() {
this.addBookForm.title = "";
this.addBookForm.author = "";
this.addBookForm.read = [];
this.editForm.id = "";
this.editForm.title = "";
this.editForm.author = "";
this.editForm.read = [];
}
}
};
</script>
好了。现在让我们看看更新和更改是否生效:
删除现有课程
要从课程列表中删除现有课程,我们将在 `<section>`script部分创建一个方法Courses.vue。我们的方法应该能够delete通过 `<remove>` 请求axios,根据课程 ID 删除课程,获取当前课程列表,并显示一条提示信息,表明该课程已被删除:
<!-- src/components/Courses.vue -->
<script>
import Alert from "./Alert.vue";
export default {
methods: {
removeCourse(courseID) {
const path = `http://localhost:5000/courses/${courseID}`;
axios
.delete(path)
.then(() => {
this.getCourses();
this.message = " Course removed!";
this.showMessage = true;
})
.catch(error => {
console.error(error);
this.getCourses();
});
},
onDeleteCourse(course) {
this.removeCourse(course.id);
}
}
};
</script>
让我们回顾一下它是如何运作的:
结论
框架的构建旨在为开发者提供更多编程语言的功能和优势。值得庆幸的是,我们已经展示了如果使用得当,这种优势将有多么强大。我们鼓励读者在此基础上进行改进——例如添加购物车、图片或支付方式。您可以在 GitHub 上查看该项目的完整代码。
像用户一样体验您的 Vue 应用
调试 Vue.js 应用可能很困难,尤其是在用户会话期间发生数十甚至数百次变更时。如果您有兴趣监控和跟踪生产环境中所有用户的 Vue 变更,不妨试试 LogRocket。
LogRocket就像 Web 应用的 DVR,它会记录 Vue 应用中发生的一切,包括网络请求、JavaScript 错误、性能问题等等。你无需猜测问题发生的原因,即可汇总并报告问题发生时应用的状态。
LogRocket Vuex 插件会将 Vuex mutations 记录到 LogRocket 控制台,从而提供有关导致错误的原因以及问题发生时应用程序所处状态的上下文信息。
革新您的 Vue 应用调试方式 -开始免费监控。
这篇文章《使用 Flask 和 Vue 搭建在线商店》最初发表在LogRocket 博客上。
文章来源:https://dev.to/bnevilleoneill/setting-up-an-online-store-with-flask-and-vue-3j8j




