【未経験】Laravel10 + Sail + Inertia + Vue3アプリでVuetify3のワイヤーフレームを使いコントローラーから変数を渡すまで

# 概要
初めてVuetifyを使いましたがLaravel10 + Sail + Inertia + Vue3を使ったアプリでVuetify3を使う記事が見当たらず苦戦したので備忘録として残します。

まだ初心者のため間違いがあるかもしれませんがコメントで教えていただけると助かります。

※Laravelについての知識があるのは前提とします。

# 環境
WSL2
Ubuntu 22.04.2 LTS

# 手順

### 1.Laravel10 + Sail + Inertia + Vue3のセットアップ

私はこちらの動画を参考にしました。とても分かりやすかったのでこちらの動画を見るのが早いと思いますが一応下に手順を記します。動画を参考にする方は`4.モデルやコントローラーを作成する`まで飛ばしてください。

:::note warn
こちらの動画ではvueは使っていないので[11:13](https://youtu.be/OZ2ks5wJiZ0?t=674)の`./vendor/bin/sail artisan breeze:install`の際にvueを選択してください。
:::

https://youtu.be/OZ2ks5wJiZ0




### 2.Laravel10をSail(Dokcer)で立ち上げる
コマンドラインで以下のコマンドを入力する。`example-app`の部分は自分のプロジェクト名にしてください。
途中でパスワードの入力を求められますので入力します。
```sh
curl -s "https://laravel.build/example-app" | bash
```
#参考
https://readouble.com/laravel/10.x/ja/installation.html




### 3.breezeのインストール
まずはsailを起動します。
```sh
./vendor/bin/sail up
```
その後以下のコマンドを入力
```sh
./vendor/bin/sail composer require laravel/breeze --dev
```
```sh
./vendor/bin/sail artisan breeze:install
```
この時viewに何を使用するか選べるのでここでvueを選択。
その後は自由に選べますがこだわりがなければ全てEnterを入力。
```sh
 Which stack would you like to install?
  blade ...................................................................... 0  
  react ...................................................................... 1  
  vue ........................................................................ 2  
  api ........................................................................ 3  
❯ 2

  Would you like to install dark mode support? (yes/no) [no]
❯ 

  Would you like TypeScript support? (Experimental): (yes/no) [no]
❯ 

  Would you like to install Inertia SSR support? (yes/no) [no]
❯ 

  Would you prefer Pest tests instead of PHPUnit? (yes/no) [no]
❯ 
```

その後migrateします。
```sh
./vendor/bin/sail artisan migrate
```






### 4.モデルやコントローラーを作成する
モデルやコントローラーを作成します。名前はご自身のプロジェクトによってきめてください。
今回は`Test`という名前で作ります。

```sh
./vendor/bin/sail php artisan make:model Test -a 
```

出来たマイグレーションファイルにテーブルを追加します。
```php:database/migrations/2023_06_17_133705_create_tests_table.php
~~省略~~
    public function up(): void
    {
        Schema::create('tests', function (Blueprint $table) {
            $table->id();
            $table->string('text'); //追加
            $table->timestamps();
        });
    }

~~省略~~
```

その後migrateします。
```sh
./vendor/bin/sail artisan migrate
```

### 5.DBに適当なデータを入れる
出来たTestモデルに適当なデータを入れます。
```php:database/seeders/TestSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; //追加
use Illuminate\Support\Str; //追加

class TestSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        //textにランダムな文字列を追加したものを10件ほど作成する
        for ($i = 0; $i < 10; $i++) {
            DB::table('tests')->insert([
                'text' => Str::random(10),
                'created_at' => now(),
                'updated_at' => now(),
            ]);
        }
    }
}

```
以下のコマンドで生成する

```sh
./vendor/bin/sail php artisan db:seed --class=TestSeeder
```

### 6.コントローラーを設定する

```php:app/Http/Controllers/TestController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreTestRequest;
use App\Http\Requests\UpdateTestRequest;
use App\Models\Test; //追加
use Inertia\Inertia;

class TestController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        // 先ほど作ったデータを$testsに入れる
        $tests = Test::all();
        
        // Tests/Indexに$testsを渡す
        return Inertia::render('Tests/Index', [
            'tests' => $tests,
        ]);
    }

~~省略~~
```

### 7.ルーティングを設定する
```php:routes/web.php
<?php

use App\Http\Controllers\ProfileController;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\TestController; //追加

Route::resource('test', TestController::class); //追加

~~省略~~
```


### 8.Vuetify3のインストール
Yarnを使いVuetify3をインストールします。

```sh
yarn add vuetify@^3.3.4
```
VuetifyのMaterial Design Iconsもインストールします。
```sh
yarn add @mdi/font
```

### 9.Vuetifyのセットアップ
app.jsを編集します。真ん中と下の方にコードを追加します。
```php:resources/js/app.js
// import "./bootstrap";
// import "../css/app.css";

import { createApp, h } from "vue";
import { createInertiaApp } from "@inertiajs/inertia-vue3";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
import { ZiggyVue } from "../../vendor/tightenco/ziggy/dist/vue.m";

// ここから
import "vuetify/dist/vuetify.min.css";
import App from "./Pages/Tests/Index.vue";
import "@mdi/font/css/materialdesignicons.css"; 
import "vuetify/styles";
import { createVuetify } from "vuetify";
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";

const vuetify = createVuetify({
    components,
    directives,
});
// ここまで追加

const appName =
    window.document.getElementsByTagName("title")[0]?.innerText || "Laravel";

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) =>
        resolvePageComponent(
            `./Pages/${name}.vue`,
            import.meta.glob("./Pages/**/*.vue")
        ),
    setup({ el, App, props, plugin }) {
        return createApp({ render: () => h(App, props) })
            .use(vuetify) //追加
            .use(plugin)
            .use(ZiggyVue, Ziggy)
            .mount(el);
    },
    progress: {
        color: "#4B5563",
    },
});

```

### 10.Viewを作成する
`resources/js/Pages`の中に`Tests`フォルダを作り、その中に`Index.vue`ファイルを作成します。


### 11.Vuetifyのワイヤーフレームを使う
[Vuetifyの公式ページ](https://vuetifyjs.com/en/getting-started/wireframes/)からワイヤーフレームを選択します。
※公式ページは英語版と日本語版がありますが、日本語版だとうまくいかなかったので英語版の方をつかってください。

今回はこちらを選択します。
![スクリーンショット (69).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3027729/19217cab-6604-0ba7-008a-659573612103.png)

右下の青色のgithubのマークをクリックします。
その後、出てきたコードをコピーし、先ほど作った`resources/js/Pages/Tests/Index.vue`に貼り付けます。

![スクリーンショット (71).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3027729/5416379d-cc73-e3cd-7e15-cb8f9dde5ad3.png)



### 12.アプリケーションを立ち上げる
以下の2つのコマンドでアプリケーションを立ち上げます。
```sh
vendor/bin/sail up
```
```sh
npm run dev
```
ここで`localhost/tests`にアクセスすると先ほど選択したワイヤーフレームが表示されていると思います。

### 13\.コントローラーから変数を渡す
「6.コントローラーを設定する」ですでに$testsをview側に渡しているのでIndex.vueを以下のように編集します。

```vue
<script setup>
import { ref } from "vue";

// コントローラーから渡された値を受け取る
defineProps({
    tests: {
        type: String,
        required: true,
    },
});
```

```vue
<!-- testsを繰り返し表示する -->
<template v-for="test in tests" :key="test.id">
    <v-list-item>
        <template v-slot:prepend>
            <v-avatar color="grey-darken-1"></v-avatar>
        </template>

        <v-list-item-title>
            {{ test.text }}
        </v-list-item-title>
    </v-list-item>
    <!-- v-list-item-subtitleは今回は邪魔なので削除する -->
    <!-- <v-list-item-subtitle>
        Lorem ipsum dolor sit amet,
        consectetur adipisicing elit. Nihil
        repellendus distinctio similique
    </v-list-item-subtitle> -->
    <v-divider
        v-if="test.id !== 6"
        :key="`divider-${test.id}`"
        inset
    ></v-divider>
</template>
```

___全体像___

```vue:resources/js/Pages/Tests/Index.vue
<template>
    <v-app id="inspire">
        <v-system-bar>
            <v-spacer></v-spacer>

            <v-icon>mdi-square</v-icon>

            <v-icon>mdi-circle</v-icon>

            <v-icon>mdi-triangle</v-icon>
        </v-system-bar>

        <v-navigation-drawer v-model="drawer">
            <v-sheet color="grey-lighten-4" class="pa-4">
                <v-avatar
                    class="mb-4"
                    color="grey-darken-1"
                    size="64"
                ></v-avatar>

                <div>john@google.com</div>
            </v-sheet>

            <v-divider></v-divider>

            <v-list>
                <v-list-item v-for="[icon, text] in links" :key="icon" link>
                    <template v-slot:prepend>
                        <v-icon>{{ icon }}</v-icon>
                    </template>

                    <v-list-item-title>{{ text }}</v-list-item-title>
                </v-list-item>
            </v-list>
        </v-navigation-drawer>

        <v-main>
            <v-container class="py-8 px-6" fluid>
                <v-row>
                    <v-col v-for="card in cards" :key="card" cols="12">
                        <v-card>
                            <v-list lines="two">
                                <v-list-subheader>{{ card }}</v-list-subheader>

                                <template v-for="test in tests" :key="test.id">
                                    <v-list-item>
                                        <template v-slot:prepend>
                                            <v-avatar
                                                color="grey-darken-1"
                                            ></v-avatar>
                                        </template>

                                        <v-list-item-title>
                                            {{ test.text }}
                                        </v-list-item-title>
                                    </v-list-item>

                                    <v-divider
                                        v-if="test.id !== 6"
                                        :key="`divider-${test.id}`"
                                        inset
                                    ></v-divider>
                                </template>
                            </v-list>
                        </v-card>
                    </v-col>
                </v-row>
            </v-container>
        </v-main>
    </v-app>
</template>

<script setup>
import { ref } from "vue";

// コントローラーから渡された値を受け取る
defineProps({
    tests: {
        type: String,
        required: true,
    },
});

const cards = ["Today", "Yesterday"];
const links = [
    ["mdi-inbox-arrow-down", "Inbox"],
    ["mdi-send", "Send"],
    ["mdi-delete", "Trash"],
    ["mdi-alert-octagon", "Spam"],
];

const drawer = ref(null);
</script>

```

以下のようにランダムな文字列が表示されていたらVuetify3を使ったviewにコントローラーから変数を渡せています。

![スクリーンショット (73).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3027729/41bbc6dc-f81c-123a-8c09-ad6c2fcee581.png)



### まとめ
調べても同じ環境の記事が見つからず結構苦戦しました。
特に`app.js`ファイルの設定は調べてもなかなかうまくいかず、vuetifyのデザインが消失したり、エラーが出たりと大変でしたが、何とかうまくいってよかったです。