使用Flask和Vue开发单页面应用
使用Flask和Vue制作一个基本的CRUD单页面应用,其中后台用到了RESTful API
参考原版的教程https://testdriven.io/blog/developing-a-single-page-app-with-flask-and-vuejs/ 步骤清晰,动作规范.
下面开始我的实际操练步骤记录
最终效果
环境准备
系统 : Windows10 1903
IDE : VSCode
Python : 3.7.5
Node.js : 10.16.3
# 安装pyton-venv
pip install virtualenv
创建venv环境&安装Flask
C:\Users\lpwm\Desktop>cd flask-vue-crud
C:\Users\lpwm\Desktop\flask-vue-crud>mkdir server
C:\Users\lpwm\Desktop\flask-vue-crud>cd server
C:\Users\lpwm\Desktop\flask-vue-crud\server>python -m venv env
C:\Users\lpwm\Desktop\flask-vue-crud\server>env\Scripts\activate.bat
(env) C:\Users\lpwm\Desktop\flask-vue-crud\server>pip install flask flask-cors
测试Flask功能
打开Visual Studio Code并连接到WSL,在server文件夹中创建app.py文件
"""
测试Flask
"""
from flask import Flask, jsonify
from flask_cors import CORS# 配置参数,开启debug模式,json转换中文不使用unicode
DEBUG = True
JSON_AS_ASCII = False# 实例化Flask
app = Flask(__name__)
app.config.from_object(__name__)# 开启CORS,解决跨域调用问题
CORS(app, resources={r'/*': {'origins': '*'}})
# 也可以简单直接写CORS(app)# 配置路由
@app.route('/ping', methods=['GET'])
def ping_pong():return jsonify('pong!中文!')if __name__ == "__main__":app.run()
运行app.py后打开浏览器访问http://127.0.0.1:5000/ping可以看到返回"pong!中文!",Flask工作正常.
安装Vue
# 退出venv环境
(env) C:\Users\lpwm\Desktop\flask-vue-crud\server>env\Scripts\deactivate.bat
# 配置使用淘宝的npm镜像站
npm config set registry https://registry.npm.taobao.org
# 安装vue/cli
npm install -g @vue/cli
下面开始使用vue的cli命令行创建项目,在flask-vue-crud位置执行:
C:\Users\lpwm\Desktop\flask-vue-crud>vue create clientVue CLI v4.0.4
? Please pick a preset:default (babel, eslint)
❯ Manually select features
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)◉ Babel◯ TypeScript◯ Progressive Web App (PWA) Support
❯◉ Router◯ Vuex◯ CSS Pre-processors◯ Linter / Formatter # 被Linter折腾了一天,最终决定放弃了,代码样式自己写的爽就行了◯ Unit Testing◯ E2E Testing
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.?In dedicated config files
❯ In package.json
? Save this as a preset for future projects? (y/N) No# 安装速度还可以,差不多2分钟吧
? Successfully created project client.
? Get started with the following commands:$ cd client$ npm run serve
# 参照上面命令跑一下试试
棒棒哒~下面开始解释vue命令创建的client/src文件夹中主要文件的作用
文件名 | 作用 |
---|---|
main.js | 应用的主入口,用来加载和初始化Vue的root根模块 |
App.vue | root根模块,用来加载渲染需要用到的其他模块 |
components文件夹 | UI中用到的模块都放在这里 |
router/index.js | 模块和URL映射 |
views文件夹 | 模板 |
assets文件夹 | 用来存放静态css,js,字体等文件 |
其中重点说一下client/src/components/HelloWorld.vue这个文件,这是一个单模块文件,包含下面三部分内容:
<template></template>
:静态HTML模板<script></script>
:使用js脚本与模块进行交互<style></style>
:CSS样式表
HBuilderX配置
插件安装
- eslint-plugin-vue
- js-beautify
- NPM
修改format插件的jsbeatufyrc.js文件options将换行符改为LF以兼容Linux
"eol": "\n", //行结束符 Win:\r\n Linux:\n
测试Vue
开始改造上面flask输出的ping路径.
删除views文件夹,同时删除components下的HelloWorld.vue,并在components文件夹中创建一个新的Ping.vue文件:
<template><div><p>{{ msg }}</p></div>
</template><script>
export default {name: 'Ping',data() {return {msg: 'Hello!',};},
};
</script>
修改client/src/router/index.js映射URL的/ping路径到Ping.vue模块:
import Vue from 'vue';
import VueRouter from 'vue-router';
import Ping from '../component/Ping.vue';Vue.use(VueRouter);const routes = [{path: '/ping',name: 'Ping',component: Ping,},
];const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes,
});export default router;
修改App.vue
<template><div id="app"><router-view/></div>
</template>
再打开浏览器访问http://127.0.0.1:8080/ping
接下来使用axios库来连接Vue前端和Flask后端使用AJAX请求进行通信,axios详细API文档参考这里Axios中文说明
PS C:\Users\lpwm\Desktop\flask-vue-crud\client> npm install axios
更新一下Ping.vue文件中的<scipt>
标签内容:
<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 => {console.error(error);});}},created() {this.getMessage();}
};
</script>
这里使用了ES6语法中的箭头函数(Arrow Function),援引一下MDN中的相关解释MDN完整原文
var elements = ['Hydrogen','Helium','Lithium','Beryllium'
];elements.map(function(element) { return element.length;
}); // 返回数组:[8, 6, 7, 9]// 上面的普通函数可以改写成如下的箭头函数
elements.map((element) => {return element.length;
}); // [8, 6, 7, 9]// 当箭头函数只有一个参数时,可以省略参数的圆括号
elements.map(element => {return element.length;
}); // [8, 6, 7, 9]// 当箭头函数的函数体只有一个 `return` 语句时,可以省略 `return` 关键字和方法体的花括号
elements.map(element => element.length); // [8, 6, 7, 9]// 在这个例子中,因为我们只需要 `length` 属性,所以可以使用参数解构
// 需要注意的是字符串 `"length"` 是我们想要获得的属性的名称,而 `lengthFooBArX` 则只是个变量名,
// 可以替换成任意合法的变量名
elements.map(({ "length": lengthFooBArX }) => lengthFooBArX); // [8, 6, 7, 9]
现在分别在两个VSCode中运行起来Flask应用和使用npm run serve
跑起来vue应用
打开浏览器访问http://localhost:8080/ping可以看到收到后台Flask返回的pong
数据
安装Bootstrap
npm install @bootstrap --save
安装结束后警告缺少jquery依赖,这个不用管它,使用Vue就不用jQuery了,避免产生语法冲突.
在client/src/main.js
中引入Bootstrap
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
为app所在的DIV添加个margin-top
的样式
<template><div id="app"><router-view /></div>
</template><style>
#app {margin-top: 60px;
}
</style>
更新client/src/components/Ping.vue
添加个Bootstrap按钮控件试试效果
<template><div class='container'><p>{{ msg }}</p><div class='btn btn-primary'>{{msg}}</div></div>
</template><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 => {console.error(error);});}},created() {this.getMessage();}
};
</script>
重新跑一下vue应用
npm run serve
接下来,在components
中添加一个新的模块Books.vue
<template><div class="container"><p>books</p></div>
</template>
更新一下路由router/index.js
将首页/
路由指向Books模块
import Vue from "vue";
import VueRouter from "vue-router";
import Books from "../components/Books.vue";
import Ping from "../components/Ping.vue";Vue.use(VueRouter);const routes = [{path: "/",name: "books",component: Books},{path: "/ping",name: "Ping",component: Ping}
];const router = new VueRouter({mode: "history",base: process.env.BASE_URL,routes
});export default router;
分别测试一下访问http://localhost:8080
和http://localhost:8080/ping
可以看到路由工作正常
最后,修改一下Books
模块中的<template></template>
模板部分代码,写一个快速的Bootstrap风格表格
<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 ml-2">Delete</button></div></td></tr></tbody></table></div></div></div>
</template>
预览效果:
接下来就是要开始搞CRUD部分的功能实现了
要实现的目标:
使用Python和Flask怼一个RESTfu API规范的后端服务,使用HTTP中的GET,POST,PUT,DELETE方法进行通信.
使用VUE.js怼单页面风格的前端界面和后端的API进行交互
GET路由
Server端(Flask)
给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}
]
修改Flask主文件app.py
的路由
@app.route('/books', methods=['GET'])
def all_books():return jsonify({'status': 'success','books': BOOKS})
现在测试一下访问http://localhost:5000/books
可以拿到flask返回的json数据
Client端(VUE)
更新一下components/Books.vue
模块
<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 ml-2">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>
当Books.vue
模块created()
完成的时候,调用getBooks()
函数从后台获取图书数据
模板部分使用了v-for
标签,在table中循环输出行,将循环过程中的index
作为了key
主键(该主键设置是VUE规范推荐的,据说可以提高渲染效率). 使用v-if
标签来判断渲染read是否的值.
Bootstrap Vue
接下来使用Bootstrap中的模态框控件来实现添加新书的界面,需要使用npm安装bootstrap-vue
包,注意这个包和之前已经装好的bootstrap
包是不一样的,由于原生的bootstrap中模态框是基于jQuery实现的,而VUE和jQuery同时使用会产生语法上的冲突,所以这里要引入这个专门为vue改良的bootstrap包,后面的html语法也会有些不同,现在先按下不表.
安装一下:
npm install @bootstrap-vue --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路由
Server端
更新app.py
处理添加新书时POST请求的路由,修改之前添加过的/books
路由, methods
增加POST
类型,然后通过判断request.method
来确定接下来要进行的操作
@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)
别忘了更新一下import
部分的引入:
from flask import Flask, jsonify, request
可以使用WSL中的curl
命令测试发送POST数据
$ curl -X POST http://localhost:5000/books -d \'{"title": "1Q84", "author": "Haruki Murakami", "read": "true"}' \-H 'Content-Type: application/json'
使用浏览器访问http://localhost:5000/books
默认的GET请求可以正常获取到图书的json数据
Client端
在主<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 type="submit" variant="primary">Submit</b-button><b-button type="reset" variant="danger">Reset</b-button></b-form>
</b-modal>
这里使用了v-model
标签来绑定input
输入框中的值和VUE中data
数据对象中的addBookForm
变量(这个变量在下面代码中声明)
更新下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>
代码解释一下:
data
中新创建了一个addBookForm
对象,用来和表单中的input
控件使用v-model
标签进行数据双向绑定(这正是VUE的魅力所在)onSubmit()
函数和<b-form>
中的submit
属性进行了绑定,用户在提交表单的时候会调用.其中evt.preventDefault()
是用来屏蔽浏览器默认的提交表单操作,this.$refs.addBookModal.hide()
用来关闭模态框,随后调用addBook()
提交数据给后台添加新图书,最后通过initForm()
重置表单数据addBook()
函数发送POST请求给后台的/books
路径用来添加新图书
最后更新一下模板中"Add Book"按钮以触发模态框显示
<button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>
现在Books.vue
模块的完整代码应该是这样:
<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 ml-2">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-inputid="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-inputid="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提醒模块
接下来添加一个用来反馈提醒用户操作结果的模块client/src/components/Alert.vue
<template><p>It works!</p>
</template>
在client/src/components/Books.vue
模块中修改script
部分代码注册上面添加的Alert.vue
模块
<script>
import axios from 'axios';
import Alert from './Alert.vue';//中间省略...export default {data() {return {books: [],addBookForm: {title: '',author: '',read: [],},};},components: {alert: Alert,},//中间省略...};
</script>
现在可以在Books.vue
模块的template
区域中引用Alert.vue
模块了
<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>
刷新一下浏览器,可以看到Alert
模块引用并显示成功
下面我们对Alert
模块使用b-alert
标签进行Bootstrap样式改造
<template><div><b-alert variant="success" show>{{ message }}</b-alert><br></div>
</template><script>
export default {props: ['message'],
};
</script>
b-alert
标签中使用了variant
来定义颜色样式, show
定义了显示时候使用渐变动画
这里注意一下在script
中使用了props
来定义声明模块中所包含的属性变量,这样我们自定义的这个模块就可以在被调用的时候,message
会作为<alert></alert>
标签中的一个属性来接收参数.
回到Books
模块中,可以带参数形式的调用Alert
模块了,例如这样:
<alert message="hi"></alert>
为了让alert
模块中的message
变量实现动态化,使用:变量名=
语法进行数据双向绑定
<alert :message="message"></alert>
在VUE的data
变量中声明message
变量:
data() {return {books: [],addBookForm: {title: '',author: '',read: [],},message: '',};
},
接着改造一下addBook()
函数,加入对message
变量的操作
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
变量为真的时候才显示Alert
模块
<alert :message=message v-if="showMessage"></alert>
当然同上面的操作一样,需要在data
中声明这个showMessage
变量,并且修改addBook()
函数,加入对showMessage
的操作
data() {return {books: [],addBookForm: {title: '',author: '',read: [],},message: '',showMessage: false,};
},
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();});
},
PUT路由
Server端
更新已有的数据需要添加唯一的id标识,可以使用Python标准库中的uuid
来生成
修改server/app.py
中BOOKS
数组变量,别忘了引入uuid
库
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}
]
修改/books
路由中的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)
增加一条新的路由,用来根据id查看单个书信息
@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
Client端
实现修改书籍操作功能的待办事项:
- 添加一个模态框表单
- 处理Update按钮的点击事件
- 通过AJAX发送请求
- 通知用户操作结果处理取消按钮的点击事件
1.添加模态框表单
在Books.vue
模块的template
区域之前写好的模态框后面再写个模态框:
<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>
在script
区域给export的data
对象中添加新的模态框关联数据:
data() {return {books: [],addBookForm: {title: "",author: "",read: []},editForm: {id: "",title: "",author: "",read: []},message: "",showMessage: false};},
说明:由于read
对应的是checkbox控件,取到的值必须以数组形式保存.
2.处理update按钮的点击事件
修改一下表格中update
按钮:
<buttontype="button"class="btn btn-warning btn-sm"v-b-modal.book-update-modal@click="editBook(book)">Update
</button>
这里使用了v-b-modal.模态框id
来关联按钮点击后触发模态框.
更新内容:使用v-b-modal.book-update-modal来设置按钮触发模态框存在一个诡异的问题,就是单击按钮并不会触发模态框显示,需要再点一次才能打开模态框,百思不得其解~于是删掉这个属性,在下面的editBook函数里写this.$refs.editBookModal.show();实现模态框显示.
接着在script
区域methods
中添加一个新的函数,用来更新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();});
},
注意这里定义常量path
的时候使用了反引号
模板字符串的语法,详细用法参考MDN
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(); // 由于上面initForm清空了表单中的数据,同时双向绑定的效果也将当前的book对象数据给清空了,因此需要重新再get一次
},
更新initForm()
函数:
initForm() {this.addBookForm.title = '';this.addBookForm.author = '';this.addBookForm.read = [];this.editForm.id = '';this.editForm.title = '';this.editForm.author = '';this.editForm.read = [];
},
删除操作的路由
Server端
修改/books/<book_id>
路由对应的函数:
@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)
Client端
更新Delete
按钮
<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);
},
使用Flask和Vue开发单页面应用相关推荐
- vue开发单页面应用
今天我们看看VUE怎么开发单页面应用,VUE提供了脚手架vue-cli,通过这个可以很轻松的创建VUE单页面应用, 1.创建VUE项目 首先确保电脑上安装了NODE.JS, 在创建项目的目录下,打开 ...
- EasyDSS高性能流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载 - 副本...
为了让页面更快完成加载, 第一时间呈现给客户端, 也为了帮助客户端节省流量资源, 我们可以开启 vue-router 提供的按需加载功能, 让客户端打开页面时, 只自动加载必要的资源文件, 当客户端操 ...
- React + webpack 开发单页面应用简明中文文档教程(一)一些基础概念
React + webpack 开发单页面应用简明中文文档教程(一)一些基础概念 React 入门系列教程导航 React + webpack 开发单页面应用简明中文文档教程(一)一些基础概念 Rea ...
- 用python+vue+node开发单页面网站
一.开发前准备 1.安装node:为了运行npm命令,该命令会开启网站服务器以及完成相关配置 详情见:https://blog.csdn.net/mrwangweijin/article/detail ...
- Day 22: 使用Spring、MongoDB和AngularJS开发单页面应用
今天的<30天学习30种新技术>挑战,我决定使用Spring框架.MongoDB和AngularJS开发一个单页面应用.我很熟悉Spring和MongoDB,但是我没用配合Spring使用 ...
- vue切换路由页面数据缓存_Vue-Router实现前端页面缓存
一.使用情景 在使用Vue开发单页面应用时,我们通常会使用Vue-Router进行页面导航,Vue-Router在进行路由切换的时候,页面是会重新加载,对应的生命周期函数也会再次执行一遍,但是在有些业 ...
- webpack服务器性能,高性能流媒体服务器EasyDSS前端重构(三)- webpack + vue + AdminLTE 多页面引入 element-ui...
接上篇 本文围绕着实现EasyDSS高性能流媒体服务器的前端框架来展开的,具体EasyDSS的相关信息可在:www.easydss.com 找到! element-ui 介绍 饿了么前端开发组件框架 ...
- vue在html中写style,vue开发之style(六)(CSS页面布局之样式、背景、文字)
最近在学vue开发,并且将学习笔记与大家一起分享,前面讲了vue环境的搭建: 基于vscode的Vue前端环境搭建问题及解决办法 还有vue的使用,重点就在webpack这个: vue开发之webpa ...
- 使用 Vue.js 和 Flask 实现全栈单页面应用
原文链接: codeburst.io 在本教程中,我将向大家展示如何使用前端的 Vue.js 单页面应用和后端的 Flask 进行交互. 如果你只是想使用 Vue.js 库和 Flask 模板基本上是 ...
最新文章
- hdu 2154 跳舞毯 (DP)
- 在原有Android项目中快速集成React Native
- centos6 kvm网卡桥接
- EasyUI + Bootstrap 界面整合
- JSPServlet学习笔记----第4章
- 给ztree节点赋值
- 定位排查工作流的计算结果数据量不符合预期的方法
- leetcode35. 搜索插入位置(二分搜索)
- catia直线测距怎么调出来_CATIA怎么测量两线夹角.
- 2017年中国大功率UPS市场需求、市场需求及市场结构占比分析预测
- 中移4G模块-ML302-OpenCpu开发-CJSON
- php安装make报错信息及解决方法
- 【开发工具IDE】eclipse的web项目的tomcat安装部署问题
- xml转excel(Office XML转excel)
- Arcgis地籍图河流注记字体批量修改
- HTML5响应式手机模板:MUI手机app前端页面开发框架模板HTML+CSS+JavaScript
- request.getParameter()与request.getParameterValues()的区别和使用方式
- oracle显示连接超时,Oracle 12179:tns:连接超时的问题
- 网络准入认证系统方案评估
- 基于时间序列AR模型的PHM预测