1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Todoリストを作ってみる。 

Last updated at Posted at 2022-04-20

todoリスト(laravel + vite)を作ってみる。
環境はmacです。
(あらかじめlaravelとかphpとかnpmとかtodoリスト用のデータベースは用意しておきます。)
(LARAVEL VITEはphp 8以上、node 16以上が必要です)

まず、ターミナルで作りたい場所に移動してlaravelでプロジェクトを作成。

composer create-project --prefer-dist laravel/laravel todolist

できたtodolistのフォルダにある.envファイルにあらかじめ作っておいたデータベースの設定を書きます。

.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=todolist
DB_USERNAME=[ユーザー名]
DB_PASSWORD=[パスワード]

ターミナルでtodolistのフォルダに移動して

php artisan make:model Item -m

"Model created successfully."が出たのを確認して、
database > migrations の中にある今日の日付のマイグレーションファイルを開いて、
upのfunctionに項目を追加します。

public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->boolean('completed')->default(false);
            $table->timestamp('completed_at')->nullable();
            $table->timestamps();
        });
    }

ターミナルで

//データベースにテーブルを作ります
php artisan migrate
//ついでにコントローラーも作ります
php artisan make:controller ItemController --resource

todoリストを動かすのに必要なapiを作成します。
routes > api.php

api.php
//上の方に追加
use App\Http\Controllers\ItemController;

//省略

//ここから追加
//リストを取得するapi
Route::get('/items', [ItemController::class, 'index']);
Route::prefix('/item')->group( function () {
    //リストを追加するapi
    Route::post('/store', [ItemController::class, 'store']);
    //リストを変更するapi
    Route::put('/{id}', [ItemController::class, 'update']);
    //リストを削除するapi
    Route::delete('/{id}', [ItemController::class, 'destroy']);
});

todoリストを動かすのに必要なファンクションを作成します。
(すでにあるindex(),store(),update(),destroy()を変更します。)
app > Http > Controllers > ItemController.php

ItemController.php
//上の方に追加
use App\Models\Item;
use Carbon\Carbon;

//省略

public function index()
    {
        return Item::orderBy('created_at', 'DESC')->get();
    }

//省略

public function store(Request $request)
    {
        $newItem = new Item;
        $newItem->name = $request->item["name"];
        $newItem->save();

        return $newItem;
    }

//省略

public function update(Request $request, $id)
    {
        $existingItem = Item::find( $id );
        if($existingItem) {
            $existingItem->completed = $request->item['completed'] ? true : false;
            $existingItem->completed_at = $request->item['completed'] ? Carbon::now() : null;
            $existingItem->save();
            return $existingItem;
        }

        return "Item not found";
    }

//省略

public function destroy($id)
    {
        $existingItem = Item::find( $id );
        if($existingItem) {
            $existingItem->delete();
            return "Item successfully deleted";
        }

        return "item not found";
    }


ここでできたらPostManとかでAPIのテストをしたほうがいいです。

ここからフロント部分をviteで作っていきます。
LaravelでViteを使うための laravel-vite をインストールします。

//いらないファイル削除
rm webpack.mix.js
npm remove laravel-mix
//PHPパッケージとViteプラグインのインストール
composer require "innocenzi/laravel-vite:0.2.*"
npm i -D vite vite-plugin-laravel @vitejs/plugin-vue

package.jsonのscriptsの部分を書き換えます。

"scripts": {
        "dev": "vite",
        "build": "vite build"
    },

package.jsonと同じ場所にvite.config.tsファイルを作ります。

vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import laravel from 'vite-plugin-laravel'

export default defineConfig({
    plugins: [
        vue(),
        laravel()
    ]
})

viteに合わせて、require()をimportへ変更します。

resources/js/bootstrap.js
import _ from 'lodash';

// 省略

import axios from 'axios'

axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

// 省略

app.jsにvueのマウント設定を追加します。

resources/js/app.js
import './bootstrap'

import { createApp } from 'vue'
import App from './vue/App.vue'
const app = createApp(App)
app.mount('#app')

welcome.blade.phpを変更します。

resources/views/welcome.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>

        <!-- Fonts -->
        <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
    </head>
    <body>
        <div id="app"></div>
        @vite
    </body>
</html>

あらかじめfont-awsomeをインストールしておきます。
ターミナルで

npm i --save @fortawesome/fontawesome-svg-core
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/vue-fontawesome@prerelease

app.jsにfont-awsomeを追加します。

