單機版 v.0.0.8 20250618
自動同步 SongLibraryCache 的表
This commit is contained in:
parent
9a89fc8273
commit
134b9de09d
@ -1,80 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\Song;
|
|
||||||
use App\Models\SongLibraryCache;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ExportSqliteSongLibraryCacheJob implements ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
// 安全清空資料(兼容 SQLite 和其他資料庫)
|
|
||||||
if (Schema::hasTable('song_library_cache')) {
|
|
||||||
if (DB::getDriverName() === 'sqlite') {
|
|
||||||
SongLibraryCache::query()->delete();
|
|
||||||
} else {
|
|
||||||
DB::table('song_library_cache')->truncate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$totalInserted = 0;
|
|
||||||
|
|
||||||
Song::with(['artists', 'categories'])->chunk(500, function ($songs) use (&$totalInserted) {
|
|
||||||
$rows = [];
|
|
||||||
|
|
||||||
foreach ($songs as $song) {
|
|
||||||
$sortedArtists = $song->artists->sortBy('id')->values();
|
|
||||||
$artistA = $sortedArtists->get(0);
|
|
||||||
$artistB = $sortedArtists->get(1);
|
|
||||||
|
|
||||||
$rows[] = [
|
|
||||||
'song_id' => $song->id,
|
|
||||||
'song_name' => $song->name,
|
|
||||||
'song_simplified' => $song->simplified,
|
|
||||||
'phonetic_abbr' => $song->phonetic_abbr ?? '',
|
|
||||||
'pinyin_abbr' => $song->pinyin_abbr ?? '',
|
|
||||||
'strokes_abbr' => $song->strokes_abbr ?? 0,
|
|
||||||
'song_number' => $song->song_number ?? 0,
|
|
||||||
'artistA' => $artistA?->name,
|
|
||||||
'artistB' => $artistB?->name,
|
|
||||||
'artistA_simplified' => $artistA?->simplified,
|
|
||||||
'artistB_simplified' => $artistB?->simplified,
|
|
||||||
'artistA_category' => $artistA?->category?->value ?? '未定義',
|
|
||||||
'artistB_category' => $artistB?->category?->value ?? '未定義',
|
|
||||||
'artist_category' => in_array(\App\Enums\ArtistCategory::Group->value, [
|
|
||||||
$artistA?->category?->value,
|
|
||||||
$artistB?->category?->value,
|
|
||||||
]) ? '團' : '未定義',
|
|
||||||
'song_filename' => $song->filename,
|
|
||||||
'song_category' => $song->categories->pluck('code')->unique()->sort()->implode(', '),
|
|
||||||
'language_name' => $song->language_type ?? '未定義',
|
|
||||||
'add_date' => $song->adddate,
|
|
||||||
'situation' => $song->situation?->value ?? '未定義',
|
|
||||||
'vocal' => $song->vocal,
|
|
||||||
'db_change' => $song->db_change,
|
|
||||||
'song_counts' => $song->song_counts ?? 0,
|
|
||||||
'updated_at' => now(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
collect($rows)->chunk(1000)->each(function ($chunk) use (&$totalInserted) {
|
|
||||||
SongLibraryCache::insert($chunk->toArray());
|
|
||||||
$totalInserted += $chunk->count();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// 你也可以 log 或通知插入結果
|
|
||||||
// logger("Exported {$totalInserted} songs to cache.");
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ namespace App\Jobs;
|
|||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
||||||
use App\Models\SongCategory;
|
use App\Models\SongCategory;
|
||||||
|
use App\Models\SongLibraryCache;
|
||||||
use App\Enums\ArtistCategory;
|
use App\Enums\ArtistCategory;
|
||||||
use App\Enums\SongLanguageType;
|
use App\Enums\SongLanguageType;
|
||||||
use App\Enums\SongSituation;
|
use App\Enums\SongSituation;
|
||||||
@ -23,11 +24,11 @@ class ImportSongChunkJob implements ShouldQueue
|
|||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
protected Collection $rows;
|
protected Collection $rows;
|
||||||
protected String $id;
|
protected string $id;
|
||||||
protected array $categoryMap = [];
|
protected array $categoryMap = [];
|
||||||
protected array $artistCache =[];
|
protected array $artistCache = [];
|
||||||
|
|
||||||
public function __construct(Collection $rows,String $id)
|
public function __construct(Collection $rows, string $id)
|
||||||
{
|
{
|
||||||
$this->rows = $rows;
|
$this->rows = $rows;
|
||||||
$this->id = $id;
|
$this->id = $id;
|
||||||
@ -36,13 +37,13 @@ class ImportSongChunkJob implements ShouldQueue
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
Log::warning('匯入啟動', [
|
Log::warning('匯入啟動', ['model' => "ImportSongChunkJob", 'rows_id' => $this->id]);
|
||||||
'model' => "ImportSongChunkJob",
|
|
||||||
'rows_id' =>$this->id,
|
|
||||||
]);
|
|
||||||
$ToInsert = [];
|
$ToInsert = [];
|
||||||
|
$songIdMap = [];
|
||||||
$artistMap = [];
|
$artistMap = [];
|
||||||
$categoryMap = [];
|
$categoryMap = [];
|
||||||
|
|
||||||
$pMap = [
|
$pMap = [
|
||||||
'\\\\SVR01\\DISK01\\' => 'DISK01\\',
|
'\\\\SVR01\\DISK01\\' => 'DISK01\\',
|
||||||
'\\\\SVR01\\DISK02\\' => 'DISK02\\',
|
'\\\\SVR01\\DISK02\\' => 'DISK02\\',
|
||||||
@ -54,53 +55,37 @@ class ImportSongChunkJob implements ShouldQueue
|
|||||||
'\\\\SVR01\\DISK08\\' => 'DISK08\\',
|
'\\\\SVR01\\DISK08\\' => 'DISK08\\',
|
||||||
'\\\\SVR01\\DISK09\\' => 'DISK09\\',
|
'\\\\SVR01\\DISK09\\' => 'DISK09\\',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($this->rows as $index => $row) {
|
foreach ($this->rows as $index => $row) {
|
||||||
$songId = trim($row['編號'] ?? '');
|
$songId = trim($row['編號'] ?? '');
|
||||||
|
if (!$songId || Song::where('id', $songId)->exists()) continue;
|
||||||
if (!$songId || Song::where('id', $songId)->exists()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 字元處理
|
$songName = $this->normalizeName($row['歌名'] ?? '');
|
||||||
$songName=$this->normalizeName($row['歌名'] ?? '');
|
$simplified = ChineseNameConverter::convertToSimplified($songName);
|
||||||
$simplified=ChineseNameConverter::convertToSimplified($songName);// 繁體轉簡體
|
$phoneticAbbr = $row->has('注音') ? trim($row['注音'] ?? '') : ChineseNameConverter::getKTVZhuyinAbbr($simplified);
|
||||||
if (!$row->has('注音')) {
|
$pinyinAbbr = $row->has('拼音') ? trim($row['拼音'] ?? '') : ChineseNameConverter::getKTVPinyinAbbr($simplified);
|
||||||
$phoneticAbbr = ChineseNameConverter::getKTVZhuyinAbbr($simplified);// 注音符號
|
|
||||||
} else {
|
$strokesAbbr = $row->has('kk3')
|
||||||
$phoneticAbbr = trim($row['注音'] ?? '');
|
? trim($row['kk3'] ?? 0)
|
||||||
}
|
: (preg_match('/\p{Han}/u', $songName[0] ?? '') ? ChineseStrokesConverter::getStrokes($songName[0]) : 0);
|
||||||
if (!$row->has('拼音')) {
|
|
||||||
$pinyinAbbr = ChineseNameConverter::getKTVPinyinAbbr($simplified);// 拼音首字母
|
$songNumber = $row->has('kk4')
|
||||||
} else {
|
? trim($row['kk4'] ?? 0)
|
||||||
$pinyinAbbr = trim($row['拼音'] ?? '');
|
: mb_strlen($songName, 'UTF-8');
|
||||||
}
|
|
||||||
if (!$row->has('kk3')) {//歌名第一個字筆畫
|
$disk = $pMap[trim($row['路徑01'] ?? '')] ?? '';
|
||||||
$chars = preg_split('//u', $songName, -1, PREG_SPLIT_NO_EMPTY);
|
|
||||||
$firstChar = $chars[0] ?? null;
|
|
||||||
$strokesAbbr=( $firstChar && preg_match('/\p{Han}/u', $firstChar) )? ChineseStrokesConverter::getStrokes($firstChar) : 0;
|
|
||||||
} else {
|
|
||||||
$strokesAbbr=trim($row['kk3'] ?? 0);
|
|
||||||
}
|
|
||||||
if (!$row->has('kk4')) {//歌名字數
|
|
||||||
$songNumber = mb_strlen($songName, 'UTF-8');
|
|
||||||
} else {
|
|
||||||
$songNumber=trim($row['kk4'] ?? 0);
|
|
||||||
}
|
|
||||||
$pathKey = trim($row['路徑01'] ?? '');
|
|
||||||
$disk = $pMap[$pathKey] ?? '';
|
|
||||||
$filename = trim($row['檔名'] ?? '');
|
$filename = trim($row['檔名'] ?? '');
|
||||||
// 準備 song 資料
|
|
||||||
$ToInsert[] = [
|
$ToInsert[] = [
|
||||||
'id' => $songId,
|
'id' => $songId,
|
||||||
'name' => $this->formatText($row['歌名']),
|
'name' => $this->formatText($row['歌名']),
|
||||||
'adddate' => $this->parseExcelDate($row['日期'] ?? null),
|
'adddate' => $this->parseExcelDate($row['日期'] ?? null),
|
||||||
'filename' => $disk . $filename,
|
'filename' => $disk . $filename,
|
||||||
//'language_type' => SongLanguageType::tryFrom(trim($row['語別'] ?? '')) ?? SongLanguageType::Unset,
|
|
||||||
'language_type' => SongLanguageType::fromLabelOrName($row['語別'] ?? ''),
|
'language_type' => SongLanguageType::fromLabelOrName($row['語別'] ?? ''),
|
||||||
'db_change' => trim($row['kk2'] ?? 0),//分貝增減
|
'db_change' => trim($row['kk2'] ?? 0),
|
||||||
'vocal' => trim($row['kk6'] ?? 0),//人聲
|
'vocal' => trim($row['kk6'] ?? 0),
|
||||||
'situation' => SongSituation::tryFrom(trim($row['kk7'] ?? '')) ?? SongSituation::Unset,//情境
|
'situation' => SongSituation::tryFrom(trim($row['kk7'] ?? '')) ?? SongSituation::Unset,
|
||||||
'copyright01' => trim($row['版權01'] ?? ''),
|
'copyright01' => trim($row['版權01'] ?? ''),
|
||||||
'copyright02' => trim($row['版權02'] ?? ''),
|
'copyright02' => trim($row['版權02'] ?? ''),
|
||||||
'note01' => trim($row['版權03'] ?? ''),
|
'note01' => trim($row['版權03'] ?? ''),
|
||||||
@ -116,67 +101,58 @@ class ImportSongChunkJob implements ShouldQueue
|
|||||||
'song_counts' => trim($row['點播次數'] ?? 0),
|
'song_counts' => trim($row['點播次數'] ?? 0),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 處理關聯 - 歌手
|
|
||||||
$artistIds = [];
|
|
||||||
foreach (['歌星A', '歌星B'] as $key) {
|
foreach (['歌星A', '歌星B'] as $key) {
|
||||||
$artistName = $this->normalizeName($row[$key] ?? '');
|
$artistName = $this->normalizeName($row[$key] ?? '');
|
||||||
if ($artistName === '') continue;
|
if ($artistName === '' || ($key === '歌星B' && $artistName === $this->normalizeName($row['歌星A'] ?? ''))) continue;
|
||||||
|
|
||||||
// 若是歌星B,且與歌星A相同,則跳過
|
|
||||||
if ($key === '歌星B' && $artistName === $this->normalizeName($row['歌星A'] ?? '')) continue;
|
|
||||||
|
|
||||||
$artistMap[$songId][] = $this->getOrCreateArtistId($artistName);
|
$artistMap[$songId][] = $this->getOrCreateArtistId($artistName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分類處理(多個用 , 分隔)
|
|
||||||
if (!empty($row['分類'])) {
|
if (!empty($row['分類'])) {
|
||||||
$categoryIds = [];
|
foreach (explode(',', $row['分類']) as $code) {
|
||||||
$codes = explode(',', $row['分類']);
|
|
||||||
foreach ($codes as $code) {
|
|
||||||
$code = trim($code);
|
$code = trim($code);
|
||||||
if (isset($this->categoryMap[$code])) {
|
if (isset($this->categoryMap[$code])) {
|
||||||
$categoryMap[$songId][] = $this->categoryMap[$code];
|
$categoryMap[$songId][] = $this->categoryMap[$code];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$songIdMap[] = $songId;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
\Log::error("Row {$index} failed: {$e->getMessage()}", [
|
Log::error("Row {$index} failed: {$e->getMessage()}", [
|
||||||
'row' => $row,
|
'row' => $row,
|
||||||
'trace' => $e->getTraceAsString()
|
'trace' => $e->getTraceAsString()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 寫入資料庫
|
|
||||||
Song::insert($ToInsert);
|
// ✅ 批次插入
|
||||||
// 同步關聯(建議可用事件或批次處理)
|
collect($ToInsert)->chunk(500)->each(fn($chunk) => Song::insert($chunk->toArray()));
|
||||||
foreach ($artistMap as $songId => $artistIds) {
|
|
||||||
$song = Song::find($songId);
|
// ✅ 一次撈資料並同步
|
||||||
if ($song) {
|
$songs = Song::whereIn('id', $songIdMap)->get()->keyBy('id');
|
||||||
$song->artists()->sync($artistIds);
|
|
||||||
}
|
foreach ($songs as $songId => $song) {
|
||||||
}
|
if (isset($artistMap[$songId])) {
|
||||||
foreach ($categoryMap as $songId => $categoryIds) {
|
$song->artists()->sync($artistMap[$songId]);
|
||||||
$song = Song::find($songId);
|
}
|
||||||
if ($song) {
|
if (isset($categoryMap[$songId])) {
|
||||||
$song->categories()->sync($categoryIds);
|
$song->categories()->sync($categoryMap[$songId]);
|
||||||
}
|
}
|
||||||
|
SongLibraryCache::syncFromSong($song);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getOrCreateArtistId(string $name): int
|
private function getOrCreateArtistId(string $name): int
|
||||||
{
|
{
|
||||||
if (isset($this->artistCache[$name])) {
|
if (isset($this->artistCache[$name])) {
|
||||||
return $this->artistCache[$name];
|
return $this->artistCache[$name];
|
||||||
}
|
}
|
||||||
|
|
||||||
$artist = Artist::firstOrCreate(
|
$artist = Artist::firstOrCreate(['name' => $name], ['category' => ArtistCategory::Unset]);
|
||||||
['name' => $name],
|
|
||||||
['category' => ArtistCategory::Unset]
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this->artistCache[$name] = $artist->id;
|
return $this->artistCache[$name] = $artist->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function normalizeName(?string $str): string
|
private function normalizeName(?string $str): string
|
||||||
{
|
{
|
||||||
return strtoupper(mb_convert_kana(trim($str ?? ''), 'as'));
|
return strtoupper(mb_convert_kana(trim($str ?? ''), 'as'));
|
||||||
}
|
}
|
||||||
@ -184,24 +160,21 @@ class ImportSongChunkJob implements ShouldQueue
|
|||||||
protected function formatText($value)
|
protected function formatText($value)
|
||||||
{
|
{
|
||||||
if (is_numeric($value) && $value < 1 && $value > 0) {
|
if (is_numeric($value) && $value < 1 && $value > 0) {
|
||||||
// 嘗試判斷為時間類型的小數,轉為時間字串
|
return \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value)->format('H:i');
|
||||||
$time = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value);
|
|
||||||
return $time->format('H:i');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return trim((string) $value);
|
return trim((string)$value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseExcelDate($value): ?string
|
private function parseExcelDate($value): ?string
|
||||||
{
|
{
|
||||||
if (is_numeric($value)) {
|
if (is_numeric($value)) {
|
||||||
return \Carbon\Carbon::createFromFormat('Y-m-d', '1900-01-01')
|
return \Carbon\Carbon::createFromFormat('Y-m-d', '1900-01-01')->addDays((int)$value - 2)->format('Y-m-d');
|
||||||
->addDays((int)$value - 2)
|
|
||||||
->format('Y-m-d');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return \Carbon\Carbon::parse($value)->format('Y-m-d');
|
return \Carbon\Carbon::parse($value)->format('Y-m-d');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,13 @@ use App\Enums\SongLanguageType;
|
|||||||
use App\Enums\SongSituation;
|
use App\Enums\SongSituation;
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
use App\Models\SongCategory;
|
use App\Models\SongCategory;
|
||||||
use App\Jobs\ExportSqliteSongLibraryCacheJob;
|
use App\Models\SongLibraryCache;
|
||||||
|
|
||||||
class SongForm extends Component
|
class SongForm extends Component
|
||||||
{
|
{
|
||||||
use WireUiActions;
|
use WireUiActions;
|
||||||
|
|
||||||
protected $listeners = ['openModal','closeModal', 'deleteSong','synchronous'];
|
protected $listeners = ['openModal','closeModal', 'deleteSong'];
|
||||||
|
|
||||||
public bool $canCreate;
|
public bool $canCreate;
|
||||||
public bool $canEdit;
|
public bool $canEdit;
|
||||||
@ -70,6 +70,10 @@ class SongForm extends Component
|
|||||||
'name' => $situation->labels(),
|
'name' => $situation->labels(),
|
||||||
'value' => $situation->value,
|
'value' => $situation->value,
|
||||||
])->toArray();
|
])->toArray();
|
||||||
|
$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 openModal($id = null)
|
public function openModal($id = null)
|
||||||
@ -91,16 +95,6 @@ class SongForm extends Component
|
|||||||
$this->resetFields();
|
$this->resetFields();
|
||||||
$this->showModal = false;
|
$this->showModal = false;
|
||||||
}
|
}
|
||||||
public function synchronous(): void
|
|
||||||
{
|
|
||||||
ExportSqliteSongLibraryCacheJob::dispatch();
|
|
||||||
|
|
||||||
$this->notification()->send([
|
|
||||||
'icon' => 'success',
|
|
||||||
'title' => '歌庫_Cache 同步',
|
|
||||||
'description' => '已經加入排程',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function save()
|
public function save()
|
||||||
{
|
{
|
||||||
@ -113,6 +107,7 @@ class SongForm extends Component
|
|||||||
// ⭐ 同步多對多關聯
|
// ⭐ 同步多對多關聯
|
||||||
$song->artists()->sync($this->selectedArtists ?? []);
|
$song->artists()->sync($this->selectedArtists ?? []);
|
||||||
$song->categories()->sync($this->selectedCategories ?? []);
|
$song->categories()->sync($this->selectedCategories ?? []);
|
||||||
|
SongLibraryCache::syncFromSong($song);
|
||||||
$this->notification()->send([
|
$this->notification()->send([
|
||||||
'icon' => 'success',
|
'icon' => 'success',
|
||||||
'title' => '成功',
|
'title' => '成功',
|
||||||
@ -126,6 +121,7 @@ class SongForm extends Component
|
|||||||
// ⭐ 同步多對多關聯
|
// ⭐ 同步多對多關聯
|
||||||
$song->artists()->sync($this->selectedArtists ?? []);
|
$song->artists()->sync($this->selectedArtists ?? []);
|
||||||
$song->categories()->sync($this->selectedCategories ?? []);
|
$song->categories()->sync($this->selectedCategories ?? []);
|
||||||
|
SongLibraryCache::syncFromSong($song);
|
||||||
$this->notification()->send([
|
$this->notification()->send([
|
||||||
'icon' => 'success',
|
'icon' => 'success',
|
||||||
'title' => '成功',
|
'title' => '成功',
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire\Forms;
|
namespace App\Livewire\Forms;
|
||||||
|
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
|
use App\Models\SongLibraryCache;
|
||||||
use App\Enums\SongLanguageType;
|
use App\Enums\SongLanguageType;
|
||||||
use App\Enums\SongSituation;
|
use App\Enums\SongSituation;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
@ -136,6 +137,7 @@ final class SongTable extends PowerGridComponent
|
|||||||
->add('note03')
|
->add('note03')
|
||||||
->add('note04')
|
->add('note04')
|
||||||
->add('enable')
|
->add('enable')
|
||||||
|
->add('song_counts')
|
||||||
->add('created_at_formatted', fn (Song $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s'));
|
->add('created_at_formatted', fn (Song $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,6 +175,8 @@ final class SongTable extends PowerGridComponent
|
|||||||
$column[]=Column::make(__('songs.note04'), 'note04')->sortable()->searchable()->hidden(true, false)
|
$column[]=Column::make(__('songs.note04'), 'note04')->sortable()->searchable()->hidden(true, false)
|
||||||
->editOnClick(hasPermission: $this->canEdit, dataField: 'note04', fallback: 'N/A', saveOnMouseOut: true);
|
->editOnClick(hasPermission: $this->canEdit, dataField: 'note04', fallback: 'N/A', saveOnMouseOut: true);
|
||||||
$column[]=Column::make(__('songs.enable'), 'enable')->toggleable(hasPermission: $this->canEdit, trueLabel: 'yes', falseLabel: 'no');
|
$column[]=Column::make(__('songs.enable'), 'enable')->toggleable(hasPermission: $this->canEdit, trueLabel: 'yes', falseLabel: 'no');
|
||||||
|
$column[]=Column::make(__('songs.song_counts'), 'song_counts')->sortable()->searchable()
|
||||||
|
->editOnClick(hasPermission: $this->canEdit, dataField: 'song_counts', fallback: 'N/A', saveOnMouseOut: true);
|
||||||
$column[]=Column::make('Created at', 'created_at_formatted', 'created_at')->sortable()->hidden(true, false);
|
$column[]=Column::make('Created at', 'created_at_formatted', 'created_at')->sortable()->hidden(true, false);
|
||||||
$column[]=Column::action(__('songs.actions'));
|
$column[]=Column::action(__('songs.actions'));
|
||||||
return $column;
|
return $column;
|
||||||
@ -197,6 +201,7 @@ final class SongTable extends PowerGridComponent
|
|||||||
Filter::inputText('note02')->placeholder(__('songs.note02')),
|
Filter::inputText('note02')->placeholder(__('songs.note02')),
|
||||||
Filter::inputText('note03')->placeholder(__('songs.note03')),
|
Filter::inputText('note03')->placeholder(__('songs.note03')),
|
||||||
Filter::inputText('note04')->placeholder(__('songs.note04')),
|
Filter::inputText('note04')->placeholder(__('songs.note04')),
|
||||||
|
Filter::number('song_counts'),
|
||||||
Filter::boolean('enable')->label('✅', '❌'),
|
Filter::boolean('enable')->label('✅', '❌'),
|
||||||
Filter::datetimepicker('created_at'),
|
Filter::datetimepicker('created_at'),
|
||||||
];
|
];
|
||||||
@ -229,7 +234,7 @@ final class SongTable extends PowerGridComponent
|
|||||||
public function onUpdatedEditable($id, $field, $value): void
|
public function onUpdatedEditable($id, $field, $value): void
|
||||||
{
|
{
|
||||||
if (in_array($field,[
|
if (in_array($field,[
|
||||||
'name','filename','db_change',
|
'name','filename','db_change','song_counts',
|
||||||
'copyright01','copyright02','note01','note02','note03','note04'
|
'copyright01','copyright02','note01','note02','note03','note04'
|
||||||
]) && $this->canEdit) {
|
]) && $this->canEdit) {
|
||||||
$this->noUpdated($id,$field,$value);
|
$this->noUpdated($id,$field,$value);
|
||||||
@ -247,6 +252,7 @@ final class SongTable extends PowerGridComponent
|
|||||||
if ($song) {
|
if ($song) {
|
||||||
$song->{$field} = $value;
|
$song->{$field} = $value;
|
||||||
$song->save(); // 明確觸發 saving
|
$song->save(); // 明確觸發 saving
|
||||||
|
SongLibraryCache::syncFromSong($song);
|
||||||
}
|
}
|
||||||
$this->notification()->send([
|
$this->notification()->send([
|
||||||
'icon' => 'success',
|
'icon' => 'success',
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\SongLibraryCache;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use App\Helpers\ChineseNameConverter;
|
use App\Helpers\ChineseNameConverter;
|
||||||
@ -83,7 +84,9 @@ class Song extends Model
|
|||||||
$song->strokes_abbr=($firstChar && preg_match('/\p{Han}/u', $firstChar)) ? ChineseStrokesConverter::getStrokes($firstChar) : 0;
|
$song->strokes_abbr=($firstChar && preg_match('/\p{Han}/u', $firstChar)) ? ChineseStrokesConverter::getStrokes($firstChar) : 0;
|
||||||
$song->song_number = mb_strlen($song->name, 'UTF-8');
|
$song->song_number = mb_strlen($song->name, 'UTF-8');
|
||||||
});
|
});
|
||||||
|
|
||||||
static::deleting(function (Song $song) {
|
static::deleting(function (Song $song) {
|
||||||
|
SongLibraryCache::where('song_id', $song->id)->delete();
|
||||||
// Detach 關聯資料
|
// Detach 關聯資料
|
||||||
$song->artists()->detach();
|
$song->artists()->detach();
|
||||||
$song->categories()->detach();
|
$song->categories()->detach();
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\Models\Song;
|
||||||
|
|
||||||
class SongLibraryCache extends Model
|
class SongLibraryCache extends Model
|
||||||
{
|
{
|
||||||
@ -39,4 +40,43 @@ class SongLibraryCache extends Model
|
|||||||
'updated_at',
|
'updated_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static function syncFromSong(Song $song): void
|
||||||
|
{
|
||||||
|
$song->load(['artists', 'categories']);
|
||||||
|
$sortedArtists = $song->artists->sortBy('id')->values();
|
||||||
|
$artistA = $sortedArtists->get(0);
|
||||||
|
$artistB = $sortedArtists->get(1);
|
||||||
|
|
||||||
|
static::updateOrCreate(
|
||||||
|
['song_id' => $song->id],
|
||||||
|
[
|
||||||
|
'song_name' => $song->name,
|
||||||
|
'song_simplified' => $song->simplified,
|
||||||
|
'phonetic_abbr' => $song->phonetic_abbr ?? '',
|
||||||
|
'pinyin_abbr' => $song->pinyin_abbr ?? '',
|
||||||
|
'strokes_abbr' => $song->strokes_abbr ?? 0,
|
||||||
|
'song_number' => $song->song_number ?? 0,
|
||||||
|
'artistA' => $artistA?->name,
|
||||||
|
'artistB' => $artistB?->name,
|
||||||
|
'artistA_simplified' => $artistA?->simplified,
|
||||||
|
'artistB_simplified' => $artistB?->simplified,
|
||||||
|
'artistA_category' => $artistA?->category?->value ?? '未定義',
|
||||||
|
'artistB_category' => $artistB?->category?->value ?? '未定義',
|
||||||
|
'artist_category' => in_array(\App\Enums\ArtistCategory::Group->value, [
|
||||||
|
$artistA?->category?->value,
|
||||||
|
$artistB?->category?->value,
|
||||||
|
]) ? '團' : '未定義',
|
||||||
|
'song_filename' => $song->filename,
|
||||||
|
'song_category' => $song->categories->pluck('code')->unique()->sort()->implode(', '),
|
||||||
|
'language_name' => $song->language_type ?? '未定義',
|
||||||
|
'add_date' => $song->adddate,
|
||||||
|
'situation' => $song->situation?->value ?? '未定義',
|
||||||
|
'vocal' => $song->vocal,
|
||||||
|
'db_change' => $song->db_change,
|
||||||
|
'song_counts' => $song->song_counts ?? 0,
|
||||||
|
'updated_at' => now(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ return [
|
|||||||
'vocal' => '人聲',
|
'vocal' => '人聲',
|
||||||
'situation' => '情境',
|
'situation' => '情境',
|
||||||
'simplified' => '歌名簡體',
|
'simplified' => '歌名簡體',
|
||||||
|
'song_counts' => '點播次數',
|
||||||
|
|
||||||
|
|
||||||
'select_artists' =>'輸入搜尋歌手',
|
'select_artists' =>'輸入搜尋歌手',
|
||||||
|
@ -12,11 +12,4 @@
|
|||||||
label="{{ __('songs.ImportData') }}"
|
label="{{ __('songs.ImportData') }}"
|
||||||
class="bg-green-600 text-white"
|
class="bg-green-600 text-white"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<x-wireui:button
|
|
||||||
wire:click="$dispatchTo('forms.song-form','synchronous')"
|
|
||||||
icon="document-plus"
|
|
||||||
label="同步歌庫"
|
|
||||||
class="bg-green-600 text-white"
|
|
||||||
/>
|
|
||||||
</x-admin.section-header>
|
</x-admin.section-header>
|
5
開發手冊.ini
5
開發手冊.ini
@ -41,3 +41,8 @@ IP
|
|||||||
3F;pc311,pc312,pc313,pc315,pc316,pc317,pc318,pc319,pc320
|
3F;pc311,pc312,pc313,pc315,pc316,pc317,pc318,pc319,pc320
|
||||||
3F;pc321,pc322,pc323,
|
3F;pc321,pc322,pc323,
|
||||||
9F;pc901,pc902,pc903,pc910
|
9F;pc901,pc902,pc903,pc910
|
||||||
|
|
||||||
|
3F;svr01,svr02,svr03,svr04,svr05
|
||||||
|
3F;pc301,pc302,pc303,pc305,pc306,pc307,pc308,pc309,pc310
|
||||||
|
3F;pc311,pc312,pc313,pc315,pc316,pc317,pc318,pc319,pc320
|
||||||
|
3F;pc321,pc322,pc323
|
||||||
|
Loading…
x
Reference in New Issue
Block a user