Создание одностраничного приложения с Vue.js и Flask. Часть 1 | OTUS

Создание одностраничного приложения с Vue.js и Flask. Часть 1

В этой статье мы настроим базовое CRUD-приложении, используя Vue и Flask. Сначала создадим новое Vue-приложение с помощью Vue CLI, а потом приступим к выполнению CRUD-операций посредством RESTful API на бэкенде под управлением Flask и Python.

Ожидаемый результат:

final_1-20219-a11d35.gif

Что нам потребуется: • Vue v2.5.2; • Vue CLI v2.9.3; • Python v3.6.5; • npm v6.1.0; • Node v10.3.0; • Flask v1.0.2.

Настраиваем Flask

Начнём работу с создания новой директории нашего проекта:

$ mkdir flask-vue-crud
$ cd flask-vue-crud

Во-первых, в директории flask-vue-crud нужно создать новый каталог и назвать его server. Далее следует создать и активировать виртуальную среду разработки:

$ python3.6 -m venv env
$ source env/bin/activate

Обратите внимание, что команды могут быть другими, если у вас другая среда разработки.

Теперь установим Flask и Flask-CORS:

(env)$ pip install Flask==1.0.2 Flask-Cors==3.0.4

Добавляем файл 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)

# sanity check route
@app.route('/ping', methods=['GET'])
def ping_pong():
    return jsonify('pong!')

if __name__ == '__main__':
    app.run()

Flask-CORS потребуется для отправки cross-origin-запросов, следовательно, надо включить общий доступ к ресурсам (CORS).

Запускаем наше приложение:

(env)$ python app.py

Чтобы выполнить проверку, вводим в строку браузера http://localhost:5000/ping. Ожидаемый результат — «Pong!».

Чтобы завершить работу сервера, нажимаем Ctrl + C. После этого можно перейти к фронтенду и настройкам Vue.

Настраиваем Vue

Чтобы создать индивидуальный темплейт проекта, мы будем задействовать мощный интерфейс Vue CLI. Устанавливаем его глобально:

$ npm install -g [email protected]

Теперь в каталоге flask-vue-crud выполняем команду, которая нужна для инициализации нового проекта Vue с конфигурацией webpack и под именем client :

$ vue init webpack client

Что касается webpack, то это инструмент для сборки и пакетный модуль, применяемый для создания, минимизации и объединения JS-файлов и прочих клиентских ресурсов.

При создании нового Vue-проекта вам потребуется ответить на несколько вопросов: 1. Установить vue-router? —  Да. 2. Использовать для линтинга кода ESLint? —  Да. 3. Выберите пресет ESLint —  Airbnb. 4. Настраивать юнит-тесты? —  Нет. 5. Настраивать тесты e2e с Nightwatch? —  Нет. 6. Запускать установку npm после создания проекта? — Да, применять NPM.

Vue-сборка в нашем случае —  Runtime + Compiler.

Vue CLI v3.7.0
? 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.

Что касается файла index.html, то он является отправной точкой нашего Vue-приложения.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>client</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- встроенные файлы вводятся автоматически -->
  </body>
</html>

Кроме того, у нас есть элемент

с идентификатором app. Это не что иное, как контейнер, который Vue станет применять для присоединения сгенерированных CSS и HTML во время создания пользовательского интерфейса.

Давайте посмотрим, на каталоги и файлы внутри src:

├── App.vue
├── assets
│ └──── logo.png
├── components
│ └──── HelloWorld.vue
├── main.js
├── router
└─── index.js

Что тут что: — main.js — точка входа в наше приложение, осуществляет загрузку и инициализацию Vue совместно с корневым компонентом; — App.vue — корневой компонент, из него рендерятся прочие компоненты (отправная точка); — assets — место для хранения статических ассетов типа шрифтов и изображений; — components — место для хранения UI-компонентов; — router — место для определения URL-адресов и сопоставления их с компонентами.

Файл client/src/components/HelloWorld.vue является компонентом Single File, разбитым на 3 различных подраздела: • template: для компонентного HTML; • script: тут компонентная логика реализована посредством JS; • style: для CSS-стилей.

Запускаем dev-сервер:

$ cd client
$ npm run dev

Перейдя по адресу http://localhost:8080, увидим:

default_vue_app_1-20219-f41b3d.jpg

Теперь добавим в папку client/src/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:

import Vue from 'vue';
import Router from 'vue-router';
import Ping from '@/components/Ping';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Ping',
      component: Ping,
    },
  ],
});

