[FEAT] 完善個人資料頭像上傳功能與導覽列介面優化

This commit is contained in:
2026-03-13 09:30:07 +08:00
parent 773396fc90
commit ea460cf6d9
15 changed files with 234 additions and 382 deletions

View File

@@ -7,6 +7,7 @@ use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\View;
class ProfileController extends Controller
@@ -17,17 +18,10 @@ class ProfileController extends Controller
public function edit(Request $request): View
{
$user = $request->user();
// 如果沒有歷史紀錄,注入一些模擬資料供預覽
if ($user->loginLogs()->count() === 0) {
$user->loginLogs()->createMany([
['ip_address' => '127.0.0.1', 'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)', 'login_at' => now()->subHours(2)],
['ip_address' => '192.168.1.100', 'user_agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)', 'login_at' => now()->subDays(1)],
]);
}
return view('profile.edit', [
'user' => $user->load(['loginLogs' => fn($q) => $q->latest()]),
// 只取最新 10 筆登入紀錄
'user' => $user->load(['loginLogs' => fn($q) => $q->latest('login_at')->limit(10)]),
]);
}
@@ -36,13 +30,24 @@ class ProfileController extends Controller
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
$user = $request->user();
$user->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
if ($user->isDirty('email')) {
$user->email_verified_at = null;
}
$request->user()->save();
if ($request->hasFile('avatar')) {
// Delete old avatar if exists
if ($user->avatar) {
Storage::disk('public')->delete($user->avatar);
}
$path = $request->file('avatar')->store('avatars', 'public');
$user->avatar = $path;
}
$user->save();
return Redirect::route('profile.edit')->with('status', 'profile-updated');
}

View File

@@ -19,7 +19,7 @@ class ProfileUpdateRequest extends FormRequest
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
'phone' => ['nullable', 'string', 'max:20'],
'avatar' => ['nullable', 'string', 'max:255'],
'avatar' => ['nullable', 'image', 'mimes:jpeg,png,jpg,gif', 'max:2048'],
];
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Listeners;
use App\Models\UserLoginLog;
use Illuminate\Auth\Events\Login;
use Illuminate\Http\Request;
class LogSuccessfulLogin
{
/**
* The request instance.
*
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* Create the event listener.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Handle the event.
*
* @param \Illuminate\Auth\Events\Login $event
* @return void
*/
public function handle(Login $event)
{
UserLoginLog::create([
'user_id' => $event->user->id,
'ip_address' => $this->request->ip(),
'user_agent' => $this->request->userAgent(),
'login_at' => now(),
]);
}
}

View File

@@ -54,4 +54,16 @@ class User extends Authenticatable
{
return $this->hasMany(\App\Models\UserLoginLog::class);
}
/**
* Get the user's avatar URL.
*/
public function getAvatarUrlAttribute(): string
{
if ($this->avatar) {
return asset('storage/' . $this->avatar);
}
return "https://ui-avatars.com/api/?name=" . urlencode($this->name) . "&background=0D8ABC&color=fff";
}
}

View File

@@ -2,6 +2,9 @@
namespace App\Providers;
use App\Listeners\LogSuccessfulLogin;
use Illuminate\Auth\Events\Login;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@@ -22,5 +25,9 @@ class AppServiceProvider extends ServiceProvider
if (!$this->app->isLocal()) {
\Illuminate\Support\Facades\URL::forceScheme('https');
}
// 記錄使用者成功登入的歷史
Event::listen(Login::class, LogSuccessfulLogin::class);
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Providers;
use App\Listeners\LogSuccessfulLogin;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@@ -18,6 +20,9 @@ class EventServiceProvider extends ServiceProvider
Registered::class => [
SendEmailVerificationNotification::class,
],
Login::class => [
LogSuccessfulLogin::class,
],
];
/**