202509041808
加入RoomApiToken 驗証 加入通知顯示
This commit is contained in:
parent
355dde4481
commit
d70da24a75
33
app/Http/Middleware/RoomApiTokenMiddleware.php
Normal file
33
app/Http/Middleware/RoomApiTokenMiddleware.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use App\Models\RoomSession;
|
||||
|
||||
class RoomApiTokenMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
// 優先從 query 拿,沒就 session
|
||||
$roomCode = $request->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);
|
||||
}
|
||||
}
|
@ -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');
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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', [
|
||||
|
@ -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' => '已加入已點歌曲',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -5,6 +5,6 @@
|
||||
<img src="{{ asset('手機點歌/BANNER-12.png') }}" alt="超級巨星 Banner">
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<x-notifications/>
|
||||
<livewire:pages.love-message />
|
||||
</x-app-layout>
|
@ -5,6 +5,7 @@
|
||||
<img src="{{ asset('手機點歌/BANNER-01.png') }}" alt="新歌快報">
|
||||
</div>
|
||||
</x-slot>
|
||||
<x-notifications/>
|
||||
<livewire:pages.search-song :searchCategory="'New'" />
|
||||
<div class="image-container">
|
||||
<img src="{{ asset('手機點歌/LOGO_800x400px.png') }}" alt="Wolf Fox Logo">
|
||||
|
@ -5,5 +5,6 @@
|
||||
<img src="{{ asset('手機點歌/BANNER-06.png') }}" alt="歌名查詢">
|
||||
</div>
|
||||
</x-slot>
|
||||
<x-notifications/>
|
||||
<livewire:pages.search-song />
|
||||
</x-app-layout>
|
@ -5,6 +5,6 @@
|
||||
<img src="{{ asset('手機點歌/BANNER-09.png') }}" alt="超級巨星 Banner">
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<x-notifications/>
|
||||
<livewire:pages.sound-control />
|
||||
</x-app-layout>
|
@ -5,6 +5,7 @@
|
||||
<img src="{{ asset('手機點歌/BANNER-02.png') }}" alt="熱門排行">
|
||||
</div>
|
||||
</x-slot>
|
||||
<x-notifications/>
|
||||
<livewire:pages.search-song :searchCategory="'Hot'" />
|
||||
<div class="image-container">
|
||||
<img src="{{ asset('手機點歌/LOGO_800x400px.png') }}" alt="Wolf Fox Logo">
|
||||
|
@ -1,15 +1,7 @@
|
||||
<div class="py-12 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
||||
|
||||
<x-button.flat-card image="{{ asset('手機點歌/首頁-新歌快報.png') }}" href="{{ route('new-songs') }}" />
|
||||
<x-button.flat-card image="{{ asset('手機點歌/首頁-熱門排行.png') }}" href="{{ route('top-ranking') }}" />
|
||||
<x-button.flat-card image="{{ asset('手機點歌/首頁-歌名查詢.png') }}" href="{{ route('search-song') }}" />
|
||||
@if($roomCode)
|
||||
<x-button.flat-card image="{{ asset('手機點歌/首頁-已點歌曲.png') }}" onclick="orderSongAndNavigate()" />
|
||||
<x-button.flat-card image="{{ asset('手機點歌/首頁-聲音控制.png') }}" href="{{ route('sound-control') }}" />
|
||||
<x-button.flat-card image="{{ asset('手機點歌/首頁-社群媒體.png') }}" href="{{ route('social-media') }}" />
|
||||
<x-button.flat-card image="{{ asset('手機點歌/首頁-真情告白.png') }}" href="{{ route('love-message') }}" />
|
||||
<x-button.flat-card image="{{ asset('手機點歌/首頁-心情貼圖.png') }}" href="{{ route('mood-stickers') }}" />
|
||||
@endif
|
||||
@foreach($menus as $menu)
|
||||
<x-button.flat-card image="{{ asset($menu['image']) }}" href="{{ route($menu['route']) }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,12 +62,14 @@
|
||||
<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>
|
||||
@if($roomSession)
|
||||
<th class="px-4 py-2 w-24 text-center">操作</th>
|
||||
@endif
|
||||
</tr>
|
||||
</x-slot>
|
||||
|
||||
@forelse($songs as $song)
|
||||
<tr wire:click="orderSong({{ $song->id }})" class="hover:bg-gray-50 cursor-pointer">
|
||||
<tr @if($roomSession) wire:click="orderSong({{ $song->song_id }})" @endif 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">
|
||||
@ -75,7 +77,9 @@
|
||||
<span class="text-xs text-gray-500 self-end">{{ $song->str_artists_plus() }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2 text-blue-500 text-center">點歌</td>
|
||||
@if($roomSession)
|
||||
<td class="px-4 py-2 text-blue-500 text-center">點歌</td>
|
||||
@endif
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
|
@ -1,9 +1,10 @@
|
||||
<div class="py-12 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
@foreach($buttons as $btn)
|
||||
@foreach($buttons as $key => $btn)
|
||||
<x-button.flat-card
|
||||
image="{{ asset('手機點歌/'.$btn['img']) }}"
|
||||
wire:click="sendVolumeControl('{{ $btn['action'] }}')"
|
||||
wire:click="sendControl('{{ $key }}')"
|
||||
label="{{ $btn['label'] ?? '' }}"
|
||||
/>
|
||||
@endforeach
|
||||
</div>
|
||||
|
@ -11,9 +11,11 @@ Route::view('/welcome', 'livewire.app.welcome')->name('welcome');
|
||||
Route::view('/new-songs', 'livewire.app.new-songs')->name('new-songs');
|
||||
Route::view('/top-ranking', 'livewire.app.top-ranking')->name('top-ranking');
|
||||
Route::view('/search-song', 'livewire.app.search-song')->name('search-song');
|
||||
Route::view('/clicked-song', 'livewire.app.clicked-song')->name('clicked-song');
|
||||
Route::view('/sound-control', 'livewire.app.sound-control')->name('sound-control');
|
||||
Route::view('/love-message', 'livewire.app.love-message')->name('love-message');
|
||||
Route::middleware('room_api_token')->group(function () {
|
||||
Route::view('/clicked-song', 'livewire.app.clicked-song')->name('clicked-song');
|
||||
Route::view('/sound-control', 'livewire.app.sound-control')->name('sound-control');
|
||||
Route::view('/love-message', 'livewire.app.love-message')->name('love-message');
|
||||
});
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
// Profile
|
||||
|
30
開發手冊.ini
30
開發手冊.ini
@ -89,4 +89,32 @@ php artisan migrate:rollback
|
||||
php artisan migrate
|
||||
php artisan transfer:sqlite sqlite/tempUser.sqlite --sync
|
||||
|
||||
php artisan queue:work --daemon --timeout=3600 --tries=1 --queue=default
|
||||
php artisan queue:work --daemon --timeout=3600 --tries=1 --queue=default
|
||||
|
||||
npm install && npm run build
|
||||
|
||||
https://ktvremote.test/?room_code=abc123
|
||||
http://192.168.12.14:9090/wnknwl1f3yy/windows.html
|
||||
php artisan storage:link
|
||||
|
||||
# 執行全部測試
|
||||
php artisan test
|
||||
# 執行特定檔案
|
||||
php artisan test tests/Feature/InternalHtmlTest.php
|
||||
# 執行特定測試方法
|
||||
php artisan test --filter test_internal_html_page
|
||||
|
||||
# 執行全部測試
|
||||
vendor/bin/phpunit
|
||||
|
||||
# 執行特定檔案
|
||||
vendor/bin/phpunit tests/Feature/InternalHtmlTest.php
|
||||
|
||||
# 執行特定測試方法
|
||||
vendor/bin/phpunit --filter test_internal_html_page
|
||||
|
||||
|
||||
02 2171 1168 89843
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user