1. 新增 `ImpersonateController` 處理帳號切換與退出邏輯,並提供公司與使用者的動態下拉選單 API (包含 username 支援)。 2. 於 `admin.blade.php` 的 Header 個人選單中加入「切換帳號」入口,並支援退出切換狀態的顯示。 3. 實作 `modal.blade.php` Alpine.js 彈窗,使用專案規範的 `x-searchable-select` 動態重建模式 (Dynamic Rebuild Pattern) 與 `focus-within:z-[60]` 防護。 4. 註冊相關 Web Routes。 5. 更新 `ui-minimal-luxury/SKILL.md`,新增 Alpine.js Modal 實作規範,並禁止使用 `hs-overlay` 避免層級堆疊問題。
99 lines
3.2 KiB
PHP
99 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\System\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
class ImpersonateController extends Controller
|
|
{
|
|
/**
|
|
* Start impersonating a user.
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
$request->validate([
|
|
'user_id' => 'required|exists:users,id',
|
|
]);
|
|
|
|
$currentUser = $request->user();
|
|
|
|
// 1. Only system admins can impersonate
|
|
if (!$currentUser->isSystemAdmin()) {
|
|
abort(403, '只有系統管理員可以使用此功能。');
|
|
}
|
|
|
|
// 2. Prevent nested impersonation (cannot impersonate someone while already impersonating)
|
|
if (session()->has('impersonated_by')) {
|
|
return back()->with('error', '您目前已經處於切換狀態,無法再次切換。請先退出目前的切換狀態。');
|
|
}
|
|
|
|
$targetUserId = $request->input('user_id');
|
|
|
|
// 3. Cannot impersonate oneself
|
|
if ($currentUser->id == $targetUserId) {
|
|
return back()->with('error', '無法切換至自己的帳號。');
|
|
}
|
|
|
|
$targetUser = User::findOrFail($targetUserId);
|
|
|
|
// 4. Set session and login
|
|
session()->put('impersonated_by', $currentUser->id);
|
|
Auth::loginUsingId($targetUser->id);
|
|
|
|
return redirect()->route('admin.dashboard')->with('success', "已成功切換至 {$targetUser->name} 的帳號。");
|
|
}
|
|
|
|
/**
|
|
* Stop impersonating and return to the original user.
|
|
*/
|
|
public function leave(Request $request)
|
|
{
|
|
if (!session()->has('impersonated_by')) {
|
|
return back()->with('error', '您目前並未處於切換狀態。');
|
|
}
|
|
|
|
$originalUserId = session()->pull('impersonated_by');
|
|
Auth::loginUsingId($originalUserId);
|
|
|
|
return redirect()->route('admin.dashboard')->with('success', '已退出切換狀態,返回原帳號。');
|
|
}
|
|
|
|
/**
|
|
* Fetch companies for the impersonate modal.
|
|
*/
|
|
public function companies(Request $request)
|
|
{
|
|
if (!$request->user()->isSystemAdmin()) abort(403);
|
|
|
|
$companies = \App\Models\System\Company::select('id', 'name')->orderBy('name')->get();
|
|
return response()->json($companies);
|
|
}
|
|
|
|
/**
|
|
* Fetch users for the impersonate modal, optionally filtered by company.
|
|
*/
|
|
public function users(Request $request)
|
|
{
|
|
if (!$request->user()->isSystemAdmin()) abort(403);
|
|
|
|
$query = User::select('id', 'name', 'username', 'email', 'company_id');
|
|
|
|
if ($request->filled('company_id')) {
|
|
$query->where('company_id', $request->company_id);
|
|
} else {
|
|
// Include system accounts (company_id is null) when no company is selected?
|
|
// Actually let's just return all users if no company selected, or just system accounts.
|
|
// But if company_id is explicitly sent as 'system', we filter for null.
|
|
if ($request->company_id === 'system') {
|
|
$query->whereNull('company_id');
|
|
}
|
|
}
|
|
|
|
$users = $query->orderBy('name')->get();
|
|
return response()->json($users);
|
|
}
|
|
}
|