发布于 2026-01-06 1 阅读
0

使用 Laravel 和 VueJS 构建你自己的看板(第一部分)——设置项目并构建看板 入门 创建任务和状态 构建看板 总结

使用 Laravel 和 VueJS 构建您自己的看板

第一部分 - 设置项目并构建看板

入门

创建我们的任务和状态

搭建看板

总结

第一部分 - 设置项目并构建看板

看板是管理任务、项目或生活的绝佳方式。它使用列来表示任务流程,也是一种流行的敏捷项目管理工具。如果你用过 Trello,就会明白我的意思。

那么,让我们来构建我们自己的 Trello 式看板吧!

完成的看板 gif


入门

让我们开始为新项目设置所有样板代码。

Laravel 安装和设置

首先创建一个新的 Laravel 项目。在终端中导航到你想创建项目的目录,然后运行以下命令之一。



# with the laravel installer
laravel new kanban-board

# or using composer
composer create-project --prefer-dist laravel/laravel kanban-board


Enter fullscreen mode Exit fullscreen mode

现在创建一个新的数据库并配置连接.env。对我来说,配置如下所示:



DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=kanban
DB_USERNAME=root
DB_PASSWORD=


Enter fullscreen mode Exit fullscreen mode

使用 TailwindCSS 构建 UI 和身份验证框架

本指南中我们不会过多关注身份验证,Laravel 会自动处理框架搭建。此外,我们将使用Tailwind CSS作为 CSS 框架。

幸运的是,@michaeldyrynda制作了一个非常棒的 laravel/ui 预设,用于初始化所有框架。您可以在这里查看:laravel-frontend-presets/tailwindcss

安装依赖项并运行迁移:



# install laravel/ui from composer
composer require laravel-frontend-presets/tailwindcss --dev

# then generate the scaffolding including authentication
php artisan ui tailwindcss --auth

# install npm dependencies
npm install && npm run dev

# ready to migrate
php artisan migrate


Enter fullscreen mode Exit fullscreen mode

太棒了!我们已经完成了身份验证框架的搭建,数据库也已设置完毕,使用 Tailwind 一切看起来都很完美。请在浏览器中访问您的本地站点并创建您的用户帐户 👉 http://kanban-board.test

注册页面


创建我们的任务和状态

我们知道需要创建任务并通过不同的列(我们称之为状态)来跟踪它们,那么让我们开始在 Laravel 中创建模型吧。

一项任务应包含以下内容:

  • 标题 — 必须有标题
  • 描述——可添加的额外详细信息(可选)
  • 顺序——应该知道它在列中的位置。
  • 状态 ID — 我们需要跟踪任务当前所处的状态。
  • 用户 ID — 一项任务应该只属于一个用户

状态应包含以下内容

  • 标题——状态应具有描述性标题
  • Slug — 标题的 Slug 化版本,可用作键值对和更美观的 URL
  • 顺序——对于列来说尤其如此,状态应该有明确的顺序。
  • 用户 ID — 允许用户创建自己的状态和工作流程

创建任务状态模型、迁移和控制器:



php artisan make:model -mc Task
php artisan make:model -mc Status


Enter fullscreen mode Exit fullscreen mode

注意这些-mc标志;这将为我们的模型创建一个迁移控制器。


迁徙

一些



// database/migrations/XXXX_create_tasks_table.php
class CreateTasksTable extends Migration
{
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('description')->nullable();
            $table->smallInteger('order')->default(0);
            $table->unsignedInteger('user_id');
            $table->unsignedInteger('status_id');
            $table->timestamps();
        });
    }
}

// database/migrations/XXXX_create_statuses_table.php
class CreateStatusesTable extends Migration
{
    public function up()
    {
        Schema::create('statuses', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('slug');
            $table->smallInteger('order')->default(0);
            $table->unsignedInteger('user_id');
        });
    }
}


Enter fullscreen mode Exit fullscreen mode

