Разработка одностраничного приложения PHP c помощью Yii 2.0 и Vue.js

На заре Интернета ряд статических HTML-файлов был связан вместе, чтобы сформировать веб-сайт. Щелчок по ссылкам страниц вызовет запросы к серверу, который ответит новым HTML-файлом.

Однако развитие JavaScript и появление AJAX сделали возможным отправку и получение данных с сервера без полной перезагрузки страницы. Эти технологии добавили динамизма веб-сайтам, открыв дверь для значительного прогресса в том, как веб-сайты работают сегодня. Например, с помощью JavaScript можно обрабатывать взаимодействие с пользователем и обновлять веб-сайты с помощью асинхронных запросов.

Эти типы веб-сайтов стали известны как одностраничные приложения (SPA). На фоне этого несколько библиотек и фреймворков JavaScript получили известность, особенно Vue.js.

В этой статье вы узнаете, как создать одностраничное приложение с использованием Vue.js и PHP-фреймворка Yii 2.0 ; CRUD API для библиотечного приложения, которое будет обрабатывать один основной ресурс: книги.

Предпосылки

В этом руководстве вам поможет базовое понимание Yii 2.0 и PHP. Тем не менее, я буду давать пояснения и ссылки на официальную документацию на протяжении всего руководства. Если вам неясна какая-либо концепция, вы можете просмотреть связанный материал, прежде чем продолжить обучение.

Вам также потребуется базовое понимание Vue.js и ES6 , чтобы помочь в создании SPA, а также следующее, установленное в вашей системе:

Создайте серверную часть

Создайте проект Yii 2.0

Для начала создайте новое приложение с именем vue_library_app и переключитесь в каталог проекта, используя следующие команды.

composer create-project --prefer-dist yiisoft/yii2-app-basic vue_library_app
cd vue_library_app

Затем запустите приложение с помощью следующей команды.

php yii serve

По умолчанию приложение будет обслуживаться по адресу http://localhost:8080/ . Откройте URL-адрес в своем браузере, где вы должны увидеть страницу приветствия, как показано ниже.

Домашняя страница приложения Yii 2 по умолчанию

Вернитесь к терминалу и нажмите Ctrl + C , чтобы выйти из приложения.

Настройка базы данных

Создайте новый каталог в корне приложения с именем db и в нем файл с именем app.db. Затем обновите config/db.php , чтобы он соответствовал следующему коду.

<?php

return [
    'class'   => 'yii\db\Connection',
    'dsn'     => 'sqlite:' . dirname(__DIR__) . '/db/app.db',
    'charset' => 'utf8',
];

Создать перенос книги

Затем создайте миграцию для таблицы базы данных. Приложение будет иметь одну сущность с именем Book, которая будет представлять книгу, доступную в библиотеке. Для этой статьи таблица book будет содержать название книги, автора, IBAN и год выпуска.

Чтобы создать миграцию, используйте приведенную yii migrate/createниже команду, указав имя создаваемой миграции ( create_book_table). При запросе подтверждения введите «да», чтобы миграция была создана.

php yii migrate/create create_book_table

По умолчанию файлы миграции находятся в каталоге миграции . Их имена файлов начинаются с буквы m и даты и времени создания в формате UTC, например, migrations/m<ГГММДД_ЧЧММСС>_create_book_table.php .

Отредактируйте safeUpфункцию migrations/m<ГГММДД_ЧЧММСС>_create_book_table.php , чтобы она соответствовала следующему коду.

public function safeUp()
{
        $this->createTable('book', [
            'id'           => $this->primaryKey(),
            'title'        => $this->string(),
            'author'       => $this->string(),
            'iban'         => $this->string(),
            'release_year' => $this->smallInteger(),
            'cover_image'  => $this->string()
        ]);
}

Заполнить базу данных

Чтобы вставить некоторые поддельные данные в таблицу, создайте начальную миграцию, выполнив следующую команду и ответив «да» на запрос.

php yii migrate/create seed_book_table

Откройте файл миграции, migrations/m<ГГММДД_ЧЧММСС>_seed_book_table.php и замените safeUpфункцию следующими двумя функциями.

public function safeUp() 
{
        $this->insertFakeBooks();
}

