日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > vue >内容正文

vue

使用 Vue + Flask 搭建单页应用

發(fā)布時(shí)間:2024/9/21 vue 73 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 Vue + Flask 搭建单页应用 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

單頁(yè)應(yīng)用,只加載一個(gè)主頁(yè)面,然后通過(guò) AJAX 無(wú)刷新加載其它頁(yè)面片段。表面上看,就只有一個(gè) HTML 文件,所謂單頁(yè)。開(kāi)發(fā)上,做到了前后端分離,前端專注于渲染模板,而后端只要提供 API 就行,不用自己去套模板了。效果上,頁(yè)面和共用的 JS、CSS 文件都只加載一次,能減輕服務(wù)器壓力和節(jié)省一定的網(wǎng)絡(luò)帶寬。另外,由于不需要每次都加載頁(yè)面以及共用的靜態(tài)文件,響應(yīng)速度也有一定提高,用戶體驗(yàn)比較好。當(dāng)然,也有一些缺點(diǎn),比如 SEO 優(yōu)化不大方便,不過(guò)也有相應(yīng)的解決方案。總的來(lái)說(shuō),使用單頁(yè)應(yīng)用的好處還是遠(yuǎn)多于壞處,這也是越來(lái)越多的人使用單頁(yè)應(yīng)用的原因。

構(gòu)建單頁(yè)應(yīng)用的方式有很多,這里我們選擇 Flask + Vue 實(shí)現(xiàn)。本文以實(shí)現(xiàn)一個(gè) CRUD 的 Demo 為主線,在其中穿插必要的技術(shù)點(diǎn)進(jìn)行講述。里面可能涉及了一些你沒(méi)接觸或者不熟悉的概念,不過(guò)不要緊,我會(huì)給出相應(yīng)的參考文章幫助你理解。當(dāng)然,大牛可忽略這些 :)。看完這篇文章后,相信你也能搭建自己的單頁(yè)應(yīng)用了。

1 前端

這里我們會(huì)用到 Vue 框架。如果你之前沒(méi)有接觸過(guò),推薦去看下官方文檔的「基礎(chǔ)」一節(jié)。也可以先直接向下看,Demo 用的都是一些基礎(chǔ)的東西,大致看下應(yīng)該就能理解。即使暫時(shí)不理解,照著例子實(shí)踐一遍后,去看下文檔收獲也應(yīng)該更多。

為了更便捷的創(chuàng)建基于 Vue 的項(xiàng)目,我們可以使用 Vue Cli 腳手架。通過(guò)腳手架創(chuàng)建項(xiàng)目的時(shí)候,它會(huì)輔助我們做一些配置,省去我們手動(dòng)配置的時(shí)間。剛接觸的伙伴前期會(huì)用它創(chuàng)建項(xiàng)目就行了,至于更深的一些東西后期再去了解。

安裝腳手架

$ npm install -g @vue/cli 復(fù)制代碼

這里我們安裝的是最新的 3 版本。

基于 Vue 的 UI 組件庫(kù)很多,比如 iView、Element、Vuetify 等。國(guó)內(nèi)使用 iView、Element 的特別多,而使用 Vuetify 的人相對(duì)要少很多,不知道是大家看不慣它的 Material Design 風(fēng)格還是它的中文文檔稀缺的緣故。不過(guò)我個(gè)人挺喜歡 Vuetify 的風(fēng)格的,所以我會(huì)使用這個(gè)組件庫(kù)搭建前端頁(yè)面。

如果你沒(méi)使用過(guò)這個(gè)組件庫(kù),照著本文一步步實(shí)踐下去,也能對(duì) Vuetify 的用法有個(gè)大致的了解。如果這個(gè)過(guò)程中,感覺(jué)碰到的疑問(wèn)太多,可以看下 YouTube 上的這個(gè)視頻教程。

https://dwz.cn/lxMHF4bY

也不要到處去找類似的資源了,就是這個(gè)系列的視頻看完再加上官方文檔,掌握常用的點(diǎn)基本沒(méi)問(wèn)題。

