2026年1月4日 星期日

利用 Laravel 12 中實現 SSO (Single Sign-On) 登入功能

    在 Laravel 12 中實現 SSO (Single Sign-On) 登入功能,通常會使用 OAuth2 或 OpenID Connect 來集成第三方身份提供者 (Identity Providers, IDP),如 Google、GitHub、Facebook 或其他自定義的 SSO 解決方案。Laravel 提供了非常強大的身份驗證和授權系統,可以與這些外部身份提供者進行集成。
    一、安裝 Socialite 套件



資料來源:




利用 laravel 12 做一個類似Google文件的簡單範例

系列文章:

      以下是一個簡單的 Laravel 範例,實現類似 Google Docs 的基礎功能,包括用戶認證、文檔創建、編輯、和即時更新(使用 Pusher 和 Quill 編輯器)。
    一、安裝 Laravel 和所需的套件
    a.建立laravel 12 專案
    指令:composer create-project --prefer-dist laravel/laravel google-docs-clone
                cd google-docs-clone
    b.安裝認證包( Laravel Breeze)
    指令:composer require laravel/breeze --dev
                php artisan breeze:install
                npm install 
                npm run dev
                php artisan migrate
    c.安裝 PusherLaravel Echo
    指令:composer require pusher/pusher-php-server
                npm install --save laravel-echo pusher-js

    二、編輯 .env 中設定 Pusher
        BROADCAST_DRIVER=pusher
        PUSHER_APP_ID=your-app-id
        PUSHER_APP_KEY=your-app-key
        PUSHER_APP_SECRET=your-app-secret
        PUSHER_APP_CLUSTER=your-app-cluster
        
        註冊一個 Pusher 帳戶來獲取這些值:https://pusher.com/,如下圖

    三、建立 Document 模型
    a.指令:php artisan make:model Document -m
    b.編輯 database/migrations/xxxx_xx_xx_create_documents_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('documents', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->string('title');
            $table->text('content'); // 存儲文檔內容
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('documents');
    }
};
    c.指令:php artisan migrate

    四、建立 Controller 和路由
    a.指令:php artisan make:controller DocumentController
    b.編輯 DocumentController.php,直接套用即可。
<?php

namespace App\Http\Controllers;

use App\Models\Document;
use Illuminate\Http\Request;

class DocumentController extends Controller
{
    // 顯示所有文檔
    public function index()
    {
        $documents = Document::where('user_id', auth()->id())->get();  // 根據用戶顯示文檔
        return view('documents.index', compact('documents'));
    }

    // 顯示創建文檔頁面
    public function create()
    {
        return view('documents.create');
    }

    // 儲存新文檔
    public function store(Request $request)
    {
        $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
        ]);

        $document = new Document();
        $document->title = $request->title;
        $document->content = $request->content;
        $document->user_id = auth()->id();
        $document->save();

        return redirect()->route('documents.index')->with('success', 'Document created successfully!');
    }

    // 顯示編輯文檔頁面
    public function edit($id)
    {
        $document = Document::findOrFail($id);
        return view('documents.edit', compact('document'));
    }

    // 更新文檔
    public function update(Request $request, $id)
    {
        $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
        ]);

        $document = Document::findOrFail($id);
        $document->title = $request->title;
        $document->content = $request->content;
        $document->save();

        return redirect()->route('documents.index')->with('success', 'Document updated successfully!');
    }

}
    c.編輯 routes/web.php,直接套用即可。
<?php

use App\Http\Controllers\DocumentController;
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;

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

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


Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');

    // 顯示所有文檔
    Route::get('documents', [DocumentController::class, 'index'])->name('documents.index');

    // 創建新文檔
    Route::get('documents/create', [DocumentController::class, 'create'])->name('documents.create');
    Route::post('documents', [DocumentController::class, 'store'])->name('documents.store');

    // 編輯文檔
    Route::get('documents/{id}/edit', [DocumentController::class, 'edit'])->name('documents.edit');
    Route::post('documents/{id}/update', [DocumentController::class, 'update'])->name('documents.update');
});