private function insertFakeBooks()
{
        $faker = \Faker\Factory::create();
        for ($i = 0; $i < 50; $i++) {
            $this->insert(
                'book',
                [
                    'title' => $faker->sentence(),
                    'author' => $faker->name,
                    'iban' => $faker->iban(),
                    'release_year' => (int)$faker->year,
                    'cover_image'  => $faker->imageUrl()
                ]
            );
        }
}

Затем запустите все миграции, используя следующую команду, введя «yes» и нажав Enter при появлении запроса:

php yii migrate

Вы можете убедиться, что база данных была создана и заполнена с помощью инструмента командной строки SQLite3 , выполнив следующую команду.

sqlite3 -table db/app.db "select * from book limit 10;"

Первые 10 книг в базе данных будут напечатаны в командной строке в красиво отформатированной таблице.

Если -tableон недоступен, попробуйте использовать -lineили -columnвместо него.

Создать модель книги

Вместо написания необработанных SQL-запросов для взаимодействия с базой данных мы будем использовать ActiveRecord для нашей модели. Это даст нам объектно-ориентированные средства доступа и хранения данных в базе данных.

Создайте ActiveRecord для объекта Book, выполнив приведенную ниже команду.

php yii gii/model --tableName=book --modelClass=Book

Введите «да» и нажмите Enter , когда будет предложено.

Класс модели создается в каталоге моделей и называется Book.php . Имея готовую модель, мы теперь можем создать контроллер для обработки вызовов RESTful API.

Создайте контроллер книги

Для начала создайте контроллер для объекта Book. Yii 2.0 предоставляет класс ActiveController , который обеспечивает общие действия RESTful , позволяя нам создавать конечные точки для обработки действий CRUD без необходимости самостоятельно писать шаблонный код.

Для этого выполните команду ниже.

php yii gii/controller \
    --controllerClass=app\\controllers\\BookController \
    --baseClass=yii\\rest\\ActiveController

Введите «да» и нажмите Enter , когда будет предложено.

Аргумент controllerClassуказывает имя создаваемого контроллера. Ожидается, что вы предоставите класс Fully-Qualified Namespaced (FQN).

Используемый \\при указании пространства имен экранирует \символ.

Класс контроллера хранится в каталоге контроллера и называется BookController.php . Откройте controllers/BookController.php и отредактируйте содержимое, чтобы оно соответствовало следующему.

<?php

namespace app\controllers;

use yii\data\ActiveDataProvider;
use yii\rest\ActiveController;

class BookController extends ActiveController
{
    public $modelClass = 'app\models\Book';

    public function actions(): array 
    {
        $actions = parent::actions();
        $actions['index'] = [
            'class' => 'yii\rest\IndexAction',
            'modelClass' => $this->modelClass,
            'prepareDataProvider' => fn() => new ActiveDataProvider(
                [
                    'query' => $this->modelClass::find(),
                    'pagination' => false,
                ]
            ),
        ];

        return $actions;
    }
}

В actionsметоде мы переопределяем поставщика данных, используемого для indexдействия, и отключаем разбиение на страницы, чтобы все книги возвращались одним запросом.

Далее в конфигурации приложения в config/web.php модифицируем urlManagerкомпонент. Там $configобъявляется массив, который содержит componentsмассив. В componentsмассиве раскомментируйте urlManagerкомпонент и обновите его, чтобы он соответствовал следующему коду.

'urlManager' => [
            'enablePrettyUrl'     => true,
            'showScriptName'      => false,
            'enableStrictParsing' => true,
            'rules'               => [
                ['class' => 'yii\rest\UrlRule', 'controller' => 'book'],
            ],
],

Этот код добавляет правило URL для контроллеров книг, чтобы можно было получить доступ к связанным данным и управлять ими с помощью красивых URL-адресов и значимых HTTP-команд .

Массив componentsтакже содержит requestмассив, содержащий конфигурацию компонента приложения запроса . Чтобы API мог анализировать ввод JSON, добавьте в requestмассив следующее.

'parsers' => [
    'application/json' => 'yii\web\JsonParser',
],