不過(guò),還是建議先照著本文實(shí)現(xiàn)一下 Demo,再去學(xué)習(xí),我覺(jué)得這樣效果更好。

新建目錄 spa-demo,然后切換到該目錄下新建前端項(xiàng)目 client

$ vue create client 復(fù)制代碼

創(chuàng)建項(xiàng)目時(shí)會(huì)讓你手動(dòng)選擇一些配置,這里貼下我當(dāng)時(shí)的設(shè)置

? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, Router, Linter ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Pick a linter / formatter config: Basic ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)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) N 復(fù)制代碼

回車安裝完成后,我們切換到 client 目錄下,執(zhí)行命令

$ npm run serve 復(fù)制代碼

上述命令執(zhí)行完成后會(huì)有類似這樣的輸出

...App running at: - Local: http://localhost:8080/ - Network: http://172.20.10.3:8080/... 復(fù)制代碼

在瀏覽器中訪問(wèn)

http://localhost:8080/

如果看到包含下面文字的頁(yè)面

Welcome to Your Vue.js App

說(shuō)明項(xiàng)目安裝成功。

安裝 Vuetify

$ vue add vuetify 復(fù)制代碼

同樣會(huì)提示你選擇一些配置,這里我選擇的 Default

? Choose a preset: Default (recommended) 復(fù)制代碼

回車安裝完成后,重新開(kāi)下服務(wù)器

$ npm run serve 復(fù)制代碼

執(zhí)行完畢后,我們?cè)跒g覽器中訪問(wèn)

http://localhost:8080/

會(huì)看到頁(yè)面內(nèi)容又些改變,有這么一行文字

Welcome to Vuetify

這里說(shuō)明 Vuetify 安裝成功。

看下此時(shí)的目錄結(jié)構(gòu)

spa-demo └── client├── README.md├── babel.config.js├── package-lock.json├── package.json├── node_module│?? └── ...├── public│?? ├── favicon.ico│?? └── index.html└── src├── App.vue├── assets│?? ├── logo.png│?? └── logo.svg├── components│?? └── HelloWorld.vue├── main.js├── plugins│?? └── vuetify.js├── router.js└── views├── About.vue└── Home.vue 復(fù)制代碼

簡(jiǎn)化 spa-demo/client/src/App.vue,將其修改為