require __DIR__.'/auth.php';

    五、建立DocumentUpdated 事件
    a.指令:php artisan make:event DocumentUpdated
    b.編輯 app/Events/DocumentUpdated.php ,直接套用即可。
<?php

namespace App\Events;

use App\Models\Document;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class DocumentUpdated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $document;

    public function __construct(Document $document)
    {
        $this->document = $document;
    }

    public function broadcastOn()
    {
        return new Channel('document.' . $this->document->id);
    }

    public function broadcastWith()
    {
        return ['content' => $this->document->content];
    }
}

    六、建立 Quill 編輯器前端頁面
    a.編輯 resources/views/documents/create.blade.php,直接套用即可。
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Create Document') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <!-- Form to create a new document -->
                    <form action="{{ url('documents') }}" method="POST">
                        @csrf
                        <div class="form-group">
                            <label for="title">Title</label>
                            <input type="text" name="title" class="form-control" required>
                        </div>
                        <div id="editor"></div>
                        <textarea name="content" id="content" style="display:none;"></textarea>
                        <button type="submit" class="btn btn-primary mt-2">Create Document</button>
                    </form>
                </div>
            </div>
        </div>
    </div>

    <!-- Quill.js -->
    <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
    <!-- Quill.css -->
    <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">

    <script>
        // Initialize Quill editor
        var quill = new Quill('#editor', {
            theme: 'snow', // Use the 'snow' theme
            placeholder: 'Start writing your document...',
            modules: {
                toolbar: [
                    [{ 'header': '1' }, { 'header': '2' }, { 'font': [] }],
                    [{ 'list': 'ordered'}, { 'list': 'bullet' }],
                    ['bold', 'italic', 'underline'],
                    [{ 'align': [] }],
                    ['link'],
                    ['image']
                ]
            }
        });

        // Update the hidden textarea with the editor content when it's changed
        quill.on('text-change', function () {
            document.getElementById('content').value = quill.root.innerHTML;
        });
    </script>
</x-app-layout>
    b.編輯 resources/views/documents/edit.blade.php,直接套用即可。
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Edit Document') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <form action="{{ url('documents/' . $document->id . '/update') }}" method="POST">
                        @csrf
                        @method('POST')

                        <div class="form-group">
                            <label for="title">Title</label>
                            <input type="text" name="title" class="form-control" value="{{ $document->title }}" required>
                        </div>
                        <div id="editor">{!! $document->content !!}</div>
                        <textarea name="content" id="content" style="display:none;">{{ $document->content }}</textarea>
                        <button type="submit" class="btn btn-primary mt-2">Update Document</button>
                    </form>
                </div>
            </div>
        </div>
    </div>

    <!-- Quill.js -->
    <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
    <!-- Quill.css -->
    <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">

    <script>
        var quill = new Quill('#editor', {
            theme: 'snow',
            placeholder: 'Start editing your document...',
            modules: {
                toolbar: [
                    [{ 'header': '1' }, { 'header': '2' }, { 'font': [] }],
                    [{ 'list': 'ordered'}, { 'list': 'bullet' }],
                    ['bold', 'italic', 'underline'],
                    [{ 'align': [] }],
                    ['link'],
                    ['image']
                ]
            }
        });

        // Update the hidden textarea with the editor content when it's changed
        quill.on('text-change', function () {
            document.getElementById('content').value = quill.root.innerHTML;
        });
    </script>