根据我们上面的简要说明,我们已经创建了所有需要的列。现在运行迁移:



php artisan migrate


Enter fullscreen mode Exit fullscreen mode

模型

我们需要定义每个模型之间的关系,并告诉 Laravel 哪些属性可以批量赋值。以后需要的时候我们会再补充,但这已经足够我们入门了。

用户

将关系添加到任务和状态中。请注意,我们可以设置关系始终按照 order 属性指定的顺序返回记录。



// app/User.php

// ...
class User extends Authenticatable
{
    protected $fillable = [
        'name', 'email', 'password',
    ];

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

    public function tasks()
    {
        return $this->hasMany(Task::class);
    }

    public function statuses()
    {
        return $this->hasMany(Status::class)->orderBy('order');
    }
}


Enter fullscreen mode Exit fullscreen mode

任务



// app/Task.php

// ...
class Task extends Model
{
    protected $fillable = [title, description, order, status_id];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function status()
    {
        return $this->belongsTo(Status::class);
    }
}


Enter fullscreen mode Exit fullscreen mode

地位

由于我们在迁移文件中移除了时间戳,我们需要告诉 Laravel 在创建/更新记录时不要尝试修改它们。此外,我们还需要orderBy为任务关联添加一个默认值。



// app/Status.php

// ...
class Status extends Model
{
    protected $fillable = [title, slug, order];

    public $timestamps = false;

    public function tasks()
    {
        return $this->hasMany(Task::class)->orderBy('order');
    }

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}


Enter fullscreen mode Exit fullscreen mode

路由和控制器

让我们思考一下我们需要采取哪些行动,以及我们需要哪些路由和控制器方法。

任务

  • 获取所有任务GET tasks——TaskController@index
  • 添加新任务POST tasks——TaskController@store
  • 更新任务PUT tasks/{task}——TaskController@update

状态

  • 添加新状态 — POST statusesStatusController@store
  • 更新状态PUT statuses——StatusController@update

为了简单起见,我们现在先将/home路由重定向到/tasks

请将文件更新/routes/web.php为以下内容:



// routes/web.php

// Update our 'home' route to redirect to /tasks
Route::get('/home', function () {
    return redirect()->route('tasks.index');
})->name('home');

Route::group(['middleware' => 'auth'], function () {
    Route::get('tasks', 'TaskController@index')->name('tasks.index');
    Route::post('tasks', 'TaskController@store')->name('tasks.store');
    Route::put('tasks/sync', 'TaskController@sync')->name('tasks.sync');
    Route::put('tasks/{task}', 'TaskController@update')->name('tasks.update');
});

Route::group(['middleware' => 'auth'], function () {
    Route::post('statuses', 'StatusController@store')->name('statuses.store');
    Route::put('statuses', 'StatusController@update')->name('statuses.update');
});


Enter fullscreen mode Exit fullscreen mode

您可以运行程序php artisan route:list查看所有可用的路线。

路线列表

更新任务控制器

让我们思考一下看板上预期呈现的数据:一个用户有很多任务,这些任务也属于同一个状态。如果我们按这种结构返回数据会怎样呢?

我们不会只返回用户的任务数组,而是返回用户的状态数组,该状态数组又包含该状态下的任务数组。

更新index()TaskController 中的方法:



// app/Http/Controllers/TaskController.php

// ...
class TaskController extends Controller
{
    public function index()
    {
        $tasks = auth()->user()->statuses()->with('tasks')->get();

        return view('tasks.index', compact('tasks'));
    }

    // ...
}


Enter fullscreen mode Exit fullscreen mode

创建看板将要放置的视图

/resources/views/tasks在名为 `named` 的文件夹中创建一个新的 Blade 模板index.blade.php,并添加以下样板代码:



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

@extends('layouts.app')

@section('content')
<div class="md:mx-4 relative overflow-hidden">
    <main class="h-full flex flex-col overflow-auto">
        <!-- Our Kanban Vue component will go here -->
    </main>