На данном этапе мы создали приложение, которое может обрабатывать следующие запросы:

  • GET (/books): список всех книг страница за страницей;
  • POST (/books): создать новую книгу;
  • GET (/books/<id>): вернуть информацию о книге с идентификатором <id>;
  • PATCH и PUT (/books/<id>): обновить книгу с помощью id <id>;
  • УДАЛИТЬ (/books/<id>): удалить книгу с id <id>;

Настроить интеграцию с Vue.js

Чтобы наш API правильно работал с интерфейсом, нам нужно внести некоторые дополнительные изменения. Идея состоит в том, чтобы страница индекса обслуживалась приложением Yii 2.0 с приложением Vue, связанным и указанным в <script>теге.

После загрузки JavaScript приложение Vue нацеливается на указанную <div>и загружает страницу, а затем обрабатывает взаимодействие с пользователем, не обновляя страницу, выполняя сетевые запросы к API, повторно отображая DOM по мере необходимости.

Первое, что нужно сделать, это зарегистрировать связанное приложение. Для этого откройте assets/AppAsset.php и отредактируйте $jsмассив, чтобы он соответствовал следующему коду.

public $js = [
        'app.js'
    ];

Это делает наше связанное приложение Vue доступным в представлениях Yii 2.0.

Затем отредактируйте urlManagerкомпонент $configмассива в config/web.php , чтобы он соответствовал следующему коду.

'urlManager'   => [
            'enablePrettyUrl'     => true,
            'showScriptName'      => false,
            'enableStrictParsing' => true,
            'rules'               => [
                ['class' => 'yii\rest\UrlRule', 'controller' => 'book'],
                '<url:(.*)>' => 'site/index',
            ],
],

Здесь мы добавляем правило маршрутизации для индексной страницы, чтобы загружать индексную страницу по умолчанию, предоставляемую Yii 2.0. Это правило гарантирует, что индексная страница будет возвращена для любого другого запроса к API Yii 2.0. Это предотвращает возврат API-интерфейсом ошибки 404 при обновлении нашего приложения. Вместо этого загружается индексная страница, а маршрутизатор Vue загружает соответствующий компонент.

Наконец, обновите содержимое views/layouts/main.php , чтобы оно соответствовало следующему коду.

<?php

/* @var $this \yii\\web\View */
/* @var $content string */

use app\assets\AppAsset;
use app\widgets\Alert;
use yii\bootstrap4\Html;

AppAsset::register($this);
?>
<?php
$this->beginPage() ?>
<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>" class="h-100">
<head>
    <meta charset="<?= Yii::$app->charset ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <?php
    $this->registerCsrfMetaTags() ?>
    <title>Vue Library App</title>
    <?php
    $this->head() ?>
</head>
<body>
<?php
$this->beginBody() ?>
<div id="app">

</div>
<?php
$this->endBody() ?>
</body>
</html>
<?php
$this->endPage() ?>

Здесь мы удаляем содержимое тела макета. Это означает, что представления Yii 2.0 не будут отображаться, поскольку мы больше не предоставляем их. Мы также объявляем DIV с идентификатором app. В этот DIV будет монтироваться приложение Vue при загрузке страницы.

Имея это на месте, мы готовы построить интерфейс!

Создайте интерфейс

Настройте зависимости проекта

Прежде чем мы начнем создавать приложение Vue, давайте настроим зависимости для проекта. Создайте новый файл с именем package.json и добавьте в него следующее.

{
  "dependencies": {
    "ant-design-vue": "^2.2.7",
    "axios": "^0.21.4",
    "core-js": "^3.6.5",
    "vue": "^3.0.0",
    "vue-router": "^4.0.6"
  },
  "devDependencies": {
    "@vue/compiler-sfc": "^3.2.11",
    "laravel-mix": "^6.0.31",
    "vue-loader": "^16.2.0"
  }
}

В дополнение к базовой зависимости Vue мы добавляем vue-router для маршрутизации на стороне клиента и Axios для сетевых запросов к API. Ant Design Vue будет использоваться для создания пользовательского интерфейса приложения.

Мы также добавляем некоторые зависимости, доступные только в среде разработки. Laravel Mix будет использоваться для компиляции нашего приложения Vue в один файл JavaScript, который можно импортировать на индексную страницу, обслуживаемую приложением Yii 2.0. vue — loader и компилятор используются для обработки файлов .vue , которые мы будем создавать.

