加入 ⻆色權限控制

修正 'n' => 'ㄣ' 轉值問題
加入 歌手搜尋功能
DB 開 點播次數欄位
20250505
This commit is contained in:
allen.yan 2025-05-05 11:22:40 +08:00
parent 077d418357
commit d9924bf05b
31 changed files with 621 additions and 442 deletions

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Enums\Traits;
use Illuminate\Support\Collection;
trait HasLabels
{
public static function options(): Collection
{
return collect(self::cases())->mapWithKeys(function (self $case) {
return [$case->value => $case->labels()];
});
}
public function labelPowergridFilter(): string
{
return $this->labels();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers;
use App\Models\Artist;
use Illuminate\Http\Request;
class ArtistController extends Controller
{
public function search(Request $request)
{
$query = Artist::query();
if ($request->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']);
}
}

View File

@ -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;
}

View File

@ -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();
if($this->canDownload || $this->canDelect){
$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(),
];
$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 (<span x-text="window.pgBulkActions.count(\'' . $this->tableName . '\')"></span>)')
->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(
'<x-select-category type="occurrence" :options=$options :modelId=$modelId :fieldName=$fieldName :selected=$selected/>',
[
'options' => $options,
'modelId' => intval($model->id),
'fieldName'=>'category',
'selected' => $model->category->value
]);
->add('category_str', function (Artist $model) {
if ($this->canEdit) {
return Blade::render(
'<x-select-category type="occurrence" :options=$options :modelId=$modelId :fieldName=$fieldName :selected=$selected/>',
[
'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=[];
$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'));
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'))
];
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;
}

View File

@ -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,24 +17,46 @@ 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 (<span x-text="window.pgBulkActions.count(\'' . $this->tableName . '\')"></span>)')
->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;
}
/*

View File

@ -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;

View File

@ -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 (<span x-text="window.pgBulkActions.count(\'' . $this->tableName . '\')"></span>)')
->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(
'<x-select-category type="occurrence" :options=$options :modelId=$modelId :fieldName=$fieldName :selected=$selected/>',
[
'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(
'<x-select-category type="occurrence" :options=$options :modelId=$modelId :fieldName=$fieldName :selected=$selected/>',
[
'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
{

View File

@ -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 (<span x-text="window.pgBulkActions.count(\'' . $this->tableName . '\')"></span>)')
->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(
'<x-select-category type="occurrence" :options=$options :modelId=$modelId :fieldName=$fieldName :selected=$selected/>',
[
'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(
'<x-select-category type="occurrence" :options=$options :modelId=$modelId :fieldName=$fieldName :selected=$selected/>',
[
'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;
}

View File

@ -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;
});
}
}

View File

@ -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,
];
}

View File

@ -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',

View File

@ -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();
});
}

View File

@ -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('小嗯'));
}
}

View File

@ -4,6 +4,7 @@ return [
'management' => '歌手管理',
'list' => '歌手列表',
'CreateNew' => '新增歌手',
'EditArtist' => '編輯歌手',
'create_edit' => '新增 / 編輯',
'create' => '新增',
'edit' => '編輯',
@ -16,6 +17,8 @@ return [
'name.pinyin' => '拼音',
'name.strokes' => '筆劃',
'select_category' =>'選擇類別',
'actions' => '操作',
'view' => '查看',
'submit' => '提交',

View File

@ -3,6 +3,7 @@
return [
'list' => '角色列表',
'CreateNew' => '新增角色',
'EditRole' => '編輯角色',
'edit' => '編輯',
'delete' => '刪除',
@ -10,6 +11,9 @@ return [
'name' => '名稱',
'permissions' => '權限',
'role_name' =>'角色名稱',
'select_permissions'=>'選擇權限',
'create' => '新增',
'action' => '操作',
'view' => '查看',

View File

@ -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' => '提交',

View File

@ -3,6 +3,7 @@
return [
'list' => '使用者列表',
'CreateNew' => '新增使用者',
'EditUser' => '編輯使用者',
'edit' => '編輯',
'delete' => '刪除',
@ -14,6 +15,10 @@ return [
'status' => '狀態',
'role' =>'角色',
'select_gender'=>'選擇性別',
'select_status'=>'選擇狀態',
'select_role'=>'選擇角色',
'create' => '新增',
'action' => '操作',
'view' => '查看',

View File

@ -1,9 +1,9 @@
@if(auth()->user()->hasRole('Admin'))
<x-layouts.admin>
{{ $slot }}
</x-layouts.admin>
@else
@if(auth()->user()->hasRole('User'))
<x-layouts.user>
{{ $slot }}
</x-layouts.user>
@else
<x-layouts.admin>
{{ $slot }}
</x-layouts.admin>
@endif

View File

@ -1,20 +1,20 @@
<x-wireui:modal-card title="{{ $artistId ? '編輯歌手' : '新增歌手' }}" blur wire:model.defer="showCreateModal">
<x-wireui:modal-card title="{{ $artistId ? __('artists.EditArtist') : __('artists.CreateNew') }}" blur wire:model.defer="showCreateModal">
<div class="space-y-4">
<x-wireui:select
label="類別"
label="{{__('artists.category')}}"
wire:model.defer="fields.category"
placeholder="選擇類別"
placeholder="{{__('artists.select_category')}}"
:options="$categoryOptions"
option-label="name"
option-value="value"
/>
<x-wireui:input label="名稱" wire:model.defer="fields.name" />
<x-wireui:input label="{{__('artists.name')}}" wire:model.defer="fields.name" />
</div>
<x-slot name="footer">
<div class="flex justify-between w-full">
<x-wireui:button flat label="{{__('atrists.cancel')}}" @click="$wire.showCreateModal = false" />
<x-wireui:button primary type="submit" label="儲存" />
<x-wireui:button flat label="{{__('artists.cancel')}}" @click="$wire.showCreateModal = false" />
<x-wireui:button primary label="{{__('artists.submit')}}" wire:click="save" />
</div>
</x-slot>
</x-wireui:modal-card>

View File

@ -1,25 +1,21 @@
<div>
@if ($showCreateModal)
<x-wireui:modal-card title="{{ $roleId ? '編輯角色' : '新增角色' }}" blur wire:model.defer="showCreateModal">
<div class="space-y-4">
<x-wireui:input label="角色名稱" wire:model.defer="name" />
<x-wireui: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-wireui:modal-card title="{{ $roleId ? __('roles.EditRole') : __('roles.CreateNew') }}" blur wire:model.defer="showCreateModal">
<div class="space-y-4">
<x-wireui:input label="{{__('roles.role_name')}}" wire:model.defer="name" />
<x-wireui:select
label="{{__('roles.permissions')}}"
wire:model.defer="selectedPermissions"
placeholder="{{__('roles.select_permissions')}}"
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-between w-full">
<x-wireui:button flat label="{{__('roles.cancel')}}" @click="$wire.showCreateModal = false" />
<x-wireui:button primary label="{{__('roles.submit')}}" wire:click="save" />
</div>
</x-slot>
</x-wireui:modal-card>
@endif
</div>
<x-slot name="footer">
<div class="flex justify-between w-full">
<x-wireui:button flat label="{{__('roles.cancel')}}" @click="$wire.showCreateModal = false" />
<x-wireui:button primary label="{{__('roles.submit')}}" wire:click="save" />
</div>
</x-slot>
</x-wireui:modal-card>

View File

@ -1,17 +1,15 @@
<x-wireui:modal-card title="{{ $songId ? '編輯歌曲' : '新增歌曲' }}" wire:model.defer="showCreateModal">
<form wire:submit.prevent="save">
<x-wireui:modal-card title="{{ $songId ? __('songs.EditSong') : __('songs.CreateNew') }}" wire:model.defer="showCreateModal">
<div class="grid grid-cols-3 gap-4 sm:grid-cols-3">
<x-wireui:input label="歌曲編號" wire:model.defer="fields.id" required />
<x-wireui:input label="歌曲名稱" wire:model.defer="fields.name" required />
<x-wireui:input label="檔名" wire:model.defer="fields.filename" />
<x-wireui:input label="{{__('songs.id')}}" wire:model.defer="fields.id" required />
<x-wireui:input label="{{__('songs.name')}}" wire:model.defer="fields.name" required />
<x-wireui:input label="{{__('songs.filename')}}" wire:model.defer="fields.filename" />
</div>
<div class="grid grid-cols-3 gap-4 sm:grid-cols-3">
<x-wireui:select
label="歌手"
label="{{__('songs.artists')}}"
wire:model.defer="selectedArtists"
placeholder="輸入搜尋歌手"
placeholder="{{__('songs.select_artists')}}"
:async-data="route('api.artists.search')"
option-label="name"
option-value="id"
@ -19,56 +17,56 @@
hide-empty-message
/>
<x-wireui:select
label="語言類型"
label="{{__('songs.language_type')}}"
wire:model.defer="fields.language_type"
placeholder="選擇語言"
placeholder="{{__('songs.select_language_type')}}"
:options="$songLanguageType"
option-label="name"
option-value="value"
/>
<x-wireui:select
label="分類"
label="{{__('songs.categorys')}}"
wire:model.defer="selectedCategories"
:options="$songCategories->map(fn($c) => ['label' => $c->name, 'value' => $c->id])->toArray()"
option-label="label"
option-value="value"
multiselect
placeholder="選擇分類"
placeholder="{{__('songs.select_categorys')}}"
/>
</div>
<div class="grid grid-cols-3 gap-4 sm:grid-cols-3">
<x-wireui:select
label="情境"
label="{{__('songs.situation')}}"
wire:model.defer="fields.situation"
placeholder="選擇情境"
placeholder="{{__('songs.select_situation')}}"
:options="$songSituation"
option-label="name"
option-value="value"
/>
<x-wireui:input label="分貝增減" wire:model.defer="fields.db_change" />
<x-wireui:input label="新增日期" wire:model.defer="fields.adddate" type="date" />
<x-wireui:input label="{{__('songs.db_change')}}" wire:model.defer="fields.db_change" />
<x-wireui:input label="{{__('songs.adddate')}}" wire:model.defer="fields.adddate" type="date" />
</div>
<div class="grid grid-cols-2 gap-4 sm:grid-cols-2">
<x-wireui:toggle label="人聲" wire:model.defer="fields.vocal" />
<x-wireui:toggle label="啟用" wire:model.defer="fields.enable" />
<x-wireui:toggle label="{{__('songs.vocal')}}" wire:model.defer="fields.vocal" />
<x-wireui:toggle label="{{__('songs.enable')}}" wire:model.defer="fields.enable" />
</div>
<div class="grid grid-cols-2 gap-4 sm:grid-cols-2">
<x-wireui:input label="版權1" wire:model.defer="fields.copyright01" />
<x-wireui:input label="版權2" wire:model.defer="fields.copyright02" />
<x-wireui:input label="{{__('songs.copyright01')}}" wire:model.defer="fields.copyright01" />
<x-wireui:input label="{{__('songs.copyright02')}}" wire:model.defer="fields.copyright02" />
</div>
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
<x-wireui:textarea label="備註1" wire:model.defer="fields.note01" />
<x-wireui:textarea label="備註2" wire:model.defer="fields.note02" />
<x-wireui:textarea label="備註3" wire:model.defer="fields.note03" />
<x-wireui:textarea label="備註4" wire:model.defer="fields.note04" />
<x-wireui:textarea label="{{__('songs.note01')}}" wire:model.defer="fields.note01" />
<x-wireui:textarea label="{{__('songs.note02')}}" wire:model.defer="fields.note02" />
<x-wireui:textarea label="{{__('songs.note03')}}" wire:model.defer="fields.note03" />
<x-wireui:textarea label="{{__('songs.note04')}}" wire:model.defer="fields.note04" />
</div>
<x-slot name="footer">
<div class="flex justify-between w-full">
<x-wireui:button flat label="{{__('songs.cancel')}}" @click="$wire.showCreateModal = false" />
<x-wireui:button primary type="submit" label="儲存" />
<x-wireui:button primary label="{{__('songs.submit')}}" wire:click="save" />
</div>
</x-slot>

View File

@ -1,29 +1,29 @@
<x-wireui:modal-card title="{{ $userId ? '編輯使用者' : '新增使用者' }}" blur wire:model.defer="showCreateModal">
<x-wireui:modal-card title="{{ $userId ? __('users.EditUser') : __('users.CreateNew') }}" blur wire:model.defer="showCreateModal">
<div class="space-y-4">
<x-wireui:input label="名稱" wire:model.defer="fields.name" required />
<x-wireui:input label="{{__('users.name')}}" wire:model.defer="fields.name" required />
<x-wireui:input label="Email" wire:model.defer="fields.email" required />
<x-wireui:input label="Phone" wire:model.defer="fields.phone" />
<x-wireui:select
label="性別"
label="{{__('users.gender')}}"
wire:model.defer="fields.gender"
placeholder="選擇性別"
placeholder="{{__('users.select_gender')}}"
:options="$genderOptions"
option-label="name"
option-value="value"
/>
<x-wireui:select
label="狀態"
label="{{__('users.status')}}"
wire:model.defer="fields.status"
placeholder="選擇狀態"
placeholder="{{__('users.select_status')}}"
:options="$statusOptions"
option-label="name"
option-value="value"
/>
<x-wireui:select
label="角色"
label="{{__('users.role')}}"
wire:model.defer="selectedRoles"
placeholder="選擇角色"
placeholder="{{__('users.select_role')}}"
multiselect
option-label="label"
option-value="value"

View File

@ -22,10 +22,10 @@ new #[Layout('layouts.guest')] class extends Component
//$this->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);
}
}
}; ?>

7
routes/api.php Normal file
View File

@ -0,0 +1,7 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArtistController;
Route::get('/artists/search', [App\Http\Controllers\ArtistController::class, 'search'])->name('api.artists.search');

View File

@ -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');
});