加入 user:phone,birthday,gender,status.

功能調整 20250429
This commit is contained in:
allen.yan 2025-04-29 14:39:36 +08:00
parent b48d55197b
commit a8caec12e6
15 changed files with 253 additions and 170 deletions

BIN
.DS_Store vendored

Binary file not shown.

27
app/Enums/UserGender.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace App\Enums;
enum UserGender: string
{
case Male = 'male';
case Female = 'female';
case Other = 'other';
case Unset = 'unset';
// 返回對應的顯示文字
public function labels(): string
{
return match($this) {
self::Male => __('enums.user.gender.Male'),
self::Female => __('enums.user.gender.Female'),
self::Other => __('enums.user.gender.Other'),
self::Unset => __('enums.user.gender.Unset'),
};
}
public function labelPowergridFilter(): String
{
return $this -> labels();
}
}

25
app/Enums/UserStatus.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace App\Enums;
enum UserStatus: int
{
case Active = 0; // 正常
case Suspended = 1; // 停權
case Deleting = 2; // 刪除中
// 返回對應的顯示文字
public function labels(): string
{
return match($this) {
self::Active => __('enums.user.status.Active'),
self::Suspended => __('enums.user.status.Suspended'),
self::Deleting => __('enums.user.status.Deleting'),
};
}
public function labelPowergridFilter(): String
{
return $this -> labels();
}
}

View File

@ -50,17 +50,16 @@ final class RoleTable extends PowerGridComponent
return PowerGrid::fields() return PowerGrid::fields()
->add('id') ->add('id')
->add('name') ->add('name')
->add('permissions_list' ,fn(Role $model)=> $model->permissions->pluck('name')->implode(', '))
->add('created_at_formatted', fn (Role $model) => Carbon::parse($model->created_at)->format('d/m/Y H:i:s')); ->add('created_at_formatted', fn (Role $model) => Carbon::parse($model->created_at)->format('d/m/Y H:i:s'));
} }
public function columns(): array public function columns(): array
{ {
return [ return [
Column::make('ID', 'id')->sortable()->searchable(), Column::make(__('roles.no'), 'id')->sortable()->searchable(),
Column::make('名稱', 'name')->sortable()->searchable(), Column::make(__('roles.name'), 'name')->sortable()->searchable(),
//Column::make('權限', 'permissions_list', function ($role) { Column::make(__('roles.permissions'), 'permissions_list'),
// return $role->permissions->pluck('name')->implode(', ');
//}),
Column::make('Created at', 'created_at_formatted', 'created_at')->sortable(), Column::make('Created at', 'created_at_formatted', 'created_at')->sortable(),
Column::action('Action') Column::action('Action')
]; ];
@ -78,12 +77,12 @@ final class RoleTable extends PowerGridComponent
{ {
return [ return [
Button::add('edit') Button::add('edit')
->slot('編輯') ->slot(__('roles.edit'))
->icon('solid-pencil-square') ->icon('solid-pencil-square')
->class('inline-flex items-center gap-1 px-3 py-1 rounded ') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
->dispatchTo('admin.role-form', 'openEditRoleModal', ['id' => $row->id]), ->dispatchTo('admin.role-form', 'openEditRoleModal', ['id' => $row->id]),
Button::add('delete') Button::add('delete')
->slot('刪除') ->slot(__('delete'))
->icon('solid-trash') ->icon('solid-trash')
->class('inline-flex items-center gap-1 px-3 py-1 rounded ') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
->dispatchTo('admin.role-form', 'deleteRole', ['id' => $row->id]), ->dispatchTo('admin.role-form', 'deleteRole', ['id' => $row->id]),

View File

@ -1,104 +0,0 @@
<?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,
]);
}
}

View File

@ -5,6 +5,8 @@ namespace App\Livewire\Admin;
use Livewire\Component; use Livewire\Component;
use App\Models\User; use App\Models\User;
use App\Enums\UserGender;
use App\Enums\UserStatus;
use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Role;
class UserForm extends Component class UserForm extends Component
@ -12,20 +14,44 @@ class UserForm extends Component
protected $listeners = ['openCreateUserModal','openEditUserModal', 'deleteUser','bulkDeleteUser']; protected $listeners = ['openCreateUserModal','openEditUserModal', 'deleteUser','bulkDeleteUser'];
public bool $showCreateModal = false; public bool $showCreateModal = false;
public ?int $userId = null;
public $name; public array $genderOptions =[];
public $email; public array $statusOptions =[];
public $roles = []; // 所有角色清單 public $rolesOptions = []; // 所有角色清單
public $selectedRoles = []; // 表單中選到的權限 public $selectedRoles = []; // 表單中選到的權限
public ?int $userId = null;
public array $fields = [
'name' =>'',
'email' => '',
'phone' => '',
'birthday' => '',
'gender' => 'unset',
'status' => 0,
];
protected $rules = [ protected $rules = [
'name' => 'required|string|max:255', 'fields.name' => 'required|string|max:255',
'email' => 'required|string|max:255', 'fields.email' => 'required|string|email|max:255',
'fields.phone' => 'nullable|regex:/^09\d{8}$/',
'fields.birthday' =>'nullable|date',
'fields.gender' => 'required|in:male,female,other,unset',
'fields.status' => 'required|integer|in:0,1,2',
]; ];
public function mount() public function mount()
{ {
$this->roles = Role::all(); $this->fields['birthday'] = now()->toDateString();
$this->genderOptions = collect(UserGender::cases())->map(fn ($gender) => [
'name' => $gender->labels(),
'value' => $gender->value,
])->toArray();
$this->statusOptions = collect(UserStatus::cases())->map(fn ($status) => [
'name' => $status->labels(),
'value' => $status->value,
])->toArray();
$this->rolesOptions = Role::all();
} }
public function openCreateUserModal() public function openCreateUserModal()
@ -38,8 +64,8 @@ class UserForm extends Component
{ {
$user = User::findOrFail($id); $user = User::findOrFail($id);
$this->userId = $user->id; $this->userId = $user->id;
$this->name = $user->name; $this->fields = $user->only(array_keys($this->fields));
$this->email =$user->email;
$this->selectedRoles = $user->roles()->pluck('id')->toArray(); $this->selectedRoles = $user->roles()->pluck('id')->toArray();
$this->showCreateModal = true; $this->showCreateModal = true;
} }
@ -49,24 +75,19 @@ class UserForm extends Component
$this->validate(); $this->validate();
if ($this->userId) { if ($this->userId) {
$role = User::findOrFail($this->userId); $user = User::findOrFail($this->userId);
$role->update([ $user->update($this->fields);
'name' => $this->name, $user->syncRoles($this->selectedRoles);
'email' => $this->email,
]);
$role->syncRolses($this->selectedRoles);
session()->flash('message', '使用者已更新'); session()->flash('message', '使用者已更新');
} else { } else {
$role = User::create([ $user = User::create($this->fields);
'name' => $this->name, $user->syncRoles($this->selectedRoles);
'email' => $this->email,
]);
$role->syncRolses($this->selectedRoles);
session()->flash('message', '使用者已新增'); session()->flash('message', '使用者已新增');
} }
$this->resetFields(); $this->resetFields();
$this->showCreateModal = false; $this->showCreateModal = false;
$this->dispatch('pg:eventRefresh-user-table');
} }
public function deleteUser($id) public function deleteUser($id)
@ -77,10 +98,19 @@ class UserForm extends Component
public function resetFields() public function resetFields()
{ {
$this->name = ''; foreach ($this->fields as $key => $value) {
$this->email = ''; if ($key == 'gender') {
$this->selectedRoles = []; $this->fields[$key] = 'unset';
} elseif ($key == 'status') {
$this->fields[$key] = 0;
} elseif ($key == 'birthday') {
$this->fields[$key] = now()->toDateString();
} else {
$this->fields[$key] = '';
}
}
$this->userId = null; $this->userId = null;
$this->selectedRoles = [];
} }
public function render() public function render()

