202508281721
加入 己點歌曲
This commit is contained in:
parent
d13b00cb1f
commit
42f69e677a
28
app/Enums/OrderedSongStatus.php
Normal file
28
app/Enums/OrderedSongStatus.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use App\Enums\Traits\HasLabels;
|
||||||
|
|
||||||
|
|
||||||
|
enum OrderedSongStatus: string
|
||||||
|
{
|
||||||
|
case NotPlayed = 'NotPlayed';
|
||||||
|
case Playing = 'Playing';
|
||||||
|
case Played = 'Played';
|
||||||
|
case NoFile = 'NoFile';
|
||||||
|
case Skipped = 'Skipped';
|
||||||
|
case InsertPlayback = 'InsertPlayback';
|
||||||
|
|
||||||
|
public function labels(): string
|
||||||
|
{
|
||||||
|
return match($this) {
|
||||||
|
self::NotPlayed => __('enums.NotPlayed'),
|
||||||
|
self::Playing => __('enums.Playing'),
|
||||||
|
self::Played => __('enums.Played'),
|
||||||
|
self::NoFile => __('enums.NoFile'),
|
||||||
|
self::Skipped => __('enums.Skipped'),
|
||||||
|
self::InsertPlayback => __('enums.InsertPlayback'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -16,9 +16,7 @@ class Navigation extends Component
|
|||||||
private array $roomMenus = [
|
private array $roomMenus = [
|
||||||
['name' => '已點歌曲', 'route' => 'clicked-song'],
|
['name' => '已點歌曲', 'route' => 'clicked-song'],
|
||||||
['name' => '聲音控制', 'route' => 'sound-control'],
|
['name' => '聲音控制', 'route' => 'sound-control'],
|
||||||
['name' => '社群媒體', 'route' => 'social-media'],
|
|
||||||
['name' => '真情告白', 'route' => 'love-message'],
|
['name' => '真情告白', 'route' => 'love-message'],
|
||||||
['name' => '心情貼圖', 'route' => 'mood-stickers'],
|
|
||||||
];
|
];
|
||||||
public array $menus = [];
|
public array $menus = [];
|
||||||
|
|
||||||
|
58
app/Livewire/Pages/OrderedSongList.php
Normal file
58
app/Livewire/Pages/OrderedSongList.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Pages;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||||
|
use App\Models\RoomSession;
|
||||||
|
use App\Models\OrderedSong;
|
||||||
|
|
||||||
|
class OrderedSongList extends Component
|
||||||
|
{
|
||||||
|
public ?string $roomSessionId = null;
|
||||||
|
|
||||||
|
public EloquentCollection $playing ;
|
||||||
|
public EloquentCollection $notPlayed ;
|
||||||
|
public EloquentCollection $finished ;
|
||||||
|
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$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($api_token): ?RoomSession
|
||||||
|
{
|
||||||
|
if (!$apiToken) return null;
|
||||||
|
return RoomSession::where('api_token', $api_token)
|
||||||
|
->whereIn('status', ['active', 'maintain'])
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshSongs()
|
||||||
|
{
|
||||||
|
if ($this->roomSessionId) {
|
||||||
|
$this->loadSongs($this->roomSessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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', [
|
||||||
|
'playing' => $this->playing,
|
||||||
|
'nextSong' => $this->nextSong,
|
||||||
|
'finished' => $this->finished,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
83
app/Models/OrderedSong.php
Normal file
83
app/Models/OrderedSong.php
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\Enums\OrderedSongStatus;
|
||||||
|
|
||||||
|
class OrderedSong extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'room_session_id',
|
||||||
|
'from_by',
|
||||||
|
'order_number',
|
||||||
|
'song_id',
|
||||||
|
'song_name',
|
||||||
|
'artist_name',
|
||||||
|
'status',
|
||||||
|
'ordered_at',
|
||||||
|
'started_at',
|
||||||
|
'finished_at',
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'ordered_at' => 'datetime',
|
||||||
|
'started_at' => 'datetime',
|
||||||
|
'finished_at' => 'datetime',
|
||||||
|
'status' => \App\Enums\OrderedSongStatus::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function session()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(RoomSession::class, 'room_session_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function song()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Song::class);
|
||||||
|
}
|
||||||
|
public function scopeWithPartialSong($query)
|
||||||
|
{
|
||||||
|
return $query->with([
|
||||||
|
'song' => function ($q) {
|
||||||
|
$q->select('id', 'name','filename','db_change','vocal','situation'); // 精簡版
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
42
app/Models/RoomSession.php
Normal file
42
app/Models/RoomSession.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class RoomSession extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'room_id',
|
||||||
|
'started_at',
|
||||||
|
'ended_at',
|
||||||
|
'status',
|
||||||
|
'mode',
|
||||||
|
'close_reason',
|
||||||
|
'api_token',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'started_at' => 'datetime',
|
||||||
|
'ended_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 狀態常數
|
||||||
|
public const STATUS_ACTIVE = 'active';
|
||||||
|
public const STATUS_CLOSED = 'closed';
|
||||||
|
public const STATUS_FORCE_CLOSED = 'force_closed';
|
||||||
|
public const STATUS_FIRE_CLOSED = 'fire_closed';
|
||||||
|
|
||||||
|
// 模式常數
|
||||||
|
public const MODE_NORMAL = 'normal';
|
||||||
|
public const MODE_VIP = 'vip';
|
||||||
|
public const MODE_TEST = 'test';
|
||||||
|
|
||||||
|
public function room()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Room::class);
|
||||||
|
}
|
||||||
|
}
|
10
resources/views/clicked-song.blade.php
Normal file
10
resources/views/clicked-song.blade.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="header">
|
||||||
|
<div class="header">超級巨星 自助式KTV</div>
|
||||||
|
<div class="banner">
|
||||||
|
<img src="{{ asset('手機點歌/BANNER-07.png') }}" alt="超級巨星 Banner">
|
||||||
|
</div>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<livewire:pages.ordered-song-list />
|
||||||
|
</x-app-layout>
|
88
resources/views/livewire/pages/ordered-song-list.blade.php
Normal file
88
resources/views/livewire/pages/ordered-song-list.blade.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<div wire:poll.5s="refreshSongs" class="space-y-6 p-4">
|
||||||
|
|
||||||
|
<x-table bordered striped size="sm" class="w-full">
|
||||||
|
<x-slot name="header">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2 w-16 text-center">編號</th>
|
||||||
|
<th class="px-4 py-2">歌曲</th>
|
||||||
|
<th class="px-4 py-2 w-24 text-center">狀態</th>
|
||||||
|
</tr>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
{{-- 正在播放 --}}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-2 text-center text-blue-600 font-semibold" colspan="3">
|
||||||
|
🎵 正在播放
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@forelse($playing as $song)
|
||||||
|
<tr class="hover:bg-gray-50 cursor-pointer">
|
||||||
|
<td class="px-4 py-2 text-center">{{ $song->song_id }}</td>
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-lg font-semibold">{{ $song->song_name }}</span>
|
||||||
|
<span class="text-xs text-gray-500 self-end">{{ $song->artist_name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2 text-blue-500 text-center">{{ $song->status->labels() }}</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-4 text-center text-gray-400" colspan="3">
|
||||||
|
目前沒有正在播放的歌曲
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
|
||||||
|
{{-- 待播 / 插播 --}}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-2 text-center text-yellow-600 font-semibold" colspan="3">
|
||||||
|
⏳ 待播 / 插播
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@forelse($nextSong as $song)
|
||||||
|
<tr class="hover:bg-gray-50 cursor-pointer">
|
||||||
|
<td class="px-4 py-2 text-center">{{ $song->song_id }}</td>
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-lg font-semibold">{{ $song->song_name }}</span>
|
||||||
|
<span class="text-xs text-gray-500 self-end">{{ $song->artist_name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2 text-yellow-500 text-center">{{ $song->status->labels() }}</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-4 text-center text-gray-400" colspan="3">
|
||||||
|
目前沒有待播歌曲
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
|
||||||
|
{{-- 已結束播放 --}}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-2 text-center text-gray-600 font-semibold" colspan="3">
|
||||||
|
✅ 已結束播放
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@forelse($finished as $song)
|
||||||
|
<tr class="hover:bg-gray-50 cursor-pointer">
|
||||||
|
<td class="px-4 py-2 text-center">{{ $song->song_id }}</td>
|
||||||
|
<td class="px-4 py-2">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-lg font-semibold">{{ $song->song_name }}</span>
|
||||||
|
<span class="text-xs text-gray-500 self-end">{{ $song->artist_name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2 text-gray-500 text-center">{{ $song->status->labels() }}</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-4 text-center text-gray-400" colspan="3">
|
||||||
|
尚無結束播放的歌曲
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
|
||||||
|
</x-table>
|
||||||
|
</div>
|
@ -7,6 +7,7 @@ Route::view('/welcome', 'welcome')->name('welcome');
|
|||||||
Route::view('/new-songs', 'new-songs')->name('new-songs');
|
Route::view('/new-songs', 'new-songs')->name('new-songs');
|
||||||
Route::view('/top-ranking', 'top-ranking')->name('top-ranking');
|
Route::view('/top-ranking', 'top-ranking')->name('top-ranking');
|
||||||
Route::view('/search-song', 'search-song')->name('search-song');
|
Route::view('/search-song', 'search-song')->name('search-song');
|
||||||
|
Route::view('/clicked-song', 'clicked-song')->name('clicked-song');
|
||||||
Route::view('/sound-control', 'sound-control')->name('sound-control');
|
Route::view('/sound-control', 'sound-control')->name('sound-control');
|
||||||
Route::view('/love-message', 'love-message')->name('love-message');
|
Route::view('/love-message', 'love-message')->name('love-message');
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user