+```
+
+**跨元件開啟 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 --}}
+