您现在的位置是:自如初>LaravelLaravel

Laravel9 & Inertia & Vue3 构建单页 SPA CURD 页面演示案例

温新 2022-08-07 23:04:00 Laravel 2555人已围观

简介Laravel9 & Inertia & Vue3 构建单页 SPA CURD 页面演示案例。Laravel9 已经使用 Vite 前端工具来构建应用,习惯了原有的方式时,再来使用 Vite 新工具,似乎有点不太会用了,一切都好像变了,但一切都好像又没变。一股熟悉的陌生感迎面而来。就以本篇文章作为拥抱新变化的开始吧!

hi,我是温新,一名PHPer


积极拥抱变化,不要止步不前

版本:Laravel9.3.1

时间:2022-08-07

Laravel9 已经使用 Vite 前端工具来构建应用,习惯了原有的方式时,再来使用 Vite 新工具,似乎有点不太会用了,一切都好像变了,但一切都好像又没变。一股熟悉的陌生感迎面而来。就以本篇文章作为拥抱新变化的开始吧!

目标:本篇文章将使用 Laravel9 & Inertia Js & Vue3 来构建一个 CURD 简单的 SPA 应用。

第一步:创建项目并配置数据库

1)创建项目

composer create-project laravel/laravel la9vue3

2)配置数据库

// .env

DB_DATABASE=la9vue3
DB_USERNAME=la9vue3
DB_PASSWORD=la9vue3

第二步:安装 breeze & inertia js & vue3

composer require laravel/breeze --dev
php artisan breeze:install vue
npm install && npm run dev

第三步:创建 Post 模型 迁移文件 控制器

1)创建 Post 相关文件

php artisan make:model Post -mcr

2)编写迁移文件

<?php
// database/migrations/2022_08_07_131142_create_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
   public function up()
   {
       Schema::create('posts', function (Blueprint $table) {
           $table->bigIncrements('id');
           $table->string('title');
           $table->string('slug')->unique();
           $table->text('content');
           $table->timestamps();
       });
   }

   public function down()
   {
       Schema::dropIfExists('posts');
   }
};

2)修改 Post 模型文件

<?php
// app/Models/Post.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
   protected $fillable = ['title','slug','content'];
}

3)添加路由

<?php
// routes/web.php
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

Route::get('/', function () {
   return Inertia::render('Welcome', [
       'canLogin' => Route::has('login'),
       'canRegister' => Route::has('register'),
       'laravelVersion' => Application::VERSION,
       'phpVersion' => PHP_VERSION,
   ]);
});

Route::get('/dashboard', function () {
   return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');

// 添加 posts 资源路由
Route::resource('posts', \App\Http\Controllers\PostController::class);

require __DIR__.'/auth.php';

4)编写 PostController 控制器

<?php
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Inertia\Inertia;

class PostController extends Controller
{
   public function index()
   {
       $posts = Post::all();

       return Inertia::render('Post/Index', compact('posts'));
   }

   public function create()
   {
       return Inertia::render('Post/Create');
   }

   public function store(Request $request)
   {
       $request->validate([
           'title' => 'required|string|max:255',
           'slug' => 'required|string|max:255',
           'content' => 'required',
       ]);
       Post::create([
           'title' => $request->title,
           'slug' => Str::slug($request->slug),
           'content' => $request->content
       ]);

       return redirect()->route('posts.index')->with('message', 'Post Created Successfully');
   }

   public function edit(Post $post)
   {
       return Inertia::render('Post/Edit', compact('posts'));
   }

   public function update(Request $request, Post $post)
   {
       $request->validate([
           'title' => 'required|string|max:255',
           'slug' => 'required|string|max:255',
           'content' => 'required',
       ]);

       $post->title = $request->title;
       $post->slug = Str::slug($request->slug);
       $post->content = $request->content;
       $post->save();
       
       return redirect()->route('$post.index')->with('message', 'Post Updated Successfully');
   }

   public function destroy(Post $post)
   {
       $post->delete();
       return redirect()->route('posts.index')->with('message', 'Post Delete Successfully');
   }
}

第四步:创建 Vue 视图文件

1)创建 Index.vue。resources/js/Pages/Posts/Index.vue

