翻译进度
16
分块数量
7
参与人数

教程:使用 Vue.js 和 Laravel 共建一个简单的 CRUD 应用

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。

file

CURD (增删改查)是数据存储的基本操作,也是你作为 Laravel 开发人员首先要学习的内容之一

但是,如果要结合以 Vue.js 作为前端的应用程序该注意哪些问题呢?首先,因为现在的操作不刷新页面,所以你需要异步 CURD 。因此,你需要确保数据在前后端的一致性。

在本教程中,我会演示如何开发完整的 Laravel&Vue.js 应用程序和每个 CURD 的例子。 AJAX 是连接前后端的关键,所以,我会使用 Axios 作为 HTTP 客户端。我还将向您展示一些处理这种体系结构的用户体验方面缺陷的方法。

你可以在  GitHub 中查看完整的项目。

Bard 翻译于 3周前

演示 app

这是一个让使用者创建一个 “Cruds“ 的全栈应用,当我进入这个应用时,它会创造很多不可思议的东西。外星人独特的名称和可以在红色,绿色和黑色的自由转换。

Cruds 应用展示在主页,你可以通过 add 按钮添加 Cruds , delete 按钮删除它们,或者更新它们的颜色。

file

dengminfeng 翻译于 3周前

Laravel 后端的 CRUD

我们将使用 Laravel 后端开始本教程,来完成 CRUD 操作。我将保持这一部分简短,因为 Laravel 的 CRUD 是其他地方广泛涉及的主题.

总之,我们完成以下操作

  • 设置数据库
  • 通过资源控制器来编写一个 RESTful API 的路由
  • 在控制器中定义方法,来完成 CRUD 操作
hedeqiang 翻译于 3周前

查看其他 1 个版本

Database

首先是迁移,我们的 Cruds 有两个属性:名称和颜色,我们将其设置为 text 类型

2018_02_02_081739_create_cruds_table.php

<?php

...

class CreateCrudsTable extends Migration
{
  public function up()
  {
    Schema::create('cruds', function (Blueprint $table) {
      $table->increments('id');
      $table->text('name');
      $table->text('color');
      $table->timestamps();
    });
  }

  ...
}
...
hedeqiang 翻译于 3周前

API

现在我们来设置 RESTful API 路由. 这个 resource 方法将自动创建我们所需要的所有操作. 但是, 我们不需要 editshow 和 store 这几个路由,因此我们需要排除它们.

routes/api.php

<?php

Route::resource('/cruds', 'CrudsController', [
  'except' => ['edit', 'show', 'store']
]);

有了这些, 我们现在可以在API中使用以下路由:

HTTP 方法 地址 方法 路由名
GET /api/cruds index cruds.index
GET /api/cruds/create create cruds.create
PUT /api/cruds/{id} update cruds.update
DELETE /api/cruds/{id} destroy cruds.destroy
黄志成 翻译于 3周前

控制器

我们现在需要在控制器中实现这些操作:

app/Http/Controllers/CrudsController.php

<?php

namespace App\Http\Controllers;

use App\Crud;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Faker\Generator;

class CrudsController extends Controller
{
  // Methods
}

我们先简要概述下每种方法:

create 方法。我们使用 Laravel 附带的 Faker 包,为 Crud 随机生成名称和颜色字段 。随后,我们将新生成的数据作为 JSON 返回。

<?php

...

public function create(Generator $faker)
{
  $crud = new Crud();
  $crud->name = $faker->lexify('????????');
  $crud->color = $faker->boolean ? 'red' : 'green';
  $crud->save();

  return response($crud->jsonSerialize(), Response::HTTP_CREATED);
}

index方法。我们使用 index 方法返回 Cruds 的全部数据。在一个更严肃的应用中,我们会使用分页,但是现在我们尽量保持简洁。

<?php

...

public function index()
{
  return response(Crud::all()->jsonSerialize(), Response::HTTP_OK);
}

update。此方法允许客户端更改 Crud 的颜色。

<?php

...

public function update(Request $request, $id)
{
  $crud = Crud::findOrFail($id);
  $crud->color = $request->color;
  $crud->save();

  return response(null, Response::HTTP_OK);
}

destroy。 删除 Cruds 的方法。

<?php

...

public function destroy($id)
{
  Crud::destroy($id);

  return response(null, Response::HTTP_OK);
}
hedeqiang 翻译于 2周前

Vue.js app

Now for our Vue single-page app. We'll begin by creating a single-file component to represent our Cruds called CrudComponent.vue.

file

This component is just for display and doesn't have much logic. Here are the noteworthy aspects:

  • The image shown depends on the color of the Crud (either red.png or green.png)
  • Has a delete button which triggers a method del on click, which emits an event delete with the ID of the Crud
  • Has an HTML select (for choosing the color) which triggers a method update on change, which emits an event update with the ID of the Crud and the new color selected

resources/assets/js/components/CrudComponent.vue

