предисловие:
Я читал некоторые зарубежные статьи о разделении передней и задней частей flask и vue, но я не видел, чтобы это было относительно легко понять, и код был идеальным.Вчера я видел очень новую статью, и содержание было очень хорошо, вот и захотелось перевести.Чтобы все учились вместе.
Оригинал изDeveloping a CRUD App with Flask and Vue.js
текст:
Далее будет показано шаг за шагом, как завершить базовое приложение CRUD с помощью Flask и Vue. Мы начнем с настройки фреймворка, создания нового приложения Vue с использованием интерфейса командной строки Vue, а затем выполнения основных операций CRUD с помощью RESTful API, разработанного на Python и Flask.
К основным зависимым библиотекам относятся:
- 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
Цель этой статьи
К концу статьи вы сможете узнать:
- Что такое фляга
- Что такое Vue и другие UI-библиотеки или интерфейсные фреймворки (React и Angular)
- Создайте проект Vue с помощью Vue CLI
- Создайте и отправьте компонент Vue в браузере
- Создайте одностраничное приложение с компонентами Vue
- Соедините приложение Vue и серверную часть Flask
- Разрабатывайте RESTful API с помощью Flask
- Стилизовать компоненты Vue с помощью Bootstrap
- Создание маршрутов и визуализация компонентов с помощью Vue Router
Что такое Фласк?
Flask— это простая, но мощная микро-веб-инфраструктура Python, идеально подходящая для создания RESTful API. рисунокSinatra(Рубин) иExpress(узел), он очень маленький и гибкий, поэтому вы можете начать с небольшого приложения и расширять его по мере необходимости для создания более сложных приложений.
Если вы используете Flask впервые, вы можете обратиться к следующим двум учебным ресурсам:
Что такое Вью?
VUEЭто среда JavaScript с открытым исходным кодом для создания пользовательских интерфейсов. Он использует некоторые из лучших практик React и Angular. То есть по сравнению с React и Angular он более доступен, поэтому новички могут быстро и начать использовать Vue. Он также очень мощный, предоставляет все функциональные возможности для создания новых потребностей внешнего интерфейса.
Для получения дополнительной информации о Vue и его различных плюсах и минусах с React и Angular вы можете прочитать следующие статьи:
- VUE: сравнение с другими фреймворками
- React vs Angular vs Vue.js: полное руководство по сравнению
- React vs Angular vs Vue: сравнение в 2017 году
Впервые используя 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
Создайте новую папку сервера в корневом каталоге и создайте в ней файл 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.
Запустите приложение:
(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руководство.
После установки инициализируйте проект Vue с именем client с помощью следующей команды:
$ 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
После завершения создания в корневом каталоге проекта появится дополнительная клиентская папка. В нем много контента, но нам нужно разобраться только с некоторым контентом в папке 'src' и файлом index.html в папке 'public', а остальное нам не нужно для работы.
Файл index.html является отправной точкой приложения Vue.
Структура файла в папке src следующая:
├── App.vue
├── assets
│ └── logo.png
├── components
│ └── HelloWorld.vue
├── main.js
├── router.js
└── views
├── About.vue
└── Home.vue
Детальное объяснение:
- main.jsТочка входа приложения, которая загружает и инициализирует Vue вместе с корневым компонентом.
- app.vueКорневой компонент, который является отправной точкой при рендеринге всех остальных компонентов.
- 'components'Хранить компоненты пользовательского интерфейса
- router.js Определите URL-адреса и сопоставьте URL-адреса с соответствующими компонентами
- 'views' Хранить компоненты пользовательского интерфейса, привязанные к маршрутизатору
- 'asset' Храните статические ресурсы, такие как изображения и шрифты
Откройте файл /client/src/components/HelloWorld.vue. Это однофайловый компонент, состоящий из трех частей:
- шаблон: для частей HTML, специфичных для компонентов
- Скрипт: где логика компонента реализована через JavaScript
- стиль: для стилей CSS
Теперь запустите сервер разработки:
$ 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>
<div id="app">
<router-view/>
</div>
</template>
Теперь вы можете войти через браузерhttp://localhost:8080/pingСмотрите привет! сейчас.
Чтобы соединить клиентское приложение Vue с внутренним приложением Flask, мы можем использоватьaxios для отправки запросов Ajax.
Сначала установите соответствующую библиотеку:
$ npm install axios@0.18.0 --save
Обновите раздел сценария компонента в ping.vue следующим образом:
<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!. На самом деле, когда ответ возвращается из бэкэнда, мы устанавливаем указанное выше сообщение в значение данных из объекта ответа сервера.
Установить Bootstrap
Затем давайте добавим в приложение Bootstrap, популярную структуру CSS, чтобы мы могли быстро добавить некоторые стили.
Установить:
$ npm install bootstrap@4.3.1 --save
Игнорировать предупреждения для jquery и popper.js. Не добавляйте их в проект. Это будет подробно объяснено позже.
Импорт стилей из 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>
#app {
margin-top: 60px
}
</style>
Убедитесь, что Bootstrap подключается правильно с помощью кнопки и контейнера в компоненте Ping:
<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,
},
],
});
контрольная работа:http://localhost:8080
Наконец, давайте добавим форму в стиле Bootstrap к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.
Что будем строить?
Наша цель — разработать внутренний RESTful API для книг, реализованный на Python и Flask. Сам API должен следовать принципам проектирования RESTful и может использовать основные функции HTTP: GET, POST, PUT и DELETE.
Мы также будем использовать Vue для создания полного внешнего приложения на основе внутреннего API:
В этом руководстве обсуждается только путь счастливой сборки, обработка ошибок — это отдельное упражнение. Вы можете попытаться добавить правильную обработку ошибок на фронт и бэкенд самостоятельно, если понимаете.
ПОЛУЧИТЬ маршрут
Сервис-терминал
Добавьте список книг в 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
})
Запустите приложение фляги и проверьте маршрутизациюhttp://localhost:5000/books.
Хотите больше проблем? Для этой программы можно написать автоматизированный тест. ПроверятьздесьЕсть больше информации о ресурсах о тестировании приложения Flask.
клиент
Обновите компонент книг:
<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-для инструкции, создайте новую строку таблицы в каждой итерации. Значение индекса в качестве ключа для использования. Наконец, V-если для представления «да» или «нет», указывая, прочитал ли пользователь книгу
Bootstrap Vue
В следующем разделе мы будем использовать шаблон для добавления новой книги. Мы добавимBootstrap VueБиблиотека, предоставляющая набор компонентов Vue, использующих стили HTML и CSS на основе Bootstrap.
Зачем использовать библиотеку Bootstrap Vue? Начальная загрузкаmodalКомпонент используетjQuery, поэтому вам следует избегать использования Bootstrap с Vue в одном проекте, потому что Vue используетВиртуальный домдля обновления DOM. Другими словами, если вы используете jQuery для управления DOM, Vue не сможет узнать об этих операциях. Если вы должны использовать jQuery, по крайней мере, не используйте и Vue, и jQuery для одного и того же элемента DOM.
Установить:
$ npm install bootstrap-vue@2.0.0-rc.19 --save
Включите библиотеку Bootstrap Vue в Client/src/main.js:
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.
Что делать, если название уже существует? Или что, если у заголовка более одного автора? Вы можете проверить свои знания, попробовав решить эти задачи самостоятельно. Кроме того, что делать с неверными телами данных, такими как отсутствие заголовка, автора или чтения?
клиент
Давайте теперь добавим модальное окно POST на стороне клиента, чтобы добавить новую книгу в компонент «Книги», начиная с 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>
Что делает этот код?
- addBookForm привязан к вводу формы через v-modal. Когда один обновляется, другой тоже обновляется, это называется двусторонней привязкой. Найдите минутку, чтобы подумать об этом, как вы думаете, облегчит или усложнит это управление состоянием? Как с этим справляются React и Angular? На мой взгляд, двусторонняя привязка (наряду с изменчивостью) делает Vue более понятным, чем React, но менее масштабируемым в долгосрочной перспективе.
- onSubmit запускается, когда пользователь успешно отправляет форму. При отправке мы предотвращаем нормальное поведение браузера (evt.preitDefault()), закрываем модальный компонент (здесь $rens.addBookModal.hid()), запускаем метод addBook и очищаем форму (initForm()).
- addBook отправляет запрос POST в /books для добавления новой книги.
- Ознакомьтесь с остальными изменениями самостоятельно и при необходимости обратитесь к ним.Vue-документация.
Можете ли вы обнаружить потенциальные ошибки на клиенте или сервере? Обработайте их самостоятельно, чтобы улучшить пользовательский опыт.
Наконец, обновите кнопку «Добавить книгу» в шаблоне, чтобы отображать модальное окно при нажатии кнопки:
<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Сборка, чтобы после добавления книги пользователь мог вывести подсказку. Для этого мы создадим только новый компонент, потому что вы можете использовать эту функцию во многих компонентах.
Добавьте новый файл с именем Alert.vue в «Client/src/Components»:
<template>
<p>It works!</p>
</template>
Затем импортируйте его в раздел Script компонента Books и зарегистрируйте компонент:
<script>
import axios from 'axios';
import Alert from './Alert.vue';
...
export default {
data() {
return {
books: [],
addBookForm: {
title: '',
author: '',
read: [],
},
};
},
components: {
alert: Alert,
},
...
};
</script>
Теперь мы можем сослаться на новый компонент в разделе шаблона:
<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>
Обратите внимание на параметр реквизита в разделе сценария. Мы можем передавать сообщения из родительского компонента (Книги) следующим образом:
<alert message="hi"></alert>
Попробуйте эффект:
Проверятьdocsдля получения дополнительной информации о реквизите
Чтобы сделать его динамическим, чтобы вы могли передавать пользовательские сообщения, вы можете использовать выражения привязки в Books.vue:
<alert :message="message"></alert>
Добавьте сообщение к опции данных в Books.vue:
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() {
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();
});
},
Как эффект!
вызов:
- Подумайте, когда для showMessage должно быть установлено значение false, и обновите свой код.
- Попытка использовать компонент Alert вызывает ошибку.
- Предупреждение о рефакторингезакрывающийся.
PUT-маршрутизация
Сервис-терминал
Для обновления нам нужно использовать уникальный идентификатор, потому что мы не можем ожидать, что все заголовки будут уникальными. Мы можем использовать UUID стандартной библиотеки Python.
Обновить книги в 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 для добавления уникального идентификатора при добавлении новой книги:
@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
Найдите минутку, чтобы подумать об этом, как бы вы поступили в случае, когда идентификатор не существует? Что делать, если тело данных неверно? Кроме того, попробуйте реорганизовать цикл for во вспомогательном методе, чтобы сделать его более питоническим.
клиент
шаг:
- Добавляйте модальные окна и формы
- кнопка "обновить"
- Подключите Ajax-запросы
- Подсказка пользователя
- Кнопка "Отмена"
(1) Добавить модальные окна и формы
Во-первых, добавьте новый МОДАЛ в Шаблон, напишите ниже первого модала:
<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:
editForm: {
id: '',
title: '',
author: '',
read: [],
},
Задача: попробуйте использовать одно и то же модальное окно для обработки запросов POST и PUT вместо использования нового модального окна.
(2) Кнопка «Обновить»
Функция кнопки обновления в таблице обновлений:
<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);
},
(3) Подключите запрос AJAX
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();
});
},
(4) Подсказка пользователя
Обновление книги обновлений:
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();
});
},
(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 = [];
},
Обязательно просмотрите код, прежде чем продолжить. После этого протестируйте приложение. Убедитесь, что модальное окно отображается при нажатии кнопки, а поле ввода заполнено правильным значением.
Удалить маршрут
Сервис-терминал
Обновите обработчик маршрута:
@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 срабатывает, затем триггерный метод удаления книги. Этот метод удаляет запрос на бэкэнду. Когда ответ вернется, отображается сообщение и запускает GetBooks.
вызов:
- Вместо удаления после нажатия кнопки добавьте запрос подтверждения.
- Если в «Книгах» нет книг, отобразите сообщение, например «Нет книг! Добавьте одну».
Эпилог
В этом посте рассматриваются основы настройки CRUD-приложения с помощью Vue и Flask.
Проверьте свои собственные знания, начните с обзора этой статьи и выполните каждую из этих задач.
Если вы хотите узнать больше, вы можете просмотреть конкретный исходный код, адрес исходного кодаflask-vue-crud.
Спасибо за чтение.