resources/js/app.js
// 省略
//ここから追加
import { library } from '@fortawesome/fontawesome-svg-core'
import { faSquarePlus, faTrash } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

library.add(faSquarePlus, faTrash)

//ここから変更
const app = createApp(App)
app.component('font-awesome-icon', FontAwesomeIcon )
app.mount('#app')

resources/js/ の中にvueディレクトリを作り、その中に
・App.vue
・addItemForm.vue
・listView.vue
・listItem.vue
を作ります。

resources/js/vue/App.vue
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
import addItemForm from "./addItemForm.vue"
import listView from "./listView.vue"

const items = ref([])
const getList = () => {
    axios.get('api/items')
    .then( response => {
        items.value = response.data
    })
    .catch( error => {
        console.log(error)
    })
}

onMounted(() => {
    getList();
})
</script>

<template>
  <div class="todoListContainer">
      <div class="heading">
          <h2 id="title">Todo List</h2>
          <add-item-form @reloadlist="getList()" />
      </div>
      <list-view :items="items"
                @reloadlist="getList()"
      />
  </div>
</template>

<style scoped>
.todoListContainer {
    width: 350px;
    margin: auto;
}
.heading {
    background: #e6e6e6;
    padding: 10px;
}
#title {
    text-align: center;
}
</style>
resources/js/vue/addItemForm.vue
<script setup>
import { reactive } from 'vue'
import axios from 'axios'

const emit = defineEmits();
const item = reactive({ name: ""})
const addItem = () => {
    if(item.name=='') {
        return;
    }

    axios.post('api/item/store', {
        item: item
    })
    .then(response => {
        if( response.status == 201){
            item.name = "";
            emit('reloadlist');
        }
    })
    .catch( error => {
        console.log(error);
    })
}

</script>

<template>
    <div class="addItem">
        <input type="text" v-model="item.name" />
        <font-awesome-icon
            icon="square-plus"
            @click="addItem()"
            :class="[item.name ? 'active' : 'inactive', 'plus']"
        />
    </div>
</template>

<style scoped>
.addItem {
    display: flex;
    justify-content: center;
    align-items: center;
}
input {
    background: #f7f7f7;
    border: 0px;
    outline: none;
    padding: 5px;
    margin-right: 10px;
    width: 100%;
}
.plus {
    font-size: 20px;
}
.active {
    color: #00CE25;
}
.inactive {
    color: #999;
}
</style>
resources/js/vue/listView.vue
<script setup>
    import listItem from "./listItem.vue"

    const props = defineProps({
        items: Object
    })
    const emit = defineEmits();
</script>

<template>
    <div>
        <div v-for="(item, index) in items" :key="index">
            <list-item
                :item="item"
                class="item"
                @itemchanged="emit('reloadlist')"
            />
        </div>
    </div>
</template>

<style scoped>
.item {
    background: #e6e6e6;
    padding: 5px;
    margin-top: 5px;
}
</style>
resources/js/vue/listItem.vue
<script setup>
    import axios from "axios"

    const props = defineProps({
        item: Object
    })
    const emit = defineEmits();

    const updateCheck = () => {
        axios.put('api/item/' + props.item.id, {
            item: props.item
        })
        .then( response => {
            if( response.status == 200 ) {
                emit('itemchanged');
            }
        })
        .catch( error => {
            console.log(error);
        })
    }

    const removeItem = () => {
        axios.delete('api/item/' + props.item.id)
        .then( response => {
            if( response.status == 200 ) {
                emit('itemchanged');
            }
        })
        .catch( error => {
            console.log( error );
        })
    }
</script>

<template>
    <div class="item">
        <input type="checkbox"
                @change="updateCheck()"
                v-model="item.completed"
        />
        <span :class="[item.completed ? 'completed' : '', 'itemText']">
            {{ item.name }}
        </span>
        <button @click="removeItem()" class="trashcan">
            <font-awesome-icon icon="trash" />
        </button>
    </div>
</template>

<style scoped>
.completed {
    text-decoration: line-through;
    color: #999;
}
.itemText {
    width: 100%;
    margin-left: 20px;
}
.item {
    display: flex;
    justify-content: center;
    align-items: center;
}
.trashcan {
    background: #e6e6e6;
    border: none;
    color: #ff0000;
    outline: none;
}
</style>

これで完成なので、ターミナルでまずviteを立ち上げる

npm run dev

次に別のターミナルでlaravelを立ち上げます

php artisan serve

立ち上がったlaravelにtodoリストができてるはず。。



参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?