View File

@ -3,6 +3,8 @@
namespace App\Livewire\Admin; namespace App\Livewire\Admin;
use App\Models\User; use App\Models\User;
use App\Enums\UserGender;
use App\Enums\UserStatus;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use PowerComponents\LivewirePowerGrid\Button; use PowerComponents\LivewirePowerGrid\Button;
@ -11,14 +13,14 @@ use PowerComponents\LivewirePowerGrid\Facades\Filter;
use PowerComponents\LivewirePowerGrid\Facades\PowerGrid; use PowerComponents\LivewirePowerGrid\Facades\PowerGrid;
use PowerComponents\LivewirePowerGrid\PowerGridFields; use PowerComponents\LivewirePowerGrid\PowerGridFields;
use PowerComponents\LivewirePowerGrid\PowerGridComponent; use PowerComponents\LivewirePowerGrid\PowerGridComponent;
use PowerComponents\LivewirePowerGrid\Traits\WithExport; //use PowerComponents\LivewirePowerGrid\Traits\WithExport;
use PowerComponents\LivewirePowerGrid\Components\SetUp\Exportable; use PowerComponents\LivewirePowerGrid\Components\SetUp\Exportable;
use PowerComponents\LivewirePowerGrid\Facades\Rule; use PowerComponents\LivewirePowerGrid\Facades\Rule;
use Livewire\Attributes\On; use Livewire\Attributes\On;
final class UserTable extends PowerGridComponent final class UserTable extends PowerGridComponent
{ {
use WithExport; //use WithExport ;
public string $tableName = 'user-table'; public string $tableName = 'user-table';
@ -71,25 +73,68 @@ final class UserTable extends PowerGridComponent
->add('id') ->add('id')
->add('name') ->add('name')
->add('email') ->add('email')
->add('created_at_formatted', fn (User $model) => Carbon::parse($model->created_at)->format('d/m/Y H:i:s')); ->add('phone')
->add('birthday_formatted',fn (User $model) => Carbon::parse($model->birthday)->format('Y-m-d'))
->add('gender', fn (User $model) => UserGender::from($model->gender)->labels())
->add('status', fn (User $model) => UserStatus::from($model->status)->labels())
->add('roles' ,fn(User $model)=> $model->roles->pluck('name')->implode(', '))
->add('created_at_formatted', fn (User $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s'));
} }
public function columns(): array public function columns(): array
{ {
return [ return [
Column::make('ID', 'id'), Column::make('ID', 'id'),
Column::make('名稱', 'name')->sortable()->searchable(), Column::make(__('users.name'), 'name')
Column::make('Email', 'email')->sortable()->searchable(), ->sortable()
->searchable()
->editOnClick(
hasPermission: true,
dataField: 'name',
fallback: 'N/A',
saveOnMouseOut: true
),
Column::make('Email', 'email')
->sortable()
->searchable()
->editOnClick(
hasPermission: true,
dataField: 'email',
fallback: 'N/A',
saveOnMouseOut: true
),
Column::make(__('users.phone'), 'phone')
->sortable()
->searchable()
->editOnClick(
hasPermission: true,
dataField: 'phone',
fallback: 'N/A',
saveOnMouseOut: true
),
Column::make(__('users.gender'), 'gender','users.gender'),
Column::make(__('users.birthday'), 'birthday_formatted')->sortable()->searchable(),
Column::make(__('users.status'), 'status','users.status'),
Column::make(__('users.role'), 'roles'),
Column::make('建立時間', 'created_at_formatted', 'created_at')->sortable(), Column::make('建立時間', 'created_at_formatted', 'created_at')->sortable(),
Column::action('操作') Column::action('操作')
]; ];
} }
public function filters(): array public function filters(): array
{ {
return [ return [
Filter::inputText('name')->placeholder('Dish Name'), Filter::inputText('name')->placeholder(__('users.name')),
Filter::inputText('email')->placeholder('Dish Email'), Filter::inputText('email')->placeholder('Email'),
Filter::inputText('phone')->placeholder(__('users.phone')),
Filter::enumSelect('gender','users.gender')
->datasource(UserGender::cases())
->optionLabel('users.gender'),
Filter::datepicker('birthday'),
Filter::enumSelect('status', 'users.status')
->datasource(UserStatus::cases())
->optionLabel('users.status'),
Filter::datetimepicker('created_at'), Filter::datetimepicker('created_at'),
]; ];
} }
@ -99,17 +144,36 @@ final class UserTable extends PowerGridComponent
return [ return [
Button::add('edit') Button::add('edit')
->slot('編輯') ->slot(__('users.edit'))
->icon('solid-pencil-square') ->icon('solid-pencil-square')
->class('inline-flex items-center gap-1 px-3 py-1 rounded ') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
->dispatchTo('admin.user-form', 'openEditUserModal', ['id' => $row->id]), ->dispatchTo('admin.user-form', 'openEditUserModal', ['id' => $row->id]),
Button::add('delete') Button::add('delete')
->slot('刪除') ->slot(__('users.delete'))
->icon('solid-trash') ->icon('solid-trash')
->class('inline-flex items-center gap-1 px-3 py-1 rounded ') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
->dispatchTo('admin.user-form', 'deleteUser', ['id' => $row->id]), ->dispatchTo('admin.user-form', 'deleteUser', ['id' => $row->id]),
]; ];
} }
public function onUpdatedEditable($id, $field, $value): void
{
$updated = User::query()->where('id', $id)->update([
$field => $value,
]);
if ($updated) {
$this->fillData();
}
}
public function onUpdatedToggleable($id, $field, $value): void
{
$updated = User::query()->where('id', $id)->update([
$field => $value,
]);
if ($updated) {
$this->fillData();
}
}
#[On('bulkDelete.{tableName}')] #[On('bulkDelete.{tableName}')]
public function bulkDelete(): void public function bulkDelete(): void
{ {

View File

@ -21,6 +21,10 @@ class User extends Authenticatable
protected $fillable = [ protected $fillable = [
'name', 'name',
'email', 'email',
'phone',
'birthday',
'gender',
'status',
'password', 'password',
]; ];
@ -44,6 +48,7 @@ class User extends Authenticatable
return [ return [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'password' => 'hashed', 'password' => 'hashed',
'birthday' => 'date'
]; ];
} }
} }