Установите зависимости, выполнив следующую команду.

yarn install

Далее нам нужно настроить Laravel Mix и предоставить некоторую необходимую информацию, которая поможет связать приложение Vue. В корне проекта создайте новый файл с именем webpack.mix.js и добавьте в него следующий код.

const mix = require('laravel-mix');

mix.js('app/app.js', 'web/app.js')
      .setPublicPath('web')
      .vue();

Точкой входа для приложения Vue является индексный файл app.js , который находится в каталоге приложения . Мы создадим этот файл позже, но сейчас мы просто хотим, чтобы Laravel Mix знал, где он находится.

Мы также указываем выходной путь (и имя) для связанного файла JavaScript. Он будет называться app.js и храниться в веб- каталоге. Затем мы устанавливаем общедоступный путь к веб- каталогу.

Создайте Vue-приложение

В корне проекта создайте новый каталог с именем app. Этот каталог будет содержать весь код, относящийся к интерфейсу. В каталоге приложения создайте два файла: app.js и App.vue .

Добавьте следующее в app/app.js.

import {createApp} from 'vue';
import App from './App.vue';

const app = createApp(App);

app.mount('#app');

Затем добавьте следующий код в App.vue.

<template>
 <h1>Welcome to your Yii Powered Vue3 App!!!!</h1>
</template>

Чтобы связать приложение с нашими новыми изменениями, выполните следующую команду.

yarn mix

Когда Webpack завершит компиляцию и отобразит подтверждающее сообщение, запустите приложение с помощью следующей команды.

php yii serve

Затем перейдите на индексную страницу ( по умолчанию http://localhost:8080/ ), где вы должны увидеть приветственное сообщение, похожее на скриншот ниже.

Приветственное сообщение приложения Vue

На данный момент ничего захватывающего (за исключением того, что мы только что получили Vue и Yii 2.0, чтобы хорошо играть!). В следующих разделах мы создадим функции CRUD приложения.

Создание помощника API

В каталоге приложения создайте новый файл с именем api.js и добавьте в него следующий код.

import axios from 'axios';

const axiosClient = axios.create({
    baseURL: 'http://localhost:8080',
    responseType: 'json',
    headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
    },
});

export default {
    helpGet: url => axiosClient.get(url).then(res => res.data),
    helpPost: (url, data) => axiosClient.post(url, data).then(res => res.data),
    helpPatch: (url, data) => axiosClient.patch(url, data).then(res => res.data),
    helpDelete: (url) => axiosClient.delete(url)
}

Здесь мы создаем экземпляр Axios с baseUrlнабором URL-адреса нашего приложения Yii 2.0. Затем мы экспортируем четыре функции, чтобы помочь с четырьмя методами запросов, которые мы собираемся использовать (GET, POST, PATCH и DELETE). Помимо helpDelete()метода, обещание , возвращаемое каждым вспомогательным методом, может быть разрешено для получения данных, возвращаемых API.

Создайте компонент BooksList

Компонент BooksListотобразит все книги в библиотеке. Для начала создайте новый каталог с именем components в каталоге приложения и в этом каталоге новый файл с именем BooksList.vue.

Добавьте следующий код во вновь созданный файл app/components/BooksList.vue .

<template>
  <a-button
      type='primary'
      style='margin-bottom: 8px'
      @click='showAddBookForm'
  >
    Add Book
  </a-button>
  <a-table
      :dataSource='books'
      :columns='columns'
      rowKey='id'
      bordered
  >
    <template #action='{ record }'>
      <div>
        <a-button
            type='primary'
            @click='showBook(record.id)'
            style='margin-right: 5px'
            ghost
        >
          View
        </a-button>
        <a-button
            @click='showEditBookForm(record.id)'
            style='margin-right: 5px'
        >
          Edit
        </a-button>
        <a-popconfirm
            title='Delete book? This action cannot be undone'
            @confirm='deleteBook(record.id)'
            okText='Delete'
            okType='danger'
        >
          <template #icon>
            <WarningOutlined style='color: red'/>
          </template>
          <a-button danger>
            Delete
          </a-button>
        </a-popconfirm>
      </div>
    </template>
  </a-table>