<template><v-app><v-content><router-view></router-view></v-content></v-app> </template><script>export default {name: 'App',data () {return {//}}} </script> 復(fù)制代碼

修改 spa-demo/client/src/views/Home.vue,在頁(yè)面放入一個(gè) Data table

<template><div class="home"><v-container class="my-5"><!-- 對(duì)話框 --><!-- 表格 --><v-data-table:headers="headers":items="books"hide-actionsclass="elevation-1"><template slot="items" slot-scope="props"><td>{{ props.item.name }}</td><td>{{ props.item.category }}</td><td class="layout px-0"><v-icon small class="ml-4" @click="editItem(props.item)">edit</v-icon><v-icon small @click="deleteItem(props.item)">delete</v-icon></td></template><template slot="no-data"><v-alert :value="true" color="info" outline>無(wú)數(shù)據(jù)</v-alert></template></v-data-table></v-container></div> </template><script>export default {data: () => ({headers: [{ text: '書(shū)名', value: 'name', sortable: false, align: 'left'},{ text: '分類', value: 'category', sortable: false },{ text: '操作', value: 'name', sortable: false }],books: [],}),created () {this.books = [{ name: '生死疲勞', category: '文學(xué)' },{ name: '國(guó)家寶藏', category: '人文社科' },{ name: '人類簡(jiǎn)史', category: '科技' },]},} </script> 復(fù)制代碼

我們使用了數(shù)據(jù) headers 和 books 控制表的頭部和數(shù)據(jù),并在創(chuàng)建的時(shí)候,給 books 填充了一些臨時(shí)數(shù)據(jù)。

這個(gè)頁(yè)面中涉及到了 Data table 的使用,相關(guān)代碼不用記,在 Vuetify 文檔中搜索 Data table 有很多例子,看了幾個(gè)之后你就知道怎么使用了。對(duì)于新手來(lái)說(shuō),不好理解的可能就是那個(gè) slot-scope(作用域插槽 ),這個(gè)看下 Vue 官方文檔這些內(nèi)容

  • 「基礎(chǔ)」一節(jié)的「組件基礎(chǔ)」
  • 「深入了解組件」一節(jié)的「組件注冊(cè)」、「Prop」、「自定義事件」、「插槽」

靜下心來(lái)讀讀就明白了,不難,這里我不再贅述。

同樣,這里你也可以先照葫蘆畫(huà)瓢,可以先暫時(shí)忽略掉一些不好理解的地方,待實(shí)踐一遍之后再去搞清楚。

打開(kāi)

http://localhost:8080/

看到的頁(yè)面是這樣的

就是一個(gè)圖書(shū)列表。

現(xiàn)在我們要做個(gè)可以彈出的對(duì)話框,用于新增書(shū)籍。我們?cè)?<!-- 對(duì)話框 --> 位置新增如下代碼

<v-toolbar flat class="white"><v-toolbar-title>圖書(shū)列表</v-toolbar-title><v-spacer></v-spacer><v-dialog v-model="dialog" max-width="600px"><v-btn slot="activator" class="primary" dark>新增</v-btn><v-card><v-card-title><span class="headline">{{ formTitle }}</span></v-card-title><v-card-text><v-alert :value="Boolean(errMsg)" color="error" icon="warning" outline>{{ errMsg }}</v-alert><v-container grid-list-md><v-layout><v-flex xs12 sm6 md4><v-text-field label="書(shū)名" v-model="editedItem.name"></v-text-field></v-flex><v-flex xs12 sm6 md4><v-text-field label="分類" v-model="editedItem.category"></v-text-field></v-flex></v-layout></v-container></v-card-text><v-card-actions><v-spacer></v-spacer><v-btn color="blue darken-1" flat @click="close">取消</v-btn><v-btn color="blue darken-1" flat @click="save">保存</v-btn></v-card-actions></v-card></v-dialog> </v-toolbar> 復(fù)制代碼

對(duì)應(yīng)的,要在 <script></script> 之間添加一些 JS

export default {data: () => ({dialog: false, // 是否展示對(duì)話框errMsg: '', // 是否有錯(cuò)誤信息editedIndex: -1, // 當(dāng)前在對(duì)話框中編輯的圖書(shū)在列表中的序號(hào)editedItem: { // 當(dāng)前在對(duì)話框中編輯的圖書(shū)內(nèi)容id: 0,name: '',category: ''},defaultItem: { // 默認(rèn)的圖書(shū)內(nèi)容,用于初始化新增對(duì)話框內(nèi)容id: 0,name: '',category: ''}}),computed: {formTitle () {return this.editedIndex === -1 ? '新增' : '編輯'}},watch: {dialog (val) {if (!val) {this.close()this.clearErrMsg()}}},methods: {clearErrMsg () {this.errMsg = ''},close () {this.dialog = falsesetTimeout(() => {this.editedItem = Object.assign({}, this.defaultItem)this.editedIndex = -1}, 300)}} } 復(fù)制代碼

為了讓文章簡(jiǎn)潔一些,貼代碼的時(shí)候我將之前已有的片段進(jìn)行了省略,你寫(xiě)的時(shí)候可以將上面的代碼根據(jù)位置添加到合適的地方。

我們使用了 Toolbar、Dialog 在表格上面添加對(duì)話框相關(guān)的東西,同樣,不必記代碼,不知道怎么寫(xiě)的時(shí)候查閱下文檔就行。

數(shù)據(jù) dialog 表示當(dāng)前對(duì)話框是否展示,errMsg 控制錯(cuò)誤信息的展示,監(jiān)聽(tīng) dialog 當(dāng)它變化為 false 的時(shí)候關(guān)閉對(duì)話框并清空 errMsg。計(jì)算屬性 formTitle 用于控制對(duì)話框的標(biāo)題。然后添加了兩個(gè)表單元素用于填寫(xiě)書(shū)籍的名稱以及分類。

當(dāng)我們點(diǎn)擊新增后,頁(yè)面是這樣的

其實(shí),到這里,我們的前端頁(yè)面差不多就 OK 了,后面便是增刪改的實(shí)現(xiàn)。這個(gè)我們先在前端單方面的實(shí)現(xiàn)下,后面再和后端進(jìn)行整合。這樣,會(huì)讓前端的 Demo 更完整一些。

實(shí)現(xiàn)保存方法,在 methods 新增 save

save() {if (this.editedIndex > -1) { // 編輯Object.assign(this.books[this.editedIndex], this.editedItem)} else { // 新增this.books.push(this.editedItem)}this.close() } 復(fù)制代碼

編輯的時(shí)候,要展示彈框,我們需要添加 editItem 方法

editItem (item) {this.editedIndex = this.books.indexOf(item)this.editedItem = Object.assign({}, item)this.dialog = true } 復(fù)制代碼

保存方法和新增時(shí)的一致。

實(shí)現(xiàn)刪除方法 deleteItem

deleteItem (item) {const index = this.books.indexOf(item)confirm('確認(rèn)刪除?') && this.books.splice(index, 1) } 復(fù)制代碼

至此,前端項(xiàng)目告一段落。

2 后端

后端,我們只需要提供增刪改查的接口供前端使用就行。RESTful API 是目前比較成熟的一套互聯(lián)網(wǎng)應(yīng)用程序設(shè)計(jì)理論,我也會(huì)基于此實(shí)現(xiàn)圖書(shū)的相關(guān)操作接口。

考慮到有對(duì) RESTful API 不大熟悉的伙伴,我列了幾個(gè)我之前學(xué)習(xí)的文章,供大家參考。

  • 《理解RESTful架構(gòu)》
    • https://dwz.cn/eXu0p6pv
  • 《RESTful API 設(shè)計(jì)指南》
    • https://dwz.cn/8v4B0twY
  • 《RESTful API 最佳實(shí)踐》
    • https://dwz.cn/2aSnI8fF
  • 知乎問(wèn)題《怎樣用通俗的語(yǔ)言解釋REST,以及RESTful?》
    • https://dwz.cn/bVxrSsf4

看完上面的相關(guān)資料,你對(duì)這種設(shè)計(jì)理論應(yīng)該就有一定掌握了。

同樣,你暫時(shí)可不必對(duì) RESTful API 了解得很全面,暫時(shí)像下面這樣理解它就行

就是用 URL 定位資源,用 HTTP 描述操作。

這個(gè)是在刷上面知乎問(wèn)題看到的一個(gè)回答,作者是 @Ivony。寫(xiě)得很簡(jiǎn)潔,但確實(shí)有道理。

等到自己實(shí)踐一次后,再回頭看看理論的一些東西,印象更深。

首先列下我們需要實(shí)現(xiàn)的接口

序號(hào)方法URL描述
1GEThttp://domain/api/v1/books獲取所有圖書(shū)
2GEThttp://domain/api/v1/books/123獲取主鍵為 123 的圖書(shū)
3POSThttp://domain/api/v1/books新增圖書(shū)
4PUThttp://domain/api/v1/books/123更新主鍵為 123 的圖書(shū)
5DELETEhttp://domain/api/v1/books/123刪除主鍵為 123 的圖書(shū)

我們可以直接使用 Flask 實(shí)現(xiàn)上面的接口,不過(guò)當(dāng)資源多的時(shí)候,我們寫(xiě)代碼時(shí)會(huì)寫(xiě)很多重復(fù)的片段,違反了 DRY(Don't Repeat Yourself) 原則,后面維護(hù)起來(lái)比較麻煩,所以我們借助 Flask-RESTful 擴(kuò)展實(shí)現(xiàn)。

另外,本節(jié)的重心是放在接口的實(shí)現(xiàn)上,也為了行文更簡(jiǎn)潔,我們將數(shù)據(jù)直接存在字典里,就不涉及數(shù)據(jù)庫(kù)相關(guān)的操作了。

在 spa-demo 目錄下新建 server 目錄,并切換到該目錄下,初始化 Python 環(huán)境

$ pipenv --python 3.6.0 復(fù)制代碼

Pipenv 是當(dāng)前官方推薦的虛擬環(huán)境和包管理工具,我之前寫(xiě)過(guò)一篇文章《Pipenv 快速上手》介紹過(guò),沒(méi)接觸過(guò)的可以去看下。

安裝 Flask

$ pipenv install flask 復(fù)制代碼

安裝 Flask-RESTful

$ pipenv install flask-restful 復(fù)制代碼

新建 spa-demo/server/app.py

# coding=utf-8from flask import Flask, request from flask_restful import Api, Resource, reqparse, abortapp = Flask(__name__) api = Api(app)books = [{'id': 1, 'name': 'book1', 'category': 'cat1'},{'id': 2, 'name': 'book2', 'category': 'cat2'},{'id': 3, 'name': 'book3', 'category': 'cat3'}]# 公共方法區(qū)class BookApi(Resource):def get(self, book_id):passdef put(self, book_id):passdef delete(self, book_id):passclass BookListApi(Resource):def get(self):return booksdef post(self):passapi.add_resource(BookApi, '/api/v1/books/<int:book_id>', endpoint='book') api.add_resource(BookListApi, '/api/v1/books', endpoint='books')if __name__ == '__main__':app.run(debug=True) 復(fù)制代碼

上面就是一個(gè)標(biāo)準(zhǔn)的整合了 Flask-RESTful 的代碼結(jié)構(gòu),在 Flask-RESTful 的官方文檔中可以看到相似的例子。對(duì)于每一種資源,我們都可以用類似的結(jié)構(gòu)實(shí)現(xiàn)接口。BookApi 類中的 get、put、delete 方法對(duì)應(yīng)接口 2、4、5,BookListApi 類中的 get、post 方法對(duì)應(yīng)接口 1、3。之后便是注冊(cè)路由。看到這,有的伙伴可能會(huì)有疑問(wèn),為什么同一個(gè)資源需要定義兩個(gè)類呢?其實(shí)就是方便給一個(gè)資源注冊(cè)帶主鍵和不帶主鍵的路由。

此時(shí),項(xiàng)目結(jié)構(gòu)為

spa-demo ├── client │?? └── ... └── server├── Pipfile├── Pipfile.lock└── app.py 復(fù)制代碼

切換到 spa-demo/server 目錄,運(yùn)行 app.py

$ pipenv run python app.py 復(fù)制代碼

然后測(cè)試獲取所有圖書(shū)接口是否可用。由于是 API 測(cè)試,不建議直接使用瀏覽器,畢竟有時(shí)構(gòu)造參數(shù)和看 HTTP 信息不大方便,推薦使用 Postman,當(dāng)然簡(jiǎn)單的測(cè)試的話可以直接使用命令 curl。

請(qǐng)求接口 1,獲取所有圖書(shū)信息

$ curl -i http://127.0.0.1:5000/api/v1/books 復(fù)制代碼

得到結(jié)果

HTTP/1.0 200 OK Content-Type: application/json Content-Length: 249 Server: Werkzeug/0.14.1 Python/3.6.0 Date: Thu, 13 Dec 2018 15:21:56 GMT[{"id": 1,"name": "book1","category": "cat1"},{"id": 2,"name": "book2","category": "cat2"},{"id": 3,"name": "book3","category": "cat3"} ] 復(fù)制代碼

成功獲取所有圖書(shū),說(shuō)明接口 1 已經(jīng) OK。

然后實(shí)現(xiàn)接口 2,獲取指定 ID 的圖書(shū)。由于根據(jù) ID 獲取圖書(shū)以及圖書(shū)不存在時(shí)拋 404 的操作后面會(huì)頻繁使用到,所以這里提兩個(gè)方法到「公共方法區(qū)」。

def get_by_id(book_id):book = [v for v in books if v['id'] == book_id]return book[0] if book else Nonedef get_or_abort(book_id):book = get_by_id(book_id)if not book:abort(404, message=f'Book {book_id} not found')return book 復(fù)制代碼

然后實(shí)現(xiàn) BookApi 中 get 方法

def get(self, book_id):book = get_or_abort(book_id)return book 復(fù)制代碼

取 ID 為 1 的圖書(shū)測(cè)試下

$ curl -i http://127.0.0.1:5000/api/v1/books/1 復(fù)制代碼

結(jié)果

HTTP/1.0 200 OK Content-Type: application/json Content-Length: 61 Server: Werkzeug/0.14.1 Python/3.6.0 Date: Thu, 13 Dec 2018 15:31:48 GMT{"id": 1,"name": "book1","category": "cat1" } 復(fù)制代碼

取 ID 為 5 的圖書(shū)測(cè)試下

$ curl -i http://127.0.0.1:5000/api/v1/books/5 復(fù)制代碼

結(jié)果

HTTP/1.0 404 NOT FOUND Content-Type: application/json Content-Length: 149 Server: Werkzeug/0.14.1 Python/3.6.0 Date: Thu, 13 Dec 2018 15:32:47 GMT{"message": "Book 5 not found. You have requested this URI [/api/v1/books/5] but did you mean /api/v1/books/<int:book_id> or /api/v1/books ?" } 復(fù)制代碼

ID 為 1 時(shí),成功獲取到圖書(shū)信息;ID 為 5 時(shí),由于圖書(shū)不存在,所以會(huì)返回 404 的響應(yīng)。測(cè)試結(jié)果與預(yù)期一致,說(shuō)明這個(gè)接口也 OK 了。

實(shí)現(xiàn)接口 3,新增圖書(shū)。新增圖書(shū)的時(shí)候,我們應(yīng)該校驗(yàn)參數(shù)是否符合要求。Flask-RESTFul 給我們提供了比較優(yōu)雅的實(shí)現(xiàn),不需要我們使用多個(gè) if 判斷的硬編碼的形式去檢測(cè)參數(shù)是否有效。

由于圖書(shū)名稱和分類都是不能為空的,我們需要自定義規(guī)則,我們可以在「公共方法區(qū)」新增一個(gè)方法

def not_empty_str(s):s = str(s)if not s:raise ValueError("Must not be empty string")return s 復(fù)制代碼

重寫(xiě) BookListApi 的初始化方法

def __init__(self):self.reqparse = reqparse.RequestParser()self.reqparse.add_argument('name', type=not_empty_str, required=True, location='json')self.reqparse.add_argument('category', type=not_empty_str, required=True, location='json')super(BookListApi, self).__init__() 復(fù)制代碼

然后實(shí)現(xiàn) post 方法

def post(self):args = self.reqparse.parse_args()book = {'id': books[-1]['id'] + 1 if books else 1,'name': args['name'],'category': args['category'],}books.append(book)return book, 201 復(fù)制代碼

方法中,首先檢測(cè)參數(shù)是否有效,然后取最后一本書(shū)的 ID 加上 1 作為新書(shū)的 ID 保存,最后返回添加的圖書(shū)信息和狀態(tài)碼 201(表示已創(chuàng)建)。

測(cè)試下參數(shù)校驗(yàn)是否 OK

$ curl -i \-H "Content-Type: application/json" \-X POST \-d '{"name":"","category":""}' \http://127.0.0.1:5000/api/v1/books 復(fù)制代碼

結(jié)果

HTTP/1.0 400 BAD REQUEST Content-Type: application/json Content-Length: 70 Server: Werkzeug/0.14.1 Python/3.6.0 Date: Thu, 13 Dec 2018 15:46:18 GMT{"message": {"name": "Must not be empty string"} } 復(fù)制代碼

返回 400 的錯(cuò)誤,說(shuō)明參數(shù)校驗(yàn)有效。

看下新增接口是否可用

$ curl -i \-H "Content-Type: application/json" \-X POST \-d '{"name":"t_name","category":"t_cat"}' \http://127.0.0.1:5000/api/v1/books 復(fù)制代碼

結(jié)果

HTTP/1.0 201 CREATED Content-Type: application/json Content-Length: 63 Server: Werkzeug/0.14.1 Python/3.6.0 Date: Thu, 13 Dec 2018 15:53:54 GMT{"id": 4,"name": "t_name","category": "t_cat" } 復(fù)制代碼

說(shuō)明創(chuàng)建成功。我們通過(guò)獲取指定 ID 的圖書(shū)接口確認(rèn)下

$ curl -i http://127.0.0.1:5000/api/v1/books/4 復(fù)制代碼

結(jié)果

HTTP/1.0 200 OK Content-Type: application/json Content-Length: 63 Server: Werkzeug/0.14.1 Python/3.6.0 Date: Thu, 13 Dec 2018 15:54:18 GMT{"id": 4,"name": "t_name","category": "t_cat" } 復(fù)制代碼

獲取成功,說(shuō)明確實(shí)創(chuàng)建成功,說(shuō)明接口 3 也好了。

接口 4、5 的實(shí)現(xiàn)與上面類似,這里貼下代碼,就不詳細(xì)說(shuō)明了。

和 BookListApi 類似,首先重寫(xiě) BookApi 的初始化方法

def __init__(self):self.reqparse = reqparse.RequestParser()self.reqparse.add_argument('name', type=not_empty_str, required=True, location='json')self.reqparse.add_argument('category', type=not_empty_str, required=True, location='json')super(BookApi, self).__init__() 復(fù)制代碼

然后實(shí)現(xiàn) put 和 delete 方法

def put(self, book_id):book = get_or_abort(book_id)args = self.reqparse.parse_args()for k, v in args.items():book[k] = vreturn book, 201def delete(self, book_id):book = get_or_abort(book_id)del bookreturn '', 204 復(fù)制代碼

至此,后端項(xiàng)目基本完畢。

當(dāng)然,這是不完整的,比如這里面都沒(méi)有實(shí)現(xiàn)對(duì) API 的認(rèn)證,這個(gè)可以通過(guò) Flask-HTTPAuth 或者其它方式實(shí)現(xiàn)。限于篇幅,這里就不展開(kāi)說(shuō)明了,有興趣的可以看下這個(gè)這個(gè)擴(kuò)展的文檔或者自己研究實(shí)現(xiàn)下。

3 整合

單獨(dú)的前端或后端都有了雛形,就差整合這一步了。

前端需要請(qǐng)求數(shù)據(jù),這里我們使用 axios,切換到 spa-demo/client 目錄下進(jìn)行安裝

$ npm install axios --save 復(fù)制代碼

修改 spa-demo/client/src/views/Home.vue,在 script 標(biāo)簽之間引入 axios,并初始化 API 地址

import axios from 'axios'const booksApi = 'http://localhost:5000/api/v1/books'export default {... } 復(fù)制代碼

修改鉤子 created 的邏輯,從后端獲取數(shù)據(jù)

created () {axios.get(booksApi).then(response => {this.books = response.data}).catch(error => {console.log(error)}) } 復(fù)制代碼

運(yùn)行前端項(xiàng)目后,查看首頁(yè),會(huì)發(fā)現(xiàn)沒(méi)有數(shù)據(jù)。查看開(kāi)發(fā)者工具,我們會(huì)發(fā)現(xiàn)這么一個(gè)錯(cuò)誤

Access to XMLHttpRequest at 'http://localhost:5000/api/v1/books' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 復(fù)制代碼

就是說(shuō)當(dāng)前項(xiàng)目不支持 CORS(Cross-Origin Resource Sharing,即跨域資源訪問(wèn))。這個(gè)我們可以在前端添加代理的形式實(shí)現(xiàn),也可以在后端通過(guò) Flask-CORS 實(shí)現(xiàn)。這里,我使用的后者。

切換到 spa-demo/server 目錄,安裝 Flask-CORS

$ pipenv install flask-cors 復(fù)制代碼

修改 spa-demo/server/app.py,在頭部引入 CORS

from flask_cors import CORS 復(fù)制代碼

在代碼

app = Flask(__name__) 復(fù)制代碼

api = Api(app) 復(fù)制代碼

之間添加一行

CORS(app, resources={r"/api/*": {"origins": "*"}}) 復(fù)制代碼

然后重新運(yùn)行 app.py,刷新首頁(yè),我們會(huì)看到列表有數(shù)據(jù)了,說(shuō)明 CORS 的問(wèn)題成功解決。

在 spa-demo/client/src/views/Home.vue 中,修改 save 方法,同時(shí)新增輔助方法 setErrMsg

setErrMsg (errResponse) {let errResMsg = errResponse.data.messageif (typeof errResMsg === 'string') {this.errMsg = errResMsg} else {let errMsgs = []let kfor (k in errResMsg) {errMsgs.push('' + k + ' ' + errResMsg[k])}this.errMsg = errMsgs.join(',')} }, save() {if (this.editedIndex > -1) { // 編輯axios.put(booksApi + '/' + this.editedItem.id, this.editedItem).then(response => {Object.assign(this.books[this.editedIndex], response.data)this.close()}).catch(error => {this.setErrMsg(error.response)console.log(error)})} else { // 新增axios.post(booksApi, this.editedItem).then(response => {this.books.push(response.data)this.close()}).catch(error => {this.setErrMsg(error.response)console.log(error)})} } 復(fù)制代碼

此時(shí),圖書(shū)新增、保存搞定。

修改 deleteItem 方法

deleteItem (item) {const index = this.books.indexOf(item)confirm('確認(rèn)刪除?') && axios.delete(booksApi + '/' + this.books[0].id).then(response => {this.books.splice(index, 1)}).catch(error => {this.setErrMsg(error.response)console.log(error)}) } 復(fù)制代碼

此時(shí),刪除方法也搞定了。

至此,整合完畢,基于 Vue + Flask 的前后端分離的一個(gè) CRUD Demo 就完成了。

看完本文,你可以按著步驟自己實(shí)現(xiàn)下。剛接觸的伙伴在看的過(guò)程中在某些地方可能有疑惑,我也在我能想到的地方提供了一些資料,你可以試著看下。如果沒(méi)能提供全,你需要自己百度/谷歌下解決。不過(guò),我還是建議不要妄求每個(gè)點(diǎn)都了解的特別清楚,先明白關(guān)鍵點(diǎn),試著實(shí)現(xiàn)一下,回頭去看相關(guān)資料的時(shí)候,也更有感觸一些。

完整代碼可到 GitHub 查看

https://github.com/kevinbai-cn/spa-demo

4 參考

  • 《Full-stack single page application with Vue.js and Flask》
    • https://bit.ly/2C9kSiG
  • 《Developing a Single Page App with Flask and Vue.js》
    • https://bit.ly/2ElaXrB
  • 《Vuetify Documents》
    • https://bit.ly/2QupMzx
  • 《Designing a RESTful API with Python and Flask》
    • https://bit.ly/2vqq3Y1
  • 《Designing a RESTful API using Flask-RESTful》
    • https://bit.ly/2nGDNtL

本文首發(fā)于公眾號(hào)「小小后端」。

與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖

總結(jié)

以上是生活随笔為你收集整理的使用 Vue + Flask 搭建单页应用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。