</div>
@endsection


Enter fullscreen mode Exit fullscreen mode

默认列

为了更快地入门,请创建一些默认状态,每个新用户都将拥有这些状态。使用 User 类的booted方法,我们可以在创建新用户时创建和添加状态。

将以下内容添加到您的User.php模型中:



// app/User.php

protected static function booted()
    {
        static::created(function ($user) {
            // Create default statuses
            $user->statuses()->createMany([
                [
                    'title' => 'Backlog',
                    'slug' => 'backlog',
                    'order' => 1
                ],
                [
                    'title' => 'Up Next',
                    'slug' => 'up-next',
                    'order' => 2
                ],
                [
                    'title' => 'In Progress',
                    'slug' => 'in-progress',
                    'order' => 3
                ],
                [
                    'title' => 'Done',
                    'slug' => 'done',
                    'order' => 4
                ]
            ]);
        });
    }


Enter fullscreen mode Exit fullscreen mode

完成初始后端

我们目前为止做了哪些工作?

  1. 已安装 Laravel 和所需的 Composer 依赖项。
  2. 身份验证框架
  3. 创建了状态任务的资源
  4. 将用户任务返回到视图
  5. 创建用户时创建一些默认状态

后端初始样板代码和脚手架都已完成,接下来我们来编写一些前端代码。


搭建看板

让我们来详细分析一下如何才能让我们的第一个组件正常运行:

  1. 设置 Vue
  2. 创建我们的看板组件
  3. 添加新任务
  4. 实现拖放功能
  5. 任务移动后,更新其顺序和状态。

安装 Vue

由于我们没有使用 Laravel 的 Vue 脚手架,所以需要自己进行配置。请从 npm 获取:



npm install vue


Enter fullscreen mode Exit fullscreen mode

Vue安装完成后,我们可以在resources/js/app.js文件中对其进行初始化:



require("./bootstrap");

window.Vue = require("vue");

// Register our components (in the next step)

const app = new Vue({
    el: "#app"
});


Enter fullscreen mode Exit fullscreen mode

搞定!我们的 Laravel 应用现在已经集成了 Vue,可以开始利用它提供的所有功能了。npm run dev在浏览器中运行并检查网站,控制台现在应该会提示你正在以开发模式运行 Vue,这很好。

提示:
在继续之前,我建议你安装适用于你当前浏览器的Vue 开发者工具。这样可以方便你调试并深入了解 Vue 组件的状态。


注册看板组件

KanbanBoard.vue在组件文件夹中创建一个名为 `<components_name>` 的新文件resources/js/components/,并将其注册到您的app.js文件中:



// resources/js/app.js

// Register our components
Vue.component("kanban-board", require("./components/KanbanBoard.vue").default);


Enter fullscreen mode Exit fullscreen mode

将该组件添加到 Blade 视图中:



// resources/views/tasks/index.blade.php

<main class="h-full flex flex-col overflow-auto">
    <kanban-board :initial-data="{{ $tasks }}"></kanban-board>
</main>


Enter fullscreen mode Exit fullscreen mode

为了避免npm run dev每次更改组件时都必须这样做:



npm run watch


Enter fullscreen mode Exit fullscreen mode

让我们一起建造它

我知道了……终于!

接下来内容很多,但大部分只是 HTML 脚手架搭建和应用 TailwindCSS 类。我们会把重点部分拆解开来。



