feat(app-ui): Phase3 後台 — 支援 color 型部位(色票選擇器)+ 新增 BG0整頁背景/C0底色/T0文字色
- config 加 background/bgcolor/textcolor 群組 + BG0(image)/C0/T0(color) - 內容設定頁 color 型渲染勾選套用+色票(input color);updateBundleContent 存 color_value - B014 下發 color 型已支援(不需改) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f0a6c80137
commit
a0553258f8
@ -161,21 +161,42 @@ class UiElementController extends Controller
|
|||||||
public function updateBundleContent(Request $request, UiBundle $uiBundle)
|
public function updateBundleContent(Request $request, UiBundle $uiBundle)
|
||||||
{
|
{
|
||||||
$parts = $this->activeParts();
|
$parts = $this->activeParts();
|
||||||
$input = $request->input('parts', []);
|
$input = $request->input('parts', []); // 圖片型:part_key => ui_element_id
|
||||||
|
$colorInput = $request->input('parts_color', []); // 色彩型:part_key => #RRGGBB
|
||||||
|
$colorEnabled = $request->input('parts_enabled', []); // 色彩型:part_key => "1"(有勾才套用)
|
||||||
|
|
||||||
foreach (array_keys($parts) as $key) {
|
foreach ($parts as $key => $part) {
|
||||||
|
$type = $part['type'] ?? 'image';
|
||||||
|
|
||||||
|
// 色彩型(C 底色 / T 文字色)
|
||||||
|
if ($type === 'color') {
|
||||||
|
$isOn = !empty($colorEnabled[$key]);
|
||||||
|
$color = $colorInput[$key] ?? null;
|
||||||
|
$valid = is_string($color) && preg_match('/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/', $color);
|
||||||
|
|
||||||
|
if (!$isOn || !$valid) {
|
||||||
|
$uiBundle->items()->where('part_key', $key)->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UiBundleItem::updateOrCreate(
|
||||||
|
['ui_bundle_id' => $uiBundle->id, 'part_key' => $key],
|
||||||
|
['ui_element_id' => null, 'color_value' => strtoupper($color)]
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 圖片型
|
||||||
$elementId = $input[$key] ?? null;
|
$elementId = $input[$key] ?? null;
|
||||||
$elementId = ($elementId === null || $elementId === '' || trim((string) $elementId) === '') ? null : (int) $elementId;
|
$elementId = ($elementId === null || $elementId === '' || trim((string) $elementId) === '') ? null : (int) $elementId;
|
||||||
|
|
||||||
if ($elementId === null) {
|
if ($elementId === null) {
|
||||||
// 未設定 → 移除該部位設定
|
|
||||||
$uiBundle->items()->where('part_key', $key)->delete();
|
$uiBundle->items()->where('part_key', $key)->delete();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只接受屬於該部位的元素
|
// 只接受屬於該部位的元素
|
||||||
$valid = UiElement::where('id', $elementId)->where('part_key', $key)->exists();
|
if (!UiElement::where('id', $elementId)->where('part_key', $key)->exists()) {
|
||||||
if (!$valid) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,9 @@ return [
|
|||||||
'fullscreen' => '滿版畫面(A 系列)',
|
'fullscreen' => '滿版畫面(A 系列)',
|
||||||
'transaction' => '交易結果(E 系列)',
|
'transaction' => '交易結果(E 系列)',
|
||||||
'payment' => '支付方式(P 系列)',
|
'payment' => '支付方式(P 系列)',
|
||||||
|
'background' => '整頁背景',
|
||||||
|
'bgcolor' => '背景色(C 系列)',
|
||||||
|
'textcolor' => '文字色(T 系列)',
|
||||||
],
|
],
|
||||||
|
|
||||||
'parts' => [
|
'parts' => [
|
||||||
@ -44,6 +47,15 @@ return [
|
|||||||
'P5' => ['label' => 'P5 - 現金支付', 'group' => 'payment', 'type' => 'image', 'width' => 846, 'height' => 266, 'crop' => false, 'active' => true],
|
'P5' => ['label' => 'P5 - 現金支付', 'group' => 'payment', 'type' => 'image', 'width' => 846, 'height' => 266, 'crop' => false, 'active' => true],
|
||||||
'P6' => ['label' => 'P6 - TapPay 掃碼', 'group' => 'payment', 'type' => 'image', 'width' => 579, 'height' => 282, 'crop' => false, 'active' => true],
|
'P6' => ['label' => 'P6 - TapPay 掃碼', 'group' => 'payment', 'type' => 'image', 'width' => 579, 'height' => 282, 'crop' => false, 'active' => true],
|
||||||
|
|
||||||
|
// === 整頁背景(image,滿版裁切)===
|
||||||
|
'BG0' => ['label' => 'BG0 - 整頁背景(待機/銷售頁)', 'group' => 'background', 'type' => 'image', 'width' => 1080, 'height' => 1920, 'active' => true],
|
||||||
|
|
||||||
|
// === C 系列:背景色(color)===
|
||||||
|
'C0' => ['label' => 'C0 - 機台資訊列底色', 'group' => 'bgcolor', 'type' => 'color', 'active' => true],
|
||||||
|
|
||||||
|
// === T 系列:文字色(color)===
|
||||||
|
'T0' => ['label' => 'T0 - 機台資訊列文字色', 'group' => 'textcolor', 'type' => 'color', 'active' => true],
|
||||||
|
|
||||||
// --- 以下為舊後台既有部位,Phase 2+ 逐步啟用(active=false 時後台不顯示、不下發)---
|
// --- 以下為舊後台既有部位,Phase 2+ 逐步啟用(active=false 時後台不顯示、不下發)---
|
||||||
'A4' => ['label' => 'A4 - 對話框標頭', 'group' => 'fullscreen', 'type' => 'image', 'width' => 1080, 'height' => 192, 'active' => false],
|
'A4' => ['label' => 'A4 - 對話框標頭', 'group' => 'fullscreen', 'type' => 'image', 'width' => 1080, 'height' => 192, 'active' => false],
|
||||||
'A7' => ['label' => 'A7 - 購買頁廣告底圖', 'group' => 'fullscreen', 'type' => 'image', 'width' => 1080, 'height' => 620, 'active' => false],
|
'A7' => ['label' => 'A7 - 購買頁廣告底圖', 'group' => 'fullscreen', 'type' => 'image', 'width' => 1080, 'height' => 620, 'active' => false],
|
||||||
|
|||||||
@ -2357,6 +2357,7 @@
|
|||||||
"Content Settings": "Content Settings",
|
"Content Settings": "Content Settings",
|
||||||
"Not set": "Not set",
|
"Not set": "Not set",
|
||||||
"No image selected": "No image selected",
|
"No image selected": "No image selected",
|
||||||
|
"Apply": "Apply",
|
||||||
"Current Bundle": "Current Bundle",
|
"Current Bundle": "Current Bundle",
|
||||||
"Bind UI Bundle": "Bind UI Bundle",
|
"Bind UI Bundle": "Bind UI Bundle",
|
||||||
"Image will be center-cropped to the category size.": "Numbers show the recommended size per category (full-screen types are center-cropped; button types keep their aspect ratio).",
|
"Image will be center-cropped to the category size.": "Numbers show the recommended size per category (full-screen types are center-cropped; button types keep their aspect ratio).",
|
||||||
|
|||||||
@ -2358,6 +2358,7 @@
|
|||||||
"Content Settings": "內容設定",
|
"Content Settings": "內容設定",
|
||||||
"Not set": "未設定",
|
"Not set": "未設定",
|
||||||
"No image selected": "尚未選擇圖片",
|
"No image selected": "尚未選擇圖片",
|
||||||
|
"Apply": "套用",
|
||||||
"Current Bundle": "當前組合",
|
"Current Bundle": "當前組合",
|
||||||
"Bind UI Bundle": "綁定 UI 組合",
|
"Bind UI Bundle": "綁定 UI 組合",
|
||||||
"Image will be center-cropped to the category size.": "括號內為各類別建議上傳尺寸(滿版類會置中裁切,按鈕類保留原比例)。",
|
"Image will be center-cropped to the category size.": "括號內為各類別建議上傳尺寸(滿版類會置中裁切,按鈕類保留原比例)。",
|
||||||
|
|||||||
@ -34,30 +34,50 @@
|
|||||||
<h4 class="text-base font-semibold text-gray-800 dark:text-gray-200 mb-3">{{ $groupDefs[$groupKey] ?? $groupKey }}</h4>
|
<h4 class="text-base font-semibold text-gray-800 dark:text-gray-200 mb-3">{{ $groupDefs[$groupKey] ?? $groupKey }}</h4>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||||
@foreach($grouped[$groupKey] as $key => $part)
|
@foreach($grouped[$groupKey] as $key => $part)
|
||||||
@php
|
@php $item = $itemsByPart->get($key); $partType = $part['type'] ?? 'image'; @endphp
|
||||||
$currentId = optional($itemsByPart->get($key))->ui_element_id;
|
|
||||||
$currentThumb = optional(optional($itemsByPart->get($key))->element)->image_url ?? '';
|
@if($partType === 'color')
|
||||||
@endphp
|
{{-- 色彩型(C 底色 / T 文字色):勾選套用 + 色票 --}}
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-100 dark:border-gray-700 p-4" x-data="{ thumb: @js($currentThumb) }">
|
@php $currentColor = $item->color_value ?? ''; @endphp
|
||||||
<label class="block text-sm font-semibold text-gray-800 dark:text-gray-100 mb-2">
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-100 dark:border-gray-700 p-4"
|
||||||
{{ $part['label'] }}
|
x-data="{ enabled: {{ $currentColor ? 'true' : 'false' }}, color: '{{ $currentColor ?: '#000000' }}' }">
|
||||||
@if($part['width'])<span class="text-xs font-normal text-gray-400">({{ $part['width'] }}x{{ $part['height'] }})</span>@endif
|
<label class="block text-sm font-semibold text-gray-800 dark:text-gray-100 mb-2">{{ $part['label'] }}</label>
|
||||||
</label>
|
<label class="inline-flex items-center gap-2 mb-3 text-sm text-gray-600 dark:text-gray-300">
|
||||||
<select name="parts[{{ $key }}]"
|
<input type="checkbox" name="parts_enabled[{{ $key }}]" value="1" x-model="enabled" class="rounded text-cyan-600 focus:ring-cyan-500">
|
||||||
@change="thumb = ($event.target.selectedOptions[0]?.dataset.thumb) || ''"
|
{{ __('Apply') }}
|
||||||
class="w-full px-3 py-2 bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 border rounded-md text-gray-900 dark:text-gray-200 text-sm focus:ring-2 focus:ring-cyan-500">
|
</label>
|
||||||
<option value="">{{ __('Not set') }}</option>
|
<div class="flex items-center gap-3" :class="enabled ? '' : 'opacity-40 pointer-events-none'">
|
||||||
@foreach($elementsByPart[$key] as $element)
|
<input type="color" name="parts_color[{{ $key }}]" x-model="color" class="h-10 w-14 rounded border border-gray-300 dark:border-gray-600 bg-transparent cursor-pointer">
|
||||||
<option value="{{ $element->id }}" data-thumb="{{ $element->image_url }}" {{ (string)$currentId === (string)$element->id ? 'selected' : '' }}>
|
<span class="text-sm font-mono text-gray-500 dark:text-gray-400" x-text="color.toUpperCase()"></span>
|
||||||
{{ $element->name }}
|
</div>
|
||||||
</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
<div class="mt-3">
|
|
||||||
<img x-show="thumb" x-cloak :src="thumb" class="h-28 w-auto max-w-full object-contain rounded border border-gray-200 dark:border-gray-700 bg-gray-50">
|
|
||||||
<p x-show="!thumb" x-cloak class="text-xs text-gray-400">{{ __('No image selected') }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@else
|
||||||
|
{{-- 圖片型:選對應類別的 UI 元素 --}}
|
||||||
|
@php
|
||||||
|
$currentId = optional($item)->ui_element_id;
|
||||||
|
$currentThumb = optional(optional($item)->element)->image_url ?? '';
|
||||||
|
@endphp
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-100 dark:border-gray-700 p-4" x-data="{ thumb: @js($currentThumb) }">
|
||||||
|
<label class="block text-sm font-semibold text-gray-800 dark:text-gray-100 mb-2">
|
||||||
|
{{ $part['label'] }}
|
||||||
|
@if($part['width'])<span class="text-xs font-normal text-gray-400">({{ $part['width'] }}x{{ $part['height'] }})</span>@endif
|
||||||
|
</label>
|
||||||
|
<select name="parts[{{ $key }}]"
|
||||||
|
@change="thumb = ($event.target.selectedOptions[0]?.dataset.thumb) || ''"
|
||||||
|
class="w-full px-3 py-2 bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 border rounded-md text-gray-900 dark:text-gray-200 text-sm focus:ring-2 focus:ring-cyan-500">
|
||||||
|
<option value="">{{ __('Not set') }}</option>
|
||||||
|
@foreach($elementsByPart[$key] as $element)
|
||||||
|
<option value="{{ $element->id }}" data-thumb="{{ $element->image_url }}" {{ (string)$currentId === (string)$element->id ? 'selected' : '' }}>
|
||||||
|
{{ $element->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<div class="mt-3">
|
||||||
|
<img x-show="thumb" x-cloak :src="thumb" class="h-28 w-auto max-w-full object-contain rounded border border-gray-200 dark:border-gray-700 bg-gray-50">
|
||||||
|
<p x-show="!thumb" x-cloak class="text-xs text-gray-400">{{ __('No image selected') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user