<template>
  <div class="crud">
    <div class="col-1">
      <img :src="image"/>
    </div>
    <div class="col-2">
      <h3>Name: {{ name | properCase }}</h3>
      <select @change="update">
        <option
          v-for="col in [ 'red', 'green' ]"
          :value="col"
          :key="col"
          :selected="col === color ? 'selected' : ''"
        >{{ col | properCase }}</option>
      </select>
      <button @click="del">Delete</button>
    </div>
  </div>
</template>
<script>
  export default {
    computed: {
      image() {
        return `/images/${this.color}.png`;
      }
    },
    methods: {
      update(val) {
        this.$emit('update', this.id, val.target.selectedOptions[0].value);
      },
      del() {
        this.$emit('delete', this.id);
      }
    },
    props: ['id', 'color', 'name'],
    filters: {
      properCase(string) {
        return string.charAt(0).toUpperCase() + string.slice(1);
      }
    }
  }
</script>
<style>...</style>

The other component in this project is App.js. This is where all the interesting logic occurs so we're going to go through this one step-by-step.

Let's begin with the template. This has the following jobs:

  • Display our Cruds with the crud-component component discussed above
  • Loop through an array of Crud objects (in the array cruds), with each mapping to an instance of crud-component. We pass all the properties of a Crud through to the component as props, and set up listeners for the update and delete events coming from the component
  • We also have an Add button that will create new Cruds by triggering a method create on click

resources/assets/js/components/App.vue

<template>
  <div id="app">
    <div class="heading">
      <h1>Cruds</h1>
    </div>
    <crud-component
      v-for="crud in cruds"
      v-bind="crud"
      :key="crud.id"
      @update="update"
      @delete="del"
    ></crud-component>
    <div>
      <button @click="create()">Add</button>
    </div>
  </div>
</template>

Here's the script from App.js. Let's talk this out too:

  • We start with a function Crud that creates new objects used to represent our Cruds. Each has an ID, color, and name
  • We import the adjacent CrudComponent
  • The component definition contains the array cruds as a data property. I've also stubbed methods for each CRUD operation which will be populated in the next section

resources/assets/js/components/App.vue

<template>...</template>
<script>
  function Crud({ id, color, name}) {
    this.id = id;
    this.color = color;
    this.name = name;
  }

  import CrudComponent from './CrudComponent.vue';

  export default {
    data() {
      return {
        cruds: []
      }
    },
    methods: {
      create() {
        // To do
      },
      read() {
        // To do
      },
      update(id, color) {
        // To do
      },
      del(id) {
        // To do
      }
    },
    components: {
      CrudComponent
    }
  }
</script>

Triggering CRUD from the frontend with AJAX

All the CRUD operations in a full-stack app will be executed in the backend since that's where the database is. However, the triggering of CRUD operations will often happen in the frontend.

As such, an HTTP client (something that can communicate between our front and backends across the internet) will be of importance here. Axios is a great HTTP client that comes pre-installed with the default Laravel frontend.

Let's look at our resource table again, as each AJAX call will need to target a relevant API endpoint:

Verb Path Action Route Name
GET /api/cruds index cruds.index
GET /api/cruds/create create cruds.create
PUT /api/cruds/{id} update cruds.update
DELETE /api/cruds/{id} destroy cruds.destroy

Read

Let's begin with the read method. This method is responsible for retrieving our Cruds from the backend and will target the index action of our Laravel controller, thus using the GET endpoint /api/cruds.

We can set up a GET call with window.axios.get, as the Axios library has been aliased as a property of the window object in the default Laravel frontend setup.

Axios methods like getpost etc return a promise. We chain a then method with a callback to access the response. The object resolved can be destructured to allow convenient access to the data property in the callback, which is the body of the AJAX response.

resources/assets/js/components/App.vue

...

methods() {
  read() {
    window.axios.get('/api/cruds').then(({ data }) => {
      // console.log(data)
    });
  },
  ...
}

/*
Sample response:

[
  {
    "id": 0,
    "name": "ijjpfodc",
    "color": "green",
    "created_at": "2018-02-02 09:15:24",
    "updated_at": "2018-02-02 09:24:12"
  },
  {
    "id": 1,
    "name": "wjwxecrf",
    "color": "red",
    "created_at": "2018-02-03 09:26:31",
    "updated_at": "2018-02-03 09:26:31"
  }
]
*/

As you can see, the Cruds are returned in a JSON array. Axios automatically parses the JSON and gives us JavaScript objects, which is nice. Let's iterate through these in the callback, then create new Cruds with our Crud factory function, then push them to the cruds array data property i.e. this.cruds.push(...).

resources/assets/js/components/App.vue

...

methods() {
  read() {
    window.axios.get('/api/cruds').then(({ data }) => {
      data.forEach(crud => {
        this.cruds.push(new Crud(crud));
      });
    });
  },
},
...
created() {
  this.read();
}

Note: We need to trigger the read method programmatically when the app loads. We do this from the created hook, which works, but is not very efficient. It'd be far better to get rid of the read method altogether and just include the initial state of the app inlined into the document head when the first loads.

With that done, we can now see the Cruds displayed in our app when we load it:

file