</template>
<script>
import api from '../api';
import {
  PlusOutlined,
  EyeOutlined,
  EditOutlined,
  DeleteOutlined,
  WarningOutlined
} from '@ant-design/icons-vue';

export default {
  components: {
    PlusOutlined,
    EditOutlined,
    EyeOutlined,
    DeleteOutlined,
    WarningOutlined
  },
  data() {
    return {
      books: [],
      columns: [
        {
          title: 'Name',
          dataIndex: 'title',
          key: 'title',
          ellipsis: true
        },
        {
          title: 'Author',
          dataIndex: 'author',
          key: 'author',
        },
        {
          title: 'Release Year',
          dataIndex: 'release_year',
          key: 'release_year',
        },
        {
          title: 'Action',
          key: 'action',
          slots: {customRender: 'action'},
        },
      ]
    };
  },
  methods: {
    async deleteBook(bookId) {
      await api.helpDelete(`books/${bookId}`);
      this.books = this.books.filter(({id}) => id !== bookId);
    },
    showBook(bookId) {
      this.$router.push({name: 'book-item', params: {bookId}});
    },
    showAddBookForm() {
      this.$router.push('/book/add');
    },
    showEditBookForm(bookId) {
      this.$router.push({name: 'book-form', params: {bookId}});
    }
  },
  async mounted() {
    this.books = await api.helpGet('books');
  }
};
</script>

В этом компоненте наш шаблон состоит из кнопки для добавления новой книги и таблицы, показывающей существующие книги. В таблице есть три столбца, в которых указаны название, автор и год выпуска каждой книги. Также есть столбец действий с тремя вариантами просмотра, редактирования и удаления книг.

Компонент также прослушивает событие жизненного цикла монтирования , где он отправляет запрос API, чтобы получить все книги и установить их в компонент данных книг.

Создайте компонент BookForm

Далее мы создадим форму для добавления новой книги. В каталоге app/components создайте новый файл с именем BookForm.vue и добавьте в него следующий код.

<template>
  <a-card
      hoverable
      style='width: 100%'
      :loading='loading'
  >
    <a-form
        :model='book'
        :label-col='labelCol'
        :wrapper-col='wrapperCol'
        :rules='rules'
    >
      <a-form-item
          label='Book title'
          v-bind='validationErrors.title'
      >
        <a-input
            v-model:value='book.title'
        />
      </a-form-item>
      <a-form-item
          label='Author'
          v-bind='validationErrors.author'
      >
        <a-input
            v-model:value='book.author'
        />
      </a-form-item>
      <a-form-item
          label='IBAN'
          v-bind='validationErrors.iban'
      >
        <a-input
            v-model:value='book.iban'
        />
      </a-form-item>
      <a-form-item
          label='Release Year'
          v-bind='validationErrors.release_year'
      >
        <a-input
            v-model:value='book.release_year'
        />
      </a-form-item>
      <a-form-item
          label='Cover Image URL'
          v-bind='validationErrors.cover_image'
      >
        <a-input
            v-model:value='book.cover_image'
        />
      </a-form-item>
      <a-form-item
          :wrapper-col='{ span: 14, offset: 4 }'
      >
        <a-button
            size='large'
            type='primary'
            @click='handleSubmit'
        >
          {{ isEdit ? 'Update' : 'Create' }}
        </a-button>
        <a-button
            size='large'
            style='margin-left: 10px'
            @click='resetFields'
            v-if='!isEdit'
        >
          Reset
        </a-button>
        <a-button
            size='large'
            style='margin-left: 10px'
            @click='showAllBooks'
            danger
        >
          Cancel
        </a-button>
      </a-form-item>
    </a-form>
  </a-card>
</template>
<script>
import api from '../api';
import {Form} from 'ant-design-vue';
import {reactive} from 'vue';
import {useRouter} from 'vue-router'

const {useForm} = Form;