</x-app-layout>
    c.編輯 resources/views/documents/index.blade.php,直接套用即可。
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Your Documents') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <h3 class="text-lg font-medium">Here are your documents</h3>

                    <!-- Document List -->
                    <ul class="space-y-4 mt-4">
                        @foreach($documents as $document)
                            <li class="bg-gray-100 p-4 rounded-md shadow-md">
                                <h4 class="font-medium text-gray-800">{{ $document->title }}</h4>
                                <p class="text-sm text-gray-600">{{ $document->created_at->format('M d, Y') }}</p>
                                <a href="{{ route('documents.edit', $document->id) }}" class="text-blue-500 hover:text-blue-700">Edit</a>
                            </li>
                        @endforeach
                    </ul>

                    <!-- If no documents -->
                    @if($documents->isEmpty())
                        <p class="mt-4 text-gray-600">You have no documents yet.</p>
                    @endif
                </div>
            </div>
        </div>
    </div>
</x-app-layout>
    d.編輯 resources/views/dashboard.blade.php,直接套用即可。
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <h3 class="text-lg font-medium">Welcome, {{ Auth::user()->name }}!</h3>
                    <p class="mt-2 text-gray-600">You're logged in successfully. Here’s your dashboard.</p>

                    <!-- Link to documents -->
                    <div class="mt-6">
                        <a href="{{ route('documents.index') }}" class="inline-block bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">
                            View Documents
                        </a>
                    </div>

                    <!-- Create new document -->
                    <div class="mt-6">
                        <a href="{{ route('documents.create') }}" class="inline-block bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600">
                            Create New Document
                        </a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

    e.編輯 resources/views/layouts/navigation.blade.php,直接套用即可。
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
    <!-- Primary Navigation Menu -->
    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div class="flex justify-between h-16">
            <div class="flex">
                <!-- Logo -->
                <div class="shrink-0 flex items-center">
                    <a href="{{ route('dashboard') }}">
                        <x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
                    </a>
                </div>

                <!-- Navigation Links -->
                <div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
                    <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
                        {{ __('Dashboard') }}
                    </x-nav-link>
                </div>
            </div>

            <!-- Settings Dropdown -->
            <div class="hidden sm:flex sm:items-center sm:ms-6">
                <x-dropdown align="right" width="48">
                    <x-slot name="trigger">
                        <button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
                            <div>{{ Auth::user()->name }}</div>

                            <div class="ms-1">
                                <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                                    <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
                                </svg>
                            </div>
                        </button>
                    </x-slot>

                    <x-slot name="content">
                        <x-dropdown-link :href="route('profile.edit')">
                            {{ __('Profile') }}
                        </x-dropdown-link>

                        <!-- Authentication -->
                        <form method="POST" action="{{ route('logout') }}">
                            @csrf

                            <x-dropdown-link :href="route('logout')"
                                    onclick="event.preventDefault();
                                                this.closest('form').submit();">
                                {{ __('Log Out') }}
                            </x-dropdown-link>
                        </form>
                    </x-slot>
                </x-dropdown>
            </div>

            <!-- Hamburger -->
            <div class="-me-2 flex items-center sm:hidden">
                <button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
                    <svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
                        <path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                        <path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                </button>
            </div>
        </div>
    </div>

    <!-- Responsive Navigation Menu -->
    <div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
        <div class="pt-2 pb-3 space-y-1">
            <x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
                {{ __('Dashboard') }}
            </x-responsive-nav-link>
        </div>

        <!-- Responsive Settings Options -->
        <div class="pt-4 pb-1 border-t border-gray-200">
            <div class="px-4">
                <div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
                <div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
            </div>

            <div class="mt-3 space-y-1">
                <x-responsive-nav-link :href="route('profile.edit')">
                    {{ __('Profile') }}
                </x-responsive-nav-link>

                <!-- Authentication -->
                <form method="POST" action="{{ route('logout') }}">
                    @csrf

                    <x-responsive-nav-link :href="route('logout')"
                            onclick="event.preventDefault();
                                        this.closest('form').submit();">
                        {{ __('Log Out') }}
                    </x-responsive-nav-link>
                </form>
            </div>
        </div>
    </div>
</nav>


    七、監聽文檔更新
    a.編輯 resources/js/app.js,直接套用即可。
import './bootstrap';