<template>
  <div class="relative p-2 flex overflow-x-auto h-full">

    <!-- Columns (Statuses) -->
    <div
      v-for="status in statuses"
      :key="status.slug"
      class="mr-6 w-4/5 max-w-xs flex-1 flex-shrink-0"
    >
      <div class="rounded-md shadow-md overflow-hidden">
        <div class="p-3 flex justify-between items-baseline bg-blue-800 ">
          <h4 class="font-medium text-white">
            {{ status.title }}
          </h4>
          <button class="py-1 px-2 text-sm text-orange-500 hover:underline">
            Add Task
          </button>
        </div>
        <div class="p-2 flex-1 flex flex-col h-full overflow-x-hidden overflow-y-auto bg-blue-100">

          <!-- Tasks -->
          <div
            v-for="task in status.tasks"
            :key="task.id"
            class="mb-3 p-3 h-24 flex flex-col bg-white rounded-md shadow transform hover:shadow-md cursor-pointer"
          >
            <span class="block mb-2 text-xl text-gray-900">
              {{ task.title }}
            </span>
            <p class="text-gray-700 truncate">
              {{ task.description }}
            </p>
          </div>
          <!-- ./Tasks -->

          <!-- No Tasks -->
          <div
            v-show="!status.tasks.length"
            class="flex-1 p-4 flex flex-col items-center justify-center"
          >
            <span class="text-gray-600">No tasks yet</span>
            <button
              class="mt-1 text-sm text-orange-600 hover:underline"
            >
              Add one
            </button>
          </div>
          <!-- ./No Tasks -->
        </div>
      </div>
    </div>
    <!-- ./Columns -->

  </div>
</template>

<script>
export default {
  props: {
    initialData: Array
  },
  data() {
    return {
      statuses: []
    };
  },
  mounted() {
    // 'clone' the statuses so we don't alter the prop when making changes
    this.statuses = JSON.parse(JSON.stringify(this.initialData));
  }
};
</script>


Enter fullscreen mode Exit fullscreen mode

请记住,在命名 props 时,HTML 中应使用 kebab-cased 命名法(initial-data),组件中应使用 camelCase 命名法(initialData)。

您可能已经注意到,我们对 prop 数据进行了一些奇怪的 JSON 解析,这会创建一个数组的“克隆”,因此我们实际上不会更改 prop 传递的数据,而只会更改我们的副本。


我们使用替代方案JSON.parse(JSON.stringify(this.initialData));而不是类似方案[…this.initialData],因为它在处理嵌套数据(例如每个状态对象中的任务数组)时更安全。

在模板中,我们用来v-for="status in statuses"遍历状态数组以显示列。别忘了添加一个 `<div>` 标签key,这对于本项目尤为重要,因为我们希望 Vue 能够跟踪元素的顺序。

同样地,在每一列中,我们v-for使用当前状态下的任务列表来显示我们的任务卡片。

空板


添加新任务

添加任务表单

创建 AddTaskForm 组件

我们将AddTaskForm.vue/resources/js/components目录中创建一个名为“Vue”的新组件。

该组件将包含一个表单,其中包含标题和描述字段。它会将表单数据发送到我们的服务器,服务器将在我们的数据库中创建新任务,并在没有验证错误的情况下返回该任务。

我们将把这个新任务传递给看板组件,并将其添加到正确的列中。



// resources/js/components/AddTaskForm.vue

<template>
  <form
    class="relative mb-3 flex flex-col justify-between bg-white rounded-md shadow overflow-hidden"
    @submit.prevent="handleAddNewTask"
  >
    <div class="p-3 flex-1">
      <input
        class="block w-full px-2 py-1 text-lg border-b border-blue-800 rounded"
        type="text"
        placeholder="Enter a title"
        v-model.trim="newTask.title"
      />
      <textarea
        class="mt-3 p-2 block w-full p-1 border text-sm rounded"
        rows="2"
        placeholder="Add a description (optional)"
        v-model.trim="newTask.description"
      ></textarea>
      <div v-show="errorMessage">
        <span class="text-xs text-red-500">
          {{ errorMessage }}
        </span>
      </div>
    </div>
    <div class="p-3 flex justify-between items-end text-sm bg-gray-100">
      <button
        @click="$emit('task-canceled')"
        type="reset"
        class="py-1 leading-5 text-gray-600 hover:text-gray-700"
      >
        cancel
      </button>
      <button
        type="submit"
        class="px-3 py-1 leading-5 text-white bg-orange-600 hover:bg-orange-500 rounded"
      >
        Add
      </button>
    </div>
  </form>
