Files
star-cloud/app/Http/Controllers/Admin/RemoteController.php
sky121113 376f43fa3a
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 3m58s
[FIX] 修正 IoT 管理介面分頁持久化與實作 B055 遠端出貨 API
1. 視圖持久化優化:將 index/stock 視圖切換從 x-if 改為 x-show,解決 HSSelect 在切換分頁後失效的問題。
2. 變數存取安全:為所有 selectedMachine 屬性存取補上可選鏈 (?.) 保護,防止 x-show 模式下的 null 錯誤。
3. UI 體驗提升:放大「指令中心」與「庫存管理」歷史紀錄的時間字體至 15px 並加粗顯示。
4. API 功能實作:在 routes/api.php 與 MachineController 中實作 B055 遠端指令出貨控制端點。
5. 文件同步:更新 SKILL.md 技術規格文件,明確定義 B055 的請求與回應格式。
6. 樣式調整:修改 app.css 優化奢華風 UI 字體與間距細節。
2026-04-15 10:54:58 +08:00

214 lines
7.8 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Machine\Machine;
use App\Models\Machine\RemoteCommand;
use Illuminate\Support\Facades\Auth;
class RemoteController extends Controller
{
/**
* 遠端管理指揮中心
*/
public function index(Request $request)
{
$machines = Machine::withCount(['slots'])->orderBy('last_heartbeat_at', 'desc')->orderBy('id', 'desc')->get();
$selectedMachine = null;
$historyQuery = RemoteCommand::where('command_type', '!=', 'reload_stock')
->with(['machine', 'user']);
if ($request->filled('search')) {
$search = $request->input('search');
$historyQuery->where(function($q) use ($search) {
$q->whereHas('machine', function($mq) use ($search) {
$mq->where('name', 'like', "%{$search}%")
->orWhere('serial_no', 'like', "%{$search}%");
})->orWhereHas('user', function($uq) use ($search) {
$uq->where('name', 'like', "%{$search}%");
});
});
}
// 時間區間過濾 (created_at)
if ($request->filled('start_date') || $request->filled('end_date')) {
try {
if ($request->filled('start_date')) {
$start = \Illuminate\Support\Carbon::parse($request->input('start_date'));
$historyQuery->where('created_at', '>=', $start);
}
if ($request->filled('end_date')) {
$end = \Illuminate\Support\Carbon::parse($request->input('end_date'));
$historyQuery->where('created_at', '<=', $end);
}
} catch (\Exception $e) {
// 忽略解析錯誤
}
}
// 指令類型過濾
if ($request->filled('command_type')) {
$historyQuery->where('command_type', $request->input('command_type'));
}
// 狀態過濾
if ($request->filled('status')) {
$historyQuery->where('status', $request->input('status'));
}
$history = $historyQuery->latest()->paginate($request->input('per_page', 10));
if ($request->has('machine_id')) {
$selectedMachine = Machine::with(['slots.product', 'commands' => function($query) {
$query->where('command_type', '!=', 'reload_stock')
->latest()
->limit(5);
}])->find($request->machine_id);
}
if ($request->ajax()) {
return response()->json([
'success' => true,
'machine' => $selectedMachine,
'commands' => $selectedMachine ? $selectedMachine->commands : []
]);
}
return view('admin.remote.index', [
'machines' => $machines,
'selectedMachine' => $selectedMachine,
'history' => $history,
'title' => __('Remote Command Center'),
'subtitle' => __('Execute maintenance and operational commands remotely')
]);
}
/**
* 儲存遠端指令
*/
public function storeCommand(Request $request)
{
$validated = $request->validate([
'machine_id' => 'required|exists:machines,id',
'command_type' => 'required|string|in:reboot,reboot_card,checkout,lock,unlock,change,dispense',
'amount' => 'nullable|integer|min:0',
'slot_no' => 'nullable|string',
'note' => 'nullable|string|max:255',
]);
$payload = [];
if ($validated['command_type'] === 'change') {
$payload['amount'] = $validated['amount'];
} elseif ($validated['command_type'] === 'dispense') {
$payload['slot_no'] = $validated['slot_no'];
}
// 指令去重:將同機台、同類型的 pending 指令標記為「已取代」
RemoteCommand::where('machine_id', $validated['machine_id'])
->where('command_type', $validated['command_type'])
->where('status', 'pending')
->update([
'status' => 'superseded',
'note' => __('Superseded by new command'),
'executed_at' => now(),
]);
RemoteCommand::create([
'machine_id' => $validated['machine_id'],
'user_id' => auth()->id(),
'command_type' => $validated['command_type'],
'payload' => $payload,
'status' => 'pending',
'note' => $validated['note'] ?? null,
]);
session()->flash('success', __('Command has been queued successfully.'));
if ($request->expectsJson()) {
return response()->json([
'success' => true,
'message' => __('Command has been queued successfully.')
]);
}
return redirect()->back();
}
/**
* 機台庫存管理 (現有功能保留)
*/
public function stock(Request $request)
{
$machines = Machine::withCount([
'slots as slots_count',
'slots as low_stock_count' => function ($query) {
$query->where('stock', '<=', 5);
},
'slots as expiring_soon_count' => function ($query) {
$query->whereNotNull('expiry_date')
->where('expiry_date', '<=', now()->addDays(7))
->where('expiry_date', '>=', now()->startOfDay());
}
])->orderBy('last_heartbeat_at', 'desc')->orderBy('id', 'desc')->get();
$historyQuery = RemoteCommand::with(['machine', 'user']);
$historyQuery->where('command_type', 'reload_stock');
if ($request->filled('search')) {
$search = $request->input('search');
$historyQuery->where(function($q) use ($search) {
$q->whereHas('machine', function($mq) use ($search) {
$mq->where('name', 'like', "%{$search}%")
->orWhere('serial_no', 'like', "%{$search}%");
})->orWhereHas('user', function($uq) use ($search) {
$uq->where('name', 'like', "%{$search}%");
});
});
}
// 時間區間過濾 (created_at)
if ($request->filled('start_date') || $request->filled('end_date')) {
try {
if ($request->filled('start_date')) {
$start = \Illuminate\Support\Carbon::parse($request->input('start_date'));
$historyQuery->where('created_at', '>=', $start);
}
if ($request->filled('end_date')) {
$end = \Illuminate\Support\Carbon::parse($request->input('end_date'));
$historyQuery->where('created_at', '<=', $end);
}
} catch (\Exception $e) {
// 忽略解析錯誤
}
}
// 狀態過濾
if ($request->filled('status')) {
$historyQuery->where('status', $request->input('status'));
}
$history = $historyQuery->latest()->paginate($request->input('per_page', 10));
$selectedMachine = null;
if ($request->has('machine_id')) {
$selectedMachine = Machine::with(['slots.product', 'commands' => function($query) {
$query->where('command_type', 'reload_stock')
->latest()
->limit(50);
}])->find($request->machine_id);
}
return view('admin.remote.stock', [
'machines' => $machines,
'selectedMachine' => $selectedMachine,
'history' => $history,
'title' => __('Stock & Expiry Management'),
'subtitle' => __('Real-time monitoring and adjustment of cargo lane inventory and expiration dates')
]);
}
}