diff --git a/.agents/skills/ui-minimal-luxury/SKILL.md b/.agents/skills/ui-minimal-luxury/SKILL.md index cab0720..884425e 100644 --- a/.agents/skills/ui-minimal-luxury/SKILL.md +++ b/.agents/skills/ui-minimal-luxury/SKILL.md @@ -404,7 +404,82 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範 | 刪除確認 Modal | `` | | 頁面內警告(琥珀色)| `
` | ---- +### 7.1 Modal 實作規範(強制) + +> [!CAUTION] +> **嚴禁使用 Preline 的 `hs-overlay` 系統**來實作 Modal。 +> 本專案全部 Modal 統一使用 **Alpine.js** (`x-show` + `x-transition`) 實作。 +> 使用 `hs-overlay` class 會導致 Modal 出現但完全透明(`opacity: 0`),因為 Preline overlay 的動畫 class 在此專案中未被正確初始化。 + +**正確:Alpine.js Modal 標準結構(參考 `modal-replenishment-create.blade.php`)** + +```html +{{-- 1. 觸發按鈕:用 $dispatch 發送 window 事件 --}} + + +{{-- 2. Modal 主體:放在 layout 底部,x-data 宣告 Alpine state --}} +
+ +
+``` + +**跨元件開啟 Modal(dropdown 內的按鈕):** +- 按鈕在 Alpine.js dropdown 內時,需先關閉 dropdown 再發送事件: + ```html + + ``` +- Modal 透過 `@open-xxx-modal.window` 接收(`window` 層級才能跨 Alpine scope 傳遞) + + ## 8. AJAX fetchPage 標準實作 diff --git a/app/Http/Controllers/Admin/ImpersonateController.php b/app/Http/Controllers/Admin/ImpersonateController.php new file mode 100644 index 0000000..52dcd42 --- /dev/null +++ b/app/Http/Controllers/Admin/ImpersonateController.php @@ -0,0 +1,98 @@ +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); + } +} diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 049ddb1..f839a32 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -9,9 +9,11 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Storage; use Illuminate\View\View; +use App\Traits\ImageHandler; class ProfileController extends Controller { + use ImageHandler; /** * Display the user's profile form. */ @@ -48,7 +50,7 @@ class ProfileController extends Controller public function updateAvatar(Request $request): \Illuminate\Http\JsonResponse { $request->validate([ - 'avatar' => ['required', 'image', 'mimes:jpeg,png,jpg,gif,webp', 'max:1024'], + 'avatar' => ['required', 'image', 'mimes:jpeg,png,jpg,gif,webp', 'max:5120'], ]); $user = $request->user(); @@ -59,7 +61,8 @@ class ProfileController extends Controller Storage::disk('public')->delete($user->avatar); } - $path = $request->file('avatar')->store('avatars', 'public'); + // 將上傳的大頭貼轉換為高壓縮率且清晰的 WebP 格式 (300x300 居中裁切) + $path = $this->storeAsWebp($request->file('avatar'), 'avatars', 85, 300, 300); $user->avatar = $path; $user->save(); diff --git a/app/Http/Requests/ProfileUpdateRequest.php b/app/Http/Requests/ProfileUpdateRequest.php index f4a10f8..9e5ded7 100644 --- a/app/Http/Requests/ProfileUpdateRequest.php +++ b/app/Http/Requests/ProfileUpdateRequest.php @@ -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', 'image', 'mimes:jpeg,png,jpg,gif', 'max:2048'], + 'avatar' => ['nullable', 'image', 'mimes:jpeg,png,jpg,gif,webp', 'max:5120'], ]; } } diff --git a/lang/en.json b/lang/en.json index 447b741..0b6003e 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1616,6 +1616,7 @@ "The Super Admin role is immutable.": "The Super Admin role is immutable.", "The Super Admin role name cannot be modified.": "The Super Admin role name cannot be modified.", "The image is too large. Please upload an image smaller than 1MB.": "The image is too large. Please upload an image smaller than 1MB.", + "The image is too large. Please upload an image smaller than 5MB.": "The image is too large. Please upload an image smaller than 5MB.", "This is a system administrator role. Its name is locked to ensure system stability.": "This is a system administrator role. Its name is locked to ensure system stability.", "This machine has a pending command. Please wait.": "This machine has a pending command. Please wait.", "This role belongs to another company and cannot be assigned.": "This role belongs to another company and cannot be assigned.", diff --git a/lang/ja.json b/lang/ja.json index 2c012c2..af811ed 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -1616,6 +1616,7 @@ "The Super Admin role is immutable.": "特権管理者ロールは変更できません。", "The Super Admin role name cannot be modified.": "特権管理者ロールの名前は変更できません。", "The image is too large. Please upload an image smaller than 1MB.": "画像サイズが大きすぎます。1MB未満の画像をアップロードしてください。", + "The image is too large. Please upload an image smaller than 5MB.": "画像サイズが大きすぎます。5MB未満の画像をアップロードしてください。", "This is a system administrator role. Its name is locked to ensure system stability.": "これはシステム管理者ロールです。システムの安定性を確保するため、名前はロックされています。", "This machine has a pending command. Please wait.": "この機器には実行中のコマンドがあります。しばらくお待ちください。", "This role belongs to another company and cannot be assigned.": "このロールは別の会社に属しているため、割り当てることができません。", diff --git a/lang/zh_TW.json b/lang/zh_TW.json index e55b14a..370fcc9 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -1638,6 +1638,7 @@ "The Super Admin role is immutable.": "超級管理員角色不可修改。", "The Super Admin role name cannot be modified.": "超級管理員角色的名稱無法修改。", "The image is too large. Please upload an image smaller than 1MB.": "圖片檔案太大,請上傳小於 1MB 的圖片。", + "The image is too large. Please upload an image smaller than 5MB.": "圖片檔案太大,請上傳小於 5MB 的圖片。", "This is a system administrator role. Its name is locked to ensure system stability.": "這是系統管理員角色,名稱已鎖定以確保系統穩定性。", "This machine has a pending command. Please wait.": "此機台已有指令正在執行,請稍後。", "This role belongs to another company and cannot be assigned.": "此角色屬於其他公司,無法指派。", diff --git a/public/favicon.ico b/public/favicon.ico index aa04a47..13dae33 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/starcloud_icon.png b/public/starcloud_icon.png new file mode 100644 index 0000000..bdf1adf Binary files /dev/null and b/public/starcloud_icon.png differ diff --git a/public/starcloudlogo-Photoroom.png b/public/starcloudlogo-Photoroom.png new file mode 100644 index 0000000..44b2219 Binary files /dev/null and b/public/starcloudlogo-Photoroom.png differ diff --git a/public/starcloudlogo.png b/public/starcloudlogo.png new file mode 100644 index 0000000..09124fe Binary files /dev/null and b/public/starcloudlogo.png differ diff --git a/public/tech_lines_bg.png b/public/tech_lines_bg.png new file mode 100644 index 0000000..1c44459 Binary files /dev/null and b/public/tech_lines_bg.png differ diff --git a/public/tech_vending_bg.png b/public/tech_vending_bg.png new file mode 100644 index 0000000..158bfe1 Binary files /dev/null and b/public/tech_vending_bg.png differ diff --git a/resources/views/admin/impersonate/modal.blade.php b/resources/views/admin/impersonate/modal.blade.php new file mode 100644 index 0000000..9b2328a --- /dev/null +++ b/resources/views/admin/impersonate/modal.blade.php @@ -0,0 +1,273 @@ +{{-- 切換帳號 Modal --}} +
+ +
+ + diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index cf167e4..24be2f7 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -5,94 +5,138 @@ {{ config('app.name', 'Laravel') }} + - + @vite(['resources/css/app.css', 'resources/js/app.js']) - -
- -