Update (and syncing state)

The update action requires us to send form data, i.e. color, so the controller knows what to update. The ID of the Crud is given in the endpoint.

This is a good time to discuss an issue I mentioned at the beginning of the article: with full-stack apps, you must ensure the state of the data is consistent in both the front and backends.

In the case of the update method, we could update the Crud object in the frontend app instantly before the AJAX call is made, since we already know the new state.

However, we don't perform this update until the AJAX call completes. Why? The reason is that the action might fail for some reason: the internet connection might drop, the updated value may be rejected by the database, or some other reason.

If we wait until the server responds before updating the frontend state, we can be sure the action was successful and the front and backend data is synchronized.

resources/assets/js/components/App.vue

methods: {
  read() {
    ...
  },
  update(id, color) {
    window.axios.put(`/api/cruds/${id}`, { color }).then(() => {
      // Once AJAX resolves we can update the Crud with the new color
      this.cruds.find(crud => crud.id === id).color = color;
    });
  },
  ...
}

You might argue its bad UX to wait for the AJAX to resolve before showing the changed data when you don't have to, but I think it's much worse UX to mislead the user into thinking a change is done, when in fact, we aren't sure if it is done or not.

Create and Delete

Now that you understand the key points of the architecture, you will be able to understand these last two operations without my commentary:

resources/assets/js/components/App.vue

methods: {
  read() {
    ...
  },
  update(id, color) {
    ...
  },
  create() {
    window.axios.get('/api/cruds/create').then(({ data }) => {
      this.cruds.push(new Crud(data));
    });
  },
  del(id) {
    window.axios.delete(`/api/cruds/${id}`).then(() => {
      let index = this.cruds.findIndex(crud => crud.id === id);
      this.cruds.splice(index, 1);
    });
  }
}

加载界面 和 禁止互动


你应该知道,我们这个项目VUE前端的CRUD操作都是异步方式的,所以前端AJAX请求服务器并等待服务器响应返回响应,总会有一点延迟。因为用户不知道网站在做什么,此空档期用户的体验不是很好,这问题关联到UX。

为了改善这UX问题,因此最好添加上一些加载界面并在等待当前操作解决时禁用任何交互。这可以让用户知道网站在做了什么,而且可以确保数据的状态。

Vuejs有很多很好的插件能完成这个功能,但是在此为了让学者更好的理解,做一些简单的快速的逻辑来完成这个功能,我将创建一个半透明的div,在AJAX操作过程中覆盖整个屏幕,这个逻辑能完成两个功能:加载界面和禁止互动。一石两鸟,完美~

resources/views/index.blade.php

<body>
<div id="mute"></div>
<div id="app"></div>
<script src="js/app.js"></script>
</body>
Joooohnnnn 翻译于 5天前

To do this, we'll toggle the value of a boolean mute from false to true whenever AJAX is underway, and use this value to show/hide the div.

resources/assets/js/components/App.vue

export default {
  data() {
    return {
      cruds: [],
      mute: false
    }
  },
  ...
}

Here's how we implement the toggling of mute in the update method. When the method is called, mute is set to true. When the promise resolves, AJAX is done so it's safe for the user to interact with the app again, so we set mute back to false.

resources/assets/js/components/App.vue

update(id, color) {
  this.mute = true;
  window.axios.put(`/api/cruds/${id}`, { color }).then(() => {
    this.cruds.find(crud => crud.id === id).color = color;
    this.mute = false;
  });
},

You'll need to implement the same thing in each of the CRUD methods, but I won't show that here for brevity.

To make our loading indicator markup and CSS, we add the element <div id="mute"></div> directly above our mount element <div id="app"></div>.

As you can see from the inline style, when the class on is added to <div id="mute">, it will completely cover the app, adding a greyish tinge and preventing any click events from reaching the buttons and selects:

resources/views/index.blade.php

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="csrf-token" content="{{ csrf_token() }}">
  <title>Cruds</title>
  <style>
    html, body {
      margin: 0;
      padding: 0;,
      height: 100%;
      width: 100%;
      background-color: #d1d1d1
    }
    #mute {
      position: absolute;
    }
    #mute.on {
      opacity: 0.7;
      z-index: 1000;
      background: white;
      height: 100%;
      width: 100%;
    }
  </style>
</head>
<body>
<div id="mute"></div>
<div id="app"></div>
<script src="js/app.js"></script>
</body>
</html>

The last piece of the puzzle is to toggle the on class by utilizing a watch on the value of mute, which calls this method each time mute changes:

export default {
  ...
  watch: {
    mute(val) {
      document.getElementById('mute').className = val ? "on" : "";
    }
  }
}

With that done, you now have a working full-stack Vue/Laravel CRUD app with a loading indicator. Here it is again in its full glory:

file

Don't forget to grab the code in this GitHub repo and leave me a comment if you have any thoughts or questions!

本文章首发在 LearnKu.com 网站上。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://vuejsdevelopers.com/2018/02/05/v...

译文地址:https://learnku.com/vuejs/t/24724

参与译者:7
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!