</template>

<script>
export default {
  props: {
    statusId: Number
  },
  data() {
    return {
      newTask: {
        title: "",
        description: "",
        status_id: null
      },
      errorMessage: ""
    };
  },
  mounted() {
    this.newTask.status_id = this.statusId;
  },
  methods: {
    handleAddNewTask() {
      // Basic validation so we don't send an empty task to the server
      if (!this.newTask.title) {
        this.errorMessage = "The title field is required";
        return;
      }

      // Send new task to server
      axios
        .post("/tasks", this.newTask)
        .then(res => {
          // Tell the parent component we've added a new task and include it
          this.$emit("task-added", res.data);
        })
        .catch(err => {
          // Handle the error returned from our request
          this.handleErrors(err);
        });
    },
    handleErrors(err) {
      if (err.response && err.response.status === 422) {
        // We have a validation error
        const errorBag = err.response.data.errors;
        if (errorBag.title) {
          this.errorMessage = errorBag.title[0];
        } else if (errorBag.description) {
          this.errorMessage = errorBag.description[0];
        } else {
          this.errorMessage = err.response.message;
        }
      } else {
        // We have bigger problems
        console.log(err.response);
      }
    }
  }
};
</script>


Enter fullscreen mode Exit fullscreen mode

在这个组件中,data我们持续跟踪一个newTask对象,该对象status_id从 props 获取其属性。

通过使用,v-model.trim=“newTask.title”我们可以在输入和状态之间建立双向绑定,并告诉 Vue 删除任何空白字符。

提交表单后,我们需要将发生的情况告知父组件并返回新任务。如果响应中存在错误,我们会显示一条消息,告知用户哪里出了问题。

将新组件添加到看板



// resources/js/components/KanbanBoard.vue

<template>
  // ...
  <AddTaskForm
    v-if="newTaskForStatus === status.id"
    :status-id="status.id"
    v-on:task-added="handleTaskAdded"
    v-on:task-canceled="closeAddTaskForm"
  />
  // Add this just above our list of tasks
  <!-- Tasks -->
  //...
  <!-- No Tasks -->
  // Update the placeholder to include a click handler to create a new task
  // and hide it when the form is open
  <div
    v-show="!status.tasks.length && newTaskForStatus !== status.id"
    class="flex-1 p-4 flex flex-col items-center justify-center"
  >
    <span class="text-gray-600">No tasks yet</span>
    <button
      class="mt-1 text-sm text-orange-600 hover:underline"
      @click="openAddTaskForm(status.id)"
    >
      Add one
    </button>
  </div>
  <!-- ./No Tasks -->
</template>

<script>
import AddTaskForm from "./AddTaskForm"; // import the component

export default {
  components: { AddTaskForm }, // register component
  // ...
  data() {
    return {
      statuses: [],

      newTaskForStatus: 0 // track the ID of the status we want to add to
    };
  },
  // ...
  methods: {
    // set the statusId and trigger the form to show 
    openAddTaskForm(statusId) {
      this.newTaskForStatus = statusId;
    },
    // reset the statusId and close form
    closeAddTaskForm() {
      this.newTaskForStatus = 0;
    },
    // add a task to the correct column in our list
    handleTaskAdded(newTask) {
      // Find the index of the status where we should add the task
      const statusIndex = this.statuses.findIndex(
        status => status.id === newTask.status_id
      );

      // Add newly created task to our column
      this.statuses[statusIndex].tasks.push(newTask);

      // Reset and close the AddTaskForm
      this.closeAddTaskForm();
    },
  }
};
</script>


Enter fullscreen mode Exit fullscreen mode

将新任务存储到服务器上

回到我们的 Laravel 应用中,我们需要更新我们的代码TasksController来处理存储新任务。



// app/Http/Controllers/TaskController