<script setup>
   import BreezeButton from "@/Components/Button.vue";
   import { Head, Link, useForm } from '@inertiajs/inertia-vue3';

// 接收控制器数据
   const props = defineProps({
       posts: {
           type: Object,
           default: () => ({}),
       },
   });
   const form = useForm();

   function destroy(id) {
       if (confirm("Are you sure you want to Delete")) {
           form.delete(route('posts.destroy', id));
       }
   }
</script>

<template>
   <Head title="Post list" />

   <BreezeAuthenticatedLayout>
       <template #header>
           <h2 class="text-xl font-semibold leading-tight text-gray-800">
               Post List
           </h2>
       </template>

       <div class="py-12">
           <div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
               <div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
                   <div class="p-6 bg-white border-b border-gray-200">
                       <div class="mb-2">
                           <Link :href="route('posts.create')">
                               <BreezeButton>添加文章</BreezeButton></Link>
                       </div>
                       <div class="relative overflow-x-auto shadow-md sm:rounded-lg">
                           <table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
                               <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
                               <tr>
                                   <th scope="col" class="px-6 py-3">#</th>
                                   <th scope="col" class="px-6 py-3">
                                       Title
                                   </th>
                                   <th scope="col" class="px-6 py-3">
                                       Slug
                                   </th>
                                   <th scope="col" class="px-6 py-3">
                                       Edit
                                   </th>
                                   <th scope="col" class="px-6 py-3">
                                   </th>
                               </tr>
                               </thead>
                               <tbody>
                               <tr
                                   v-for="post in posts"
                                   :key="post.id"
                                   class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
                               >
                                   <th
                                       scope="row"
                                       class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"
                                   >
                                       {{ post.id }}
                                   </th>
                                   <th
                                       scope="row"
                                       class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"
                                   >
                                       {{ post.title }}
                                   </th>
                                   <td class="px-6 py-4">
                                       {{ post.slug }}
                                   </td>

                                   <td class="px-6 py-4">
                                       <Link :href="route('posts.edit',post.id)"
                                           class="px-4 py-2 text-white bg-blue-600 rounded-lg" >编辑</Link
                                       >
                                   </td>
                                   <td class="px-6 py-4">
                                       <BreezeButton
                                           class="bg-red-700"
                                           @click="destroy(post.id)"
                                       >
                                           删除
                                       </BreezeButton>
                                   </td>
                               </tr>
                               </tbody>
                           </table>
                       </div>
                   </div>
               </div>
           </div>
       </div>
   </BreezeAuthenticatedLayout>
</template>

2)创建 Create.vue。resources/js/Pages/Posts/Create.vue

<script setup>
   import BreezeButton from "@/Components/Button.vue";
   import { Head, Link, useForm } from '@inertiajs/inertia-vue3';

   const props = defineProps({
       posts: {
           type: Object,
           default: () => ({}),
       },
   });

   const form = useForm({
       title: '',
       slug: '',
       content: '',
   });

   const submit = () => {
       form.post(route("Posts.store"));
   };
</script>

