[FEAT] 完善全站多語系支援、角色權限篩選優化及 UI 元件重構
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m4s
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m4s
- [DOCS] 補齊 en, ja, zh_TW 語系檔翻譯並完善驗證錯誤訊息 (validation.php) - [FEAT] 角色權限頁面新增「所屬單位」篩選功能 (僅限系統管理員) - [STYLE] 優化角色列表顯示,將「類型」變更為具體「所屬單位」名稱 - [STYLE] 修正角色頁面工具列佈局,搜尋框置前並修正下拉箭頭顯示 - [REFACTOR] 統一全站刪除確認視窗,導入新版 <x-delete-confirm-modal /> 組件 - [REFACTOR] 優化 PermissionController 查詢效能 (Eager Loading) - [FIX] 修正 RoleSeeder 角色命名與資料庫同步邏輯
This commit is contained in:
@@ -17,6 +17,12 @@ class PaymentConfigController extends AdminController
|
||||
{
|
||||
$per_page = $request->input('per_page', 20);
|
||||
$configs = PaymentConfig::query()
|
||||
->when($request->search, function ($query, $search) {
|
||||
$query->where('name', 'like', "%{$search}%")
|
||||
->orWhereHas('company', function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%");
|
||||
});
|
||||
})
|
||||
->with(['company', 'creator'])
|
||||
->latest()
|
||||
->paginate($per_page)
|
||||
|
||||
@@ -32,9 +32,9 @@ class CompanyController extends Controller
|
||||
$per_page = $request->input('per_page', 10);
|
||||
$companies = $query->latest()->paginate($per_page)->withQueryString();
|
||||
|
||||
// 取得可供選擇的客戶角色範本 (is_system = 0, company_id = null)
|
||||
$template_roles = \App\Models\System\Role::where('is_system', 0)
|
||||
->whereNull('company_id')
|
||||
// 取得可供選擇的客戶角色範本 (系統層級的角色,排除 super-admin)
|
||||
$template_roles = \App\Models\System\Role::whereNull('company_id')
|
||||
->where('name', '!=', 'super-admin')
|
||||
->get();
|
||||
|
||||
return view('admin.companies.index', compact('companies', 'template_roles'));
|
||||
@@ -86,23 +86,23 @@ class CompanyController extends Controller
|
||||
]);
|
||||
|
||||
// 角色初始化與克隆邏輯 (優先使用選擇的角色,否則使用預設)
|
||||
$selected_role_name = $validated['admin_role'] ?? '通用客戶角色範本';
|
||||
$role_to_assign = '管理員';
|
||||
$selected_role_name = $validated['admin_role'] ?? '客戶管理員角色模板';
|
||||
$role_to_assign = null;
|
||||
|
||||
$template_role = \App\Models\System\Role::where('name', $selected_role_name)
|
||||
->whereNull('company_id')
|
||||
->where('is_system', 0)
|
||||
->where('name', '!=', 'super-admin')
|
||||
->first();
|
||||
|
||||
if ($template_role) {
|
||||
// 克隆範本為該公司的「管理員」
|
||||
$clonedRole = \App\Models\System\Role::query()->create([
|
||||
$role_to_assign = \App\Models\System\Role::query()->create([
|
||||
'name' => '管理員',
|
||||
'guard_name' => 'web',
|
||||
'company_id' => $company->id,
|
||||
'is_system' => false,
|
||||
]);
|
||||
$clonedRole->syncPermissions($template_role->permissions);
|
||||
$role_to_assign->syncPermissions($template_role->getPermissionNames());
|
||||
} else {
|
||||
// 如果找不到選定的角色範本,退而求其次嘗試指派現有角色 (通常不應發生)
|
||||
$role_to_assign = $selected_role_name;
|
||||
|
||||
@@ -12,9 +12,9 @@ class PermissionController extends Controller
|
||||
{
|
||||
$per_page = request()->input('per_page', 10);
|
||||
$user = auth()->user();
|
||||
$query = \App\Models\System\Role::query()->with(['permissions', 'users']);
|
||||
$query = \App\Models\System\Role::query()->with(['permissions', 'users', 'company']);
|
||||
|
||||
// 租戶隔離:租戶只能看到自己公司的角色 + 系統角色 (company_id is null)
|
||||
// 租戶隔離:租戶只能看到自己公司的角色
|
||||
if (!$user->isSystemAdmin()) {
|
||||
$query->where('company_id', $user->company_id);
|
||||
}
|
||||
@@ -24,10 +24,27 @@ class PermissionController extends Controller
|
||||
$query->where('name', 'like', "%{$search}%");
|
||||
}
|
||||
|
||||
// 篩選:所屬單位 (僅限系統管理員)
|
||||
if ($user->isSystemAdmin() && request()->filled('company_id')) {
|
||||
if (request()->company_id === 'system') {
|
||||
$query->where('is_system', true);
|
||||
} else {
|
||||
$query->where('company_id', request()->company_id);
|
||||
}
|
||||
}
|
||||
|
||||
$roles = $query->latest()->paginate($per_page)->withQueryString();
|
||||
$all_permissions = \Spatie\Permission\Models\Permission::all()
|
||||
$companies = $user->isSystemAdmin() ? \App\Models\System\Company::all() : collect();
|
||||
|
||||
// 權限遞迴約束:租戶管理員只能看到並指派自己擁有的權限
|
||||
$permissionQuery = \Spatie\Permission\Models\Permission::query();
|
||||
if (!$user->isSystemAdmin()) {
|
||||
$permissionQuery->whereIn('name', $user->getAllPermissions()->pluck('name'));
|
||||
}
|
||||
|
||||
$all_permissions = $permissionQuery->get()
|
||||
->filter(function($perm) {
|
||||
// 排除子項目的權限,只顯示主選單權限
|
||||
// 排除子項項目,只顯示主要權限
|
||||
$excluded = [
|
||||
'menu.basic.machines',
|
||||
'menu.basic.payment-configs',
|
||||
@@ -47,7 +64,8 @@ class PermissionController extends Controller
|
||||
// 根據路由決定標題
|
||||
$title = request()->routeIs('*.sub-account-roles') ? __('Sub Account Roles') : __('Role Settings');
|
||||
|
||||
return view('admin.permission.roles', compact('roles', 'all_permissions', 'title'));
|
||||
$currentUserRoleIds = $user->roles->pluck('id')->toArray();
|
||||
return view('admin.permission.roles', compact('roles', 'all_permissions', 'title', 'currentUserRoleIds', 'companies'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,6 +96,15 @@ class PermissionController extends Controller
|
||||
|
||||
if (!empty($validated['permissions'])) {
|
||||
$perms = $validated['permissions'];
|
||||
|
||||
// 權限遞迴約束驗證:確保指派的權限是操作者權限的子集
|
||||
if (!auth()->user()->isSystemAdmin()) {
|
||||
$currentUserPerms = auth()->user()->getAllPermissions()->pluck('name');
|
||||
if (collect($perms)->diff($currentUserPerms)->isNotEmpty()) {
|
||||
return redirect()->back()->with('error', __('You cannot assign permissions you do not possess.'));
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是系統角色,排除主選單的系統權限
|
||||
if (!$is_system) {
|
||||
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
|
||||
@@ -128,6 +155,15 @@ class PermissionController extends Controller
|
||||
]);
|
||||
|
||||
$perms = $validated['permissions'] ?? [];
|
||||
|
||||
// 權限遞迴約束驗證
|
||||
if (!auth()->user()->isSystemAdmin()) {
|
||||
$currentUserPerms = auth()->user()->getAllPermissions()->pluck('name');
|
||||
if (collect($perms)->diff($currentUserPerms)->isNotEmpty()) {
|
||||
return redirect()->back()->with('error', __('You cannot assign permissions you do not possess.'));
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是系統角色,排除主選單的系統權限
|
||||
if (!$is_system) {
|
||||
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
|
||||
@@ -188,9 +224,9 @@ class PermissionController extends Controller
|
||||
$per_page = $request->input('per_page', 10);
|
||||
$users = $query->latest()->paginate($per_page)->withQueryString();
|
||||
$companies = auth()->user()->isSystemAdmin() ? \App\Models\System\Company::all() : collect();
|
||||
$roles_query = \App\Models\System\Role::where('name', '!=', 'super-admin');
|
||||
$roles_query = \App\Models\System\Role::query();
|
||||
if (!auth()->user()->isSystemAdmin()) {
|
||||
$roles_query->where('company_id', auth()->user()->company_id);
|
||||
$roles_query->forCompany(auth()->user()->company_id);
|
||||
}
|
||||
$roles = $roles_query->get();
|
||||
|
||||
@@ -231,9 +267,9 @@ class PermissionController extends Controller
|
||||
|
||||
// 驗證角色與公司的匹配性 (RBAC Safeguard)
|
||||
if ($company_id !== null) {
|
||||
// 如果是租戶帳號,不能選各項系統角色 (is_system = 1)
|
||||
if ($role->is_system) {
|
||||
return redirect()->back()->with('error', __('System roles cannot be assigned to tenant accounts.'));
|
||||
// 如果是租戶帳號,不能選超級管理員角色
|
||||
if ($role->is_system && $role->name === 'super-admin') {
|
||||
return redirect()->back()->with('error', __('Super-admin role cannot be assigned to tenant accounts.'));
|
||||
}
|
||||
// 如果角色有特定的 company_id,必須匹配
|
||||
if ($role->company_id !== null && $role->company_id != $company_id) {
|
||||
@@ -247,10 +283,9 @@ class PermissionController extends Controller
|
||||
}
|
||||
|
||||
// 角色初始化與克隆邏輯 (只有 super-admin 在幫空白公司開帳號時觸發)
|
||||
$role_to_assign = $validated['role'];
|
||||
$company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
|
||||
|
||||
if ($company_id && $role && !$role->is_system && $role->company_id === null) {
|
||||
if ($company_id && $role && $role->company_id === null && $role->name !== 'super-admin') {
|
||||
// 檢查該公司是否已有名為「管理員」的角色
|
||||
$existingRole = \App\Models\System\Role::where('company_id', $company_id)
|
||||
->where('name', '管理員')
|
||||
@@ -258,17 +293,17 @@ class PermissionController extends Controller
|
||||
|
||||
if (!$existingRole) {
|
||||
// 克隆範本為該公司的「管理員」
|
||||
$clonedRole = \App\Models\System\Role::query()->create([
|
||||
$newRole = \App\Models\System\Role::query()->create([
|
||||
'name' => '管理員',
|
||||
'guard_name' => 'web',
|
||||
'company_id' => $company_id,
|
||||
'is_system' => false,
|
||||
]);
|
||||
$clonedRole->syncPermissions($role->permissions);
|
||||
$role_to_assign = '管理員';
|
||||
$newRole->syncPermissions($role->getPermissionNames());
|
||||
$role = $newRole;
|
||||
} else {
|
||||
// 如果已存在名為「管理員」的角色,則直接使用它
|
||||
$role_to_assign = '管理員';
|
||||
$role = $existingRole;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,10 +314,10 @@ class PermissionController extends Controller
|
||||
'password' => \Illuminate\Support\Facades\Hash::make($validated['password']),
|
||||
'status' => $validated['status'],
|
||||
'company_id' => $company_id,
|
||||
'phone' => $validated['phone'],
|
||||
'phone' => $validated['phone'] ?? null,
|
||||
]);
|
||||
|
||||
$user->assignRole($role_to_assign);
|
||||
$user->assignRole($role);
|
||||
|
||||
return redirect()->back()->with('success', __('Account created successfully.'));
|
||||
}
|
||||
@@ -325,8 +360,8 @@ class PermissionController extends Controller
|
||||
// 驗證角色與公司的匹配性 (RBAC Safeguard)
|
||||
if ($user->id !== auth()->id()) { // 排除編輯自己 (super-admin 有特殊邏輯)
|
||||
if ($target_company_id !== null) {
|
||||
if ($roleObj->is_system) {
|
||||
return redirect()->back()->with('error', __('System roles cannot be assigned to tenant accounts.'));
|
||||
if ($roleObj->is_system && $roleObj->name === 'super-admin') {
|
||||
return redirect()->back()->with('error', __('Super-admin role cannot be assigned to tenant accounts.'));
|
||||
}
|
||||
if ($roleObj->company_id !== null && $roleObj->company_id != $target_company_id) {
|
||||
return redirect()->back()->with('error', __('This role belongs to another company and cannot be assigned.'));
|
||||
@@ -343,7 +378,7 @@ class PermissionController extends Controller
|
||||
'username' => $validated['username'],
|
||||
'email' => $validated['email'],
|
||||
'status' => $validated['status'],
|
||||
'phone' => $validated['phone'],
|
||||
'phone' => $validated['phone'] ?? null,
|
||||
];
|
||||
|
||||
if (auth()->user()->isSystemAdmin()) {
|
||||
@@ -361,26 +396,25 @@ class PermissionController extends Controller
|
||||
}
|
||||
|
||||
// 角色初始化與克隆邏輯
|
||||
$role_to_assign = $validated['role'];
|
||||
$target_company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
|
||||
|
||||
if ($target_company_id && $roleObj && !$roleObj->is_system && $roleObj->company_id === null) {
|
||||
if ($target_company_id && $roleObj && $roleObj->company_id === null && $roleObj->name !== 'super-admin') {
|
||||
// 檢查該公司是否已有名為「管理員」的角色
|
||||
$existingRole = \App\Models\System\Role::where('company_id', $target_company_id)
|
||||
->where('name', '管理員')
|
||||
->first();
|
||||
|
||||
if (!$existingRole) {
|
||||
$clonedRole = \App\Models\System\Role::query()->create([
|
||||
$newRole = \App\Models\System\Role::query()->create([
|
||||
'name' => '管理員',
|
||||
'guard_name' => 'web',
|
||||
'company_id' => $target_company_id,
|
||||
'is_system' => false,
|
||||
]);
|
||||
$clonedRole->syncPermissions($roleObj->permissions);
|
||||
$role_to_assign = '管理員';
|
||||
$newRole->syncPermissions($roleObj->getPermissionNames());
|
||||
$roleObj = $newRole;
|
||||
} else {
|
||||
$role_to_assign = '管理員';
|
||||
$roleObj = $existingRole;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +424,7 @@ class PermissionController extends Controller
|
||||
if ($user->id === auth()->id() && auth()->user()->isSystemAdmin()) {
|
||||
$user->syncRoles(['super-admin']);
|
||||
} else {
|
||||
$user->syncRoles([$role_to_assign]);
|
||||
$user->syncRoles([$roleObj]);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Account updated successfully.'));
|
||||
|
||||
@@ -24,6 +24,6 @@ class PasswordController extends Controller
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
return back()->with('status', 'password-updated');
|
||||
return back()->with('success', __('Password updated successfully.'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class ProfileController extends Controller
|
||||
|
||||
$user->save();
|
||||
|
||||
return Redirect::route('profile.edit')->with('status', 'profile-updated');
|
||||
return Redirect::route('profile.edit')->with('success', __('Profile updated successfully.'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,10 @@ class Role extends SpatieRole
|
||||
'is_system',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_system' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the company that owns the role.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user