From d9924bf05bf591205cee3810444d53189833307a Mon Sep 17 00:00:00 2001 From: "allen.yan" Date: Mon, 5 May 2025 11:22:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5=20=E2=BB=86=E8=89=B2?= =?UTF-8?q?=E6=AC=8A=E9=99=90=E6=8E=A7=E5=88=B6=20=E4=BF=AE=E6=AD=A3=20'n'?= =?UTF-8?q?=20=3D>=20'=E3=84=A3'=20=E8=BD=89=E5=80=BC=E5=95=8F=E9=A1=8C=20?= =?UTF-8?q?=E5=8A=A0=E5=85=A5=20=E6=AD=8C=E6=89=8B=E6=90=9C=E5=B0=8B?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20DB=20=E9=96=8B=20=E9=BB=9E=E6=92=AD?= =?UTF-8?q?=E6=AC=A1=E6=95=B8=E6=AC=84=E4=BD=8D=2020250505?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Enums/ArtistCategory.php | 9 +- app/Enums/SongLanguageType.php | 9 +- app/Enums/SongSituation.php | 8 +- app/Enums/Traits/HasLabels.php | 20 ++ app/Enums/UserGender.php | 8 +- app/Enums/UserStatus.php | 8 +- app/Helpers/ChineseNameConverter.php | 3 +- app/Http/Controllers/ArtistController.php | 30 +++ app/Http/Controllers/Controller.php | 11 +- app/Livewire/Admin/ArtistTable.php | 192 ++++++++------ app/Livewire/Admin/RoleTable.php | 106 ++++++-- app/Livewire/Admin/SongForm.php | 8 +- app/Livewire/Admin/SongTable.php | 248 ++++++++---------- app/Livewire/Admin/UserTable.php | 181 ++++++++----- app/Models/Song.php | 3 +- app/Models/User.php | 4 +- bootstrap/app.php | 1 + .../2025_04_23_031630_create_songs_table.php | 3 +- .../seeders/ChineseNameConverterSeeder.php | 8 +- resources/lang/zh-tw/artists.php | 3 + resources/lang/zh-tw/roles.php | 4 + resources/lang/zh-tw/songs.php | 8 + resources/lang/zh-tw/users.php | 5 + .../views/components/layouts/app.blade.php | 10 +- .../livewire/admin/artist-form.blade.php | 12 +- .../views/livewire/admin/role-form.blade.php | 44 ++-- .../views/livewire/admin/song-form.blade.php | 48 ++-- .../views/livewire/admin/user-form.blade.php | 16 +- .../views/livewire/pages/auth/login.blade.php | 6 +- routes/api.php | 7 + routes/web.php | 40 +-- 31 files changed, 621 insertions(+), 442 deletions(-) create mode 100644 app/Enums/Traits/HasLabels.php create mode 100644 app/Http/Controllers/ArtistController.php create mode 100644 routes/api.php diff --git a/app/Enums/ArtistCategory.php b/app/Enums/ArtistCategory.php index 256c837..f2509a9 100644 --- a/app/Enums/ArtistCategory.php +++ b/app/Enums/ArtistCategory.php @@ -2,8 +2,12 @@ namespace App\Enums; +use App\Enums\Traits\HasLabels; + enum ArtistCategory: string { + use HasLabels; + case Unset = '未定義'; case Male = '男'; case Female = '女'; @@ -23,9 +27,4 @@ enum ArtistCategory: string self::Unset => __('enums.Unset'), }; } - - public function labelPowergridFilter(): String - { - return $this -> labels(); - } } \ No newline at end of file diff --git a/app/Enums/SongLanguageType.php b/app/Enums/SongLanguageType.php index 261317c..7f9a715 100644 --- a/app/Enums/SongLanguageType.php +++ b/app/Enums/SongLanguageType.php @@ -2,8 +2,12 @@ namespace App\Enums; +use App\Enums\Traits\HasLabels; + enum SongLanguageType: string { + use HasLabels; + case Unset = '未定義'; case Mandarin = '國語'; case Taiwanese = '台語'; @@ -31,9 +35,4 @@ enum SongLanguageType: string self::Other => __('enums.song.LanguageType.Other'), }; } - - public function labelPowergridFilter(): String - { - return $this -> labels(); - } } \ No newline at end of file diff --git a/app/Enums/SongSituation.php b/app/Enums/SongSituation.php index 68e97b9..967c3e5 100644 --- a/app/Enums/SongSituation.php +++ b/app/Enums/SongSituation.php @@ -2,8 +2,12 @@ namespace App\Enums; +use App\Enums\Traits\HasLabels; + enum SongSituation: string { + use HasLabels; + case Unset = '未定義'; case Romantic = '浪漫'; case Soft = '柔和'; @@ -22,8 +26,4 @@ enum SongSituation: string }; } - public function labelPowergridFilter(): String - { - return $this -> labels(); - } } \ No newline at end of file diff --git a/app/Enums/Traits/HasLabels.php b/app/Enums/Traits/HasLabels.php new file mode 100644 index 0000000..ca0662a --- /dev/null +++ b/app/Enums/Traits/HasLabels.php @@ -0,0 +1,20 @@ +mapWithKeys(function (self $case) { + return [$case->value => $case->labels()]; + }); + } + + public function labelPowergridFilter(): string + { + return $this->labels(); + } +} \ No newline at end of file diff --git a/app/Enums/UserGender.php b/app/Enums/UserGender.php index 9e6e4a7..d23a906 100644 --- a/app/Enums/UserGender.php +++ b/app/Enums/UserGender.php @@ -2,8 +2,12 @@ namespace App\Enums; +use App\Enums\Traits\HasLabels; + enum UserGender: string { + use HasLabels; + case Male = 'male'; case Female = 'female'; case Other = 'other'; @@ -20,8 +24,4 @@ enum UserGender: string }; } - public function labelPowergridFilter(): String - { - return $this -> labels(); - } } \ No newline at end of file diff --git a/app/Enums/UserStatus.php b/app/Enums/UserStatus.php index 6f147cc..b95aa83 100644 --- a/app/Enums/UserStatus.php +++ b/app/Enums/UserStatus.php @@ -2,8 +2,12 @@ namespace App\Enums; +use App\Enums\Traits\HasLabels; + enum UserStatus: int { + use HasLabels; + case Active = 0; // 正常 case Suspended = 1; // 停權 case Deleting = 2; // 刪除中 @@ -17,9 +21,5 @@ enum UserStatus: int self::Deleting => __('enums.user.status.Deleting'), }; } - public function labelPowergridFilter(): String - { - return $this -> labels(); - } } \ No newline at end of file diff --git a/app/Helpers/ChineseNameConverter.php b/app/Helpers/ChineseNameConverter.php index e6b51e4..43fd631 100644 --- a/app/Helpers/ChineseNameConverter.php +++ b/app/Helpers/ChineseNameConverter.php @@ -8,7 +8,7 @@ use Overtrue\Pinyin\Pinyin; class ChineseNameConverter { public static array $pinyinToZhuyinMap = [ - 'a' => 'ㄚ' ,'o' => 'ㄛ' ,'e' => 'ㄜ' ,'er' => 'ㄦ','ai' => 'ㄞ' ,'ei' => 'ㄟ' ,'ao' => 'ㄠ' ,'ou' => 'ㄡ' ,'an' => 'ㄢ' ,'en' => 'ㄣ' + 'a' => 'ㄚ' ,'o' => 'ㄛ' ,'e' => 'ㄜ' ,'er' => 'ㄦ','ai' => 'ㄞ' ,'ei' => 'ㄟ' ,'ao' => 'ㄠ' ,'ou' => 'ㄡ' ,'an' => 'ㄢ' ,'n' => 'ㄣ','en' => 'ㄣ' ,'ang' => 'ㄤ' ,'eng' => 'ㄥ' ,'yi' => 'ㄧ' ,'ya' => 'ㄧㄚ' ,'yo' => 'ㄧㄛ' ,'ye' => 'ㄧㄝ' ,'yai' => 'ㄧㄞ' ,'yao' => 'ㄧㄠ' ,'you' => 'ㄧㄡ' ,'yan' => 'ㄧㄢ' ,'yin' => 'ㄧㄣ' ,'yang' => 'ㄧㄤ' ,'ying' => 'ㄧㄥ' ,'wu' => 'ㄨ' ,'wa' => 'ㄨㄚ' ,'wo' => 'ㄨㄛ' ,'wai' => 'ㄨㄞ' ,'wei' => 'ㄨㄟ' ,'wan' => 'ㄨㄢ' ,'wen' => 'ㄨㄣ' ,'wang' => 'ㄨㄤ' ,'weng' => 'ㄨㄥ' ,'yu' => 'ㄩ' ,'yue' => 'ㄩㄝ' @@ -79,6 +79,7 @@ class ChineseNameConverter 'ū'=>'u', 'ú'=>'u', 'ǔ'=>'u', 'ù'=>'u', //'ǖ'=>'ü','ǘ'=>'ü','ǚ'=>'ü','ǜ'=>'ü', 'ǖ'=>'u','ǘ'=>'u','ǚ'=>'u','ǜ'=>'u', + 'ǹ'=>'n', ]; return strtr($pinyin, $map); diff --git a/app/Http/Controllers/ArtistController.php b/app/Http/Controllers/ArtistController.php new file mode 100644 index 0000000..5260181 --- /dev/null +++ b/app/Http/Controllers/ArtistController.php @@ -0,0 +1,30 @@ +filled('selected')) { + // 取得已選擇的項目(初始化時用) + return $query->whereIn('id', (array) $request->input('selected')) + ->get(['id', 'name']); + } + + if ($request->filled('search')) { + // 關鍵字搜尋(選單輸入文字時用) + $search = $request->input('search'); + $query->where('name', 'like', "%{$search}%"); + } + + return $query->limit(10)->get(['id', 'name']); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 8677cd5..858c1b1 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,7 +2,12 @@ namespace App\Http\Controllers; -abstract class Controller +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Illuminate\Foundation\Bus\DispatchesJobs; +use Illuminate\Foundation\Validation\ValidatesRequests; +use Illuminate\Routing\Controller as BaseController; + +class Controller extends BaseController { - // -} + use AuthorizesRequests, DispatchesJobs, ValidatesRequests; +} \ No newline at end of file diff --git a/app/Livewire/Admin/ArtistTable.php b/app/Livewire/Admin/ArtistTable.php index 83bec22..68194e6 100644 --- a/app/Livewire/Admin/ArtistTable.php +++ b/app/Livewire/Admin/ArtistTable.php @@ -6,6 +6,7 @@ use App\Models\Artist; use App\Enums\ArtistCategory; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Blade; use Illuminate\Database\Eloquent\Builder; use PowerComponents\LivewirePowerGrid\Button; @@ -22,6 +23,10 @@ use Livewire\Attributes\On; final class ArtistTable extends PowerGridComponent { public string $tableName = 'artist-table'; + public bool $canCreate; + public bool $canEdit; + public bool $canDownload; + public bool $canDelect; //public bool $deferLoading = true; //public string $loadingComponent = 'components.power-grid-loading'; @@ -31,36 +36,47 @@ final class ArtistTable extends PowerGridComponent public function boot(): void { config(['livewire-powergrid.filter' => 'outside']); + //權限設定 + $this->canCreate = Auth::user()?->can('song-edit') ?? false; + $this->canEdit = Auth::user()?->can('song-edit') ?? false; + $this->canDownload=Auth::user()?->can('song-delete') ?? false; + $this->canDelect = Auth::user()?->can('song-delete') ?? false; } public function setUp(): array { - //$this->showCheckBox(); - - return [ - //PowerGrid::exportable(fileName: 'artist-file') - // ->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV), - PowerGrid::header() - ->withoutLoading() - ->showToggleColumns() - //->showSoftDeletes() - //->showSearchInput() - ->includeViewOnTop('livewire.admin.artist-header') - , - PowerGrid::footer() - ->showPerPage() - ->showRecordCount(), - ]; + if($this->canDownload || $this->canDelect){ + $this->showCheckBox(); + } + + $actions = []; + if($this->canDownload){ + $actions[]=PowerGrid::exportable(fileName: 'artist-file') + ->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV); + } + $header = PowerGrid::header() + ->withoutLoading() + ->showToggleColumns(); + //->showSoftDeletes() + //->showSearchInput() + if($this->canCreate){ + $header->includeViewOnTop('livewire.admin.artist-header') ; + } + $actions[]=$header; + $actions[]=PowerGrid::footer()->showPerPage()->showRecordCount(); + return $actions; } public function header(): array { - return [ - /* Button::add('bulk-delete') + $actions = []; + if ($this->canDelect) { + $actions[]=Button::add('bulk-delete') ->slot('Bulk delete ()') ->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold']) ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispat ch('bulkDelete.' . $this->tableName, []),*/ - ]; + ->dispatch('bulkDelete.' . $this->tableName, []); + } + return $actions; } public function datasource(): Builder @@ -75,19 +91,23 @@ final class ArtistTable extends PowerGridComponent public function fields(): PowerGridFields { - $options = $this->categorySelectOptions(); - return PowerGrid::fields() ->add('id') - ->add('category_str', function (Artist $model) use ($options){ - return Blade::render( - '', - [ - 'options' => $options, - 'modelId' => intval($model->id), - 'fieldName'=>'category', - 'selected' => $model->category->value - ]); + ->add('category_str', function (Artist $model) { + if ($this->canEdit) { + return Blade::render( + '', + [ + 'options' => ArtistCategory::options(), + 'modelId' => intval($model->id), + 'fieldName'=>'category', + 'selected' => $model->category->value + ] + ); + } + // 沒有權限就顯示對應的文字 + + return $model->category->labelPowergridFilter(); // 假設 label() 會回傳顯示文字 } ) ->add('name') ->add('simplified') @@ -99,51 +119,59 @@ final class ArtistTable extends PowerGridComponent public function columns(): array { - return [ - Column::make(__('artists.no'), 'id'), - Column::make(__('artists.category'),'category_str', 'artists.category')->searchable(), - Column::make(__('artists.name'), 'name') - ->sortable() - ->searchable(), - - Column::make(__('artists.name.simplified'), 'simplified') - ->sortable() - ->searchable() - ->hidden(true, false), - - Column::make(__('artists.name.phinetic'), 'phonetic_abbr') - ->sortable() - ->searchable(), - - Column::make(__('artists.name.pinyin'), 'pinyin_abbr') - ->sortable() - ->searchable(), - Column::make(__('artists.name.strokes'), 'strokes_abbr') - ->sortable() - ->searchable(), - - Column::make('Created at', 'created_at_formatted', 'created_at') - ->sortable() - ->hidden(true, false), - - Column::action(__('artists.actions')) - ]; + $column=[]; + $column[]=Column::make(__('artists.no'), 'id'); + $column[]=Column::make(__('artists.category'),'category_str', 'artists.category')->searchable(); + $column[]=Column::make(__('artists.name'), 'name')->sortable()->searchable() + ->editOnClick( + hasPermission: $this->canEdit, + dataField: 'name', + fallback: 'N/A', + saveOnMouseOut: true + ); + $column[]=Column::make(__('artists.name.simplified'), 'simplified')->sortable()->searchable()->hidden(true, false); + $column[]=Column::make(__('artists.name.phinetic'), 'phonetic_abbr')->sortable()->searchable(); + $column[]=Column::make(__('artists.name.pinyin'), 'pinyin_abbr')->sortable()->searchable(); + $column[]=Column::make(__('artists.name.strokes'), 'strokes_abbr')->sortable()->searchable(); + $column[]=Column::make('Created at', 'created_at_formatted', 'created_at')->sortable()->hidden(true, false); + $column[]=Column::action(__('artists.actions')); + + return $column; } - - public function categorySelectOptions():Collection + #[On('bulkDelete.{tableName}')] + public function bulkDelete(): void { - return collect(ArtistCategory::cases())->mapWithKeys(function (ArtistCategory $case) { - return [$case->value => $case->labels()]; - }); - } + if ($this->canDelect) { + $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); + if($this->checkboxValues){ + Artist::destroy($this->checkboxValues); + $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. + } + } + } #[On('categoryChanged')] public function categoryChanged($value,$fieldName, $modelId): void { // dd($value,$fieldName, $modelId); - if($fieldName=='category'){ - Artist::find($modelId)?->update([$fieldName => $value]); + if ($fieldName == 'category' && $this->canEdit) { + $this->noUpdated($modelId,$fieldName,$value); } } + #[On('onUpdatedEditable')] + public function onUpdatedEditable($id, $field, $value): void + { + if ($field === 'name' && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + private function noUpdated($id,$field,$value){ + $artist = Artist::find($id); + if ($artist) { + $artist->{$field} = $value; + $artist->save(); // 明確觸發 saving + } + } + public function filters(): array { return [ @@ -157,32 +185,24 @@ final class ArtistTable extends PowerGridComponent Filter::datetimepicker('created_at'), ]; } - - /* #[On('bulkDelete.{tableName}')] - public function bulkDelete(): void - { - $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); - if($this->checkboxValues){ - Artist::destroy($this->checkboxValues); - $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. - } - } */ - public function actions(Artist $row): array { - return [ - - Button::add('edit') + $actions = []; + if ($this->canEdit) { + $actions[] =Button::add('edit') ->slot(__('artists.edit')) ->icon('solid-pencil-square') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.artist-form', 'openEditArtistModal', ['id' => $row->id]), - /* Button::add('delete') + ->dispatchTo('admin.artist-form', 'openEditArtistModal', ['id' => $row->id]); + } + if($this->canDelect){ + $actions[] =Button::add('delete') ->slot(__('artists.delete')) ->icon('solid-trash') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.artist-form', 'deleteArtist', ['id' => $row->id]), */ - ]; + ->dispatchTo('admin.artist-form', 'deleteArtist', ['id' => $row->id]); + } + return $actions; } diff --git a/app/Livewire/Admin/RoleTable.php b/app/Livewire/Admin/RoleTable.php index 4a27cca..01bec58 100644 --- a/app/Livewire/Admin/RoleTable.php +++ b/app/Livewire/Admin/RoleTable.php @@ -5,6 +5,7 @@ namespace App\Livewire\Admin; use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Permission; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Auth; use Illuminate\Database\Eloquent\Builder; use PowerComponents\LivewirePowerGrid\Button; use PowerComponents\LivewirePowerGrid\Column; @@ -16,25 +17,47 @@ use PowerComponents\LivewirePowerGrid\PowerGridComponent; final class RoleTable extends PowerGridComponent { public string $tableName = 'role-table'; + public bool $canCreate; + public bool $canEdit; + public bool $canDelect; public function boot(): void { config(['livewire-powergrid.filter' => 'outside']); + //權限設定 + $this->canCreate = Auth::user()?->can('role-edit') ?? false; + $this->canEdit = Auth::user()?->can('role-edit') ?? false; + $this->canDelect = Auth::user()?->can('role-delete') ?? false; } public function setUp(): array { - //$this->showCheckBox(); - - return [ - PowerGrid::header() - // ->showSearchInput(), - ->includeViewOnTop('livewire.admin.role-header'), - //PowerGrid::footer() - // ->showPerPage() - // ->showRecordCount(), - ]; + if($this->canDelect){ + $this->showCheckBox(); + } + $actions = []; + $header =PowerGrid::header(); + if($this->canCreate){ + $header->includeViewOnTop('livewire.admin.role-header'); + } + $actions[]=$header; + $actions[]=PowerGrid::footer() + ->showPerPage() + ->showRecordCount(); + return $actions; } + public function header(): array + { + $actions = []; + if ($this->canDelect) { + $actions[]=Button::add('bulk-delete') + ->slot('Bulk delete ()') + ->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold']) + ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') + ->dispatch('bulkDelete.' . $this->tableName, []); + } + return $actions; + } public function datasource(): Builder { @@ -67,18 +90,49 @@ final class RoleTable extends PowerGridComponent public function columns(): array { - return [ - Column::make(__('roles.no'), 'id')->sortable()->searchable(), - Column::make(__('roles.name'), 'name')->sortable()->searchable(), - Column::make(__('roles.permissions'), 'permissions_list'), - Column::make('Created at', 'created_at_formatted', 'created_at')->sortable(), - Column::action('Action') - ]; + $column=[]; + $column[]=Column::make(__('roles.no'), 'id')->sortable()->searchable(); + $column[]=Column::make(__('roles.name'), 'name')->sortable()->searchable() + ->editOnClick( + hasPermission: $this->canEdit, + dataField: 'name', + fallback: 'N/A', + saveOnMouseOut: true + ); + $column[]=Column::make(__('roles.permissions'), 'permissions_list'); + $column[]=Column::make('Created at', 'created_at_formatted', 'created_at')->sortable(); + $column[]=Column::action('Action'); + return $column; + } + #[On('bulkDelete.{tableName}')] + public function bulkDelete(): void + { + if ($this->canDelect) { + $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); + if($this->checkboxValues){ + Role::destroy($this->checkboxValues); + $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. + } + } + } + #[On('onUpdatedEditable')] + public function onUpdatedEditable($id, $field, $value): void + { + if ($field === 'name' && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + private function noUpdated($id,$field,$value){ + $role = Role::find($id); + if ($role) { + $role->{$field} = $value; + $role->save(); // 明確觸發 saving + } } - public function filters(): array { return [ + Filter::inputText('name')->placeholder(__('roles.name')), Filter::datetimepicker('created_at'), ]; } @@ -86,18 +140,22 @@ final class RoleTable extends PowerGridComponent public function actions(Role $row): array { - return [ - Button::add('edit') + $actions = []; + if ($this->canEdit) { + $actions[] =Button::add('edit') ->slot(__('roles.edit')) ->icon('solid-pencil-square') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.role-form', 'openEditRoleModal', ['id' => $row->id]), - Button::add('delete') + ->dispatchTo('admin.role-form', 'openEditRoleModal', ['id' => $row->id]); + } + if($this->canDelect){ + $actions[] =Button::add('delete') ->slot(__('roles.delete')) ->icon('solid-trash') ->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]); + } + return $actions; } /* diff --git a/app/Livewire/Admin/SongForm.php b/app/Livewire/Admin/SongForm.php index 25d8b6d..fbd2226 100644 --- a/app/Livewire/Admin/SongForm.php +++ b/app/Livewire/Admin/SongForm.php @@ -72,6 +72,9 @@ class SongForm extends Component $song = Song::findOrFail($id); $this->songId = $song->id; $this->fields = $song->only(array_keys($this->fields)); + $this->selectedCategories = $song->categories()->pluck('id')->toArray(); + $this->selectedArtists = $song->artists()->pluck('id')->toArray(); + //dd($this->fields,$this->selectedCategories,$this->selectedArtists); $this->showCreateModal = true; } @@ -84,9 +87,12 @@ class SongForm extends Component $song->update($this->fields); session()->flash('message', '歌曲已更新'); } else { - Song::create($this->fields); + $song = Song::create($this->fields); session()->flash('message', '歌曲已新增'); } + // ⭐ 同步多對多關聯 + $song->artists()->sync($this->selectedArtists ?? []); + $song->categories()->sync($this->selectedCategories ?? []); $this->resetFields(); $this->showCreateModal = false; diff --git a/app/Livewire/Admin/SongTable.php b/app/Livewire/Admin/SongTable.php index 751bff9..877c327 100644 --- a/app/Livewire/Admin/SongTable.php +++ b/app/Livewire/Admin/SongTable.php @@ -7,6 +7,7 @@ use App\Enums\SongLanguageType; use App\Enums\SongSituation; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Blade; use Illuminate\Database\Eloquent\Builder; use PowerComponents\LivewirePowerGrid\Button; @@ -23,6 +24,10 @@ use Livewire\Attributes\On; final class SongTable extends PowerGridComponent { public string $tableName = 'song-table'; + public bool $canCreate; + public bool $canEdit; + public bool $canDownload; + public bool $canDelect; /* public bool $deferLoading = true; @@ -33,36 +38,43 @@ final class SongTable extends PowerGridComponent public function boot(): void { config(['livewire-powergrid.filter' => 'outside']); + $this->canCreate = Auth::user()?->can('song-edit') ?? false; + $this->canEdit = Auth::user()?->can('song-edit') ?? false; + $this->canDownload=Auth::user()?->can('song-delete') ?? false; + $this->canDelect = Auth::user()?->can('song-delete') ?? false; } public function setUp(): array { - $this->showCheckBox(); + if($this->canDownload || $this->canDelect){ + $this->showCheckBox(); + } + $actions = []; + if($this->canDownload){ + $actions[]=PowerGrid::exportable(fileName: 'song-file') + ->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV); + } + $header = PowerGrid::header()->showSoftDeletes()->showToggleColumns(); + if($this->canCreate){ + $header->includeViewOnTop('livewire.admin.song-header'); + } + $actions[]=$header; + $actions[]=PowerGrid::footer()->showPerPage()->showRecordCount(); - return [ - PowerGrid::exportable(fileName: 'song-file') - ->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV), - PowerGrid::header() - ->showSoftDeletes() - ->showToggleColumns() - //->showSearchInput() - ->includeViewOnTop('livewire.admin.song-header') - , - PowerGrid::footer() - ->showPerPage() - ->showRecordCount(), - ]; + return $actions; } public function header(): array { - return [ - Button::add('bulk-delete') + $actions = []; + if ($this->canDelect) { + $actions[]=Button::add('bulk-delete') ->slot('Bulk delete ()') ->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold']) ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatch('bulkDelete.' . $this->tableName, []), - ]; + ->dispatch('bulkDelete.' . $this->tableName, []); + } + return $actions; } public function datasource(): Builder @@ -94,7 +106,7 @@ final class SongTable extends PowerGridComponent return Blade::render( '', [ - 'options' => $this->languageTypeSelectOptions(), + 'options' => SongLanguageType::options(), 'modelId' => intval($model->id), 'fieldName'=>'language_type', 'selected' => $model->language_type->value @@ -108,7 +120,7 @@ final class SongTable extends PowerGridComponent return Blade::render( '', [ - 'options' => $this->situationSelectOptions(), + 'options' => SongSituation::options(), 'modelId' => intval($model->id), 'fieldName'=>'situation', 'selected' => $model->situation->value @@ -126,87 +138,41 @@ final class SongTable extends PowerGridComponent public function columns(): array { - return [ - Column::make(__('songs.id'), 'id'), - Column::make(__('songs.name'), 'name') - ->sortable() - ->searchable(), - Column::make(__('songs.simplified'), 'simplified') - ->sortable() - ->searchable() - ->hidden(true, false), - Column::make(__('songs.name.phinetic'), 'phonetic_abbr') - ->sortable() - ->searchable() - ->hidden(true, false), - Column::make(__('songs.name.pinyin'), 'pinyin_abbr') - ->sortable() - ->searchable() - ->hidden(true, false), - Column::make(__('songs.name.strokes'), 'strokes_abbr') - ->sortable() - ->searchable() - ->hidden(true, false), - - Column::make(__('songs.filename'), 'filename') - ->sortable() - ->searchable(), - Column::make(__('songs.adddate'), 'adddate_formatted', 'adddate') - ->sortable(), - //歌手 - Column::make(__('songs.artists'), 'song_artists'), - Column::make(__('songs.language_type'),'language_type_str', 'songs.language_type')->searchable(), - //分類 - Column::make(__('songs.categorys'), 'song_categories'), - //點播次數 - Column::make('Db change', 'db_change'), - Column::make(__('songs.vocal'), 'vocal') - ->toggleable(hasPermission: true, trueLabel: 'yes', falseLabel: 'no'), - Column::make(__('songs.situation'), 'situation_str','songs.situation')->searchable(), - - Column::make(__('songs.copyright01'), 'copyright01') - ->sortable() - ->searchable() - ->editOnClick(hasPermission: true, dataField: 'copyright01', fallback: 'N/A', saveOnMouseOut: true), - - Column::make(__('songs.copyright02'), 'copyright02') - ->sortable() - ->searchable() - ->editOnClick(hasPermission: true, dataField: 'copyright02', fallback: 'N/A', saveOnMouseOut: true) - ->hidden(true, false), - - Column::make(__('songs.note01'), 'note01') - ->sortable() - ->searchable() - ->editOnClick(hasPermission: true, dataField: 'note01', fallback: 'N/A', saveOnMouseOut: true), - - Column::make(__('songs.note02'), 'note02') - ->sortable() - ->searchable() - ->editOnClick(hasPermission: true, dataField: 'note02', fallback: 'N/A', saveOnMouseOut: true) - ->hidden(true, false), - - Column::make(__('songs.note03'), 'note03') - ->sortable() - ->searchable() - ->editOnClick(hasPermission: true, dataField: 'note03', fallback: 'N/A', saveOnMouseOut: true) - ->hidden(true, false), - - Column::make(__('songs.note04'), 'note04') - ->sortable() - ->searchable() - ->editOnClick(hasPermission: true, dataField: 'note04', fallback: 'N/A', saveOnMouseOut: true) - ->hidden(true, false), - - Column::make(__('songs.enable'), 'enable') - ->toggleable(hasPermission: true, trueLabel: 'yes', falseLabel: 'no'), - - Column::make('Created at', 'created_at_formatted', 'created_at') - ->sortable() - ->hidden(true, false), - - Column::action(__('songs.actions')) - ]; + $column=[]; + $column[]=Column::make(__('songs.id'), 'id') + ->editOnClick(hasPermission: $this->canEdit, dataField: 'id', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.name'), 'name')->sortable()->searchable() + ->editOnClick(hasPermission: $this->canEdit, dataField: 'name', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.simplified'), 'simplified')->sortable()->searchable()->hidden(true, false); + $column[]=Column::make(__('songs.name.phinetic'), 'phonetic_abbr')->sortable()->searchable()->hidden(true, false); + $column[]=Column::make(__('songs.name.pinyin'), 'pinyin_abbr')->sortable()->searchable()->hidden(true, false); + $column[]=Column::make(__('songs.name.strokes'), 'strokes_abbr')->sortable()->searchable()->hidden(true, false); + $column[]=Column::make(__('songs.filename'), 'filename')->sortable()->searchable() + ->editOnClick(hasPermission: $this->canEdit, dataField: 'filename', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.adddate'), 'adddate_formatted', 'adddate')->sortable(); + $column[]=Column::make(__('songs.artists'), 'song_artists'); + $column[]=Column::make(__('songs.language_type'),'language_type_str', 'songs.language_type')->searchable(); + $column[]=Column::make(__('songs.categorys'), 'song_categories'); + $column[]=Column::make('Db change', 'db_change') + ->editOnClick(hasPermission: $this->canEdit, dataField: 'db_change', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.vocal'), 'vocal')->toggleable(hasPermission: true, trueLabel: 'yes', falseLabel: 'no'); + $column[]=Column::make(__('songs.situation'), 'situation_str','songs.situation')->searchable(); + $column[]=Column::make(__('songs.copyright01'), 'copyright01')->sortable()->searchable() + ->editOnClick(hasPermission: $this->canEdit, dataField: 'copyright01', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.copyright02'), 'copyright02')->sortable()->searchable()->hidden(true, false) + ->editOnClick(hasPermission: $this->canEdit, dataField: 'copyright02', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.note01'), 'note01')->sortable()->searchable() + ->editOnClick(hasPermission: $this->canEdit, dataField: 'note01', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.note02'), 'note02')->sortable()->searchable()->hidden(true, false) + ->editOnClick(hasPermission: $this->canEdit, dataField: 'note02', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.note03'), 'note03')->sortable()->searchable()->hidden(true, false) + ->editOnClick(hasPermission: $this->canEdit, dataField: 'note03', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.note04'), 'note04')->sortable()->searchable()->hidden(true, false) + ->editOnClick(hasPermission: $this->canEdit, dataField: 'note04', fallback: 'N/A', saveOnMouseOut: true); + $column[]=Column::make(__('songs.enable'), 'enable')->toggleable(hasPermission: true, trueLabel: 'yes', falseLabel: 'no'); + $column[]=Column::make('Created at', 'created_at_formatted', 'created_at')->sortable()->hidden(true, false); + $column[]=Column::action(__('songs.actions')); + return $column; } public function filters(): array @@ -232,58 +198,70 @@ final class SongTable extends PowerGridComponent Filter::datetimepicker('created_at'), ]; } - public function languageTypeSelectOptions():Collection - { - return collect(SongLanguageType::cases())->mapWithKeys(function (SongLanguageType $case) { - return [$case->value => $case->labels()]; - }); - } - public function situationSelectOptions():Collection - { - return collect(SongSituation::cases())->mapWithKeys(function (SongSituation $case) { - return [$case->value => $case->labels()]; - }); - } - #[On('categoryChanged')] - public function categoryChanged($value,$fieldName, $modelId): void - { - // dd($value,$fieldName, $modelId); - if (in_array($fieldName, ['language_type', 'situation'])) { - Artist::find($modelId)?->update([$fieldName => $value]); - } - } #[On('bulkDelete.{tableName}')] public function bulkDelete(): void { $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); if($this->checkboxValues){ - Artist::destroy($this->checkboxValues); + Song::destroy($this->checkboxValues); $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. } } + #[On('categoryChanged')] + public function categoryChanged($value,$fieldName, $modelId): void + { + // dd($value,$fieldName, $modelId); + if (in_array($fieldName, ['language_type', 'situation'])) { + $this->noUpdated($modelId,$fieldName,$value); + } + } + #[On('onUpdatedEditable')] + public function onUpdatedEditable($id, $field, $value): void + { + if (in_array($field,[ + 'name','filename','db_change', + 'copyright01','copyright02','note01','note02','note03','note04' + ]) && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + #[On('onUpdatedToggleable')] + public function onUpdatedToggleable($id, $field, $value): void + { + if (in_array($field,['vocal','enable']) && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + private function noUpdated($id,$field,$value){ + $song = Song::find($id); + if ($song) { + $song->{$field} = $value; + $song->save(); // 明確觸發 saving + } + } + public function actions(Song $row): array { - return [ - - Button::add('edit') + $actions = []; + if ($this->canEdit) { + $actions[]=Button::add('edit') ->slot(__('songs.edit')) ->icon('solid-pencil-square') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.song-form', 'openEditSongModal', ['id' => $row->id]), - Button::add('delete') + ->dispatchTo('admin.song-form', 'openEditSongModal', ['id' => $row->id]); + } + if($this->canDelect){ + $actions[]=Button::add('delete') ->slot(__('songs.delete')) ->icon('solid-trash') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.song-form', 'deleteSong', ['id' => $row->id]), - ]; - } - public function onUpdatedToggleable($id, $field, $value): void - { - $updated = Song::query()->where('id', $id)->update([ - $field => $value, - ]); + ->dispatchTo('admin.song-form', 'deleteSong', ['id' => $row->id]); + } + + return $actions; } + /* public function actionRules($row): array { diff --git a/app/Livewire/Admin/UserTable.php b/app/Livewire/Admin/UserTable.php index 5e0c166..975eb7a 100644 --- a/app/Livewire/Admin/UserTable.php +++ b/app/Livewire/Admin/UserTable.php @@ -6,6 +6,8 @@ use App\Models\User; use App\Enums\UserGender; use App\Enums\UserStatus; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Blade; use Illuminate\Database\Eloquent\Builder; use PowerComponents\LivewirePowerGrid\Button; use PowerComponents\LivewirePowerGrid\Column; @@ -25,38 +27,50 @@ final class UserTable extends PowerGridComponent public string $tableName = 'user-table'; public bool $showFilters = false; + public bool $canCreate; + public bool $canEdit; + public bool $canDownload; + public bool $canDelect; public function boot(): void { config(['livewire-powergrid.filter' => 'outside']); + //權限設定 + $this->canCreate = Auth::user()?->can('user-edit') ?? false; + $this->canEdit = Auth::user()?->can('user-edit') ?? false; + $this->canDownload=Auth::user()?->can('user-delete') ?? false; + $this->canDelect = Auth::user()?->can('user-delete') ?? false; } public function setUp(): array { - $this->showCheckBox(); - - return [ - PowerGrid::exportable(fileName: 'my-export-file') - ->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV), - PowerGrid::header() - //->showSoftDeletes() - ->showToggleColumns() - //->showSearchInput() - ->includeViewOnTop('livewire.admin.user-header') - , - PowerGrid::footer()->showPerPage()->showRecordCount(), - ]; + if($this->canDownload || $this->canDelect){ + $this->showCheckBox(); + } + $actions = []; + $actions[] =PowerGrid::exportable(fileName: 'user-file') + ->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV); + $header = PowerGrid::header() + ->showToggleColumns(); + if($this->canCreate){ + $header->includeViewOnTop('livewire.admin.user-header'); + } + $actions[]=$header; + $actions[]=PowerGrid::footer()->showPerPage()->showRecordCount(); + return $actions; } public function header(): array { - return [ - Button::add('bulk-delete') + $actions = []; + if ($this->canDelect) { + $actions[]=Button::add('bulk-delete') ->slot('Bulk delete ()') ->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold']) ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatch('bulkDelete.' . $this->tableName, []), - ]; + ->dispatch('bulkDelete.' . $this->tableName, []); + } + return $actions; } public function datasource(): Builder @@ -71,14 +85,45 @@ final class UserTable extends PowerGridComponent public function fields(): PowerGridFields { + return PowerGrid::fields() ->add('id') ->add('name') ->add('email') ->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('gender_str', function (User $model) { + if ($this->canEdit) { + return Blade::render( + '', + [ + 'options' => UserGender::options(), + 'modelId' => intval($model->id), + 'fieldName'=>'gender', + 'selected' => $model->gender->value + ] + ); + } + // 沒有權限就顯示對應的文字 + + return $model->gender->labelPowergridFilter(); // 假設 label() 會回傳顯示文字 + } ) + ->add('status_str', function (User $model) { + if ($this->canEdit) { + return Blade::render( + '', + [ + 'options' => UserStatus::options(), + 'modelId' => intval($model->id), + 'fieldName'=>'status', + 'selected' => $model->status->value + ] + ); + } + // 沒有權限就顯示對應的文字 + + return $model->status->labelPowergridFilter(); // 假設 label() 會回傳顯示文字 + } ) ->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')); } @@ -91,7 +136,7 @@ final class UserTable extends PowerGridComponent ->sortable() ->searchable() ->editOnClick( - hasPermission: true, + hasPermission: $this->canEdit, dataField: 'name', fallback: 'N/A', saveOnMouseOut: true @@ -100,7 +145,7 @@ final class UserTable extends PowerGridComponent ->sortable() ->searchable() ->editOnClick( - hasPermission: true, + hasPermission: $this->canEdit, dataField: 'email', fallback: 'N/A', saveOnMouseOut: true @@ -109,32 +154,71 @@ final class UserTable extends PowerGridComponent ->sortable() ->searchable() ->editOnClick( - hasPermission: true, + hasPermission: $this->canEdit, dataField: 'phone', fallback: 'N/A', saveOnMouseOut: true ), - Column::make(__('users.gender'), 'gender','users.gender'), + Column::make(__('users.gender'), 'gender_str','users.gender'), Column::make(__('users.birthday'), 'birthday_formatted')->sortable()->searchable(), - Column::make(__('users.status'), 'status','users.status'), + Column::make(__('users.status'), 'status_str','users.status'), Column::make(__('users.role'), 'roles'), Column::make('建立時間', 'created_at_formatted', 'created_at')->sortable(), Column::action('操作') ]; } - + #[On('bulkDelete.{tableName}')] + public function bulkDelete(): void + { + if ($this->canDelect) { + $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); + if($this->checkboxValues){ + Artist::destroy($this->checkboxValues); + $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. + } + } + } + #[On('categoryChanged')] + public function categoryChanged($value,$fieldName, $modelId): void + { + // dd($value,$fieldName, $modelId); + if (in_array($fieldName,['gender','status']) && $this->canEdit) { + $this->noUpdated($modelId,$fieldName,$value); + } + } + #[On('onUpdatedEditable')] + public function onUpdatedEditable($id, $field, $value): void + { + if (in_array($field,['name','email','phone']) && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + #[On('onUpdatedToggleable')] + public function onUpdatedToggleable($id, $field, $value): void + { + if (in_array($field,[]) && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + private function noUpdated($id,$field,$value){ + $artist = Artist::find($id); + if ($artist) { + $artist->{$field} = $value; + $artist->save(); // 明確觸發 saving + } + } public function filters(): array { return [ Filter::inputText('name')->placeholder(__('users.name')), Filter::inputText('email')->placeholder('Email'), Filter::inputText('phone')->placeholder(__('users.phone')), - Filter::enumSelect('gender','users.gender') + Filter::enumSelect('gender_str','users.gender') ->datasource(UserGender::cases()) ->optionLabel('users.gender'), Filter::datepicker('birthday'), - Filter::enumSelect('status', 'users.status') + Filter::enumSelect('status_str', 'users.status') ->datasource(UserStatus::cases()) ->optionLabel('users.status'), Filter::datetimepicker('created_at'), @@ -143,47 +227,22 @@ final class UserTable extends PowerGridComponent public function actions(User $row): array { - return [ - - Button::add('edit') + $actions = []; + if ($this->canEdit) { + $actions[]=Button::add('edit') ->slot(__('users.edit')) ->icon('solid-pencil-square') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.user-form', 'openEditUserModal', ['id' => $row->id]), - Button::add('delete') + ->dispatchTo('admin.user-form', 'openEditUserModal', ['id' => $row->id]); + } + if($this->canDelect){ + $actions[]=Button::add('delete') ->slot(__('users.delete')) ->icon('solid-trash') ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->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}')] - public function bulkDelete(): void - { - $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); - if($this->checkboxValues){ - User::destroy($this->checkboxValues); - $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. + ->dispatchTo('admin.user-form', 'deleteUser', ['id' => $row->id]); } + return $actions; } diff --git a/app/Models/Song.php b/app/Models/Song.php index 17c4dac..032a405 100644 --- a/app/Models/Song.php +++ b/app/Models/Song.php @@ -5,6 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use App\Helpers\ChineseNameConverter; +use App\Helpers\ChineseStrokesConverter; class Song extends Model { @@ -70,7 +71,7 @@ class Song extends Model $chars = preg_split('//u', $song->name, -1, PREG_SPLIT_NO_EMPTY); $firstChar = $chars[0] ?? null; - $song->strokes_abbr=$firstChar ? getStrokeCountFromChar::getStrokes($firstChar) : null; + $song->strokes_abbr=$firstChar ? ChineseStrokesConverter::getStrokes($firstChar) : null; }); } } diff --git a/app/Models/User.php b/app/Models/User.php index 296bde5..79be10f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -48,7 +48,9 @@ class User extends Authenticatable return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', - 'birthday' => 'date' + 'birthday' => 'date', + 'gender' => \App\Enums\UserGender::class, + 'status' => \App\Enums\UserStatus::class, ]; } diff --git a/bootstrap/app.php b/bootstrap/app.php index 4455074..0ebf1c9 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -6,6 +6,7 @@ use Illuminate\Foundation\Configuration\Middleware; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( + api: __DIR__.'/../routes/api.php', web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', diff --git a/database/migrations/2025_04_23_031630_create_songs_table.php b/database/migrations/2025_04_23_031630_create_songs_table.php index 237bd3b..5341249 100644 --- a/database/migrations/2025_04_23_031630_create_songs_table.php +++ b/database/migrations/2025_04_23_031630_create_songs_table.php @@ -30,7 +30,8 @@ return new class extends Migration $table->string('simplified')->comment('歌曲簡體'); $table->string('phonetic_abbr')->comment('歌曲注音'); $table->string('pinyin_abbr')->comment('歌曲拼音'); - $table->integer('strokes_abbr')->nullable()->comment('歌曲筆劃'); + $table->integer('strokes_abbr')->default(0)->comment('歌曲筆劃'); + $table->integer('song_counts')->default(0)->comment('點播次數'); $table->timestamps(); }); } diff --git a/database/seeders/ChineseNameConverterSeeder.php b/database/seeders/ChineseNameConverterSeeder.php index 182c379..169ef31 100644 --- a/database/seeders/ChineseNameConverterSeeder.php +++ b/database/seeders/ChineseNameConverterSeeder.php @@ -16,9 +16,9 @@ class ChineseNameConverterSeeder extends Seeder { //dd(ChineseStrokesConverter::getStrokes('羅')); - echo "羅:" . ChineseStrokesConverter::getStrokes('羅') . PHP_EOL; - echo "陳:" . ChineseStrokesConverter::getStrokes('陳') . PHP_EOL; - echo "阪:" . ChineseStrokesConverter::getStrokes('阪') . PHP_EOL; - //dd(ChineseNameConverter::convertAll('比愛更愛')); + //echo "羅:" . ChineseStrokesConverter::getStrokes('羅') . PHP_EOL; + //echo "陳:" . ChineseStrokesConverter::getStrokes('陳') . PHP_EOL; + //echo "阪:" . ChineseStrokesConverter::getStrokes('阪') . PHP_EOL; + dd(ChineseNameConverter::convertAll('小嗯')); } } diff --git a/resources/lang/zh-tw/artists.php b/resources/lang/zh-tw/artists.php index a07287e..d1e1234 100644 --- a/resources/lang/zh-tw/artists.php +++ b/resources/lang/zh-tw/artists.php @@ -4,6 +4,7 @@ return [ 'management' => '歌手管理', 'list' => '歌手列表', 'CreateNew' => '新增歌手', + 'EditArtist' => '編輯歌手', 'create_edit' => '新增 / 編輯', 'create' => '新增', 'edit' => '編輯', @@ -15,6 +16,8 @@ return [ 'name.phinetic' => '注音', 'name.pinyin' => '拼音', 'name.strokes' => '筆劃', + + 'select_category' =>'選擇類別', 'actions' => '操作', 'view' => '查看', diff --git a/resources/lang/zh-tw/roles.php b/resources/lang/zh-tw/roles.php index 23bcc13..e6e0be1 100644 --- a/resources/lang/zh-tw/roles.php +++ b/resources/lang/zh-tw/roles.php @@ -3,12 +3,16 @@ return [ 'list' => '角色列表', 'CreateNew' => '新增角色', + 'EditRole' => '編輯角色', 'edit' => '編輯', 'delete' => '刪除', 'no' => '編號', 'name' => '名稱', 'permissions' => '權限', + + 'role_name' =>'角色名稱', + 'select_permissions'=>'選擇權限', 'create' => '新增', 'action' => '操作', diff --git a/resources/lang/zh-tw/songs.php b/resources/lang/zh-tw/songs.php index f322295..211561f 100644 --- a/resources/lang/zh-tw/songs.php +++ b/resources/lang/zh-tw/songs.php @@ -4,6 +4,7 @@ return [ 'management' => '歌曲管理', 'list' => '歌曲列表', 'CreateNew' => '新增歌曲', + 'EditSong' => '編輯歌曲', 'create_edit' => '新增 / 編輯', 'create' => '新增', 'edit' => '編輯', @@ -27,10 +28,17 @@ return [ 'note04' => '備註04', 'enable' => '狀態', 'name_length' => '歌名字數', + 'db_change'=>'分貝增減', 'vocal' => '人聲', 'situation' => '情境', 'simplified' => '歌名簡體', + + 'select_artists' =>'輸入搜尋歌手', + 'select_language_type'=>'選擇語言', + 'select_categorys'=>'選擇分類', + 'select_situation'=>'選擇情境', + 'actions' => '操作', 'view' => '查看', 'submit' => '提交', diff --git a/resources/lang/zh-tw/users.php b/resources/lang/zh-tw/users.php index 41361f2..bb934dd 100644 --- a/resources/lang/zh-tw/users.php +++ b/resources/lang/zh-tw/users.php @@ -3,6 +3,7 @@ return [ 'list' => '使用者列表', 'CreateNew' => '新增使用者', + 'EditUser' => '編輯使用者', 'edit' => '編輯', 'delete' => '刪除', @@ -13,6 +14,10 @@ return [ 'birthday' => '生日', 'status' => '狀態', 'role' =>'角色', + + 'select_gender'=>'選擇性別', + 'select_status'=>'選擇狀態', + 'select_role'=>'選擇角色', 'create' => '新增', 'action' => '操作', diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php index 7e1e83e..fbf0e94 100644 --- a/resources/views/components/layouts/app.blade.php +++ b/resources/views/components/layouts/app.blade.php @@ -1,9 +1,9 @@ -@if(auth()->user()->hasRole('Admin')) - - {{ $slot }} - -@else +@if(auth()->user()->hasRole('User')) {{ $slot }} +@else + + {{ $slot }} + @endif \ No newline at end of file diff --git a/resources/views/livewire/admin/artist-form.blade.php b/resources/views/livewire/admin/artist-form.blade.php index b904ac6..91f8725 100644 --- a/resources/views/livewire/admin/artist-form.blade.php +++ b/resources/views/livewire/admin/artist-form.blade.php @@ -1,20 +1,20 @@ - +
- +
- - + +
diff --git a/resources/views/livewire/admin/role-form.blade.php b/resources/views/livewire/admin/role-form.blade.php index 18a1b9a..1d55768 100644 --- a/resources/views/livewire/admin/role-form.blade.php +++ b/resources/views/livewire/admin/role-form.blade.php @@ -1,25 +1,21 @@ -
-@if ($showCreateModal) - -
- - -
+ +
+ + +
- -
- - -
-
-
- @endif -
+ +
+ + +
+
+
\ No newline at end of file diff --git a/resources/views/livewire/admin/song-form.blade.php b/resources/views/livewire/admin/song-form.blade.php index 6eb759e..fdfafaf 100644 --- a/resources/views/livewire/admin/song-form.blade.php +++ b/resources/views/livewire/admin/song-form.blade.php @@ -1,17 +1,15 @@ - - -
+
- - - + + +
- - + +
- - + +
- - + +
- - - - + + + +
- +
diff --git a/resources/views/livewire/admin/user-form.blade.php b/resources/views/livewire/admin/user-form.blade.php index 040e9cf..e9d3e42 100644 --- a/resources/views/livewire/admin/user-form.blade.php +++ b/resources/views/livewire/admin/user-form.blade.php @@ -1,29 +1,29 @@ - +
- + redirectIntended(default: route('dashboard', absolute: false), navigate: true); $user = auth()->user(); - if ($user->hasRole('Admin')) { - $this->redirect(route('admin.dashboard'), navigate: true); - } else { + if ($user->hasRole('User')) { $this->redirect(route('dashboard'), navigate: true); + } else { + $this->redirect(route('admin.dashboard'), navigate: true); } } }; ?> diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..dadb8b3 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,7 @@ +name('api.artists.search'); diff --git a/routes/web.php b/routes/web.php index e7f2bda..660026d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,13 +3,11 @@ use Illuminate\Support\Facades\Route; use App\Livewire\Admin\Dashboard as AdminDashboard; -use App\Livewire\Admin\RoleTable; -use App\Livewire\Admin\UserTable; -use App\Livewire\Admin\ArtistTable; -use App\Livewire\Admin\SongTable; Route::view('/', 'welcome'); + + Route::view('dashboard', 'dashboard') ->middleware(['auth', 'verified']) ->name('dashboard'); @@ -19,36 +17,16 @@ Route::view('profile', 'profile') ->name('profile'); require __DIR__.'/auth.php'; -Route::get('/api/artists/search', function (Request $request) { - - return \App\Models\Artist::query() - ->where('name', 'like', "%{$request->input('search')}%") - ->limit(20) - ->get(['id', 'name']); -})->name('api.artists.search'); + Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () { Route::get('/dashboard', AdminDashboard::class)->name('dashboard'); - Route::get('/roles', function () { - return view('livewire.admin.roles'); - })->name('roles'); - Route::get('/roles-table', RoleTable::class)->name('roles-table'); - - Route::get('/users', function () { - return view('livewire.admin.users'); - })->name('users'); - Route::get('/users-table', UserTable::class)->name('users-table'); - - Route::get('/artists', function () { - return view('livewire.admin.artists'); - })->name('artists'); - - Route::get('/artists-table', ArtistTable::class)->name('artists-table'); - - Route::get('/songs', function () { - return view('livewire.admin.songs'); - })->name('songs'); - Route::get('/songs-table', ArtistTable::class)->name('songs-table'); + Route::get('/roles', function () {return view('livewire.admin.roles');})->name('roles'); + Route::get('/users', function () {return view('livewire.admin.users');})->name('users'); + Route::get('/artists', function () {return view('livewire.admin.artists');})->name('artists'); + Route::get('/songs', function () {return view('livewire.admin.songs');})->name('songs'); + Route::get('/branches', function () {return view('livewire.admin.branches');})->name('branches'); + Route::get('/rooms', function () {return view('livewire.admin.rooms');})->name('rooms'); }); \ No newline at end of file