[FEAT] 實作角色權限分類、租戶角控管理與介面多語系優化

1. [FEAT] 權限劃分為「系統層級」與「客戶層級」,並在後端強制過濾跨權限分配。
2. [FEAT] 整合選單權限至主選單層級 (基本設定、權限設定),簡化角色管理 UI。
3. [STYLE] 側邊欄優化:補齊多語系翻譯,並為基本設定子選單增加視覺圖示。
4. [REFACTOR] 更新 RoleSeeder,將 tenant-admin 重新分類為客戶層級角色。
This commit is contained in:
2026-03-17 16:53:28 +08:00
parent 3ce88ed342
commit fc79148879
38 changed files with 2398 additions and 303 deletions

View File

@@ -11,15 +11,46 @@ class PermissionController extends Controller
public function roles()
{
$per_page = request()->input('per_page', 10);
$roles = \Spatie\Permission\Models\Role::with(['permissions', 'users'])->latest()->paginate($per_page)->withQueryString();
$all_permissions = \Spatie\Permission\Models\Permission::all()->groupBy(function($perm) {
if (str_starts_with($perm->name, 'menu.')) {
return 'menu';
}
return 'other';
});
$user = auth()->user();
$query = \App\Models\System\Role::query()->with(['permissions', 'users']);
// 租戶隔離:租戶只能看到自己公司的角色 + 系統角色 (company_id is null)
if (!$user->isSystemAdmin()) {
$query->where(function($q) use ($user) {
$q->where('company_id', $user->company_id)
->orWhereNull('company_id');
});
}
// 搜尋:角色名稱
if ($search = request()->input('search')) {
$query->where('name', 'like', "%{$search}%");
}
$roles = $query->latest()->paginate($per_page)->withQueryString();
$all_permissions = \Spatie\Permission\Models\Permission::all()
->filter(function($perm) {
// 排除子項目的權限,只顯示主選單權限
$excluded = [
'menu.basic.machines',
'menu.basic.payment-configs',
'menu.companies',
'menu.accounts',
'menu.roles',
];
return !in_array($perm->name, $excluded);
})
->groupBy(function($perm) {
if (str_starts_with($perm->name, 'menu.')) {
return 'menu';
}
return 'other';
});
return view('admin.permission.roles', compact('roles', 'all_permissions'));
// 根據路由決定標題
$title = request()->routeIs('*.sub-account-roles') ? __('Sub Account Roles') : __('Role Settings');
return view('admin.permission.roles', compact('roles', 'all_permissions', 'title'));
}
/**
@@ -33,14 +64,22 @@ class PermissionController extends Controller
'permissions.*' => 'string|exists:permissions,name',
]);
$role = \Spatie\Permission\Models\Role::create([
$is_system = auth()->user()->isSystemAdmin() && $request->boolean('is_system');
$role = \App\Models\System\Role::create([
'name' => $validated['name'],
'guard_name' => 'web',
'is_system' => false,
'company_id' => $is_system ? null : auth()->user()->company_id,
'is_system' => $is_system,
]);
if (!empty($validated['permissions'])) {
$role->syncPermissions($validated['permissions']);
$perms = $validated['permissions'];
// 如果不是系統角色,排除主選單的系統權限
if (!$is_system) {
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
}
$role->syncPermissions($perms);
}
return redirect()->back()->with('success', __('Role created successfully.'));
@@ -51,7 +90,7 @@ class PermissionController extends Controller
*/
public function updateRole(Request $request, $id)
{
$role = \Spatie\Permission\Models\Role::findOrFail($id);
$role = \App\Models\System\Role::findOrFail($id);
$validated = $request->validate([
'name' => 'required|string|max:255|unique:roles,name,' . $id,
@@ -59,11 +98,28 @@ class PermissionController extends Controller
'permissions.*' => 'string|exists:permissions,name',
]);
if (!$role->is_system) {
$role->update(['name' => $validated['name']]);
if ($role->name === 'super-admin') {
return redirect()->back()->with('error', __('The Super Admin role is immutable.'));
}
$role->syncPermissions($validated['permissions'] ?? []);
if (!auth()->user()->isSystemAdmin() && $role->is_system) {
return redirect()->back()->with('error', __('System roles cannot be modified by tenant administrators.'));
}
$is_system = auth()->user()->isSystemAdmin() ? $request->boolean('is_system') : $role->is_system;
$role->update([
'name' => $validated['name'],
'is_system' => $is_system,
'company_id' => $is_system ? null : $role->company_id,
]);
$perms = $validated['permissions'] ?? [];
// 如果不是系統角色,排除主選單的系統權限
if (!$is_system) {
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
}
$role->syncPermissions($perms);
return redirect()->back()->with('success', __('Role updated successfully.'));
}
@@ -73,10 +129,14 @@ class PermissionController extends Controller
*/
public function destroyRole($id)
{
$role = \Spatie\Permission\Models\Role::findOrFail($id);
$role = \App\Models\System\Role::findOrFail($id);
if ($role->is_system) {
return redirect()->back()->with('error', __('System roles cannot be deleted.'));
if ($role->name === 'super-admin') {
return redirect()->back()->with('error', __('The Super Admin role cannot be deleted.'));
}
if (!auth()->user()->isSystemAdmin() && $role->is_system) {
return redirect()->back()->with('error', __('System roles cannot be deleted by tenant administrators.'));
}
if ($role->users()->count() > 0) {
@@ -115,9 +175,19 @@ 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 = \Spatie\Permission\Models\Role::all();
$roles_query = \App\Models\System\Role::where('name', '!=', 'super-admin');
if (!auth()->user()->isSystemAdmin()) {
$roles_query->where(function($q) {
$q->where('company_id', auth()->user()->company_id)
->orWhereNull('company_id');
});
}
$roles = $roles_query->get();
return view('admin.data-config.accounts', compact('users', 'companies', 'roles'));
// 根據路由決定標題
$title = request()->routeIs('*.sub-accounts') ? __('Sub Account Management') : __('Account Management');
return view('admin.data-config.accounts', compact('users', 'companies', 'roles', 'title'));
}
/**
@@ -158,6 +228,10 @@ class PermissionController extends Controller
{
$user = \App\Models\System\User::findOrFail($id);
if ($user->hasRole('super-admin')) {
return redirect()->back()->with('error', __('System super admin accounts cannot be modified via this interface.'));
}
$validated = $request->validate([
'name' => 'required|string|max:255',
'username' => 'required|string|max:255|unique:users,username,' . $id,
@@ -178,7 +252,13 @@ class PermissionController extends Controller
];
if (auth()->user()->isSystemAdmin()) {
$updateData['company_id'] = $validated['company_id'];
// 防止超級管理員不小心把自己綁定到租客公司或降級
if ($user->id === auth()->id()) {
$updateData['company_id'] = null;
$validated['role'] = 'super-admin';
} else {
$updateData['company_id'] = $validated['company_id'];
}
}
if (!empty($validated['password'])) {
@@ -187,7 +267,12 @@ class PermissionController extends Controller
$user->update($updateData);
$user->syncRoles([$validated['role']]);
// 如果是編輯自己且原本是超級管理員,強制保留 super-admin 角色
if ($user->id === auth()->id() && auth()->user()->isSystemAdmin()) {
$user->syncRoles(['super-admin']);
} else {
$user->syncRoles([$validated['role']]);
}
return redirect()->back()->with('success', __('Account updated successfully.'));
}
@@ -199,6 +284,10 @@ class PermissionController extends Controller
{
$user = \App\Models\System\User::findOrFail($id);
if ($user->hasRole('super-admin')) {
return redirect()->back()->with('error', __('System super admin accounts cannot be deleted.'));
}
if ($user->id === auth()->id()) {
return redirect()->back()->with('error', __('You cannot delete your own account.'));
}