В client/src/App.vue удаляем изображение из темплейта:

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

После всего этого, в браузере должно отобразиться «Hello!»

Для соединения клиентского Vue-приложения с бэкендом на Flask, подойдёт библиотека axios. Установим её:

$ npm install [email protected] –save

Теперь следует обновить раздел script компонента в 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-выключение следующей строки
          console.error(error);
        });
    },
  },
  created() {
    this.getMessage();
  },
};
</script>

Что же, давайте запустим Flask-приложение в новом окне. Теперь по адресу http://localhost:8080 отобразится «pong!».

Настраиваем Bootstrap

Чтобы быстро настраивать стиль нашего приложения, добавляем Bootstrap:

$ npm install [email protected] --save

Предупреждения для jquery и popper.js игнорируем — они нам не нужны.

Стили Bootstrap импортируем в client/src/main.js:

import 'bootstrap/dist/css/bootstrap.css';
import Vue from 'vue';
import App from './App';
import router from './router';

Vue.config.productionTip = false;

/* eslint-отключение no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
});

Обновляем раздел style в client/src/App.vue:

<style>
#app {
  margin-top: 60px
}
</style>

Теперь нужно убедиться, что Bootstrap подключён корректно, для чего используем Container и Button в компоненте Ping:

<template>
  <div class="container">
    <button type="button" class="btn btn-primary">{{ msg }}</button>
  </div>
</template>

Запускаем dev-сервер:

$ npm run dev

Ожидаемый результат:

bootstrap_1-20219-416217.png

Теперь добавляем в новый файл Books.vue компонент Books:

<template>
  <div class="container">
    <p>books</p>
  </div>
</template>

И обновляем роутер:

import Vue from 'vue';
import Router from 'vue-router';
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,
    },
  ],
  mode: 'hash',
});

Как обычно, тестируем: 1. http://localhost:8080. 2. http://localhost:8080/#/ping.

Чтобы избавиться от хеша в URL, меняем mode на history, дабы использовать для навигации history API-браузера:

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Books',
      component: Books,
    },
    {
      path: '/ping',
      name: 'Ping',
      component: Ping,
    },
  ],
  mode: 'history',
});

Добавляем таблицу в 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>
                <button type="button" class="btn btn-warning btn-sm">Update</button>
                <button type="button" class="btn btn-danger btn-sm">Delete</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>

Ожидаемый результат:

books_component_1_1540x501_1-20219-9f5cf9.jpg

Итак, тепреь можно создавать функциональность CRUD-приложения.

Что предстоит создавать?

Наша задача — разработка бэкенда RESTful API, работающего на Python и Flask. API должен следовать принципам RESTful-разработки, применяя основные HTTP-команды: POST, PUT, GET, DELETE.

final_1-20219-a11d35.gif

GET-маршрут

Сервер

Начнём с добавления списка книг в 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.

Клиент

Обновляем компонент:

<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>
                <button type="button" class="btn btn-warning btn-sm">Update</button>
                <button type="button" class="btn btn-danger btn-sm">Delete</button>
              </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-отключение следующей строки
          console.error(error);
        });
    },
  },
  created() {
    this.getBooks();
  },
};
</script>

После того, как компонент инициализирован, вызываем метод getBooks() посредством Lifecycle Hook.

Для просмотра книг в темплейте используется директива v-for, создающая на каждой итерации новую строку таблицы. При этом значение индекса применяется в виде ключа (key). Потом применяется директива v-if, обеспечивающая отображение Yes либо No (читал ли пользователь книгу).

books_component_2_1540x689_1-20219-c3dbe9.jpg

Bootstrap Vue

Далее используем компонент Modal, необходимый для добавления новых книг. Пригодится библиотека Bootstrap Vue, предоставляющая нам набор Vue-компонентов, стилизованных посредством CSS и HTML на основе Bootstrap.

Почему мы выбрали Bootstrap Vue? Потому что Modal Bootstrap использует jQuery, а нам желательно избегать применения в одном проекте jQuery и Vue, ведь последний задействует для обновления DOM-структуры Virtual Dom. В результате, если вы используете для манипуляций с DOM jQuery, Vue об этом не узнает.

Выполняем установку:

$ npm install [email protected] –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';
import router from './router';

Vue.config.productionTip = false;

Vue.use(BootstrapVue);

/* eslint-отключение no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
});

Это ещё далеко не всё, продолжение смотрите в следующей части нашей статьи.

Источник — «Developing a Single Page App with Flask and Vue.js».

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто