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)
+
+ {{ $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 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
-
-
-
-
-
+
+
+
+
+
+
+
- Laravel
-
-
-
-
-
-
- @vite(['resources/css/app.css', 'resources/js/app.js'])
-
-
-
-
-
-
-
-
- @if (Route::has('login'))
-
- @endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Documentation
-
-
- Laravel has wonderful documentation covering every aspect of the framework. Whether you are a newcomer or have prior experience with Laravel, we recommend reading our documentation from beginning to end.
-
-
-
-
-
-
-
-
-
-
-
-
-
Laracasts
-
-
- Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
-
-
-
-
-
-
-
-
-
-
-
Laravel News
-
-
- Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials.
-
-
-
-
-
-
-
-
-
-
-
Vibrant Ecosystem
-
-
- Laravel's robust library of first-party tools and libraries, such as Forge , Vapor , Nova , Envoyer , and Herd help you take your projects to the next level. Pair them with powerful open source libraries like Cashier , Dusk , Echo , Horizon , Sanctum , Telescope , and more.
-
-
-
-
-
-
-
- Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }})
-
-
-
-
-
-
+
+
\ 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');