View File

@ -15,6 +15,10 @@ return new class extends Migration
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->string('email')->unique(); $table->string('email')->unique();
$table->string('phone', 10)->unique();
$table->date('birthday')->nullable(); // 生日
$table->enum('gender', ['unset','male', 'female', 'other'])->default('unset'); // 性別
$table->tinyInteger('status')->default(0); // 啟動
$table->timestamp('email_verified_at')->nullable(); $table->timestamp('email_verified_at')->nullable();
$table->string('password'); $table->string('password');
$table->rememberToken(); $table->rememberToken();

View File

@ -16,12 +16,16 @@ class CreateAdminUserSeeder extends Seeder
$user = User::create([ $user = User::create([
'name' => 'Allen Yan(admin)', 'name' => 'Allen Yan(admin)',
'email' => 'admin@gmail.com', 'email' => 'admin@gmail.com',
'phone' => '0900000000',
'birthday' => now()->toDateString(),
'password' => bcrypt('aa1234') 'password' => bcrypt('aa1234')
]); ]);
$user->assignRole('Admin'); $user->assignRole('Admin');
$user = User::create([ $user = User::create([
'name' => 'Allen Yan(User)', 'name' => 'Allen Yan(User)',
'email' => 'allen.yan@gmail.com', 'email' => 'allen.yan@gmail.com',
'phone' => '0900000001',
'birthday' => now()->toDateString(),
'password' => bcrypt('aa1234') 'password' => bcrypt('aa1234')
]); ]);
$user->assignRole('User'); $user->assignRole('User');

