歌曲加入滙入功能

This commit is contained in:
allen.yan 2025-05-05 16:54:21 +08:00
parent ac8a4f2eb0
commit f81ebcdc87
12 changed files with 945 additions and 20 deletions

View File

@ -0,0 +1,125 @@
<?php
namespace App\Imports;
use App\Models\Song;
use App\Models\Artist;
use App\Models\SongCategory;
use App\Enums\ArtistCategory;
use App\Enums\SongLanguageType;
use App\Enums\SongSituation;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithChunkReading;
class SongDataImport implements ToCollection, WithHeadingRow, WithChunkReading
{
protected array $artistCache = [];
protected array $categoryMap = [];
public function __construct()
{
// 快取分類代碼對應 ID
$this->categoryMap = SongCategory::pluck('id', 'code')->toArray();
}
public function collection(Collection $rows)
{
$songsToInsert = [];
$artistMap = []; // [song_id => [artist_id]]
$categoryMap = []; // [song_id => [category_id]]
foreach ($rows as $row) {
$songId = trim($row['編號'] ?? '');
if (!$songId) {
continue;
}
// 準備 song 資料
$songsToInsert[] = [
'id' => $songId,
'name' => trim($row['歌名'] ?? ''),
'adddate' => trim($row['日期'] ?? null),
'filename' => trim($row['檔名'] ?? ''),
'language_type' => SongLanguageType::tryFrom(trim($row['語別'] ?? '')) ?? SongLanguageType::Unset,
'db_change' => trim($row['分貝增減'] ?? 0),
'vocal' => trim($row['人聲'] ?? 0),
'situation' => SongSituation::tryFrom(trim($row['情境'] ?? '')) ?? SongSituation::Unset,
'copyright01' => trim($row['版權01'] ?? ''),
'copyright02' => trim($row['版權02'] ?? ''),
'note01' => trim($row['備註01'] ?? ''),
'note02' => trim($row['備註02'] ?? ''),
'note03' => trim($row['備註03'] ?? ''),
'note04' => trim($row['備註04'] ?? ''),
'enable' => trim($row['狀態'] ?? 1),
'song_counts' => trim($row['點播次數'] ?? 0),
];
// 歌星 A/B 處理
foreach (['歌星A', '歌星B'] as $key) {
$artistName = trim($row[$key] ?? '');
if ($artistName === '') continue;
$artistId = $this->getOrCreateArtistId($artistName);
$artistMap[$songId][] = $artistId;
}
// 分類處理(多個用 , 分隔)
if (!empty($row['分類'])) {
$codes = explode(',', $row['分類']);
foreach ($codes as $code) {
$code = trim($code);
if (isset($this->categoryMap[$code])) {
$categoryMap[$songId][] = $this->categoryMap[$code];
}
}
}
}
// 寫入資料庫
Song::insert($songsToInsert);
// 同步關聯(建議可用事件或批次處理)
foreach ($artistMap as $songId => $artistIds) {
$song = Song::find($songId);
if ($song) {
$song->artists()->sync($artistIds);
}
}
foreach ($categoryMap as $songId => $categoryIds) {
$song = Song::find($songId);
if ($song) {
$song->categories()->sync($categoryIds);
}
}
}
protected function getOrCreateArtistId(string $name): int
{
if (isset($this->artistCache[$name])) {
return $this->artistCache[$name];
}
$artist = Artist::firstOrCreate(
['name' => $name],
['category' => ArtistCategory::Unset]
);
return $this->artistCache[$name] = $artist->id;
}
public function chunkSize(): int
{
return 1000;
}
public function headingRow(): int
{
return 1;
}
}

View File

@ -11,9 +11,9 @@ use App\Models\SongCategory;
class SongForm extends Component
{
protected $listeners = ['openCreateSongModal','openEditSongModal', 'deleteSong'];
protected $listeners = ['openModal','closeModal', 'deleteSong'];
public bool $showCreateModal = false;
public bool $showModal = false;
public $songCategories =[];
public $songLanguageType =[];
@ -61,21 +61,24 @@ class SongForm extends Component
])->toArray();
}
public function openCreateSongModal()
public function openModal($id = null)
{
$this->resetFields();
$this->showCreateModal = true;
if ($id) {
$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->showModal = true;
}
public function openEditSongModal($id)
public function closeModal()
{
$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;
$this->resetFields();
$this->showModal = false;
}
public function save()
@ -95,7 +98,7 @@ class SongForm extends Component
$song->categories()->sync($this->selectedCategories ?? []);
$this->resetFields();
$this->showCreateModal = false;
$this->showModal = false;
$this->dispatch('pg:eventRefresh-song-table');
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Livewire\Admin;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use Livewire\WithFileUploads;
use Maatwebsite\Excel\Facades\Excel;
use App\Imports\SongDataImport;
class SongImportData extends Component
{
use WithFileUploads;
protected $listeners = ['openModal','closeModal'];
public bool $canCreate;
public bool $showModal = false;
public $file;
public function mount()
{
$this->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 SongDataImport();
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.song-import-data');
}
}

View File

@ -31,6 +31,7 @@ class Song extends Model
'phonetic_abbr',
'pinyin_abbr',
'strokes_abbr',
'song_counts',
];
protected function casts(): array

