實作 User,Role 功能介面
This commit is contained in:
parent
bc5e75f151
commit
945185a7e9
104
app/Livewire/Admin/Roles.php
Normal file
104
app/Livewire/Admin/Roles.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
|
|
||||||
|
class Roles extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
public $users ;
|
||||||
|
public $search = '';
|
||||||
|
public $name = '';
|
||||||
|
public bool $showCreateModal = false;
|
||||||
|
public ?int $editingRoleId = null;
|
||||||
|
public string $sortField = 'id';
|
||||||
|
public string $sortDirection = 'asc';
|
||||||
|
public $permissions = []; // 所有權限清單
|
||||||
|
public $selectedPermissions = []; // 表單中選到的權限
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $paginationTheme = 'tailwind';
|
||||||
|
|
||||||
|
public function getRolesProperty()
|
||||||
|
{
|
||||||
|
return Role::where('name', 'like', "%{$this->search}%")
|
||||||
|
->orderBy($this->sortField, $this->sortDirection)
|
||||||
|
->paginate(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sortBy($field)
|
||||||
|
{
|
||||||
|
if ($this->sortField === $field) {
|
||||||
|
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
$this->sortField = $field;
|
||||||
|
$this->sortDirection = 'asc';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->permissions = Permission::all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function openCreateModal()
|
||||||
|
{
|
||||||
|
$this->resetFields();
|
||||||
|
$this->showCreateModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function openEditModal($id)
|
||||||
|
{
|
||||||
|
$role = Role::findOrFail($id);
|
||||||
|
$this->editingRoleId = $role->id;
|
||||||
|
$this->name = $role->name;
|
||||||
|
$this->selectedPermissions = $role->permissions()->pluck('id')->toArray();
|
||||||
|
$this->showCreateModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
if ($this->editingRoleId) {
|
||||||
|
$role = Role::findOrFail($this->editingRoleId);
|
||||||
|
$role->update(['name' => $this->name]);
|
||||||
|
$role->syncPermissions($this->selectedPermissions);
|
||||||
|
session()->flash('message', '角色已更新');
|
||||||
|
} else {
|
||||||
|
$role = Role::create(['name' => $this->name]);
|
||||||
|
$role->syncPermissions($this->selectedPermissions);
|
||||||
|
session()->flash('message', '角色已新增');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resetFields();
|
||||||
|
$this->showCreateModal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($id)
|
||||||
|
{
|
||||||
|
Role::findOrFail($id)->delete();
|
||||||
|
session()->flash('message', '角色已刪除');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetFields()
|
||||||
|
{
|
||||||
|
$this->name = '';
|
||||||
|
$this->selectedPermissions = [];
|
||||||
|
$this->editingRoleId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.admin.roles', [
|
||||||
|
'roles' => $this->roles,
|
||||||
|
])->layout('layouts.admin');
|
||||||
|
}
|
||||||
|
}
|
104
app/Livewire/Admin/Users.php
Normal file
104
app/Livewire/Admin/Users.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
use App\Models\User;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
class Users extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
|
||||||
|
public $search = '';
|
||||||
|
public $name = '';
|
||||||
|
public bool $showCreateModal = false;
|
||||||
|
public ?int $editingUserId = null;
|
||||||
|
public string $sortField = 'id';
|
||||||
|
public string $sortDirection = 'asc';
|
||||||
|
public $roles = []; // 所有權限清單
|
||||||
|
public $selectedRoles = []; // 表單中選到的權限
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $paginationTheme = 'tailwind';
|
||||||
|
|
||||||
|
public function getUsersProperty()
|
||||||
|
{
|
||||||
|
return User::where('name', 'like', "%{$this->search}%")
|
||||||
|
->orderBy($this->sortField, $this->sortDirection)
|
||||||
|
->paginate(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sortBy($field)
|
||||||
|
{
|
||||||
|
if ($this->sortField === $field) {
|
||||||
|
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
$this->sortField = $field;
|
||||||
|
$this->sortDirection = 'asc';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->roles = Role::all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function openCreateModal()
|
||||||
|
{
|
||||||
|
$this->resetFields();
|
||||||
|
$this->showCreateModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function openEditModal($id)
|
||||||
|
{
|
||||||
|
$user = User::findOrFail($id);
|
||||||
|
$this->editingUserId = $user->id;
|
||||||
|
$this->name = $user->name;
|
||||||
|
$this->selectedRoles = $user->roles()->pluck('id')->toArray();
|
||||||
|
$this->showCreateModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
if ($this->editingRoleId) {
|
||||||
|
$role = User::findOrFail($this->editingRoleId);
|
||||||
|
$role->update(['name' => $this->name]);
|
||||||
|
$role->syncRolses($this->selectedRoles);
|
||||||
|
session()->flash('message', '使用者已更新');
|
||||||
|
} else {
|
||||||
|
$role = User::create(['name' => $this->name]);
|
||||||
|
$role->syncRolses($this->selectedRoles);
|
||||||
|
session()->flash('message', '使用者已新增');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resetFields();
|
||||||
|
$this->showCreateModal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($id)
|
||||||
|
{
|
||||||
|
User::findOrFail($id)->delete();
|
||||||
|
session()->flash('message', '使用者已刪除');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetFields()
|
||||||
|
{
|
||||||
|
$this->name = '';
|
||||||
|
$this->selectedRoles = [];
|
||||||
|
$this->editingUserId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.admin.users', [
|
||||||
|
'users' => $this->users,
|
||||||
|
])->layout('layouts.admin');
|
||||||
|
}
|
||||||
|
}
|
26
app/View/Components/Table.php
Normal file
26
app/View/Components/Table.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class Table extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new component instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represent the component.
|
||||||
|
*/
|
||||||
|
public function render(): View|Closure|string
|
||||||
|
{
|
||||||
|
return view('components.table');
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,10 @@ class PermissionTableSeeder extends Seeder
|
|||||||
'role-create',
|
'role-create',
|
||||||
'role-edit',
|
'role-edit',
|
||||||
'role-delete',
|
'role-delete',
|
||||||
|
'user-list',
|
||||||
|
'user-create',
|
||||||
|
'user-edit',
|
||||||
|
'user-delete',
|
||||||
'product-list',
|
'product-list',
|
||||||
'product-create',
|
'product-create',
|
||||||
'product-edit',
|
'product-edit',
|
||||||
|
17
resources/lang/zh-TW/roles.php
Normal file
17
resources/lang/zh-TW/roles.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'list' => '角色列表',
|
||||||
|
'CreateNewRole' => '新增角色',
|
||||||
|
'edit' => '編輯',
|
||||||
|
'delete' => '刪除',
|
||||||
|
|
||||||
|
'no' => '編號',
|
||||||
|
'name' => '名稱',
|
||||||
|
|
||||||
|
'create' => '新增',
|
||||||
|
'action' => '操作',
|
||||||
|
'view' => '查看',
|
||||||
|
'submit' => '提交',
|
||||||
|
'cancel' => '取消',
|
||||||
|
];
|
17
resources/lang/zh-TW/users.php
Normal file
17
resources/lang/zh-TW/users.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'list' => '使用者列表',
|
||||||
|
'CreateNewRole' => '新增使用者',
|
||||||
|
'edit' => '編輯',
|
||||||
|
'delete' => '刪除',
|
||||||
|
|
||||||
|
'no' => '編號',
|
||||||
|
'name' => '名稱',
|
||||||
|
|
||||||
|
'create' => '新增',
|
||||||
|
'action' => '操作',
|
||||||
|
'view' => '查看',
|
||||||
|
'submit' => '提交',
|
||||||
|
'cancel' => '取消',
|
||||||
|
];
|
27
resources/views/components/table.blade.php
Normal file
27
resources/views/components/table.blade.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
@php
|
||||||
|
$classes = [
|
||||||
|
'table-auto w-full text-left',
|
||||||
|
$attributes->get('bordered', true) ? 'border border-gray-200' : '',
|
||||||
|
$attributes->get('striped') ? 'divide-y divide-gray-100' : '',
|
||||||
|
$attributes->get('size') === 'sm' ? 'text-sm' : 'text-base',
|
||||||
|
];
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table {{ $attributes->except(['bordered', 'striped', 'size'])->merge(['class' => implode(' ', $classes)]) }}>
|
||||||
|
@isset($header)
|
||||||
|
<thead class="bg-gray-100 text-gray-700">
|
||||||
|
{{ $header }}
|
||||||
|
</thead>
|
||||||
|
@endisset
|
||||||
|
<tbody>
|
||||||
|
{{ $slot }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
@isset($footer)
|
||||||
|
<div class="mt-2">
|
||||||
|
{{ $footer }}
|
||||||
|
</div>
|
||||||
|
@endisset
|
||||||
|
</div>
|
@ -14,7 +14,7 @@
|
|||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
@livewireStyles
|
@livewireStyles
|
||||||
<wireui:scripts />
|
@wireUiScripts
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 text-gray-800">
|
<body class="bg-gray-100 text-gray-800">
|
||||||
@auth
|
@auth
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
@livewireStyles
|
@livewireStyles
|
||||||
|
@wireUiScripts
|
||||||
</head>
|
</head>
|
||||||
<body class="font-sans antialiased">
|
<body class="font-sans antialiased">
|
||||||
<div class="min-h-screen bg-gray-100">
|
<div class="min-h-screen bg-gray-100">
|
||||||
|
66
resources/views/livewire/admin/roles.blade.php
Normal file
66
resources/views/livewire/admin/roles.blade.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-xl font-bold">{{ __('roles.list') }}</h2>
|
||||||
|
<x-button primary label="{{__('roles.CreateNewRole')}}" wire:click="openCreateModal" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<x-input wire:model.debounce.300ms="search" placeholder="搜尋角色名稱..." class="w-64" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<x-table divider="thin" has_shadow="true" celled="true">
|
||||||
|
<x-slot name="header">
|
||||||
|
<th class="cursor-pointer" wire:click="sortBy('id')">{{__('roles.no')}}</th>
|
||||||
|
<th class="cursor-pointer" wire:click="sortBy('name')">{{__('roles.name')}}</th>
|
||||||
|
<th>{{__('roles.action')}}</th>
|
||||||
|
</x-slot>
|
||||||
|
@forelse ($roles as $role)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $role->id }}</td>
|
||||||
|
<td>{{ $role->name }}</td>
|
||||||
|
<td class="space-x-2">
|
||||||
|
@can('role-edit')
|
||||||
|
<x-button flat label="{{ __('roles.edit') }}" color="yellow" icon="pencil-square" wire:click="openEditModal({{ $role->id }})" />
|
||||||
|
@endcan
|
||||||
|
@can('role-delete')
|
||||||
|
<x-button flat label="{{ __('roles.delete') }}" color="red" icon="trash" wire:click="delete({{ $role->id }})" />
|
||||||
|
@endcan
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">{{ __('No roles found.') }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</x-table>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
{!! $roles->links('pagination::tailwind') !!}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($showCreateModal)
|
||||||
|
<x-modal-card title="{{ $editingRoleId ? '編輯角色' : '新增角色' }}" blur wire:model.defer="showCreateModal">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<x-input label="角色名稱" wire:model.defer="name" />
|
||||||
|
|
||||||
|
<x-select
|
||||||
|
label="權限"
|
||||||
|
wire:model.defer="selectedPermissions"
|
||||||
|
placeholder="選擇權限"
|
||||||
|
multiselect
|
||||||
|
option-label="label"
|
||||||
|
option-value="value"
|
||||||
|
:options="$permissions->map(fn($p) => ['value' => $p->id, 'label' => $p->name])->toArray()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<x-slot name="footer">
|
||||||
|
<div class="flex justify-center gap-2">
|
||||||
|
<x-button primary label="{{__('roles.cancel')}}" x-on:click="$dispatch('close')" />
|
||||||
|
<x-button primary label="{{__('roles.submit')}}" wire:click="save" />
|
||||||
|
</div>
|
||||||
|
</x-slot>
|
||||||
|
</x-modal-card>
|
||||||
|
@endif
|
||||||
|
</div>
|
65
resources/views/livewire/admin/users.blade.php
Normal file
65
resources/views/livewire/admin/users.blade.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-xl font-bold">{{ __('users.list') }}</h2>
|
||||||
|
<x-button primary label="{{__('users.CreateNewRole')}}" wire:click="openCreateModal" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<x-input wire:model.debounce.300ms="search" placeholder="搜尋使用者名稱..." class="w-64" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<x-table divider="thin" has_shadow="true" celled="true">
|
||||||
|
<x-slot name="header">
|
||||||
|
<th class="cursor-pointer" wire:click="sortBy('id')">{{__('users.no')}}</th>
|
||||||
|
<th class="cursor-pointer" wire:click="sortBy('name')">{{__('users.name')}}</th>
|
||||||
|
<th>{{__('users.action')}}</th>
|
||||||
|
</x-slot>
|
||||||
|
@forelse ($users as $user)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $user->id }}</td>
|
||||||
|
<td>{{ $user->name }}</td>
|
||||||
|
<td class="space-x-2">
|
||||||
|
@can('role-edit')
|
||||||
|
<x-button flat label="{{ __('users.edit') }}" color="yellow" icon="pencil-square" wire:click="openEditModal({{ $user->id }})" />
|
||||||
|
@endcan
|
||||||
|
@can('role-delete')
|
||||||
|
<x-button flat label="{{ __('users.delete') }}" color="red" icon="trash" wire:click="delete({{ $user->id }})" />
|
||||||
|
@endcan
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">{{ __('No users found.') }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</x-table>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
{!! $users->links('pagination::tailwind') !!}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($showCreateModal)
|
||||||
|
<x-modal-card title="{{ $editingUserId ? '編輯使用者' : '新增使用者' }}" blur wire:model.defer="showCreateModal">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<x-input label="使用者名稱" wire:model.defer="name" />
|
||||||
|
|
||||||
|
<x-select
|
||||||
|
label="角色"
|
||||||
|
wire:model.defer="selectedRoles"
|
||||||
|
placeholder="選擇角色"
|
||||||
|
option-label="label"
|
||||||
|
option-value="value"
|
||||||
|
:options="$roles->map(fn($p) => ['value' => $p->id, 'label' => $p->name])->toArray()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<x-slot name="footer">
|
||||||
|
<div class="flex justify-center gap-2">
|
||||||
|
<x-button primary label="{{__('users.cancel')}}" x-on:click="$dispatch('close')" />
|
||||||
|
<x-button primary label="{{__('users.submit')}}" wire:click="save" />
|
||||||
|
</div>
|
||||||
|
</x-slot>
|
||||||
|
</x-modal-card>
|
||||||
|
@endif
|
||||||
|
</div>
|
@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
$menus = [
|
$menus = [
|
||||||
['label' => 'Dashboard', 'route' => 'admin.dashboard', 'icon' => 'home', 'permission' => null],
|
['label' => 'Dashboard', 'route' => 'admin.dashboard', 'icon' => 'home', 'permission' => null],
|
||||||
|
['label' => 'Role', 'route' => 'admin.roles', 'icon' => 'user-circle', 'permission' => 'role-list'],
|
||||||
|
['label' => 'User', 'route' => 'admin.users', 'icon' => 'user-circle', 'permission' => 'user-list'],
|
||||||
];
|
];
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
use App\Livewire\Admin\Dashboard as AdminDashboard;
|
use App\Livewire\Admin\Dashboard as AdminDashboard;
|
||||||
|
use App\Livewire\Admin\Roles;
|
||||||
|
use App\Livewire\Admin\Users;
|
||||||
|
|
||||||
Route::view('/', 'welcome');
|
Route::view('/', 'welcome');
|
||||||
|
|
||||||
@ -18,4 +20,6 @@ require __DIR__.'/auth.php';
|
|||||||
|
|
||||||
Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () {
|
Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () {
|
||||||
Route::get('/dashboard', AdminDashboard::class)->name('dashboard');
|
Route::get('/dashboard', AdminDashboard::class)->name('dashboard');
|
||||||
|
Route::get('/roles', Roles::class)->name('roles');
|
||||||
|
Route::get('/users', Users::class)->name('users');
|
||||||
});
|
});
|
Loading…
x
Reference in New Issue
Block a user