零、目標:
1.專案目標:
A.環境準備
B.建立 Laravel 專案 + 登入系統
C.建立資料庫(資料夾 / 檔案)
D.資料夾 CRUD
E.檔案上傳 / 下載
F.檔案列表(像 Google Drive)
G.分享連結
H.權限與安全
1.專案目標:
A.環境準備
B.建立 Laravel 專案 + 登入系統
C.建立資料庫(資料夾 / 檔案)
D.資料夾 CRUD
E.檔案上傳 / 下載
F.檔案列表(像 Google Drive)
G.分享連結
H.權限與安全
I.回收桶(Soft Delete)
2.目前目標:
E.檔案上傳 / 下載+F.檔案列表(像 Google Drive)
a.上傳檔案到指定資料夾
b.在資料夾中看到[資料夾+檔案]混合列表
c.下載檔案
d.檔案真實存到 storage
2.目前目標:
E.檔案上傳 / 下載+F.檔案列表(像 Google Drive)
a.上傳檔案到指定資料夾
b.在資料夾中看到[資料夾+檔案]混合列表
c.下載檔案
d.檔案真實存到 storage
一、建立 File Model + Migration
1.指令:php artisan make:model File -m
2.編輯 database/migrations/xxxx_create_files_table.php
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('folder_id')->nullable()
->constrained('folders')->cascadeOnDelete();
$table->string('name'); // 原始檔名
$table->string('path'); // 儲存路徑
$table->unsignedBigInteger('size');
如下圖:
3.指令:php artisan migrate
二、File Model
1.編輯 app/models/File.php
use App\Models\Folder;
與
protected $fillable = [
'user_id',
'folder_id',
'name',
'path',
'size',
];
public function folder(){
return $this->belongsTo(Folder::class);
}
如下圖:
三、建立 FileController
1.指令:php artisan make:controller FileController
2.編輯 app/Http/Controllers/FileController.php
use App\Models\File;
use App\Models\Folder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
與
public function store(Request $request){
$request->validate([
'file' => 'required|file|max:10240', // 10MB
]);
$uploaded = $request->file('file');
$path = $uploaded->store('files', 'public');
File::create([
'user_id' => Auth::id(),
'folder_id' => $request->folder_id,
'name' => $uploaded->getClientOriginalName(),
'path' => $path,
'size' => $uploaded->getSize(),
]);
return back();
}
public function download(File $file){
abort_if($file->user_id !== Auth::id(), 403);
return Storage::disk('public')->download($file->path, $file->name);
}
如下圖:
四、設定路由
1. 編輯 routes/web.php
use App\Http\Controllers\FileController;
與
// File
Route::post('/files', [FileController::class, 'store'])->name('files.store');
Route::get('/files/{file}/download', [FileController::class, 'download'])
->name('files.download');
如下圖
五、顯示檔案(修改 folders.show)
1.編輯 FolderController@show
use App\Models\File;
與
// File
$files = File::where('folder_id', $folder->id)->get();
return view('folders.show', compact('folder', 'subFolders','files'));
如下圖
六、修改畫面:上傳+檔案列表
1.編輯 resources/views/folders/show.blade.php
{{-- 上傳表單 --}}
<form method="POST" action="{{ route('files.store') }}" enctype="multipart/form-data" class="mb-4">
@csrf
<input type="hidden" name="folder_id" value="{{ $folder->id }}">
<input type="file" name="file">
<button class="bg-green-600 text-white px-4 py-2 rounded">
上傳檔案
</button>
</form>
{{-- 檔案列表 --}}
<h2 class="font-semibold mb-2">檔案</h2>
<ul class="space-y-2">
@forelse($files as $file)
<li class="border p-3 rounded flex justify-between">
📄 {{ $file->name }}
<a href="{{ route('files.download', $file) }}"
class="text-blue-600 hover:underline">
下載
</a>
</li>
@empty
<li class="text-gray-400">尚無檔案</li>
@endforelse
</ul>
如下圖
七、建立 storage 連結(必要)
1.指令:php artisan storage:link
八、測試流程
1.點擊某資料夾
2.上傳檔案
3.看到檔案列表
4.點[下載]成功
調整方式如下:
1.調整 web.php
// 檔案重新命名
Route::patch('/files/{file}', [FileController::class, 'update'])->name('files.update');
// 檔案刪除
Route::delete('/files/{file}', [FileController::class, 'destroy'])->name('files.destroy');
如下圖:
public function update(Request $request, File $file){
// 安全檢查
abort_if($file->user_id !== Auth::id(), 403);
// 驗證新名稱
$request->validate([
'name' => 'required|string|max:255',
]);
$file->update([
'name' => $request->name,
]);
return back()->with('success', '檔案名稱已更新');
}
與
public function destroy(File $file){
// 安全檢查
abort_if($file->user_id !== Auth::id(), 403);
// 刪除實體檔案
if (Storage::disk('public')->exists($file->path)) {
Storage::disk('public')->delete($file->path);
}
// 刪除資料庫紀錄
$file->delete();
return back()->with('success', '檔案已刪除');
}
如下圖:
<x-app-layout>
<div class="p-6 max-w-4xl mx-auto">
{{-- breadcrumb --}}
<div class="mb-4 text-sm text-gray-500">
<a href="{{ route('folders.index') }}" class="hover:underline">
我的雲端硬碟
</a>
@foreach ($breadcrumbs as $crumb)
/ <a href="{{ route('folders.show', $crumb) }}" class="hover:underline">{{ $crumb->name }}</a>
@endforeach
</div>
{{-- 資料夾名稱 --}}
<h1 class="text-xl font-bold mb-4">📁 {{ $folder->name }}</h1>
{{-- 成功訊息 --}}
@if(session('success'))
<div class="mb-4 text-green-600">
{{ session('success') }}
</div>
@endif
{{-- 新增子資料夾 --}}
<form method="POST" action="{{ route('folders.store') }}" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="parent_id" value="{{ $folder->id }}">
<input type="text" name="name" placeholder="子資料夾名稱" class="border rounded p-2 flex-1" required>
<button class="bg-blue-500 text-white px-4 py-2 rounded">新增</button>
</form>
{{-- 子資料夾列表 --}}
<ul class="space-y-2 mb-6">
@forelse($subFolders as $sub)
<li class="border p-3 rounded hover:bg-gray-100 flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($sub->name) }}' }">
{{-- 資料夾名稱 --}}
<div class="flex items-center gap-2">
📁
<template x-if="!editing">
<span @dblclick="editing = true" class="font-semibold text-blue-600 cursor-pointer">
{{ $sub->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('folders.update', $sub) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('folders.show', $sub) }}" class="text-sm text-blue-500">開啟</a>
<button @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('folders.destroy', $sub) }}"
onsubmit="return confirm('確定要刪除這個資料夾及其所有子資料夾嗎?')">
@csrf
@method('DELETE')
<button class="text-sm text-red-600">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">此資料夾尚無子資料夾</li>
@endforelse
</ul>
{{-- 上傳檔案 --}}
<form method="POST" action="{{ route('files.store') }}" enctype="multipart/form-data" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="folder_id" value="{{ $folder->id }}">
<input type="file" name="file" required>
<button class="bg-green-600 text-white px-4 py-2 rounded">上傳檔案</button>
</form>
{{-- 檔案列表 --}}
<h2 class="font-semibold mb-2">檔案</h2>
<ul class="space-y-2">
@forelse($files as $file)
<li class="border p-3 rounded flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($file->name) }}' }">
{{-- 檔案名稱 --}}
<div class="flex items-center gap-2">
📄
<template x-if="!editing">
<span @dblclick="editing = true" class="cursor-pointer text-blue-600">
{{ $file->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('files.update', $file) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('files.download', $file) }}" class="text-blue-600 hover:underline">下載</a>
<button type="button" @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('files.destroy', $file) }}"
onsubmit="return confirm('確定要刪除這個檔案嗎?')">
@csrf
@method('DELETE')
<button class="text-red-600 text-sm">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">尚無檔案</li>
@endforelse
</ul>
</div>
</x-app-layout>
3.呈現如下圖
編輯 show.blade.php,直接將整個直接替換成下面的程式碼
<x-app-layout>
<div class="p-6 max-w-4xl mx-auto">
{{-- breadcrumb --}}
<div class="mb-4 text-sm text-gray-500">
<a href="{{ route('folders.index') }}" class="hover:underline">
我的雲端硬碟
</a>
@foreach ($breadcrumbs as $crumb)
/ <a href="{{ route('folders.show', $crumb) }}" class="hover:underline">{{ $crumb->name }}</a>
@endforeach
</div>
{{-- 資料夾名稱 --}}
<h1 class="text-xl font-bold mb-4">📁 {{ $folder->name }}</h1>
{{-- 成功訊息 --}}
@if(session('success'))
<div class="mb-4 text-green-600">
{{ session('success') }}
</div>
@endif
{{-- 新增子資料夾 --}}
<form method="POST" action="{{ route('folders.store') }}" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="parent_id" value="{{ $folder->id }}">
<input type="text" name="name" placeholder="子資料夾名稱" class="border rounded p-2 flex-1" required>
<button class="bg-blue-500 text-white px-4 py-2 rounded">新增</button>
</form>
{{-- 子資料夾列表 --}}
<ul class="space-y-2 mb-6">
@forelse($subFolders as $sub)
<li class="border p-3 rounded hover:bg-gray-100 flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($sub->name) }}' }">
{{-- 資料夾名稱 --}}
<div class="flex items-center gap-2">
📁
<template x-if="!editing">
<span @dblclick="editing = true" class="font-semibold text-blue-600 cursor-pointer">
{{ $sub->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('folders.update', $sub) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('folders.show', $sub) }}" class="text-sm text-blue-500">開啟</a>
<button @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('folders.destroy', $sub) }}"
onsubmit="return confirm('確定要刪除這個資料夾及其所有子資料夾嗎?')">
@csrf
@method('DELETE')
<button class="text-sm text-red-600">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">此資料夾尚無子資料夾</li>
@endforelse
</ul>
{{-- 上傳檔案 --}}
<form method="POST" action="{{ route('files.store') }}" enctype="multipart/form-data" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="folder_id" value="{{ $folder->id }}">
<input type="file" name="file" required>
<button class="bg-green-600 text-white px-4 py-2 rounded">上傳檔案</button>
</form>
{{-- 檔案列表 --}}
<h2 class="font-semibold mb-2">檔案</h2>
<ul class="space-y-2">
@forelse($files as $file)
<li class="border p-3 rounded flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($file->name) }}' }">
{{-- 檔案名稱 + 大小 + 上傳日期 --}}
<div class="flex items-center gap-4">
📄
<template x-if="!editing">
<span @dblclick="editing = true" class="cursor-pointer text-blue-600">
{{ $file->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('files.update', $file) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
{{-- 檔案大小 --}}
<span class="text-gray-500 text-sm">
@php
if($file->size >= 1048576){
$size = number_format($file->size / 1048576, 2) . ' MB';
} elseif($file->size >= 1024){
$size = number_format($file->size / 1024, 2) . ' KB';
} else {
$size = $file->size . ' B';
}
@endphp
{{ $size }}
</span>
{{-- 上傳日期 --}}
<span class="text-gray-400 text-sm">{{ $file->created_at->format('Y-m-d H:i') }}</span>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('files.download', $file) }}" class="text-blue-600 hover:underline">下載</a>
<button type="button" @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('files.destroy', $file) }}"
onsubmit="return confirm('確定要刪除這個檔案嗎?')">
@csrf
@method('DELETE')
<button class="text-red-600 text-sm">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">尚無檔案</li>
@endforelse
</ul>
</div>
</x-app-layout>
結果如下圖:
十一、問題03:希望能加上排序功能
1.修改 FolderController@show,直接將整個直接替換成下面的程式碼
public function show(Folder $folder, Request $request){
// 安全檢查:只能看自己的資料夾
abort_if($folder->user_id !== Auth::id(), 403);
$subFolders = Folder::where('parent_id', $folder->id)->get();
// 建立 breadcrumb
$breadcrumbs = collect();
$current = $folder;
while($current) {
$breadcrumbs->prepend($current);
$current = $current->parent;
}
// 檔案排序
$sort = $request->get('sort', 'name'); // 預設按名稱
$direction = $request->get('direction', 'asc'); // 預設升序
$files = File::where('folder_id', $folder->id)
->orderBy($sort, $direction)
->get();
return view('folders.show', compact('folder', 'subFolders','files','breadcrumbs','sort','direction'));
}
2.編輯 show.blade.php,直接將整個直接替換成下面的程式碼
<x-app-layout>
<div class="p-6 max-w-4xl mx-auto">
{{-- Breadcrumb --}}
<div class="mb-4 text-sm text-gray-500">
<a href="{{ route('folders.index') }}" class="hover:underline">我的雲端硬碟</a>
@foreach ($breadcrumbs as $crumb)
/ <a href="{{ route('folders.show', $crumb) }}" class="hover:underline">{{ $crumb->name }}</a>
@endforeach
</div>
{{-- 資料夾名稱 --}}
<h1 class="text-xl font-bold mb-4">📁 {{ $folder->name }}</h1>
{{-- 成功訊息 --}}
@if(session('success'))
<div class="mb-4 text-green-600">
{{ session('success') }}
</div>
@endif
{{-- 新增子資料夾 --}}
<form method="POST" action="{{ route('folders.store') }}" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="parent_id" value="{{ $folder->id }}">
<input type="text" name="name" placeholder="子資料夾名稱" class="border rounded p-2 flex-1" required>
<button class="bg-blue-500 text-white px-4 py-2 rounded">新增</button>
</form>
{{-- 子資料夾列表 --}}
<ul class="space-y-2 mb-6">
@forelse($subFolders as $sub)
<li class="border p-3 rounded hover:bg-gray-100 flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($sub->name) }}' }">
{{-- 資料夾名稱 --}}
<div class="flex items-center gap-2">
📁
<template x-if="!editing">
<span @dblclick="editing = true" class="font-semibold text-blue-600 cursor-pointer">
{{ $sub->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('folders.update', $sub) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('folders.show', $sub) }}" class="text-sm text-blue-500">開啟</a>
<button @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('folders.destroy', $sub) }}"
onsubmit="return confirm('確定要刪除這個資料夾及其所有子資料夾嗎?')">
@csrf
@method('DELETE')
<button class="text-sm text-red-600">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">此資料夾尚無子資料夾</li>
@endforelse
</ul>
{{-- 上傳檔案 --}}
<form method="POST" action="{{ route('files.store') }}" enctype="multipart/form-data" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="folder_id" value="{{ $folder->id }}">
<input type="file" name="file" required>
<button class="bg-green-600 text-white px-4 py-2 rounded">上傳檔案</button>
</form>
{{-- 檔案排序 --}}
<div class="mb-2 flex items-center gap-2 text-sm text-gray-500">
排序:
@php
$options = ['name' => '名稱', 'size' => '大小', 'created_at' => '上傳日期'];
@endphp
@foreach($options as $key => $label)
<a href="{{ route('folders.show', $folder) }}?sort={{ $key }}&direction={{ $sort === $key && $direction === 'asc' ? 'desc' : 'asc' }}"
class="hover:underline {{ $sort === $key ? 'font-semibold' : '' }}">
{{ $label }}
@if($sort === $key)
{{ $direction === 'asc' ? '↑' : '↓' }}
@endif
</a>
@if(!$loop->last) | @endif
@endforeach
</div>
{{-- 檔案列表 --}}
<h2 class="font-semibold mb-2">檔案</h2>
<ul class="space-y-2">
@forelse($files as $file)
<li class="border p-3 rounded flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($file->name) }}' }">
{{-- 檔案名稱 + 大小 + 上傳日期 --}}
<div class="flex items-center gap-4">
📄
<template x-if="!editing">
<span @dblclick="editing = true" class="cursor-pointer text-blue-600">
{{ $file->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('files.update', $file) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
{{-- 檔案大小 --}}
<span class="text-gray-500 text-sm">
@php
if($file->size >= 1048576){
$size = number_format($file->size / 1048576, 2) . ' MB';
} elseif($file->size >= 1024){
$size = number_format($file->size / 1024, 2) . ' KB';
} else {
$size = $file->size . ' B';
}
@endphp
{{ $size }}
</span>
{{-- 上傳日期 --}}
<span class="text-gray-400 text-sm">{{ $file->created_at->format('Y-m-d H:i') }}</span>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('files.download', $file) }}" class="text-blue-600 hover:underline">下載</a>
<button type="button" @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('files.destroy', $file) }}"
onsubmit="return confirm('確定要刪除這個檔案嗎?')">
@csrf
@method('DELETE')
<button class="text-red-600 text-sm">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">尚無檔案</li>
@endforelse
</ul>
</div>
</x-app-layout>
3.最後成果
1.編輯 FolderController@index,直接將整個直接替換成下面的程式碼
public function index(Request $request){
// 根目錄資料夾
$folders = Folder::where('user_id', Auth::id())
->whereNull('parent_id')
->get();
// 檔案排序設定
$sort = $request->query('sort', 'name'); // 預設按名稱排序
$direction = $request->query('direction', 'asc');
// 安全檢查:只允許這三個欄位排序
if (!in_array($sort, ['name', 'size', 'created_at'])) {
$sort = 'name';
}
if (!in_array($direction, ['asc', 'desc'])) {
$direction = 'asc';
}
// 根目錄檔案
$files = File::where('user_id', Auth::id())
->whereNull('folder_id')
->orderBy($sort, $direction)
->get();
return view('folders.index', compact('folders', 'files', 'sort', 'direction'));
}
2.編輯 index.blade.php,直接將整個直接替換成下面的程式碼
<x-app-layout>
<div class="p-6 max-w-4xl mx-auto">
{{-- Breadcrumb --}}
<div class="mb-4 text-sm text-gray-500">
我的雲端硬碟
</div>
<h1 class="text-xl font-bold mb-4">我的資料夾</h1>
{{-- 成功訊息 --}}
@if(session('success'))
<div class="mb-4 text-green-600">
{{ session('success') }}
</div>
@endif
{{-- 新增根資料夾 --}}
<form method="POST" action="{{ route('folders.store') }}" class="mb-4 flex gap-2">
@csrf
<input type="text" name="name" placeholder="新資料夾名稱" class="border rounded p-2 flex-1" required>
<button class="bg-blue-500 text-white px-4 py-2 rounded">新增</button>
</form>
{{-- 資料夾列表 --}}
<ul class="space-y-2 mb-6">
@forelse($folders as $folder)
<li class="border p-3 rounded hover:bg-gray-100 flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($folder->name) }}' }">
{{-- 資料夾名稱 --}}
<div class="flex items-center gap-2">
📁
<template x-if="!editing">
<span @dblclick="editing = true" class="font-semibold text-blue-600 cursor-pointer">
{{ $folder->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('folders.update', $folder) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('folders.show', $folder) }}" class="text-sm text-blue-500">開啟</a>
<button type="button" @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('folders.destroy', $folder) }}"
onsubmit="return confirm('確定要刪除這個資料夾及其所有子資料夾嗎?')">
@csrf
@method('DELETE')
<button class="text-sm text-red-600">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">尚無資料夾</li>
@endforelse
</ul>
{{-- 上傳檔案 --}}
<form method="POST" action="{{ route('files.store') }}" enctype="multipart/form-data" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="folder_id" value="">
<input type="file" name="file" required>
<button class="bg-green-600 text-white px-4 py-2 rounded">上傳檔案</button>
</form>
{{-- 檔案排序 --}}
<div class="mb-2 flex items-center gap-2 text-sm text-gray-500">
排序:
@php
$options = ['name' => '名稱', 'size' => '大小', 'created_at' => '上傳日期'];
@endphp
@foreach($options as $key => $label)
<a href="{{ route('folders.index') }}?sort={{ $key }}&direction={{ $sort === $key && $direction === 'asc' ? 'desc' : 'asc' }}"
class="hover:underline {{ $sort === $key ? 'font-semibold' : '' }}">
{{ $label }}
@if($sort === $key)
{{ $direction === 'asc' ? '↑' : '↓' }}
@endif
</a>
@if(!$loop->last) | @endif
@endforeach
</div>
{{-- 檔案列表 --}}
<h2 class="font-semibold mb-2">檔案</h2>
<ul class="space-y-2">
@forelse($files as $file)
<li class="border p-3 rounded flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($file->name) }}' }">
{{-- 檔案名稱 + 大小 + 上傳日期 --}}
<div class="flex items-center gap-4">
📄
<template x-if="!editing">
<span @dblclick="editing = true" class="cursor-pointer text-blue-600">
{{ $file->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('files.update', $file) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
{{-- 檔案大小 --}}
<span class="text-gray-500 text-sm">
@php
if($file->size >= 1048576){
$size = number_format($file->size / 1048576, 2) . ' MB';
} elseif($file->size >= 1024){
$size = number_format($file->size / 1024, 2) . ' KB';
} else {
$size = $file->size . ' B';
}
@endphp
{{ $size }}
</span>
{{-- 上傳日期 --}}
<span class="text-gray-400 text-sm">{{ $file->created_at->format('Y-m-d H:i') }}</span>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('files.download', $file) }}" class="text-blue-600 hover:underline">下載</a>
<button type="button" @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('files.destroy', $file) }}"
onsubmit="return confirm('確定要刪除這個檔案嗎?')">
@csrf
@method('DELETE')
<button class="text-red-600 text-sm">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">尚無檔案</li>
@endforelse
</ul>
</div>
</x-app-layout>
3.結果如下圖:
1.編輯 FileController@store,直接將整個直接替換成下面的程式碼
public function store(Request $request){
$request->validate([
'files.*' => 'required|file|max:10240', // 每個檔案最大 10MB
]);
$uploadedFiles = $request->file('files');
foreach ($uploadedFiles as $uploaded) {
$path = $uploaded->store('files', 'public');
File::create([
'user_id' => Auth::id(),
'folder_id' => $request->folder_id,
'name' => $uploaded->getClientOriginalName(),
'path' => $path,
'size' => $uploaded->getSize(),
]);
}
return back()->with('success', count($uploadedFiles) . ' 個檔案上傳成功');
}
<x-app-layout>
<div class="p-6 max-w-4xl mx-auto">
{{-- Breadcrumb --}}
<div class="mb-4 text-sm text-gray-500">
我的雲端硬碟
</div>
<h1 class="text-xl font-bold mb-4">我的資料夾</h1>
{{-- 成功訊息 --}}
@if(session('success'))
<div class="mb-4 text-green-600">
{{ session('success') }}
</div>
@endif
{{-- 新增根資料夾 --}}
<form method="POST" action="{{ route('folders.store') }}" class="mb-4 flex gap-2">
@csrf
<input type="text" name="name" placeholder="新資料夾名稱" class="border rounded p-2 flex-1" required>
<button class="bg-blue-500 text-white px-4 py-2 rounded">新增</button>
</form>
{{-- 根目錄資料夾列表 --}}
<ul class="space-y-2 mb-6">
@forelse($folders as $folder)
<li class="border p-3 rounded hover:bg-gray-100 flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($folder->name) }}' }">
{{-- 資料夾名稱 --}}
<div class="flex items-center gap-2">
📁
<template x-if="!editing">
<span @dblclick="editing = true" class="font-semibold text-blue-600 cursor-pointer">
{{ $folder->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('folders.update', $folder) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('folders.show', $folder) }}" class="text-sm text-blue-500">開啟</a>
<button type="button" @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('folders.destroy', $folder) }}"
onsubmit="return confirm('確定要刪除這個資料夾及其所有子資料夾嗎?')">
@csrf
@method('DELETE')
<button class="text-sm text-red-600">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">尚無資料夾</li>
@endforelse
</ul>
{{-- 多檔案上傳 --}}
<form method="POST" action="{{ route('files.store') }}" enctype="multipart/form-data" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="folder_id" value="">
<input type="file" name="files[]" multiple required>
<button class="bg-green-600 text-white px-4 py-2 rounded">上傳檔案</button>
</form>
{{-- 檔案排序 --}}
<div class="mb-2 flex items-center gap-2 text-sm text-gray-500">
排序:
@php
$options = ['name' => '名稱', 'size' => '大小', 'created_at' => '上傳日期'];
@endphp
@foreach($options as $key => $label)
<a href="{{ route('folders.index') }}?sort={{ $key }}&direction={{ $sort === $key && $direction === 'asc' ? 'desc' : 'asc' }}"
class="hover:underline {{ $sort === $key ? 'font-semibold' : '' }}">
{{ $label }}
@if($sort === $key)
{{ $direction === 'asc' ? '↑' : '↓' }}
@endif
</a>
@if(!$loop->last) | @endif
@endforeach
</div>
{{-- 根目錄檔案列表 --}}
<h2 class="font-semibold mb-2">檔案</h2>
<ul class="space-y-2">
@forelse($files as $file)
<li class="border p-3 rounded flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($file->name) }}' }">
{{-- 檔案名稱 + 大小 + 上傳日期 --}}
<div class="flex items-center gap-4">
📄
<template x-if="!editing">
<span @dblclick="editing = true" class="cursor-pointer text-blue-600">
{{ $file->name }}
</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('files.update', $file) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
{{-- 檔案大小 --}}
<span class="text-gray-500 text-sm">
@php
if($file->size >= 1048576){
$size = number_format($file->size / 1048576, 2) . ' MB';
} elseif($file->size >= 1024){
$size = number_format($file->size / 1024, 2) . ' KB';
} else {
$size = $file->size . ' B';
}
@endphp
{{ $size }}
</span>
{{-- 上傳日期 --}}
<span class="text-gray-400 text-sm">{{ $file->created_at->format('Y-m-d H:i') }}</span>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('files.download', $file) }}" class="text-blue-600 hover:underline">下載</a>
<button type="button" @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('files.destroy', $file) }}"
onsubmit="return confirm('確定要刪除這個檔案嗎?')">
@csrf
@method('DELETE')
<button class="text-red-600 text-sm">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">尚無檔案</li>
@endforelse
</ul>
</div>
</x-app-layout>
3.編輯 show.blade.php,直接將整個直接替換成下面的程式碼
<x-app-layout>
<div class="p-6 max-w-4xl mx-auto">
{{-- Breadcrumb --}}
<div class="mb-4 text-sm text-gray-500">
<a href="{{ route('folders.index') }}" class="hover:underline">我的雲端硬碟</a>
@foreach ($breadcrumbs as $crumb)
/ <a href="{{ route('folders.show', $crumb) }}" class="hover:underline">{{ $crumb->name }}</a>
@endforeach
</div>
<h1 class="text-xl font-bold mb-4">📁 {{ $folder->name }}</h1>
{{-- 成功訊息 --}}
@if(session('success'))
<div class="mb-4 text-green-600">
{{ session('success') }}
</div>
@endif
{{-- 新增子資料夾 --}}
<form method="POST" action="{{ route('folders.store') }}" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="parent_id" value="{{ $folder->id }}">
<input type="text" name="name" placeholder="子資料夾名稱" class="border rounded p-2 flex-1" required>
<button class="bg-blue-500 text-white px-4 py-2 rounded">新增</button>
</form>
{{-- 子資料夾列表 --}}
<ul class="space-y-2 mb-6">
@forelse($subFolders as $sub)
<li class="border p-3 rounded hover:bg-gray-100 flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($sub->name) }}' }">
{{-- 子資料夾名稱 --}}
<div class="flex items-center gap-2">
📁
<template x-if="!editing">
<span @dblclick="editing = true" class="font-semibold text-blue-600 cursor-pointer">{{ $sub->name }}</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('folders.update', $sub) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('folders.show', $sub) }}" class="text-sm text-blue-500">開啟</a>
<button type="button" @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('folders.destroy', $sub) }}"
onsubmit="return confirm('確定要刪除這個資料夾及其所有子資料夾嗎?')">
@csrf
@method('DELETE')
<button class="text-red-600 text-sm">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">此資料夾尚無子資料夾</li>
@endforelse
</ul>
{{-- 多檔案上傳 --}}
<form method="POST" action="{{ route('files.store') }}" enctype="multipart/form-data" class="mb-4 flex gap-2">
@csrf
<input type="hidden" name="folder_id" value="{{ $folder->id }}">
<input type="file" name="files[]" multiple required>
<button class="bg-green-600 text-white px-4 py-2 rounded">上傳檔案</button>
</form>
{{-- 檔案排序 --}}
<div class="mb-2 flex items-center gap-2 text-sm text-gray-500">
排序:
@php
$options = ['name' => '名稱', 'size' => '大小', 'created_at' => '上傳日期'];
@endphp
@foreach($options as $key => $label)
<a href="{{ route('folders.show', $folder) }}?sort={{ $key }}&direction={{ $sort === $key && $direction === 'asc' ? 'desc' : 'asc' }}"
class="hover:underline {{ $sort === $key ? 'font-semibold' : '' }}">
{{ $label }}
@if($sort === $key)
{{ $direction === 'asc' ? '↑' : '↓' }}
@endif
</a>
@if(!$loop->last) | @endif
@endforeach
</div>
{{-- 子資料夾檔案列表 --}}
<h2 class="font-semibold mb-2">檔案</h2>
<ul class="space-y-2">
@forelse($files as $file)
<li class="border p-3 rounded flex justify-between items-center"
x-data="{ editing: false, name: '{{ e($file->name) }}' }">
<div class="flex items-center gap-4">
📄
<template x-if="!editing">
<span @dblclick="editing = true" class="cursor-pointer text-blue-600">{{ $file->name }}</span>
</template>
<template x-if="editing">
<form method="POST" action="{{ route('files.update', $file) }}" class="flex items-center gap-1">
@csrf
@method('PATCH')
<input type="text" name="name" x-model="name"
@keydown.enter.prevent="$el.form.submit()"
@keydown.escape="editing = false"
class="border rounded p-1 text-sm w-32" autofocus required>
<button class="text-green-600 text-sm">儲存</button>
<button type="button" @click="editing = false" class="text-gray-500 text-sm">取消</button>
</form>
</template>
{{-- 檔案大小 --}}
<span class="text-gray-500 text-sm">
@php
if($file->size >= 1048576){
$size = number_format($file->size / 1048576, 2) . ' MB';
} elseif($file->size >= 1024){
$size = number_format($file->size / 1024, 2) . ' KB';
} else {
$size = $file->size . ' B';
}
@endphp
{{ $size }}
</span>
{{-- 上傳日期 --}}
<span class="text-gray-400 text-sm">{{ $file->created_at->format('Y-m-d H:i') }}</span>
</div>
{{-- 操作按鈕 --}}
<div class="flex gap-2 items-center">
<a href="{{ route('files.download', $file) }}" class="text-blue-600 hover:underline">下載</a>
<button type="button" @click="editing = true" class="text-sm text-gray-600">重新命名</button>
<form method="POST" action="{{ route('files.destroy', $file) }}"
onsubmit="return confirm('確定要刪除這個檔案嗎?')">
@csrf
@method('DELETE')
<button class="text-red-600 text-sm">刪除</button>
</form>
</div>
</li>
@empty
<li class="text-gray-400">尚無檔案</li>
@endforelse
</ul>
</div>
</x-app-layout>













沒有留言:
張貼留言