feat: 完成權限管理系統、統一頁面標題樣式與表格對齊規範

This commit is contained in:
2026-01-13 13:30:51 +08:00
parent 6770a4ec2f
commit ecfcbb93ed
28 changed files with 2333 additions and 34 deletions

View File

@@ -0,0 +1,170 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Inertia\Inertia;
use Illuminate\Validation\Rule;
class RoleController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$roles = Role::withCount('users', 'permissions')
->orderBy('id')
->get();
return Inertia::render('Admin/Role/Index', [
'roles' => $roles
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$permissions = $this->getGroupedPermissions();
return Inertia::render('Admin/Role/Create', [
'groupedPermissions' => $permissions
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255', 'unique:roles,name'],
'permissions' => ['array'],
'permissions.*' => ['exists:permissions,name']
]);
$role = Role::create(['name' => $validated['name']]);
if (!empty($validated['permissions'])) {
$role->syncPermissions($validated['permissions']);
}
return redirect()->route('roles.index')->with('success', '角色建立成功');
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$role = Role::with('permissions')->findOrFail($id);
// 禁止編輯超級管理員角色
if ($role->name === 'super-admin') {
return redirect()->route('roles.index')->with('error', '超級管理員角色不可編輯');
}
$groupedPermissions = $this->getGroupedPermissions();
$currentPermissions = $role->permissions->pluck('name')->toArray();
return Inertia::render('Admin/Role/Edit', [
'role' => $role,
'groupedPermissions' => $groupedPermissions,
'currentPermissions' => $currentPermissions
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
$role = Role::findOrFail($id);
if ($role->name === 'super-admin') {
return redirect()->route('roles.index')->with('error', '超級管理員角色不可變更');
}
$validated = $request->validate([
'name' => ['required', 'string', 'max:255', Rule::unique('roles', 'name')->ignore($role->id)],
'permissions' => ['array'],
'permissions.*' => ['exists:permissions,name']
]);
$role->update(['name' => $validated['name']]);
if (isset($validated['permissions'])) {
$role->syncPermissions($validated['permissions']);
}
return redirect()->route('roles.index')->with('success', '角色更新成功');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
$role = Role::withCount('users')->findOrFail($id);
if ($role->name === 'super-admin') {
return back()->with('error', '超級管理員角色不可刪除');
}
if ($role->users_count > 0) {
return back()->with('error', "尚有 {$role->users_count} 位使用者屬於此角色,無法刪除");
}
$role->delete();
return redirect()->route('roles.index')->with('success', '角色已刪除');
}
/**
* 取得並分組權限
*/
private function getGroupedPermissions()
{
$allPermissions = Permission::orderBy('name')->get();
$grouped = [];
foreach ($allPermissions as $permission) {
// 假設命名格式為 group.action (例如 products.create)
$parts = explode('.', $permission->name);
$group = $parts[0];
if (!isset($grouped[$group])) {
$grouped[$group] = [];
}
$grouped[$group][] = $permission;
}
// 翻譯群組名稱 (可選,優化顯示)
$groupNames = [
'products' => '商品資料管理',
'vendors' => '廠商資料管理',
'purchase_orders' => '採購單管理',
'warehouses' => '倉庫管理',
'inventory' => '庫存管理',
'users' => '使用者管理',
'roles' => '角色權限管理',
];
$result = [];
foreach ($grouped as $key => $permissions) {
$result[] = [
'key' => $key,
'name' => $groupNames[$key] ?? ucfirst($key),
'permissions' => $permissions
];
}
return $result;
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use Inertia\Inertia;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Hash;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$users = User::with('roles')
->orderBy('id')
->paginate(10); // 分頁
return Inertia::render('Admin/User/Index', [
'users' => $users
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$roles = Role::pluck('name', 'id');
return Inertia::render('Admin/User/Create', [
'roles' => $roles
]);
}
/**
* Store a newly created resource in storage.
*/
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'],
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'username' => $validated['username'],
'password' => Hash::make($validated['password']),
]);
if (!empty($validated['roles'])) {
$user->syncRoles($validated['roles']);
}
return redirect()->route('users.index')->with('success', '使用者建立成功');
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$user = User::with('roles')->findOrFail($id);
$roles = Role::get(['id', 'name']);
return Inertia::render('Admin/User/Edit', [
'user' => $user,
'roles' => $roles,
'currentRoles' => $user->getRoleNames()
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
$user = User::findOrFail($id);
$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'],
]);
$userData = [
'name' => $validated['name'],
'email' => $validated['email'],
'username' => $validated['username'],
];
if (!empty($validated['password'])) {
$userData['password'] = Hash::make($validated['password']);
}
$user->update($userData);
if (isset($validated['roles'])) {
$user->syncRoles($validated['roles']);
}
return redirect()->route('users.index')->with('success', '使用者更新成功');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
$user = User::findOrFail($id);
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', '使用者已刪除');
}
}

View File

@@ -35,10 +35,20 @@ class HandleInertiaRequests extends Middleware
*/
public function share(Request $request): array
{
$user = $request->user();
return [
...parent::share($request),
'auth' => [
'user' => $request->user(),
'user' => $user ? [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'username' => $user->username ?? null,
// 權限資料
'roles' => $user->getRoleNames(),
'permissions' => $user->getAllPermissions()->pluck('name')->toArray(),
] : null,
],
'flash' => [
'success' => $request->session()->get('success'),

View File

@@ -6,11 +6,12 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
use HasFactory, Notifiable, HasRoles;
/**
* The attributes that are mass assignable.