前言:css
看了一些国外的关于介绍flask和vue的先后端分离的文章,但没看到比较通俗易懂,代码完善的,昨天看到一篇很新的文章,并且内容很是棒,因此想翻译过来,供你们一块儿学习。html
原文来自Developing a CRUD App with Flask and Vue.js前端
正文:vue
下面会逐步演示经过Flask和Vue如何完成一个基本的CRUD应用程序。咱们将从搭建框架开始,使用Vue CLI构建一个新的Vue应用程序,而后经过Python和Flask开发的RESTful API执行基本的CRUD操做。node
主要依赖的库包括:python
在文章结束时,你将可以知道:react
Flask是一个简单但功能强大的Python微Web框架,很是适合构建RESTful API。像Sinatra(Ruby)和Express(节点)同样,它很是小并且很灵活,因此你能够先开始一个小型的应用,并在它的基础上根据需求创建更加复杂的应用程序。jquery
若是是第一次使用Flask,能够参考如下两个学习资源:ios
VUE是一个开源JavaScript框架,用于构建用户界面。它采用了React和Angular方面的一些最佳作法。也就是说,与React和Angular相比,它更平易近人,因此初学者能够快速地开始和运用Vue。它一样很强大,提供了建立最新前端应用程序所须要的全部功能。git
有关Vue的更多信息,以及它与React和Angular的各类优缺点,能够参阅如下文章:
第一次用Vue,能够花些时间学习一遍官方的Vue指南。
首先新建一个文件夹:
$ mkdir flask-vue-crud
$ cd flask-vue-crud复制代码
接下来,为这个目录建立一个虚拟环境,建立虚拟环境的方式因不一样的开发环境可能存在不一样。
$ python3.7 -m venv env
$ source env/bin/activate复制代码
安装Flask和和Flask-CORS扩展。
(env)$ pip install Flask==1.0.2 Flask-Cors==3.0.7复制代码
在根目录下新建server文件夹,并在文件夹中建立一个app.py文件:
from flask import Flask, jsonify
from flask_cors import CORS
# configuration
DEBUG = True
# instantiate the app
app = Flask(__name__)
app.config.from_object(__name__)
# enable CORS
CORS(app, resources={r'/*': {'origins': '*'}})
# sanity check route
@app.route('/ping', methods=['GET'])
def ping_pong():
return jsonify('pong!')
if __name__ == '__main__':
app.run()复制代码
为何要用Flask-CORS扩展呢?是为了发出跨域请求——好比,来自不一样协议,IP地址,域名或端口的请求——而Flask-CORS能够帮咱们处理这些。
须要注意的是,虽然上面的设置容许全部路由上的跨域请求(来自任何域、协议或端口)。但在生产环境中,您应该只容许来自托管前端应用程序的域的跨域请求。有关此问题的更多信息,请参阅 Flask-CORS文档。
运行app:
(env)$ python server/app.py复制代码
如今能够用浏览器登陆http://localhost:5000/ping来测试了,你会看到一个json格式的
"pong!"复制代码
回到终端中,按Ctrl+C键关闭服务器。如今,咱们就能够把注意力转向前端,开始设置Vue。
咱们将使用强大的Vue CLI工具来生成一个自定义项目样板。
在全局内安装Vue CLI:
$ npm install -g @vue/cli@3.7.0复制代码
第一次使用npm,能够查阅 About npm指南。
安装完成后,用下面的命令来初始化一个名为client的Vue项目:
$ vue create client复制代码
接下来,须要回答一些关于项目的问题。在此项目中,具体的选择以下所示:
Vue CLI v3.7.0
? Please pick a preset: Manually select features
? Check the features needed for your project:
◉ Babel
◯ TypeScript
◯ Progressive Web App (PWA) Support
❯◉ Router
◯ Vuex
◯ CSS Pre-processors
◉ Linter / Formatter
◯ Unit Testing
◯ E2E Testing
? 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复制代码
等待建立完毕后,项目根目录下多出一个client文件夹。里面有不少的内容,不过咱们只须要处理其中‘src’文件夹内的一些内容以及‘public’文件夹内的index.html文件,其余的不须要咱们操做。
index.html文件是Vue应用的起点。
src文件夹内的文件结构以下:
├── App.vue
├── assets
│ └── logo.png
├── components
│ └── HelloWorld.vue
├── main.js
├── router.js
└── views
├── About.vue
└── Home.vue复制代码
详解:
打开/client/src/components/HelloWorld.vue文件。这是一个单文件组件,它包括三个部分:
如今开始运行开发服务器:
$ cd client
$ npm run serve复制代码
为了简化项目,咱们能够删除views文件夹,而后添加一个名为ping.vue的文件到Client/src/Components文件夹下。
<template>
<div>
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
name: 'Ping',
data() {
return {
msg: 'Hello!',
};
},
};
</script>复制代码
而后更新Client/src/router.js,将“/ping”映射到ping组件,以下所示:
import Vue from 'vue';
import Router from 'vue-router';
import Ping from './components/Ping.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/ping',
name: 'Ping',
component: Ping,
}
],
});复制代码
最后,删除Client/src/App.vue中template部分的导航栏,变为以下所示:
<template>
<div id="app">
<router-view/>
</div>
</template>复制代码
如今,你能够经过浏览器登陆http://localhost:8080/ping看到 hello! 了。
要将客户端Vue应用程序与后端Flask应用程序链接起来,咱们可使用axios库发送Ajax请求。
首先安装对应库:
$ npm install axios@0.18.0 --save复制代码
在ping.vue中更新组件的script部分,以下所示:
<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>复制代码
在一个新的终端窗口中启动Flask应用程序。你能够看到http://localhost:8080/ping页面再也不是hello!而是pong!。实际上,当从后端返回响应时,咱们将上面的msg设置为来自服务器响应对象的data的值。
接下来,让咱们将一个流行的CSS框架Bootstrap添加到应用程序中,这样咱们就能够快速地添加一些样式。
安装:
$ npm install bootstrap@4.3.1 --save 复制代码
忽略jquery和popper.js的warnings警告。不要将它们添加到项目中。后面会详细的解释。
将Bootstrap中的样式导入到Client/src/main.js:
import 'bootstrap/dist/css/bootstrap.css';
import Vue from 'vue';
import App from './App.vue';
import router from './router';
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App),
}).$mount('#app');复制代码
更新Client/src/App.vue中的style部分:
<style>
#app {
margin-top: 60px
}
</style>复制代码
经过使用ping组件中的Button和Container,确保Bootstrap能正确链接:
<template>
<div class="container">
<button type="button" class="btn btn-primary">{{ msg }}</button>
</div>
</template>复制代码
运行服务器:
$ npm run serve复制代码
你能够看到:
接下来,新建一个Books.vue的新文件,并在其中添加一个Books组件:
<template>
<div class="container">
<p>books</p>
</div>
</template>复制代码
更新路由文件router.js:
import Vue from 'vue';
import Router from 'vue-router';
import Books from './components/Books.vue';
import Ping from './components/Ping.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'Books',
component: Books,
},
{
path: '/ping',
name: 'Ping',
component: Ping,
},
],
});复制代码
最后,让咱们将Bootstrap-styled表单添加到Books
组件:
<template>
<div class="container">
<div class="row">
<div class="col-sm-10">
<h1>Books</h1>
<hr><br><br>
<button type="button" class="btn btn-success btn-sm">Add Book</button>
<br><br>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Read?</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>foo</td>
<td>bar</td>
<td>foobar</td>
<td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-warning btn-sm">Update</button>
<button type="button" class="btn btn-danger btn-sm">Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>复制代码
你如今应该看到:
如今咱们能够开始构建CRUD应用程序的功能了。
咱们的目标是为books设计一个后端RESTful API,由Python和Flask实现。API自己应该遵循RESTful设计原则,而且可使用基本的HTTP功能:GET、POST、PUT和DELETE。
咱们还将在后端API的基础上使用Vue搭建完整的前端应用:
本教程只讨论快乐的构建之路,处理错误是一个单独的练习。能够尝试经过您的理解,本身在前端和后端添加适当的错误处理。
向server/app.py添加书籍列表:
BOOKS = [
{
'title': 'On the Road',
'author': 'Jack Kerouac',
'read': True
},
{
'title': 'Harry Potter and the Philosopher\'s Stone', 'author': 'J. K. Rowling', 'read': False }, { 'title': 'Green Eggs and Ham', 'author': 'Dr. Seuss', 'read': True } ]复制代码
添加路由处理程序:
@app.route('/books', methods=['GET'])
def all_books():
return jsonify({
'status': 'success',
'books': BOOKS
})复制代码
运行flask app,并测试路由http://localhost:5000/books.
想要进行更多的挑战吗?能够为这个程序编写一个自动测试。查看 这儿有更多关于测试Flask应用的资源信息。
更新books组件:
<template>
<div class="container">
<div class="row">
<div class="col-sm-10">
<h1>Books</h1>
<hr><br><br>
<button type="button" class="btn btn-success btn-sm">Add Book</button>
<br><br>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Read?</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(book, index) in books" :key="index">
<td>{{ book.title }}</td>
<td>{{ book.author }}</td>
<td>
<span v-if="book.read">Yes</span>
<span v-else>No</span>
</td>
<td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-warning btn-sm">Update</button>
<button type="button" class="btn btn-danger btn-sm">Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
books: [],
};
},
methods: {
getBooks() {
const path = 'http://localhost:5000/books';
axios.get(path)
.then((res) => {
this.books = res.data.books;
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
});
},
},
created() {
this.getBooks();
},
};
</script>复制代码
初始化组件后,经过建立的生命周期钩子来调用getBooks( )方法,该方法从咱们刚刚设置的后端端点获取书籍。
查看 实例的生命周期钩子能够了解更多有关组件生命周期和可用方法的信息。
在模板中,咱们经过v-for指令遍历图书列表,在每次迭代中建立一个新的表行。索引值当作key使用。最后,v-if用于呈现“yes”或“no”,指示用户是否已读过书
在下一节中,咱们将使用一个模式添加一本新书。咱们将为此添加一个Bootstrap Vue库,它提供了一组使用基于引导的HTML和CSS样式的Vue组件。
为何要使用Bootstrap Vue库?Bootstrap的 modal组件使用的是 jQuery,所以,您应该避免在同一个项目中Bootstrap与Vue一块儿使用,由于Vue使用的是 虚拟DOM来更新DOM。换句话说,若是您使用jQuery操做DOM,Vue将没法知道这些操做。若是您必定要使用jQuery,至少不要在同一个DOM元素上同时使用Vue和jQuery。
安装:
$ npm install bootstrap-vue@2.0.0-rc.19 --save复制代码
在Client/src/main.js中启用Bootstrap Vue库:
import 'bootstrap/dist/css/bootstrap.css';
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');复制代码
更新如今的路由处理程序,让它支持处理POST请求,从而添加新的书籍:
from flask import Flask, jsonify, request
@app.route('/books', methods=['GET', 'POST'])
def all_books():
response_object = {'status': 'success'}
if request.method == 'POST':
post_data = request.get_json()
BOOKS.append({
'title': post_data.get('title'),
'author': post_data.get('author'),
'read': post_data.get('read')
})
response_object['message'] = 'Book added!'
else:
response_object['books'] = BOOKS
return jsonify(response_object)复制代码
当Flask服务器运行时,您能够在一个新的终端选项卡中测试POST路由的功能:
$ curl -X POST http://localhost:5000/books -d \ '{"title": "1Q84", "author": "Haruki Murakami", "read": "true"}' \ -H 'Content-Type: application/json'
你能够看到:
{ "message": "Book added!", "status": "success" }
您还能够经过访问http://localhost:5000/books端点查看响应中的是否成功添加了新书。
若是标题已经存在怎么办?或者,若是一个标题有一个以上的做者呢?你能够本身尝试解决这些问题来检测你的知识理解。还有,如何处理无效的数据体呢,好比在缺乏title、author或read的状况下?
让咱们如今在客户端添加POST模式,以便将新书添加到Books组件中,先从HTML开始:
<b-modal ref="addBookModal"
id="book-modal"
title="Add a new book"
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="addBookForm.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="addBookForm.author"
required
placeholder="Enter author">
</b-form-input>
</b-form-group>
<b-form-group id="form-read-group">
<b-form-checkbox-group v-model="addBookForm.read" id="form-checks">
<b-form-checkbox value="true">Read?</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>复制代码
将它添加到最后结束的dev标签以前。能够查看代码。v-model
是一个用于将输入值绑定到对应状态的指令。你很快就会看到这一点。
hide-footer
有什么用?想了解的话,能够在Bootstrap Vue文档中查看这个对应
文档。
更新script
部分:
<script>
import axios from 'axios';
export default {
data() {
return {
books: [],
addBookForm: {
title: '',
author: '',
read: [],
},
};
},
methods: {
getBooks() {
const path = 'http://localhost:5000/books';
axios.get(path)
.then((res) => {
this.books = res.data.books;
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
});
},
addBook(payload) {
const path = 'http://localhost:5000/books';
axios.post(path, payload)
.then(() => {
this.getBooks();
})
.catch((error) => {
// eslint-disable-next-line
console.log(error);
this.getBooks();
});
},
initForm() {
this.addBookForm.title = '';
this.addBookForm.author = '';
this.addBookForm.read = [];
},
onSubmit(evt) {
evt.preventDefault();
this.$refs.addBookModal.hide();
let read = false;
if (this.addBookForm.read[0]) read = true;
const payload = {
title: this.addBookForm.title,
author: this.addBookForm.author,
read, // property shorthand
};
this.addBook(payload);
this.initForm();
},
onReset(evt) {
evt.preventDefault();
this.$refs.addBookModal.hide();
this.initForm();
},
},
created() {
this.getBooks();
},
};
</script>复制代码
这段代码作了什么?
您能发现客户端或服务器上的潜在错误吗?自行处理这些以提升用户体验。
最后,更新模板中的“AddBook”按钮,以便在单击按钮时显示modal:
<button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>复制代码
完整的组件代码如今应该以下所示:
<template>
<div class="container">
<div class="row">
<div class="col-sm-10">
<h1>Books</h1>
<hr><br><br>
<button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>
<br><br>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Read?</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(book, index) in books" :key="index">
<td>{{ book.title }}</td>
<td>{{ book.author }}</td>
<td>
<span v-if="book.read">Yes</span>
<span v-else>No</span>
</td>
<td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-warning btn-sm">Update</button>
<button type="button" class="btn btn-danger btn-sm">Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<b-modal ref="addBookModal"
id="book-modal"
title="Add a new book"
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="addBookForm.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="addBookForm.author"
required
placeholder="Enter author">
</b-form-input>
</b-form-group>
<b-form-group id="form-read-group">
<b-form-checkbox-group v-model="addBookForm.read" id="form-checks">
<b-form-checkbox value="true">Read?</b-form-checkbox>
</b-form-checkbox-group>
</b-form-group>
<b-button-group>
<b-button type="submit" variant="primary">Submit</b-button>
<b-button type="reset" variant="danger">Reset</b-button>
</b-button-group>
</b-form>
</b-modal>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
books: [],
addBookForm: {
title: '',
author: '',
read: [],
},
};
},
methods: {
getBooks() {
const path = 'http://localhost:5000/books';
axios.get(path)
.then((res) => {
this.books = res.data.books;
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
});
},
addBook(payload) {
const path = 'http://localhost:5000/books';
axios.post(path, payload)
.then(() => {
this.getBooks();
})
.catch((error) => {
// eslint-disable-next-line
console.log(error);
this.getBooks();
});
},
initForm() {
this.addBookForm.title = '';
this.addBookForm.author = '';
this.addBookForm.read = [];
},
onSubmit(evt) {
evt.preventDefault();
this.$refs.addBookModal.hide();
let read = false;
if (this.addBookForm.read[0]) read = true;
const payload = {
title: this.addBookForm.title,
author: this.addBookForm.author,
read, // property shorthand
};
this.addBook(payload);
this.initForm();
},
onReset(evt) {
evt.preventDefault();
this.$refs.addBookModal.hide();
this.initForm();
},
},
created() {
this.getBooks();
},
};
</script>复制代码
试试看!试着增长一本书:
接下来,让咱们添加一个Alert组件,这样在添加新书后,就能够向用户显示一条提示消息。咱们将为此单首创建一个新组件,由于您可能会在许多组件中使用这一功能。
向“Client/src/Components”添加一个名为Alert.vue的新文件:
<template>
<p>It works!</p>
</template>复制代码
而后,将其导入Books组件的Script部分,并注册该组件:
<script>
import axios from 'axios';
import Alert from './Alert.vue';
...
export default {
data() {
return {
books: [],
addBookForm: {
title: '',
author: '',
read: [],
},
};
},
components: {
alert: Alert,
},
...
};
</script>复制代码
如今,咱们能够在template部分引用新组件:
<template>
<b-container>
<b-row>
<b-col col sm="10">
<h1>Books</h1>
<hr><br><br>
<alert></alert>
<button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>
...
</b-col>
</b-row>
</b-container>
</template> 复制代码
刷新浏览器。你如今能够看到:
有关在其余组件中使用某一组件的更多信息,能够查看官方vue文档的 Composing with Components部分。
接下来,让咱们将b-alert组件添加到template中::
<template>
<div>
<b-alert variant="success" show>{{ message }}</b-alert>
<br>
</div>
</template>
<script>
export default {
props: ['message'],
};
</script>复制代码
注意脚本部分中的props选项。咱们能够从父组件(Books)传递消息,以下所示:
<alert message="hi"></alert>复制代码
试试效果:
查看 docs以得到更多关于props的信息
要使其具备动态,以便传递自定义消息,能够在Books.vue中使用绑定表达式:
<alert :message="message"></alert>复制代码
将message消息添加到Books.vue中的data选项中:
data() {
return {
books: [],
addBookForm: {
title: '',
author: '',
read: [],
},
message: '',
};
},复制代码
而后,在addBook
中,更新消息:
addBook(payload) {
const path = 'http://localhost:5000/books';
axios.post(path, payload)
.then(() => {
this.getBooks();
this.message = 'Book added!';
})
.catch((error) => {
// eslint-disable-next-line
console.log(error);
this.getBooks();
});
},复制代码
最后,添加一个v-if
,所以只有在showMessage为true的时才会有提示消息:
<alert :message=message v-if="showMessage"></alert>复制代码
将showMessage添加到data中:
data() {
return {
books: [],
addBookForm: {
title: '',
author: '',
read: [],
},
message: '',
showMessage: false,
};
},复制代码
再次更新addBook,并将showMessage设置为true:
addBook(payload) {
const path = 'http://localhost:5000/books';
axios.post(path, payload)
.then(() => {
this.getBooks();
this.message = 'Book added!';
this.showMessage = true;
})
.catch((error) => {
// eslint-disable-next-line
console.log(error);
this.getBooks();
});
},复制代码
再试试效果怎么样!
挑战:
对于更新,咱们须要使用惟一的标识符,由于咱们不能期待全部标题是惟一的。咱们可使用Python标准库中的UUID。
更新server/app.py中的书籍:
import uuid
BOOKS = [
{
'id': uuid.uuid4().hex,
'title': 'On the Road',
'author': 'Jack Kerouac',
'read': True
},
{
'id': uuid.uuid4().hex,
'title': 'Harry Potter and the Philosopher\'s Stone', 'author': 'J. K. Rowling', 'read': False }, { 'id': uuid.uuid4().hex, 'title': 'Green Eggs and Ham', 'author': 'Dr. Seuss', 'read': True } ]复制代码
在添加新书时,重构All_Books以添加惟一的id:
@app.route('/books', methods=['GET', 'POST'])
def all_books():
response_object = {'status': 'success'}
if request.method == 'POST':
post_data = request.get_json()
BOOKS.append({
'id': uuid.uuid4().hex,
'title': post_data.get('title'),
'author': post_data.get('author'),
'read': post_data.get('read')
})
response_object['message'] = 'Book added!'
else:
response_object['books'] = BOOKS
return jsonify(response_object)复制代码
添加一个新的路由处理程序:
@app.route('/books/<book_id>', methods=['PUT'])
def single_book(book_id):
response_object = {'status': 'success'}
if request.method == 'PUT':
post_data = request.get_json()
remove_book(book_id)
BOOKS.append({
'id': uuid.uuid4().hex,
'title': post_data.get('title'),
'author': post_data.get('author'),
'read': post_data.get('read')
})
response_object['message'] = 'Book updated!'
return jsonify(response_object)复制代码
添加辅助方法:
def remove_book(book_id):
for book in BOOKS:
if book['id'] == book_id:
BOOKS.remove(book)
return True
return False复制代码
花点时间思考一下,您将如何处理id不存在时的状况?若是数据体不正确怎么办?此外,还能够尝试着重构辅助方法中的for循环,使其更加Pythonic。
步骤:
首先,在template中添加一个新的modal,写在第一个modal的下面:
<b-modal ref="editBookModal"
id="book-update-modal"
title="Update"
hide-footer>
<b-form @submit="onSubmitUpdate" @reset="onResetUpdate" class="w-100">
<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.read" id="form-checks">
<b-form-checkbox value="true">Read?</b-form-checkbox>
</b-form-checkbox-group>
</b-form-group>
<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>
</b-modal> 复制代码
将表单状态添加到script部分的data中:
editForm: {
id: '',
title: '',
author: '',
read: [],
},复制代码
挑战:尝试使用相同的modal来处理POST和PUT请求,而不是使用新的modal。
更新表格中的“更新”按钮功能:
<button
type="button"
class="btn btn-warning btn-sm"
v-b-modal.book-update-modal
@click="editBook(book)">
Update
</button>复制代码
添加一个新方法来更新editForm:
editBook(book) {
this.editForm = book;
},复制代码
而后,添加一个方法用来处理表单的提交:
onSubmitUpdate(evt) {
evt.preventDefault();
this.$refs.editBookModal.hide();
let read = false;
if (this.editForm.read[0]) read = true;
const payload = {
title: this.editForm.title,
author: this.editForm.author,
read,
};
this.updateBook(payload, this.editForm.id);
},复制代码
updateBook(payload, bookID) {
const path = `http://localhost:5000/books/${bookID}`;
axios.put(path, payload)
.then(() => {
this.getBooks();
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
this.getBooks();
});
},复制代码
更新updateBook:
updateBook(payload, bookID) {
const path = `http://localhost:5000/books/${bookID}`;
axios.put(path, payload)
.then(() => {
this.getBooks();
this.message = 'Book updated!';
this.showMessage = true;
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
this.getBooks();
});
},复制代码
添加方法:
onResetUpdate(evt) {
evt.preventDefault();
this.$refs.editBookModal.hide();
this.initForm();
this.getBooks(); // why?
},复制代码
更新initForm
:
initForm() {
this.addBookForm.title = '';
this.addBookForm.author = '';
this.addBookForm.read = [];
this.editForm.id = '';
this.editForm.title = '';
this.editForm.author = '';
this.editForm.read = [];
},复制代码
在继续以前,必定要检查代码。完成后,测试应用程序。确保在点击按钮时modal可以显示而且输入框中填充的值是正确的。
更新路由处理程序:
@app.route('/books/<book_id>', methods=['PUT', 'DELETE'])
def single_book(book_id):
response_object = {'status': 'success'}
if request.method == 'PUT':
post_data = request.get_json()
remove_book(book_id)
BOOKS.append({
'id': uuid.uuid4().hex,
'title': post_data.get('title'),
'author': post_data.get('author'),
'read': post_data.get('read')
})
response_object['message'] = 'Book updated!'
if request.method == 'DELETE':
remove_book(book_id)
response_object['message'] = 'Book removed!'
return jsonify(response_object)复制代码
更新“删除”按钮以下:
<button
type="button"
class="btn btn-danger btn-sm"
@click="onDeleteBook(book)">
Delete
</button>复制代码
添加删除按钮:
removeBook(bookID) {
const path = `http://localhost:5000/books/${bookID}`;
axios.delete(path)
.then(() => {
this.getBooks();
this.message = 'Book removed!';
this.showMessage = true;
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
this.getBooks();
});
},
onDeleteBook(book) {
this.removeBook(book.id);
},复制代码
如今,当用户单击“删除”按钮时,onDeleteBook方法被触发,接着触发removeBook方法。此方法将DELETE请求发送到后端。当响应返回时,显示提示消息并运行getBooks。
挑战:
这篇文章涵盖了使用Vue和Flask设置CRUD应用程序的基础知识。
能够检查一下本身的学习效果,从这篇文章开始回顾,并完成其中的每个挑战。
若是想更多了解,能够查看具体的源码,源码地址为flask-vue-crud。
感谢您的阅读。