public function store(Request $request)
{
    $this->validate($request, [
        'title' => ['required', 'string', 'max:56'],
        'description' => ['required', 'string'],
        'status_id' => ['required', 'exists:statuses,id']
    ]);

    return $request->user()
        ->tasks()
        ->create($request->only('title', 'description', 'status_id'));
}


Enter fullscreen mode Exit fullscreen mode

首先,我们需要验证传入的请求,以确保我们获取的数据符合预期,否则将返回 422 响应以及验证错误(我们已经捕获并处理了这些错误 🙌)。

如果一切顺利,我们可以保存新任务并将其附加到已验证用户并返回该任务。

安全提示:
虽然任务会添加到已认证用户,但我们不会检查该用户是否拥有所附加的状态。我们将在后续章节中讨论授权和策略,请记住这一点。


拖拽和掉落

好了,我们现在有很多列,每列里都包含一些任务,我们还可以创建新任务。接下来,让我们进入有趣的部分:使用拖放功能将卡片从一列移动到另一列。

我们将使用SortableJS/Vue.Draggable来快速实现拖放功能。请使用 npm 安装它:



npm install vuedraggable

# once installed run watch again
npm run watch


Enter fullscreen mode Exit fullscreen mode

将其添加到我们的看板组件中

安装完成后vuedraggable,让我们把它连接到我们的列上,开始传输任务。



// resources/js/components/KanbanBoard.vue

<template>
  // ...
  <div class="p-2 bg-blue-100"> // Update these classes because we're moving them to our transition-group
    <!-- AddTaskForm -->
    // ...
    <!-- ./AddTaskForm -->

    <!-- Tasks -->
    <draggable
      class="flex-1 overflow-hidden"
      v-model="status.tasks"
      v-bind="taskDragOptions"
      @end="handleTaskMoved"
    >
      <transition-group
        class="flex-1 flex flex-col h-full overflow-x-hidden overflow-y-auto rounded shadow-xs"
        tag="div"
      >
        <div
          v-for="task in status.tasks"
          :key="task.id"
          class="mb-3 p-3 h-24 flex flex-col bg-white rounded-md shadow transform hover:shadow-md cursor-pointer"
        >
          // ... nothing changed in here
        </div>
        <!-- ./Tasks -->
      </transition-group>
    </draggable>
  // ...
  </div>
<template>

<script>
import draggable from "vuedraggable"; // import the vuedraggable component
// ...

export default {
  components: { draggable, AddTaskForm }, // register 
  // ...
  computed: {
    taskDragOptions() {
      return {
        animation: 200,
        group: "task-list",
        dragClass: "status-drag"
      };
    }
  },
  // ...
  methods: {
    // ...
    handleTaskMoved() {
      // Send the entire list of statuses to the server
      axios.put("/tasks/sync", {columns: this.statuses}).catch(err => {
        console.log(err.response);
      });
    }
  }
};
</script>

<style scoped>
.status-drag {
  transition: transform 0.5s;
  transition-property: all;
}
</style>


Enter fullscreen mode Exit fullscreen mode

导入并注册draggable组件后,我们需要对其进行配置。

group我们的值告诉taskDragOptionssortable,即使我们的元素分布在不同的列中,它们也应该分组在一起。

上面的模板中,我们添加了 `<div>`<draggable>和 `<div>`组件,这样在拖动列表卡片时,卡片移动会更加流畅,从而带来更好的视觉反馈。请注意我们对 CSS 类所做的更新,这些组件会渲染它们自己的 DOM 元素,在使用flex布局排列子元素<transition-group>时,我们需要考虑到这一点。

这也是我们第一次在 Vue 组件中使用 section 属性style。我们只是给可拖动元素添加了一些过渡效果,但你也可以在拖动过程中更改卡片的样式。比如,添加一个更大的阴影?

现在我们可以在列内和列之间移动任务了!我们离目标很近了,最后一步是将订单保存到数据库。我们已经在组件中发出了请求,PUT所以/tasks/sync需要创建一个控制器方法来处理这个请求。


