沿用 app 既有 update_settings→downloadSetting 處理(APK 不需改);推送失敗(離線)則下次同步套用 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
227 lines
7.7 KiB
PHP
227 lines
7.7 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Admin;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Machine\Machine;
|
||
use App\Models\System\UiBundle;
|
||
use App\Models\System\UiBundleItem;
|
||
use App\Models\System\UiElement;
|
||
use App\Traits\ImageHandler;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Storage;
|
||
|
||
class UiElementController extends Controller
|
||
{
|
||
use ImageHandler;
|
||
|
||
/**
|
||
* 本階段啟用的部位(config/ui_parts.php 內 active=true)。
|
||
*/
|
||
private function activeParts(): array
|
||
{
|
||
return array_filter(config('ui_parts.parts', []), fn($p) => $p['active'] ?? false);
|
||
}
|
||
|
||
// ==================================================================
|
||
// 頁籤一:UI 元素列表(素材庫)
|
||
// ==================================================================
|
||
|
||
public function uiElements()
|
||
{
|
||
$elements = UiElement::orderByDesc('id')->get();
|
||
|
||
return view('admin.app.ui-elements.index', [
|
||
'elements' => $elements,
|
||
'parts' => $this->activeParts(),
|
||
]);
|
||
}
|
||
|
||
public function storeElement(Request $request)
|
||
{
|
||
$activeKeys = array_keys($this->activeParts());
|
||
|
||
$validated = $request->validate([
|
||
'name' => 'required|string|max:255',
|
||
'part_key' => 'required|string|in:' . implode(',', $activeKeys),
|
||
'file' => 'required|image|mimes:jpeg,png,jpg,gif,webp|max:10240',
|
||
]);
|
||
|
||
$part = config("ui_parts.parts.{$validated['part_key']}");
|
||
|
||
// 依部位建議尺寸做居中裁切,存為 webp
|
||
$path = $this->storeAsWebp(
|
||
$request->file('file'),
|
||
'ui_elements',
|
||
80,
|
||
$part['width'] ?? null,
|
||
$part['height'] ?? null
|
||
);
|
||
|
||
UiElement::create([
|
||
'part_key' => $validated['part_key'],
|
||
'name' => $validated['name'],
|
||
'type' => 'image',
|
||
'image_url' => Storage::disk('public')->url($path),
|
||
'is_active' => true,
|
||
]);
|
||
|
||
return redirect()->route('admin.app.ui-elements')->with('success', __('UI element created'));
|
||
}
|
||
|
||
public function destroyElement(UiElement $uiElement)
|
||
{
|
||
// 刪除實體檔(以 URL 反推相對路徑)
|
||
if ($uiElement->image_url) {
|
||
$relative = str_replace(Storage::disk('public')->url(''), '', $uiElement->image_url);
|
||
Storage::disk('public')->delete($relative);
|
||
}
|
||
|
||
$uiElement->delete();
|
||
|
||
return redirect()->route('admin.app.ui-elements')->with('success', __('UI element deleted'));
|
||
}
|
||
|
||
// ==================================================================
|
||
// 頁籤二:UI 組合編輯
|
||
// ==================================================================
|
||
|
||
public function bundles()
|
||
{
|
||
$bundles = UiBundle::orderBy('id')->get();
|
||
|
||
return view('admin.app.ui-bundles.index', compact('bundles'));
|
||
}
|
||
|
||
public function storeBundle(Request $request)
|
||
{
|
||
$validated = $request->validate(['name' => 'required|string|max:255']);
|
||
UiBundle::create(['name' => $validated['name']]);
|
||
|
||
return redirect()->route('admin.app.ui-bundles')->with('success', __('UI bundle created'));
|
||
}
|
||
|
||
public function updateBundle(Request $request, UiBundle $uiBundle)
|
||
{
|
||
$validated = $request->validate(['name' => 'required|string|max:255']);
|
||
$uiBundle->update(['name' => $validated['name']]);
|
||
|
||
return redirect()->route('admin.app.ui-bundles')->with('success', __('Saved'));
|
||
}
|
||
|
||
public function destroyBundle(UiBundle $uiBundle)
|
||
{
|
||
$uiBundle->delete();
|
||
|
||
return redirect()->route('admin.app.ui-bundles')->with('success', __('UI bundle deleted'));
|
||
}
|
||
|
||
/**
|
||
* 組合內容設定頁:每個部位一個下拉,選對應類別的 UI 元素。
|
||
*/
|
||
public function editBundleContent(UiBundle $uiBundle)
|
||
{
|
||
$parts = $this->activeParts();
|
||
|
||
// 每個部位可選的元素(依 part_key 過濾)
|
||
$elementsByPart = [];
|
||
foreach (array_keys($parts) as $key) {
|
||
$elementsByPart[$key] = UiElement::active()->where('part_key', $key)->orderBy('name')->get();
|
||
}
|
||
|
||
return view('admin.app.ui-bundles.content', [
|
||
'bundle' => $uiBundle,
|
||
'parts' => $parts,
|
||
'elementsByPart' => $elementsByPart,
|
||
'itemsByPart' => $uiBundle->itemsByPart(),
|
||
]);
|
||
}
|
||
|
||
public function updateBundleContent(Request $request, UiBundle $uiBundle)
|
||
{
|
||
$parts = $this->activeParts();
|
||
$input = $request->input('parts', []);
|
||
|
||
foreach (array_keys($parts) as $key) {
|
||
$elementId = $input[$key] ?? null;
|
||
$elementId = ($elementId === null || $elementId === '' || trim((string) $elementId) === '') ? null : (int) $elementId;
|
||
|
||
if ($elementId === null) {
|
||
// 未設定 → 移除該部位設定
|
||
$uiBundle->items()->where('part_key', $key)->delete();
|
||
continue;
|
||
}
|
||
|
||
// 只接受屬於該部位的元素
|
||
$valid = UiElement::where('id', $elementId)->where('part_key', $key)->exists();
|
||
if (!$valid) {
|
||
continue;
|
||
}
|
||
|
||
UiBundleItem::updateOrCreate(
|
||
['ui_bundle_id' => $uiBundle->id, 'part_key' => $key],
|
||
['ui_element_id' => $elementId, 'color_value' => null]
|
||
);
|
||
}
|
||
|
||
return redirect()->route('admin.app.ui-bundles.content', $uiBundle)->with('success', __('Saved'));
|
||
}
|
||
|
||
// ==================================================================
|
||
// 頁籤三:機台設定(綁定 UI 組合)
|
||
// ==================================================================
|
||
|
||
public function machines(Request $request)
|
||
{
|
||
$machines = Machine::orderBy('name')->paginate(20);
|
||
$bundles = UiBundle::orderBy('name')->get();
|
||
|
||
// 現有綁定名稱對照
|
||
$bundleNames = $bundles->pluck('name', 'id');
|
||
|
||
return view('admin.app.ui-machines.index', [
|
||
'machines' => $machines,
|
||
'bundles' => $bundles,
|
||
'bundleNames' => $bundleNames,
|
||
]);
|
||
}
|
||
|
||
public function updateMachineBundle(Request $request, Machine $machine, \App\Services\Machine\MqttService $mqttService)
|
||
{
|
||
$validated = $request->validate([
|
||
'ui_bundle_id' => 'nullable|integer|exists:ui_bundles,id',
|
||
]);
|
||
|
||
$settings = $machine->settings ?? [];
|
||
if (empty($validated['ui_bundle_id'])) {
|
||
unset($settings['ui_bundle_id']);
|
||
} else {
|
||
$settings['ui_bundle_id'] = (int) $validated['ui_bundle_id'];
|
||
}
|
||
$machine->settings = $settings;
|
||
$machine->save();
|
||
|
||
// 儲存後主動推送,讓機台立即重拉設定並套用 UI 換膚。
|
||
// 沿用 app 端既有的 update_settings 處理(MqttService → downloadSetting → 解析 UISet),APK 不需改動。
|
||
$command = \App\Models\Machine\RemoteCommand::create([
|
||
'machine_id' => $machine->id,
|
||
'user_id' => auth()->id(),
|
||
'command_type' => 'update_settings',
|
||
'payload' => [],
|
||
'status' => 'pending',
|
||
'remark' => __('Sync UI bundle'),
|
||
]);
|
||
|
||
$pushed = $mqttService->pushCommand($machine->serial_no, 'update_settings', [], (string) $command->id);
|
||
if (!$pushed) {
|
||
$command->update(['status' => 'failed', 'note' => 'MQTT push failed']);
|
||
}
|
||
|
||
$message = $pushed
|
||
? __('Saved and update pushed to machine.')
|
||
: __('Saved. Machine will apply on next sync/reboot.');
|
||
|
||
return redirect()->route('admin.app.ui-machines')->with('success', $message);
|
||
}
|
||
}
|