export default {

  setup(props) {
    let book = reactive({
      title: '',
      author: '',
      iban: '',
      release_year: '',
      cover_image: '',
    });

    const rules = reactive({
      title: [
        {
          required: true,
          message: 'Please provide book title',
          trigger: 'blur'
        },
      ],
      author: [
        {
          required: true,
          message: 'Please provide book author',
          trigger: 'blur'
        },
      ],
      iban: [
        {
          required: true,
          message: 'Please provide book IBAN',
          trigger: 'blur'
        },
      ],
      release_year: [
        {
          required: true,
          message: 'Please provide book release year',
          trigger: 'blur'
        },
        {
          length: 4,
          message: 'Length should be 4',
          trigger: 'blur'
        },
      ],
      cover_image: [
        {
          required: true,
          message: 'Please provide url for book cover image',
          trigger: 'blur'
        },
      ],
    });

    const {
      resetFields,
      validate,
      validateInfos: validationErrors
    } = useForm(book, rules);

    const router = useRouter();

    const handleSubmit = () => {
      validate()
          .then(
              async () => {
                const {bookId} = props;
                const updatedBook = !!bookId ?
                    await api.helpPatch(`books/${bookId}`, book) :
                    await api.helpPost('books', book);
                Object.assign(book, updatedBook);
                router.push({name: 'book-item', params: {bookId: book.id}});
              }
          )
          .catch(() => {
          });
    }

    return {
      resetFields,
      validationErrors,
      book,
      handleSubmit,
      rules
    };
  },
  props: ['bookId'],
  data() {
    return {
      isEdit: !!this.bookId,
      loading: !!this.bookId,
      labelCol: {span: 4},
      wrapperCol: {span: 14},
    }
  },
  methods: {
    async loadBook() {
      Object.assign(this.book, await api.helpGet(`books/${this.bookId}`));
      this.loading = false;
    },
    showAllBooks() {
      this.$router.push({name: 'books'});
    },
  },
  async mounted() {
    if (this.isEdit) {
      await this.loadBook();
    }
  }
};
</script>

Шаблон для этого компонента представляет собой форму с полями для названия, автора, IBAN, года выпуска и URL-адреса обложки добавляемой книги. Кроме того, в форму добавлены кнопка отправки, кнопка сброса и кнопка отмены.

Поскольку эта форма будет использоваться либо для создания новой книги, либо для редактирования существующей книги, мы проверяем, передается ли идентификатор книги в качестве реквизита в смонтированном событии жизненного цикла. Если указан идентификатор, выполняется запрос API для получения книги и заполнения полей формы.

При handleSubmit()отправке вызывается функция, которая проверяет форму перед отправкой соответствующего запроса POST или PATCH для создания или обновления книги соответственно. В случае успеха приложение перенаправляется к представлению, отображающему сведения о книге.

Создайте компонент BookItem

В каталоге app/components создайте новый файл с именем BookItem.vue и добавьте в него следующий код.

<template>
  <a-spin
      tip='Loading Book'
      v-if='book === null'
  >
  </a-spin>
  <a-card
      hoverable
      style='width: 60%'
      v-else
  >
    <template #cover>
      <img
          alt='example'
          :src='book.cover_image'
      />
    </template>
    <template
        class='ant-card-actions'
        #actions
    >
      <a-button
          @click='showAllBooks'
          type='primary'
          style='margin-right: 5px'
          ghost
      >
        Home
      </a-button>
      <a-button
          @click='showEditBookForm'
          style='margin-right: 5px'
      >
        Edit
      </a-button>
      <a-popconfirm
          title='Delete book? This action cannot be undone'
          @confirm='deleteBook'
          okText='Delete'
          okType='danger'
      >
        <template #icon>
          <WarningOutlined style='color: red'/>
        </template>
        <a-button danger>
          Delete
        </a-button>
      </a-popconfirm>
    </template>
    <a-card-meta
        :title='book.title'
        :description='`Book by ${book.author}`'
    >
      <template #avatar>
        <a-avatar
            src='<https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png>'
        />
      </template>
    </a-card-meta>
    <a-row style='margin-top: 50px'>
      <a-col :span='6'>
        <a-statistic
            title='Release Year'
            groupSeparator=''
            :value='book.release_year'
            style='margin-right: 50px'
        />
      </a-col>
      <a-col :span='18'>
        <a-statistic
            title='IBAN'
            :value='book.iban'
            groupSeparator=''
        />
      </a-col>
    </a-row>
  </a-card>
</template>
<script>
import api from '../api';
import {
  EditOutlined,
  ArrowLeftOutlined,
  WarningOutlined,
  DeleteOutlined
} from '@ant-design/icons-vue';

