[Flask+Vue]Books全栈应用
Flask和Vue.js構(gòu)建全棧單頁面web應(yīng)用【通過Flask開發(fā)RESTful API】
路小飛
退乎(假的)
?關(guān)注
146 人贊同了該文章
前言:
看了一些國外的關(guān)于介紹flask和vue的前后端分離的文章,但沒看到比較通俗易懂,代碼完善的,直到昨天看到一篇新出的文章,而且內(nèi)容非常棒,所以翻譯過來,供大家一起學(xué)習(xí)。
原文來自Developing a CRUD App with Flask and Vue.js
正文:
下面會逐步演示通過Flask和Vue如何完成一個基本的CRUD應(yīng)用程序。我們將從搭建框架開始,使用Vue CLI構(gòu)建一個新的Vue應(yīng)用程序,然后通過Python和Flask開發(fā)的RESTful API執(zhí)行基本的CRUD操作。
主要依賴的庫包括:
- Vue v2.6.10
- Vue CLI v3.7.0
- Node v12.1.0
- npm v6.9.0
- Flask v1.0.2
- Python v3.7.3
本文章的目標(biāo)
在文章結(jié)束時,你將能夠知道:
什么是Flask?
Flask是一個簡單但功能強大的Python微Web框架,非常適合構(gòu)建RESTful API。像Sinatra(Ruby)和Express(Node)一樣,它非常小而且很靈活,所以你可以先開始一個小型的應(yīng)用,并在它的基礎(chǔ)上根據(jù)需求建立更加復(fù)雜的應(yīng)用程序。
如果是第一次使用Flask,可以參考以下兩個學(xué)習(xí)資源:
什么是Vue?
VUE是一個開源JavaScript框架,用于構(gòu)建用戶界面。它采用了React和Angular方面的一些最佳做法。也就是說,與React和Angular相比,它更平易近人,所以初學(xué)者可以快速地開始和運用Vue。它同樣很強大,提供了創(chuàng)建最新前端應(yīng)用程序所需要的所有功能。
有關(guān)Vue的更多信息,以及它與React和Angular的各種優(yōu)缺點,可以參閱以下文章:
第一次用Vue,可以花些時間學(xué)習(xí)一遍官方的Vue指南。
Flask安裝
首先新建一個文件夾:
$ mkdir flask-vue-crud $ cd flask-vue-crud接下來,為這個目錄創(chuàng)建一個虛擬環(huán)境,創(chuàng)建虛擬環(huán)境的方式因不同的開發(fā)環(huán)境可能存在不同。
$ 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文件夾,并在文件夾中創(chuàng)建一個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擴展呢?是為了發(fā)出跨域請求——比如,來自不同協(xié)議,IP地址,域名或端口的請求——而Flask-CORS可以幫我們處理這些。
需要注意的是,雖然上面的設(shè)置允許所有路由上的跨域請求(來自任何域、協(xié)議或端口)。但在生產(chǎn)環(huán)境中,您應(yīng)該只允許來自托管前端應(yīng)用程序的域的跨域請求。有關(guān)此問題的更多信息,請參閱Flask-CORS文檔。運行app:
(env)$ python server/app.py現(xiàn)在可以用瀏覽器登錄http://localhost:5000/ping來測試了,你會看到一個json格式的
"pong!"回到終端中,按Ctrl+C鍵關(guān)閉服務(wù)器。現(xiàn)在,我們就可以把注意力轉(zhuǎn)向前端,開始設(shè)置Vue。
VUE設(shè)置
我們將使用強大的Vue CLI工具來生成一個自定義項目樣板。
在全局內(nèi)安裝Vue CLI:
$ npm install -g @vue/cli@3.7.0 第一次使用npm,可以查閱About npm指南。安裝完成后,用下面的命令來初始化一個名為client的Vue項目:
$ vue create client接下來,需要回答一些關(guān)于項目的問題。在此項目中,具體的選擇如下所示:
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 ? 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等待創(chuàng)建完畢后,項目根目錄下多出一個client文件夾。里面有很多的內(nèi)容,不過我們只需要處理其中‘src’文件夾內(nèi)的一些內(nèi)容以及‘public’文件夾內(nèi)的index.html文件,其他的不需要我們操作。
index.html文件是Vue應(yīng)用的起點。
src文件夾內(nèi)的文件結(jié)構(gòu)如下:
├── App.vue ├── assets │ └── logo.png ├── components │ └── HelloWorld.vue ├── main.js ├── router.js └── views├── About.vue└── Home.vue詳解:
- main.js?app入口點,它與根組件一起加載和初始化Vue。
- app.vue?根組件,它是開始渲染所有其他組件時的起點。
- 'components'?存儲UI組件
- router.js?定義URL并將URL映射到對應(yīng)的組件
- 'views'?存儲綁定到路由器的UI組件
- 'asset'?存儲靜態(tài)資源,如圖像和字體
打開/client/src/components/HelloWorld.vue文件。這是一個單文件組件,它包括三個部分:
現(xiàn)在開始運行開發(fā)服務(wù)器:
$ 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部分的導(dǎo)航欄,變?yōu)槿缦滤?#xff1a;
<template><div id="app"><router-view/></div> </template>現(xiàn)在,你可以通過瀏覽器登錄http://localhost:8080/ping看到 hello! 了。
要將客戶端Vue應(yīng)用程序與后端Flask應(yīng)用程序連接起來,我們可以使用axios庫發(fā)送Ajax請求。
首先安裝對應(yīng)庫:
$ 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-lineconsole.error(error);});},},created() {this.getMessage();}, }; </script>在一個新的終端窗口中啟動Flask應(yīng)用程序。你可以看到http://localhost:8080/ping頁面不再是hello!而是pong!。實際上,當(dāng)從后端返回響應(yīng)時,我們將上面的msg設(shè)置為來自服務(wù)器響應(yīng)對象的data的值。
安裝Bootstrap
接下來,讓我們將一個流行的CSS框架Bootstrap添加到應(yīng)用程序中,這樣我們就可以快速地添加一些樣式。
安裝:
$ npm install bootstrap@4.3.1 --save 忽略jquery和popper.js的warnings警告。不要將它們添加到項目中。后面會詳細的解釋。將Bootstrap中的樣式導(dǎo)入到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>運行服務(wù)器:
$ 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,},], });測試:http://localhost:8080
最后,讓我們將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>你現(xiàn)在應(yīng)該看到:
現(xiàn)在我們可以開始構(gòu)建CRUD應(yīng)用程序的功能了。
我們要建什么?
我們的目標(biāo)是為books設(shè)計一個后端RESTful API,由Python和Flask實現(xiàn)。API本身應(yīng)該遵循RESTful設(shè)計原則,并且可以使用基本的HTTP功能:GET、POST、PUT和DELETE。
我們還將在后端API的基礎(chǔ)上使用Vue搭建完整的前端應(yīng)用:
本教程只討論快樂的構(gòu)建之路,處理錯誤是一個單獨的練習(xí)。可以嘗試通過您的理解,自己在前端和后端添加適當(dāng)?shù)腻e誤處理。GET 路由
服務(wù)器端
向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.
想要進行更多的挑戰(zhàn)嗎?可以為這個程序編寫一個自動測試。查看這兒有更多關(guān)于測試Flask應(yīng)用的資源信息。客戶端
更新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-lineconsole.error(error);});},},created() {this.getBooks();}, }; </script>初始化組件后,通過創(chuàng)建的生命周期鉤子來調(diào)用getBooks( )方法,該方法從我們剛剛設(shè)置的后端端點獲取書籍。
查看實例的生命周期鉤子可以了解更多有關(guān)組件生命周期和可用方法的信息。在模板中,我們通過v-for指令遍歷圖書列表,在每次迭代中創(chuàng)建一個新的表行。索引值當(dāng)做key使用。最后,v-if用于呈現(xiàn)“yes”或“no”,指示用戶是否已讀過書
Bootstrap Vue
在下一節(jié)中,我們將使用一個模式添加一本新書。我們將為此添加一個Bootstrap Vue庫,它提供了一組使用基于引導(dǎo)的HTML和CSS樣式的Vue組件。
為什么要使用Bootstrap Vue庫?Bootstrap的modal組件使用的是jQuery,因此,您應(yīng)該避免在同一個項目中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路由
服務(wù)器端
更新現(xiàn)在的路由處理程序,讓它支持處理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'] = BOOKSreturn jsonify(response_object)當(dāng)Flask服務(wù)器運行時,您可以在一個新的終端選項卡中測試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端點查看響應(yīng)中的是否成功添加了新書。
如果標(biāo)題已經(jīng)存在怎么辦?或者,如果一個標(biāo)題有一個以上的作者呢?你可以自己嘗試解決這些問題來檢測你的知識理解。還有,如何處理無效的數(shù)據(jù)體呢,比如在缺少title、author或read的情況下?客戶端
讓我們現(xiàn)在在客戶端添加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"requiredplaceholder="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"requiredplaceholder="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>將它添加到最后結(jié)束的dev標(biāo)簽之前。可以查看代碼。v-model是一個用于將輸入值綁定到對應(yīng)狀態(tài)的指令。你很快就會看到這一點。
hide-footer有什么用?想了解的話,可以在Bootstrap Vue文檔中查看這個對應(yīng)文檔。更新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-lineconsole.error(error);});},addBook(payload) {const path = 'http://localhost:5000/books';axios.post(path, payload).then(() => {this.getBooks();}).catch((error) => {// eslint-disable-next-lineconsole.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>完整的組件代碼現(xiàn)在應(yīng)該如下所示:
<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"requiredplaceholder="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"requiredplaceholder="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-lineconsole.error(error);});},addBook(payload) {const path = 'http://localhost:5000/books';axios.post(path, payload).then(() => {this.getBooks();}).catch((error) => {// eslint-disable-next-lineconsole.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 組件
接下來,讓我們添加一個Alert組件,這樣在添加新書后,就可以向用戶顯示一條提示消息。我們將為此單獨創(chuàng)建一個新組件,因為您可能會在許多組件中使用這一功能。
向“Client/src/Components”添加一個名為Alert.vue的新文件:
<template><p>It works!</p> </template>然后,將其導(dǎo)入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>現(xiàn)在,我們可以在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>刷新瀏覽器。你現(xiàn)在可以看到:
有關(guān)在其他組件中使用某一組件的更多信息,可以查看官方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以獲得更多關(guān)于props的信息要使其具有動態(tài),以便傳遞自定義消息,可以在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-lineconsole.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設(shè)置為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-lineconsole.log(error);this.getBooks();}); },再試試效果怎么樣!
挑戰(zhàn):
PUT路由
服務(wù)器端
對于更新,我們需要使用唯一的標(biāo)識符,因為我們不能期待所有標(biāo)題是唯一的。我們可以使用Python標(biāo)準(zhǔn)庫中的UUID。
更新server/app.py中的書籍:
import uuidBOOKS = [{'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} ]在添加新書時,重構(gòu)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'] = BOOKSreturn 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 Truereturn False 花點時間思考一下,您將如何處理id不存在時的情況?如果數(shù)據(jù)體不正確怎么辦?此外,還可以嘗試著重構(gòu)輔助方法中的for循環(huán),使其更加Pythonic。客戶端
步驟:
(1)增加模態(tài)和表單
首先,在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"requiredplaceholder="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"requiredplaceholder="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>將表單狀態(tài)添加到script部分的data中:
editForm: {id: '',title: '',author: '',read: [], }, 挑戰(zhàn):嘗試使用相同的modal來處理POST和PUT請求,而不是使用新的modal。(2)“更新”按鈕
更新表格中的“更新”按鈕功能:
<buttontype="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); },(3)連接Ajax請求
updateBook(payload, bookID) {const path = `http://localhost:5000/books/${bookID}`;axios.put(path, payload).then(() => {this.getBooks();}).catch((error) => {// eslint-disable-next-lineconsole.error(error);this.getBooks();}); },(4)用戶提示
更新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-lineconsole.error(error);this.getBooks();}); },(5)“取消”按鈕
添加方法:
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 = []; },在繼續(xù)之前,一定要檢查代碼。完成后,測試應(yīng)用程序。確保在點擊按鈕時modal能夠顯示并且輸入框中填充的值是正確的。
Delete路由
服務(wù)器端
更新路由處理程序:
@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)客戶端
更新“刪除”按鈕如下:
<buttontype="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-lineconsole.error(error);this.getBooks();}); }, onDeleteBook(book) {this.removeBook(book.id); },現(xiàn)在,當(dāng)用戶單擊“刪除”按鈕時,onDeleteBook方法被觸發(fā),接著觸發(fā)removeBook方法。此方法將DELETE請求發(fā)送到后端。當(dāng)響應(yīng)返回時,顯示提示消息并運行g(shù)etBooks。
挑戰(zhàn):
結(jié)語
這篇文章涵蓋了使用Vue和Flask設(shè)置CRUD應(yīng)用程序的基礎(chǔ)知識。
可以檢查一下自己的學(xué)習(xí)效果,從這篇文章開始回顧,并完成其中的每一個挑戰(zhàn)。
如果想更多了解,可以查看具體的源碼,源碼地址為flask-vue-crud。
感謝您的閱讀。
總結(jié)
以上是生活随笔為你收集整理的[Flask+Vue]Books全栈应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vue+flask前后端开发
- 下一篇: Vue+blockly 制作与自定义美化