<template>
   <Head title="Post Create" />

   <BreezeAuthenticatedLayout>
       <template #header>
           <h2 class="text-xl font-semibold leading-tight text-gray-800">
               添加文章
           </h2>
       </template>

       <div class="py-12">
           <div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
               <div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
                   <div class="p-6 bg-white border-b border-gray-200">
                       <form @submit.prevent="submit">
                           <div class="mb-6">
                               <label
                                   for="Title"
                                   class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                               >Title</label
                               >
                               <input
                                   type="text"
                                   v-model="form.title"
                                   name="title"
                                   class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                                   placeholder=""
                               />
                               <div
                                   v-if="form.errors.title"
                                   class="text-sm text-red-600"
                               >
                                   {{ form.errors.title }}
                               </div>
                           </div>
                           <div class="mb-6">
                               <label
                                   for="Slug"
                                   class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                               >Slug</label
                               >
                               <input
                                   type="text"
                                   v-model="form.slug"
                                   name="title"
                                   class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                                   placeholder=""
                               />
                               <div
                                   v-if="form.errors.slug"
                                   class="text-sm text-red-600"
                               >
                                   {{ form.errors.slug }}
                               </div>
                           </div>
                           <div class="mb-6">
                               <label
                                   for="slug"
                                   class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                               >Content</label
                               >
                               <textarea
                                   type="text"
                                   v-model="form.content"
                                   name="content"
                                   id=""
                                   class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                               ></textarea>

                               <div
                                   v-if="form.errors.content"
                                   class="text-sm text-red-600"
                               >
                                   {{ form.errors.content }}
                               </div>
                           </div>

                           <BreezeButton class="ml-4 mt-4" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
                              创建文章
                           </BreezeButton>
                       </form>
                   </div>
               </div>
           </div>
       </div>
   </BreezeAuthenticatedLayout>
</template>

3)创建 Edit.vue。resources/js/Pages/Posts/Edit.vue

<template>
   <Head title="Post Edit" />

   <BreezeAuthenticatedLayout>
       <template #header>
           <h2 class="text-xl font-semibold leading-tight text-gray-800">
               编辑文章
           </h2>
       </template>

       <div class="py-12">
           <div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
               <div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
                   <div class="p-6 bg-white border-b border-gray-200">
                       <form @submit.prevent="submit">
                           <div class="mb-6">
                               <label
                                   for="Title"
                                   class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                               >Title</label
                               >
                               <input
                                   type="text"
                                   v-model="form.title"
                                   name="title"
                                   class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                                   placeholder=""
                               />
                               <div
                                   v-if="form.errors.title"
                                   class="text-sm text-red-600"
                               >
                                   {{ form.errors.title }}
                               </div>
                           </div>
                           <div class="mb-6">
                               <label
                                   for="Slug"
                                   class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                               >Slug</label
                               >
                               <input
                                   type="text"
                                   v-model="form.slug"
                                   name="title"
                                   class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                                   placeholder=""
                               />
                               <div
                                   v-if="form.errors.slug"
                                   class="text-sm text-red-600"
                               >
                                   {{ form.errors.slug }}
                               </div>
                           </div>
                           <div class="mb-6">
                               <label
                                   for="slug"
                                   class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                               >Content</label
                               >
                               <textarea
                                   type="text"
                                   v-model="form.content"
                                   name="content"
                                   id=""
                                   class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                               ></textarea>

                               <div
                                   v-if="form.errors.content"
                                   class="text-sm text-red-600"
                               >
                                   {{ form.errors.content }}
                               </div>
                           </div>
                           <BreezeButton class="ml-4 mt-4" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
                               修改
                           </BreezeButton>
                       </form>
                   </div>
               </div>
           </div>
       </div>
   </BreezeAuthenticatedLayout>
</template>

<script setup>
   import BreezeButton from "@/Components/Button.vue";
   import { Head, Link, useForm } from '@inertiajs/inertia-vue3';

   const props = defineProps({
       post: {
           type: Object,
           default: () => ({}),
       },
   });

   const form = useForm({
       id: props.post.id,
       title: props.post.title,
       slug: props.post.slug,
       content: props.Post.content,
   });

   const submit = () => {
       form.put(route("posts.update", props.post.id));
   };
</script>

<style scoped></style>

这一个简单的案例到这里就已经完成了。

第五步:总结

这个案例使用了 Inertia Js & Vue3 & Tailwind CSS,从中可以看出,应用的构建发生了巨大的变化,初次转过来时,可能会有种难度加大的感觉,除了这个种感觉外,还会有种新颖强大的感觉。

后面,通过更多的案例走进新的世界。


很赞哦!(13)

文章评论

登录 注册

自如初--时间轴

站名:自如初

独白:向前走!向前走!

邮箱:ziruchu@qq.com

RSS: RSS

站点信息