diff --git a/app/Imports/ArtistDataImport.php b/app/Imports/ArtistDataImport.php new file mode 100644 index 0000000..3330ba2 --- /dev/null +++ b/app/Imports/ArtistDataImport.php @@ -0,0 +1,47 @@ +failCount++; + return null; + } + + try { + Artist::firstOrCreate( + ['name' => $name], + ['category' => ArtistCategory::tryFrom($category) ?? ArtistCategory::Unset] + ); + $this->successCount++; + } catch (\Throwable $e) { + $this->failCount++; + } + + return null; + } + public function headingRow(): int + { + return 1; + } +} \ No newline at end of file diff --git a/app/Livewire/Admin/ArtistForm.php b/app/Livewire/Admin/ArtistForm.php index 37f9d82..a1a793f 100644 --- a/app/Livewire/Admin/ArtistForm.php +++ b/app/Livewire/Admin/ArtistForm.php @@ -4,14 +4,19 @@ namespace App\Livewire\Admin; use Livewire\Component; use Illuminate\Validation\Rule; +use Illuminate\Support\Facades\Auth; use App\Models\Artist; use App\Enums\ArtistCategory; class ArtistForm extends Component { - protected $listeners = ['openCreateArtistModal','openEditArtistModal', 'deleteArtist']; + protected $listeners = ['openModal','closeModal', 'deleteArtist']; - public bool $showCreateModal = false; + public bool $canCreate; + public bool $canEdit; + public bool $canDelect; + + public bool $showModal = false; public ?int $artistId = null; public array $categoryOptions =[]; @@ -29,43 +34,64 @@ class ArtistForm extends Component 'name' => $category->labels(), 'value' => $category->value, ])->toArray(); + $this->canCreate = Auth::user()?->can('song-edit') ?? false; + $this->canEdit = Auth::user()?->can('song-edit') ?? false; + $this->canDelect = Auth::user()?->can('song-delete') ?? false; } - public function openCreateArtistModal() + public function openModal($id = null) { $this->resetFields(); - $this->showCreateModal = true; + + if ($id) { + $artist = Artist::findOrFail($id); + $this->artistId = $artist->id; + $this->fields = $artist->only(array_keys($this->fields)); + } + + $this->showModal = true; } - public function openEditArtistModal($id) + public function closeModal() { - $artist = Artist::findOrFail($id); - $this->artistId = $artist->id; - $this->fields = $artist->only(array_keys($this->fields)); - $this->showCreateModal = true; + $this->resetFields(); + $this->showModal = false; } - + public function save() { - if ($this->artistId) { - $artist = Artist::findOrFail($this->artistId); - $artist->update($this->fields); - session()->flash('message', '歌手已更新'); + if ($this->canEdit) { + $artist = Artist::findOrFail($this->artistId); + $artist->update($this->fields); + $this->dispatch('notify', [ + 'title' => '成功', + 'description' => '歌手已更新', + 'icon' => 'success', + ]); + } } else { - $artist = Artist::create($this->fields); - session()->flash('message', '歌手已新增'); + if ($canCreate) { + $artist = Artist::create($this->fields); + $this->dispatch('notify', [ + 'title' => '成功', + 'description' => '歌手已新增', + 'icon' => 'success', + ]); + } } - $this->resetFields(); - $this->showCreateModal = false; + $this->showModal = false; $this->dispatch('pg:eventRefresh-artist-table'); } public function deleteArtist($id) { - Artist::findOrFail($id)->delete(); - session()->flash('message', '歌手已刪除'); + if ($this->canDelect) { + Artist::findOrFail($id)->delete(); + session()->flash('message', '歌手已刪除'); + $this->dispatch('pg:eventRefresh-artist-table'); + } } public function resetFields() diff --git a/app/Livewire/Admin/ArtistImportData.php b/app/Livewire/Admin/ArtistImportData.php new file mode 100644 index 0000000..d14b4d4 --- /dev/null +++ b/app/Livewire/Admin/ArtistImportData.php @@ -0,0 +1,59 @@ +canCreate = Auth::user()?->can('song-edit') ?? false; + } + + public function openModal() + { + $this->showModal = true; + } + public function closeModal() + { + $this->showModal = false; + } + + public function import() + { + // 檢查檔案是否有上傳 + $this->validate([ + 'file' => 'required|file|mimes:csv,xlsx,xls' + ]); + if ($this->canCreate) { + $import = new ArtistDataImport(); + Excel::import($import, $this->file); + $success = $import->successCount; + $fail = $import->failCount; + $this->reset('file'); + $this->showModal =false; + session()->flash('message', '匯入完成:成功 $success 筆,失敗 $fail 筆。'); + } + } + + public function render() + { + return view('livewire.admin.artist-import-data'); + } + +} \ No newline at end of file diff --git a/app/Livewire/Admin/ArtistTable.php b/app/Livewire/Admin/ArtistTable.php index 68194e6..3142cfb 100644 --- a/app/Livewire/Admin/ArtistTable.php +++ b/app/Livewire/Admin/ArtistTable.php @@ -193,7 +193,7 @@ final class ArtistTable extends PowerGridComponent ->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]); + ->dispatchTo('admin.artist-form', 'openModal', ['id' => $row->id]); } if($this->canDelect){ $actions[] =Button::add('delete') diff --git a/config/excel.php b/config/excel.php new file mode 100644 index 0000000..c1fd34a --- /dev/null +++ b/config/excel.php @@ -0,0 +1,380 @@ + [ + + /* + |-------------------------------------------------------------------------- + | Chunk size + |-------------------------------------------------------------------------- + | + | When using FromQuery, the query is automatically chunked. + | Here you can specify how big the chunk should be. + | + */ + 'chunk_size' => 1000, + + /* + |-------------------------------------------------------------------------- + | Pre-calculate formulas during export + |-------------------------------------------------------------------------- + */ + 'pre_calculate_formulas' => false, + + /* + |-------------------------------------------------------------------------- + | Enable strict null comparison + |-------------------------------------------------------------------------- + | + | When enabling strict null comparison empty cells ('') will + | be added to the sheet. + */ + 'strict_null_comparison' => false, + + /* + |-------------------------------------------------------------------------- + | CSV Settings + |-------------------------------------------------------------------------- + | + | Configure e.g. delimiter, enclosure and line ending for CSV exports. + | + */ + 'csv' => [ + 'delimiter' => ',', + 'enclosure' => '"', + 'line_ending' => PHP_EOL, + 'use_bom' => false, + 'include_separator_line' => false, + 'excel_compatibility' => false, + 'output_encoding' => '', + 'test_auto_detect' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Worksheet properties + |-------------------------------------------------------------------------- + | + | Configure e.g. default title, creator, subject,... + | + */ + 'properties' => [ + 'creator' => '', + 'lastModifiedBy' => '', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'manager' => '', + 'company' => '', + ], + ], + + 'imports' => [ + + /* + |-------------------------------------------------------------------------- + | Read Only + |-------------------------------------------------------------------------- + | + | When dealing with imports, you might only be interested in the + | data that the sheet exists. By default we ignore all styles, + | however if you want to do some logic based on style data + | you can enable it by setting read_only to false. + | + */ + 'read_only' => true, + + /* + |-------------------------------------------------------------------------- + | Ignore Empty + |-------------------------------------------------------------------------- + | + | When dealing with imports, you might be interested in ignoring + | rows that have null values or empty strings. By default rows + | containing empty strings or empty values are not ignored but can be + | ignored by enabling the setting ignore_empty to true. + | + */ + 'ignore_empty' => false, + + /* + |-------------------------------------------------------------------------- + | Heading Row Formatter + |-------------------------------------------------------------------------- + | + | Configure the heading row formatter. + | Available options: none|slug|custom + | + */ + 'heading_row' => [ + 'formatter' => 'slug', + ], + + /* + |-------------------------------------------------------------------------- + | CSV Settings + |-------------------------------------------------------------------------- + | + | Configure e.g. delimiter, enclosure and line ending for CSV imports. + | + */ + 'csv' => [ + 'delimiter' => null, + 'enclosure' => '"', + 'escape_character' => '\\', + 'contiguous' => false, + 'input_encoding' => Csv::GUESS_ENCODING, + ], + + /* + |-------------------------------------------------------------------------- + | Worksheet properties + |-------------------------------------------------------------------------- + | + | Configure e.g. default title, creator, subject,... + | + */ + 'properties' => [ + 'creator' => '', + 'lastModifiedBy' => '', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'manager' => '', + 'company' => '', + ], + + /* + |-------------------------------------------------------------------------- + | Cell Middleware + |-------------------------------------------------------------------------- + | + | Configure middleware that is executed on getting a cell value + | + */ + 'cells' => [ + 'middleware' => [ + //\Maatwebsite\Excel\Middleware\TrimCellValue::class, + //\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class, + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Extension detector + |-------------------------------------------------------------------------- + | + | Configure here which writer/reader type should be used when the package + | needs to guess the correct type based on the extension alone. + | + */ + 'extension_detector' => [ + 'xlsx' => Excel::XLSX, + 'xlsm' => Excel::XLSX, + 'xltx' => Excel::XLSX, + 'xltm' => Excel::XLSX, + 'xls' => Excel::XLS, + 'xlt' => Excel::XLS, + 'ods' => Excel::ODS, + 'ots' => Excel::ODS, + 'slk' => Excel::SLK, + 'xml' => Excel::XML, + 'gnumeric' => Excel::GNUMERIC, + 'htm' => Excel::HTML, + 'html' => Excel::HTML, + 'csv' => Excel::CSV, + 'tsv' => Excel::TSV, + + /* + |-------------------------------------------------------------------------- + | PDF Extension + |-------------------------------------------------------------------------- + | + | Configure here which Pdf driver should be used by default. + | Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF + | + */ + 'pdf' => Excel::DOMPDF, + ], + + /* + |-------------------------------------------------------------------------- + | Value Binder + |-------------------------------------------------------------------------- + | + | PhpSpreadsheet offers a way to hook into the process of a value being + | written to a cell. In there some assumptions are made on how the + | value should be formatted. If you want to change those defaults, + | you can implement your own default value binder. + | + | Possible value binders: + | + | [x] Maatwebsite\Excel\DefaultValueBinder::class + | [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class + | [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class + | + */ + 'value_binder' => [ + 'default' => Maatwebsite\Excel\DefaultValueBinder::class, + ], + + 'cache' => [ + /* + |-------------------------------------------------------------------------- + | Default cell caching driver + |-------------------------------------------------------------------------- + | + | By default PhpSpreadsheet keeps all cell values in memory, however when + | dealing with large files, this might result into memory issues. If you + | want to mitigate that, you can configure a cell caching driver here. + | When using the illuminate driver, it will store each value in the + | cache store. This can slow down the process, because it needs to + | store each value. You can use the "batch" store if you want to + | only persist to the store when the memory limit is reached. + | + | Drivers: memory|illuminate|batch + | + */ + 'driver' => 'memory', + + /* + |-------------------------------------------------------------------------- + | Batch memory caching + |-------------------------------------------------------------------------- + | + | When dealing with the "batch" caching driver, it will only + | persist to the store when the memory limit is reached. + | Here you can tweak the memory limit to your liking. + | + */ + 'batch' => [ + 'memory_limit' => 60000, + ], + + /* + |-------------------------------------------------------------------------- + | Illuminate cache + |-------------------------------------------------------------------------- + | + | When using the "illuminate" caching driver, it will automatically use + | your default cache store. However if you prefer to have the cell + | cache on a separate store, you can configure the store name here. + | You can use any store defined in your cache config. When leaving + | at "null" it will use the default store. + | + */ + 'illuminate' => [ + 'store' => null, + ], + + /* + |-------------------------------------------------------------------------- + | Cache Time-to-live (TTL) + |-------------------------------------------------------------------------- + | + | The TTL of items written to cache. If you want to keep the items cached + | indefinitely, set this to null. Otherwise, set a number of seconds, + | a \DateInterval, or a callable. + | + | Allowable types: callable|\DateInterval|int|null + | + */ + 'default_ttl' => 10800, + ], + + /* + |-------------------------------------------------------------------------- + | Transaction Handler + |-------------------------------------------------------------------------- + | + | By default the import is wrapped in a transaction. This is useful + | for when an import may fail and you want to retry it. With the + | transactions, the previous import gets rolled-back. + | + | You can disable the transaction handler by setting this to null. + | Or you can choose a custom made transaction handler here. + | + | Supported handlers: null|db + | + */ + 'transactions' => [ + 'handler' => 'db', + 'db' => [ + 'connection' => null, + ], + ], + + 'temporary_files' => [ + + /* + |-------------------------------------------------------------------------- + | Local Temporary Path + |-------------------------------------------------------------------------- + | + | When exporting and importing files, we use a temporary file, before + | storing reading or downloading. Here you can customize that path. + | permissions is an array with the permission flags for the directory (dir) + | and the create file (file). + | + */ + 'local_path' => storage_path('framework/cache/laravel-excel'), + + /* + |-------------------------------------------------------------------------- + | Local Temporary Path Permissions + |-------------------------------------------------------------------------- + | + | Permissions is an array with the permission flags for the directory (dir) + | and the create file (file). + | If omitted the default permissions of the filesystem will be used. + | + */ + 'local_permissions' => [ + // 'dir' => 0755, + // 'file' => 0644, + ], + + /* + |-------------------------------------------------------------------------- + | Remote Temporary Disk + |-------------------------------------------------------------------------- + | + | When dealing with a multi server setup with queues in which you + | cannot rely on having a shared local temporary path, you might + | want to store the temporary file on a shared disk. During the + | queue executing, we'll retrieve the temporary file from that + | location instead. When left to null, it will always use + | the local path. This setting only has effect when using + | in conjunction with queued imports and exports. + | + */ + 'remote_disk' => null, + 'remote_prefix' => null, + + /* + |-------------------------------------------------------------------------- + | Force Resync + |-------------------------------------------------------------------------- + | + | When dealing with a multi server setup as above, it's possible + | for the clean up that occurs after entire queue has been run to only + | cleanup the server that the last AfterImportJob runs on. The rest of the server + | would still have the local temporary file stored on it. In this case your + | local storage limits can be exceeded and future imports won't be processed. + | To mitigate this you can set this config value to be true, so that after every + | queued chunk is processed the local temporary file is deleted on the server that + | processed it. + | + */ + 'force_resync_remote' => null, + ], +]; diff --git a/resources/lang/zh-tw/artists.php b/resources/lang/zh-tw/artists.php index d1e1234..fc5f555 100644 --- a/resources/lang/zh-tw/artists.php +++ b/resources/lang/zh-tw/artists.php @@ -3,8 +3,9 @@ return [ 'management' => '歌手管理', 'list' => '歌手列表', - 'CreateNew' => '新增歌手', + 'CreateNew' => '新增', 'EditArtist' => '編輯歌手', + 'ImportData' => '滙入', 'create_edit' => '新增 / 編輯', 'create' => '新增', 'edit' => '編輯', diff --git a/resources/views/livewire/admin/artist-form.blade.php b/resources/views/livewire/admin/artist-form.blade.php index 91f8725..3114986 100644 --- a/resources/views/livewire/admin/artist-form.blade.php +++ b/resources/views/livewire/admin/artist-form.blade.php @@ -1,4 +1,4 @@ - +
- +
diff --git a/resources/views/livewire/admin/artist-header.blade.php b/resources/views/livewire/admin/artist-header.blade.php index e86265b..fbbc99d 100644 --- a/resources/views/livewire/admin/artist-header.blade.php +++ b/resources/views/livewire/admin/artist-header.blade.php @@ -1,8 +1,15 @@ -
+
+ +
\ No newline at end of file diff --git a/resources/views/livewire/admin/artist-import-data.blade.php b/resources/views/livewire/admin/artist-import-data.blade.php new file mode 100644 index 0000000..ae378cf --- /dev/null +++ b/resources/views/livewire/admin/artist-import-data.blade.php @@ -0,0 +1,43 @@ + + + {{-- 說明區塊 --}} +
+

匯入格式說明

+

請依下列表格格式準備 CSV 檔案:

+ +
+ + + + + + + + + + + + + + + + + +
類別名稱
某男
某女
+
+ +

+ ※ 類別欄位僅可使用:其他。 +

+
+ + {{-- 檔案上傳 --}} + + + +
+ + +
+
+
\ No newline at end of file diff --git a/resources/views/livewire/admin/artists.blade.php b/resources/views/livewire/admin/artists.blade.php index c60cb48..b4a9f1a 100644 --- a/resources/views/livewire/admin/artists.blade.php +++ b/resources/views/livewire/admin/artists.blade.php @@ -1,6 +1,6 @@ - @if (session()->has('message')) + - @endif {{-- 單一 Livewire 元件,內含資料表與 Modal --}} + \ No newline at end of file