添加 Books 组件 新建组件src/components/Books.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div class="container"> <p>books</p> </div> </template> <script> /* eslint-disable */ import axios from 'axios'; export default { name: 'Books', data() { return { }; }, methods: { }, created() { }, }; </script>
更新路由如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import Vue from 'vue'; import Router from 'vue-router'; import HelloWorld from '@/components/HelloWorld'; import Ping from '@/components/Ping'; import Books from '@/components/Books'; Vue.use(Router); export default new Router({ routes: [ { path: '/', name: 'books', component: Books, }, { path: '/ping', name: 'Ping', component: Ping, }, { path: '/books', name: 'Books', component: Books, }, ], mode: 'history', });
修改 Books 组件的样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <template> <div class="container"> <div class="row"> <div class="col-sm-10"> <h1>书籍</h1> <hr><br><br> <button type="button" class="btn btn-success btn-sm">添加</button> <br><br> <table class="table table-hover"> <thead> <tr> <th>书名</th> <th>作者</th> <th>读过?</th> <th></th> </tr> </thead> <tbody> <tr> <td>失控</td> <td>凯文凯利</td> <td>是</td> <td> <button type="button" class="btn btn-warning btn-sm">更新</button> <button type="button" class="btn btn-danger btn-sm">删除</button> </td> </tr> </tbody> </table> </div> </div> </div> </template>
接下来,就可以集中处理CRUD
的功能逻辑了。
GET 操作 服务端 首先,在后台构造书籍数据,修改app.py
文件,添加书籍列表,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 BOOKS = [ { 'title': '失控', 'author': '凯文凯利', 'read': True }, { 'title': '黑客与画家', 'author': '保罗格雷厄姆', 'read': False }, { 'title': '未来简史', 'author': '尤瓦尔赫拉利', 'read': True } ]
添加路由处理,代码如下:
1 2 3 4 5 6 @app.route('/books', methods=['GET']) def all_books(): return jsonify({ 'status': 'success', 'books': BOOKS })
运行 Flask 应用,手动测试路由:http://localhost:5000/books
客户端 更新组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 ... <tbody> <tr v-for="(book, index) in books" :key="index"> <td>{{ book.title }}</td> <td>{{ book.author}}</td> <td> <span v-if="book.read">是</span> <span v-else>否</span> </td> <td> <button type="button" class="btn btn-warning btn-sm">更新</button> <button type="button" class="btn btn-danger btn-sm">删除</button> </td> </tr> </tbody> ... <script> /* eslint-disable */ 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>
POST 操作 在点击添加按钮时,我们希望弹出模态对话框,然后键入书籍信息,bootstrap 的 modal 组件使用 jQuery,而 Vue 使用虚Dom,所以,尽量避免 jQuery 和 Vue 同时使用。为此,可使用 Bootstrap Vue 这个库来进行样式处理。
Bootstrap Vue 执行如下命令,安装 bootstrap-vue :
在 app 的入口文件client/src/main.js
中导入bootstrap-vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import 'bootstrap/dist/css/bootstrap.css'; import BootstrapVue from 'bootstrap-vue'; import Vue from 'vue'; import App from './App'; import router from './router'; Vue.config.productionTip = false; Vue.use(BootstrapVue); /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>', });
服务端 更新路由函数,使其支持 POST 请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from flask import Flask, jsonify, request @app.route('/books', methods=['GET', 'POST']) def all_books(): res_obj = {'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') } ) res_obj['message'] = '添加成功!' else: res_obj['books'] = BOOKS return jsonify(res_obj) if __name__ == '__main__': app.run()
在终端中测试如下:
客户端 增加添加书籍的模态呈现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <b-modal ref="addBookModal" id="book-modal" title="添加一本新书" hide-footer> <b-form @submit="onSubmit" @reset="onReset"> <b-form-group id="form-title-group" label="书名:" label-for="form-title-input"> <b-form-input id="form-title-input" type="text" v-model="addBookForm.title" required placeholder="请输入书名"> </b-form-input> </b-form-group> <b-form-group id="form-author-group" label="作者:" label-for="form-author-input"> <b-form-input id="form-author-input" type="text" v-model="addBookForm.author" required placeholder="请输入作者"> </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">读过?</b-form-checkbox> </b-form-checkbox-group> </b-form-group> <b-button type="submit" variant="primary">提交</b-button> <b-button type="reset" variant="danger">取消</b-button> </b-form> </b-modal>
以上代码插入在最后一个封闭的 div 之前。
接着,更新 script
部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 <script> /* eslint-disable */ import axios from 'axios'; export default { name: 'Books', 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) => { console.error(error); } ); }, addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload).then( (res) => { this.getBooks(); } ).catch( (error) => { console.error(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>
最后,在模板中更新添加
按钮,使点击按钮时出现模态对话框:
1 <button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>添加</button>
Alert 组件 接着,添加 Alert 组件用来向用户展示添加新书后的消息。
测试版 首先,添加新的组件client/src/components
:
1 2 3 <template> <p>It works!</p> </template>
接着,在 Books 组件中导入并注册该组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <script> import axios from 'axios'; import Alert from './Alert'; ... export default { data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, }; }, components: { alert: Alert, }, ... }; </script>
现在就可以在模板中使用该组件了:
1 2 3 4 5 6 7 8 9 <template> <div class="container"> <div class="row"> <div class="col-sm-10"> <h1>书籍</h1> <hr><br><br> <alert></alert> <button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>添加</button> ...
刷新浏览器,能看到该组件生效了:
真实版 接下来,在Alert
组件中添加真正的b-alert
组件:
1 2 3 4 5 6 7 8 9 10 11 12 <template> <div> <b-alert variant="success" show>{{ message }}</b-alert> <br> </div> </template> <script> export default { props: ['message'], }; </script>
试着在父组件Books
中,向 message
属性传值:
1 <alert message="hi"></alert>
浏览器结果如下所示:
为了真正实现动态消息,在Books
组件中,进行数据绑定:
1 <alert :message="message"></alert>
然后,在data
选项中,添加message
:
1 2 3 4 5 6 7 8 9 10 11 12 data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, message: '', showMsg: false, }; },
接着,在addBook
中,更新message
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload).then( (res) => { this.getBooks(); this.message = '添加成功!'; this.showMsg = true; } ).catch( (error) => { console.error(error); this.getBooks(); } ); },
PUT 操作 服务端 考虑到更新书的内容,书必须有一个唯一的标识符。可以使用 python 的 uuid
库。
更新 app.py
中的 BOOKS
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import uuid BOOKS = [ { 'id': uuid.uuid4().hex, 'title': '失控', 'author': '凯文凯利', 'read': True }, { 'id': uuid.uuid4().hex, 'title': '黑客与画家', 'author': '保罗格雷厄姆', 'read': False }, { 'id': uuid.uuid4().hex, 'title': '未来简史', 'author': '尤瓦尔赫拉利', 'read': True } ]
重构all_books
方法,为每一个新添加的书添加id
字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @app.route('/books', methods=['GET', 'POST']) def all_books(): res_obj = {'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') } ) res_obj['message'] = '添加成功!' else: res_obj['books'] = BOOKS return jsonify(res_obj)
添加新的路由处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @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'] = '更新成功!' 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
客户端 步骤
添加 modal 和 form
处理更新按钮的点击事件
进行 AJAX 请求
提醒用户
处理取消按钮点击事件
增加新的更新书籍的模态对话框:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <b-modal ref="editBookModal" id="book-update-modal" title="更新" hide-footer> <b-form @submit="onSubmitUpdate" @reset="onResetUpdate"> <b-form-group id="form-title-edit-group" label="书名:" label-for="form-title-edit-input"> <b-form-input id="form-title-edit-input" type="text" v-model="editForm.title" required placeholder="请输入书名"> </b-form-input> </b-form-group> <b-form-group id="form-author-edit-group" label="作者:" label-for="form-author-edit-input"> <b-form-input id="form-author-edit-input" type="text" v-model="editForm.author" required placeholder="请输入作者"> </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">读过?</b-form-checkbox> </b-form-checkbox-group> </b-form-group> <b-button type="submit" variant="primary">更新</b-button> <b-button type="reset" variant="danger">取消</b-button> </b-form> </b-modal>
在data
选项中添加 editForm
:
1 2 3 4 5 6 editForm: { id: '', title: '', author: '', read: [], },
处理更新按钮的点击事件 在模板中增加更新
按钮的点击事件:
1 2 3 4 5 6 <button type="button" class="btn btn-warning btn-sm" v-b-modal.book-update-modal @click="editBook(book)">更新 </button>
增加新的方法,用来更新editForm
:
1 2 3 editBook(book) { this.editForm = book; },
接着,增加提交
按钮的处理方法:
1 2 3 4 5 6 7 8 9 10 11 12 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); },
进行 AJAX 请求 1 2 3 4 5 6 7 8 9 10 11 12 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
,增加如下代码:
1 2 this.message = '已更新!'; this.showMsg = true;
处理取消按钮点击事件 添加处理方法:
1 2 3 4 5 6 onResetUpdate(evt) { evt.preventDefault(); this.$refs.editBookModal.hide(); this.initForm(); this.getBooks(); },
更新initForm
:
1 2 3 4 5 6 7 8 9 initForm() { this.addBookForm.title = ''; this.addBookForm.author = ''; this.addBookForm.read = []; this.editForm.id = ''; this.editForm.title = ''; this.editForm.author = ''; this.editForm.read = []; },
DELETE 操作 服务端 更新路由处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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'] = '更新成功!' if request.method == 'DELETE': remove_book(book_id) response_object['message'] = '已移除!' return jsonify(response_object)
客户端 更新模板中的删除
按钮:
1 2 3 4 5 <button type="button" class="btn btn-danger btn-sm" @click="onDeleteBook(book)">删除 </button>
添加如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 removeBook(bookID) { const path = `http://localhost:5000/books/${bookID}`; axios.delete(path) .then(() => { this.getBooks(); this.message = '已移除!'; this.showMessage = true; }) .catch((error) => { // eslint-disable-next-line console.error(error); this.getBooks(); }); }, onDeleteBook(book) { this.removeBook(book.id); },