使用Laravel 5和AngularJS构建时间跟踪器–第2部分

This is the second of a two-part series on using Laravel 5 and AngularJS together to build a simple time tracking application. If you've gone through part 1, you'll have seen that we put together the front-end first and used a simple JSON file with some mocked-up data to test with. We left off with the ability to add new time entries and have the total time from all of them display on the side. We didn't include any way to edit or delete the time entries, and of course there was no persistence to a database.

这是将Laravel 5和AngularJS一起使用以构建简单的时间跟踪应用程序的两部分系列的第二部分。 如果您已经读完了第1部分 ,您将看到我们首先将前端放在一起,并使用了一个简单的JSON文件和一些模拟数据进行测试。 我们放弃了添加新时间条目的功能,并在侧面显示了所有时间的总时间。 我们没有提供任何编辑或删除时间条目的方法,当然,数据库也没有持久性。

In this part we will complete the application so that the time entries get stored in a database and our Angular front-end and Laravel backend work together to create, read, update and delete from it.

在这一部分中,我们将完成应用程序,以便将时间条目存储在数据库中,并且我们的Angular前端和Laravel后端可以一起创建,读取,更新和删除数据库。

安装Laravel (Installing Laravel)

To get us started, let's grab a fresh install of Laravel. We won't go into great detail about the Laravel installation process here, and if you're using Laravel for the first time there might be some snags you run into when setting it up. The Laravel docs are the best place to go to get full instructions on installation.

为了让我们开始,我们来重新安装Laravel。 我们不会在这里详细介绍Laravel的安装过程,如果您是第一次使用Laravel,在安装时可能会遇到一些麻烦。 Laravel文档是获得完整安装说明的最佳去处。

In the terminal, navigate to where your localhost files are served from. Assuming you have Composer and the Laravel installer properly installed globally, from the command line:

在终端中,导航到提供localhost文件的位置。 假设您已经从命令行正确地全局安装了Composer和Laravel安装程序:

laravel new time-tracker-2

This command will create a new directory called time-tracker-2 and will populate it with Laravel 5 files. It also does a lot of the initial setup work for us, like setting the application key. With Laravel's Artisan CLI we can spin up a web server. Let's do that now to make sure the application is working. From the command line,

该命令将创建一个名为time-tracker-2的新目录,并使用Laravel 5文件填充该目录。 它还为我们完成了许多初始设置工作,例如设置应用程序密钥。 使用Laravel的Artisan CLI,我们可以启动Web服务器。 现在让我们这样做,以确保该应用程序正常运行。 在命令行中

cd time-tracker-2
php artisan serve

If everything is working properly, you should see the Laravel 5 welcome page:

如果一切正常,您应该看到Laravel 5欢迎页面:

time-tracker-2-1

设置数据库 (Setting Up the Database)

For this tutorial we're going to use a SQLite database for the sake of simplicity. SQLite is a local database that sits on the filesystem and gives us a quick and flexible way of storing data without the need for connecting to an external database. You might not have used SQLite before, so if you feel more comfortable with MySQL or even something else, feel free to change the database up as you like. All of the Laravel code we write to run migrations, seed the database, and setup relationships will work across all databases that Laravel supports.

在本教程中,为简单起见,我们将使用SQLite数据库。 SQLite是位于文件系统上的本地数据库,它使我们能够快速灵活地存储数据,而无需连接到外部数据库。 您可能以前没有使用过SQLite,因此,如果您对MySQL甚至其他方面感到更满意,请随时更改数据库。 我们编写的用于运行迁移,播种数据库和建立关系的所有Laravel代码将在Laravel支持的所有数据库中工作。

创建SQLite数据库 (Create SQLite Database)

Navigate to the time-tracker-2/storage folder and add a new sqlite database. I like to do this from my Sublime editor by right-clicking the folder, then selecting new file. Save the file and name it database.sqlite.

导航到time-tracker-2/storage文件夹并添加一个新的sqlite数据库。 我喜欢通过Sublime编辑器执行此操作,方法是右键单击文件夹,然后选择new file 。 保存该文件并将其命名为database.sqlite

修改配置文件 (Modify the Config File)

Next, let's modify the database config file to let Laravel know that we're using SQLite. In congfig/database.php, switch this line:

接下来,让我们修改数据库配置文件,以使Laravel知道我们正在使用SQLite。 在congfig/database.php ,切换此行:

'default' => 'mysql',

for this:

为了这:

'default' => 'sqlite',

Since we are using a database stored on our filesystem, we don't need to update the .env file with any database user credentials. If, however, you are using MySQL, you'll need to modify it so that your local web server's database user is recognized. To do so, open time-tracker-2/.env and modify the credentials. These are the ones that come with the Laravel installation:

由于我们使用的是存储在文件系统上的数据库,因此不需要使用任何数据库用户凭据来更新.env文件。 但是,如果您使用的是MySQL,则需要对其进行修改,以便识别本地Web服务器的数据库用户。 为此,打开time-tracker-2/.env并修改凭据。 这些是Laravel安装随附的软件:

DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

下载SQLite的数据库GUI (Download a Database GUI for SQLite)

If you're used to using phpMyAdmin for MySQL, you'll likely want to have a similar tool for viewing your SQLite database. There is a Firefox add-on called SQLite Manager which is great for this job.

如果您习惯使用MySQL的phpMyAdmin,则可能需要一个类似的工具来查看SQLite数据库。 有一个名为SQLite Manager的Firefox插件,非常适合此工作。

迁移与播种 (Migrations and Seeding)

更新和创建迁移 (Updating and Creating Migrations)

Now that the database is setup, the first thing we'll want to do is run some migrations to setup our tables, and also seed the database with some initial data.

现在已经建立了数据库,我们要做的第一件事是运行一些迁移来建立我们的表,并为数据库提供一些初始数据。

If you look in the database/migrations folder, you'll see that Laravel comes with two migrations---one that creates a users table and another that creates a password reset table. We'll use the default migrations, but let's make a small change to the columns used for storing the user's name in the users table migration so that both first name and last name are stored separately:

如果查看database/migrations文件夹,您会看到Laravel附带了两个迁移-一个创建用户表,另一个创建密码重置表。 我们将使用默认迁移,但是让我们对users表迁移中用于存储用户名的列进行一些小的更改,以便将名字和姓氏分别存储:

// database/migrations/2014_10_12_000000_create_users_table.php

...

// the up method creates the below fields in our users table
public function up()
{
    Schema::create('users', function(Blueprint $table)
    {
        $table->increments('id');
        $table->string('first_name');
        $table->string('last_name');
        $table->string('email')->unique();
        $table->string('password', 60);
        $table->rememberToken();
        $table->timestamps();
    });
}