同步 TaskController 中的任务顺序

当我们把任务拖到新列中时,我们希望数据库中也能反映出这个变化。这部分代码会返回包含所有任务的列列表,然后由后端处理这些任务的更新方式。

首先,我们来添加新路线:



// routes/web.php

//..
Route::group(['middleware' => 'auth'], function () {
    Route::get('tasks', 'TaskController@index')->name('tasks.index');
    Route::post('tasks', 'TaskController@store')->name('tasks.store');
    // Important: this needs to be above the /tasks/{task} route
    Route::put('tasks/sync', 'TaskController@sync')->name('tasks.sync');
    Route::put('tasks/{task}', 'TaskController@update')->name('tasks.update');
});


Enter fullscreen mode Exit fullscreen mode

请务必将此新路由添加到更新路由之上/tasks/{task}。这一点很重要,否则会触发该路由/tasks/sync而不是更新路由,导致我们的应用程序查找 ID 为“sync”的任务。

将新方法添加到我们的控制器中:



// app/Http/Controllers/TaskController.php

// ...
public function sync(Request $request)
    {
        $this->validate(request(), [
            'columns' => ['required', 'array']
        ]);

        foreach ($request->columns as $status) {
            foreach ($status['tasks'] as $i => $task) {
                $order = $i + 1;
                if ($task['status_id'] !== $status['id'] || $task['order'] !== $order) {
                    request()->user()->tasks()
                        ->find($task['id'])
                        ->update(['status_id' => $status['id'], 'order' => $order]);
                }
            }
        }

        return $request->user()->statuses()->with('tasks')->get();
    }


Enter fullscreen mode Exit fullscreen mode

我们在这里所做的,是遍历所有列,检查任务的顺序状态是否发生了变化。如果发生了变化,我们就更新该任务。

性能说明:
这不是处理同步更改的最有效方法,但稍后我们将深入研究并重构它,并构建一个更强大的 API。


现在轮到你了

目前我们可以创建新任务,并将其在列之间以及列表中上下移动,但无法更新或删除任务。请运用所学知识,尝试将此功能添加到我们的 Vue 组件中,并创建相应的控制器方法。

我们将在下一部分一起完成,并比较不同的方法!

完整看板

总结

呼,这部分比我预想的要长得多。如果你读到这里,恭喜你!搭建和撰写这个模型的过程非常有趣。

如果您有任何疑问,或者想讨论不同的方法,请留言。

接下来会发生什么?

接下来我们将:

  • 将Vuex实现为状态管理系统
  • 自定义我们的专栏
  • 更新和删除任务
  • 重构我们的 API
  • 在我们的 Vue 组件和后端之间构建一个存储库层。

如果大家感兴趣,我们将继续深入研究如何改进代码、重构代码以及增加测试覆盖率。

Github 仓库

本指南中的所有内容都已上传至 GitHub 👉 GitHub - messerli90/laravel-vue-kanban-tutorial

GitHub 标志 messerli90 / laravel-vue-kanban-tutorial

使用 Laravel 和 Vue 构建看板(系列教程)

我会为本系列的每个部分创建一个分支。因此,如果您提交到主分支,它看起来可能与我们上面构建的内容完全不同。


插头

几个月前我发表了这篇博文:

从那时起,我决定围绕这个想法打造一款真正的产品。它使用与我们今天构建的类似的看板来跟踪求职申请的状态,并提供许多其他功能来帮助您管理求职过程。

替代文字

您可以点击这里查看 👉 JobHuntBuddy.co

原版 JobHuntBuddy 将继续保持免费开源,您可以在这里找到它

关注我的推特账号@michaelmesserli,即可与我互动,并查看我关于科技、旅行和游戏的随笔。

文章来源:https://dev.to/messerli90/build-your-own-kanban-board-with-laravel-vuejs-2i5l