如何用Yii 2.0和Vue.js在PHP中建立一个单页应用程序

744 阅读8分钟

在网络的早期,一系列静态的HTML文件会被链接在一起,形成一个网站。点击页面链接会触发对服务器的请求,服务器会以新的HTML文件作为回应。

然而,JavaScript的兴起和AJAX的出现,使得从服务器发送和接收数据成为可能*,而不需要*重新加载整个页面。这些技术为网站增加了活力,为今天网站运作方式的巨大进步打开了大门。例如,通过使用JavaScript,可以通过异步请求处理用户互动和更新网站。

这些类型的网站被称为单页应用程序(SPA)。在此基础上,一些JavaScript库和框架已经崛起,其中最突出的是Vue.js

在这篇文章中,你将学习如何使用Vue.js和Yii 2.0PHP框架建立一个单页应用程序;一个图书馆应用程序的CRUD API,它将处理一个主要资源:书籍。

前提条件

对Yii 2.0和PHP的基本了解将对本教程有帮助。然而,我将提供解释,并在整个教程中提供官方文档的链接。如果你对任何概念不清楚,你可以在继续学习本教程之前查看链接材料。

你还需要对Vue.js和ES6有一个基本的了解,以帮助建立SPA,同时在你的系统上安装以下东西:

  • 启用PDO扩展的PHP7.4。
  • 全局安装的Composer
  • 一个本地数据库服务器。虽然本教程将使用SQLite,但你可以自由选择你喜欢的数据库服务。
  • 一个JavaScript包管理器,如NPM,或Yarn(我将使用它)。

构建后端

创建一个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,你应该看到欢迎页面,如下图所示:

The default Yii 2 application home page

返回到终端,按Ctrl + C退出应用程序。

设置数据库

在应用程序的根目录下创建一个名为db的新目录*,并在其中创建一个名为app.db的文件接下来,更新config/db.php*以匹配以下代码:

<?php

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

创建一个图书迁移

接下来,为数据库表创建一个迁移。该应用程序将有一个名为Book 的实体,它将代表图书馆中的一本书。对于这篇文章,图书表将包含图书的名称、作者、IBAN和发行年份。

要创建迁移,请使用下面的yii migrate/create 命令,提供要创建的迁移的名称(create_book_table )。当要求确认时,输入 "是 "就可以创建迁移了:

php yii migrate/create create_book_table

默认情况下,迁移文件位于migrations目录下。它们的文件名以字母m和创建时的UTC日期时间为前缀,例如,migrations/m<YYMMDD_HHMMSS>_create_book_table.php

编辑migrations/m<YYMMDD_HHMMSS>_create_book_table.phpsafeUp 函数,使之与以下代码相匹配:

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<YYMMDD_HHMMSS>_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 "并按回车键

php yii migrate

你可以使用SQLite3命令行工具,通过运行下面的命令来验证数据库是否已经被创建并播种:

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

数据库中的前10本书将被打印到命令行中,并有一个很好的格式化的表格。

如果-table ,请尝试使用-line-column 来代替。

创建一个图书模型

我们将使用ActiveRecord作为我们的模型,而不是编写原始的SQL查询来与数据库进行交互。这样做将给我们提供一种面向对象的方法来访问和存储数据库中的数据。

通过运行下面的命令,为Book实体创建一个ActiveRecord:

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

键入 "是",并在提示时按回车键

模型类在models目录下被创建,并命名为Book.php。有了这个模型,我们现在可以创建一个控制器来处理RESTful API调用。

创建Book控制器

首先,为Book实体创建一个控制器。Yii 2.0提供了一个ActiveController类,它提供了常见的[RESTful]动作,允许我们创建端点来处理CRUD动作,而不需要自己编写模板代码的压力。

要做到这一点,运行下面的命令:

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

输入 "yes",并在出现提示时按回车键

