[FEAT] 遠端控制新增「強制重啟 APP」(reboot_force) 指令

1. RemoteController storeCommand 驗證白名單新增 reboot_force
2. ProcessCommandAck 將 reboot_force 納入維護類指令日誌記錄
3. 遠端控制頁新增強制重啟按鈕(琥珀色,提示卡死時使用,無視忙碌狀態)
4. 指令歷史篩選與庫存頁指令名稱對應補上 reboot_force
5. 三語系新增 Machine Force Reboot / command.reboot_force / 忙碌狀態提示,並 ksort 對齊

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-23 14:05:52 +08:00
parent 7b6dc3cb8e
commit 871b223440
8 changed files with 36 additions and 2 deletions

View File

@ -142,7 +142,7 @@ class RemoteController extends Controller
{ {
$validated = $request->validate([ $validated = $request->validate([
'machine_id' => 'required|exists:machines,id', 'machine_id' => 'required|exists:machines,id',
'command_type' => 'required|string|in:reboot,reboot_card,checkout,lock,unlock,change,dispense,fanon,fanoff,fanauto', 'command_type' => 'required|string|in:reboot,reboot_force,reboot_card,checkout,lock,unlock,change,dispense,fanon,fanoff,fanauto',
'amount' => 'nullable|integer|min:0', 'amount' => 'nullable|integer|min:0',
'slot_no' => 'nullable|string', 'slot_no' => 'nullable|string',
'note' => 'nullable|string|max:255', 'note' => 'nullable|string|max:255',

View File

@ -147,7 +147,7 @@ class ProcessCommandAck implements ShouldQueue
]); ]);
// 記錄維護類指令到機台日誌 (MachineLog) // 記錄維護類指令到機台日誌 (MachineLog)
if (in_array($command->command_type, ['reboot', 'reboot_card', 'lock', 'unlock', 'checkout', 'change', 'reload_stock', 'update_products', 'update_ads', 'update_settings'], true)) { if (in_array($command->command_type, ['reboot', 'reboot_force', 'reboot_card', 'lock', 'unlock', 'checkout', 'change', 'reload_stock', 'update_products', 'update_ads', 'update_settings'], true)) {
$msgKey = $status === 'success' ? 'log.command.success' : 'log.command.failed'; $msgKey = $status === 'success' ? 'log.command.success' : 'log.command.failed';
$level = $status === 'success' ? 'info' : 'warning'; $level = $status === 'success' ? 'info' : 'warning';

View File

@ -927,6 +927,7 @@
"Identification": "Identification", "Identification": "Identification",
"Identity & Codes": "Identity & Codes", "Identity & Codes": "Identity & Codes",
"Identity and Codes": "Identity and Codes", "Identity and Codes": "Identity and Codes",
"Ignore busy state (use when stuck)": "Ignore busy state (use when stuck)",
"Image": "Image", "Image": "Image",
"Image Downloaded": "Image Downloaded", "Image Downloaded": "Image Downloaded",
"Immediate": "Immediate", "Immediate": "Immediate",
@ -1077,6 +1078,7 @@
"Machine Flavor": "Machine Flavor", "Machine Flavor": "Machine Flavor",
"Machine Flow ID": "Machine Flow ID", "Machine Flow ID": "Machine Flow ID",
"Machine Flow ID / Time": "Machine Flow ID / Time", "Machine Flow ID / Time": "Machine Flow ID / Time",
"Machine Force Reboot": "Machine Force Reboot",
"Machine Images": "Machine Images", "Machine Images": "Machine Images",
"Machine Inbound Confirmation": "Machine Inbound Confirmation", "Machine Inbound Confirmation": "Machine Inbound Confirmation",
"Machine Info": "Machine Info", "Machine Info": "Machine Info",
@ -2422,6 +2424,7 @@
"command.lock": "Lock", "command.lock": "Lock",
"command.reboot": "Reboot", "command.reboot": "Reboot",
"command.reboot_card": "Reboot Card Reader", "command.reboot_card": "Reboot Card Reader",
"command.reboot_force": "Force Reboot",
"command.reload_stock": "Sync Stock", "command.reload_stock": "Sync Stock",
"command.unlock": "Unlock", "command.unlock": "Unlock",
"command.update_ads": "Sync Ads", "command.update_ads": "Sync Ads",

View File

@ -927,6 +927,7 @@
"Identification": "識別情報", "Identification": "識別情報",
"Identity & Codes": "識別・コード", "Identity & Codes": "識別・コード",
"Identity and Codes": "Identity and Codes", "Identity and Codes": "Identity and Codes",
"Ignore busy state (use when stuck)": "ビジー状態を無視(フリーズ時に使用)",
"Image": "画像", "Image": "画像",
"Image Downloaded": "画像をダウンロードしました", "Image Downloaded": "画像をダウンロードしました",
"Immediate": "即時", "Immediate": "即時",
@ -1077,6 +1078,7 @@
"Machine Flavor": "Machine Flavor", "Machine Flavor": "Machine Flavor",
"Machine Flow ID": "機器フローID", "Machine Flow ID": "機器フローID",
"Machine Flow ID / Time": "機器フローID / 時間", "Machine Flow ID / Time": "機器フローID / 時間",
"Machine Force Reboot": "アプリ強制再起動",
"Machine Images": "機器写真", "Machine Images": "機器写真",
"Machine Inbound Confirmation": "入庫確認", "Machine Inbound Confirmation": "入庫確認",
"Machine Info": "機器情報", "Machine Info": "機器情報",
@ -2422,6 +2424,7 @@
"command.lock": "ロック", "command.lock": "ロック",
"command.reboot": "再起動", "command.reboot": "再起動",
"command.reboot_card": "カードリーダー再起動", "command.reboot_card": "カードリーダー再起動",
"command.reboot_force": "強制再起動",
"command.reload_stock": "在庫同期", "command.reload_stock": "在庫同期",
"command.unlock": "ロック解除", "command.unlock": "ロック解除",
"command.update_ads": "広告同期", "command.update_ads": "広告同期",

View File

@ -927,6 +927,7 @@
"Identification": "識別資訊", "Identification": "識別資訊",
"Identity & Codes": "識別與代碼", "Identity & Codes": "識別與代碼",
"Identity and Codes": "識別與代碼", "Identity and Codes": "識別與代碼",
"Ignore busy state (use when stuck)": "無視忙碌狀態(卡死時使用)",
"Image": "圖片", "Image": "圖片",
"Image Downloaded": "圖片已下載", "Image Downloaded": "圖片已下載",
"Immediate": "立即", "Immediate": "立即",
@ -1077,6 +1078,7 @@
"Machine Flavor": "機台風味", "Machine Flavor": "機台風味",
"Machine Flow ID": "機台流水號", "Machine Flow ID": "機台流水號",
"Machine Flow ID / Time": "機台流水號 / 時間", "Machine Flow ID / Time": "機台流水號 / 時間",
"Machine Force Reboot": "強制重啟 APP",
"Machine Images": "機台照片", "Machine Images": "機台照片",
"Machine Inbound Confirmation": "機台入庫確認", "Machine Inbound Confirmation": "機台入庫確認",
"Machine Info": "機台資訊", "Machine Info": "機台資訊",
@ -2422,6 +2424,7 @@
"command.lock": "鎖機", "command.lock": "鎖機",
"command.reboot": "重啟", "command.reboot": "重啟",
"command.reboot_card": "讀卡機重啟", "command.reboot_card": "讀卡機重啟",
"command.reboot_force": "強制重啟",
"command.reload_stock": "同步庫存", "command.reload_stock": "同步庫存",
"command.unlock": "解鎖", "command.unlock": "解鎖",
"command.update_ads": "同步廣告", "command.update_ads": "同步廣告",

View File

@ -34,6 +34,7 @@
'No active cargo lanes found' => __('No active cargo lanes found'), 'No active cargo lanes found' => __('No active cargo lanes found'),
'Empty' => __('Empty'), 'Empty' => __('Empty'),
'Machine Reboot' => __('Machine Reboot'), 'Machine Reboot' => __('Machine Reboot'),
'Machine Force Reboot' => __('Machine Force Reboot'),
'Card Reader Reboot' => __('Card Reader Reboot'), 'Card Reader Reboot' => __('Card Reader Reboot'),
'Remote Settlement' => __('Remote Settlement'), 'Remote Settlement' => __('Remote Settlement'),
'Lock Page Lock' => __('Lock Page Lock'), 'Lock Page Lock' => __('Lock Page Lock'),
@ -444,6 +445,7 @@
getCommandName(type) { getCommandName(type) {
const names = { const names = {
'reboot': this.translations['Machine Reboot'], 'reboot': this.translations['Machine Reboot'],
'reboot_force': this.translations['Machine Force Reboot'],
'reboot_card': this.translations['Card Reader Reboot'], 'reboot_card': this.translations['Card Reader Reboot'],
'checkout': this.translations['Remote Settlement'], 'checkout': this.translations['Remote Settlement'],
'lock': this.translations['Lock Page Lock'], 'lock': this.translations['Lock Page Lock'],
@ -661,6 +663,27 @@
</div> </div>
</button> </button>
<!-- Force Reboot (ignores busy / stuck dialog) -->
<button @click="sendCommand('reboot_force')"
class="p-6 rounded-3xl border border-amber-200 dark:border-amber-500/30 flex items-center gap-5 hover:border-amber-500/60 dark:hover:border-amber-400/70 hover:bg-amber-500/5 dark:hover:bg-amber-400/5 group transition-all bg-white/50 dark:bg-slate-900/40 shadow-sm">
<div
class="w-12 h-12 rounded-2xl bg-amber-500/10 flex items-center justify-center text-amber-500 dark:text-amber-400 group-hover:scale-110 transition-transform duration-500 border border-amber-500/20 dark:border-amber-400/20">
<svg class="w-6 h-6" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div class="text-left">
<div
class="text-sm font-black text-slate-800 dark:text-white group-hover:text-amber-600 dark:group-hover:text-amber-400 transition-colors">
{{ __('Machine Force Reboot') }}</div>
<div class="text-[11px] text-slate-400 dark:text-slate-500 mt-0.5">
{{ __('Ignore busy state (use when stuck)') }}</div>
</div>
</button>
<!-- Card Reader Reboot --> <!-- Card Reader Reboot -->
<button @click="sendCommand('reboot_card')" <button @click="sendCommand('reboot_card')"
class="p-6 rounded-3xl border border-slate-100 dark:border-slate-800 flex items-center gap-5 hover:border-cyan-500/50 dark:hover:border-cyan-400/60 hover:bg-cyan-500/5 dark:hover:bg-cyan-400/5 group transition-all bg-white/50 dark:bg-slate-900/40 shadow-sm"> class="p-6 rounded-3xl border border-slate-100 dark:border-slate-800 flex items-center gap-5 hover:border-cyan-500/50 dark:hover:border-cyan-400/60 hover:bg-cyan-500/5 dark:hover:bg-cyan-400/5 group transition-all bg-white/50 dark:bg-slate-900/40 shadow-sm">

View File

@ -85,6 +85,7 @@
<div class="w-full sm:w-48"> <div class="w-full sm:w-48">
<x-searchable-select name="command_type" :options="[ <x-searchable-select name="command_type" :options="[
'reboot' => __('Machine Reboot'), 'reboot' => __('Machine Reboot'),
'reboot_force' => __('Machine Force Reboot'),
'reboot_card' => __('Card Reader Reboot'), 'reboot_card' => __('Card Reader Reboot'),
'checkout' => __('Remote Settlement'), 'checkout' => __('Remote Settlement'),
'lock' => __('Lock Page Lock'), 'lock' => __('Lock Page Lock'),

View File

@ -304,6 +304,7 @@
getCommandName(type) { getCommandName(type) {
const names = { const names = {
'reboot': {{ Js::from(__('Machine Reboot')) }}, 'reboot': {{ Js::from(__('Machine Reboot')) }},
'reboot_force': {{ Js::from(__('Machine Force Reboot')) }},
'reboot_card': {{ Js::from(__('Card Reader Reboot')) }}, 'reboot_card': {{ Js::from(__('Card Reader Reboot')) }},
'checkout': {{ Js::from(__('Remote Settlement')) }}, 'checkout': {{ Js::from(__('Remote Settlement')) }},
'lock': {{ Js::from(__('Lock Page Lock')) }}, 'lock': {{ Js::from(__('Lock Page Lock')) }},