...

Next, let's create a migration for the time_entries table. A cool feature in Laravel 5 is that when you use Artisan to create a model, it will automatically create a migration for that model for you. Let's hit two birds with one stone here. From the command line:

接下来,让我们为time_entries表创建迁移。 Laravel 5的一个很酷的功能是,当您使用Artisan创建模型时,它将自动为您创建该模型的迁移。 让我们在这里用一块石头打两只鸟。 在命令行中:

php artisan make:model TimeEntries

Let's specify in our TimeEntry model that we want it to use the time_entries table.

让我们在我们的TimeEntry模型中指定我们希望它使用time_entries表。

// app/TimeEntry.php

...

use IlluminateDatabaseEloquentModel;

class TimeEntry extends Model {

    // Use the time_entries table
    protected $table = 'time_entries';

}

...

We'll need to specify the fields we want to add to the table in the migration script.

我们需要在迁移脚本中指定要添加到表中的字段。

// database/migrations/0000_00_00_000000_create_time_entries_table.php

...

use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

class CreateTimeEntriesTable extends Migration {

    // the up method creates the below fields in our time entries table
    public function up()
    {
        Schema::create('time_entries', function(Blueprint $table)
        {
            $table->increments('id');
            $table->integer('user_id')->unsigned();
            $table->dateTime('start_time');
            $table->dateTime('end_time');
            $table->string('comment')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::drop('time_entries');
    }

}

Next, let's run the migrations. From the command line:

接下来,让我们运行迁移。 在命令行中:

php artisan migrate

If everything worked, you should see that the migrations were created successfully. We can also check SQLite Manager to be sure:

如果一切正常,您应该看到迁移已成功创建。 我们还可以检查SQLite Manager以确保:

time-tracker-2-2

创建种子数据 (Creating Seed Data)

We'll need to provide some intial data to work with, much like we did in the first part of this series, only this time it will be in a database instead of a static JSON file. Laravel offers a great method for seeding databases, and we'll make use of it here. Let's modify the already existing database/seeds/DatabaseSeeder.php file.

我们需要提供一些初始数据,就像我们在本系列第一部分中所做的一样,只是这一次它将在数据库中,而不是静态JSON文件中。 Laravel为种子数据库提供了一种很好的方法,我们将在这里使用它。 让我们修改已经存在的database/seeds/DatabaseSeeder.php文件。

// database/seeds/DatabaseSeeder.php

...

use IlluminateDatabaseSeeder;
use IlluminateDatabaseEloquentModel;
use AppUser;
use AppTimeEntry;

class DatabaseSeeder extends Seeder {

    public function run()
    {
        Model::unguard();

        // Call the seed classes to run the seeds
        $this->call('UsersTableSeeder');
        $this->call('TimeEntriesTableSeeder');
    }

}

class UsersTableSeeder extends Seeder {

    public function run()
    {

        // We want to delete the users table if it exists before running the seed
        DB::table('users')->delete();

        $users = array(
                ['first_name' => 'Ryan', 'last_name' => 'Chenkie', 'email' => 'ryanchenkie@gmail.com', 'password' => Hash::make('secret')],
                ['first_name' => 'Chris', 'last_name' => 'Sevilleja', 'email' => 'chris@scotch.io', 'password' => Hash::make('secret')],
                ['first_name' => 'Holly', 'last_name' => 'Lloyd', 'email' => 'holly@scotch.io', 'password' => Hash::make('secret')],
                ['first_name' => 'Adnan', 'last_name' => 'Kukic', 'email' => 'adnan@scotch.io', 'password' => Hash::make('secret')],
        );

        // Loop through each user above and create the record for them in the database
        foreach ($users as $user)
        {
            User::create($user);
        }
    }
}

class TimeEntriesTableSeeder extends Seeder {

    public function run()
    {
        DB::table('time_entries')->delete();

        $time_entries = array(
                ['user_id' => 1, 'start_time' => '2015-02-21T18:56:48Z', 'end_time' => '2015-02-21T20:33:10Z', 'comment' => 'Initial project setup.'],
                ['user_id' => 2, 'start_time' => '2015-02-27T10:22:42Z','end_time' => '2015-02-27T14:08:10Z','comment' => 'Review of project requirements and notes for getting started.'],
                ['user_id' => 3, 'start_time' => '2015-03-03T09:55:32Z','end_time' => '2015-03-03T12:07:09Z','comment' => 'Front-end and backend setup.'],
        );

        foreach($time_entries as $time_entry)
        {
            TimeEntry::create($time_entry); 
        }   

    }
}

With database seeding, we can either break the seeds for each table into separate files, or just pile them all into one big file. If you are doing a lot of seeding, I'd certainly recommend breaking them out, but we'll just put everything in one file in our case. You'll see that we're creating an array with a list of names, along with email addresses and a hashed password. We then loop through the array and populate the database with the data. The same thing goes for the time_entries table, only that the data structure is different. Up at the top in the run function, we are calling on the seeder classes.

使用数据库种子,我们可以将每个表的种子分解为单独的文件,也可以将它们全部堆积为一个大文件。 如果您正在做大量播种工作,我当然建议您将它们分解,但是我们只将所有内容放在一个文件中。 您会看到我们正在创建一个包含名称列表,电子邮件地址和哈希密码的数组。 然后,我们遍历数组,并使用数据填充数据库。 time_entries表也​​有time_entries ,只是数据结构不同。 在run函数的顶部,我们正在调用seeder类。

Let's run the seed. From the command line:

让我们播下种子。 在命令行中:

php artisan db:seed

Browsing back to SQLite Manager, we can see that the database has been seeded successfully:

回到SQLite Manager,我们可以看到数据库已经成功播种:

time-tracker-2-3

迁移前端文件 (Migrating the Front-end Files)

How that we've got our seed data in place, it's time to migrate all of the HTML, CSS, and Javascript we wrote in part 1. If you've worked with Laravel before, you'll likely know that there is a public folder at the root of the project. Let's copy the bower_components, data, and scripts folders and paste them right into the public directory of our Laravel project. Then, let's copy css/style.css and paste it into the css folder.

我们如何获取种子数据,是时候迁移我们在第1部分中编写的所有HTML,CSS和Javascript了。如果您以前使用过Laravel,您可能会知道有一个public文件夹位于项目的根目录。 让我们复制bower_componentsdatascripts文件夹,然后将它们直接粘贴到Laravel项目的public目录中。 然后,让我们复制css/style.css并将其粘贴到css文件夹中。

那么index.html呢? (What About index.html?)

There are a couple different ways to deal with the index file when making Angular and Laravel work together, but the way I like to do it is to create an index.php file in the Laravel views folder, located at resources/views, and then create a new route within routes.php to handle the rendering.

使Angular和Laravel一起工作时,有两种处理索引文件的方法,但是我喜欢的方法是在位于resources/views的Laravel views文件夹中创建一个index.php文件,然后在routes.php创建一个新路由来处理渲染。

Let's create a new file in resources/views called index.php and copy all of the index.html content from part 1 into it. Note: You'll see that the other views are named with .blade.php to make use of Laravel's Blade syntax. We won't be using Blade, so we'll just use standard naming.

让我们在resources/views创建一个名为index.php的新文件,并将第1部分中的所有index.html内容复制到其中。 注意:您会看到其他视图都以.blade.php命名,以使用Laravel的Blade语法。 我们不会使用Blade,因此我们将仅使用标准命名。

<!-- resources/views/index.php -->

<!doctype html>
<html>
    <head>
        <title>Time Tracker</title>
        <link rel="stylesheet" href="css/style.css">
        <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
    </head>
    <body ng-app="timeTracker" ng-controller="TimeEntry as vm">

        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a class="navbar-brand" href="#">Time Tracker</a>
                </div>
            </div>
            <div class="container-fluid time-entry">
                <div class="timepicker">
                    <span class="timepicker-title label label-primary">Clock In</span><timepicker ng-model="vm.clockIn" hour-step="1" minute-step="1" show-meridian="true"></timepicker> 
                </div>
                <div class="timepicker">
                    <span class="timepicker-title label label-primary">Clock Out</span><timepicker ng-model="vm.clockOut" hour-step="1" minute-step="1" show-meridian="true"></timepicker>
                </div>
                <div class="time-entry-comment">                
                    <form class="navbar-form">
                        <input class="form-control" ng-model="vm.comment" placeholder="Enter a comment"></input>
                        <button class="btn btn-primary" ng-click="vm.logNewTime()">Log Time</button>
                    </form>
                </div>    
            </div>
        </nav>

        <div class="container">
            <div class="col-sm-8">

                <div class="well vm" ng-repeat="time in vm.timeentries">
                    <div class="row">
                        <div class="col-sm-8">
                            <h4><i class="glyphicon glyphicon-user"></i> {{time.user_firstname}} {{time.user_lastname}}</h4>
                            <p><i class="glyphicon glyphicon-pencil"></i> {{time.comment}}</p>                  
                        </div>
                        <div class="col-sm-4 time-numbers">
                            <h4><i class="glyphicon glyphicon-calendar"></i> {{time.end_time | date:'mediumDate'}}</h4>
                            <h2><span class="label label-primary" ng-show="time.loggedTime.duration._data.hours > 0">{{time.loggedTime.duration._data.hours}} hour<span ng-show="time.loggedTime.duration._data.hours > 1">s</span></span></h2>
                            <h4><span class="label label-default">{{time.loggedTime.duration._data.minutes}} minutes</span></h4>
                        </div>
                    </div>
                </div>

            </div>

            <div class="col-sm-4">
                <div class="well time-numbers">
                    <h1><i class="glyphicon glyphicon-time"></i> Total Time</h1>
                    <h1><span class="label label-primary">{{vm.totalTime.hours}} hours</span></h1>
                    <h3><span class="label label-default">{{vm.totalTime.minutes}} minutes</span></h3>
                </div>
            </div>
        </div>  
    </body>

    <!-- Application Dependencies -->
    <script type="text/javascript" src="bower_components/angular/angular.js"></script>
    <script type="text/javascript" src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
    <script type="text/javascript" src="bower_components/angular-resource/angular-resource.js"></script>
    <script type="text/javascript" src="bower_components/moment/moment.js"></script>

    <!-- Application Scripts -->
    <script type="text/javascript" src="scripts/app.js"></script>
    <script type="text/javascript" src="scripts/controllers/TimeEntry.js"></script>
    <script type="text/javascript" src="scripts/services/time.js"></script>
</html>

Next, let's add a route to routes.php to handle this view, and remove the already existing route that renders the welcome view.

接下来,让我们向routes.php添加一条路由来处理此视图,并删除呈现欢迎视图的现有路由。

// app/Http/routes.php

...

Route::get('/', function() 
{
    return view('index');
});

// Remove or comment this out
// Route::get('/', 'WelcomeController@index');

...

What we've done here is registered a route such that when a GET request is made to the default route of the site (signified by '/'), Laravel will render the index view, which we have in our index.php file. Let's make sure the page is being displayed as it should. Make sure you still have the local web server fired up, and head to the page. For me, it's at localhost:8000. If everything is working, you should see the application we had from part 1:

我们在这里所做的是注册一条路由,以便当对站点的默认路由(以'/'表示)进行GET请求时,Laravel将呈现index视图,该视图位于我们的index.php文件中。 让我们确保页面显示正确。 确保仍启动本地Web服务器,然后转到页面。 对我而言,它位于localhost:8000。 如果一切正常,您应该看到第1部分中的应用程序:

time-tracker-2-4

Awesome, we're done migrating the front-end from part 1. However, the data that is being displayed is still from the static JSON file. Let's fix that in the next section.

太棒了,我们已经从第1部分迁移了前端。但是,正在显示的数据仍然来自静态JSON文件。 让我们在下一节中修复它。

完成模型设置 (Finishing the Model Setup)

There is a bit more we need to do with our TimeEntry model before we can proceed. By default, Laravel protects against mass assignment, so we'll need to tell the model which fields are fillable. We'll also need to setup an Eloquent relationship so that our TimeEntry model talks to the User model.

在继续之前,我们还需要处理TimeEntry模型。 默认情况下,Laravel防止大规模分配 ,因此我们需要告诉模型哪些字段是可填充的。 我们还需要设置一个Eloquent关系,以便我们的TimeEntry模型与User模型对话。

// app/TimeEntry.php

...

use IlluminateDatabaseEloquentModel;

class TimeEntry extends Model {

    protected $table = "time_entries";

    // An array of the fields we can fill in the time_entries table
    protected $fillable = ['user_id', 'start_time', 'end_time', 'comment'];

    protected $hidden = ['user_id'];

    // Eloquent relationship that says one user belongs to each time entry
    public function user()
    {
        return $this->belongsTo('AppUser');
    }

}

An Eloquent relationship is setup by creating a new method called user that calls belongsTo on the User model (namespaced here with AppUser). Setting up relationships with Eloquent is a whole other article, but using belongsTo here means that we're going to have our TimeEntry model use it's user_id and look up that id in the users table to match up time entries with users.

通过创建一个名为user的新方法来建立belongsTo ,该方法在User模型上(在此使用AppUser命名)来调用belongsTo 。 与belongsTo建立关系是另一篇完整的文章,但是在这里使用belongsTo意味着我们将让我们的TimeEntry模型使用它的user_id并在users表中查找该id以与users匹配时间条目。

For the sake of reducing unnecessary data in our API, we're also hiding the user_id field from being returned since we won't need it for the app to function as it should.

为了减少API中不必要的数据,我们还隐藏了user_id字段,以免其返回,因为我们不需要它来使应用程序正常运行。

The User model is already setup for us out of the box with Laravel, but I like to hide the created_at and updated_at fields since we won't require them on the front-end:

Laravel已经为我们提供了User模型的现成设置,但是我想隐藏created_atupdated_at字段,因为我们不需要在前端使用它们:

// app/User.php

...

protected $hidden = ['password', 'remember_token', 'created_at', 'updated_at'];

...

设置API (Setting Up the API)

To pull data from our database, we'll first need to setup our API. Laravel offers a very easy way to create a REST API along with the resource controllers necessary for it. We won't get into details about what REST is in this article, but if you are new to the concept, you should check out Teach a Dog to REST.

要从数据库中提取数据,我们首先需要设置我们的API。 Laravel提供了一种非常轻松的方法来创建REST API及其所需的资源控制器。 我们不会在本文中详细介绍什么是REST,但是如果您是不熟悉REST的概念,则应该查看Teach a Dog to REST

First, let's define a route group and resources within it in routes.php.

首先,让我们在routes.php定义一个路由组和其中的资源。

// app/Http/routes.php

...

// A route group allows us to have a prefix, in this case api
Route::group(array('prefix' => 'api'), function()
{
    Route::resource('time', 'TimeEntriesController');
    Route::resource('users', 'UsersController');
});

Our RESTful controllers are going to respond to HTTP requests that we make to various routes. It's a good practice to prefix the group of routes that we will send requests to with something that signifies that it is an API. In our case, we'll just use the prefix api, but you could also prefix it with a version number.

我们的RESTful控制器将响应我们对各种路由发出的HTTP请求。 优良作法是在发送请求的路由组之前添加一些前缀,以表示这是API。 在本例中,我们将仅使用前缀api ,但您也可以为其添加版本号。

Within this api route group, we are defining two resources. The first is a time resource, which will be for our time entries. The other is a users resource, which will make use of the users table to give us options for who the time entries are assigned to.

在此api路由组中,我们定义了两个资源。 首先是time资源,它将用于我们的时间输入。 另一个是users资源,它将利用users表为我们提供时间条目分配给谁的选项。

Now that we have our route groups and resources, let's use Artisan to create our resource controllers. From the command line:

现在我们有了路由组和资源,让我们使用Artisan创建我们的资源控制器。 在命令行中:

php artisan make:controller TimeEntriesController
php artisan make:controller UsersController

If you now navigate to app/Http/Controllers, you should see that TimeEntriesController.php and UsersController.php have been created.

如果现在导航到app/Http/Controllers ,则应该看到已经创建了TimeEntriesController.phpUsersController.php

查看所有可用路线 (View all the Available Routes)

When we register route resources, Laravel will enumerate the endpoints and we can view them using Artisan. From the command line:

注册路线资源时,Laravel将枚举端点,我们可以使用Artisan进行查看。 在命令行中:

php artisan route:list

This will return a list of all the defined routes along with which HTTP verbs they respond to. Since we've registered our API resource routes, you should see them listed:

这将返回所有已定义路由的列表以及它们响应的HTTP动词。 由于我们已经注册了API资源路由,因此您应该在列表中看到它们:

time-tracker-2-5

返回所有时间条目 (Return all Time Entries)

The first thing we'll want to do with the TimeEntriesController is make it return all of our time entries. Let's use the index method to accomplish that:

我们要对TimeEntriesController进行的第一件事是使其返回我们所有的时间条目。 让我们使用index方法来实现这一点:

// app/Http/Controllers/TimeEntriesController.php

...

use AppHttpRequests;
use AppHttpControllersController;    
use AppTimeEntry;

use IlluminateSupportFacadesRequest;

class TimeEntriesController extends Controller {

    // Gets time entries and eager loads their associated users
    public function index()
    {
        $time = TimeEntry::with('user')->get();

        return $time;
    }

...

The index method is usually responsibile for displaying all of the data for a given resource, and that's just what we're doing with it here. We are using eager loading to get all the results from our time_entries table along with the associated users and returning it. Note that we're also pulling in our TimeEntry model at the top with a use statement.

index方法通常负责显示给定资源的所有数据,这就是我们在此所做的。 我们正在使用time_entries 加载time_entries表中获取所有结果以及相关联的用户,然后将其返回。 请注意,我们还将在顶部use语句引入TimeEntry模型。

Let's check to make sure we are getting all the results for this endpoint. I like the Chrome app Postman which is a REST client to help test and debug REST APIs.

我们进行检查以确保获得该端点的所有结果。 我喜欢Chrome应用Postman ,它是一个REST客户端,可帮助测试和调试REST API。

If we make a GET request to the api/time endpoint, we should see a list of all our time entries from the database:

如果我们向api/time端点发出GET请求,则应该从数据库中看到所有时间条目的列表:

time-tracker-2-6

返回所有用户 (Return All Users)

Let's also setup our UsersController to display all users when we do a GET request to the /users endpoint:

我们还要设置UsersController ,以便在对/users端点执行GET请求时显示所有用户:

// app/Http/Controllers/UsersController.php

...

use AppHttpRequests;
use AppHttpControllersController;
use AppUser;

use IlluminateHttpRequest;

class UsersController extends Controller {

    // Gets all users in the users table and returns them
    public function index()
    {
        $users = User::all();

        return $users;
    }

...

Here you can see that we're doing the same thing as the TimeEntriesController on the index method: using the model to get all of the users and then returning them. If I use Postman to hit the /users endpoint, I see the data returned properly.

在这里您可以看到我们在index方法上所做的与TimeEntriesController相同:使用模型获取所有用户,然后返回它们。 如果我使用Postman击中/users端点,则可以看到正确返回的数据。

更新前端 (Updating the Front-End)

Now that we've got Laravel serving our API, let's update the front-end Anuglar code to make calls to it. The first thing we'll need to change is our time service that currently makes a call to the static JSON file.

现在我们已经有了Laravel服务我们的API,让我们更新前端Anuglar代码以对其进行调用。 我们需要更改的第一件事是我们的time服务,该服务当前正在调用静态JSON文件。

// public/scripts/services/time.js

(function() {

  'use strict';

  angular
    .module('timeTracker')
    .factory('time', time);

    function time($resource) {

      // ngResource call to the API with id as a paramaterized URL
      // and setup for the update method
      var Time = $resource('api/time/:id', {}, {
        update: {
            method: 'PUT'
        }
      });

  ...

You'll see here that we are again using ngResource to make a call for the data, but this time it is to our api/time endpoint. We've also put in a bit of extra stuff here to set us up for later. For updating and deleting time entries, we are going to require the id of the entry as a URL parameter so that we can properly communicate with our API, so we use :id here as a URL template. The second argument that $resource expects is an object for parameter defaults, but we don't need that in our case so we pass in an empty object. Finally you'll see that we are defining a custom action for updating our time entries. We call the custom action update and specify that it uses the PUT method.

您将在此处看到我们再次使用ngResource调用数据,但这一次是针对我们的api/time端点。 我们还在这里添加了一些额外的东西,以备将来使用。 为了更新和删除时间条目,我们将要求条目的id作为URL参数,以便我们能够正确地与我们的API通信,因此我们在此处使用:id作为URL模板。 $resource期望的第二个参数是参数默认值的对象,但是在我们的情况下我们不需要它,因此我们传入一个空对象。 最终,您将看到我们正在定义一个自定义动作来更新我们的时间条目。 我们调用自定义操作update并指定它使用PUT方法。

Let's check to see if that worked. If you refresh the page, you should see that we are still getting time entries but they are now coming from the API:

让我们检查一下是否可行。 如果刷新页面,您应该看到我们仍在获取时间条目,但现在它们来自API:

time-tracker-2-7

The only thing that is different here is that we aren't getting the names of the users for each time entry. This is because our data structure has changed a little bit from our test data. When we use eager loading with Laravel, it nests related data one level deeper than the rest of the data. Let's change up our view to reflect this new structure.

唯一不同的是,我们没有获得每次输入的用户名。 这是因为我们的数据结构与测试数据相比发生了一些变化。 当我们对Laravel使用急切加载时,它会将相关数据嵌套比其他数据深一层。 让我们改变看法以反映这种新结构。

<!-- resources/views/index.php -->

...

<h4><i class="glyphicon glyphicon-user"></i> {{time.user.first_name}} {{time.user.last_name}}</h4>

...

You should now be seeing the names displayed:

现在,您应该看到显示的名称:

time-tracker-2-8

设置用户列表 (Settting Up the Users List)

Earlier we setup the UsersController with Laravel and successfully retrieved a list of all users with the API using Postman. Now, let's setup an Anuglar service that will make a call for that data. The user service will be similar to our time service, but we'll make it be responsible only for retrieving the user data.

之前,我们使用Laravel设置了UsersController ,并使用Postman使用API​​成功检索了所有用户的列表。 现在,让我们设置一个Anuglar服务,该服务将调用该数据。 user服务将类似于我们的time服务,但我们将使其仅负责检索用户数据。

//public/scripts/services/user.js

(function() {

  'use strict';

  angular
    .module('timeTracker')
    .factory('user', user);

    function user($resource) {

      // ngResource call to the API for the users
      var User = $resource('api/users');

      // Query the users and return the results
      function getUsers() {
        return User.query().$promise.then(function(results) {
          return results;
        }, function(error) {
          console.log(error);
        });
      }

      return {
        getUsers: getUsers
      }
    }
  })();

This looks quite similar to the first part of the time service. We setup the factory, define User which is a call to our API at the /users endpoint, and then define a method that uses ngResource to retrieve all the users. Finally, we return an object that has on it a key called getUsers which references out getUsers method.

这看起来与time服务的第一部分非常相似。 我们设置工厂,定义User (在/users端点处对我们的API的调用),然后定义一个使用ngResource检索所有用户的方法。 最后,我们返回一个对象,该对象上有一个名为getUsers的键,该键引用了getUsers方法。

We'll need to add the newly created user service to the application scripts we call in the main view.

我们需要将新创建的user服务添加到在主视图中调用的应用程序脚本中。

<!-- resources/views/index.php -->

...

<!-- Application Scripts -->
<script type="text/javascript" src="scripts/app.js"></script>
<script type="text/javascript" src="scripts/controllers/TimeEntry.js"></script>
<script type="text/javascript" src="scripts/services/time.js"></script>
<script type="text/javascript" src="scripts/services/user.js"></script>

Let's now make use of this in the controller:

现在让我们在控制器中使用它:

// public/scripts/controllers/TimeEntry.js

(function() {

  'use strict';

  angular
    .module('timeTracker')
    .controller('TimeEntry', TimeEntry);

    function TimeEntry(time, user, $scope) {

      var vm = this;

      vm.timeentries = [];
      vm.totalTime = {};
      vm.users = [];

      // Initialize the clockIn and clockOut times to the current time.
      vm.clockIn = moment();
      vm.clockOut = moment();

      // Grab all the time entries saved in the database
      getTimeEntries();

      // Get the users from the database so we can select
      // who the time entry belongs to
      getUsers();

      function getUsers() {
        user.getUsers().then(function(result) {
          vm.users = result;
        }, function(error) {
          console.log(error);
        });
      }

      // Fetches the time entries and puts the results
      // on the vm.timeentries array
      function getTimeEntries() {
        time.getTime().then(function(results) {
          vm.timeentries = results;
            updateTotalTime(vm.timeentries);
            console.log(vm.timeentries);
          }, function(error) {
            console.log(error);
          });
        }         
...

Notice here that we are injecting the user service into our TimeEntry method so that we can use it as a dependency. We then define a getUsers method on the controller that allows us to make use of the user service to grab a list of all users. Finally, we put the list of users we retrieve onto the vm.users array.

请注意,这里我们将user服务注入到我们的TimeEntry方法中,以便可以将其用作依赖项。 然后,我们在控制器上定义一个getUsers方法,该方法允许我们利用user服务来获取所有用户的列表。 最后,我们将检索到的用户列表放入vm.users数组。

You'll also notice that we've wrapped our call to time.getTime from part 1 into a function that we call getTimeEntries. This will come in handy later when we need to update time entries after creating, editing and deleting them.

您还将注意到,我们已经将第1部分中对time.getTime的调用包装到一个名为getTimeEntries的函数中。 当我们需要在创建,编辑和删除时间条目之后更新时间条目时,这将派上用场。

Finally for this part, let's create a dropdown list of the users so we can choose who time entries should be attributed to.

最后,对于这一部分,让我们创建一个用户下拉列表,以便我们可以选择应将时间条目归于谁。

<!-- resources/views/index.php -->

...

<div class="time-entry-comment">                
    <form class="navbar-form">
        <select name="user" class="form-control" ng-model="vm.timeEntryUser" ng-options="user.first_name + ' ' + user.last_name for user in vm.users">
            <option value="">-- Select a user --</option>
        </select>
        <input class="form-control" ng-model="vm.comment" placeholder="Enter a comment"></input>
        <button class="btn btn-primary" ng-click="vm.logNewTime()">Log Time</button>
    </form>
</div>

...

We've added in a select element just beside our comment input box and we're using the ng-options directive to loop through and display the users on the vm.users array. You should now see the list of users available in our app:

我们在注释输入框旁边添加了一个select元素,并使用ng-options指令遍历并在vm.users数组上显示用户。 现在,您应该在我们的应用程序中看到可用的用户列表:

time-tracker-2-8

Now that we are able to read the time entries and users from the database, in the next sections we will look at creating, updating, and deleting time entries.

现在我们已经能够从数据库中读取时间条目和用户,在接下来的部分中,我们将介绍如何创建,更新和删除时间条目。

创建时间条目 (Creating Time Entries)

In part 1 we had the ability to create time entries, but we were just pushing the new entries onto the already existing array. This time, let's create the Laravel logic to capture and store time entries, and then the Angular parts send the HTTP request to the API.

在第1部分中,我们具有创建时间条目的能力,但是我们只是将新条目推入已经存在的数组中。 这次,让我们创建Laravel逻辑来捕获和存储时间条目,然后Angular部分将HTTP请求发送到API。

First, let's add to the already existing store method in the TimeEntriesController.

首先,让我们添加到TimeEntriesController已经存在的store方法中。

// app/Http/Controllers/TimeEntriesController.php

...

use AppHttpRequests;
use AppHttpControllersController;
use AppTimeEntry;

use IlluminateSupportFacadesRequest;

...

// Grab all the data passed into the request and save a new record
public function store()
{
    $data = Request::all();

    $timeentry = new TimeEntry();

    $timeentry->fill($data);

    $timeentry->save();

}

...

The store method accepts a POST to the /time endpoint of our API. In the method we are grabbing all of the data passed along with the request, creating a new instance of our TimeEntry class, then using Laravel's fill method to store all of the data. The fill method is a nice helper that goes through all of the data and puts it in the appropriate database fields. Finally we call save to finish things out. Notice here that we are also using Larave's Request facade which we bring in with the use statement. To avoid conflicts, we'll have to get rid of the already existing HttpRequest class that was previously brought in.

store方法接受POST到我们API的/time端点。 在该方法中,我们获取与请求一起传递的所有数据,创建TimeEntry类的新实例,然后使用Laravel的fill方法存储所有数据。 fill方法是一个很好的助手,它可以遍历所有数据并将其放入适当的数据库字段中。 最后,我们调用save完成操作。 请注意,这里我们还使用了Larave的Request门面,它是通过use语句引入的。 为了避免冲突,我们必须摆脱以前引入的已经存在的HttpRequest类。

Now let's setup Angular to send data to the API to store new time entries. There are a few spots we'll need to change from part 1. First, let's update the time service:

现在,我们将Angular设置为将数据发送到API以存储新的时间条目。 在第1部分中,我们需要更改一些地方。首先,让我们更新time服务:

// public/scripts/services/time.js

... 

// Grab data passed from the view and send
// a POST request to the API to save the data
function saveTime(data) {

  return Time.save(data).$promise.then(function(success) {
    console.log(success);
  }, function(error) {
    console.log(error);
  });
}

...

return {
  getTime: getTime,
  getTimeDiff: getTimeDiff,
  getTotalTime: getTotalTime,
  saveTime: saveTime
}

...

Here we've created a new method called saveTime which gets all the user input and uses ngResource's save method to send a POST request to the API with the data. We have to be sure to add this new method to our returned object at the end of the service.

在这里,我们创建了一个名为saveTime的新方法,该方法获取所有用户输入,并使用ngResourcesave方法向数据发送POST请求到API。 我们必须确保在服务结束时将此新方法添加到返回的对象中。

Next, let's change the vm.logNewTime method in our TimeEntry controller from part 1 to use this new method on our service.

接下来,让我们将第1部分中的TimeEntry控制器中的vm.logNewTime方法更改为在我们的服务上使用此新方法。

// public/scripts/controllers/TimeEntry.js

...

// Submit the time entry that will be called 
// when we click the "Log Time" button
vm.logNewTime = function() {
  // Make sure that the clock-in time isn't after
  // the clock-out time!
  if(vm.clockOut < vm.clockIn) {
    alert("You can't clock out before you clock in!");
    return;
  }

  // Make sure the time entry is greater than zero
  if(vm.clockOut - vm.clockIn === 0) {
    alert("Your time entry has to be greater than zero!");
    return;
  }

  // Call to the saveTime method on the time service
  // to save the new time entry to the database
  time.saveTime({
    "user_id":vm.timeEntryUser.id,
    "start_time":vm.clockIn,
    "end_time":vm.clockOut,
    "comment":vm.comment
  }).then(function(success) {
    getTimeEntries();
    console.log(success);
  }, function(error) {
    console.log(error);
  });

  getTimeEntries();

  // Reset clockIn and clockOut times to the current time
  vm.clockIn = moment();
  vm.clockOut = moment();

  // Clear the comment field
  vm.comment = "";

  // Deselect the user
  vm.timeEntryUser = "";

}

...

In the newly renovated logNewTime method, we are still checking to be sure that the clock-in time isn't after the clock-out time and then we call the saveTime method on the time service. We pass an object into the saveTime method where we specify the fields in our table that will need to be filled. If the save was successfull, we call our getTimeEntries method to refresh the listing of time entries.

在新近更新的logNewTime方法中,我们仍在检查以确保时钟输入时间不在时钟输出时间之后,然后在time服务上调用saveTime方法。 我们将一个对象传递到saveTime方法中,在saveTime方法中,我们在表中指定需要填充的字段。 如果保存成功,则调用getTimeEntries方法刷新时间条目列表。

There are a couple different ways we could refresh the listing of time entries on save. I've elected here to get the full list again each time a new time entry is saved, but some people prefer to push the new data onto the local array. The problem I see with this is that the local data and what is in the database become out of sync. For a simple application like this, I prefer to take the expense of doing a database query again to make sure what is being shown to the user is what is in the database.

我们可以通过几种不同的方式来刷新保存的时间条目列表。 我选择在这里选择是在每次保存新的时间条目时再次获取完整列表,但是有些人喜欢将新数据推送到本地数组中。 我看到的问题是本地数据和数据库中的内容不同步。 对于像这样的简单应用程序,我宁愿花钱再次进行数据库查询,以确保向用户显示的是数据库中的内容。

更新时间条目 (Updating Time Entries)

We'll need to provide the user a way to update their time entries in case they need to change the clock-in or clock-out times, the user the time entry belongs to, or the comment provided. To get us started, let's first adjust the view to give us a UI for editing the time entries.

我们需要为用户提供一种更新其时间条目的方法,以防他们需要更改输入或输出时间,该时间条目所属的用户或提供的注释。 首先,让我们开始调整视图,以提供一个用于编辑时间条目的UI。

<!-- resources/views/index.php -->

...

<div class="container">
    <div class="col-sm-8">

        <div class="well vm" ng-repeat="time in vm.timeentries">
            <div class="row">
                <div class="col-sm-8">
                    <h4><i class="glyphicon glyphicon-user"></i> {{time.user.first_name}} {{time.user.last_name}}</h4>
                   <p><i class="glyphicon glyphicon-pencil"></i> {{time.comment}}</p>                  
               </div>
               <div class="col-sm-4 time-numbers">
                    <h4><i class="glyphicon glyphicon-calendar"></i> {{time.end_time | date:'mediumDate'}}</h4>
                   <h2><span class="label label-primary" ng-show="time.loggedTime.duration._data.hours > 0">{{time.loggedTime.duration._data.hours}} hour<span ng-show="time.loggedTime.duration._data.hours > 1">s</span></span></h2>
                   <h4><span class="label label-default">{{time.loggedTime.duration._data.minutes}} minutes</span></h4>
               </div>

           </div>
           <div class="row">
                <div class="col-sm-3">
                    <button class="btn btn-primary btn-xs" ng-click="showEditDialog = true">Edit</button>
                    <button class="btn btn-danger btn-xs" ng-click="vm.deleteTimeEntry(time)">Delete</button>
                </div>
            </div>

            <div class="row edit-time-entry" ng-show="showEditDialog === true">
                <h4>Edit Time Entry</h4>
                <div class="time-entry">
                    <div class="timepicker">
                        <span class="timepicker-title label label-primary">Clock In</span><timepicker ng-model="time.start_time" hour-step="1" minute-step="1" show-meridian="true"></timepicker> 
                    </div>
                    <div class="timepicker">
                        <span class="timepicker-title label label-primary">Clock Out</span><timepicker ng-model="time.end_time" hour-step="1" minute-step="1" show-meridian="true"></timepicker>
                    </div>
               </div>
               <div class="col-sm-6">
                    <h5>User</h5>
                    <select name="user" class="form-control" ng-model="time.user" ng-options="user.first_name + ' ' + user.last_name for user in vm.users track by user.id">
                        <option value="user.id"></option>
                    </select>
                </div>
                <div class="col-sm-6">
                    <h5>Comment</h5>
                    <textarea ng-model="time.comment" class="form-control">{{time.comment}}</textarea>
                </div>
                <div class="edit-controls">
                    <button class="btn btn-primary btn-sm" ng-click="vm.updateTimeEntry(time)">Save</button>
                    <button class="btn btn-danger btn-sm" ng-click="showEditDialog = false">Close</button>
                </div>                            
            </div>

        </div>

    </div>

    ...

We've added in quite a bit since the original markup we had in part 1. We've included some controls for opening the edit dialog and deleting the time entry (which we will handle next) and we've put in the structure for the editing screen. We're hiding and showing the edit area conditionally using ng-show to which we attach a property called showEditDialog, the state of which determines whether the edit screen is shown.

自从第1部分中的原始标记以来,我们已经添加了很多内容。我们提供了一些控件,用于打开编辑对话框并删除时间条目(我们将在接下来处理),并在其中添加了编辑屏幕。 我们使用ng-show有条件地隐藏和显示编辑区域,在该区域中附加了一个名为showEditDialog的属性,该属性的状态决定是否显示编辑屏幕。

The select element that uses ng-options looks a bit different for the editing screen. We use the track by statement for ng-options here to let the select box know which user should be pre-selected when the screen is opened.

使用ng-options的select元素在编辑屏幕上看起来有些不同。 我们在这里使用ng-optionstrack by语句来使选择框知道打开屏幕时应预先选择哪个用户。

Finally, we are passing the whole time record into the vm.updateTimeEntry method which is called when we click the save button.

最后,我们将整个time记录传递到vm.updateTimeEntry方法中,该方法在单击保存按钮时被调用。

We'll need some additional CSS to make things look a bit better:

我们将需要一些其他CSS来使外观看起来更好:

/* public/css/style.css */

.edit-time-entry {
    border-top: 1px solid #ccc;
    background-color: #e0e0e0;
    padding: 15px;
    margin: 10px !important;
}

time-tracker-2-10

向Laravel控制器添加逻辑 (Adding Logic to the Laravel Controller)

Next, let's add some logic to the Laravel controller to update the database.

接下来,让我们向Laravel控制器添加一些逻辑以更新数据库。

// app/Http/Controllers/TimeEntriesController.php

...

// Grab all the data passed into the request and fill the database record with the new data
public function update($id)
{
    $timeentry = TimeEntry::find($id);

    $data = Request::all();

    $timeentry->fill($data);

    $timeentry->save();

}

...

The update method needs to know which time entry we want to update, so we pass in an id to work with. We use Laravel's find helper to get the appropriate time entry and also capture all the data passed to the endpoint. We again use fill to fill in all of the database fields with the data we passed to the controller and then save the record.

update方法需要知道我们要更新哪个时间条目,因此我们传入一个id进行处理。 我们使用Laravel的find助手来获取适当的时间条目,并捕获传递给端点的所有数据。 我们再次使用fill将传递给控制器​​的数据fill到所有数据库字段中,然后保存记录。

更新服务和控制器 (Updating the Service and Controller)

Now that the markup and Laravel logic is in place, let's update the service and the controller.

现在标记和Laravel逻辑已经就绪,让我们更新服务和控制器。

// public/scripts/services/time.js

...

// Use a PUT request to save the updated data passed in
function updateTime(data) {
  return Time.update({id:data.id}, data).$promise.then(function(success) {
    console.log(success);
  }, function(error) {
    console.log(error);
  });
}

...

return {
  getTime: getTime,
  getTimeDiff: getTimeDiff,
  getTotalTime: getTotalTime,
  saveTime: saveTime,
  updateTime: updateTime
}

...

Here we've added in a new method called updateTime which takes the data passed from the controller and calls update on the Time resource. This sends a PUT request to the Laravel update controller which takes the data in and saves it.

在这里,我们添加了一个名为updateTime的新方法,该方法接收从控制器传递的数据,并在Time资源上调用update 。 这会将PUT请求发送到Laravel update控制器,后者将数据放入并保存。

// public/scripts/controllers/TimeEntry.js

...

vm.updateTimeEntry = function(timeentry) {

  // Collect the data that will be passed to the updateTime method
  var updatedTimeEntry = {
    "id":timeentry.id,
    "user_id":timeentry.user.id,
    "start_time":timeentry.start_time,
    "end_time":timeentry.end_time,
    "comment":timeentry.comment
  }     

  // Update the time entry and then refresh the list
  time.updateTime(updatedTimeEntry).then(function(success) {
    getTimeEntries();
    $scope.showEditDialog = false;
    console.log(success);
  }, function(error) {
    console.log(error);
  });

}  
...

In the controller we have added a method called vm.updateTimeEntry which is called from the view using ng-click when the save button is clicked. This method accepts the time object which has all the properties of a single time record---more specifically, the record that is being edited. In this method we call the updateTime method on the time service and after the request has succeeded we call getTimeEntries to refresh the list, as well as close the edit dialog by setting our showEditDialog to false.

在控制器中,我们添加了一个名为vm.updateTimeEntry的方法, ng-click保存按钮后会使用ng-click从视图中调用该方法。 此方法接受具有单个时间记录的所有属性的time对象-更具体地说,是正在编辑的记录。 在此方法中,我们在time服务上调用updateTime方法,在请求成功后,我们调用getTimeEntries刷新列表,以及通过将showEditDialog设置为false来关闭编辑对话框。

All the pieces are now in place to edit our time entries. The only thing left now is to give the user the ability to delete them.

现在所有部分都已准备就绪,可以编辑我们的时间条目。 现在剩下的唯一一件事就是使用户能够删除它们。

删除时间条目 (Deleting Time Entries)

We've already put a button in place for deleting time entries so all we need to do now is put the Laravel logic in place and update the Angular service and controller to handle the delete request.

我们已经放置了一个删除时间条目的按钮,因此我们现在要做的就是放置Laravel逻辑并更新Angular服务和控制器以处理删除请求。

// app/Http/Controllers/TimeEntriesController.php

...

// Find the time entry to be deleted and then call delete
public function destroy($id)
{
    $timeentry = TimeEntry::find($id);

    $timeentry->delete();   
}

...

The Laravel logic for deleting a time entry is pretty straight forward---we just need to find the record based on an id we pass into the controller and then call delete on it.

用于删除时间条目的Laravel逻辑非常简单-我们只需要根据传递给控制器​​的id查找记录,然后对其调用delete。

Now let's update the Angular service:

现在让我们更新Angular服务:

// public/scripts/services/time.js

...

// Send a DELETE request for a specific time entry
function deleteTime(id) {
  return Time.delete({id:id}).$promise.then(function(success) {
    console.log(success);
  }, function(error) {
    console.log(error);
  });
}

...

return {
  getTime: getTime,
  getTimeDiff: getTimeDiff,
  getTotalTime: getTotalTime,
  saveTime: saveTime,
  updateTime: updateTime,
  deleteTime: deleteTime
}

And finally the controller:

最后是控制器:

// public/scripts/controllers/TimeEntry.js

// Specify the time entry to be deleted and pass it to the deleteTime method on the time service
vm.deleteTimeEntry = function(timeentry) {

  var id = timeentry.id;

  time.deleteTime(id).then(function(success) {
    getTimeEntries();
    console.log(success);
  }, function(error) {
    console.log(error);
  });      

}

The vm.timeentry method is called when we click the delete button in the view and uses the id property of the time object to tell Laravel which record to delete

当我们单击视图中的删除按钮时,将调用vm.timeentry方法,并使用时间对象的id属性告诉Laravel要删除的记录

结语 (Wrapping Up)

There we have it! A fully functioning app that lets users create, read, update and delete time entries. Although we have the base functionality in place for the app to work, there are still a few items that are missing which could improve it:

到了! 一个功能齐全的应用程序,可让用户创建,读取,更新和删除时间条目。 尽管我们具备该应用程序可以使用的基本功能,但是仍然缺少一些可以改进它的项目:

  • It would be great to have a flash message show when entries are created, updated and deleted

    创建,更新和删除条目时显示闪动消息将是很棒的
  • We should be more careful with the Laravel logic---it would be great if we could add in some conditional checks to our Laravel controllers to make sure data made it from the front-end to the API

    我们应该对Laravel逻辑更加谨慎-如果我们可以在Laravel控制器中添加一些条件检查以确保数据从前端发送到API,那将是很好的
  • We don't need every Laravel controller method that we currently have---we could get rid of the ones we're not using

    我们不需要我们目前拥有的所有Laravel控制器方法-我们可以摆脱不使用的方法
  • As it stands, users can't select dates from a calendar picker like they should be able to.

    就目前而言,用户无法像他们应该的那样从日历选择器中选择日期。

对您的挑战 (A Challenge for You)

Now that we're done building the app together, here's a challenge for you: put the necessary code in place to accomplish the suggested improvements I mentioned above. I'd love to see your ideas for making those improvements or anything else you think the app could benefit from. Feel free to send pull requests our way!

现在我们已经完成了一起构建应用程序,这对您来说是一个挑战:放置必要的代码以完成我上面提到的建议的改进。 我很乐意看到您的想法,以进行这些改进或您认为应用程序可以从中受益的任何其他内容。 随意发送请求请求我们的方式!

给我留言 (Drop Me a Line)

If you’d like to get more AngularJS and Laravel tutorials, feel free to head over to my website and signup for my mailing list. You should follow me on Twitter---I'd love to hear about what you're working on!

如果您想获得更多AngularJS和Laravel教程,请随时访问我的网站注册我的邮件列表 。 您应该在Twitter上关注我---我很想知道您在做什么!

翻译自: https://scotch.io/tutorials/build-a-time-tracker-with-laravel-5-and-angularjs-part-2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值