View File

@ -11,6 +11,7 @@
"laravel/tinker": "^2.10.1",
"livewire/livewire": "^3.4",
"livewire/volt": "^1.7.0",
"maatwebsite/excel": "^3.1",
"openspout/openspout": "^4.0",
"overtrue/php-opencc": "^1.2",
"overtrue/pinyin": "^5.3",

595
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6659720a36b980b544687668bfd4b880",
"content-hash": "d5d6344c1be46378bdaa0bfecd2e300e",
"packages": [
{
"name": "brick/math",
@ -135,6 +135,166 @@
],
"time": "2024-02-09T16:56:22+00:00"
},
{
"name": "composer/pcre",
"version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<1.11.10"
},
"require-dev": {
"phpstan/phpstan": "^1.12 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2",
"phpunit/phpunit": "^8 || ^9"
},
"type": "library",
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
"PCRE",
"preg",
"regex",
"regular expression"
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.3.2"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2024-11-12T16:29:46+00:00"
},
{
"name": "composer/semver",
"version": "3.4.3",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.11",
"symfony/phpunit-bridge": "^3 || ^7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Semver\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.4.3"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2024-09-19T14:15:21+00:00"
},
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
@ -510,6 +670,67 @@
],
"time": "2025-03-06T22:45:56+00:00"
},
{
"name": "ezyang/htmlpurifier",
"version": "v4.18.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "cb56001e54359df7ae76dc522d08845dc741621b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b",
"reference": "cb56001e54359df7ae76dc522d08845dc741621b",
"shasum": ""
},
"require": {
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"require-dev": {
"cerdic/css-tidy": "^1.7 || ^2.0",
"simpletest/simpletest": "dev-master"
},
"suggest": {
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
"ext-bcmath": "Used for unit conversion and imagecrash protection",
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
"ext-tidy": "Used for pretty-printing HTML"
},
"type": "library",
"autoload": {
"files": [
"library/HTMLPurifier.composer.php"
],
"psr-0": {
"HTMLPurifier": "library/"
},
"exclude-from-classmap": [
"/library/HTMLPurifier/Language/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Edward Z. Yang",
"email": "admin@htmlpurifier.org",
"homepage": "http://ezyang.com"
}
],
"description": "Standards compliant HTML filter written in PHP",
"homepage": "http://htmlpurifier.org/",
"keywords": [
"html"
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0"
},
"time": "2024-11-01T03:51:45+00:00"
},
{
"name": "fruitcake/php-cors",
"version": "v1.3.0",
@ -2154,6 +2375,272 @@
},
"time": "2025-04-08T15:13:36+00:00"
},
{
"name": "maatwebsite/excel",
"version": "3.1.64",
"source": {
"type": "git",
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
"reference": "e25d44a2d91da9179cd2d7fec952313548597a79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/e25d44a2d91da9179cd2d7fec952313548597a79",
"reference": "e25d44a2d91da9179cd2d7fec952313548597a79",
"shasum": ""
},
"require": {
"composer/semver": "^3.3",
"ext-json": "*",
"illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0",
"php": "^7.0||^8.0",
"phpoffice/phpspreadsheet": "^1.29.9",
"psr/simple-cache": "^1.0||^2.0||^3.0"
},
"require-dev": {
"laravel/scout": "^7.0||^8.0||^9.0||^10.0",
"orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0",
"predis/predis": "^1.1"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Excel": "Maatwebsite\\Excel\\Facades\\Excel"
},
"providers": [
"Maatwebsite\\Excel\\ExcelServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Maatwebsite\\Excel\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Patrick Brouwers",
"email": "patrick@spartner.nl"
}
],
"description": "Supercharged Excel exports and imports in Laravel",
"keywords": [
"PHPExcel",
"batch",
"csv",
"excel",
"export",
"import",
"laravel",
"php",
"phpspreadsheet"
],
"support": {
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.64"
},
"funding": [
{
"url": "https://laravel-excel.com/commercial-support",
"type": "custom"
},
{
"url": "https://github.com/patrickbrouwers",
"type": "github"
}
],
"time": "2025-02-24T11:12:50+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "3.1.2",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-zlib": "*",
"php-64bit": "^8.2"
},
"require-dev": {
"brianium/paratest": "^7.7",
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^3.16",
"guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^11.0",
"vimeo/psalm": "^6.0"
},
"suggest": {
"guzzlehttp/psr7": "^2.4",
"psr/http-message": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"ZipStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Duncan",
"email": "pabs@pablotron.org"
},
{
"name": "Jonatan Männchen",
"email": "jonatan@maennchen.ch"
},
{
"name": "Jesse Donat",
"email": "donatj@gmail.com"
},
{
"name": "András Kolesár",
"email": "kolesar@kolesar.hu"
}
],
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
"keywords": [
"stream",
"zip"
],
"support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
},
"funding": [
{
"url": "https://github.com/maennchen",
"type": "github"
}
],
"time": "2025-01-27T12:07:53+00:00"
},
{
"name": "markbaker/complex",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
},
"time": "2022-12-06T16:21:08+00:00"
},
{
"name": "markbaker/matrix",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"sebastian/phpcpd": "^4.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
},
"time": "2022-12-02T22:17:43+00:00"
},
{
"name": "monolog/monolog",
"version": "3.9.0",
@ -2897,6 +3384,112 @@
],
"time": "2025-03-16T02:16:27+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.29.10",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "c80041b1628c4f18030407134fe88303661d4e4e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/c80041b1628c4f18030407134fe88303661d4e4e",
"reference": "c80041b1628c4f18030407134fe88303661d4e4e",
"shasum": ""
},
"require": {
"composer/pcre": "^1||^2||^3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"ezyang/htmlpurifier": "^4.15",
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": "^7.4 || ^8.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.0",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.10"
},
"time": "2025-02-08T02:56:14+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.3",