import Alpine from 'alpinejs';
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Alpine = Alpine;

Alpine.start();

window.Pusher = Pusher;

const echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-app-key',
    cluster: 'your-pusher-app-cluster',
    forceTLS: true
});

const documentId = window.documentId; // 文檔ID

echo.channel('document.' + documentId)
    .listen('DocumentUpdated', (event) => {
        quill.root.innerHTML = event.content;
    });


    八、結果畫面:
    九、在原有的 edit 頁面中加入刪除文檔的功能
    a. 修改 edit.blade.php,直接套用即可。
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Edit Document') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <form action="{{ url('documents/' . $document->id . '/update') }}" method="POST">
                        @csrf
                        @method('POST')

                        <div class="form-group">
                            <label for="title">Title</label>
                            <input type="text" name="title" class="form-control" value="{{ $document->title }}" required>
                        </div>
                        <div id="editor">{!! $document->content !!}</div>
                        <textarea name="content" id="content" style="display:none;">{{ $document->content }}</textarea>
                        <button type="submit" class="btn btn-primary mt-2">Update Document</button>
                    </form>

                    <!-- Delete Document Button -->
                    <form action="{{ route('documents.destroy', $document->id) }}" method="POST" class="mt-4">
                        @csrf
                        @method('DELETE') <!-- This is required for delete requests -->
                        <button type="submit" class="bg-red-500 text-white px-4 py-2 rounded-md hover:bg-red-600">
                            Delete Document
                        </button>
                    </form>
                </div>
            </div>
        </div>
    </div>

    <!-- Quill.js -->
    <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
    <!-- Quill.css -->
    <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
   
    <script>
        var quill = new Quill('#editor', {
            theme: 'snow',
            placeholder: 'Start editing your document...',
            modules: {
                toolbar: [
                    [{ 'header': '1' }, { 'header': '2' }, { 'font': [] }],
                    [{ 'list': 'ordered'}, { 'list': 'bullet' }],
                    ['bold', 'italic', 'underline'],
                    [{ 'align': [] }],
                    ['link'],
                    ['image']
                ]
            }
        });

        // Update the hidden textarea with the editor content when it's changed
        quill.on('text-change', function () {
            document.getElementById('content').value = quill.root.innerHTML;
        });
    </script>
</x-app-layout>

    b. 更新 web.php 路由,直接套用即可。
<?php

use App\Http\Controllers\DocumentController;
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;

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

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


Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');

    // 顯示所有文檔
    Route::get('documents', [DocumentController::class, 'index'])->name('documents.index');

    // 創建新文檔
    Route::get('documents/create', [DocumentController::class, 'create'])->name('documents.create');
    Route::post('documents', [DocumentController::class, 'store'])->name('documents.store');

    // 編輯文檔
    Route::get('documents/{id}/edit', [DocumentController::class, 'edit'])->name('documents.edit');
    Route::post('documents/{id}/update', [DocumentController::class, 'update'])->name('documents.update');
    // Delete Route
    Route::delete('documents/{id}', [DocumentController::class, 'destroy'])->name('documents.destroy');
});

require __DIR__.'/auth.php';

    c.更新 DocumentController,直接套用即可。
<?php

namespace App\Http\Controllers;

use App\Models\Document;
use Illuminate\Http\Request;

class DocumentController extends Controller
{
    // 顯示所有文檔
    public function index()
    {
        $documents = Document::where('user_id', auth()->id())->get();  // 根據用戶顯示文檔
        return view('documents.index', compact('documents'));
    }

    // 顯示創建文檔頁面
    public function create()
    {
        return view('documents.create');
    }

