input('per_page', 10); $sortBy = $request->input('sort_by', 'id'); $sortOrder = $request->input('sort_order', 'asc'); $search = $request->input('search'); $roleId = $request->input('role'); $isActive = $request->input('is_active'); // 'all', '1', '0' $query = User::query(); // 隱藏超級管理員:若非 super-admin,則不可看到 super-admin 過往 if (!auth()->user()->hasRole('super-admin')) { $query->whereDoesntHave('roles', function ($q) { $q->where('name', 'super-admin'); }); // 預載入角色時也過濾掉 super-admin 標籤 $query->with(['roles' => function ($q) { $q->select('id', 'name', 'display_name') ->where('name', '!=', 'super-admin'); }]); } else { $query->with(['roles:id,name,display_name']); } // 處理搜尋 if ($search) { $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%") ->orWhere('username', 'like', "%{$search}%"); }); } // 處理角色篩選 if ($roleId && $roleId !== 'all') { $query->whereHas('roles', function ($q) use ($roleId) { $q->where('id', $roleId); }); } // 處理狀態篩選 if ($isActive !== null && $isActive !== 'all') { $query->where('is_active', $isActive === '1' || $isActive === 'true'); } // 處理排序 if (in_array($sortBy, ['name', 'created_at'])) { $query->orderBy($sortBy, $sortOrder); } else { $query->orderBy('id', 'asc'); } $users = $query->paginate($perPage)->withQueryString(); // 只能看到自己權限以下的角色 $rolesQuery = Role::select('id', 'name', 'display_name'); if (!auth()->user()->hasRole('super-admin')) { $rolesQuery->where('name', '!=', 'super-admin'); } $roles = $rolesQuery->get(); return Inertia::render('Admin/User/Index', [ 'users' => $users, 'users' => $users, 'roles' => $roles, 'filters' => $request->only(['per_page', 'sort_by', 'sort_order', 'search', 'role', 'is_active']), ]); } /** * 顯示建立新資源的表單。 */ public function create() { $rolesQuery = Role::query(); if (!auth()->user()->hasRole('super-admin')) { $rolesQuery->where('name', '!=', 'super-admin'); } $roles = $rolesQuery->pluck('display_name', 'name'); return Inertia::render('Admin/User/Create', [ 'roles' => $roles ]); } /** * 將新建立的資源儲存到儲存體中。 */ public function store(Request $request) { $validated = $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['nullable', 'string', 'email', 'max:255', 'unique:users'], 'username' => ['required', 'string', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], 'roles' => ['array'], 'is_active' => ['boolean'], ], [ 'password.required' => '請輸入密碼', 'password.min' => '密碼長度至少需 :min 個字元', 'password.confirmed' => '密碼確認不符', ]); $user = User::create([ 'name' => $validated['name'], 'email' => $validated['email'], 'username' => $validated['username'], 'password' => Hash::make($validated['password']), 'is_active' => $request->boolean('is_active', true), ]); if (!empty($validated['roles'])) { // 安全檢查:非 super-admin 不能賦予 super-admin 角色 if (!auth()->user()->hasRole('super-admin') && in_array('super-admin', $validated['roles'])) { abort(403, '您沒有權限指派系統管理員角色'); } $user->syncRoles($validated['roles']); // 更新 'created' 紀錄以包含角色資訊 $activity = \Spatie\Activitylog\Models\Activity::where('subject_type', get_class($user)) ->where('subject_id', $user->id) ->where('event', 'created') ->latest() ->first(); if ($activity) { $roleNames = $user->roles()->pluck('display_name')->join(', '); $properties = $activity->properties->toArray(); $properties['attributes']['role_id'] = $roleNames; $activity->properties = $properties; $activity->save(); } } return redirect()->route('users.index')->with('success', '使用者建立成功'); } /** * 顯示編輯指定資源的表單。 */ public function edit(string $id) { $user = User::with('roles')->findOrFail($id); // 安全檢查:非 super-admin 不能編輯 super-admin if ($user->hasRole('super-admin') && !auth()->user()->hasRole('super-admin')) { abort(403, '您沒有權限編輯系統管理員'); } $rolesQuery = Role::select('id', 'name', 'display_name'); if (!auth()->user()->hasRole('super-admin')) { $rolesQuery->where('name', '!=', 'super-admin'); } $roles = $rolesQuery->get(); return Inertia::render('Admin/User/Edit', [ 'user' => $user, 'roles' => $roles, 'currentRoles' => $user->getRoleNames() ]); } /** * 更新儲存體中的指定資源。 */ public function update(Request $request, string $id) { $user = User::findOrFail($id); // 安全檢查:非 super-admin 不能更新 super-admin if ($user->hasRole('super-admin') && !auth()->user()->hasRole('super-admin')) { abort(403, '您沒有權限編輯系統管理員'); } $validated = $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['nullable', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 'username' => ['required', 'string', 'max:255', Rule::unique('users')->ignore($user->id)], 'password' => ['nullable', 'string', 'min:8', 'confirmed'], 'roles' => ['array'], 'is_active' => ['boolean'], ], [ 'password.min' => '密碼長度至少需 :min 個字元', 'password.confirmed' => '密碼確認不符', ]); // 1. 準備資料並偵測變更 $userData = [ 'name' => $validated['name'], 'email' => $validated['email'], 'username' => $validated['username'], ]; $user->fill($userData); // 捕捉變更屬性以進行手動記錄 $dirty = $user->getDirty(); $oldAttributes = []; $newAttributes = []; foreach ($dirty as $key => $value) { $oldAttributes[$key] = $user->getOriginal($key); $newAttributes[$key] = $value; } // 儲存但不觸發事件(防止重複記錄) $user->saveQuietly(); // 2. 處理角色 $roleChanges = null; if (isset($validated['roles'])) { // 安全檢查:非 super-admin 不能賦予 super-admin 角色 if (!auth()->user()->hasRole('super-admin') && in_array('super-admin', $validated['roles'])) { abort(403, '您沒有權限指派系統管理員角色'); } $oldRoles = $user->roles()->pluck('display_name')->join(', '); $user->syncRoles($validated['roles']); $newRoles = $user->roles()->pluck('display_name')->join(', '); if ($oldRoles !== $newRoles) { $roleChanges = [ 'old' => $oldRoles, 'new' => $newRoles ]; } } // 3. 手動記錄活動(單一整合記錄) if (!empty($newAttributes) || $roleChanges) { $properties = [ 'attributes' => $newAttributes, 'old' => $oldAttributes, ]; if ($roleChanges) { $properties['attributes']['role_id'] = $roleChanges['new']; $properties['old']['role_id'] = $roleChanges['old']; } activity() ->performedOn($user) ->causedBy(auth()->user()) ->event('updated') ->withProperties($properties) ->tap(function (\Spatie\Activitylog\Contracts\Activity $activity) use ($user) { // 手動加入快照,因為使用 saveQuietly 所以不使用模型的 LogOptions $activity->properties = $activity->properties->merge([ 'snapshot' => [ 'name' => $user->name, 'username' => $user->username, ] ]); }) ->log('updated'); } return redirect()->route('users.index')->with('success', '使用者更新成功'); } /** * 從儲存體中移除指定資源。 */ public function destroy(string $id) { $user = User::findOrFail($id); // 安全檢查:非 super-admin 不能刪除 super-admin if ($user->hasRole('super-admin') && !auth()->user()->hasRole('super-admin')) { abort(403, '您沒有權限刪除系統管理員'); } if ($user->hasRole('super-admin')) { return back()->with('error', '無法刪除超級管理員帳號'); } if ($user->id === auth()->id()) { return back()->with('error', '無法刪除自己'); } $user->delete(); return redirect()->route('users.index')->with('success', "使用者「{$user->name}」已刪除"); } /** * 切換使用者啟用/停用狀態 */ public function toggleActive(string $id) { $user = User::findOrFail($id); // 安全檢查:不能停用自己 if ($user->id === auth()->id() && $user->is_active) { return back()->with('error', '無法停用自己的帳號'); } // 安全檢查:非 super-admin 不能停用 super-admin if ($user->hasRole('super-admin') && !auth()->user()->hasRole('super-admin')) { abort(403, '您沒有權限變更系統管理員狀態'); } $oldStatus = $user->is_active; $user->is_active = !$oldStatus; $user->save(); // 記錄活動 activity() ->performedOn($user) ->causedBy(auth()->user()) ->event('updated') ->withProperties([ 'attributes' => ['is_active' => $user->is_active], 'old' => ['is_active' => $oldStatus], 'snapshot' => [ 'name' => $user->name, 'username' => $user->username, ] ]) ->log('updated'); $statusText = $user->is_active ? '已啟用' : '已停用'; return back()->with('success', "使用者「{$user->name}」{$statusText}"); } }