From 55666c14759d8000b123d70731fa60cf9b557730 Mon Sep 17 00:00:00 2001 From: "allen.yan" Date: Wed, 27 Aug 2025 13:03:26 +0800 Subject: [PATCH] =?UTF-8?q?202508271300=20=E5=8A=A0=E5=85=A5=E9=A6=96?= =?UTF-8?q?=E9=A0=81=EF=BC=8C=E7=86=B1=E9=96=80=EF=BC=8C=E6=9C=80=E6=96=B0?= =?UTF-8?q?=20=E9=A0=81=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 +- app/Enums/SongLanguageType.php | 60 +++++++ app/Enums/Traits/HasLabels.php | 20 +++ app/Livewire/Pages/SearchSong.php | 84 +++++++++ app/Models/Artist.php | 55 ++++++ app/Models/Song.php | 96 ++++++++++ app/Models/SongLibraryCache.php | 46 +++++ resources/lang/zh-tw/enums.php | 38 ++++ .../components/button/flat-card.blade.php | 14 ++ resources/views/components/table.blade.php | 27 +++ .../livewire/pages/search-song.blade.php | 55 ++++++ resources/views/new-songs.blade.php | 12 ++ resources/views/search-song.blade.php | 9 + resources/views/top-ranking.blade.php | 12 ++ resources/views/welcome.blade.php | 165 +++--------------- routes/web.php | 6 +- 16 files changed, 555 insertions(+), 146 deletions(-) create mode 100644 app/Enums/SongLanguageType.php create mode 100644 app/Enums/Traits/HasLabels.php create mode 100644 app/Livewire/Pages/SearchSong.php create mode 100644 app/Models/Artist.php create mode 100644 app/Models/Song.php create mode 100644 app/Models/SongLibraryCache.php create mode 100644 resources/lang/zh-tw/enums.php create mode 100644 resources/views/components/button/flat-card.blade.php create mode 100644 resources/views/components/table.blade.php create mode 100644 resources/views/livewire/pages/search-song.blade.php create mode 100644 resources/views/new-songs.blade.php create mode 100644 resources/views/search-song.blade.php create mode 100644 resources/views/top-ranking.blade.php diff --git a/.env.example b/.env.example index babd677..691e16f 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -APP_NAME=Laravel +APP_NAME=手機點歌 APP_ENV=local APP_KEY= APP_DEBUG=true diff --git a/app/Enums/SongLanguageType.php b/app/Enums/SongLanguageType.php new file mode 100644 index 0000000..64417fd --- /dev/null +++ b/app/Enums/SongLanguageType.php @@ -0,0 +1,60 @@ + __('enums.song.LanguageType.Mandarin'), + self::Taiwanese => __('enums.song.LanguageType.Taiwanese'), + self::English => __('enums.song.LanguageType.English'), + self::Japanese => __('enums.song.LanguageType.Japanese'), + self::Cantonese => __('enums.song.LanguageType.Cantonese'), + self::Korean => __('enums.song.LanguageType.Korean'), + self::Vietnamese => __('enums.song.LanguageType.Vietnamese'), + self::Hakka => __('enums.song.LanguageType.Hakka'), + self::Other => __('enums.song.LanguageType.Other'), + }; + } + public static function fromLabelOrName(string $input): self + { + $aliasMap = [ + '英文' => '英語', + '華語' => '國語', + '普通話' => '國語', + '台灣話' => '台語', + '客家話' => '客語', + ]; + // 將別名轉為正式值 + $input = $aliasMap[$input] ?? $input; + foreach (self::cases() as $case) { + if ($case->value === $input || $case->name === $input) { + return $case; + } + } + return self::Unset; + } + public static function options(): array + { + return collect(self::cases()) + ->mapWithKeys(fn($case) => [$case->value => $case->labels()]) + ->toArray(); + } +} \ No newline at end of file diff --git a/app/Enums/Traits/HasLabels.php b/app/Enums/Traits/HasLabels.php new file mode 100644 index 0000000..ca0662a --- /dev/null +++ b/app/Enums/Traits/HasLabels.php @@ -0,0 +1,20 @@ +mapWithKeys(function (self $case) { + return [$case->value => $case->labels()]; + }); + } + + public function labelPowergridFilter(): string + { + return $this->labels(); + } +} \ No newline at end of file diff --git a/app/Livewire/Pages/SearchSong.php b/app/Livewire/Pages/SearchSong.php new file mode 100644 index 0000000..bc234ae --- /dev/null +++ b/app/Livewire/Pages/SearchSong.php @@ -0,0 +1,84 @@ +searchCategory = $searchCategory; + } + + /** + * 切換語言標籤 + */ + public function selectLanguage(string $lang): void + { + $this->selectedLanguage = $lang; + } + //public function updatedSearch() + //{ + // dd($this->search); + //} + + /** + * 點歌 + */ + public function orderSong(int $songId): void + { + // TODO: 加入已點歌曲邏輯,例如: + // auth()->user()->room->addSong($songId); + + $this->dispatchBrowserEvent('notify', [ + 'message' => '已加入已點歌曲' + ]); + } + + /** + * 取得歌曲清單 + */ + protected function loadSongs() + { + return Song::query() + ->when($this->search !== null && $this->search !== '', function ($q) { + $q->where('name', 'like', "{$this->search}%"); + }) + ->when($this->selectedLanguage, function ($q) { + $q->where('language_type', $this->selectedLanguage); + }) + ->when($this->searchCategory === 'New', function ($q) { + $q->orderByDesc('created_at'); + }) + ->when($this->searchCategory === 'Hot', function ($q) { + $q->orderByDesc('song_counts'); + }, function ($q) { + $q->orderBy('name'); + }) + ->take($this->search ? 100 : 200) // 搜尋就多拿點,不搜尋就少拿 + ->get(); + } + + /** + * Render + */ + public function render() + { + $songs = $this->loadSongs(); + + return view('livewire.pages.search-song', [ + 'songs' => $songs, + 'languages' => \App\Enums\SongLanguageType::options(), + 'selectedLanguage' => $this->selectedLanguage, + ]); + } +} \ No newline at end of file diff --git a/app/Models/Artist.php b/app/Models/Artist.php new file mode 100644 index 0000000..6743759 --- /dev/null +++ b/app/Models/Artist.php @@ -0,0 +1,55 @@ + \App\Enums\ArtistCategory::class, + ]; + } + + public function songs() { + return $this->belongsToMany(Song::class); + } + + protected static function booted() + { + // 無論是 creating 或 updating,都執行這段共用的邏輯 + static::saving(function (Artist $artist) { + $simplified=ChineseNameConverter::convertToSimplified($artist->name);// 繁體轉簡體 + $artist->simplified = $simplified; + $artist->phonetic_abbr = ChineseNameConverter::getKTVZhuyinAbbr($simplified);// 注音符號 + $artist->pinyin_abbr=ChineseNameConverter::getKTVPinyinAbbr($simplified);// 拼音首字母 + + $chars = preg_split('//u', $artist->name, -1, PREG_SPLIT_NO_EMPTY); + $firstChar = $chars[0] ?? null; + $artist->strokes_abbr=( $firstChar && preg_match('/\p{Han}/u', $firstChar) ) ? ChineseStrokesConverter::getStrokes($firstChar) : 0; + }); + + static::deleting(function (Artist $artist) { + // 解除與歌曲的多對多關聯 + $artist->songs()->detach(); + }); + } + +} diff --git a/app/Models/Song.php b/app/Models/Song.php new file mode 100644 index 0000000..542b06b --- /dev/null +++ b/app/Models/Song.php @@ -0,0 +1,96 @@ + 'boolean', + 'enable' => 'boolean', + 'language_type' => \App\Enums\SongLanguageType::class, + 'situation' => \App\Enums\SongSituation::class, + ]; + } + + public function users(){ + return $this->belongsToMany(User::class, 'user_song')->withTimestamps(); + } + + public function str_artists(){ + return $this->artists->pluck('name')->implode(', '); + } + public function str_artists_plus(): string + { + return $this->artists->pluck('name')->implode(' + '); + } + public function artists(){ + return $this->belongsToMany(Artist::class); + } + public function str_categories(){ + return $this->categories->pluck('name')->implode(', '); + } + public function categories(){ + return $this->belongsToMany(SongCategory::class); + } + public function branches(){ + return $this->belongsToMany(Branch::class) + ->withPivot('counts') + ->withTimestamps(); + } + protected static function booted() + { + // 無論是 creating 或 updating,都執行這段共用的邏輯 + static::saving(function (Song $song) { + $simplified=ChineseNameConverter::convertToSimplified($song->name);// 繁體轉簡體 + $song->simplified = $simplified; + $song->phonetic_abbr = ChineseNameConverter::getKTVZhuyinAbbr($simplified);// 注音符號 + $song->pinyin_abbr=ChineseNameConverter::getKTVPinyinAbbr($simplified);// 拼音首字母 + + $chars = preg_split('//u', $song->name, -1, PREG_SPLIT_NO_EMPTY); + $firstChar = $chars[0] ?? null; + + $song->strokes_abbr=($firstChar && preg_match('/\p{Han}/u', $firstChar)) ? ChineseStrokesConverter::getStrokes($firstChar) : 0; + $song->song_number = mb_strlen($song->name, 'UTF-8'); + }); + static::deleting(function (Song $song) { + // Detach 關聯資料 + $song->artists()->detach(); + $song->categories()->detach(); + $song->branches()->detach(); + $song->users()->detach(); + }); + } +} diff --git a/app/Models/SongLibraryCache.php b/app/Models/SongLibraryCache.php new file mode 100644 index 0000000..4f208c2 --- /dev/null +++ b/app/Models/SongLibraryCache.php @@ -0,0 +1,46 @@ +artistB!=null) ? $this->artistA ." + ".$this->artistB :$this->artistA; + } +} diff --git a/resources/lang/zh-tw/enums.php b/resources/lang/zh-tw/enums.php new file mode 100644 index 0000000..bda751f --- /dev/null +++ b/resources/lang/zh-tw/enums.php @@ -0,0 +1,38 @@ + '未定義', + 'Other' => '其他', + 'Male' =>'男', + 'Female' =>'女', + 'NotPlayed' =>'未播放', + 'Playing' =>'播放中', + 'Played' =>'播畢', + 'NoFile' =>'無文件', + 'Skipped' =>'刪除', + 'InsertPlayback' =>'插播', + 'arist.category.Group' => '團', + 'arist.category.Foreign' => '外', + 'song.situation.Romantic' => '浪漫', + 'song.situation.Soft' => '柔和', + 'song.situation.Dynamic' => '動感', + 'song.situation.Bright' => '明亮', + 'song.LanguageType.Mandarin' => '國語', + 'song.LanguageType.Taiwanese' => '台語', + 'song.LanguageType.English' => '英語', + 'song.LanguageType.Japanese' => '日語', + 'song.LanguageType.Cantonese' => '粵語', + 'song.LanguageType.Korean' => '韓語', + 'song.LanguageType.Vietnamese' => '越語', + 'song.LanguageType.Hakka' => '客語', + 'song.LanguageType.Other' => '其他', + 'user.status.Active' => '正常', + 'user.status.Suspended' => '停權', + 'user.status.Deleting' => '刪除中', + + 'room.status.Active' => '啟用中', + 'room.status.Closed' => '待機', + 'room.status.Fire' => '火災', + 'room.status.Maintain' => '維修', + 'room.status.Error' => '異常', +]; \ No newline at end of file diff --git a/resources/views/components/button/flat-card.blade.php b/resources/views/components/button/flat-card.blade.php new file mode 100644 index 0000000..146b1b7 --- /dev/null +++ b/resources/views/components/button/flat-card.blade.php @@ -0,0 +1,14 @@ +@props([ + 'image' => null, + 'href' => null, +]) + +@if($href) + merge(['class' => 'relative w-full h-48 rounded-lg overflow-hidden shadow-md hover:scale-105 transition-transform cursor-pointer']) }}> + + +@else +
merge(['class' => 'relative w-full h-48 rounded-lg overflow-hidden shadow-md hover:scale-105 transition-transform cursor-pointer']) }}> + +
+@endif \ No newline at end of file diff --git a/resources/views/components/table.blade.php b/resources/views/components/table.blade.php new file mode 100644 index 0000000..39a9278 --- /dev/null +++ b/resources/views/components/table.blade.php @@ -0,0 +1,27 @@ +@php + $classes = [ + 'table-auto w-full text-left', + $attributes->get('bordered', true) ? 'border border-gray-200' : '', + $attributes->get('striped') ? 'divide-y divide-gray-100' : '', + $attributes->get('size') === 'sm' ? 'text-sm' : 'text-base', + ]; +@endphp + +
+ except(['bordered', 'striped', 'size'])->merge(['class' => implode(' ', $classes)]) }}> + @isset($header) + + {{ $header }} + + @endisset + + {{ $slot }} + +
+ + @isset($footer) +
+ {{ $footer }} +
+ @endisset +
\ No newline at end of file diff --git a/resources/views/livewire/pages/search-song.blade.php b/resources/views/livewire/pages/search-song.blade.php new file mode 100644 index 0000000..837cbc5 --- /dev/null +++ b/resources/views/livewire/pages/search-song.blade.php @@ -0,0 +1,55 @@ +
+ + {{-- 搜尋框 --}} +
+ +
+ + {{-- 語言篩選 --}} +
+ @foreach($languages as $key => $label) + + @endforeach +
+ + {{-- 歌曲列表 Table --}} + + + + 編號 + 歌曲 + 操作 + + + + @forelse($songs as $song) + + {{ $song->id }} + +
+ {{ $song->name }} + {{ $song->str_artists_plus() }} +
+ + 點歌 + + @empty + + + 沒有符合的歌曲 + + + @endforelse +
+ +
\ No newline at end of file diff --git a/resources/views/new-songs.blade.php b/resources/views/new-songs.blade.php new file mode 100644 index 0000000..11919e3 --- /dev/null +++ b/resources/views/new-songs.blade.php @@ -0,0 +1,12 @@ + + +
超級巨星 自助式KTV
+ +
+ +
+ Wolf Fox Logo +
+
\ No newline at end of file diff --git a/resources/views/search-song.blade.php b/resources/views/search-song.blade.php new file mode 100644 index 0000000..3351d52 --- /dev/null +++ b/resources/views/search-song.blade.php @@ -0,0 +1,9 @@ + + +
超級巨星 自助式KTV
+ +
+ +
\ No newline at end of file diff --git a/resources/views/top-ranking.blade.php b/resources/views/top-ranking.blade.php new file mode 100644 index 0000000..c88557a --- /dev/null +++ b/resources/views/top-ranking.blade.php @@ -0,0 +1,12 @@ + + +
超級巨星 自助式KTV
+ +
+ +
+ Wolf Fox Logo +
+
\ No newline at end of file diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index 0464add..69b66b3 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -1,145 +1,22 @@ - - - - - + + +
超級巨星 自助式KTV
+ +
- Laravel - - - - - - - @vite(['resources/css/app.css', 'resources/js/app.js']) - - - - - +
+
+ + + + + + + + + +
+
+
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index a07a282..29f4ce9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,9 +1,13 @@ name('welcome'); +Route::view('/new-songs', 'new-songs')->name('new-songs'); +Route::view('/top-ranking', 'top-ranking')->name('top-ranking'); +Route::view('/search-song', 'search-song')->name('search-song'); + Route::view('dashboard', 'dashboard') ->middleware(['auth', 'verified']) ->name('dashboard');