star-cloud/app/Http/Controllers/Admin/UiElementController.php
terrylee e789e2b02b feat(app-ui): P1~P6 加建議尺寸提示 + crop 旗標(按鈕類不裁切保留原比例)
P 系列填實際圖檔尺寸(838x155 等)供下拉提示;controller 依 part.crop 決定是否置中裁切;提示文字改中性

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:27:05 +08:00

225 lines
7.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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']}");
// crop=true預設滿版類→ 依部位尺寸置中裁切crop=false按鈕/icon 類)→ 保留原比例不裁切
$crop = $part['crop'] ?? true;
$targetW = $crop ? ($part['width'] ?? null) : null;
$targetH = $crop ? ($part['height'] ?? null) : null;
$path = $this->storeAsWebp($request->file('file'), 'ui_elements', 80, $targetW, $targetH);
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 → 解析 UISetAPK 不需改動。
$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);
}
}