diff --git a/app/Http/Middleware/RoomApiTokenMiddleware.php b/app/Http/Middleware/RoomApiTokenMiddleware.php
new file mode 100644
index 0000000..c959bac
--- /dev/null
+++ b/app/Http/Middleware/RoomApiTokenMiddleware.php
@@ -0,0 +1,33 @@
+query('room_code', session('room_code'));
+ if ($roomCode) {
+ $roomSession = RoomSession::validToken($roomCode)->first();
+ if (!$roomSession) {
+ session()->forget('room_code');
+ return redirect()->route('welcome')->with('error', '房間不存在或狀態不可用');
+ }else{
+ session(['room_code' => $roomCode]);
+ $request->merge(['roomSession' => $roomSession]); // 可選:直接注入 request
+ }
+ }
+ return $next($request);
+ }
+}
diff --git a/app/Livewire/Pages/Home.php b/app/Livewire/Pages/Home.php
index 01a6ff6..a44e74a 100644
--- a/app/Livewire/Pages/Home.php
+++ b/app/Livewire/Pages/Home.php
@@ -2,22 +2,33 @@
namespace App\Livewire\Pages;
+use App\Models\RoomSession;
use Livewire\Component;
class Home extends Component
-{
- public $roomCode;
-
+{
+ public array $menus = [];
public function mount()
- {
- // 先從 URL 取得 room_code,再存進 session
- //session()->forget('room_code');
- $this->roomCode = request()->query('room_code', session('room_code', null));
- if ($this->roomCode) {
- session(['room_code' => $this->roomCode]);
+ {
+ $this->menus = [
+ ['route' => 'new-songs','image' => '手機點歌/首頁-新歌快報.png'],
+ ['route' => 'top-ranking','image' => '手機點歌/首頁-熱門排行.png'],
+ ['route' => 'search-song','image' => '手機點歌/首頁-歌名查詢.png'],
+ ];
+ $roomMenus = [
+ ['route' => 'clicked-song','image' => '手機點歌/首頁-已點歌曲.png'],
+ ['route' => 'sound-control','image' => '手機點歌/首頁-聲音控制.png',],
+ ['route' => 'love-message','image' => '手機點歌/首頁-真情告白.png',],
+ ];
+ $roomCode = request()->query('room_code', session('room_code', null));
+ if ($roomCode) {
+ $roomSession = RoomSession::validToken($roomCode)->first();
+ if ($roomSession) {
+ session(['room_code' => $roomCode]);
+ $this->menus = array_merge($this->menus, $roomMenus);
+ }
}
}
-
public function render()
{
return view('livewire.pages.home');
diff --git a/app/Livewire/Pages/LoveMessage.php b/app/Livewire/Pages/LoveMessage.php
index 03ba3c0..8756a3a 100644
--- a/app/Livewire/Pages/LoveMessage.php
+++ b/app/Livewire/Pages/LoveMessage.php
@@ -3,12 +3,21 @@
namespace App\Livewire\Pages;
use Livewire\Component;
+use App\Models\RoomSession;
+use WireUi\Traits\WireUiActions;
+use App\Services\TcpSocketClient;
class LoveMessage extends Component
{
+ use WireUiActions;
protected $listeners = ['stickerSelected' => 'handleSticker'];
+ public $roomSession;
public $message = '';
public $selectedSticker=null;
+ public function mount()
+ {
+ $this->roomSession = request()->roomSession;
+ }
public function handleSticker($sticker)
{
diff --git a/app/Livewire/Pages/OrderedSongList.php b/app/Livewire/Pages/OrderedSongList.php
index 63be556..403d839 100644
--- a/app/Livewire/Pages/OrderedSongList.php
+++ b/app/Livewire/Pages/OrderedSongList.php
@@ -9,7 +9,7 @@ use App\Models\OrderedSong;
class OrderedSongList extends Component
{
- public ?string $roomSessionId = null;
+ public $roomSession;
public EloquentCollection $playing ;
public EloquentCollection $nextSong ;
@@ -21,32 +21,22 @@ class OrderedSongList extends Component
$this->playing = new EloquentCollection();
$this->nextSong = new EloquentCollection();
$this->finished = new EloquentCollection();
- $roomSession = $this->getRoomSession(session('room_code'))?->id ;
- if ($roomSession) {
- $this->roomSessionId = $roomSession->id;
- $this->loadSongs($this->roomSessionId);
- }
- }
- private function getRoomSession($apiToken): ?RoomSession
- {
- if (!$apiToken) return null;
- return RoomSession::where('api_token', $apiToken)
- ->whereIn('status', ['active', 'maintain'])
- ->first();
+ $this->roomSession = request()->roomSession;
+ $this->refreshSongs();
}
public function refreshSongs()
{
- if ($this->roomSessionId) {
- $this->loadSongs($this->roomSessionId);
+ $dbSession = $this->roomSession->refreshValid();
+ if (!$dbSession) {
+ session()->forget('room_code');
+ return $this->redirect(route('welcome'), navigate: true);
}
+ $this->playing = OrderedSong::forSession($this->roomSession->id)->playing()->get();
+ $this->nextSong = OrderedSong::forSession($this->roomSession->id)->nextSong()->get();
+ $this->finished = OrderedSong::forSession($this->roomSession->id)->finished()->get();
}
- protected function loadSongs(string $roomSessionId)
- {
- $this->playing = OrderedSong::forSession($roomSessionId)->playing()->get();
- $this->nextSong = OrderedSong::forSession($roomSessionId)->nextSong()->get();
- $this->finished = OrderedSong::forSession($roomSessionId)->finished()->get();
- }
+
public function render()
{
return view('livewire.pages.ordered-song-list', [
diff --git a/app/Livewire/Pages/SearchSong.php b/app/Livewire/Pages/SearchSong.php
index feca84c..4d77af6 100644
--- a/app/Livewire/Pages/SearchSong.php
+++ b/app/Livewire/Pages/SearchSong.php
@@ -3,11 +3,15 @@
namespace App\Livewire\Pages;
use Livewire\Component;
+use App\Models\RoomSession;
use App\Models\Artist;
use App\Models\SongLibraryCache;
+use WireUi\Traits\WireUiActions;
class SearchSong extends Component
{
+ use WireUiActions;
+ public $roomSession;
public string $search = '';
public $searchCategory = '';
public $selectedLanguage = '國語'; // 預設語言
@@ -21,6 +25,13 @@ class SearchSong extends Component
public function mount(string $searchCategory = '')
{
$this->searchCategory = $searchCategory;
+ $roomCode = request()->query('room_code', session('room_code', null));
+ if ($roomCode) {
+ $this->roomSession = RoomSession::validToken($roomCode)->first();
+ if ($this->roomSession) {
+ session(['room_code' => $roomCode]);
+ }
+ }
}
/**
@@ -68,8 +79,10 @@ class SearchSong extends Component
// TODO: 加入已點歌曲邏輯,例如:
// auth()->user()->room->addSong($songId);
- $this->dispatchBrowserEvent('notify', [
- 'message' => '已加入已點歌曲'
+ $this->notification()->send([
+ 'icon' => 'success',
+ 'title' => $songId,
+ 'description' => '已加入已點歌曲',
]);
}
diff --git a/app/Livewire/Pages/SoundControl.php b/app/Livewire/Pages/SoundControl.php
index e5a570f..aa57db8 100644
--- a/app/Livewire/Pages/SoundControl.php
+++ b/app/Livewire/Pages/SoundControl.php
@@ -2,37 +2,76 @@
namespace App\Livewire\Pages;
+use App\Models\RoomSession;
use Livewire\Component;
+use App\Services\ApiClient;
+use WireUi\Traits\WireUiActions;
+use App\Services\TcpSocketClient;
class SoundControl extends Component
{
+ use WireUiActions;
+ public $roomSession;
+
public $buttons = [
- ['img'=>'音控-01.jpg', 'action'=>'pause'],
- ['img'=>'音控-02.jpg', 'action'=>'volume_up'],
- ['img'=>'音控-04.jpg', 'action'=>'mic_up'],
- ['img'=>'音控-06.jpg', 'action'=>'mute'],
- ['img'=>'音控-03.jpg', 'action'=>'volume_down'],
- ['img'=>'音控-05.jpg', 'action'=>'mic_down'],
- ['img'=>'音控-07.jpg', 'action'=>'original_song'],
- ['img'=>'音控-08.jpg', 'action'=>'service'],
- ['img'=>'音控-09.jpg', 'action'=>'replay'],
- ['img'=>'音控-11.jpg', 'action'=>'male_key'],
- ['img'=>'音控-12.jpg', 'action'=>'female_key'],
- ['img'=>'音控-10.jpg', 'action'=>'cut'],
- ['img'=>'音控-15.jpg', 'action'=>'lower_key'],
- ['img'=>'音控-14.jpg', 'action'=>'standard_key'],
- ['img'=>'音控-13.jpg', 'action'=>'raise_key'],
+ 'pause' =>['img'=>'音控-01.jpg','label' =>'暫停'],
+ 'volume_up' =>['img'=>'音控-02.jpg','label' =>'音量 ↑'],
+ 'volume_down' =>['img'=>'音控-04.jpg','label' =>'音量 ↓'],
+ 'mic_up' =>['img'=>'音控-06.jpg','label' =>'麥克風 ↑'],
+ 'mic_down' =>['img'=>'音控-03.jpg','label' =>'麥克風 ↓'],
+ 'mute' =>['img'=>'音控-05.jpg','label' =>'靜音'],
+ 'original_song' =>['img'=>'音控-07.jpg','label' =>'原唱'],
+ 'service' =>['img'=>'音控-08.jpg','label' =>'服務鈴'],
+ 'replay' =>['img'=>'音控-09.jpg','label' =>'重播'],
+ 'male_key' =>['img'=>'音控-11.jpg','label' =>'男調'],
+ 'female_key' =>['img'=>'音控-12.jpg','label' =>'女調'],
+ 'cut' =>['img'=>'音控-10.jpg','label' =>'切歌'],
+ 'lower_key' =>['img'=>'音控-15.jpg','label' =>'降調'],
+ 'standard_key' =>['img'=>'音控-14.jpg','label' =>'原調'],
+ 'raise_key' =>['img'=>'音控-13.jpg','label' =>'升調'],
];
-
- public function sendVolumeControl(string $action)
+ public function mount()
{
- // 這裡可以加你的 API 或邏輯
- // 範例:發送到後台控制音量
- info("Sound control action: ".$action);
-
- $this->dispatchBrowserEvent('notify', [
- 'message' => "已執行操作: {$action}"
- ]);
+ $this->roomSession = request()->roomSession;
+ }
+ public function sendControl(string $action)
+ {
+ $dbSession = $this->roomSession->refreshValid();
+ if (!$dbSession) {
+ session()->forget('room_code');
+ $this->notification()->send([
+ 'icon' => 'error',
+ 'title' => '驗證失敗',
+ 'description' => 'Token 已失效,請重新登入',
+ ]);
+ return $this->redirect(route('welcome'), navigate: true);
+ }else{
+ $room=$this->roomSession->room;
+ }
+
+ $client = new TcpSocketClient($room->internal_ip, $room->port);
+ try {
+ $response = $client->send($room->name . "," . $action);
+ } catch (\Throwable $e) {
+ logger()->error('❌ TCP 傳送失敗: ' . $e->getMessage(), [
+ 'room_id' => $room->id,
+ 'ip' => $room->internal_ip,
+ 'port' => $room->port,
+ ]);
+ }
+ $title = $this->buttons[$action]['label'] ?? $action;
+ $this->notification()->send(
+ (isset($response) && trim($response) === "OK")
+ ?[
+ 'icon' => 'success',
+ 'title' => $title,
+ 'description' => '已執行操作',
+ ]:[
+ 'icon' => 'error',
+ 'title' => $title,
+ 'description' => $data['message'] ?? '操作失敗',
+ ]
+ );
}
public function render()
diff --git a/app/Models/OrderedSong.php b/app/Models/OrderedSong.php
index 552088c..ba7e00c 100644
--- a/app/Models/OrderedSong.php
+++ b/app/Models/OrderedSong.php
@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use App\Enums\OrderedSongStatus;
/**
* @OA\Schema(
@@ -85,4 +86,33 @@ class OrderedSong extends Model
}
]);
}
+ public function scopeForSession($query, $roomSessionId)
+ {
+ return $query->where('room_session_id', $roomSessionId);
+ }
+
+ public function scopePlaying($query)
+ {
+ return $query->where('status', OrderedSongStatus::Playing);
+ }
+
+ public function scopeNextSong($query)
+ {
+ return $query->whereIn('status', [OrderedSongStatus::InsertPlayback, OrderedSongStatus::NotPlayed])
+ ->orderByRaw("FIELD(status, ?, ?)", [
+ OrderedSongStatus::InsertPlayback->value,
+ OrderedSongStatus::NotPlayed->value,
+ ])
+ ->orderByRaw("CASE WHEN status=? THEN ordered_at END DESC", [OrderedSongStatus::InsertPlayback->value])
+ ->orderByRaw("CASE WHEN status=? THEN ordered_at END ASC", [OrderedSongStatus::NotPlayed->value]);
+ }
+
+ public function scopeFinished($query)
+ {
+ return $query->whereIn('status', [
+ OrderedSongStatus::Played,
+ OrderedSongStatus::Skipped,
+ OrderedSongStatus::NoFile,
+ ])->orderByDesc('finished_at');
+ }
}
diff --git a/app/Models/RoomSession.php b/app/Models/RoomSession.php
index 7b17096..5fe541c 100644
--- a/app/Models/RoomSession.php
+++ b/app/Models/RoomSession.php
@@ -35,8 +35,22 @@ class RoomSession extends Model
public const MODE_VIP = 'vip';
public const MODE_TEST = 'test';
+ public function scopeValidToken($query, ?string $token)
+ {
+ return $query->with('room')
+ ->where('api_token', $token)
+ ->whereIn('status', ['active', 'maintain']);
+ }
+ public function refreshValid(): ?self
+ {
+ return self::validToken($this->api_token)
+ ->where('id', $this->id)
+ ->first();
+ }
+
public function room()
{
return $this->belongsTo(Room::class);
}
+
}
diff --git a/bootstrap/app.php b/bootstrap/app.php
index 38010d6..68ef8b5 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -14,6 +14,7 @@ return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'api_token' => \App\Http\Middleware\ApiTokenMiddleware::class,
+ 'room_api_token' => \App\Http\Middleware\RoomApiTokenMiddleware::class,
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class
diff --git a/resources/views/livewire/app/love-message.blade.php b/resources/views/livewire/app/love-message.blade.php
index 2be6155..62494bc 100644
--- a/resources/views/livewire/app/love-message.blade.php
+++ b/resources/views/livewire/app/love-message.blade.php
@@ -5,6 +5,6 @@
-
+
+