View File

@ -0,0 +1,11 @@
<?php
return [
'user.gender.Male' =>'男',
'user.gender.Female' =>'女',
'user.gender.Other' =>'其他',
'user.gender.Unset' =>'未定義',
'user.status.Active' => '正常',
'user.status.Suspended' => '停權',
'user.status.Deleting' => '刪除中',
];

View File

@ -8,6 +8,7 @@ return [
'no' => '編號', 'no' => '編號',
'name' => '名稱', 'name' => '名稱',
'permissions' => '權限',
'create' => '新增', 'create' => '新增',
'action' => '操作', 'action' => '操作',

View File

@ -8,6 +8,11 @@ return [
'no' => '編號', 'no' => '編號',
'name' => '名稱', 'name' => '名稱',
'phone' => '手機門號',
'gender' => '性別',
'birthday' => '生日',
'status' => '狀態',
'role' =>'角色',
'create' => '新增', 'create' => '新增',
'action' => '操作', 'action' => '操作',

View File

@ -1,28 +1,40 @@
<div class="p-6 space-y-4"> <x-wireui:modal-card title="{{ $userId ? '編輯使用者' : '新增使用者' }}" blur wire:model.defer="showCreateModal">
@if ($showCreateModal) <div class="space-y-4">
<x-wireui:modal-card title="{{ $userId ? '編輯使用者' : '新增使用者' }}" blur wire:model.defer="showCreateModal"> <x-wireui:input label="名稱" wire:model.defer="fields.name" required />
<div class="space-y-4"> <x-wireui:input label="Email" wire:model.defer="fields.email" required />
<x-wireui:input label="名稱" wire:model.defer="name" /> <x-wireui:input label="Phone" wire:model.defer="fields.phone" />
<x-wireui:select
label="性別"
wire:model.defer="fields.gender"
placeholder="選擇性別"
:options="$genderOptions"
option-label="name"
option-value="value"
/>
<x-wireui:select
label="狀態"
wire:model.defer="fields.status"
placeholder="選擇狀態"
:options="$statusOptions"
option-label="name"
option-value="value"
/>
<x-wireui:input label="Email" wire:model.defer="email" /> <x-wireui:select
label="角色"
wire:model.defer="selectedRoles"
placeholder="選擇角色"
multiselect
option-label="label"
option-value="value"
:options="$rolesOptions->map(fn($p) => ['value' => $p->id, 'label' => $p->name])->toArray()"
/>
</div>
<x-wireui:select <x-slot name="footer">
label="角色" <div class="flex justify-center gap-2">
wire:model.defer="selectedRoles" <x-wireui:button primary label="{{__('users.cancel')}}" x-on:click="$dispatch('close')" />
placeholder="選擇角色" <x-wireui:button primary label="{{__('users.submit')}}" wire:click="save" />
multiselect </div>
option-label="label" </x-slot>
option-value="value" </x-wireui:modal-card>
: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-wireui:button primary label="{{__('users.cancel')}}" x-on:click="$dispatch('close')" />
<x-wireui:button primary label="{{__('users.submit')}}" wire:click="save" />
</div>
</x-slot>
</x-wireui:modal-card>
@endif
</div>

BIN
storage/.DS_Store vendored Normal file

Binary file not shown.