controllerClass 参数指定要创建的控制器的名称。你应该提供一个完全合格的命名(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> 的书。
  • DELETE (/books/):删除id为<id> 的书。

配置与Vue.js的集成

为了使我们的API与前端正常工作,我们需要做一些额外的改变。我们的想法是让索引页由Yii 2.0应用程序提供,并在<script> 标签中捆绑和引用Vue应用程序。

一旦JavaScript被加载,Vue应用程序就会瞄准指定的<div> ,并加载页面,然后处理用户交互,而不刷新页面,同时向API发出网络请求,根据需要重新渲染DOM。

要做的第一件事是注册捆绑的应用程序。要做到这一点,打开assets/AppAsset.php并编辑$js 数组以匹配以下代码:

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

这使得我们绑定的Vue应用程序在Yii 2.0视图中可用。

接下来,在config/web.php中编辑$config 数组中的urlManager 组件,以匹配以下代码:

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

在这里,我们为索引页添加一个路由规则,加载Yii 2.0提供的默认索引页。这条规则确保任何其他对Yii 2.0 API的请求都会返回索引页。这样做可以防止我们刷新应用程序时,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,ID为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和用于向API发出网络请求的AxiosAnt Design Vue将被用来创建应用程序的UI

我们还添加了一些只在开发环境中可用的依赖项.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应用程序的入口点是indexapp.js文件,它位于app目录下。我们以后会创建这个文件,但现在我们只想让Laravel Mix知道它的位置。

我们还指定了捆绑的JavaScript文件的输出路径(和名称)。它将被命名为app.js并存储在web目录中.然后, 我们将公共路径设置为web目录.

创建Vue应用程序

在项目的根部,创建一个名为app的新目录*。这个目录将包含所有与前端有关的代码。在app目录下创建两个文件:app.jsApp.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 application welcome message

目前没有什么令人兴奋的事情(除了我们刚刚让Vue和Yii 2.0玩得很好!)。在接下来的章节中,我们将建立应用程序的CRUD功能。

创建一个API帮助器

app目录下,创建一个名为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 ,设置为我们Yii 2.0应用程序的URL。然后我们导出四个函数来帮助我们打算使用的四个请求方法(GET、POST、PATCH和DELETE)。除了helpDelete() ,每个帮助方法返回的Promise可以被解析以检索API返回的数据。

创建一个BooksList组件

BooksList 组件将显示图书馆中的所有书籍。首先,在app目录下创建一个名为component的新目录,并在这个目录下创建一个名为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>

在这个组件中,我们的模板包括一个添加新书的按钮和一个显示现有书籍的表格。该表有三列,显示每本书的名称、作者和发行年份。还有一个行动栏,有三个选项,可以查看、编辑和删除书籍。

该组件还监听mounted lifecycle事件,在该事件中,它提出一个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等字段。此外,表单中还添加了一个提交按钮、重置按钮和取消按钮。

因为这个表单将被用于创建新书或编辑现有的书,所以我们检查书的ID是否在安装的生命周期事件中作为一个道具被传递。如果提供了id,就会发出一个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>

在这个组件中,我们显示书的细节,并添加选项来显示所有的书,编辑书,和删除书。当该组件被安装时,使用作为Prop传递的书籍ID从API中检索该书。

创建一个 路由器

app目录下,创建一个名为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/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/](http://localhost:8080/)在那里你会看到数据库中所有可用的书籍列表。

List of books displayed on the index page

创建一本书

要创建一个新书,请点击添加书籍按钮。你将被转到一个类似于下图的页面。

Create a new book

适当地填写表格并点击创建按钮。

更新一本书

从索引页上显示的列表中,点击编辑 按钮,更新列表中任何一本书的细节。更新一个或多个可用的字段,然后点击更新按钮。

Edit an existing book details

删除一本书

要删除一本书,请点击该书行动栏中的删除 按钮,并在弹出的窗口中点击删除,以确认你想删除该书。

Delete a book

总结

在这篇文章中,你学到了如何将一个Vue应用程序集成到Yii 2.0项目中。为了帮助实现这一目标,你使用Laravel Mix将Vue应用程序捆绑到一个单一的js文件中,该文件被注册并注入到索引页面中。你还使用了一个全面的URL规则,以确保只有索引页从Yii 2.0后端返回,从而让Vue应用程序完全控制路由。

通过这种方式来管理项目,对于小团队来说特别有帮助,因为在小团队中,开发人员会定期维护应用程序各层的代码。因为所有的代码都在一个项目中,所以代码导航就变得更容易。

本教程的整个代码库可在GitHub上找到。请自由地进一步探索。

编码愉快!