View File

@ -3,9 +3,9 @@
return [
'management' => '歌手管理',
'list' => '歌手列表',
'CreateNew' => '新增',
'CreateNew' => '新增歌手',
'EditArtist' => '編輯歌手',
'ImportData' => '滙入',
'ImportData' => '滙入歌手',
'create_edit' => '新增 / 編輯',
'create' => '新增',
'edit' => '編輯',

View File

@ -5,6 +5,7 @@ return [
'list' => '歌曲列表',
'CreateNew' => '新增歌曲',
'EditSong' => '編輯歌曲',
'ImportData' => '滙入歌曲',
'create_edit' => '新增 / 編輯',
'create' => '新增',
'edit' => '編輯',

View File

@ -1,4 +1,4 @@
<x-wireui:modal-card title="{{ $songId ? __('songs.EditSong') : __('songs.CreateNew') }}" wire:model.defer="showCreateModal">
<x-wireui:modal-card title="{{ $songId ? __('songs.EditSong') : __('songs.CreateNew') }}" wire:model.defer="showModal">
<div class="grid grid-cols-3 gap-4 sm:grid-cols-3">
<x-wireui:input label="{{__('songs.id')}}" wire:model.defer="fields.id" required />
@ -65,7 +65,7 @@
<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 flat label="{{__('songs.cancel')}}" wire:click="closeModal" />
<x-wireui:button primary label="{{__('songs.submit')}}" wire:click="save" />
</div>
</x-slot>

View File

@ -1,8 +1,15 @@
<div class="flex justify-end mb-2">
<div class="flex justify-end mb-2 mr-2 mt-2 sm:mt-0 gap-3">
<x-wireui:button
wire:click="$dispatchTo('admin.song-form', 'openCreateSongModal')"
icon="plus"
label="{{ __('songs.CreateNew') }}"
class="bg-blue-600 text-white"
/>
<x-wireui:button
wire:click="$dispatchTo('admin.song-import-data','openModal')"
icon="document-plus"
label="{{ __('songs.ImportData') }}"
class="bg-green-600 text-white"
/>
</div>

View File

@ -0,0 +1,134 @@
<x-wireui:modal-card title="{{ __('songs.ImportData') }}" blur wire:model.defer="showModal">
{{-- resources/views/components/import-format-info.blade.php --}}
<div class="mb-4 p-4 bg-gray-100 border border-gray-300 rounded text-sm text-gray-700">
<p class="font-semibold mb-2">匯入格式說明</p>
<p class="mb-2">請依下列表格格式準備 Excel CSV 檔案:</p>
<div class="overflow-x-auto mb-2">
<table class="min-w-full text-sm text-left border border-collapse border-gray-300">
<thead class="bg-gray-200">
<tr>
<th class="border border-gray-300 px-3 py-1">欄位名稱</th>
<th class="border border-gray-300 px-3 py-1">說明</th>
<th class="border border-gray-300 px-3 py-1">範例</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border border-gray-300 px-3 py-1">編號</td>
<td class="border border-gray-300 px-3 py-1">歌曲唯一編號</td>
<td class="border border-gray-300 px-3 py-1">993794</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">歌名</td>
<td class="border border-gray-300 px-3 py-1">歌曲名稱</td>
<td class="border border-gray-300 px-3 py-1">小幸運</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">檔名</td>
<td class="border border-gray-300 px-3 py-1">歌曲檔名</td>
<td class="border border-gray-300 px-3 py-1">993794.mpg</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">日期</td>
<td class="border border-gray-300 px-3 py-1">新增日期格式YYYY-MM-DD</td>
<td class="border border-gray-300 px-3 py-1">2015-08-14</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">歌星A</td>
<td class="border border-gray-300 px-3 py-1">可填入歌手名稱</td>
<td class="border border-gray-300 px-3 py-1">田馥甄</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">歌星B</td>
<td class="border border-gray-300 px-3 py-1">可填入歌手名稱</td>
<td class="border border-gray-300 px-3 py-1"></td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">語別</td>
<td class="border border-gray-300 px-3 py-1">可用語別:國語、台語、英語、日語、粵語、韓語、越語、客語、其他 </td>
<td class="border border-gray-300 px-3 py-1">國語</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">分類</td>
<td class="border border-gray-300 px-3 py-1">可填入多個分類代碼(用 , 分隔A1(情歌),B1(選秀),C1(串燒),D1(90),E1(懷念),F1(大陸)</td>
<td class="border border-gray-300 px-3 py-1">A1,B1,C1,D1,E1,F1</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">分貝增減</td>
<td class="border border-gray-300 px-3 py-1">歌曲播放時的音量調整(正數為增加,負數為減少)</td>
<td class="border border-gray-300 px-3 py-1">-2</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">人聲</td>
<td class="border border-gray-300 px-3 py-1">是否有人聲1有人聲0純音樂</td>
<td class="border border-gray-300 px-3 py-1">1</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">情境</td>
<td class="border border-gray-300 px-3 py-1">可標註使用情境,例如:婚禮、派對、練唱</td>
<td class="border border-gray-300 px-3 py-1"></td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">版權01</td>
<td class="border border-gray-300 px-3 py-1">版權擁有者或公司名稱</td>
<td class="border border-gray-300 px-3 py-1">華研國際</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">版權02</td>
<td class="border border-gray-300 px-3 py-1">次要版權擁有者或管理單位</td>
<td class="border border-gray-300 px-3 py-1">滾石唱片</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">備註01</td>
<td class="border border-gray-300 px-3 py-1">其他補充資訊,例如:原唱者、翻唱說明等</td>
<td class="border border-gray-300 px-3 py-1">原唱:田馥甄</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">備註02</td>
<td class="border border-gray-300 px-3 py-1">其他補充資訊</td>
<td class="border border-gray-300 px-3 py-1">翻唱版</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">備註03</td>
<td class="border border-gray-300 px-3 py-1">其他補充資訊</td>
<td class="border border-gray-300 px-3 py-1">高音質</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">備註04</td>
<td class="border border-gray-300 px-3 py-1">其他補充資訊</td>
<td class="border border-gray-300 px-3 py-1">2023重錄版</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">狀態</td>
<td class="border border-gray-300 px-3 py-1">是否啟用1啟用0停用</td>
<td class="border border-gray-300 px-3 py-1">1</td>
</tr>
<tr>
<td class="border border-gray-300 px-3 py-1">點播次數</td>
<td class="border border-gray-300 px-3 py-1">記錄此歌曲被點播的次數</td>
<td class="border border-gray-300 px-3 py-1">128</td>
</tr>
</tbody>
</table>
</div>
<p class="text-gray-600 text-xs">
語別、情境、分類等欄位請依系統定義填寫,避免導致匯入失敗。
</p>
<p class="text-gray-600 text-xs">
分類代碼請參照後台歌曲分類列表。
</p>
</div>
{{-- 檔案上傳 --}}
<input type="file" wire:model="file" accept=".csv, .xls, .xlsx" class="mb-4 w-full" />
<x-slot name="footer">
<div class="flex justify-between w-full">
<x-wireui:button flat label="{{ __('songs.cancel') }}" wire:click="closeModal" />
<x-wireui:button primary label="{{ __('songs.submit') }}" wire:click="import" />
</div>
</x-slot>
</x-wireui:modal-card>

View File

@ -14,4 +14,5 @@
{{-- 單一 Livewire 元件,內含資料表與 Modal --}}
<livewire:admin.song-table />
<livewire:admin.song-form />
<livewire:admin.song-import-data />
</x-layouts.admin>