    // 儲存新文檔
    public function store(Request $request)
    {
        $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
        ]);

        $document = new Document();
        $document->title = $request->title;
        $document->content = $request->content;
        $document->user_id = auth()->id();
        $document->save();

        return redirect()->route('documents.index')->with('success', 'Document created successfully!');
    }

    // 顯示編輯文檔頁面
    public function edit($id)
    {
        $document = Document::findOrFail($id);
        return view('documents.edit', compact('document'));
    }

    // 更新文檔
    public function update(Request $request, $id)
    {
        $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
        ]);

        $document = Document::findOrFail($id);
        $document->title = $request->title;
        $document->content = $request->content;
        $document->save();

        return redirect()->route('documents.index')->with('success', 'Document updated successfully!');
    }
    // Delete document
    public function destroy($id)
    {
        $document = Document::findOrFail($id);

        // Delete the document
        $document->delete();

        // Redirect with success message
        return redirect()->route('documents.index')->with('success', 'Document deleted successfully.');
    }

}

    d.顯示刪除後的結果,index.blade.php 中顯示刪除成功的訊息。直接套用即可。
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Your Documents') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <!-- Display success message -->
                    @if(session('success'))
                        <div class="bg-green-100 text-green-700 p-4 rounded-md mb-4">
                            {{ session('success') }}
                        </div>
                    @endif

                    <h3 class="text-lg font-medium">Here are your documents</h3>

                    <!-- Document List -->
                    <ul class="space-y-4 mt-4">
                        @foreach($documents as $document)
                            <li class="bg-gray-100 p-4 rounded-md shadow-md">
                                <h4 class="font-medium text-gray-800">{{ $document->title }}</h4>
                                <p class="text-sm text-gray-600">{{ $document->created_at->format('M d, Y') }}</p>
                                <a href="{{ route('documents.edit', $document->id) }}" class="text-blue-500 hover:text-blue-700">Edit</a>
                            </li>
                        @endforeach
                    </ul>

                    <!-- If no documents -->
                    @if($documents->isEmpty())
                        <p class="mt-4 text-gray-600">You have no documents yet.</p>
                    @endif
                </div>
            </div>
        </div>
    </div>
</x-app-layout>


    十、在原有的 index.blade.php 頁面中加入刪除文檔的功能
    a.index.blade.php 中顯示刪除連結
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Your Documents') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <!-- Display success message -->
                    @if(session('success'))
                        <div class="bg-green-100 text-green-700 p-4 rounded-md mb-4">
                            {{ session('success') }}
                        </div>
                    @endif

                    <h3 class="text-lg font-medium">Here are your documents</h3>

                    <!-- Document List -->
                    <ul class="space-y-4 mt-4">
                        @foreach($documents as $document)
                            <li class="bg-gray-100 p-4 rounded-md shadow-md flex justify-between items-center">
                                <div>
                                    <h4 class="font-medium text-gray-800">{{ $document->title }}</h4>
                                    <p class="text-sm text-gray-600">{{ $document->created_at->format('M d, Y') }}</p>
                                </div>
                                <div>
                                    <!-- Edit Link -->
                                    <a href="{{ route('documents.edit', $document->id) }}" class="text-blue-500 hover:text-blue-700 mr-4">Edit</a>

                                    <!-- Delete Link -->
                                    <form action="{{ route('documents.destroy', $document->id) }}" method="POST" style="display:inline;">
                                        @csrf
                                        @method('DELETE') <!-- This method is needed to send DELETE request -->
                                        <button type="submit" class="text-red-500 hover:text-red-700">
                                            Delete
                                        </button>
                                    </form>
                                </div>
                            </li>
                        @endforeach
                    </ul>

                    <!-- If no documents -->
                    @if($documents->isEmpty())
                        <p class="mt-4 text-gray-600">You have no documents yet.</p>
                    @endif
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

        十一、結果畫面:







利用 Laravel 12 中實現 SSO (Single Sign-On) 登入功能

系列文章: 1. 利用 laravel 12 做一個類似Google文件的簡單範例 2. 利用 Laravel 12 中實現 SSO (Single Sign-On) 登入功能      在 Laravel 12 中實現 SSO (Single Sign-On) 登入功能,通常...