export default {
  props: ['bookId'],
  data() {
    return {
      book: null
    }
  },
  components: {
    EditOutlined,
    ArrowLeftOutlined,
    WarningOutlined,
    DeleteOutlined
  },
  methods: {
    async loadBook() {
      this.book = await api.helpGet(`books/${this.bookId}`);
    },
    showAllBooks() {
      this.$router.push({name: 'books'});
    },
    showEditBookForm() {
      this.$router.push({name: 'book-form', params: {bookId: this.bookId}});
    },
    async deleteBook() {
      await api.helpDelete(`books/${this.bookId}`);
      this.showAllBooks();
    }
  },
  async mounted() {
    await this.loadBook();
  }
};
</script>

В этом компоненте мы отображаем сведения о книге и добавляем параметры для отображения всех книг, редактирования книги и удаления книги. Когда компонент смонтирован, книга извлекается из API с использованием идентификатора книги, переданного в качестве реквизита.

Создать маршрутизатор

В каталоге приложения создайте новый файл с именем router.js и добавьте в него следующий код.

import {createRouter, createWebHistory} from 'vue-router';
import BooksList from './components/BooksList';
import BookItem from './components/BookItem';
import BookForm from './components/BookForm';

const router = createRouter({
    history: createWebHistory(),
    routes: [
        {
            name: 'books',
            path: '/',
            component: BooksList,
        },
        {
            name: 'book-form',
            path: '/book/edit/:bookId?',
            component: BookForm,
            props: true,
            alias: '/book/add'
        },
        {
            name: 'book-item',
            path: '/book/:bookId(\\d+)',
            component: BookItem,
            props: true
        },
    ],
});

export default router;

Обновите компонент приложения

Обновите app/App.vue , чтобы он соответствовал следующему коду.

<template>
  <div class='container'>
    <a-typography-title>Vue Library App</a-typography-title>
    <router-view></router-view>
  </div>
</template>

<script>
import BooksList from './components/BooksList.vue';
import BookItem from './components/BookItem';

export default {
  components: {
    BooksList,
    BookItem
  }
}
</script>

<style scoped>
.container {
  padding: 2%;
}
</style>

Используя компонент представления маршрутизатора, мы отобразим компонент, назначенный совпадающему маршруту.

Обновить app.js

Наконец, обновите app/app.js , чтобы он соответствовал следующему коду.

import { createApp } from 'vue';
import router from './router';
import App from './App.vue';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';

const app = createApp(App);

app.use(Antd);
app.use(router);
app.mount('#app');

Создайте файлы внешнего интерфейса

Затем свяжите приложение со всеми новыми изменениями, используя следующую команду.

yarn mix

Протестируйте приложение

Теперь протестируйте приложение, запустив его с помощью следующей команды:

php yii serve

Посмотреть список книг

Откройте http://localhost:8080/, где вы увидите список всех доступных книг в базе данных.

Список книг, отображаемых на главной странице

Создать книгу

Чтобы создать новую книгу, нажмите кнопку « Добавить книгу» . Вы будете перенаправлены на страницу, похожую на показанную ниже:

Создать новую книгу

Заполните форму соответствующим образом и нажмите кнопку « Создать » .

Обновить книгу

В списке, отображаемом на главной странице, нажмите кнопку « Изменить » , чтобы обновить сведения о любой книге в списке. Обновите одно или несколько доступных полей и нажмите кнопку « Обновить » .

Изменить сведения о существующей книге

Удалить книгу

Чтобы удалить книгу, нажмите кнопку « Удалить » в столбце «Действие» для книги и нажмите « Удалить » во всплывающем окне, чтобы подтвердить, что вы хотите удалить книгу.

Удалить книгу

Вывод

В этой статье вы узнали, как интегрировать приложение Vue в проект Yii 2.0. Чтобы помочь с этим, вы использовали Laravel Mix для объединения приложения Vue в один файл js, который регистрируется и внедряется на страницу индекса. Вы также использовали правило перехвата всех URL-адресов, чтобы гарантировать, что из бэкэнда Yii 2.0 возвращается только индексная страница, что дает приложению Vue полный контроль над маршрутизацией.